FileList.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. <template>
  2. <div class="files-content card">
  3. <div class="content-header">
  4. <h2>📁 分享的文件</h2>
  5. <span class="file-count">{{ files.length }} 个文件</span>
  6. </div>
  7. <div class="files-list">
  8. <div
  9. v-for="file in files"
  10. :key="file.fileId"
  11. class="file-item"
  12. >
  13. <div class="file-info">
  14. <svg class="file-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
  15. <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
  16. <polyline points="13 2 13 9 20 9"></polyline>
  17. </svg>
  18. <div class="file-details">
  19. <p class="file-name">{{ file.fileName }}</p>
  20. <p class="file-size">{{ formatFileSize(file.fileSize) }}</p>
  21. </div>
  22. </div>
  23. <div class="file-actions">
  24. <!-- 预览按钮(仅图片和PDF) -->
  25. <button
  26. v-if="isPreviewable(file.mimeType)"
  27. class="btn-action btn-preview"
  28. @click="$emit('preview', file.fileId)"
  29. title="在新窗口预览"
  30. >
  31. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
  32. <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
  33. <circle cx="12" cy="12" r="3"></circle>
  34. </svg>
  35. <span>预览</span>
  36. </button>
  37. <!-- 下载按钮 -->
  38. <button
  39. class="btn-action btn-download"
  40. @click="$emit('download', file.fileId, file.fileName)"
  41. title="下载文件"
  42. >
  43. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
  44. <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
  45. <polyline points="7 10 12 15 17 10"></polyline>
  46. <line x1="12" y1="15" x2="12" y2="3"></line>
  47. </svg>
  48. <span>下载</span>
  49. </button>
  50. </div>
  51. </div>
  52. </div>
  53. <!-- 打包下载按钮 -->
  54. <button
  55. class="btn-download-all btn btn-primary btn-lg"
  56. @click="$emit('download-all')"
  57. :disabled="downloadingAll"
  58. >
  59. {{ downloadingAll ? '⏳ 正在打包...' : '📦 打包下载所有文件' }}
  60. </button>
  61. <div class="content-info">
  62. <p>创建时间:{{ formatTime(createdTime) }}</p>
  63. <p>过期时间:{{ formatTime(expirationTime) }}</p>
  64. <p>下载次数:{{ currentDownloadCount }}/{{ maxDownloadCount === -1 ? '无限制' : maxDownloadCount }}</p>
  65. </div>
  66. <!-- 删除资源按钮 -->
  67. <div class="content-actions">
  68. <button
  69. class="btn-delete"
  70. @click="$emit('delete')"
  71. title="删除此分享资源"
  72. >
  73. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor">
  74. <polyline points="3 6 5 6 21 6"></polyline>
  75. <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
  76. </svg>
  77. <span>删除资源</span>
  78. </button>
  79. </div>
  80. </div>
  81. </template>
  82. <script setup>
  83. const props = defineProps({
  84. files: {
  85. type: Array,
  86. required: true
  87. },
  88. downloadingAll: {
  89. type: Boolean,
  90. default: false
  91. },
  92. createdTime: {
  93. type: String,
  94. default: ''
  95. },
  96. expirationTime: {
  97. type: String,
  98. default: ''
  99. },
  100. currentDownloadCount: {
  101. type: Number,
  102. default: 0
  103. },
  104. maxDownloadCount: {
  105. type: Number,
  106. default: -1
  107. }
  108. })
  109. defineEmits(['download', 'download-all', 'preview', 'delete'])
  110. // 判断文件是否可预览(图片或PDF)
  111. function isPreviewable(mimeType) {
  112. if (!mimeType) return false
  113. const previewableTypes = [
  114. // 图片类型
  115. 'image/jpeg',
  116. 'image/jpg',
  117. 'image/png',
  118. 'image/gif',
  119. 'image/webp',
  120. 'image/svg+xml',
  121. 'image/bmp',
  122. 'image/x-icon',
  123. // PDF类型
  124. 'application/pdf'
  125. ]
  126. return previewableTypes.includes(mimeType)
  127. }
  128. function formatFileSize(bytes) {
  129. if (bytes === 0) return '0 B'
  130. const k = 1024
  131. const sizes = ['B', 'KB', 'MB', 'GB']
  132. const i = Math.floor(Math.log(bytes) / Math.log(k))
  133. return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
  134. }
  135. function formatTime(timeStr) {
  136. return new Date(timeStr).toLocaleString('zh-CN')
  137. }
  138. </script>
  139. <style lang="scss" scoped>
  140. @import '@/styles/variables.scss';
  141. .files-content {
  142. .content-header {
  143. display: flex;
  144. justify-content: space-between;
  145. align-items: center;
  146. margin-bottom: $spacing-lg;
  147. h2 {
  148. color: $text-secondary;
  149. }
  150. .file-count {
  151. font-size: $font-size-sm;
  152. background: $bg-tertiary;
  153. padding: $spacing-xs $spacing-sm;
  154. border-radius: $radius-full;
  155. color: $text-muted;
  156. }
  157. }
  158. }
  159. .files-list {
  160. margin-bottom: $spacing-lg;
  161. }
  162. .file-item {
  163. display: flex;
  164. justify-content: space-between;
  165. align-items: center;
  166. padding: $spacing-md;
  167. background: $bg-tertiary;
  168. border-radius: $radius-md;
  169. margin-bottom: $spacing-sm;
  170. }
  171. .file-info {
  172. display: flex;
  173. align-items: center;
  174. gap: $spacing-md;
  175. flex: 1;
  176. }
  177. .file-icon {
  178. width: 48px;
  179. height: 48px;
  180. color: $accent-color;
  181. }
  182. .file-details {
  183. flex: 1;
  184. min-width: 0; // 确保flex子项不会溢出
  185. .file-name {
  186. color: $text-primary;
  187. margin-bottom: $spacing-xs;
  188. word-break: break-word; // 长文件名自然换行
  189. overflow-wrap: break-word;
  190. white-space: normal; // 允许换行
  191. }
  192. .file-size {
  193. font-size: $font-size-sm;
  194. color: $text-muted;
  195. }
  196. }
  197. // 文件操作按钮容器
  198. .file-actions {
  199. display: flex;
  200. gap: $spacing-sm;
  201. align-items: center;
  202. flex-shrink: 0;
  203. }
  204. // 现代化按钮样式
  205. .btn-action {
  206. display: inline-flex;
  207. align-items: center;
  208. gap: $spacing-xs;
  209. padding: $spacing-sm $spacing-md;
  210. border-radius: $radius-md;
  211. border: none;
  212. font-size: $font-size-sm;
  213. font-weight: 500;
  214. cursor: pointer;
  215. transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  216. white-space: nowrap;
  217. svg {
  218. width: 18px;
  219. height: 18px;
  220. stroke-width: 2;
  221. }
  222. &:hover {
  223. transform: translateY(-1px);
  224. }
  225. &:active {
  226. transform: translateY(0);
  227. }
  228. }
  229. .btn-preview {
  230. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  231. color: white;
  232. box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
  233. &:hover {
  234. box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
  235. background: linear-gradient(135deg, #7c8efc 0%, #8a5db8 100%);
  236. }
  237. }
  238. .btn-download {
  239. background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
  240. color: white;
  241. box-shadow: 0 2px 8px rgba(17, 153, 142, 0.3);
  242. &:hover {
  243. box-shadow: 0 4px 12px rgba(17, 153, 142, 0.4);
  244. background: linear-gradient(135deg, #14b89f 0%, #4fff8d 100%);
  245. }
  246. }
  247. .btn-download-all {
  248. width: 100%;
  249. margin-bottom: $spacing-lg;
  250. }
  251. .content-info {
  252. padding-top: $spacing-lg;
  253. border-top: 1px solid $border-color;
  254. p {
  255. font-size: $font-size-sm;
  256. color: $text-muted;
  257. margin-bottom: $spacing-xs;
  258. }
  259. }
  260. // 内容操作区
  261. .content-actions {
  262. margin-top: $spacing-md;
  263. display: flex;
  264. justify-content: flex-end;
  265. }
  266. // 删除按钮
  267. .btn-delete {
  268. display: inline-flex;
  269. align-items: center;
  270. gap: $spacing-xs;
  271. padding: $spacing-sm $spacing-md;
  272. background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%); // 红色渐变
  273. color: white;
  274. border: none;
  275. border-radius: $radius-md;
  276. font-size: $font-size-sm;
  277. font-weight: 500;
  278. cursor: pointer;
  279. box-shadow: 0 2px 8px rgba(238, 90, 111, 0.3);
  280. transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  281. svg {
  282. width: 18px;
  283. height: 18px;
  284. stroke-width: 2;
  285. }
  286. &:hover {
  287. transform: translateY(-1px);
  288. box-shadow: 0 4px 12px rgba(238, 90, 111, 0.4);
  289. background: linear-gradient(135deg, #ff8787 0%, #ff6b6b 100%);
  290. }
  291. &:active {
  292. transform: translateY(0);
  293. }
  294. &:disabled {
  295. opacity: 0.6;
  296. cursor: not-allowed;
  297. transform: none !important;
  298. }
  299. }
  300. // 移动端优化
  301. @media (max-width: 768px) {
  302. .files-content {
  303. .content-header {
  304. flex-direction: column;
  305. align-items: flex-start;
  306. gap: $spacing-sm;
  307. margin-bottom: $spacing-md;
  308. h2 {
  309. font-size: $font-size-lg;
  310. }
  311. }
  312. }
  313. // 文件列表优化
  314. .file-item {
  315. flex-direction: column;
  316. align-items: stretch;
  317. gap: $spacing-sm;
  318. padding: $spacing-sm;
  319. .file-info {
  320. gap: $spacing-sm;
  321. }
  322. .file-icon {
  323. width: 36px;
  324. height: 36px;
  325. }
  326. .file-details {
  327. .file-name {
  328. font-size: $font-size-sm;
  329. }
  330. .file-size {
  331. font-size: $font-size-xs;
  332. }
  333. }
  334. .file-actions {
  335. justify-content: flex-end;
  336. gap: $spacing-xs;
  337. }
  338. }
  339. // 按钮优化
  340. .btn-action {
  341. padding: $spacing-xs $spacing-sm;
  342. font-size: $font-size-xs;
  343. svg {
  344. width: 16px;
  345. height: 16px;
  346. }
  347. span {
  348. // 在极小屏幕上隐藏文字,只显示图标
  349. @media (max-width: 480px) {
  350. display: none;
  351. }
  352. }
  353. }
  354. // 删除按钮移动端优化
  355. .btn-delete {
  356. padding: $spacing-xs $spacing-sm;
  357. font-size: $font-size-xs;
  358. svg {
  359. width: 16px;
  360. height: 16px;
  361. }
  362. span {
  363. // 在极小屏幕上隐藏文字,只显示图标
  364. @media (max-width: 480px) {
  365. display: none;
  366. }
  367. }
  368. }
  369. // 打包下载按钮优化
  370. .btn-download-all {
  371. padding: $spacing-sm $spacing-md;
  372. font-size: $font-size-base;
  373. }
  374. // 内容信息优化
  375. .content-info {
  376. p {
  377. font-size: $font-size-xs;
  378. }
  379. }
  380. }
  381. // 超小屏幕优化(<480px)
  382. @media (max-width: 480px) {
  383. .file-actions {
  384. .btn-action {
  385. padding: $spacing-xs;
  386. min-width: 36px; // 确保按钮可点击区域足够
  387. justify-content: center;
  388. span {
  389. display: none; // 只显示图标
  390. }
  391. }
  392. }
  393. }
  394. </style>