@robot-admin/naive-ui-components 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. package/README.md +257 -0
  2. package/dist/C_ActionBar-DWN-woTc.css.map +1 -0
  3. package/dist/C_ActionBar.cjs +5 -0
  4. package/dist/C_ActionBar.d.cts +2 -0
  5. package/dist/C_ActionBar.d.ts +2 -0
  6. package/dist/C_ActionBar.js +4 -0
  7. package/dist/C_ActionBar2.js +196 -0
  8. package/dist/C_ActionBar2.js.map +1 -0
  9. package/dist/C_AntV-AFKyK6hH.css.map +1 -0
  10. package/dist/C_AntV.cjs +8 -0
  11. package/dist/C_AntV.d.cts +2 -0
  12. package/dist/C_AntV.d.ts +2 -0
  13. package/dist/C_AntV.js +4 -0
  14. package/dist/C_AntV2.js +3150 -0
  15. package/dist/C_AntV2.js.map +1 -0
  16. package/dist/C_Barcode-P_EFj8dC.css.map +1 -0
  17. package/dist/C_Barcode.cjs +4 -0
  18. package/dist/C_Barcode.d.cts +2 -0
  19. package/dist/C_Barcode.d.ts +2 -0
  20. package/dist/C_Barcode.js +3 -0
  21. package/dist/C_Barcode2.js +68 -0
  22. package/dist/C_Barcode2.js.map +1 -0
  23. package/dist/C_Captcha-C-ef41xw.css.map +1 -0
  24. package/dist/C_Captcha.cjs +4 -0
  25. package/dist/C_Captcha.d.cts +2 -0
  26. package/dist/C_Captcha.d.ts +2 -0
  27. package/dist/C_Captcha.js +3 -0
  28. package/dist/C_Captcha2.js +155 -0
  29. package/dist/C_Captcha2.js.map +1 -0
  30. package/dist/C_Cascade-D9kNsjsV.css.map +1 -0
  31. package/dist/C_Cascade.cjs +4 -0
  32. package/dist/C_Cascade.d.cts +2 -0
  33. package/dist/C_Cascade.d.ts +2 -0
  34. package/dist/C_Cascade.js +3 -0
  35. package/dist/C_Cascade2.js +103 -0
  36. package/dist/C_Cascade2.js.map +1 -0
  37. package/dist/C_City-BCQ4ipiK.css.map +1 -0
  38. package/dist/C_City.cjs +4 -0
  39. package/dist/C_City.d.cts +2 -0
  40. package/dist/C_City.d.ts +2 -0
  41. package/dist/C_City.js +3 -0
  42. package/dist/C_City2.js +841 -0
  43. package/dist/C_City2.js.map +1 -0
  44. package/dist/C_Code-C9kvvEmO.css.map +1 -0
  45. package/dist/C_Code.cjs +5 -0
  46. package/dist/C_Code.d.cts +2 -0
  47. package/dist/C_Code.d.ts +2 -0
  48. package/dist/C_Code.js +4 -0
  49. package/dist/C_Code2.js +346 -0
  50. package/dist/C_Code2.js.map +1 -0
  51. package/dist/C_CollapsePanel-BUJHuYcU.css.map +1 -0
  52. package/dist/C_CollapsePanel.cjs +6 -0
  53. package/dist/C_CollapsePanel.d.cts +2 -0
  54. package/dist/C_CollapsePanel.d.ts +2 -0
  55. package/dist/C_CollapsePanel.js +4 -0
  56. package/dist/C_CollapsePanel2.js +319 -0
  57. package/dist/C_CollapsePanel2.js.map +1 -0
  58. package/dist/C_Cron-yx2Ob4Jl.css.map +1 -0
  59. package/dist/C_Cron.cjs +15 -0
  60. package/dist/C_Cron.d.cts +2 -0
  61. package/dist/C_Cron.d.ts +2 -0
  62. package/dist/C_Cron.js +4 -0
  63. package/dist/C_Cron2.js +1209 -0
  64. package/dist/C_Cron2.js.map +1 -0
  65. package/dist/C_Date.cjs +4 -0
  66. package/dist/C_Date.d.cts +2 -0
  67. package/dist/C_Date.d.ts +2 -0
  68. package/dist/C_Date.js +3 -0
  69. package/dist/C_Date2.js +219 -0
  70. package/dist/C_Date2.js.map +1 -0
  71. package/dist/C_Draggable-C483syRC.css.map +1 -0
  72. package/dist/C_Draggable.cjs +5 -0
  73. package/dist/C_Draggable.d.cts +2 -0
  74. package/dist/C_Draggable.d.ts +2 -0
  75. package/dist/C_Draggable.js +3 -0
  76. package/dist/C_Draggable2.js +295 -0
  77. package/dist/C_Draggable2.js.map +1 -0
  78. package/dist/C_Editor-Bp0SyIEw.css.map +1 -0
  79. package/dist/C_Editor.cjs +4 -0
  80. package/dist/C_Editor.d.cts +2 -0
  81. package/dist/C_Editor.d.ts +2 -0
  82. package/dist/C_Editor.js +3 -0
  83. package/dist/C_Editor2.js +160 -0
  84. package/dist/C_Editor2.js.map +1 -0
  85. package/dist/C_FilePreview-CPqvhoCy.css.map +1 -0
  86. package/dist/C_FilePreview.cjs +6 -0
  87. package/dist/C_FilePreview.d.cts +2 -0
  88. package/dist/C_FilePreview.d.ts +2 -0
  89. package/dist/C_FilePreview.js +3 -0
  90. package/dist/C_FilePreview2.js +1031 -0
  91. package/dist/C_FilePreview2.js.map +1 -0
  92. package/dist/C_Form-Jx7PY3sT.css.map +1 -0
  93. package/dist/C_Form.cjs +15 -0
  94. package/dist/C_Form.d.cts +2 -0
  95. package/dist/C_Form.d.ts +2 -0
  96. package/dist/C_Form.js +4 -0
  97. package/dist/C_Form2.js +2510 -0
  98. package/dist/C_Form2.js.map +1 -0
  99. package/dist/C_FormSearch-DvRgxlRn.css.map +1 -0
  100. package/dist/C_FormSearch.cjs +6 -0
  101. package/dist/C_FormSearch.d.cts +2 -0
  102. package/dist/C_FormSearch.d.ts +2 -0
  103. package/dist/C_FormSearch.js +3 -0
  104. package/dist/C_FormSearch2.js +356 -0
  105. package/dist/C_FormSearch2.js.map +1 -0
  106. package/dist/C_FormulaEditor-DtGkt4T_.css.map +1 -0
  107. package/dist/C_FormulaEditor.cjs +13 -0
  108. package/dist/C_FormulaEditor.d.cts +2 -0
  109. package/dist/C_FormulaEditor.d.ts +2 -0
  110. package/dist/C_FormulaEditor.js +4 -0
  111. package/dist/C_FormulaEditor2.js +1433 -0
  112. package/dist/C_FormulaEditor2.js.map +1 -0
  113. package/dist/C_FullCalendar-BF7H0YIx.css.map +1 -0
  114. package/dist/C_FullCalendar.cjs +9 -0
  115. package/dist/C_FullCalendar.d.cts +2 -0
  116. package/dist/C_FullCalendar.d.ts +2 -0
  117. package/dist/C_FullCalendar.js +3 -0
  118. package/dist/C_FullCalendar2.js +377 -0
  119. package/dist/C_FullCalendar2.js.map +1 -0
  120. package/dist/C_Guide.cjs +4 -0
  121. package/dist/C_Guide.d.cts +2 -0
  122. package/dist/C_Guide.d.ts +2 -0
  123. package/dist/C_Guide.js +3 -0
  124. package/dist/C_Guide2.js +58 -0
  125. package/dist/C_Guide2.js.map +1 -0
  126. package/dist/C_Icon.cjs +4 -0
  127. package/dist/C_Icon.d.cts +2 -0
  128. package/dist/C_Icon.d.ts +2 -0
  129. package/dist/C_Icon.js +3 -0
  130. package/dist/C_Icon2.js +286 -0
  131. package/dist/C_Icon2.js.map +1 -0
  132. package/dist/C_ImageCropper-BVJfUufl.css.map +1 -0
  133. package/dist/C_ImageCropper.cjs +6 -0
  134. package/dist/C_ImageCropper.d.cts +2 -0
  135. package/dist/C_ImageCropper.d.ts +2 -0
  136. package/dist/C_ImageCropper.js +4 -0
  137. package/dist/C_ImageCropper2.js +723 -0
  138. package/dist/C_ImageCropper2.js.map +1 -0
  139. package/dist/C_Language.cjs +4 -0
  140. package/dist/C_Language.d.cts +2 -0
  141. package/dist/C_Language.d.ts +2 -0
  142. package/dist/C_Language.js +3 -0
  143. package/dist/C_Language2.js +72 -0
  144. package/dist/C_Language2.js.map +1 -0
  145. package/dist/C_Map-DpzeuWdX.css.map +1 -0
  146. package/dist/C_Map.cjs +7 -0
  147. package/dist/C_Map.d.cts +2 -0
  148. package/dist/C_Map.d.ts +2 -0
  149. package/dist/C_Map.js +3 -0
  150. package/dist/C_Map2.js +199 -0
  151. package/dist/C_Map2.js.map +1 -0
  152. package/dist/C_Markdown-BEjxknqd.css.map +1 -0
  153. package/dist/C_Markdown.cjs +4 -0
  154. package/dist/C_Markdown.d.cts +2 -0
  155. package/dist/C_Markdown.d.ts +2 -0
  156. package/dist/C_Markdown.js +3 -0
  157. package/dist/C_Markdown2.js +186 -0
  158. package/dist/C_Markdown2.js.map +1 -0
  159. package/dist/C_NotificationCenter-0l3TY2Gn.css.map +1 -0
  160. package/dist/C_NotificationCenter.cjs +20 -0
  161. package/dist/C_NotificationCenter.d.cts +2 -0
  162. package/dist/C_NotificationCenter.d.ts +2 -0
  163. package/dist/C_NotificationCenter.js +4 -0
  164. package/dist/C_NotificationCenter2.js +1383 -0
  165. package/dist/C_NotificationCenter2.js.map +1 -0
  166. package/dist/C_Progress.cjs +4 -0
  167. package/dist/C_Progress.d.cts +2 -0
  168. package/dist/C_Progress.d.ts +2 -0
  169. package/dist/C_Progress.js +3 -0
  170. package/dist/C_Progress2.js +103 -0
  171. package/dist/C_Progress2.js.map +1 -0
  172. package/dist/C_QRCode-DbdiAIPg.css.map +1 -0
  173. package/dist/C_QRCode.cjs +5 -0
  174. package/dist/C_QRCode.d.cts +2 -0
  175. package/dist/C_QRCode.d.ts +2 -0
  176. package/dist/C_QRCode.js +3 -0
  177. package/dist/C_QRCode2.js +218 -0
  178. package/dist/C_QRCode2.js.map +1 -0
  179. package/dist/C_Signature-zhHCbra9.css.map +1 -0
  180. package/dist/C_Signature.cjs +8 -0
  181. package/dist/C_Signature.d.cts +2 -0
  182. package/dist/C_Signature.d.ts +2 -0
  183. package/dist/C_Signature.js +4 -0
  184. package/dist/C_Signature2.js +618 -0
  185. package/dist/C_Signature2.js.map +1 -0
  186. package/dist/C_SplitPane-C6sBsfKY.css.map +1 -0
  187. package/dist/C_SplitPane.cjs +6 -0
  188. package/dist/C_SplitPane.d.cts +2 -0
  189. package/dist/C_SplitPane.d.ts +2 -0
  190. package/dist/C_SplitPane.js +4 -0
  191. package/dist/C_SplitPane2.js +356 -0
  192. package/dist/C_SplitPane2.js.map +1 -0
  193. package/dist/C_Steps-CODHN5Hs.css.map +1 -0
  194. package/dist/C_Steps.cjs +4 -0
  195. package/dist/C_Steps.d.cts +2 -0
  196. package/dist/C_Steps.d.ts +2 -0
  197. package/dist/C_Steps.js +3 -0
  198. package/dist/C_Steps2.js +82 -0
  199. package/dist/C_Steps2.js.map +1 -0
  200. package/dist/C_Table-DSNsntmT.css.map +1 -0
  201. package/dist/C_Table.cjs +19 -0
  202. package/dist/C_Table.d.cts +2 -0
  203. package/dist/C_Table.d.ts +2 -0
  204. package/dist/C_Table.js +5 -0
  205. package/dist/C_Table2.js +3009 -0
  206. package/dist/C_Table2.js.map +1 -0
  207. package/dist/C_Theme.cjs +4 -0
  208. package/dist/C_Theme.d.cts +2 -0
  209. package/dist/C_Theme.d.ts +2 -0
  210. package/dist/C_Theme.js +3 -0
  211. package/dist/C_Theme2.js +60 -0
  212. package/dist/C_Theme2.js.map +1 -0
  213. package/dist/C_Time-BvZLYraL.css.map +1 -0
  214. package/dist/C_Time.cjs +5 -0
  215. package/dist/C_Time.d.cts +2 -0
  216. package/dist/C_Time.d.ts +2 -0
  217. package/dist/C_Time.js +3 -0
  218. package/dist/C_Time2.js +199 -0
  219. package/dist/C_Time2.js.map +1 -0
  220. package/dist/C_Tree-0GDv--jX.css.map +1 -0
  221. package/dist/C_Tree.cjs +7 -0
  222. package/dist/C_Tree.d.cts +2 -0
  223. package/dist/C_Tree.d.ts +2 -0
  224. package/dist/C_Tree.js +4 -0
  225. package/dist/C_Tree2.js +441 -0
  226. package/dist/C_Tree2.js.map +1 -0
  227. package/dist/C_Upload-BXd3YYLx.css.map +1 -0
  228. package/dist/C_Upload.cjs +12 -0
  229. package/dist/C_Upload.d.cts +2 -0
  230. package/dist/C_Upload.d.ts +2 -0
  231. package/dist/C_Upload.js +4 -0
  232. package/dist/C_Upload2.js +1388 -0
  233. package/dist/C_Upload2.js.map +1 -0
  234. package/dist/C_VideoPlayer-DYG3RL0Q.css.map +1 -0
  235. package/dist/C_VideoPlayer.cjs +23 -0
  236. package/dist/C_VideoPlayer.d.cts +2 -0
  237. package/dist/C_VideoPlayer.d.ts +2 -0
  238. package/dist/C_VideoPlayer.js +3 -0
  239. package/dist/C_VideoPlayer2.js +1932 -0
  240. package/dist/C_VideoPlayer2.js.map +1 -0
  241. package/dist/C_VtableGantt-fhItIiHE.css.map +1 -0
  242. package/dist/C_VtableGantt.cjs +6 -0
  243. package/dist/C_VtableGantt.d.cts +2 -0
  244. package/dist/C_VtableGantt.d.ts +2 -0
  245. package/dist/C_VtableGantt.js +4 -0
  246. package/dist/C_VtableGantt2.js +873 -0
  247. package/dist/C_VtableGantt2.js.map +1 -0
  248. package/dist/C_WaterFall-8sQDFXKg.css.map +1 -0
  249. package/dist/C_WaterFall.cjs +13 -0
  250. package/dist/C_WaterFall.d.cts +2 -0
  251. package/dist/C_WaterFall.d.ts +2 -0
  252. package/dist/C_WaterFall.js +3 -0
  253. package/dist/C_WaterFall2.js +365 -0
  254. package/dist/C_WaterFall2.js.map +1 -0
  255. package/dist/C_WorkFlow-J-dyIuh9.css.map +1 -0
  256. package/dist/C_WorkFlow.cjs +8 -0
  257. package/dist/C_WorkFlow.d.cts +2 -0
  258. package/dist/C_WorkFlow.d.ts +2 -0
  259. package/dist/C_WorkFlow.js +4 -0
  260. package/dist/C_WorkFlow2.js +1984 -0
  261. package/dist/C_WorkFlow2.js.map +1 -0
  262. package/dist/chunk.js +22 -0
  263. package/dist/city.js +4817 -0
  264. package/dist/city.js.map +1 -0
  265. package/dist/constants.d.ts +273 -0
  266. package/dist/constants.d.ts.map +1 -0
  267. package/dist/constants2.d.ts +178 -0
  268. package/dist/constants2.d.ts.map +1 -0
  269. package/dist/constants3.d.ts +475 -0
  270. package/dist/constants3.d.ts.map +1 -0
  271. package/dist/constants4.d.ts +430 -0
  272. package/dist/constants4.d.ts.map +1 -0
  273. package/dist/constants5.d.ts +4283 -0
  274. package/dist/constants5.d.ts.map +1 -0
  275. package/dist/data.d.ts +67 -0
  276. package/dist/data.d.ts.map +1 -0
  277. package/dist/export-helper.js +9 -0
  278. package/dist/index.cjs +409 -0
  279. package/dist/index.d.cts +96 -0
  280. package/dist/index.d.cts.map +1 -0
  281. package/dist/index.d.ts +103 -0
  282. package/dist/index.d.ts.map +1 -0
  283. package/dist/index.js +230 -0
  284. package/dist/index.js.map +1 -0
  285. package/dist/index.vue.d.ts +80 -0
  286. package/dist/index.vue.d.ts.map +1 -0
  287. package/dist/index10.vue.d.ts +72 -0
  288. package/dist/index10.vue.d.ts.map +1 -0
  289. package/dist/index11.vue.d.ts +26 -0
  290. package/dist/index11.vue.d.ts.map +1 -0
  291. package/dist/index12.vue.d.ts +81 -0
  292. package/dist/index12.vue.d.ts.map +1 -0
  293. package/dist/index13.vue.d.ts +55 -0
  294. package/dist/index13.vue.d.ts.map +1 -0
  295. package/dist/index14.vue.d.ts +33 -0
  296. package/dist/index14.vue.d.ts.map +1 -0
  297. package/dist/index15.vue.d.ts +18 -0
  298. package/dist/index15.vue.d.ts.map +1 -0
  299. package/dist/index16.vue.d.ts +662 -0
  300. package/dist/index16.vue.d.ts.map +1 -0
  301. package/dist/index2.vue.d.ts +38 -0
  302. package/dist/index2.vue.d.ts.map +1 -0
  303. package/dist/index3.vue.d.ts +45 -0
  304. package/dist/index3.vue.d.ts.map +1 -0
  305. package/dist/index4.vue.d.ts +31 -0
  306. package/dist/index4.vue.d.ts.map +1 -0
  307. package/dist/index5.vue.d.ts +35 -0
  308. package/dist/index5.vue.d.ts.map +1 -0
  309. package/dist/index6.vue.d.ts +48 -0
  310. package/dist/index6.vue.d.ts.map +1 -0
  311. package/dist/index7.vue.d.ts +56 -0
  312. package/dist/index7.vue.d.ts.map +1 -0
  313. package/dist/index8.vue.d.ts +41 -0
  314. package/dist/index8.vue.d.ts.map +1 -0
  315. package/dist/index9.vue.d.ts +30 -0
  316. package/dist/index9.vue.d.ts.map +1 -0
  317. package/dist/storage.js +31 -0
  318. package/dist/storage.js.map +1 -0
  319. package/dist/style.css +7725 -0
  320. package/dist/useCalendarEvents.d.ts +148 -0
  321. package/dist/useCalendarEvents.d.ts.map +1 -0
  322. package/dist/useCollapsePanel.d.ts +132 -0
  323. package/dist/useCollapsePanel.d.ts.map +1 -0
  324. package/dist/useCropperCore.d.ts +102 -0
  325. package/dist/useCropperCore.d.ts.map +1 -0
  326. package/dist/useDraggableLayout.d.ts +194 -0
  327. package/dist/useDraggableLayout.d.ts.map +1 -0
  328. package/dist/useDynamicFormState.d.ts +4248 -0
  329. package/dist/useDynamicFormState.d.ts.map +1 -0
  330. package/dist/useEdgeInteraction.d.ts +7614 -0
  331. package/dist/useEdgeInteraction.d.ts.map +1 -0
  332. package/dist/useFullscreen.d.ts +166 -0
  333. package/dist/useFullscreen.d.ts.map +1 -0
  334. package/dist/useInfiniteScroll.d.ts +169 -0
  335. package/dist/useInfiniteScroll.d.ts.map +1 -0
  336. package/dist/useModalEdit.d.ts +960 -0
  337. package/dist/useModalEdit.d.ts.map +1 -0
  338. package/dist/useQRCode.d.ts +87 -0
  339. package/dist/useQRCode.d.ts.map +1 -0
  340. package/dist/useSearchState.d.ts +180 -0
  341. package/dist/useSearchState.d.ts.map +1 -0
  342. package/dist/useSignatureHistory.d.ts +189 -0
  343. package/dist/useSignatureHistory.d.ts.map +1 -0
  344. package/dist/useSplitResize.d.ts +158 -0
  345. package/dist/useSplitResize.d.ts.map +1 -0
  346. package/dist/useTimeSelection.d.ts +105 -0
  347. package/dist/useTimeSelection.d.ts.map +1 -0
  348. package/dist/useTreeOperations.d.ts +183 -0
  349. package/dist/useTreeOperations.d.ts.map +1 -0
  350. package/dist/useWorkflowValidation.d.ts +1052 -0
  351. package/dist/useWorkflowValidation.d.ts.map +1 -0
  352. package/package.json +342 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"C_Upload2.js","names":["disabled","accept","multiple","directory","pasteable","tip","fileList","showThumbnail","hashProgress"],"sources":["../src/components/C_Upload/constants.ts","../src/components/C_Upload/composables/useFileHash.ts","../src/components/C_Upload/composables/useChunkUpload.ts","../src/components/C_Upload/composables/useUploadQueue.ts","../src/components/C_Upload/composables/useUploadCore.ts","../src/components/C_Upload/composables/useDragDrop.ts","../src/components/C_Upload/components/UploadArea.vue","../src/components/C_Upload/components/UploadArea.vue","../src/components/C_Upload/components/UploadArea.vue","../src/components/C_Upload/components/FileList.vue","../src/components/C_Upload/components/FileList.vue","../src/components/C_Upload/components/FileList.vue","../src/components/C_Upload/components/ImagePreview.vue","../src/components/C_Upload/components/ImagePreview.vue","../src/components/C_Upload/components/ImagePreview.vue","../src/components/C_Upload/index.vue","../src/components/C_Upload/index.vue","../src/components/C_Upload/index.vue"],"sourcesContent":["/**\r\n * C_Upload 组件常量\r\n */\r\n\r\n/** 默认分片大小 2MB */\r\nexport const DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024;\r\n\r\n/** 默认最大并发数 */\r\nexport const DEFAULT_CONCURRENCY = 3;\r\n\r\n/** 默认最大文件数 */\r\nexport const DEFAULT_MAX_COUNT = 10;\r\n\r\n/** 大文件阈值(超过此大小自动启用分片) */\r\nexport const LARGE_FILE_THRESHOLD = 10 * 1024 * 1024;\r\n\r\n/** 状态文案映射 */\r\nexport const STATUS_TEXT: Record<string, string> = {\r\n pending: \"等待上传\",\r\n hashing: \"计算校验…\",\r\n uploading: \"上传中…\",\r\n success: \"上传成功\",\r\n error: \"上传失败\",\r\n paused: \"已暂停\",\r\n instant: \"秒传成功\",\r\n};\r\n\r\n/** 状态颜色映射(Naive UI NTag type) */\r\nexport const STATUS_TYPE: Record<string, string> = {\r\n pending: \"default\",\r\n hashing: \"info\",\r\n uploading: \"info\",\r\n success: \"success\",\r\n error: \"error\",\r\n paused: \"warning\",\r\n instant: \"success\",\r\n};\r\n\r\n/** 文件图标映射 */\r\nexport const FILE_ICON_MAP: Record<string, string> = {\r\n \"image/\": \"mdi:file-image-outline\",\r\n \"video/\": \"mdi:file-video-outline\",\r\n \"audio/\": \"mdi:file-music-outline\",\r\n \"application/pdf\": \"mdi:file-pdf-box\",\r\n \"application/zip\": \"mdi:folder-zip-outline\",\r\n \"application/x-rar\": \"mdi:folder-zip-outline\",\r\n \"application/vnd.ms-excel\": \"mdi:file-excel-outline\",\r\n \"application/vnd.openxmlformats-officedocument.spreadsheetml\":\r\n \"mdi:file-excel-outline\",\r\n \"application/msword\": \"mdi:file-word-outline\",\r\n \"application/vnd.openxmlformats-officedocument.wordprocessingml\":\r\n \"mdi:file-word-outline\",\r\n \"text/\": \"mdi:file-document-outline\",\r\n default: \"mdi:file-outline\",\r\n};\r\n\r\n/** 根据 MIME 类型获取图标 */\r\nexport function getFileIcon(type: string): string {\r\n for (const [key, icon] of Object.entries(FILE_ICON_MAP)) {\r\n if (key !== \"default\" && type.startsWith(key)) return icon;\r\n }\r\n return FILE_ICON_MAP.default;\r\n}\r\n\r\n/** 格式化文件大小 */\r\nexport function formatFileSize(bytes: number): string {\r\n if (bytes === 0) return \"0 B\";\r\n const units = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\r\n const i = Math.floor(Math.log(bytes) / Math.log(1024));\r\n return `${(bytes / 1024 ** i).toFixed(i > 0 ? 1 : 0)} ${units[i]}`;\r\n}\r\n","/**\r\n * 文件哈希计算(Web Worker + spark-md5)\r\n */\r\n\r\nimport { ref, readonly, type Ref } from \"vue\";\r\n\r\n/**\r\n * 文件哈希计算\r\n *\r\n * 使用 spark-md5 在 Web Worker 中对文件分片计算 MD5 hash,\r\n * 不阻塞主线程,支持进度回调。\r\n *\r\n * @param chunkSize 分片大小(bytes),也用于哈希分块读取\r\n */\r\nexport function useFileHash(chunkSize: Ref<number>) {\r\n const hashing = ref(false);\r\n const hashProgress = ref(0);\r\n\r\n /**\r\n * 计算文件 hash\r\n * @param file 原生 File 对象\r\n * @returns MD5 hash 字符串\r\n */\r\n async function calculateHash(file: File): Promise<string> {\r\n hashing.value = true;\r\n hashProgress.value = 0;\r\n\r\n return new Promise((resolve, reject) => {\r\n // 使用 inline Web Worker(避免额外文件)\r\n const workerCode = `\r\n self.importScripts('https://cdn.jsdelivr.net/npm/spark-md5@3.0.2/spark-md5.min.js');\r\n\r\n self.onmessage = function(e) {\r\n const { chunks } = e.data;\r\n const spark = new self.SparkMD5.ArrayBuffer();\r\n let current = 0;\r\n\r\n function processNext() {\r\n if (current >= chunks.length) {\r\n self.postMessage({ type: 'done', hash: spark.end() });\r\n return;\r\n }\r\n const reader = new FileReaderSync();\r\n const buffer = reader.readAsArrayBuffer(chunks[current]);\r\n spark.append(buffer);\r\n current++;\r\n self.postMessage({ type: 'progress', percent: Math.round((current / chunks.length) * 100) });\r\n processNext();\r\n }\r\n\r\n processNext();\r\n };\r\n `;\r\n\r\n // 将文件分片\r\n const size = chunkSize.value;\r\n const chunks: Blob[] = [];\r\n let offset = 0;\r\n while (offset < file.size) {\r\n chunks.push(file.slice(offset, offset + size));\r\n offset += size;\r\n }\r\n\r\n // 如果浏览器不支持 Worker(SSR / 旧环境),走主线程降级\r\n if (typeof Worker === \"undefined\") {\r\n computeHashMainThread(file, chunks)\r\n .then(resolve)\r\n .catch(reject)\r\n .finally(() => {\r\n hashing.value = false;\r\n });\r\n return;\r\n }\r\n\r\n try {\r\n const blob = new Blob([workerCode], { type: \"application/javascript\" });\r\n const workerUrl = URL.createObjectURL(blob);\r\n const worker = new Worker(workerUrl);\r\n\r\n worker.onmessage = (e: MessageEvent) => {\r\n const { type, hash, percent } = e.data;\r\n if (type === \"progress\") {\r\n hashProgress.value = percent;\r\n } else if (type === \"done\") {\r\n hashing.value = false;\r\n worker.terminate();\r\n URL.revokeObjectURL(workerUrl);\r\n resolve(hash);\r\n }\r\n };\r\n\r\n worker.onerror = () => {\r\n hashing.value = false;\r\n worker.terminate();\r\n URL.revokeObjectURL(workerUrl);\r\n // 降级到主线程\r\n computeHashMainThread(file, chunks).then(resolve).catch(reject);\r\n };\r\n\r\n worker.postMessage({ chunks });\r\n } catch {\r\n // Worker 创建失败,降级\r\n computeHashMainThread(file, chunks)\r\n .then(resolve)\r\n .catch(reject)\r\n .finally(() => {\r\n hashing.value = false;\r\n });\r\n }\r\n });\r\n }\r\n\r\n /** 主线程降级计算 */\r\n async function computeHashMainThread(\r\n _file: File,\r\n chunks: Blob[],\r\n ): Promise<string> {\r\n const SparkMD5 = (await import(\"spark-md5\" as string)).default;\r\n const spark = new SparkMD5.ArrayBuffer();\r\n\r\n for (let i = 0; i < chunks.length; i++) {\r\n const buffer = await chunks[i].arrayBuffer();\r\n spark.append(buffer);\r\n hashProgress.value = Math.round(((i + 1) / chunks.length) * 100);\r\n }\r\n\r\n hashing.value = false;\r\n return spark.end();\r\n }\r\n\r\n return {\r\n /** 是否正在计算 hash */\r\n hashing: readonly(hashing),\r\n /** 哈希计算进度(0-100) */\r\n hashProgress: readonly(hashProgress),\r\n /** 计算文件 hash */\r\n calculateHash,\r\n };\r\n}\r\n","/**\r\n * 分片上传引擎\r\n */\r\n\r\nimport type { Ref } from \"vue\";\r\nimport type {\r\n UploadChunk,\r\n ChunkProgress,\r\n UploadRequestOptions,\r\n CustomUploadRequest,\r\n UploadedChunksQueryFn,\r\n MergeChunksFn,\r\n} from \"../types\";\r\n\r\ninterface UseChunkUploadOptions {\r\n /** 分片大小 */\r\n chunkSize: Ref<number>;\r\n /** 最大并发数 */\r\n concurrency: Ref<number>;\r\n /** 上传地址 */\r\n action: Ref<string>;\r\n /** 请求头 */\r\n headers: Ref<Record<string, string>>;\r\n /** 附加字段 */\r\n data: Ref<Record<string, any>>;\r\n /** 自定义上传函数 */\r\n customRequest?: Ref<CustomUploadRequest | undefined>;\r\n /** 已上传分片查询 */\r\n uploadedChunksQuery?: Ref<UploadedChunksQueryFn | undefined>;\r\n /** 分片合并函数 */\r\n mergeChunks?: Ref<MergeChunksFn | undefined>;\r\n}\r\n\r\n/**\r\n * 分片上传引擎\r\n *\r\n * 处理大文件分片切割、并发上传、断点续传、分片合并。\r\n */\r\nexport function useChunkUpload(options: UseChunkUploadOptions) {\r\n /** 正在进行的上传中止控制器映射 uid → abort[] */\r\n const abortMap = new Map<string, (() => void)[]>();\r\n\r\n /**\r\n * 将文件切割为分片\r\n */\r\n function createChunks(file: File): UploadChunk[] {\r\n const size = options.chunkSize.value;\r\n const chunks: UploadChunk[] = [];\r\n let index = 0;\r\n let offset = 0;\r\n\r\n while (offset < file.size) {\r\n const end = Math.min(offset + size, file.size);\r\n chunks.push({\r\n index,\r\n blob: file.slice(offset, end),\r\n size: end - offset,\r\n uploaded: false,\r\n });\r\n offset = end;\r\n index++;\r\n }\r\n\r\n return chunks;\r\n }\r\n\r\n /**\r\n * 查询已上传的分片(断点续传)\r\n */\r\n async function queryExistingChunks(hash: string, chunks: UploadChunk[]) {\r\n if (!options.uploadedChunksQuery?.value) return;\r\n\r\n try {\r\n const uploaded = await options.uploadedChunksQuery.value(hash);\r\n for (const idx of uploaded) {\r\n const chunk = chunks[idx];\r\n if (chunk) chunk.uploaded = true;\r\n }\r\n } catch {\r\n // 查询失败,全部重传\r\n }\r\n }\r\n\r\n /**\r\n * 上传单个分片\r\n */\r\n function uploadSingleChunk(ctx: {\r\n chunk: UploadChunk;\r\n hash: string;\r\n file: File;\r\n totalChunks: number;\r\n chunks: UploadChunk[];\r\n totalBytes: number;\r\n abortControllers: (() => void)[];\r\n onProgress: (progress: ChunkProgress) => void;\r\n setUploadedBytes: (bytes: number) => void;\r\n setError: () => void;\r\n }): Promise<void> {\r\n return new Promise<void>((resolve, reject) => {\r\n const requestOptions: UploadRequestOptions = {\r\n action: options.action.value,\r\n headers: options.headers.value,\r\n data: {\r\n ...options.data.value,\r\n hash: ctx.hash,\r\n chunkIndex: ctx.chunk.index,\r\n totalChunks: ctx.totalChunks,\r\n filename: ctx.file.name,\r\n },\r\n file: ctx.chunk.blob,\r\n filename: ctx.file.name,\r\n hash: ctx.hash,\r\n chunkIndex: ctx.chunk.index,\r\n totalChunks: ctx.totalChunks,\r\n onProgress: () => {\r\n /* 分片内部进度可选 */\r\n },\r\n onSuccess: () => {\r\n ctx.chunk.uploaded = true;\r\n ctx.setUploadedBytes(ctx.chunk.size);\r\n ctx.onProgress({\r\n uploadedChunks: ctx.chunks.filter((c) => c.uploaded).length,\r\n totalChunks: ctx.totalChunks,\r\n uploadedBytes: ctx.chunks\r\n .filter((c) => c.uploaded)\r\n .reduce((sum, c) => sum + c.size, 0),\r\n totalBytes: ctx.totalBytes,\r\n });\r\n resolve();\r\n },\r\n onError: (err) => {\r\n ctx.setError();\r\n reject(err);\r\n },\r\n };\r\n\r\n const req = options.customRequest?.value\r\n ? options.customRequest.value(requestOptions)\r\n : defaultUploadRequest(requestOptions);\r\n ctx.abortControllers.push(req.abort);\r\n });\r\n }\r\n\r\n /**\r\n * 执行分片上传\r\n */\r\n async function uploadChunks(params: {\r\n uid: string;\r\n file: File;\r\n hash: string;\r\n onProgress: (progress: ChunkProgress) => void;\r\n onSuccess: (response: any) => void;\r\n onError: (error: Error) => void;\r\n isPaused: () => boolean;\r\n }) {\r\n const { uid, file, hash, onProgress, onSuccess, onError, isPaused } =\r\n params;\r\n const chunks = createChunks(file);\r\n const totalChunks = chunks.length;\r\n const totalBytes = file.size;\r\n let uploadedBytes = 0;\r\n\r\n // 查询已上传分片(断点续传)\r\n await queryExistingChunks(hash, chunks);\r\n uploadedBytes = chunks\r\n .filter((c) => c.uploaded)\r\n .reduce((sum, c) => sum + c.size, 0);\r\n\r\n // 初始进度\r\n onProgress({\r\n uploadedChunks: chunks.filter((c) => c.uploaded).length,\r\n totalChunks,\r\n uploadedBytes,\r\n totalBytes,\r\n });\r\n\r\n // 过滤待上传分片\r\n const pendingChunks = chunks.filter((c) => !c.uploaded);\r\n\r\n if (pendingChunks.length === 0) {\r\n // 全部已上传,直接合并\r\n await mergeAndFinish(hash, file.name, totalChunks, onSuccess, onError);\r\n return;\r\n }\r\n\r\n // 并发控制上传\r\n const abortControllers: (() => void)[] = [];\r\n abortMap.set(uid, abortControllers);\r\n\r\n const concurrency = options.concurrency.value;\r\n let current = 0;\r\n let hasError = false;\r\n\r\n /** 上传下一个分片 */\r\n async function uploadNext(): Promise<void> {\r\n while (current < pendingChunks.length && !hasError && !isPaused()) {\r\n const chunk = pendingChunks[current++];\r\n\r\n await uploadSingleChunk({\r\n chunk,\r\n hash,\r\n file,\r\n totalChunks,\r\n chunks,\r\n totalBytes,\r\n abortControllers,\r\n onProgress,\r\n setUploadedBytes: (bytes: number) => {\r\n uploadedBytes += bytes;\r\n },\r\n setError: () => {\r\n hasError = true;\r\n },\r\n });\r\n }\r\n }\r\n\r\n // 启动并发池\r\n const pool = Array.from(\r\n { length: Math.min(concurrency, pendingChunks.length) },\r\n () => uploadNext(),\r\n );\r\n\r\n try {\r\n await Promise.all(pool);\r\n\r\n if (!hasError && !isPaused()) {\r\n // 全部分片完成 → 合并\r\n await mergeAndFinish(hash, file.name, totalChunks, onSuccess, onError);\r\n }\r\n } catch (err) {\r\n onError(err instanceof Error ? err : new Error(String(err)));\r\n } finally {\r\n abortMap.delete(uid);\r\n }\r\n }\r\n\r\n /** 合并分片 */\r\n async function mergeAndFinish(\r\n hash: string,\r\n filename: string,\r\n totalChunks: number,\r\n onSuccess: (response: any) => void,\r\n onError: (error: Error) => void,\r\n ) {\r\n if (options.mergeChunks?.value) {\r\n try {\r\n const result = await options.mergeChunks.value(\r\n hash,\r\n filename,\r\n totalChunks,\r\n );\r\n onSuccess(result);\r\n } catch (err) {\r\n onError(err instanceof Error ? err : new Error(\"分片合并失败\"));\r\n }\r\n } else {\r\n onSuccess({ message: \"分片上传完成(未配置合并函数)\" });\r\n }\r\n }\r\n\r\n /** 中止指定文件的分片上传 */\r\n function abortUpload(uid: string) {\r\n const controllers = abortMap.get(uid);\r\n controllers?.forEach((abort) => abort());\r\n abortMap.delete(uid);\r\n }\r\n\r\n /** 中止所有 */\r\n function abortAll() {\r\n abortMap.forEach((controllers) => {\r\n controllers.forEach((abort) => abort());\r\n });\r\n abortMap.clear();\r\n }\r\n\r\n return {\r\n createChunks,\r\n uploadChunks,\r\n abortUpload,\r\n abortAll,\r\n };\r\n}\r\n\r\n/** 默认 XMLHttpRequest 上传实现 */\r\nfunction defaultUploadRequest(options: UploadRequestOptions) {\r\n const xhr = new XMLHttpRequest();\r\n\r\n xhr.open(\"POST\", options.action);\r\n\r\n // 设置请求头\r\n if (options.headers) {\r\n Object.entries(options.headers).forEach(([key, value]) => {\r\n xhr.setRequestHeader(key, value);\r\n });\r\n }\r\n\r\n // 上传进度\r\n xhr.upload.addEventListener(\"progress\", (e) => {\r\n if (e.lengthComputable) {\r\n options.onProgress?.(Math.round((e.loaded / e.total) * 100));\r\n }\r\n });\r\n\r\n xhr.addEventListener(\"load\", () => {\r\n if (xhr.status >= 200 && xhr.status < 300) {\r\n let response: any;\r\n try {\r\n response = JSON.parse(xhr.responseText);\r\n } catch {\r\n response = xhr.responseText;\r\n }\r\n options.onSuccess?.(response);\r\n } else {\r\n options.onError?.(new Error(`上传失败: HTTP ${xhr.status}`));\r\n }\r\n });\r\n\r\n xhr.addEventListener(\"error\", () => {\r\n options.onError?.(new Error(\"网络错误\"));\r\n });\r\n\r\n const formData = new FormData();\r\n formData.append(\"file\", options.file, options.filename);\r\n\r\n if (options.data) {\r\n Object.entries(options.data).forEach(([key, value]) => {\r\n formData.append(key, String(value));\r\n });\r\n }\r\n\r\n xhr.send(formData);\r\n\r\n return {\r\n abort: () => xhr.abort(),\r\n };\r\n}\r\n","/**\r\n * 并发队列管理\r\n */\r\n\r\nimport { ref, readonly } from \"vue\";\r\nimport type { Ref } from \"vue\";\r\nimport type {\r\n UploadFileItem,\r\n UploadRequestOptions,\r\n CustomUploadRequest,\r\n} from \"../types\";\r\n\r\ninterface UseUploadQueueOptions {\r\n /** 最大并发数 */\r\n concurrency: Ref<number>;\r\n /** 上传地址 */\r\n action: Ref<string>;\r\n /** 请求头 */\r\n headers: Ref<Record<string, string>>;\r\n /** 附加字段 */\r\n data: Ref<Record<string, any>>;\r\n /** 自定义上传函数 */\r\n customRequest?: Ref<CustomUploadRequest | undefined>;\r\n}\r\n\r\n/**\r\n * 并发队列管理\r\n *\r\n * 控制普通文件(非分片)的并发上传数量,\r\n * 先进先出排队,自动从队列中取出执行。\r\n */\r\nexport function useUploadQueue(options: UseUploadQueueOptions) {\r\n /** 等待队列 */\r\n const queue: UploadFileItem[] = [];\r\n /** 正在上传的数量 */\r\n const activeCount = ref(0);\r\n /** uid → abort */\r\n const abortMap = new Map<string, () => void>();\r\n\r\n /**\r\n * 添加文件到队列\r\n */\r\n function enqueue(\r\n file: UploadFileItem,\r\n onProgress: (uid: string, percent: number) => void,\r\n onSuccess: (uid: string, response: any) => void,\r\n onError: (uid: string, error: Error) => void,\r\n ) {\r\n queue.push(file);\r\n processQueue(onProgress, onSuccess, onError);\r\n }\r\n\r\n /**\r\n * 处理队列\r\n */\r\n function processQueue(\r\n onProgress: (uid: string, percent: number) => void,\r\n onSuccess: (uid: string, response: any) => void,\r\n onError: (uid: string, error: Error) => void,\r\n ) {\r\n while (activeCount.value < options.concurrency.value && queue.length > 0) {\r\n const file = queue.shift()!;\r\n if (!file.raw) continue;\r\n\r\n activeCount.value++;\r\n\r\n const requestOptions: UploadRequestOptions = {\r\n action: options.action.value,\r\n headers: options.headers.value,\r\n data: options.data.value,\r\n file: file.raw,\r\n filename: file.name,\r\n onProgress: (percent) => {\r\n onProgress(file.uid, percent);\r\n },\r\n onSuccess: (response) => {\r\n activeCount.value--;\r\n abortMap.delete(file.uid);\r\n onSuccess(file.uid, response);\r\n processQueue(onProgress, onSuccess, onError);\r\n },\r\n onError: (error) => {\r\n activeCount.value--;\r\n abortMap.delete(file.uid);\r\n onError(file.uid, error);\r\n processQueue(onProgress, onSuccess, onError);\r\n },\r\n };\r\n\r\n if (options.customRequest?.value) {\r\n const { abort } = options.customRequest.value(requestOptions);\r\n abortMap.set(file.uid, abort);\r\n } else {\r\n const { abort } = defaultRequest(requestOptions);\r\n abortMap.set(file.uid, abort);\r\n }\r\n }\r\n }\r\n\r\n /** 中止指定文件 */\r\n function abort(uid: string) {\r\n abortMap.get(uid)?.();\r\n abortMap.delete(uid);\r\n // 从队列移除\r\n const idx = queue.findIndex((f) => f.uid === uid);\r\n if (idx !== -1) queue.splice(idx, 1);\r\n }\r\n\r\n /** 中止所有 */\r\n function abortAll() {\r\n abortMap.forEach((fn) => fn());\r\n abortMap.clear();\r\n queue.length = 0;\r\n activeCount.value = 0;\r\n }\r\n\r\n return {\r\n /** 当前活跃上传数 */\r\n activeCount: readonly(activeCount),\r\n enqueue,\r\n abort,\r\n abortAll,\r\n };\r\n}\r\n\r\n/** 默认 XHR 上传 */\r\nfunction defaultRequest(options: UploadRequestOptions) {\r\n const xhr = new XMLHttpRequest();\r\n xhr.open(\"POST\", options.action);\r\n\r\n if (options.headers) {\r\n Object.entries(options.headers).forEach(([key, value]) => {\r\n xhr.setRequestHeader(key, value);\r\n });\r\n }\r\n\r\n xhr.upload.addEventListener(\"progress\", (e) => {\r\n if (e.lengthComputable) {\r\n options.onProgress?.(Math.round((e.loaded / e.total) * 100));\r\n }\r\n });\r\n\r\n xhr.addEventListener(\"load\", () => {\r\n if (xhr.status >= 200 && xhr.status < 300) {\r\n let response: any;\r\n try {\r\n response = JSON.parse(xhr.responseText);\r\n } catch {\r\n response = xhr.responseText;\r\n }\r\n options.onSuccess?.(response);\r\n } else {\r\n options.onError?.(new Error(`上传失败: HTTP ${xhr.status}`));\r\n }\r\n });\r\n\r\n xhr.addEventListener(\"error\", () => {\r\n options.onError?.(new Error(\"网络错误\"));\r\n });\r\n\r\n const formData = new FormData();\r\n formData.append(\"file\", options.file as File, options.filename);\r\n\r\n if (options.data) {\r\n Object.entries(options.data).forEach(([key, value]) => {\r\n formData.append(key, String(value));\r\n });\r\n }\r\n\r\n xhr.send(formData);\r\n\r\n return { abort: () => xhr.abort() };\r\n}\r\n","/**\r\n * 核心上传逻辑\r\n */\r\n\r\nimport { ref, computed, reactive, onBeforeUnmount } from \"vue\";\r\nimport type { UploadFileItem, UploadProps, ChunkProgress } from \"../types\";\r\nimport { DEFAULT_CHUNK_SIZE, DEFAULT_CONCURRENCY } from \"../constants\";\r\nimport { useFileHash } from \"./useFileHash\";\r\nimport { useChunkUpload } from \"./useChunkUpload\";\r\nimport { useUploadQueue } from \"./useUploadQueue\";\r\n\r\n/**\r\n * 核心上传逻辑\r\n *\r\n * 统一调度文件校验、hash 计算、秒传检查、分片/普通上传、\r\n * 进度汇总和状态管理。\r\n */\r\nexport function useUploadCore(props: UploadProps) {\r\n // ─── 响应式 Props 引用 ────────────────────────\r\n\r\n const action = computed(() => props.action ?? \"\");\r\n const headers = computed(() => props.headers ?? {});\r\n const data = computed(() => props.data ?? {});\r\n const chunkSize = computed(() => props.chunkSize ?? DEFAULT_CHUNK_SIZE);\r\n const concurrency = computed(() => props.concurrency ?? DEFAULT_CONCURRENCY);\r\n const customRequest = computed(() => props.customRequest);\r\n const instantCheck = computed(() => props.instantCheck);\r\n const uploadedChunksQuery = computed(() => props.uploadedChunksQuery);\r\n const mergeChunks = computed(() => props.mergeChunks);\r\n\r\n // ─── 文件列表 ────────────────────────────────\r\n\r\n const fileList = ref<UploadFileItem[]>([...(props.defaultFileList ?? [])]);\r\n const pausedSet = reactive(new Set<string>());\r\n\r\n // ─── 子 composable ───────────────────────────\r\n\r\n const { hashing, hashProgress, calculateHash } = useFileHash(chunkSize);\r\n\r\n const chunkUploader = useChunkUpload({\r\n chunkSize,\r\n concurrency,\r\n action,\r\n headers,\r\n data,\r\n customRequest,\r\n uploadedChunksQuery,\r\n mergeChunks,\r\n });\r\n\r\n const uploadQueue = useUploadQueue({\r\n concurrency,\r\n action,\r\n headers,\r\n data,\r\n customRequest,\r\n });\r\n\r\n // ─── 总进度 ──────────────────────────────────\r\n\r\n const totalPercent = computed(() => {\r\n const list = fileList.value;\r\n if (list.length === 0) return 0;\r\n const total = list.reduce((sum, f) => sum + f.percent, 0);\r\n return Math.round(total / list.length);\r\n });\r\n\r\n // ─── 文件操作 ────────────────────────────────\r\n\r\n /** 生成唯一 ID */\r\n function generateUid(): string {\r\n return `upload-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\r\n }\r\n\r\n /** 创建缩略图 */\r\n function createThumbnail(file: File): string | undefined {\r\n if (file.type.startsWith(\"image/\")) {\r\n return URL.createObjectURL(file);\r\n }\r\n return undefined;\r\n }\r\n\r\n /** 添加文件到列表 */\r\n function addFiles(files: File[]): UploadFileItem[] {\r\n const items: UploadFileItem[] = [];\r\n\r\n for (const file of files) {\r\n // 数量限制\r\n if (\r\n props.maxCount &&\r\n fileList.value.length + items.length >= props.maxCount\r\n ) {\r\n break;\r\n }\r\n\r\n const item: UploadFileItem = {\r\n uid: generateUid(),\r\n name: file.name,\r\n size: file.size,\r\n type: file.type,\r\n status: \"pending\",\r\n percent: 0,\r\n raw: file,\r\n thumbUrl: createThumbnail(file),\r\n };\r\n\r\n items.push(item);\r\n }\r\n\r\n fileList.value.push(...items);\r\n return items;\r\n }\r\n\r\n /** 更新文件状态 */\r\n function updateFile(uid: string, patch: Partial<UploadFileItem>) {\r\n const file = fileList.value.find((f) => f.uid === uid);\r\n if (file) Object.assign(file, patch);\r\n }\r\n\r\n /** 移除文件 */\r\n function removeFile(uid: string) {\r\n chunkUploader.abortUpload(uid);\r\n uploadQueue.abort(uid);\r\n pausedSet.delete(uid);\r\n\r\n const idx = fileList.value.findIndex((f) => f.uid === uid);\r\n if (idx !== -1) {\r\n const file = fileList.value[idx];\r\n if (file.thumbUrl) URL.revokeObjectURL(file.thumbUrl);\r\n fileList.value.splice(idx, 1);\r\n }\r\n }\r\n\r\n /** 清空所有 */\r\n function clearAll() {\r\n chunkUploader.abortAll();\r\n uploadQueue.abortAll();\r\n pausedSet.clear();\r\n\r\n fileList.value.forEach((f) => {\r\n if (f.thumbUrl) URL.revokeObjectURL(f.thumbUrl);\r\n });\r\n fileList.value = [];\r\n }\r\n\r\n // ─── 上传流程 ────────────────────────────────\r\n\r\n /** 校验单文件 */\r\n async function validateFile(file: File): Promise<boolean> {\r\n // 大小校验\r\n if (props.maxSize && file.size > props.maxSize) {\r\n return false;\r\n }\r\n\r\n // 自定义 beforeUpload\r\n if (props.beforeUpload) {\r\n const result = await props.beforeUpload(file);\r\n return result !== false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /** 上传单个文件 */\r\n async function processFile(item: UploadFileItem) {\r\n if (!item.raw) return;\r\n\r\n // 前置校验\r\n const valid = await validateFile(item.raw);\r\n if (!valid) {\r\n updateFile(item.uid, { status: \"error\", error: \"文件校验未通过\" });\r\n return;\r\n }\r\n\r\n // 分片模式\r\n if (props.chunked && item.raw.size > chunkSize.value) {\r\n await processChunkedUpload(item);\r\n } else {\r\n processNormalUpload(item);\r\n }\r\n }\r\n\r\n /** 分片上传流程 */\r\n async function processChunkedUpload(item: UploadFileItem) {\r\n const file = item.raw!;\r\n\r\n // 1. 计算 hash\r\n updateFile(item.uid, { status: \"hashing\" });\r\n let hash: string;\r\n try {\r\n hash = await calculateHash(file);\r\n updateFile(item.uid, { hash });\r\n } catch {\r\n updateFile(item.uid, { status: \"error\", error: \"Hash 计算失败\" });\r\n return;\r\n }\r\n\r\n // 2. 秒传检查\r\n if (instantCheck.value) {\r\n try {\r\n const result = await instantCheck.value(hash, file.name);\r\n if (result.exists) {\r\n updateFile(item.uid, {\r\n status: \"instant\",\r\n percent: 100,\r\n url: result.url,\r\n });\r\n return;\r\n }\r\n } catch {\r\n // 秒传检查失败,继续正常上传\r\n }\r\n }\r\n\r\n // 3. 分片上传\r\n updateFile(item.uid, { status: \"uploading\" });\r\n\r\n await chunkUploader.uploadChunks({\r\n uid: item.uid,\r\n file,\r\n hash,\r\n onProgress: (progress: ChunkProgress) => {\r\n const percent = Math.round(\r\n (progress.uploadedBytes / progress.totalBytes) * 100,\r\n );\r\n updateFile(item.uid, { percent, chunkProgress: progress });\r\n },\r\n onSuccess: (response: any) => {\r\n updateFile(item.uid, { status: \"success\", percent: 100, response });\r\n },\r\n onError: (error: Error) => {\r\n updateFile(item.uid, { status: \"error\", error: error.message });\r\n },\r\n isPaused: () => pausedSet.has(item.uid),\r\n });\r\n }\r\n\r\n /** 普通上传 */\r\n function processNormalUpload(item: UploadFileItem) {\r\n updateFile(item.uid, { status: \"uploading\" });\r\n\r\n uploadQueue.enqueue(\r\n item,\r\n (uid, percent) => {\r\n updateFile(uid, { percent });\r\n },\r\n (uid, response) => {\r\n updateFile(uid, { status: \"success\", percent: 100, response });\r\n },\r\n (uid, error) => {\r\n updateFile(uid, { status: \"error\", error: error.message });\r\n },\r\n );\r\n }\r\n\r\n /** 开始上传(上传所有 pending 文件) */\r\n function startUpload() {\r\n const pending = fileList.value.filter((f) => f.status === \"pending\");\r\n pending.forEach(processFile);\r\n }\r\n\r\n /** 暂停所有 */\r\n function pauseAll() {\r\n fileList.value.forEach((f) => {\r\n if (f.status === \"uploading\" || f.status === \"hashing\") {\r\n pausedSet.add(f.uid);\r\n updateFile(f.uid, { status: \"paused\" });\r\n }\r\n });\r\n }\r\n\r\n /** 恢复所有 */\r\n function resumeAll() {\r\n fileList.value.forEach((f) => {\r\n if (f.status === \"paused\") {\r\n pausedSet.delete(f.uid);\r\n updateFile(f.uid, { status: \"pending\" });\r\n }\r\n });\r\n startUpload();\r\n }\r\n\r\n /** 重试单个文件 */\r\n function retryFile(uid: string) {\r\n const file = fileList.value.find((f) => f.uid === uid);\r\n if (file && file.status === \"error\") {\r\n updateFile(uid, { status: \"pending\", percent: 0, error: undefined });\r\n processFile(file);\r\n }\r\n }\r\n\r\n /** 获取成功列表 */\r\n function getSuccessList(): UploadFileItem[] {\r\n return fileList.value.filter(\r\n (f) => f.status === \"success\" || f.status === \"instant\",\r\n );\r\n }\r\n\r\n // 清理 ObjectURL\r\n onBeforeUnmount(() => {\r\n fileList.value.forEach((f) => {\r\n if (f.thumbUrl) URL.revokeObjectURL(f.thumbUrl);\r\n });\r\n });\r\n\r\n return {\r\n /** 文件列表 */\r\n fileList,\r\n /** 总进度 */\r\n totalPercent,\r\n /** 正在计算 hash */\r\n hashing,\r\n /** hash 进度 */\r\n hashProgress,\r\n\r\n /** 添加文件 */\r\n addFiles,\r\n /** 移除文件 */\r\n removeFile,\r\n /** 清空所有 */\r\n clearAll,\r\n /** 开始上传 */\r\n startUpload,\r\n /** 暂停所有 */\r\n pauseAll,\r\n /** 恢复所有 */\r\n resumeAll,\r\n /** 重试 */\r\n retryFile,\r\n /** 处理单文件 */\r\n processFile,\r\n /** 获取成功列表 */\r\n getSuccessList,\r\n };\r\n}\r\n","/**\r\n * 拖拽 & 粘贴上传\r\n */\r\n\r\nimport { ref, readonly, watch, onMounted, onBeforeUnmount, type Ref } from 'vue'\r\n\r\n/**\r\n * 拖拽 & 粘贴上传\r\n *\r\n * 监听容器的 dragover / drop / paste 事件,\r\n * 提取文件(含目录遍历)后回调给上层。\r\n *\r\n * @param containerRef 容器 DOM\r\n * @param enabled 是否启用拖拽\r\n * @param pasteable 是否启用粘贴\r\n * @param accept 文件类型限制\r\n * @param onFiles 文件回调\r\n */\r\nexport function useDragDrop(\r\n containerRef: Ref<HTMLElement | undefined>,\r\n enabled: Ref<boolean>,\r\n pasteable: Ref<boolean>,\r\n accept: Ref<string>,\r\n onFiles: (files: File[]) => void\r\n) {\r\n /** 是否正在拖拽悬停 */\r\n const isDragOver = ref(false)\r\n\r\n // ─── 拖拽事件 ─────────────────────────────────\r\n\r\n /** 处理拖入事件 */\r\n function handleDragEnter(e: DragEvent) {\r\n if (!enabled.value) return\r\n e.preventDefault()\r\n isDragOver.value = true\r\n }\r\n\r\n /** 处理拖拽经过事件 */\r\n function handleDragOver(e: DragEvent) {\r\n if (!enabled.value) return\r\n e.preventDefault()\r\n isDragOver.value = true\r\n }\r\n\r\n /** 处理拖离事件 */\r\n function handleDragLeave(e: DragEvent) {\r\n if (!enabled.value) return\r\n e.preventDefault()\r\n const container = containerRef.value\r\n if (container && !container.contains(e.relatedTarget as Node)) {\r\n isDragOver.value = false\r\n }\r\n }\r\n\r\n /** 处理放置事件 */\r\n async function handleDrop(e: DragEvent) {\r\n if (!enabled.value) return\r\n e.preventDefault()\r\n isDragOver.value = false\r\n\r\n const items = e.dataTransfer?.items\r\n if (!items) return\r\n\r\n const files = await collectDropFiles(items, e.dataTransfer)\r\n const filtered = filterByAccept(files, accept.value)\r\n if (filtered.length > 0) onFiles(filtered)\r\n }\r\n\r\n // ─── 粘贴事件 ─────────────────────────────────\r\n\r\n /** 粘贴事件 */\r\n function handlePaste(e: ClipboardEvent) {\r\n if (!pasteable.value) return\r\n\r\n const items = e.clipboardData?.items\r\n if (!items) return\r\n\r\n const files: File[] = []\r\n for (let i = 0; i < items.length; i++) {\r\n const item = items[i]\r\n if (item.kind === 'file') {\r\n const file = item.getAsFile()\r\n if (file) files.push(file)\r\n }\r\n }\r\n\r\n const filtered = filterByAccept(files, accept.value)\r\n if (filtered.length > 0) {\r\n e.preventDefault()\r\n onFiles(filtered)\r\n }\r\n }\r\n\r\n // ─── 生命周期绑定 ─────────────────────────────\r\n\r\n /** 绑定事件 */\r\n function bindEvents() {\r\n const el = containerRef.value\r\n if (!el) return\r\n\r\n el.addEventListener('dragenter', handleDragEnter)\r\n el.addEventListener('dragover', handleDragOver)\r\n el.addEventListener('dragleave', handleDragLeave)\r\n el.addEventListener('drop', handleDrop)\r\n\r\n if (pasteable.value) {\r\n document.addEventListener('paste', handlePaste)\r\n }\r\n }\r\n\r\n /** 解绑事件 */\r\n function unbindEvents() {\r\n const el = containerRef.value\r\n if (el) {\r\n el.removeEventListener('dragenter', handleDragEnter)\r\n el.removeEventListener('dragover', handleDragOver)\r\n el.removeEventListener('dragleave', handleDragLeave)\r\n el.removeEventListener('drop', handleDrop)\r\n }\r\n document.removeEventListener('paste', handlePaste)\r\n }\r\n\r\n onMounted(bindEvents)\r\n onBeforeUnmount(unbindEvents)\r\n\r\n watch([containerRef, enabled, pasteable], () => {\r\n unbindEvents()\r\n bindEvents()\r\n })\r\n\r\n return {\r\n /** 是否正在拖拽悬停 */\r\n isDragOver: readonly(isDragOver),\r\n }\r\n}\r\n\r\n// ─── 工具函数 ────────────────────────────────\r\n\r\n/** 从 drop 事件收集文件(含目录遍历) */\r\nasync function collectDropFiles(\r\n items: DataTransferItemList,\r\n dataTransfer: DataTransfer | null\r\n): Promise<File[]> {\r\n const entries: FileSystemEntry[] = []\r\n for (let i = 0; i < items.length; i++) {\r\n const entry = items[i].webkitGetAsEntry?.()\r\n if (entry) entries.push(entry)\r\n }\r\n\r\n if (entries.length > 0) {\r\n const results = await Promise.all(entries.map(readEntry))\r\n return results.flat()\r\n }\r\n\r\n const files: File[] = []\r\n const dtFiles = dataTransfer?.files\r\n if (dtFiles) {\r\n for (let i = 0; i < dtFiles.length; i++) {\r\n files.push(dtFiles[i])\r\n }\r\n }\r\n return files\r\n}\r\n\r\n/** 递归读取 FileSystemEntry */\r\nasync function readEntry(entry: FileSystemEntry): Promise<File[]> {\r\n if (entry.isFile) {\r\n return new Promise(resolve => {\r\n ;(entry as FileSystemFileEntry).file(\r\n file => resolve([file]),\r\n () => resolve([])\r\n )\r\n })\r\n }\r\n\r\n if (entry.isDirectory) {\r\n const reader = (entry as FileSystemDirectoryEntry).createReader()\r\n const entries = await new Promise<FileSystemEntry[]>(resolve => {\r\n reader.readEntries(\r\n result => resolve(result),\r\n () => resolve([])\r\n )\r\n })\r\n const results = await Promise.all(entries.map(readEntry))\r\n return results.flat()\r\n }\r\n\r\n return []\r\n}\r\n\r\n/** 根据 accept 过滤文件 */\r\nfunction filterByAccept(files: File[], accept: string): File[] {\r\n if (!accept) return files\r\n\r\n const acceptTypes = accept.split(',').map(s => s.trim().toLowerCase())\r\n\r\n return files.filter(file => {\r\n return acceptTypes.some(type => {\r\n if (type.startsWith('.')) {\r\n // 扩展名匹配\r\n return file.name.toLowerCase().endsWith(type)\r\n }\r\n if (type.endsWith('/*')) {\r\n // MIME 前缀匹配 (image/*, video/*)\r\n const prefix = type.replace('/*', '')\r\n return file.type.toLowerCase().startsWith(prefix)\r\n }\r\n // 精确 MIME 匹配\r\n return file.type.toLowerCase() === type\r\n })\r\n })\r\n}\r\n","<template>\r\n <div\r\n ref=\"areaRef\"\r\n class=\"upload-area\"\r\n :class=\"{\r\n 'upload-area--drag-over': isDragOver,\r\n 'upload-area--disabled': disabled,\r\n }\"\r\n @click=\"handleClick\"\r\n >\r\n <input\r\n ref=\"inputRef\"\r\n type=\"file\"\r\n class=\"upload-area__input\"\r\n :accept=\"accept\"\r\n :multiple=\"multiple\"\r\n :webkitdirectory=\"directory || undefined\"\r\n @change=\"handleInputChange\"\r\n />\r\n\r\n <slot>\r\n <div class=\"upload-area__default\">\r\n <C_Icon\r\n :name=\"\r\n isDragOver\r\n ? 'mdi:cloud-download-outline'\r\n : 'mdi:cloud-upload-outline'\r\n \"\r\n class=\"upload-area__icon\"\r\n />\r\n <div class=\"upload-area__text\">\r\n <span v-if=\"isDragOver\">释放文件到此处</span>\r\n <span v-else>\r\n 点击或拖拽文件到此区域上传\r\n <template v-if=\"pasteable\">\r\n ,支持 <kbd>Ctrl+V</kbd> 粘贴\r\n </template>\r\n </span>\r\n </div>\r\n <div v-if=\"tip\" class=\"upload-area__tip\">\r\n {{ tip }}\r\n </div>\r\n </div>\r\n </slot>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport { useDragDrop } from \"../composables/useDragDrop\";\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 接受的文件类型 */\r\n accept?: string;\r\n /** 是否多选 */\r\n multiple?: boolean;\r\n /** 是否目录 */\r\n directory?: boolean;\r\n /** 是否禁用 */\r\n disabled?: boolean;\r\n /** 是否启用拖拽 */\r\n draggable?: boolean;\r\n /** 是否启用粘贴 */\r\n pasteable?: boolean;\r\n /** 提示文本 */\r\n tip?: string;\r\n }>(),\r\n {\r\n accept: \"\",\r\n multiple: false,\r\n directory: false,\r\n disabled: false,\r\n draggable: true,\r\n pasteable: false,\r\n tip: \"\",\r\n },\r\n);\r\n\r\nconst emit = defineEmits<{\r\n files: [files: File[]];\r\n}>();\r\n\r\nconst areaRef = ref<HTMLElement>();\r\nconst inputRef = ref<HTMLInputElement>();\r\n\r\n// 拖拽 & 粘贴\r\nconst { isDragOver } = useDragDrop(\r\n areaRef,\r\n computed(() => props.draggable && !props.disabled),\r\n computed(() => props.pasteable && !props.disabled),\r\n computed(() => props.accept),\r\n (files) => emit(\"files\", files),\r\n);\r\n\r\n/** 点击触发文件选择 */\r\nfunction handleClick() {\r\n if (props.disabled) return;\r\n inputRef.value?.click();\r\n}\r\n\r\n/** input change */\r\nfunction handleInputChange(e: Event) {\r\n const input = e.target as HTMLInputElement;\r\n const files = Array.from(input.files ?? []);\r\n if (files.length > 0) {\r\n emit(\"files\", files);\r\n }\r\n // 清空 input 以允许重复选择同一文件\r\n input.value = \"\";\r\n}\r\n\r\n/** 暴露触发方法 */\r\ndefineExpose({\r\n triggerSelect: handleClick,\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.upload-area {\r\n position: relative;\r\n box-sizing: border-box;\r\n padding: 32px 24px;\r\n border: 2px dashed var(--border-color);\r\n border-radius: 10px;\r\n cursor: pointer;\r\n background: var(--card-color);\r\n transition: all 0.25s ease;\r\n text-align: center;\r\n\r\n &:hover:not(.upload-area--disabled) {\r\n border-color: var(--primary-color);\r\n background: color-mix(in srgb, var(--primary-color) 4%, var(--card-color));\r\n }\r\n\r\n &--drag-over {\r\n border-color: var(--primary-color);\r\n border-style: solid;\r\n background: color-mix(in srgb, var(--primary-color) 8%, var(--card-color));\r\n\r\n .upload-area__icon {\r\n color: var(--primary-color);\r\n transform: scale(1.1);\r\n }\r\n }\r\n\r\n &--disabled {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n }\r\n\r\n &__input {\r\n display: none;\r\n }\r\n\r\n &__default {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n align-items: center;\r\n }\r\n\r\n &__icon {\r\n font-size: 48px;\r\n color: var(--text-color-3);\r\n transition: all 0.25s ease;\r\n }\r\n\r\n &__text {\r\n font-size: 14px;\r\n color: var(--text-color-2);\r\n line-height: 1.6;\r\n\r\n kbd {\r\n padding: 1px 5px;\r\n font-size: 12px;\r\n border: 1px solid var(--border-color);\r\n border-radius: 3px;\r\n background: var(--body-color);\r\n }\r\n }\r\n\r\n &__tip {\r\n font-size: 12px;\r\n color: var(--text-color-4);\r\n }\r\n}\r\n</style>\r\n","<template>\r\n <div\r\n ref=\"areaRef\"\r\n class=\"upload-area\"\r\n :class=\"{\r\n 'upload-area--drag-over': isDragOver,\r\n 'upload-area--disabled': disabled,\r\n }\"\r\n @click=\"handleClick\"\r\n >\r\n <input\r\n ref=\"inputRef\"\r\n type=\"file\"\r\n class=\"upload-area__input\"\r\n :accept=\"accept\"\r\n :multiple=\"multiple\"\r\n :webkitdirectory=\"directory || undefined\"\r\n @change=\"handleInputChange\"\r\n />\r\n\r\n <slot>\r\n <div class=\"upload-area__default\">\r\n <C_Icon\r\n :name=\"\r\n isDragOver\r\n ? 'mdi:cloud-download-outline'\r\n : 'mdi:cloud-upload-outline'\r\n \"\r\n class=\"upload-area__icon\"\r\n />\r\n <div class=\"upload-area__text\">\r\n <span v-if=\"isDragOver\">释放文件到此处</span>\r\n <span v-else>\r\n 点击或拖拽文件到此区域上传\r\n <template v-if=\"pasteable\">\r\n ,支持 <kbd>Ctrl+V</kbd> 粘贴\r\n </template>\r\n </span>\r\n </div>\r\n <div v-if=\"tip\" class=\"upload-area__tip\">\r\n {{ tip }}\r\n </div>\r\n </div>\r\n </slot>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport { useDragDrop } from \"../composables/useDragDrop\";\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 接受的文件类型 */\r\n accept?: string;\r\n /** 是否多选 */\r\n multiple?: boolean;\r\n /** 是否目录 */\r\n directory?: boolean;\r\n /** 是否禁用 */\r\n disabled?: boolean;\r\n /** 是否启用拖拽 */\r\n draggable?: boolean;\r\n /** 是否启用粘贴 */\r\n pasteable?: boolean;\r\n /** 提示文本 */\r\n tip?: string;\r\n }>(),\r\n {\r\n accept: \"\",\r\n multiple: false,\r\n directory: false,\r\n disabled: false,\r\n draggable: true,\r\n pasteable: false,\r\n tip: \"\",\r\n },\r\n);\r\n\r\nconst emit = defineEmits<{\r\n files: [files: File[]];\r\n}>();\r\n\r\nconst areaRef = ref<HTMLElement>();\r\nconst inputRef = ref<HTMLInputElement>();\r\n\r\n// 拖拽 & 粘贴\r\nconst { isDragOver } = useDragDrop(\r\n areaRef,\r\n computed(() => props.draggable && !props.disabled),\r\n computed(() => props.pasteable && !props.disabled),\r\n computed(() => props.accept),\r\n (files) => emit(\"files\", files),\r\n);\r\n\r\n/** 点击触发文件选择 */\r\nfunction handleClick() {\r\n if (props.disabled) return;\r\n inputRef.value?.click();\r\n}\r\n\r\n/** input change */\r\nfunction handleInputChange(e: Event) {\r\n const input = e.target as HTMLInputElement;\r\n const files = Array.from(input.files ?? []);\r\n if (files.length > 0) {\r\n emit(\"files\", files);\r\n }\r\n // 清空 input 以允许重复选择同一文件\r\n input.value = \"\";\r\n}\r\n\r\n/** 暴露触发方法 */\r\ndefineExpose({\r\n triggerSelect: handleClick,\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.upload-area {\r\n position: relative;\r\n box-sizing: border-box;\r\n padding: 32px 24px;\r\n border: 2px dashed var(--border-color);\r\n border-radius: 10px;\r\n cursor: pointer;\r\n background: var(--card-color);\r\n transition: all 0.25s ease;\r\n text-align: center;\r\n\r\n &:hover:not(.upload-area--disabled) {\r\n border-color: var(--primary-color);\r\n background: color-mix(in srgb, var(--primary-color) 4%, var(--card-color));\r\n }\r\n\r\n &--drag-over {\r\n border-color: var(--primary-color);\r\n border-style: solid;\r\n background: color-mix(in srgb, var(--primary-color) 8%, var(--card-color));\r\n\r\n .upload-area__icon {\r\n color: var(--primary-color);\r\n transform: scale(1.1);\r\n }\r\n }\r\n\r\n &--disabled {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n }\r\n\r\n &__input {\r\n display: none;\r\n }\r\n\r\n &__default {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n align-items: center;\r\n }\r\n\r\n &__icon {\r\n font-size: 48px;\r\n color: var(--text-color-3);\r\n transition: all 0.25s ease;\r\n }\r\n\r\n &__text {\r\n font-size: 14px;\r\n color: var(--text-color-2);\r\n line-height: 1.6;\r\n\r\n kbd {\r\n padding: 1px 5px;\r\n font-size: 12px;\r\n border: 1px solid var(--border-color);\r\n border-radius: 3px;\r\n background: var(--body-color);\r\n }\r\n }\r\n\r\n &__tip {\r\n font-size: 12px;\r\n color: var(--text-color-4);\r\n }\r\n}\r\n</style>\r\n","<template>\r\n <div\r\n ref=\"areaRef\"\r\n class=\"upload-area\"\r\n :class=\"{\r\n 'upload-area--drag-over': isDragOver,\r\n 'upload-area--disabled': disabled,\r\n }\"\r\n @click=\"handleClick\"\r\n >\r\n <input\r\n ref=\"inputRef\"\r\n type=\"file\"\r\n class=\"upload-area__input\"\r\n :accept=\"accept\"\r\n :multiple=\"multiple\"\r\n :webkitdirectory=\"directory || undefined\"\r\n @change=\"handleInputChange\"\r\n />\r\n\r\n <slot>\r\n <div class=\"upload-area__default\">\r\n <C_Icon\r\n :name=\"\r\n isDragOver\r\n ? 'mdi:cloud-download-outline'\r\n : 'mdi:cloud-upload-outline'\r\n \"\r\n class=\"upload-area__icon\"\r\n />\r\n <div class=\"upload-area__text\">\r\n <span v-if=\"isDragOver\">释放文件到此处</span>\r\n <span v-else>\r\n 点击或拖拽文件到此区域上传\r\n <template v-if=\"pasteable\">\r\n ,支持 <kbd>Ctrl+V</kbd> 粘贴\r\n </template>\r\n </span>\r\n </div>\r\n <div v-if=\"tip\" class=\"upload-area__tip\">\r\n {{ tip }}\r\n </div>\r\n </div>\r\n </slot>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed } from \"vue\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport { useDragDrop } from \"../composables/useDragDrop\";\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n /** 接受的文件类型 */\r\n accept?: string;\r\n /** 是否多选 */\r\n multiple?: boolean;\r\n /** 是否目录 */\r\n directory?: boolean;\r\n /** 是否禁用 */\r\n disabled?: boolean;\r\n /** 是否启用拖拽 */\r\n draggable?: boolean;\r\n /** 是否启用粘贴 */\r\n pasteable?: boolean;\r\n /** 提示文本 */\r\n tip?: string;\r\n }>(),\r\n {\r\n accept: \"\",\r\n multiple: false,\r\n directory: false,\r\n disabled: false,\r\n draggable: true,\r\n pasteable: false,\r\n tip: \"\",\r\n },\r\n);\r\n\r\nconst emit = defineEmits<{\r\n files: [files: File[]];\r\n}>();\r\n\r\nconst areaRef = ref<HTMLElement>();\r\nconst inputRef = ref<HTMLInputElement>();\r\n\r\n// 拖拽 & 粘贴\r\nconst { isDragOver } = useDragDrop(\r\n areaRef,\r\n computed(() => props.draggable && !props.disabled),\r\n computed(() => props.pasteable && !props.disabled),\r\n computed(() => props.accept),\r\n (files) => emit(\"files\", files),\r\n);\r\n\r\n/** 点击触发文件选择 */\r\nfunction handleClick() {\r\n if (props.disabled) return;\r\n inputRef.value?.click();\r\n}\r\n\r\n/** input change */\r\nfunction handleInputChange(e: Event) {\r\n const input = e.target as HTMLInputElement;\r\n const files = Array.from(input.files ?? []);\r\n if (files.length > 0) {\r\n emit(\"files\", files);\r\n }\r\n // 清空 input 以允许重复选择同一文件\r\n input.value = \"\";\r\n}\r\n\r\n/** 暴露触发方法 */\r\ndefineExpose({\r\n triggerSelect: handleClick,\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.upload-area {\r\n position: relative;\r\n box-sizing: border-box;\r\n padding: 32px 24px;\r\n border: 2px dashed var(--border-color);\r\n border-radius: 10px;\r\n cursor: pointer;\r\n background: var(--card-color);\r\n transition: all 0.25s ease;\r\n text-align: center;\r\n\r\n &:hover:not(.upload-area--disabled) {\r\n border-color: var(--primary-color);\r\n background: color-mix(in srgb, var(--primary-color) 4%, var(--card-color));\r\n }\r\n\r\n &--drag-over {\r\n border-color: var(--primary-color);\r\n border-style: solid;\r\n background: color-mix(in srgb, var(--primary-color) 8%, var(--card-color));\r\n\r\n .upload-area__icon {\r\n color: var(--primary-color);\r\n transform: scale(1.1);\r\n }\r\n }\r\n\r\n &--disabled {\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n }\r\n\r\n &__input {\r\n display: none;\r\n }\r\n\r\n &__default {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n align-items: center;\r\n }\r\n\r\n &__icon {\r\n font-size: 48px;\r\n color: var(--text-color-3);\r\n transition: all 0.25s ease;\r\n }\r\n\r\n &__text {\r\n font-size: 14px;\r\n color: var(--text-color-2);\r\n line-height: 1.6;\r\n\r\n kbd {\r\n padding: 1px 5px;\r\n font-size: 12px;\r\n border: 1px solid var(--border-color);\r\n border-radius: 3px;\r\n background: var(--body-color);\r\n }\r\n }\r\n\r\n &__tip {\r\n font-size: 12px;\r\n color: var(--text-color-4);\r\n }\r\n}\r\n</style>\r\n","<template>\r\n <TransitionGroup name=\"file-list\" tag=\"div\" class=\"file-list\">\r\n <div\r\n v-for=\"file in fileList\"\r\n :key=\"file.uid\"\r\n class=\"file-list__item\"\r\n :class=\"`file-list__item--${file.status}`\"\r\n >\r\n <!-- 缩略图 / 图标 -->\r\n <div class=\"file-list__thumb\">\r\n <img\r\n v-if=\"showThumbnail && file.thumbUrl\"\r\n :src=\"file.thumbUrl\"\r\n :alt=\"file.name\"\r\n class=\"file-list__thumb-img\"\r\n />\r\n <C_Icon\r\n v-else\r\n :name=\"getFileIcon(file.type)\"\r\n class=\"file-list__thumb-icon\"\r\n />\r\n </div>\r\n\r\n <!-- 文件信息 -->\r\n <div class=\"file-list__info\">\r\n <div class=\"file-list__name\">\r\n <NEllipsis :line-clamp=\"1\">\r\n {{ file.name }}\r\n </NEllipsis>\r\n </div>\r\n <div class=\"file-list__meta\">\r\n <span class=\"file-list__size\">{{ formatFileSize(file.size) }}</span>\r\n <NTag\r\n :type=\"(STATUS_TYPE[file.status] as any) || 'default'\"\r\n size=\"tiny\"\r\n :bordered=\"false\"\r\n >\r\n {{ STATUS_TEXT[file.status] || file.status }}\r\n </NTag>\r\n <span v-if=\"file.status === 'instant'\" class=\"file-list__instant\">\r\n ⚡\r\n </span>\r\n </div>\r\n\r\n <!-- 进度条 -->\r\n <NProgress\r\n v-if=\"file.status === 'uploading' || file.status === 'hashing'\"\r\n :percentage=\"file.status === 'hashing' ? hashProgress : file.percent\"\r\n :height=\"4\"\r\n :border-radius=\"2\"\r\n :show-indicator=\"false\"\r\n :status=\"file.status === 'hashing' ? 'info' : 'success'\"\r\n />\r\n\r\n <!-- 分片进度描述 -->\r\n <div\r\n v-if=\"file.chunkProgress && file.status === 'uploading'\"\r\n class=\"file-list__chunk-info\"\r\n >\r\n 分片 {{ file.chunkProgress.uploadedChunks }}/{{\r\n file.chunkProgress.totalChunks\r\n }}\r\n </div>\r\n\r\n <!-- 错误信息 -->\r\n <div\r\n v-if=\"file.status === 'error' && file.error\"\r\n class=\"file-list__error\"\r\n >\r\n {{ file.error }}\r\n </div>\r\n </div>\r\n\r\n <!-- 操作按钮 -->\r\n <div class=\"file-list__actions\">\r\n <NButton\r\n v-if=\"file.status === 'error'\"\r\n text\r\n type=\"warning\"\r\n size=\"tiny\"\r\n @click=\"emit('retry', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:refresh\" />\r\n </template>\r\n </NButton>\r\n <NButton\r\n text\r\n type=\"error\"\r\n size=\"tiny\"\r\n @click=\"emit('remove', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n </TransitionGroup>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NProgress, NButton, NTag, NEllipsis } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { UploadFileItem } from \"../types\";\r\nimport {\r\n STATUS_TEXT,\r\n STATUS_TYPE,\r\n getFileIcon,\r\n formatFileSize,\r\n} from \"../constants\";\r\n\r\ndefineProps<{\r\n /** 文件列表 */\r\n fileList: UploadFileItem[];\r\n /** 是否显示缩略图 */\r\n showThumbnail?: boolean;\r\n /** hash 进度(全局共享) */\r\n hashProgress?: number;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n remove: [uid: string];\r\n retry: [uid: string];\r\n}>();\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.file-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n margin-top: 12px;\r\n\r\n &__item {\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-start;\r\n padding: 10px 12px;\r\n border-radius: 8px;\r\n border: 1px solid var(--border-color);\r\n background: var(--card-color);\r\n transition: all 0.25s ease;\r\n\r\n &:hover {\r\n border-color: color-mix(\r\n in srgb,\r\n var(--primary-color) 40%,\r\n var(--border-color)\r\n );\r\n box-shadow: 0 1px 6px rgba(0, 0, 0, 0.04);\r\n }\r\n\r\n &--error {\r\n border-color: var(--error-color);\r\n background: color-mix(in srgb, var(--error-color) 4%, var(--card-color));\r\n }\r\n\r\n &--success,\r\n &--instant {\r\n border-color: color-mix(\r\n in srgb,\r\n var(--success-color) 30%,\r\n var(--border-color)\r\n );\r\n }\r\n }\r\n\r\n &__thumb {\r\n flex-shrink: 0;\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 6px;\r\n overflow: hidden;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background: var(--body-color);\r\n }\r\n\r\n &__thumb-img {\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n }\r\n\r\n &__thumb-icon {\r\n font-size: 22px;\r\n color: var(--text-color-3);\r\n }\r\n\r\n &__info {\r\n flex: 1;\r\n min-width: 0;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n }\r\n\r\n &__name {\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--text-color-1);\r\n }\r\n\r\n &__meta {\r\n display: flex;\r\n gap: 8px;\r\n align-items: center;\r\n font-size: 12px;\r\n }\r\n\r\n &__size {\r\n color: var(--text-color-3);\r\n }\r\n\r\n &__instant {\r\n font-size: 14px;\r\n }\r\n\r\n &__chunk-info {\r\n font-size: 11px;\r\n color: var(--text-color-4);\r\n }\r\n\r\n &__error {\r\n font-size: 12px;\r\n color: var(--error-color);\r\n }\r\n\r\n &__actions {\r\n flex-shrink: 0;\r\n display: flex;\r\n gap: 4px;\r\n align-items: center;\r\n }\r\n}\r\n\r\n/* ─── 过渡动画 ─────────────────────────────── */\r\n\r\n.file-list-enter-active,\r\n.file-list-leave-active {\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.file-list-enter-from {\r\n opacity: 0;\r\n transform: translateX(-20px);\r\n}\r\n\r\n.file-list-leave-to {\r\n opacity: 0;\r\n transform: translateX(20px);\r\n}\r\n\r\n.file-list-move {\r\n transition: transform 0.3s ease;\r\n}\r\n</style>\r\n","<template>\r\n <TransitionGroup name=\"file-list\" tag=\"div\" class=\"file-list\">\r\n <div\r\n v-for=\"file in fileList\"\r\n :key=\"file.uid\"\r\n class=\"file-list__item\"\r\n :class=\"`file-list__item--${file.status}`\"\r\n >\r\n <!-- 缩略图 / 图标 -->\r\n <div class=\"file-list__thumb\">\r\n <img\r\n v-if=\"showThumbnail && file.thumbUrl\"\r\n :src=\"file.thumbUrl\"\r\n :alt=\"file.name\"\r\n class=\"file-list__thumb-img\"\r\n />\r\n <C_Icon\r\n v-else\r\n :name=\"getFileIcon(file.type)\"\r\n class=\"file-list__thumb-icon\"\r\n />\r\n </div>\r\n\r\n <!-- 文件信息 -->\r\n <div class=\"file-list__info\">\r\n <div class=\"file-list__name\">\r\n <NEllipsis :line-clamp=\"1\">\r\n {{ file.name }}\r\n </NEllipsis>\r\n </div>\r\n <div class=\"file-list__meta\">\r\n <span class=\"file-list__size\">{{ formatFileSize(file.size) }}</span>\r\n <NTag\r\n :type=\"(STATUS_TYPE[file.status] as any) || 'default'\"\r\n size=\"tiny\"\r\n :bordered=\"false\"\r\n >\r\n {{ STATUS_TEXT[file.status] || file.status }}\r\n </NTag>\r\n <span v-if=\"file.status === 'instant'\" class=\"file-list__instant\">\r\n ⚡\r\n </span>\r\n </div>\r\n\r\n <!-- 进度条 -->\r\n <NProgress\r\n v-if=\"file.status === 'uploading' || file.status === 'hashing'\"\r\n :percentage=\"file.status === 'hashing' ? hashProgress : file.percent\"\r\n :height=\"4\"\r\n :border-radius=\"2\"\r\n :show-indicator=\"false\"\r\n :status=\"file.status === 'hashing' ? 'info' : 'success'\"\r\n />\r\n\r\n <!-- 分片进度描述 -->\r\n <div\r\n v-if=\"file.chunkProgress && file.status === 'uploading'\"\r\n class=\"file-list__chunk-info\"\r\n >\r\n 分片 {{ file.chunkProgress.uploadedChunks }}/{{\r\n file.chunkProgress.totalChunks\r\n }}\r\n </div>\r\n\r\n <!-- 错误信息 -->\r\n <div\r\n v-if=\"file.status === 'error' && file.error\"\r\n class=\"file-list__error\"\r\n >\r\n {{ file.error }}\r\n </div>\r\n </div>\r\n\r\n <!-- 操作按钮 -->\r\n <div class=\"file-list__actions\">\r\n <NButton\r\n v-if=\"file.status === 'error'\"\r\n text\r\n type=\"warning\"\r\n size=\"tiny\"\r\n @click=\"emit('retry', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:refresh\" />\r\n </template>\r\n </NButton>\r\n <NButton\r\n text\r\n type=\"error\"\r\n size=\"tiny\"\r\n @click=\"emit('remove', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n </TransitionGroup>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NProgress, NButton, NTag, NEllipsis } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { UploadFileItem } from \"../types\";\r\nimport {\r\n STATUS_TEXT,\r\n STATUS_TYPE,\r\n getFileIcon,\r\n formatFileSize,\r\n} from \"../constants\";\r\n\r\ndefineProps<{\r\n /** 文件列表 */\r\n fileList: UploadFileItem[];\r\n /** 是否显示缩略图 */\r\n showThumbnail?: boolean;\r\n /** hash 进度(全局共享) */\r\n hashProgress?: number;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n remove: [uid: string];\r\n retry: [uid: string];\r\n}>();\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.file-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n margin-top: 12px;\r\n\r\n &__item {\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-start;\r\n padding: 10px 12px;\r\n border-radius: 8px;\r\n border: 1px solid var(--border-color);\r\n background: var(--card-color);\r\n transition: all 0.25s ease;\r\n\r\n &:hover {\r\n border-color: color-mix(\r\n in srgb,\r\n var(--primary-color) 40%,\r\n var(--border-color)\r\n );\r\n box-shadow: 0 1px 6px rgba(0, 0, 0, 0.04);\r\n }\r\n\r\n &--error {\r\n border-color: var(--error-color);\r\n background: color-mix(in srgb, var(--error-color) 4%, var(--card-color));\r\n }\r\n\r\n &--success,\r\n &--instant {\r\n border-color: color-mix(\r\n in srgb,\r\n var(--success-color) 30%,\r\n var(--border-color)\r\n );\r\n }\r\n }\r\n\r\n &__thumb {\r\n flex-shrink: 0;\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 6px;\r\n overflow: hidden;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background: var(--body-color);\r\n }\r\n\r\n &__thumb-img {\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n }\r\n\r\n &__thumb-icon {\r\n font-size: 22px;\r\n color: var(--text-color-3);\r\n }\r\n\r\n &__info {\r\n flex: 1;\r\n min-width: 0;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n }\r\n\r\n &__name {\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--text-color-1);\r\n }\r\n\r\n &__meta {\r\n display: flex;\r\n gap: 8px;\r\n align-items: center;\r\n font-size: 12px;\r\n }\r\n\r\n &__size {\r\n color: var(--text-color-3);\r\n }\r\n\r\n &__instant {\r\n font-size: 14px;\r\n }\r\n\r\n &__chunk-info {\r\n font-size: 11px;\r\n color: var(--text-color-4);\r\n }\r\n\r\n &__error {\r\n font-size: 12px;\r\n color: var(--error-color);\r\n }\r\n\r\n &__actions {\r\n flex-shrink: 0;\r\n display: flex;\r\n gap: 4px;\r\n align-items: center;\r\n }\r\n}\r\n\r\n/* ─── 过渡动画 ─────────────────────────────── */\r\n\r\n.file-list-enter-active,\r\n.file-list-leave-active {\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.file-list-enter-from {\r\n opacity: 0;\r\n transform: translateX(-20px);\r\n}\r\n\r\n.file-list-leave-to {\r\n opacity: 0;\r\n transform: translateX(20px);\r\n}\r\n\r\n.file-list-move {\r\n transition: transform 0.3s ease;\r\n}\r\n</style>\r\n","<template>\r\n <TransitionGroup name=\"file-list\" tag=\"div\" class=\"file-list\">\r\n <div\r\n v-for=\"file in fileList\"\r\n :key=\"file.uid\"\r\n class=\"file-list__item\"\r\n :class=\"`file-list__item--${file.status}`\"\r\n >\r\n <!-- 缩略图 / 图标 -->\r\n <div class=\"file-list__thumb\">\r\n <img\r\n v-if=\"showThumbnail && file.thumbUrl\"\r\n :src=\"file.thumbUrl\"\r\n :alt=\"file.name\"\r\n class=\"file-list__thumb-img\"\r\n />\r\n <C_Icon\r\n v-else\r\n :name=\"getFileIcon(file.type)\"\r\n class=\"file-list__thumb-icon\"\r\n />\r\n </div>\r\n\r\n <!-- 文件信息 -->\r\n <div class=\"file-list__info\">\r\n <div class=\"file-list__name\">\r\n <NEllipsis :line-clamp=\"1\">\r\n {{ file.name }}\r\n </NEllipsis>\r\n </div>\r\n <div class=\"file-list__meta\">\r\n <span class=\"file-list__size\">{{ formatFileSize(file.size) }}</span>\r\n <NTag\r\n :type=\"(STATUS_TYPE[file.status] as any) || 'default'\"\r\n size=\"tiny\"\r\n :bordered=\"false\"\r\n >\r\n {{ STATUS_TEXT[file.status] || file.status }}\r\n </NTag>\r\n <span v-if=\"file.status === 'instant'\" class=\"file-list__instant\">\r\n ⚡\r\n </span>\r\n </div>\r\n\r\n <!-- 进度条 -->\r\n <NProgress\r\n v-if=\"file.status === 'uploading' || file.status === 'hashing'\"\r\n :percentage=\"file.status === 'hashing' ? hashProgress : file.percent\"\r\n :height=\"4\"\r\n :border-radius=\"2\"\r\n :show-indicator=\"false\"\r\n :status=\"file.status === 'hashing' ? 'info' : 'success'\"\r\n />\r\n\r\n <!-- 分片进度描述 -->\r\n <div\r\n v-if=\"file.chunkProgress && file.status === 'uploading'\"\r\n class=\"file-list__chunk-info\"\r\n >\r\n 分片 {{ file.chunkProgress.uploadedChunks }}/{{\r\n file.chunkProgress.totalChunks\r\n }}\r\n </div>\r\n\r\n <!-- 错误信息 -->\r\n <div\r\n v-if=\"file.status === 'error' && file.error\"\r\n class=\"file-list__error\"\r\n >\r\n {{ file.error }}\r\n </div>\r\n </div>\r\n\r\n <!-- 操作按钮 -->\r\n <div class=\"file-list__actions\">\r\n <NButton\r\n v-if=\"file.status === 'error'\"\r\n text\r\n type=\"warning\"\r\n size=\"tiny\"\r\n @click=\"emit('retry', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:refresh\" />\r\n </template>\r\n </NButton>\r\n <NButton\r\n text\r\n type=\"error\"\r\n size=\"tiny\"\r\n @click=\"emit('remove', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n </TransitionGroup>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { NProgress, NButton, NTag, NEllipsis } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { UploadFileItem } from \"../types\";\r\nimport {\r\n STATUS_TEXT,\r\n STATUS_TYPE,\r\n getFileIcon,\r\n formatFileSize,\r\n} from \"../constants\";\r\n\r\ndefineProps<{\r\n /** 文件列表 */\r\n fileList: UploadFileItem[];\r\n /** 是否显示缩略图 */\r\n showThumbnail?: boolean;\r\n /** hash 进度(全局共享) */\r\n hashProgress?: number;\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n remove: [uid: string];\r\n retry: [uid: string];\r\n}>();\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.file-list {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 8px;\r\n margin-top: 12px;\r\n\r\n &__item {\r\n display: flex;\r\n gap: 12px;\r\n align-items: flex-start;\r\n padding: 10px 12px;\r\n border-radius: 8px;\r\n border: 1px solid var(--border-color);\r\n background: var(--card-color);\r\n transition: all 0.25s ease;\r\n\r\n &:hover {\r\n border-color: color-mix(\r\n in srgb,\r\n var(--primary-color) 40%,\r\n var(--border-color)\r\n );\r\n box-shadow: 0 1px 6px rgba(0, 0, 0, 0.04);\r\n }\r\n\r\n &--error {\r\n border-color: var(--error-color);\r\n background: color-mix(in srgb, var(--error-color) 4%, var(--card-color));\r\n }\r\n\r\n &--success,\r\n &--instant {\r\n border-color: color-mix(\r\n in srgb,\r\n var(--success-color) 30%,\r\n var(--border-color)\r\n );\r\n }\r\n }\r\n\r\n &__thumb {\r\n flex-shrink: 0;\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 6px;\r\n overflow: hidden;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background: var(--body-color);\r\n }\r\n\r\n &__thumb-img {\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n }\r\n\r\n &__thumb-icon {\r\n font-size: 22px;\r\n color: var(--text-color-3);\r\n }\r\n\r\n &__info {\r\n flex: 1;\r\n min-width: 0;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 4px;\r\n }\r\n\r\n &__name {\r\n font-size: 13px;\r\n font-weight: 500;\r\n color: var(--text-color-1);\r\n }\r\n\r\n &__meta {\r\n display: flex;\r\n gap: 8px;\r\n align-items: center;\r\n font-size: 12px;\r\n }\r\n\r\n &__size {\r\n color: var(--text-color-3);\r\n }\r\n\r\n &__instant {\r\n font-size: 14px;\r\n }\r\n\r\n &__chunk-info {\r\n font-size: 11px;\r\n color: var(--text-color-4);\r\n }\r\n\r\n &__error {\r\n font-size: 12px;\r\n color: var(--error-color);\r\n }\r\n\r\n &__actions {\r\n flex-shrink: 0;\r\n display: flex;\r\n gap: 4px;\r\n align-items: center;\r\n }\r\n}\r\n\r\n/* ─── 过渡动画 ─────────────────────────────── */\r\n\r\n.file-list-enter-active,\r\n.file-list-leave-active {\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.file-list-enter-from {\r\n opacity: 0;\r\n transform: translateX(-20px);\r\n}\r\n\r\n.file-list-leave-to {\r\n opacity: 0;\r\n transform: translateX(20px);\r\n}\r\n\r\n.file-list-move {\r\n transition: transform 0.3s ease;\r\n}\r\n</style>\r\n","<template>\r\n <div v-if=\"imageFiles.length > 0\" class=\"image-preview\">\r\n <div\r\n v-for=\"file in imageFiles\"\r\n :key=\"file.uid\"\r\n class=\"image-preview__item\"\r\n :class=\"`image-preview__item--${file.status}`\"\r\n >\r\n <img\r\n v-if=\"file.thumbUrl\"\r\n :src=\"file.thumbUrl\"\r\n :alt=\"file.name\"\r\n class=\"image-preview__img\"\r\n />\r\n\r\n <!-- 遮罩层 -->\r\n <div class=\"image-preview__overlay\">\r\n <!-- 上传中进度 -->\r\n <div\r\n v-if=\"file.status === 'uploading' || file.status === 'hashing'\"\r\n class=\"image-preview__progress\"\r\n >\r\n <NProgress\r\n type=\"circle\"\r\n :percentage=\"file.percent\"\r\n :stroke-width=\"3\"\r\n style=\"width: 40px\"\r\n />\r\n </div>\r\n\r\n <!-- 状态图标 -->\r\n <C_Icon\r\n v-else-if=\"file.status === 'success' || file.status === 'instant'\"\r\n name=\"mdi:check-circle\"\r\n class=\"image-preview__status-icon image-preview__status-icon--success\"\r\n />\r\n <C_Icon\r\n v-else-if=\"file.status === 'error'\"\r\n name=\"mdi:alert-circle\"\r\n class=\"image-preview__status-icon image-preview__status-icon--error\"\r\n />\r\n\r\n <!-- 操作 -->\r\n <div class=\"image-preview__actions\">\r\n <NButton\r\n v-if=\"file.status === 'error'\"\r\n circle\r\n size=\"tiny\"\r\n type=\"warning\"\r\n @click=\"emit('retry', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:refresh\" />\r\n </template>\r\n </NButton>\r\n <NButton\r\n circle\r\n size=\"tiny\"\r\n type=\"error\"\r\n @click=\"emit('remove', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 文件名 -->\r\n <div class=\"image-preview__name\">\r\n <NEllipsis :line-clamp=\"1\">\r\n {{ file.name }}\r\n </NEllipsis>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport { NProgress, NButton, NEllipsis } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { UploadFileItem } from \"../types\";\r\n\r\nconst props = defineProps<{\r\n /** 文件列表 */\r\n fileList: UploadFileItem[];\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n remove: [uid: string];\r\n retry: [uid: string];\r\n}>();\r\n\r\n/** 过滤出图片文件 */\r\nconst imageFiles = computed(() =>\r\n props.fileList.filter((f) => f.type?.startsWith(\"image/\")),\r\n);\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.image-preview {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));\r\n gap: 10px;\r\n margin-top: 12px;\r\n\r\n &__item {\r\n position: relative;\r\n aspect-ratio: 1;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n background: var(--body-color);\r\n\r\n &--error {\r\n border-color: var(--error-color);\r\n }\r\n\r\n &--success,\r\n &--instant {\r\n border-color: color-mix(\r\n in srgb,\r\n var(--success-color) 30%,\r\n var(--border-color)\r\n );\r\n }\r\n\r\n &:hover .image-preview__overlay {\r\n opacity: 1;\r\n }\r\n }\r\n\r\n &__img {\r\n display: block;\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n }\r\n\r\n &__overlay {\r\n position: absolute;\r\n inset: 0;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 8px;\r\n background: rgba(0, 0, 0, 0.45);\r\n opacity: 0;\r\n transition: opacity 0.2s ease;\r\n }\r\n\r\n /* 上传中始终显示遮罩 */\r\n &__item--uploading .image-preview__overlay,\r\n &__item--hashing .image-preview__overlay,\r\n &__item--error .image-preview__overlay {\r\n opacity: 1;\r\n }\r\n\r\n &__progress {\r\n :deep(.n-progress) {\r\n --n-fill-color: #fff;\r\n }\r\n }\r\n\r\n &__status-icon {\r\n font-size: 28px;\r\n\r\n &--success {\r\n color: var(--success-color);\r\n }\r\n\r\n &--error {\r\n color: var(--error-color);\r\n }\r\n }\r\n\r\n &__actions {\r\n display: flex;\r\n gap: 6px;\r\n }\r\n\r\n &__name {\r\n position: absolute;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n padding: 2px 6px;\r\n font-size: 11px;\r\n color: #fff;\r\n background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));\r\n text-align: center;\r\n }\r\n}\r\n</style>\r\n","<template>\r\n <div v-if=\"imageFiles.length > 0\" class=\"image-preview\">\r\n <div\r\n v-for=\"file in imageFiles\"\r\n :key=\"file.uid\"\r\n class=\"image-preview__item\"\r\n :class=\"`image-preview__item--${file.status}`\"\r\n >\r\n <img\r\n v-if=\"file.thumbUrl\"\r\n :src=\"file.thumbUrl\"\r\n :alt=\"file.name\"\r\n class=\"image-preview__img\"\r\n />\r\n\r\n <!-- 遮罩层 -->\r\n <div class=\"image-preview__overlay\">\r\n <!-- 上传中进度 -->\r\n <div\r\n v-if=\"file.status === 'uploading' || file.status === 'hashing'\"\r\n class=\"image-preview__progress\"\r\n >\r\n <NProgress\r\n type=\"circle\"\r\n :percentage=\"file.percent\"\r\n :stroke-width=\"3\"\r\n style=\"width: 40px\"\r\n />\r\n </div>\r\n\r\n <!-- 状态图标 -->\r\n <C_Icon\r\n v-else-if=\"file.status === 'success' || file.status === 'instant'\"\r\n name=\"mdi:check-circle\"\r\n class=\"image-preview__status-icon image-preview__status-icon--success\"\r\n />\r\n <C_Icon\r\n v-else-if=\"file.status === 'error'\"\r\n name=\"mdi:alert-circle\"\r\n class=\"image-preview__status-icon image-preview__status-icon--error\"\r\n />\r\n\r\n <!-- 操作 -->\r\n <div class=\"image-preview__actions\">\r\n <NButton\r\n v-if=\"file.status === 'error'\"\r\n circle\r\n size=\"tiny\"\r\n type=\"warning\"\r\n @click=\"emit('retry', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:refresh\" />\r\n </template>\r\n </NButton>\r\n <NButton\r\n circle\r\n size=\"tiny\"\r\n type=\"error\"\r\n @click=\"emit('remove', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 文件名 -->\r\n <div class=\"image-preview__name\">\r\n <NEllipsis :line-clamp=\"1\">\r\n {{ file.name }}\r\n </NEllipsis>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport { NProgress, NButton, NEllipsis } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { UploadFileItem } from \"../types\";\r\n\r\nconst props = defineProps<{\r\n /** 文件列表 */\r\n fileList: UploadFileItem[];\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n remove: [uid: string];\r\n retry: [uid: string];\r\n}>();\r\n\r\n/** 过滤出图片文件 */\r\nconst imageFiles = computed(() =>\r\n props.fileList.filter((f) => f.type?.startsWith(\"image/\")),\r\n);\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.image-preview {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));\r\n gap: 10px;\r\n margin-top: 12px;\r\n\r\n &__item {\r\n position: relative;\r\n aspect-ratio: 1;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n background: var(--body-color);\r\n\r\n &--error {\r\n border-color: var(--error-color);\r\n }\r\n\r\n &--success,\r\n &--instant {\r\n border-color: color-mix(\r\n in srgb,\r\n var(--success-color) 30%,\r\n var(--border-color)\r\n );\r\n }\r\n\r\n &:hover .image-preview__overlay {\r\n opacity: 1;\r\n }\r\n }\r\n\r\n &__img {\r\n display: block;\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n }\r\n\r\n &__overlay {\r\n position: absolute;\r\n inset: 0;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 8px;\r\n background: rgba(0, 0, 0, 0.45);\r\n opacity: 0;\r\n transition: opacity 0.2s ease;\r\n }\r\n\r\n /* 上传中始终显示遮罩 */\r\n &__item--uploading .image-preview__overlay,\r\n &__item--hashing .image-preview__overlay,\r\n &__item--error .image-preview__overlay {\r\n opacity: 1;\r\n }\r\n\r\n &__progress {\r\n :deep(.n-progress) {\r\n --n-fill-color: #fff;\r\n }\r\n }\r\n\r\n &__status-icon {\r\n font-size: 28px;\r\n\r\n &--success {\r\n color: var(--success-color);\r\n }\r\n\r\n &--error {\r\n color: var(--error-color);\r\n }\r\n }\r\n\r\n &__actions {\r\n display: flex;\r\n gap: 6px;\r\n }\r\n\r\n &__name {\r\n position: absolute;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n padding: 2px 6px;\r\n font-size: 11px;\r\n color: #fff;\r\n background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));\r\n text-align: center;\r\n }\r\n}\r\n</style>\r\n","<template>\r\n <div v-if=\"imageFiles.length > 0\" class=\"image-preview\">\r\n <div\r\n v-for=\"file in imageFiles\"\r\n :key=\"file.uid\"\r\n class=\"image-preview__item\"\r\n :class=\"`image-preview__item--${file.status}`\"\r\n >\r\n <img\r\n v-if=\"file.thumbUrl\"\r\n :src=\"file.thumbUrl\"\r\n :alt=\"file.name\"\r\n class=\"image-preview__img\"\r\n />\r\n\r\n <!-- 遮罩层 -->\r\n <div class=\"image-preview__overlay\">\r\n <!-- 上传中进度 -->\r\n <div\r\n v-if=\"file.status === 'uploading' || file.status === 'hashing'\"\r\n class=\"image-preview__progress\"\r\n >\r\n <NProgress\r\n type=\"circle\"\r\n :percentage=\"file.percent\"\r\n :stroke-width=\"3\"\r\n style=\"width: 40px\"\r\n />\r\n </div>\r\n\r\n <!-- 状态图标 -->\r\n <C_Icon\r\n v-else-if=\"file.status === 'success' || file.status === 'instant'\"\r\n name=\"mdi:check-circle\"\r\n class=\"image-preview__status-icon image-preview__status-icon--success\"\r\n />\r\n <C_Icon\r\n v-else-if=\"file.status === 'error'\"\r\n name=\"mdi:alert-circle\"\r\n class=\"image-preview__status-icon image-preview__status-icon--error\"\r\n />\r\n\r\n <!-- 操作 -->\r\n <div class=\"image-preview__actions\">\r\n <NButton\r\n v-if=\"file.status === 'error'\"\r\n circle\r\n size=\"tiny\"\r\n type=\"warning\"\r\n @click=\"emit('retry', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:refresh\" />\r\n </template>\r\n </NButton>\r\n <NButton\r\n circle\r\n size=\"tiny\"\r\n type=\"error\"\r\n @click=\"emit('remove', file.uid)\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"mdi:close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 文件名 -->\r\n <div class=\"image-preview__name\">\r\n <NEllipsis :line-clamp=\"1\">\r\n {{ file.name }}\r\n </NEllipsis>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { computed } from \"vue\";\r\nimport { NProgress, NButton, NEllipsis } from \"naive-ui\";\r\nimport C_Icon from \"../../C_Icon/index.vue\";\r\nimport type { UploadFileItem } from \"../types\";\r\n\r\nconst props = defineProps<{\r\n /** 文件列表 */\r\n fileList: UploadFileItem[];\r\n}>();\r\n\r\nconst emit = defineEmits<{\r\n remove: [uid: string];\r\n retry: [uid: string];\r\n}>();\r\n\r\n/** 过滤出图片文件 */\r\nconst imageFiles = computed(() =>\r\n props.fileList.filter((f) => f.type?.startsWith(\"image/\")),\r\n);\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.image-preview {\r\n display: grid;\r\n grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));\r\n gap: 10px;\r\n margin-top: 12px;\r\n\r\n &__item {\r\n position: relative;\r\n aspect-ratio: 1;\r\n border-radius: 8px;\r\n overflow: hidden;\r\n border: 1px solid var(--border-color);\r\n background: var(--body-color);\r\n\r\n &--error {\r\n border-color: var(--error-color);\r\n }\r\n\r\n &--success,\r\n &--instant {\r\n border-color: color-mix(\r\n in srgb,\r\n var(--success-color) 30%,\r\n var(--border-color)\r\n );\r\n }\r\n\r\n &:hover .image-preview__overlay {\r\n opacity: 1;\r\n }\r\n }\r\n\r\n &__img {\r\n display: block;\r\n width: 100%;\r\n height: 100%;\r\n object-fit: cover;\r\n }\r\n\r\n &__overlay {\r\n position: absolute;\r\n inset: 0;\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 8px;\r\n background: rgba(0, 0, 0, 0.45);\r\n opacity: 0;\r\n transition: opacity 0.2s ease;\r\n }\r\n\r\n /* 上传中始终显示遮罩 */\r\n &__item--uploading .image-preview__overlay,\r\n &__item--hashing .image-preview__overlay,\r\n &__item--error .image-preview__overlay {\r\n opacity: 1;\r\n }\r\n\r\n &__progress {\r\n :deep(.n-progress) {\r\n --n-fill-color: #fff;\r\n }\r\n }\r\n\r\n &__status-icon {\r\n font-size: 28px;\r\n\r\n &--success {\r\n color: var(--success-color);\r\n }\r\n\r\n &--error {\r\n color: var(--error-color);\r\n }\r\n }\r\n\r\n &__actions {\r\n display: flex;\r\n gap: 6px;\r\n }\r\n\r\n &__name {\r\n position: absolute;\r\n bottom: 0;\r\n left: 0;\r\n right: 0;\r\n padding: 2px 6px;\r\n font-size: 11px;\r\n color: #fff;\r\n background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));\r\n text-align: center;\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 增强型上传组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-upload\">\r\n <!-- 上传区域 -->\r\n <UploadArea\r\n ref=\"uploadAreaRef\"\r\n :accept=\"props.accept\"\r\n :multiple=\"props.multiple\"\r\n :directory=\"props.directory\"\r\n :disabled=\"props.disabled\"\r\n :draggable=\"props.draggable\"\r\n :pasteable=\"props.pasteable\"\r\n :tip=\"props.tip\"\r\n @files=\"handleFiles\"\r\n >\r\n <slot name=\"area\" />\r\n </UploadArea>\r\n\r\n <!-- 总进度条 -->\r\n <div v-if=\"showTotalProgress\" class=\"c-upload__total-progress\">\r\n <div class=\"c-upload__total-label\">\r\n <span>总进度</span>\r\n <span>{{ totalPercent }}%</span>\r\n </div>\r\n <NProgress\r\n :percentage=\"totalPercent\"\r\n :height=\"6\"\r\n :border-radius=\"3\"\r\n :show-indicator=\"false\"\r\n status=\"success\"\r\n />\r\n </div>\r\n\r\n <!-- 图片预览模式 -->\r\n <ImagePreview\r\n v-if=\"props.listType === 'image'\"\r\n :file-list=\"fileList\"\r\n @remove=\"handleRemove\"\r\n @retry=\"handleRetry\"\r\n />\r\n\r\n <!-- 文件列表模式 -->\r\n <FileList\r\n v-else-if=\"props.showFileList && fileList.length > 0\"\r\n :file-list=\"fileList\"\r\n :show-thumbnail=\"props.showThumbnail\"\r\n :hash-progress=\"hashProgress\"\r\n @remove=\"handleRemove\"\r\n @retry=\"handleRetry\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NProgress } from \"naive-ui\";\r\nimport type { UploadProps, UploadExpose, UploadFileItem } from \"./types\";\r\nimport { DEFAULT_CHUNK_SIZE, DEFAULT_CONCURRENCY } from \"./constants\";\r\nimport { useUploadCore } from \"./composables/useUploadCore\";\r\nimport UploadArea from \"./components/UploadArea.vue\";\r\nimport FileList from \"./components/FileList.vue\";\r\nimport ImagePreview from \"./components/ImagePreview.vue\";\r\n\r\nconst props = withDefaults(defineProps<UploadProps>(), {\r\n action: \"\",\r\n headers: () => ({}),\r\n data: () => ({}),\r\n multiple: false,\r\n directory: false,\r\n accept: \"\",\r\n maxSize: 0,\r\n maxCount: 0,\r\n disabled: false,\r\n draggable: true,\r\n pasteable: false,\r\n chunked: false,\r\n chunkSize: DEFAULT_CHUNK_SIZE,\r\n concurrency: DEFAULT_CONCURRENCY,\r\n showFileList: true,\r\n listType: \"text\",\r\n showThumbnail: true,\r\n tip: \"\",\r\n defaultFileList: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n change: [fileList: UploadFileItem[]];\r\n success: [file: UploadFileItem, response: any];\r\n error: [file: UploadFileItem, error: Error];\r\n progress: [file: UploadFileItem, percent: number];\r\n remove: [file: UploadFileItem];\r\n finish: [fileList: UploadFileItem[]];\r\n exceed: [files: File[], fileList: UploadFileItem[]];\r\n}>();\r\n\r\nconst uploadAreaRef = ref<InstanceType<typeof UploadArea>>();\r\n\r\nconst {\r\n fileList,\r\n totalPercent,\r\n hashProgress,\r\n addFiles,\r\n removeFile,\r\n clearAll,\r\n startUpload,\r\n pauseAll,\r\n resumeAll,\r\n retryFile,\r\n processFile,\r\n getSuccessList,\r\n} = useUploadCore(props);\r\n\r\n// ─── 是否显示总进度 ──────────────────────────\r\n\r\nconst showTotalProgress = computed(() => {\r\n return fileList.value.some(\r\n (f) => f.status === \"uploading\" || f.status === \"hashing\",\r\n );\r\n});\r\n\r\n// ─── 文件接收处理 ─────────────────────────────\r\n\r\n/** 处理接收到的文件 */\r\nfunction handleFiles(files: File[]) {\r\n // 数量超限\r\n if (props.maxCount && fileList.value.length + files.length > props.maxCount) {\r\n emit(\"exceed\", files, fileList.value);\r\n const available = props.maxCount - fileList.value.length;\r\n if (available <= 0) return;\r\n files = files.slice(0, available);\r\n }\r\n\r\n // 大小校验过滤\r\n if (props.maxSize) {\r\n files = files.filter((f) => f.size <= props.maxSize!);\r\n }\r\n\r\n const items = addFiles(files);\r\n emit(\"change\", fileList.value);\r\n\r\n // 自动上传\r\n items.forEach(processFile);\r\n}\r\n\r\n// ─── 移除 & 重试 ─────────────────────────────\r\n\r\n/** 移除文件 */\r\nfunction handleRemove(uid: string) {\r\n const file = fileList.value.find((f) => f.uid === uid);\r\n if (file) {\r\n emit(\"remove\", file);\r\n removeFile(uid);\r\n emit(\"change\", fileList.value);\r\n }\r\n}\r\n\r\n/** 重试上传 */\r\nfunction handleRetry(uid: string) {\r\n retryFile(uid);\r\n}\r\n\r\n// ─── 监听文件状态变化触发事件 ─────────────────\r\n\r\nwatch(\r\n fileList,\r\n (list) => {\r\n // 检查是否全部完成\r\n const allDone =\r\n list.length > 0 &&\r\n list.every(\r\n (f) =>\r\n f.status === \"success\" ||\r\n f.status === \"instant\" ||\r\n f.status === \"error\",\r\n );\r\n if (allDone) {\r\n emit(\"finish\", list);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\n// ─── Expose ──────────────────────────────────\r\n\r\ndefineExpose<UploadExpose>({\r\n selectFiles: () => uploadAreaRef.value?.triggerSelect(),\r\n startUpload,\r\n pauseAll,\r\n resumeAll,\r\n clearAll: () => {\r\n clearAll();\r\n emit(\"change\", fileList.value);\r\n },\r\n removeFile: (uid: string) => {\r\n handleRemove(uid);\r\n },\r\n retryFile,\r\n getFileList: () => fileList.value,\r\n getSuccessList,\r\n /** 总体进度 */\r\n get totalPercent() {\r\n return totalPercent.value;\r\n },\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.c-upload {\r\n width: 100%;\r\n\r\n &__total-progress {\r\n margin-top: 12px;\r\n padding: 8px 12px;\r\n border-radius: 8px;\r\n background: var(--body-color);\r\n border: 1px solid var(--border-color);\r\n }\r\n\r\n &__total-label {\r\n display: flex;\r\n justify-content: space-between;\r\n margin-bottom: 6px;\r\n font-size: 12px;\r\n color: var(--text-color-3);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 增强型上传组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-upload\">\r\n <!-- 上传区域 -->\r\n <UploadArea\r\n ref=\"uploadAreaRef\"\r\n :accept=\"props.accept\"\r\n :multiple=\"props.multiple\"\r\n :directory=\"props.directory\"\r\n :disabled=\"props.disabled\"\r\n :draggable=\"props.draggable\"\r\n :pasteable=\"props.pasteable\"\r\n :tip=\"props.tip\"\r\n @files=\"handleFiles\"\r\n >\r\n <slot name=\"area\" />\r\n </UploadArea>\r\n\r\n <!-- 总进度条 -->\r\n <div v-if=\"showTotalProgress\" class=\"c-upload__total-progress\">\r\n <div class=\"c-upload__total-label\">\r\n <span>总进度</span>\r\n <span>{{ totalPercent }}%</span>\r\n </div>\r\n <NProgress\r\n :percentage=\"totalPercent\"\r\n :height=\"6\"\r\n :border-radius=\"3\"\r\n :show-indicator=\"false\"\r\n status=\"success\"\r\n />\r\n </div>\r\n\r\n <!-- 图片预览模式 -->\r\n <ImagePreview\r\n v-if=\"props.listType === 'image'\"\r\n :file-list=\"fileList\"\r\n @remove=\"handleRemove\"\r\n @retry=\"handleRetry\"\r\n />\r\n\r\n <!-- 文件列表模式 -->\r\n <FileList\r\n v-else-if=\"props.showFileList && fileList.length > 0\"\r\n :file-list=\"fileList\"\r\n :show-thumbnail=\"props.showThumbnail\"\r\n :hash-progress=\"hashProgress\"\r\n @remove=\"handleRemove\"\r\n @retry=\"handleRetry\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NProgress } from \"naive-ui\";\r\nimport type { UploadProps, UploadExpose, UploadFileItem } from \"./types\";\r\nimport { DEFAULT_CHUNK_SIZE, DEFAULT_CONCURRENCY } from \"./constants\";\r\nimport { useUploadCore } from \"./composables/useUploadCore\";\r\nimport UploadArea from \"./components/UploadArea.vue\";\r\nimport FileList from \"./components/FileList.vue\";\r\nimport ImagePreview from \"./components/ImagePreview.vue\";\r\n\r\nconst props = withDefaults(defineProps<UploadProps>(), {\r\n action: \"\",\r\n headers: () => ({}),\r\n data: () => ({}),\r\n multiple: false,\r\n directory: false,\r\n accept: \"\",\r\n maxSize: 0,\r\n maxCount: 0,\r\n disabled: false,\r\n draggable: true,\r\n pasteable: false,\r\n chunked: false,\r\n chunkSize: DEFAULT_CHUNK_SIZE,\r\n concurrency: DEFAULT_CONCURRENCY,\r\n showFileList: true,\r\n listType: \"text\",\r\n showThumbnail: true,\r\n tip: \"\",\r\n defaultFileList: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n change: [fileList: UploadFileItem[]];\r\n success: [file: UploadFileItem, response: any];\r\n error: [file: UploadFileItem, error: Error];\r\n progress: [file: UploadFileItem, percent: number];\r\n remove: [file: UploadFileItem];\r\n finish: [fileList: UploadFileItem[]];\r\n exceed: [files: File[], fileList: UploadFileItem[]];\r\n}>();\r\n\r\nconst uploadAreaRef = ref<InstanceType<typeof UploadArea>>();\r\n\r\nconst {\r\n fileList,\r\n totalPercent,\r\n hashProgress,\r\n addFiles,\r\n removeFile,\r\n clearAll,\r\n startUpload,\r\n pauseAll,\r\n resumeAll,\r\n retryFile,\r\n processFile,\r\n getSuccessList,\r\n} = useUploadCore(props);\r\n\r\n// ─── 是否显示总进度 ──────────────────────────\r\n\r\nconst showTotalProgress = computed(() => {\r\n return fileList.value.some(\r\n (f) => f.status === \"uploading\" || f.status === \"hashing\",\r\n );\r\n});\r\n\r\n// ─── 文件接收处理 ─────────────────────────────\r\n\r\n/** 处理接收到的文件 */\r\nfunction handleFiles(files: File[]) {\r\n // 数量超限\r\n if (props.maxCount && fileList.value.length + files.length > props.maxCount) {\r\n emit(\"exceed\", files, fileList.value);\r\n const available = props.maxCount - fileList.value.length;\r\n if (available <= 0) return;\r\n files = files.slice(0, available);\r\n }\r\n\r\n // 大小校验过滤\r\n if (props.maxSize) {\r\n files = files.filter((f) => f.size <= props.maxSize!);\r\n }\r\n\r\n const items = addFiles(files);\r\n emit(\"change\", fileList.value);\r\n\r\n // 自动上传\r\n items.forEach(processFile);\r\n}\r\n\r\n// ─── 移除 & 重试 ─────────────────────────────\r\n\r\n/** 移除文件 */\r\nfunction handleRemove(uid: string) {\r\n const file = fileList.value.find((f) => f.uid === uid);\r\n if (file) {\r\n emit(\"remove\", file);\r\n removeFile(uid);\r\n emit(\"change\", fileList.value);\r\n }\r\n}\r\n\r\n/** 重试上传 */\r\nfunction handleRetry(uid: string) {\r\n retryFile(uid);\r\n}\r\n\r\n// ─── 监听文件状态变化触发事件 ─────────────────\r\n\r\nwatch(\r\n fileList,\r\n (list) => {\r\n // 检查是否全部完成\r\n const allDone =\r\n list.length > 0 &&\r\n list.every(\r\n (f) =>\r\n f.status === \"success\" ||\r\n f.status === \"instant\" ||\r\n f.status === \"error\",\r\n );\r\n if (allDone) {\r\n emit(\"finish\", list);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\n// ─── Expose ──────────────────────────────────\r\n\r\ndefineExpose<UploadExpose>({\r\n selectFiles: () => uploadAreaRef.value?.triggerSelect(),\r\n startUpload,\r\n pauseAll,\r\n resumeAll,\r\n clearAll: () => {\r\n clearAll();\r\n emit(\"change\", fileList.value);\r\n },\r\n removeFile: (uid: string) => {\r\n handleRemove(uid);\r\n },\r\n retryFile,\r\n getFileList: () => fileList.value,\r\n getSuccessList,\r\n /** 总体进度 */\r\n get totalPercent() {\r\n return totalPercent.value;\r\n },\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.c-upload {\r\n width: 100%;\r\n\r\n &__total-progress {\r\n margin-top: 12px;\r\n padding: 8px 12px;\r\n border-radius: 8px;\r\n background: var(--body-color);\r\n border: 1px solid var(--border-color);\r\n }\r\n\r\n &__total-label {\r\n display: flex;\r\n justify-content: space-between;\r\n margin-bottom: 6px;\r\n font-size: 12px;\r\n color: var(--text-color-3);\r\n }\r\n}\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2026-02-27\r\n * @Description: 增强型上传组件\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\r\n-->\r\n<template>\r\n <div class=\"c-upload\">\r\n <!-- 上传区域 -->\r\n <UploadArea\r\n ref=\"uploadAreaRef\"\r\n :accept=\"props.accept\"\r\n :multiple=\"props.multiple\"\r\n :directory=\"props.directory\"\r\n :disabled=\"props.disabled\"\r\n :draggable=\"props.draggable\"\r\n :pasteable=\"props.pasteable\"\r\n :tip=\"props.tip\"\r\n @files=\"handleFiles\"\r\n >\r\n <slot name=\"area\" />\r\n </UploadArea>\r\n\r\n <!-- 总进度条 -->\r\n <div v-if=\"showTotalProgress\" class=\"c-upload__total-progress\">\r\n <div class=\"c-upload__total-label\">\r\n <span>总进度</span>\r\n <span>{{ totalPercent }}%</span>\r\n </div>\r\n <NProgress\r\n :percentage=\"totalPercent\"\r\n :height=\"6\"\r\n :border-radius=\"3\"\r\n :show-indicator=\"false\"\r\n status=\"success\"\r\n />\r\n </div>\r\n\r\n <!-- 图片预览模式 -->\r\n <ImagePreview\r\n v-if=\"props.listType === 'image'\"\r\n :file-list=\"fileList\"\r\n @remove=\"handleRemove\"\r\n @retry=\"handleRetry\"\r\n />\r\n\r\n <!-- 文件列表模式 -->\r\n <FileList\r\n v-else-if=\"props.showFileList && fileList.length > 0\"\r\n :file-list=\"fileList\"\r\n :show-thumbnail=\"props.showThumbnail\"\r\n :hash-progress=\"hashProgress\"\r\n @remove=\"handleRemove\"\r\n @retry=\"handleRetry\"\r\n />\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NProgress } from \"naive-ui\";\r\nimport type { UploadProps, UploadExpose, UploadFileItem } from \"./types\";\r\nimport { DEFAULT_CHUNK_SIZE, DEFAULT_CONCURRENCY } from \"./constants\";\r\nimport { useUploadCore } from \"./composables/useUploadCore\";\r\nimport UploadArea from \"./components/UploadArea.vue\";\r\nimport FileList from \"./components/FileList.vue\";\r\nimport ImagePreview from \"./components/ImagePreview.vue\";\r\n\r\nconst props = withDefaults(defineProps<UploadProps>(), {\r\n action: \"\",\r\n headers: () => ({}),\r\n data: () => ({}),\r\n multiple: false,\r\n directory: false,\r\n accept: \"\",\r\n maxSize: 0,\r\n maxCount: 0,\r\n disabled: false,\r\n draggable: true,\r\n pasteable: false,\r\n chunked: false,\r\n chunkSize: DEFAULT_CHUNK_SIZE,\r\n concurrency: DEFAULT_CONCURRENCY,\r\n showFileList: true,\r\n listType: \"text\",\r\n showThumbnail: true,\r\n tip: \"\",\r\n defaultFileList: () => [],\r\n});\r\n\r\nconst emit = defineEmits<{\r\n change: [fileList: UploadFileItem[]];\r\n success: [file: UploadFileItem, response: any];\r\n error: [file: UploadFileItem, error: Error];\r\n progress: [file: UploadFileItem, percent: number];\r\n remove: [file: UploadFileItem];\r\n finish: [fileList: UploadFileItem[]];\r\n exceed: [files: File[], fileList: UploadFileItem[]];\r\n}>();\r\n\r\nconst uploadAreaRef = ref<InstanceType<typeof UploadArea>>();\r\n\r\nconst {\r\n fileList,\r\n totalPercent,\r\n hashProgress,\r\n addFiles,\r\n removeFile,\r\n clearAll,\r\n startUpload,\r\n pauseAll,\r\n resumeAll,\r\n retryFile,\r\n processFile,\r\n getSuccessList,\r\n} = useUploadCore(props);\r\n\r\n// ─── 是否显示总进度 ──────────────────────────\r\n\r\nconst showTotalProgress = computed(() => {\r\n return fileList.value.some(\r\n (f) => f.status === \"uploading\" || f.status === \"hashing\",\r\n );\r\n});\r\n\r\n// ─── 文件接收处理 ─────────────────────────────\r\n\r\n/** 处理接收到的文件 */\r\nfunction handleFiles(files: File[]) {\r\n // 数量超限\r\n if (props.maxCount && fileList.value.length + files.length > props.maxCount) {\r\n emit(\"exceed\", files, fileList.value);\r\n const available = props.maxCount - fileList.value.length;\r\n if (available <= 0) return;\r\n files = files.slice(0, available);\r\n }\r\n\r\n // 大小校验过滤\r\n if (props.maxSize) {\r\n files = files.filter((f) => f.size <= props.maxSize!);\r\n }\r\n\r\n const items = addFiles(files);\r\n emit(\"change\", fileList.value);\r\n\r\n // 自动上传\r\n items.forEach(processFile);\r\n}\r\n\r\n// ─── 移除 & 重试 ─────────────────────────────\r\n\r\n/** 移除文件 */\r\nfunction handleRemove(uid: string) {\r\n const file = fileList.value.find((f) => f.uid === uid);\r\n if (file) {\r\n emit(\"remove\", file);\r\n removeFile(uid);\r\n emit(\"change\", fileList.value);\r\n }\r\n}\r\n\r\n/** 重试上传 */\r\nfunction handleRetry(uid: string) {\r\n retryFile(uid);\r\n}\r\n\r\n// ─── 监听文件状态变化触发事件 ─────────────────\r\n\r\nwatch(\r\n fileList,\r\n (list) => {\r\n // 检查是否全部完成\r\n const allDone =\r\n list.length > 0 &&\r\n list.every(\r\n (f) =>\r\n f.status === \"success\" ||\r\n f.status === \"instant\" ||\r\n f.status === \"error\",\r\n );\r\n if (allDone) {\r\n emit(\"finish\", list);\r\n }\r\n },\r\n { deep: true },\r\n);\r\n\r\n// ─── Expose ──────────────────────────────────\r\n\r\ndefineExpose<UploadExpose>({\r\n selectFiles: () => uploadAreaRef.value?.triggerSelect(),\r\n startUpload,\r\n pauseAll,\r\n resumeAll,\r\n clearAll: () => {\r\n clearAll();\r\n emit(\"change\", fileList.value);\r\n },\r\n removeFile: (uid: string) => {\r\n handleRemove(uid);\r\n },\r\n retryFile,\r\n getFileList: () => fileList.value,\r\n getSuccessList,\r\n /** 总体进度 */\r\n get totalPercent() {\r\n return totalPercent.value;\r\n },\r\n});\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n.c-upload {\r\n width: 100%;\r\n\r\n &__total-progress {\r\n margin-top: 12px;\r\n padding: 8px 12px;\r\n border-radius: 8px;\r\n background: var(--body-color);\r\n border: 1px solid var(--border-color);\r\n }\r\n\r\n &__total-label {\r\n display: flex;\r\n justify-content: space-between;\r\n margin-bottom: 6px;\r\n font-size: 12px;\r\n color: var(--text-color-3);\r\n }\r\n}\r\n</style>\r\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"C_VideoPlayer-DYG3RL0Q.css","names":[],"sources":["../src/components/C_VideoPlayer/components/ControlBar.vue?vue&type=style&index=0&scoped=ea44a973&lang.scss","../src/components/C_VideoPlayer/components/SubtitleOverlay.vue?vue&type=style&index=0&scoped=1069c4da&lang.scss","../src/components/C_VideoPlayer/components/ChapterMarkers.vue?vue&type=style&index=0&scoped=3218c0f1&lang.scss","../src/components/C_VideoPlayer/components/BookmarkPanel.vue?vue&type=style&index=0&scoped=c353e3d6&lang.scss","../src/components/C_VideoPlayer/components/QuizOverlay.vue?vue&type=style&index=0&scoped=a8cd8fbf&lang.scss","../src/components/C_VideoPlayer/components/WatermarkOverlay.vue?vue&type=style&index=0&scoped=6928c31a&lang.scss","../src/components/C_VideoPlayer/index.vue?vue&type=style&index=0&scoped=9e9355b9&lang.scss"],"sourcesContent":[".vp-control-bar[data-v-ea44a973] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 12px;\n background: linear-gradient(rgba(0, 0, 0, 0.6), transparent);\n pointer-events: auto;\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n z-index: 100;\n opacity: 0;\n transition: opacity var(--c-transition, 0.2s ease);\n}\n.vp-control-bar[data-v-ea44a973]:hover, .c-video-player:hover .vp-control-bar[data-v-ea44a973] {\n opacity: 1;\n}\n.vp-control-bar__left[data-v-ea44a973], .vp-control-bar__center[data-v-ea44a973], .vp-control-bar__right[data-v-ea44a973] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.vp-control-bar__center[data-v-ea44a973] {\n flex: 1;\n justify-content: center;\n}","/* ========== 字幕文本 ========== */\n.vp-subtitle-overlay[data-v-1069c4da] {\n position: absolute;\n bottom: 60px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 50;\n pointer-events: none;\n max-width: 80%;\n text-align: center;\n}\n.vp-subtitle-text[data-v-1069c4da] {\n display: inline-block;\n padding: 4px 16px;\n font-size: 16px;\n line-height: 1.6;\n color: #fff;\n background: rgba(0, 0, 0, 0.65);\n border-radius: 4px;\n text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n/* ========== 字幕按钮 ========== */\n.vp-subtitle-toggle[data-v-1069c4da] {\n position: absolute;\n right: 12px;\n bottom: 46px;\n z-index: 100;\n opacity: 0;\n transition: opacity var(--c-transition, 0.2s ease);\n}\n.c-video-player:hover .vp-subtitle-toggle[data-v-1069c4da] {\n opacity: 1;\n}\n.vp-subtitle-btn[data-v-1069c4da] {\n color: rgba(255, 255, 255, 0.7);\n font-size: 12px;\n}\n.vp-subtitle-btn.is-active[data-v-1069c4da] {\n color: var(--c-primary, #18a058);\n}\n\n/* ========== 字幕菜单 ========== */\n.vp-subtitle-menu[data-v-1069c4da] {\n display: flex;\n flex-direction: column;\n min-width: 100px;\n}\n.vp-subtitle-menu__item[data-v-1069c4da] {\n padding: 6px 14px;\n cursor: pointer;\n font-size: 13px;\n border-radius: 4px;\n text-align: center;\n transition: background-color 0.2s;\n}\n.vp-subtitle-menu__item[data-v-1069c4da]:hover {\n background-color: rgba(0, 0, 0, 0.06);\n}\n.vp-subtitle-menu__item.is-active[data-v-1069c4da] {\n color: var(--c-primary, #18a058);\n font-weight: 600;\n}\n\n/* ========== 过渡 ========== */\n.vp-subtitle-fade-enter-active[data-v-1069c4da],\n.vp-subtitle-fade-leave-active[data-v-1069c4da] {\n transition: opacity 0.25s ease;\n}\n.vp-subtitle-fade-enter-from[data-v-1069c4da],\n.vp-subtitle-fade-leave-to[data-v-1069c4da] {\n opacity: 0;\n}",".vp-chapter-markers[data-v-3218c0f1] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.vp-chapter-current[data-v-3218c0f1] {\n display: flex;\n align-items: center;\n gap: 6px;\n color: #fff;\n font-size: 12px;\n}\n.vp-chapter-index[data-v-3218c0f1] {\n opacity: 0.7;\n}\n.vp-chapter-trigger[data-v-3218c0f1] {\n color: #fff;\n font-size: 12px;\n}\n.vp-chapter-icon[data-v-3218c0f1] {\n font-size: 14px;\n}\n.vp-chapter-list[data-v-3218c0f1] {\n display: flex;\n flex-direction: column;\n min-width: 240px;\n}\n.vp-chapter-item[data-v-3218c0f1] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n border-radius: 4px;\n font-size: 13px;\n transition: background-color 0.2s;\n}\n.vp-chapter-item[data-v-3218c0f1]:hover {\n background-color: rgba(0, 0, 0, 0.05);\n}\n.vp-chapter-item.is-active[data-v-3218c0f1] {\n color: var(--c-primary, #18a058);\n font-weight: 600;\n}\n.vp-chapter-item-index[data-v-3218c0f1] {\n width: 20px;\n text-align: center;\n opacity: 0.5;\n font-size: 12px;\n}\n.vp-chapter-item-title[data-v-3218c0f1] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.vp-chapter-item-time[data-v-3218c0f1] {\n opacity: 0.5;\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n}",".vp-bookmark-panel[data-v-c353e3d6] {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.vp-bookmark-add-btn[data-v-c353e3d6],\n.vp-bookmark-list-btn[data-v-c353e3d6] {\n color: #fff;\n font-size: 12px;\n}\n.vp-bookmark-list[data-v-c353e3d6] {\n display: flex;\n flex-direction: column;\n min-width: 220px;\n gap: 2px;\n}\n.vp-bookmark-item[data-v-c353e3d6] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n border-radius: 4px;\n font-size: 13px;\n}\n.vp-bookmark-item[data-v-c353e3d6]:hover {\n background-color: rgba(0, 0, 0, 0.04);\n}\n.vp-bookmark-time[data-v-c353e3d6] {\n color: var(--c-primary, #18a058);\n cursor: pointer;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.vp-bookmark-time[data-v-c353e3d6]:hover {\n text-decoration: underline;\n}\n.vp-bookmark-note[data-v-c353e3d6] {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n opacity: 0.7;\n}",".vp-quiz-overlay[data-v-a8cd8fbf] {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.7);\n z-index: 100;\n backdrop-filter: blur(4px);\n}\n.vp-quiz-card[data-v-a8cd8fbf] {\n background: var(--c-bg-card, #fff);\n border-radius: 12px;\n padding: 24px;\n max-width: 480px;\n width: 90%;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n}\n.vp-quiz-header[data-v-a8cd8fbf] {\n margin-bottom: 16px;\n}\n.vp-quiz-type-tag[data-v-a8cd8fbf] {\n display: inline-block;\n padding: 2px 8px;\n font-size: 12px;\n background: var(--c-primary, #18a058);\n color: #fff;\n border-radius: 4px;\n margin-bottom: 8px;\n}\n.vp-quiz-title[data-v-a8cd8fbf] {\n margin: 0;\n font-size: 16px;\n line-height: 1.5;\n color: var(--c-text-1, #333);\n}\n.vp-quiz-options[data-v-a8cd8fbf] {\n margin-bottom: 16px;\n}\n.vp-quiz-option[data-v-a8cd8fbf] {\n padding: 6px 0;\n}\n.vp-quiz-result[data-v-a8cd8fbf] {\n padding: 8px 12px;\n border-radius: 6px;\n font-size: 14px;\n margin-bottom: 16px;\n}\n.vp-quiz-result.is-correct[data-v-a8cd8fbf] {\n background: #e8f5e9;\n color: #2e7d32;\n}\n.vp-quiz-result.is-wrong[data-v-a8cd8fbf] {\n background: #fbe9e7;\n color: #c62828;\n}\n.vp-quiz-actions[data-v-a8cd8fbf] {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.vp-quiz-fade-enter-active[data-v-a8cd8fbf],\n.vp-quiz-fade-leave-active[data-v-a8cd8fbf] {\n transition: opacity var(--c-transition, 0.2s ease);\n}\n.vp-quiz-fade-enter-from[data-v-a8cd8fbf],\n.vp-quiz-fade-leave-to[data-v-a8cd8fbf] {\n opacity: 0;\n}",".vp-watermark[data-v-6928c31a] {\n /* 通过 CSS 阻止截图(基础防护) */\n -webkit-user-select: none;\n user-select: none;\n}","/* C_VideoPlayer 组件样式 */\n.c-video-player[data-v-9e9355b9] {\n position: relative;\n width: 100%;\n background: #000;\n border-radius: 8px;\n overflow: hidden;\n outline: none;\n /* 覆盖 xgplayer v3 默认红色主题(倍速/清晰度选中项改蓝) */\n /* 小窗模式样式 */\n}\n.c-video-player[data-v-9e9355b9] .xgplayer .xg-options-list li:hover,\n.c-video-player[data-v-9e9355b9] .xgplayer .xg-options-list li.selected,\n.c-video-player[data-v-9e9355b9] .xgplayer-mobile .xg-options-list li.selected {\n color: #409eff !important;\n}\n.c-video-player[data-v-9e9355b9] .xgplayer xg-trigger .time-preview .xg-cur {\n color: #409eff !important;\n}\n.c-video-player[data-v-9e9355b9] .xgplayer .xgplayer-progress-btn.active::before {\n box-shadow: 0 0 3px rgba(64, 152, 255, 0.7) !important;\n}\n.c-video-player__container[data-v-9e9355b9] {\n width: 100%;\n height: 100%;\n}\n.c-video-player.is-mini[data-v-9e9355b9] {\n position: fixed;\n right: 20px;\n bottom: 20px;\n width: 320px;\n height: 180px;\n z-index: 9999;\n border-radius: 8px;\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);\n transition: width 0.3s ease, height 0.3s ease;\n}\n.c-video-player__mini-close[data-v-9e9355b9] {\n position: absolute;\n top: 4px;\n right: 4px;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.6);\n color: #fff;\n border-radius: 50%;\n cursor: pointer;\n font-size: 12px;\n z-index: 10;\n}\n.c-video-player__mini-close[data-v-9e9355b9]:hover {\n background: rgba(0, 0, 0, 0.8);\n}\n.c-video-player__mini-back[data-v-9e9355b9] {\n position: absolute;\n bottom: 4px;\n left: 50%;\n transform: translateX(-50%);\n padding: 2px 10px;\n background: rgba(0, 0, 0, 0.6);\n color: #fff;\n border-radius: 4px;\n cursor: pointer;\n font-size: 11px;\n z-index: 10;\n white-space: nowrap;\n}\n.c-video-player__mini-back[data-v-9e9355b9]:hover {\n background: rgba(0, 0, 0, 0.8);\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1EA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC5DA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC1CA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpEA;AACA;AACA;AACA;AACA;ACJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
@@ -0,0 +1,23 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_C_VideoPlayer = require('./C_VideoPlayer.js');
3
+
4
+ exports.C_VideoPlayer = require_C_VideoPlayer.C_VideoPlayer_default;
5
+ exports.DEFAULT_HEARTBEAT_INTERVAL = require_C_VideoPlayer.DEFAULT_HEARTBEAT_INTERVAL;
6
+ exports.DEFAULT_PLAYBACK_RATE = require_C_VideoPlayer.DEFAULT_PLAYBACK_RATE;
7
+ exports.DEFAULT_PLAYBACK_RATES = require_C_VideoPlayer.DEFAULT_PLAYBACK_RATES;
8
+ exports.DEFAULT_VOLUME = require_C_VideoPlayer.DEFAULT_VOLUME;
9
+ exports.KEYBOARD_SHORTCUTS = require_C_VideoPlayer.KEYBOARD_SHORTCUTS;
10
+ exports.PROGRESS_THROTTLE_INTERVAL = require_C_VideoPlayer.PROGRESS_THROTTLE_INTERVAL;
11
+ exports.QUALITY_LABEL_MAP = require_C_VideoPlayer.QUALITY_LABEL_MAP;
12
+ exports.SEEK_STEP = require_C_VideoPlayer.SEEK_STEP;
13
+ exports.SOURCE_TYPE_MAP = require_C_VideoPlayer.SOURCE_TYPE_MAP;
14
+ exports.STORAGE_KEYS = require_C_VideoPlayer.STORAGE_KEYS;
15
+ exports.STORAGE_PREFIX = require_C_VideoPlayer.STORAGE_PREFIX;
16
+ exports.VOLUME_STEP = require_C_VideoPlayer.VOLUME_STEP;
17
+ exports.WATERMARK_DEFAULT_STYLE = require_C_VideoPlayer.WATERMARK_DEFAULT_STYLE;
18
+ exports.useBookmarks = require_C_VideoPlayer.useBookmarks;
19
+ exports.useChapters = require_C_VideoPlayer.useChapters;
20
+ exports.usePlaybackControl = require_C_VideoPlayer.usePlaybackControl;
21
+ exports.usePlayerCore = require_C_VideoPlayer.usePlayerCore;
22
+ exports.useProgressTracker = require_C_VideoPlayer.useProgressTracker;
23
+ exports.useSubtitle = require_C_VideoPlayer.useSubtitle;
@@ -0,0 +1,2 @@
1
+ import { A as PlayerInstance, B as VideoPlayerEmits, C as AnalyticsReporter, D as IDefinition, E as Chapter, F as QualityLevel, H as VideoPlayerProps, I as QuizOption, L as QuizType, M as ProgressData, N as ProgressReporter, O as IPlayerOptions, P as QualityDefinition, R as SubtitleTrack, S as AnalyticsEventType, T as Bookmark, U as VideoQuiz, V as VideoPlayerExpose, W as VideoSourceType, _ as useBookmarks, a as KEYBOARD_SHORTCUTS, b as _default, c as SEEK_STEP, d as STORAGE_PREFIX, f as VOLUME_STEP, g as useChapters, h as useSubtitle, i as DEFAULT_VOLUME, j as PlayerState, k as PlaybackRate, l as SOURCE_TYPE_MAP, m as useProgressTracker, n as DEFAULT_PLAYBACK_RATE, o as PROGRESS_THROTTLE_INTERVAL, p as WATERMARK_DEFAULT_STYLE, r as DEFAULT_PLAYBACK_RATES, s as QUALITY_LABEL_MAP, t as DEFAULT_HEARTBEAT_INTERVAL, u as STORAGE_KEYS, v as usePlaybackControl, w as AntiCheatConfig, x as AnalyticsEvent, y as usePlayerCore, z as ThumbnailConfig } from "./constants5.js";
2
+ export { type AnalyticsEvent, type AnalyticsEventType, type AnalyticsReporter, type AntiCheatConfig, type Bookmark, _default as C_VideoPlayer, type Chapter, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_PLAYBACK_RATE, DEFAULT_PLAYBACK_RATES, DEFAULT_VOLUME, type IDefinition, type IPlayerOptions, KEYBOARD_SHORTCUTS, PROGRESS_THROTTLE_INTERVAL, type PlaybackRate, type PlayerInstance, type PlayerState, type ProgressData, type ProgressReporter, QUALITY_LABEL_MAP, type QualityDefinition, type QualityLevel, type QuizOption, type QuizType, SEEK_STEP, SOURCE_TYPE_MAP, STORAGE_KEYS, STORAGE_PREFIX, type SubtitleTrack, type ThumbnailConfig, VOLUME_STEP, type VideoPlayerEmits, type VideoPlayerExpose, type VideoPlayerProps, type VideoQuiz, type VideoSourceType, WATERMARK_DEFAULT_STYLE, useBookmarks, useChapters, usePlaybackControl, usePlayerCore, useProgressTracker, useSubtitle };
@@ -0,0 +1,2 @@
1
+ import { A as ProgressData, B as VideoPlayerProps, C as AnalyticsReporter, D as PlaybackRate, E as Chapter, F as QuizType, H as VideoSourceType, I as SubtitleTrack, L as ThumbnailConfig, M as QualityDefinition, N as QualityLevel, O as PlayerInstance, P as QuizOption, R as VideoPlayerEmits, S as AnalyticsEventType, T as Bookmark, U as IDefinition, V as VideoQuiz, W as IPlayerOptions, _ as useBookmarks, a as KEYBOARD_SHORTCUTS, b as _default, c as SEEK_STEP, d as STORAGE_PREFIX, f as VOLUME_STEP, g as useChapters, h as useSubtitle, i as DEFAULT_VOLUME, j as ProgressReporter, k as PlayerState, l as SOURCE_TYPE_MAP, m as useProgressTracker, n as DEFAULT_PLAYBACK_RATE, o as PROGRESS_THROTTLE_INTERVAL, p as WATERMARK_DEFAULT_STYLE, r as DEFAULT_PLAYBACK_RATES, s as QUALITY_LABEL_MAP, t as DEFAULT_HEARTBEAT_INTERVAL, u as STORAGE_KEYS, v as usePlaybackControl, w as AntiCheatConfig, x as AnalyticsEvent, y as usePlayerCore, z as VideoPlayerExpose } from "./constants5.js";
2
+ export { type AnalyticsEvent, type AnalyticsEventType, type AnalyticsReporter, type AntiCheatConfig, type Bookmark, _default as C_VideoPlayer, type Chapter, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_PLAYBACK_RATE, DEFAULT_PLAYBACK_RATES, DEFAULT_VOLUME, type IDefinition, type IPlayerOptions, KEYBOARD_SHORTCUTS, PROGRESS_THROTTLE_INTERVAL, type PlaybackRate, type PlayerInstance, type PlayerState, type ProgressData, type ProgressReporter, QUALITY_LABEL_MAP, type QualityDefinition, type QualityLevel, type QuizOption, type QuizType, SEEK_STEP, SOURCE_TYPE_MAP, STORAGE_KEYS, STORAGE_PREFIX, type SubtitleTrack, type ThumbnailConfig, VOLUME_STEP, type VideoPlayerEmits, type VideoPlayerExpose, type VideoPlayerProps, type VideoQuiz, type VideoSourceType, WATERMARK_DEFAULT_STYLE, useBookmarks, useChapters, usePlaybackControl, usePlayerCore, useProgressTracker, useSubtitle };
@@ -0,0 +1,3 @@
1
+ import { _ as STORAGE_KEYS, a as useProgressTracker, b as WATERMARK_DEFAULT_STYLE, c as DEFAULT_HEARTBEAT_INTERVAL, d as DEFAULT_VOLUME, f as KEYBOARD_SHORTCUTS, g as SOURCE_TYPE_MAP, h as SEEK_STEP, i as useChapters, l as DEFAULT_PLAYBACK_RATE, m as QUALITY_LABEL_MAP, n as useSubtitle, o as usePlaybackControl, p as PROGRESS_THROTTLE_INTERVAL, r as useBookmarks, s as usePlayerCore, t as C_VideoPlayer_default, u as DEFAULT_PLAYBACK_RATES, v as STORAGE_PREFIX, y as VOLUME_STEP } from "./C_VideoPlayer2.js";
2
+
3
+ export { C_VideoPlayer_default as C_VideoPlayer, DEFAULT_HEARTBEAT_INTERVAL, DEFAULT_PLAYBACK_RATE, DEFAULT_PLAYBACK_RATES, DEFAULT_VOLUME, KEYBOARD_SHORTCUTS, PROGRESS_THROTTLE_INTERVAL, QUALITY_LABEL_MAP, SEEK_STEP, SOURCE_TYPE_MAP, STORAGE_KEYS, STORAGE_PREFIX, VOLUME_STEP, WATERMARK_DEFAULT_STYLE, useBookmarks, useChapters, usePlaybackControl, usePlayerCore, useProgressTracker, useSubtitle };