@robot-admin/naive-ui-components 0.3.1 → 0.3.3

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 (188) hide show
  1. package/dist/C_ActionBar-nnfbZCea.css.map +1 -0
  2. package/dist/C_ActionBar2.js +2 -2
  3. package/dist/C_ActionBar2.js.map +1 -1
  4. package/dist/C_AntV-DGjscTWa.css.map +1 -0
  5. package/dist/C_AntV2.js +6 -6
  6. package/dist/C_AntV2.js.map +1 -1
  7. package/dist/C_Barcode-DjTmDkbQ.css.map +1 -0
  8. package/dist/C_Barcode2.js +1 -1
  9. package/dist/C_Barcode2.js.map +1 -1
  10. package/dist/C_Captcha-Ccq3DMrR.css.map +1 -0
  11. package/dist/C_Captcha2.js +1 -1
  12. package/dist/C_Captcha2.js.map +1 -1
  13. package/dist/C_Cascade-IUUHIh7r.css.map +1 -0
  14. package/dist/C_Cascade2.js +2 -2
  15. package/dist/C_Cascade2.js.map +1 -1
  16. package/dist/C_City-Cv5BESaN.css.map +1 -0
  17. package/dist/C_City2.js +2 -2
  18. package/dist/C_City2.js.map +1 -1
  19. package/dist/C_Code-DPZlNSxL.css.map +1 -0
  20. package/dist/C_Code2.js +8 -7
  21. package/dist/C_Code2.js.map +1 -1
  22. package/dist/C_CollapsePanel-Fap-lv_5.css.map +1 -0
  23. package/dist/C_CollapsePanel2.js +1 -1
  24. package/dist/C_CollapsePanel2.js.map +1 -1
  25. package/dist/C_Cron-C0-8b5af.css.map +1 -0
  26. package/dist/C_Cron2.js +15 -14
  27. package/dist/C_Cron2.js.map +1 -1
  28. package/dist/C_Date2.js +1 -1
  29. package/dist/C_Date2.js.map +1 -1
  30. package/dist/C_Draggable-Bq6o0qXn.css.map +1 -0
  31. package/dist/C_Draggable2.js +1 -1
  32. package/dist/C_Draggable2.js.map +1 -1
  33. package/dist/C_Editor-OlxIF9-5.css.map +1 -0
  34. package/dist/C_Editor2.js +1 -1
  35. package/dist/C_Editor2.js.map +1 -1
  36. package/dist/C_FilePreview-B4XgTv-h.css.map +1 -0
  37. package/dist/C_FilePreview.cjs +1 -0
  38. package/dist/C_FilePreview.js +1 -0
  39. package/dist/C_FilePreview2.js +10 -9
  40. package/dist/C_FilePreview2.js.map +1 -1
  41. package/dist/C_Form-Cr9oX037.css.map +1 -0
  42. package/dist/C_Form.cjs +1 -0
  43. package/dist/C_Form.js +1 -0
  44. package/dist/C_Form2.js +69 -72
  45. package/dist/C_Form2.js.map +1 -1
  46. package/dist/C_FormSearch-DlIEoh7X.css.map +1 -0
  47. package/dist/C_FormSearch2.js +2 -2
  48. package/dist/C_FormSearch2.js.map +1 -1
  49. package/dist/C_FormulaEditor-Cm0CokN5.css.map +1 -0
  50. package/dist/C_FormulaEditor2.js +6 -6
  51. package/dist/C_FormulaEditor2.js.map +1 -1
  52. package/dist/C_FullCalendar-BULCIlVK.css.map +1 -0
  53. package/dist/C_FullCalendar2.js +2 -2
  54. package/dist/C_FullCalendar2.js.map +1 -1
  55. package/dist/C_Guide2.js +1 -1
  56. package/dist/C_Guide2.js.map +1 -1
  57. package/dist/C_Icon2.js.map +1 -1
  58. package/dist/C_ImageCropper-DrmUlaLi.css.map +1 -0
  59. package/dist/C_ImageCropper2.js +4 -4
  60. package/dist/C_ImageCropper2.js.map +1 -1
  61. package/dist/C_Language2.js +1 -1
  62. package/dist/C_Language2.js.map +1 -1
  63. package/dist/C_Map-WUMXSAfy.css.map +1 -0
  64. package/dist/C_Map2.js +2 -2
  65. package/dist/C_Map2.js.map +1 -1
  66. package/dist/C_Markdown-Dmv8yaM4.css.map +1 -0
  67. package/dist/C_Markdown2.js +4 -4
  68. package/dist/C_Markdown2.js.map +1 -1
  69. package/dist/C_NotificationCenter-DbgBiyqB.css.map +1 -0
  70. package/dist/C_NotificationCenter2.js +21 -20
  71. package/dist/C_NotificationCenter2.js.map +1 -1
  72. package/dist/C_Progress2.js +1 -1
  73. package/dist/C_Progress2.js.map +1 -1
  74. package/dist/C_QRCode-G7fiAkm4.css.map +1 -0
  75. package/dist/C_QRCode2.js +1 -1
  76. package/dist/C_QRCode2.js.map +1 -1
  77. package/dist/C_Signature-es-ZNPzr.css.map +1 -0
  78. package/dist/C_Signature2.js +2 -2
  79. package/dist/C_Signature2.js.map +1 -1
  80. package/dist/C_SplitPane-Br2eK8IG.css.map +1 -0
  81. package/dist/C_SplitPane2.js +1 -1
  82. package/dist/C_SplitPane2.js.map +1 -1
  83. package/dist/C_Steps-P9Qj9iDd.css.map +1 -0
  84. package/dist/C_Steps2.js +1 -1
  85. package/dist/C_Steps2.js.map +1 -1
  86. package/dist/C_Table-DAwAxr72.css.map +1 -0
  87. package/dist/C_Table.cjs +1 -0
  88. package/dist/C_Table.js +1 -0
  89. package/dist/C_Table2.js +3 -3
  90. package/dist/C_Table2.js.map +1 -1
  91. package/dist/C_Theme2.js +1 -1
  92. package/dist/C_Theme2.js.map +1 -1
  93. package/dist/C_Time-Bd_e1YDN.css.map +1 -0
  94. package/dist/C_Time2.js +2 -2
  95. package/dist/C_Time2.js.map +1 -1
  96. package/dist/C_Tree-DnGc_MPb.css.map +1 -0
  97. package/dist/C_Tree2.js +2 -2
  98. package/dist/C_Tree2.js.map +1 -1
  99. package/dist/C_Upload-i8LB_29U.css.map +1 -0
  100. package/dist/C_Upload2.js +5 -5
  101. package/dist/C_Upload2.js.map +1 -1
  102. package/dist/C_VideoPlayer-rm0MODUv.css.map +1 -0
  103. package/dist/C_VideoPlayer2.js +21 -20
  104. package/dist/C_VideoPlayer2.js.map +1 -1
  105. package/dist/C_VtableGantt-BpY-Rng3.css.map +1 -0
  106. package/dist/C_VtableGantt2.js +2 -2
  107. package/dist/C_VtableGantt2.js.map +1 -1
  108. package/dist/C_WaterFall-HWB-gPON.css.map +1 -0
  109. package/dist/C_WaterFall2.js +2 -2
  110. package/dist/C_WaterFall2.js.map +1 -1
  111. package/dist/C_WorkFlow-CSO86Cuc.css.map +1 -0
  112. package/dist/C_WorkFlow2.js +6 -6
  113. package/dist/C_WorkFlow2.js.map +1 -1
  114. package/dist/constants.d.ts +4 -4
  115. package/dist/constants2.d.ts +4 -4
  116. package/dist/constants3.d.ts +4 -4
  117. package/dist/constants4.d.ts +5 -5
  118. package/dist/constants5.d.ts +2 -2
  119. package/dist/data.d.ts +1 -1
  120. package/dist/index.js.map +1 -1
  121. package/dist/index.vue.d.ts +1 -1
  122. package/dist/index10.vue.d.ts +7 -7
  123. package/dist/index11.vue.d.ts +2 -2
  124. package/dist/index12.vue.d.ts +5 -5
  125. package/dist/index12.vue.d.ts.map +1 -1
  126. package/dist/index13.vue.d.ts +1 -1
  127. package/dist/index14.vue.d.ts +1 -1
  128. package/dist/index16.vue.d.ts +1 -1
  129. package/dist/index2.vue.d.ts +6 -6
  130. package/dist/index3.vue.d.ts +2 -2
  131. package/dist/index4.vue.d.ts +2 -2
  132. package/dist/index5.vue.d.ts +2 -2
  133. package/dist/index6.vue.d.ts +1 -1
  134. package/dist/index8.vue.d.ts +3 -3
  135. package/dist/resolver.js.map +1 -1
  136. package/dist/style.css +1555 -1555
  137. package/dist/useCalendarEvents.d.ts +2 -2
  138. package/dist/useCollapsePanel.d.ts +2 -2
  139. package/dist/useCropperCore.d.ts +3 -3
  140. package/dist/useDraggableLayout.d.ts +8 -8
  141. package/dist/useDynamicFormState.d.ts +111 -111
  142. package/dist/useDynamicFormState.d.ts.map +1 -1
  143. package/dist/useEdgeInteraction.d.ts +2 -2
  144. package/dist/useInfiniteScroll.d.ts +1 -1
  145. package/dist/useModalEdit.d.ts +4 -4
  146. package/dist/useModalEdit.d.ts.map +1 -1
  147. package/dist/useQRCode.d.ts +4 -4
  148. package/dist/useQRCode.d.ts.map +1 -1
  149. package/dist/useSearchState.d.ts +2 -2
  150. package/dist/useSignatureHistory.d.ts +4 -4
  151. package/dist/useSplitResize.d.ts +6 -6
  152. package/dist/useSplitResize.d.ts.map +1 -1
  153. package/dist/useTimeSelection.d.ts +2 -2
  154. package/dist/useTreeOperations.d.ts +6 -6
  155. package/dist/useWorkflowValidation.d.ts +5 -5
  156. package/package.json +2 -1
  157. package/dist/C_ActionBar-DWN-woTc.css.map +0 -1
  158. package/dist/C_AntV-AFKyK6hH.css.map +0 -1
  159. package/dist/C_Barcode-P_EFj8dC.css.map +0 -1
  160. package/dist/C_Captcha-C-ef41xw.css.map +0 -1
  161. package/dist/C_Cascade-D9kNsjsV.css.map +0 -1
  162. package/dist/C_City-BCQ4ipiK.css.map +0 -1
  163. package/dist/C_Code-C9kvvEmO.css.map +0 -1
  164. package/dist/C_CollapsePanel-BUJHuYcU.css.map +0 -1
  165. package/dist/C_Cron-yx2Ob4Jl.css.map +0 -1
  166. package/dist/C_Draggable-C483syRC.css.map +0 -1
  167. package/dist/C_Editor-Bp0SyIEw.css.map +0 -1
  168. package/dist/C_FilePreview-CPqvhoCy.css.map +0 -1
  169. package/dist/C_Form-Jx7PY3sT.css.map +0 -1
  170. package/dist/C_FormSearch-DvRgxlRn.css.map +0 -1
  171. package/dist/C_FormulaEditor-DtGkt4T_.css.map +0 -1
  172. package/dist/C_FullCalendar-BF7H0YIx.css.map +0 -1
  173. package/dist/C_ImageCropper-BVJfUufl.css.map +0 -1
  174. package/dist/C_Map-DpzeuWdX.css.map +0 -1
  175. package/dist/C_Markdown-BEjxknqd.css.map +0 -1
  176. package/dist/C_NotificationCenter-0l3TY2Gn.css.map +0 -1
  177. package/dist/C_QRCode-DbdiAIPg.css.map +0 -1
  178. package/dist/C_Signature-zhHCbra9.css.map +0 -1
  179. package/dist/C_SplitPane-C6sBsfKY.css.map +0 -1
  180. package/dist/C_Steps-CODHN5Hs.css.map +0 -1
  181. package/dist/C_Table-DSNsntmT.css.map +0 -1
  182. package/dist/C_Time-BvZLYraL.css.map +0 -1
  183. package/dist/C_Tree-0GDv--jX.css.map +0 -1
  184. package/dist/C_Upload-BXd3YYLx.css.map +0 -1
  185. package/dist/C_VideoPlayer-DYG3RL0Q.css.map +0 -1
  186. package/dist/C_VtableGantt-fhItIiHE.css.map +0 -1
  187. package/dist/C_WaterFall-8sQDFXKg.css.map +0 -1
  188. package/dist/C_WorkFlow-J-dyIuh9.css.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"C_FilePreview2.js","names":["totalPages","pdfUrl","headings","content","sheets","$emit"],"sources":["../src/components/C_FilePreview/data.ts","../src/components/C_FilePreview/components/PdfViewer/index.vue","../src/components/C_FilePreview/components/PdfViewer/index.vue","../src/components/C_FilePreview/components/PdfViewer/index.vue","../src/components/C_FilePreview/components/WordViewer/index.vue","../src/components/C_FilePreview/components/WordViewer/index.vue","../src/components/C_FilePreview/components/WordViewer/index.vue","../src/components/C_FilePreview/components/ExcelViewer/index.vue","../src/components/C_FilePreview/components/ExcelViewer/index.vue","../src/components/C_FilePreview/components/ExcelViewer/index.vue","../src/components/C_FilePreview/composables/useFilePreview.ts","../src/components/C_FilePreview/composables/useFullscreen.ts","../src/components/C_FilePreview/index.vue","../src/components/C_FilePreview/index.vue","../src/components/C_FilePreview/index.vue"],"sourcesContent":["/**\r\n * C_FilePreview 常量配置与工具函数\r\n * 类型定义统一在 ./types.ts\r\n */\r\nimport { type Ref } from \"vue\";\r\nimport * as XLSX from \"xlsx\";\r\nimport mammoth from \"mammoth\";\r\nimport type {\r\n FilePreviewType,\r\n FileConfig,\r\n ZoomConfig,\r\n ExcelRow,\r\n ExcelColumn,\r\n ExcelSheet,\r\n DocHeading,\r\n PdfLoadResult,\r\n WordLoadResult,\r\n ExcelLoadResult,\r\n} from \"./types\";\r\n\r\n/* ==================== 常量配置 ==================== */\r\n\r\nexport const FILE_TYPE_MAP: Record<string, FilePreviewType> = {\r\n pdf: \"pdf\",\r\n doc: \"word\",\r\n docx: \"word\",\r\n xls: \"excel\",\r\n xlsx: \"excel\",\r\n};\r\n\r\nexport const FILE_CONFIGS: Record<string, FileConfig> = {\r\n pdf: {\r\n tagType: \"error\",\r\n icon: \"ic:outline-picture-as-pdf\",\r\n color: \"#dc2626\",\r\n },\r\n word: { tagType: \"info\", icon: \"ic:outline-description\", color: \"#2563eb\" },\r\n excel: { tagType: \"success\", icon: \"ic:outline-grid-on\", color: \"#16a34a\" },\r\n unknown: {\r\n tagType: \"default\",\r\n icon: \"ic:outline-insert-drive-file\",\r\n color: \"#6b7280\",\r\n },\r\n};\r\n\r\nexport const ZOOM_CONFIGS: Record<string, ZoomConfig> = {\r\n pdf: { min: 0.5, max: 3, step: 0.25, default: 1 },\r\n word: { min: 50, max: 200, step: 10, default: 100 },\r\n};\r\n\r\nexport const PAGE_SIZE_OPTIONS = [20, 50, 100, 200];\r\n\r\nexport const FULLSCREEN_EVENTS = [\r\n \"fullscreenchange\",\r\n \"webkitfullscreenchange\",\r\n \"mozfullscreenchange\",\r\n \"MSFullscreenChange\",\r\n];\r\n\r\n/* ==================== 文件信息工具函数 ==================== */\r\n\r\nexport const extractFileNameFromUrl = (url: string): string => {\r\n try {\r\n const urlPath = url.split(\"/\").pop() || url.split(\"\\\\\").pop();\r\n if (urlPath?.includes(\".\")) {\r\n const decodedName = decodeURIComponent(urlPath.split(\"?\")[0]);\r\n if (decodedName && decodedName !== url) return decodedName;\r\n }\r\n } catch (error) {\r\n console.warn(\"解析URL文件名失败:\", error);\r\n }\r\n return \"未知文件\";\r\n};\r\n\r\nexport const formatFileSize = (bytes: number): string => {\r\n if (bytes === 0) return \"0 B\";\r\n const k = 1024;\r\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\r\n const i = Math.floor(Math.log(bytes) / Math.log(k));\r\n return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + \" \" + sizes[i];\r\n};\r\n\r\nexport const getFileType = (fileName: string): FilePreviewType => {\r\n if (!fileName || fileName === \"未知文件\") return \"unknown\";\r\n const ext = fileName.toLowerCase().split(\".\").pop();\r\n if (!ext) return \"unknown\";\r\n return FILE_TYPE_MAP[ext] || \"unknown\";\r\n};\r\n\r\nexport const getFileConfig = (fileType: string): FileConfig => {\r\n return FILE_CONFIGS[fileType] || FILE_CONFIGS.unknown;\r\n};\r\n\r\n/* ==================== 缩放工具 ==================== */\r\n\r\nexport const createZoomHandler = (\r\n valueRef: Ref<number>,\r\n config: ZoomConfig,\r\n) => {\r\n return (action: \"in\" | \"out\" | \"reset\") => {\r\n const { min, max, step, default: defaultValue } = config;\r\n switch (action) {\r\n case \"reset\":\r\n valueRef.value = defaultValue;\r\n break;\r\n case \"in\":\r\n if (valueRef.value < max) valueRef.value += step;\r\n break;\r\n case \"out\":\r\n if (valueRef.value > min) valueRef.value -= step;\r\n break;\r\n }\r\n };\r\n};\r\n\r\n/* ==================== Excel 工具函数 ==================== */\r\n\r\nexport const getColumnLetter = (index: number): string => {\r\n let result = \"\";\r\n while (index >= 0) {\r\n result = String.fromCharCode(65 + (index % 26)) + result;\r\n index = Math.floor(index / 26) - 1;\r\n }\r\n return result;\r\n};\r\n\r\nexport const formatCellValue = (value: any): string => {\r\n if (!value) return \"\";\r\n const strValue = String(value).trim();\r\n\r\n if (!isNaN(Number(strValue)) && strValue !== \"\" && strValue.length < 15) {\r\n const num = Number(strValue);\r\n return Number.isInteger(num)\r\n ? num.toLocaleString()\r\n : num.toLocaleString(undefined, { maximumFractionDigits: 2 });\r\n }\r\n return strValue;\r\n};\r\n\r\nexport const getCellClass = (value: any): string => {\r\n if (!value) return \"cell-empty\";\r\n const strValue = String(value).trim();\r\n\r\n if (!isNaN(Number(strValue)) && strValue !== \"\") return \"cell-number\";\r\n if (strValue.match(/[■▬▪▫─━]/)) return \"cell-gantt\";\r\n if (\r\n strValue.match(/^\\d{4}[-/]\\d{2}[-/]\\d{2}/) ||\r\n strValue.match(/^\\d{2}[-/]\\d{2}[-/]\\d{4}/)\r\n )\r\n return \"cell-date\";\r\n if ([\"true\", \"false\"].includes(strValue.toLowerCase())) return \"cell-boolean\";\r\n return strValue.length > 20 ? \"cell-text cell-long\" : \"cell-text\";\r\n};\r\n\r\nexport const processExcelSheet = (\r\n worksheet: any,\r\n merges: any[],\r\n): { data: ExcelRow[]; columns: ExcelColumn[] } => {\r\n const range = XLSX.utils.decode_range(worksheet[\"!ref\"] || \"A1:A1\");\r\n const { c: maxCol, r: maxRow } = range.e;\r\n\r\n const columns: ExcelColumn[] = Array.from({ length: maxCol + 1 }, (_, c) => {\r\n const cellAddress = XLSX.utils.encode_cell({ r: 0, c });\r\n const cell = worksheet[cellAddress];\r\n const columnName = cell ? String(cell.v || \"\").trim() : `列${c + 1}`;\r\n return {\r\n title: columnName || `列${c + 1}`,\r\n key: `col_${c}`,\r\n width: Math.min(Math.max(columnName.length * 12, 80), 200),\r\n };\r\n });\r\n\r\n const mergeMap = new Map();\r\n merges.forEach(({ s: { r: sRow, c: sCol }, e: { r: eRow, c: eCol } }) => {\r\n for (let r = sRow; r <= eRow; r++) {\r\n for (let c = sCol; c <= eCol; c++) {\r\n mergeMap.set(`${r}-${c}`, {\r\n isMain: r === sRow && c === sCol,\r\n rowspan: eRow - sRow + 1,\r\n colspan: eCol - sCol + 1,\r\n });\r\n }\r\n }\r\n });\r\n\r\n const data: ExcelRow[] = Array.from({ length: maxRow + 1 }, (_, r) => {\r\n const row: ExcelRow = {};\r\n for (let c = 0; c <= maxCol; c++) {\r\n const cellAddress = XLSX.utils.encode_cell({ r, c });\r\n const cell = worksheet[cellAddress];\r\n const mergeInfo = mergeMap.get(`${r}-${c}`);\r\n\r\n row[`col_${c}`] = {\r\n value: cell ? cell.v || \"\" : \"\",\r\n merged: !!mergeInfo,\r\n hidden: mergeInfo && !mergeInfo.isMain,\r\n rowspan: mergeInfo?.isMain ? mergeInfo.rowspan : 1,\r\n colspan: mergeInfo?.isMain ? mergeInfo.colspan : 1,\r\n };\r\n }\r\n return row;\r\n });\r\n\r\n return { data, columns };\r\n};\r\n\r\n/* ==================== 文件加载器 ==================== */\r\n\r\nexport async function loadPdf(file: File): Promise<PdfLoadResult> {\r\n const fileUrl = URL.createObjectURL(file);\r\n return {\r\n url: fileUrl + \"#toolbar=1&navpanes=1&scrollbar=1&view=FitH\",\r\n totalPages: 1,\r\n };\r\n}\r\n\r\nexport async function loadWord(file: File): Promise<WordLoadResult> {\r\n const arrayBuffer = await file.arrayBuffer();\r\n const result = await mammoth.convertToHtml({\r\n arrayBuffer,\r\n styleMap: [\r\n \"p[style-name='Heading 1'] => h1:fresh\",\r\n \"p[style-name='Heading 2'] => h2:fresh\",\r\n \"p[style-name='Heading 3'] => h3:fresh\",\r\n \"p[style-name='Title'] => h1.title:fresh\",\r\n ],\r\n } as any);\r\n\r\n let content = result.value;\r\n const headings: DocHeading[] = [];\r\n\r\n /* 提取标题并注入 id */\r\n const tempDiv = document.createElement(\"div\");\r\n tempDiv.innerHTML = content;\r\n const headingElements = tempDiv.querySelectorAll(\"h1, h2, h3, h4, h5, h6\");\r\n\r\n Array.from(headingElements).forEach((el, index) => {\r\n const id = `heading-${index}`;\r\n const level = parseInt(el.tagName.charAt(1));\r\n const text = el.textContent || \"\";\r\n\r\n content = content.replace(\r\n el.outerHTML,\r\n el.outerHTML.replace(\r\n el.tagName.toLowerCase(),\r\n `${el.tagName.toLowerCase()} id=\"${id}\"`,\r\n ),\r\n );\r\n\r\n headings.push({ id, text, level });\r\n });\r\n\r\n return { content, headings };\r\n}\r\n\r\nexport async function loadExcel(file: File): Promise<ExcelLoadResult> {\r\n const arrayBuffer = await file.arrayBuffer();\r\n const workbook = XLSX.read(arrayBuffer, {\r\n type: \"array\",\r\n cellStyles: true,\r\n cellFormula: true,\r\n cellDates: true,\r\n });\r\n\r\n if (!workbook.SheetNames.length) {\r\n throw new Error(\"Excel文件中没有找到工作表\");\r\n }\r\n\r\n const sheets: ExcelSheet[] = workbook.SheetNames.map((name) => {\r\n const worksheet = workbook.Sheets[name];\r\n const merges = worksheet[\"!merges\"] || [];\r\n const { data, columns } = processExcelSheet(worksheet, merges);\r\n return { name, data, merges, columns };\r\n });\r\n\r\n return { sheets };\r\n}\r\n","<!--\r\n PdfViewer — PDF 文件预览子组件\r\n 负责: 工具栏(翻页+缩放) + iframe 渲染\r\n-->\r\n<template>\r\n <div class=\"file-container pdf-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <!-- 翻页控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage <= 1\"\r\n @click=\"changePage('prev')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-left\" />\r\n </template>\r\n </NButton>\r\n <NInputNumber\r\n v-model:value=\"currentPage\"\r\n size=\"small\"\r\n :min=\"1\"\r\n :max=\"totalPages\"\r\n style=\"width: 80px\"\r\n @update:value=\"changePage\"\r\n />\r\n <span class=\"text-sm text-gray-600\">/ {{ totalPages }}</span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage >= totalPages\"\r\n @click=\"changePage('next')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-right\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n\r\n <!-- 缩放控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale <= 0.5\"\r\n @click=\"adjustZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ Math.round(scale * 100) }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale >= 3\"\r\n @click=\"adjustZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-viewer\">\r\n <iframe\r\n v-if=\"pdfUrl\"\r\n :src=\"pdfUrl\"\r\n class=\"w-full h-full border-0\"\r\n title=\"PDF预览\"\r\n frameborder=\"0\"\r\n allowfullscreen\r\n />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NInputNumber } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\n\r\nconst props = defineProps<{\r\n pdfUrl: string;\r\n totalPages: number;\r\n}>();\r\n\r\nconst currentPage = ref(1);\r\nconst scale = ref(1);\r\n\r\nconst adjustZoom = createZoomHandler(scale, ZOOM_CONFIGS.pdf);\r\n\r\nconst changePage = (action: \"prev\" | \"next\" | number) => {\r\n if (typeof action === \"number\") {\r\n currentPage.value = Math.max(1, Math.min(action, props.totalPages));\r\n } else {\r\n const delta = action === \"prev\" ? -1 : 1;\r\n currentPage.value = Math.max(\r\n 1,\r\n Math.min(currentPage.value + delta, props.totalPages),\r\n );\r\n }\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n PdfViewer — PDF 文件预览子组件\r\n 负责: 工具栏(翻页+缩放) + iframe 渲染\r\n-->\r\n<template>\r\n <div class=\"file-container pdf-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <!-- 翻页控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage <= 1\"\r\n @click=\"changePage('prev')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-left\" />\r\n </template>\r\n </NButton>\r\n <NInputNumber\r\n v-model:value=\"currentPage\"\r\n size=\"small\"\r\n :min=\"1\"\r\n :max=\"totalPages\"\r\n style=\"width: 80px\"\r\n @update:value=\"changePage\"\r\n />\r\n <span class=\"text-sm text-gray-600\">/ {{ totalPages }}</span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage >= totalPages\"\r\n @click=\"changePage('next')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-right\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n\r\n <!-- 缩放控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale <= 0.5\"\r\n @click=\"adjustZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ Math.round(scale * 100) }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale >= 3\"\r\n @click=\"adjustZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-viewer\">\r\n <iframe\r\n v-if=\"pdfUrl\"\r\n :src=\"pdfUrl\"\r\n class=\"w-full h-full border-0\"\r\n title=\"PDF预览\"\r\n frameborder=\"0\"\r\n allowfullscreen\r\n />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NInputNumber } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\n\r\nconst props = defineProps<{\r\n pdfUrl: string;\r\n totalPages: number;\r\n}>();\r\n\r\nconst currentPage = ref(1);\r\nconst scale = ref(1);\r\n\r\nconst adjustZoom = createZoomHandler(scale, ZOOM_CONFIGS.pdf);\r\n\r\nconst changePage = (action: \"prev\" | \"next\" | number) => {\r\n if (typeof action === \"number\") {\r\n currentPage.value = Math.max(1, Math.min(action, props.totalPages));\r\n } else {\r\n const delta = action === \"prev\" ? -1 : 1;\r\n currentPage.value = Math.max(\r\n 1,\r\n Math.min(currentPage.value + delta, props.totalPages),\r\n );\r\n }\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n PdfViewer — PDF 文件预览子组件\r\n 负责: 工具栏(翻页+缩放) + iframe 渲染\r\n-->\r\n<template>\r\n <div class=\"file-container pdf-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <!-- 翻页控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage <= 1\"\r\n @click=\"changePage('prev')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-left\" />\r\n </template>\r\n </NButton>\r\n <NInputNumber\r\n v-model:value=\"currentPage\"\r\n size=\"small\"\r\n :min=\"1\"\r\n :max=\"totalPages\"\r\n style=\"width: 80px\"\r\n @update:value=\"changePage\"\r\n />\r\n <span class=\"text-sm text-gray-600\">/ {{ totalPages }}</span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage >= totalPages\"\r\n @click=\"changePage('next')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-right\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n\r\n <!-- 缩放控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale <= 0.5\"\r\n @click=\"adjustZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ Math.round(scale * 100) }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale >= 3\"\r\n @click=\"adjustZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-viewer\">\r\n <iframe\r\n v-if=\"pdfUrl\"\r\n :src=\"pdfUrl\"\r\n class=\"w-full h-full border-0\"\r\n title=\"PDF预览\"\r\n frameborder=\"0\"\r\n allowfullscreen\r\n />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NInputNumber } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\n\r\nconst props = defineProps<{\r\n pdfUrl: string;\r\n totalPages: number;\r\n}>();\r\n\r\nconst currentPage = ref(1);\r\nconst scale = ref(1);\r\n\r\nconst adjustZoom = createZoomHandler(scale, ZOOM_CONFIGS.pdf);\r\n\r\nconst changePage = (action: \"prev\" | \"next\" | number) => {\r\n if (typeof action === \"number\") {\r\n currentPage.value = Math.max(1, Math.min(action, props.totalPages));\r\n } else {\r\n const delta = action === \"prev\" ? -1 : 1;\r\n currentPage.value = Math.max(\r\n 1,\r\n Math.min(currentPage.value + delta, props.totalPages),\r\n );\r\n }\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n WordViewer — Word 文档预览子组件\r\n 负责: 目录导航 + 缩放 + 文档内容渲染\r\n-->\r\n<template>\r\n <div class=\"file-container word-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showOutline = !showOutline\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-list\" />\r\n </template>\r\n {{ showOutline ? \"隐藏\" : \"显示\" }}目录\r\n </NButton>\r\n <NDivider vertical />\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom <= 50\"\r\n @click=\"adjustWordZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ wordZoom }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom >= 200\"\r\n @click=\"adjustWordZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustWordZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 重置\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <div class=\"word-layout\">\r\n <div class=\"word-main\">\r\n <!-- 侧边栏目录 -->\r\n <div v-if=\"showOutline\" class=\"word-outline\">\r\n <div class=\"outline-header\">\r\n <h3 class=\"text-sm font-semibold\">文档目录</h3>\r\n </div>\r\n <div class=\"outline-content\">\r\n <div\r\n v-for=\"(heading, index) in headings\"\r\n :key=\"index\"\r\n class=\"outline-item\"\r\n :class=\"`level-${heading.level}`\"\r\n @click=\"scrollToHeading(heading.id)\"\r\n >\r\n {{ heading.text }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 主要内容区域 -->\r\n <div class=\"word-content\">\r\n <div\r\n class=\"word-document\"\r\n :style=\"{\r\n transform: `scale(${wordZoom / 100})`,\r\n transformOrigin: 'top center',\r\n }\"\r\n v-html=\"content\"\r\n ></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NDivider } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\nimport type { DocHeading } from \"../../types\";\r\n\r\ndefineProps<{\r\n content: string;\r\n headings: DocHeading[];\r\n}>();\r\n\r\nconst showOutline = ref(true);\r\nconst wordZoom = ref(100);\r\n\r\nconst adjustWordZoom = createZoomHandler(wordZoom, ZOOM_CONFIGS.word);\r\n\r\nconst scrollToHeading = (headingId: string) => {\r\n document\r\n .getElementById(headingId)\r\n ?.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n WordViewer — Word 文档预览子组件\r\n 负责: 目录导航 + 缩放 + 文档内容渲染\r\n-->\r\n<template>\r\n <div class=\"file-container word-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showOutline = !showOutline\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-list\" />\r\n </template>\r\n {{ showOutline ? \"隐藏\" : \"显示\" }}目录\r\n </NButton>\r\n <NDivider vertical />\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom <= 50\"\r\n @click=\"adjustWordZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ wordZoom }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom >= 200\"\r\n @click=\"adjustWordZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustWordZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 重置\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <div class=\"word-layout\">\r\n <div class=\"word-main\">\r\n <!-- 侧边栏目录 -->\r\n <div v-if=\"showOutline\" class=\"word-outline\">\r\n <div class=\"outline-header\">\r\n <h3 class=\"text-sm font-semibold\">文档目录</h3>\r\n </div>\r\n <div class=\"outline-content\">\r\n <div\r\n v-for=\"(heading, index) in headings\"\r\n :key=\"index\"\r\n class=\"outline-item\"\r\n :class=\"`level-${heading.level}`\"\r\n @click=\"scrollToHeading(heading.id)\"\r\n >\r\n {{ heading.text }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 主要内容区域 -->\r\n <div class=\"word-content\">\r\n <div\r\n class=\"word-document\"\r\n :style=\"{\r\n transform: `scale(${wordZoom / 100})`,\r\n transformOrigin: 'top center',\r\n }\"\r\n v-html=\"content\"\r\n ></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NDivider } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\nimport type { DocHeading } from \"../../types\";\r\n\r\ndefineProps<{\r\n content: string;\r\n headings: DocHeading[];\r\n}>();\r\n\r\nconst showOutline = ref(true);\r\nconst wordZoom = ref(100);\r\n\r\nconst adjustWordZoom = createZoomHandler(wordZoom, ZOOM_CONFIGS.word);\r\n\r\nconst scrollToHeading = (headingId: string) => {\r\n document\r\n .getElementById(headingId)\r\n ?.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n WordViewer — Word 文档预览子组件\r\n 负责: 目录导航 + 缩放 + 文档内容渲染\r\n-->\r\n<template>\r\n <div class=\"file-container word-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showOutline = !showOutline\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-list\" />\r\n </template>\r\n {{ showOutline ? \"隐藏\" : \"显示\" }}目录\r\n </NButton>\r\n <NDivider vertical />\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom <= 50\"\r\n @click=\"adjustWordZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ wordZoom }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom >= 200\"\r\n @click=\"adjustWordZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustWordZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 重置\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <div class=\"word-layout\">\r\n <div class=\"word-main\">\r\n <!-- 侧边栏目录 -->\r\n <div v-if=\"showOutline\" class=\"word-outline\">\r\n <div class=\"outline-header\">\r\n <h3 class=\"text-sm font-semibold\">文档目录</h3>\r\n </div>\r\n <div class=\"outline-content\">\r\n <div\r\n v-for=\"(heading, index) in headings\"\r\n :key=\"index\"\r\n class=\"outline-item\"\r\n :class=\"`level-${heading.level}`\"\r\n @click=\"scrollToHeading(heading.id)\"\r\n >\r\n {{ heading.text }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 主要内容区域 -->\r\n <div class=\"word-content\">\r\n <div\r\n class=\"word-document\"\r\n :style=\"{\r\n transform: `scale(${wordZoom / 100})`,\r\n transformOrigin: 'top center',\r\n }\"\r\n v-html=\"content\"\r\n ></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NDivider } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\nimport type { DocHeading } from \"../../types\";\r\n\r\ndefineProps<{\r\n content: string;\r\n headings: DocHeading[];\r\n}>();\r\n\r\nconst showOutline = ref(true);\r\nconst wordZoom = ref(100);\r\n\r\nconst adjustWordZoom = createZoomHandler(wordZoom, ZOOM_CONFIGS.word);\r\n\r\nconst scrollToHeading = (headingId: string) => {\r\n document\r\n .getElementById(headingId)\r\n ?.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n ExcelViewer — Excel 表格预览子组件\r\n 负责: 工作表切换 + 表格渲染(合并单元格) + 分页 + 格式切换\r\n-->\r\n<template>\r\n <div class=\"file-container excel-container\">\r\n <!-- 工具栏 -->\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NTabs\r\n v-if=\"sheets.length > 1\"\r\n v-model:value=\"activeSheet\"\r\n type=\"card\"\r\n size=\"small\"\r\n >\r\n <NTabPane\r\n v-for=\"sheet in sheets\"\r\n :key=\"sheet.name\"\r\n :name=\"sheet.name\"\r\n :tab=\"sheet.name\"\r\n />\r\n </NTabs>\r\n <span v-else class=\"text-sm text-gray-600\">\r\n 工作表: {{ activeSheet }}\r\n </span>\r\n </div>\r\n\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showExcelFormat = !showExcelFormat\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-format-paint\" />\r\n </template>\r\n {{ showExcelFormat ? \"紧凑视图\" : \"完整格式\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"excel-viewer\">\r\n <!-- 信息面板 -->\r\n <div v-if=\"currentData.length > 0\" class=\"excel-info\">\r\n <NText depth=\"3\" class=\"text-sm\">\r\n 当前工作表: {{ activeSheet }} | 总行数: {{ currentData.length }} |\r\n 总列数: {{ currentColumns.length }} | 当前页:\r\n {{ currentExcelPage }}/{{ totalExcelPages }}\r\n </NText>\r\n </div>\r\n\r\n <!-- 表格 -->\r\n <div class=\"excel-table-container\">\r\n <div\r\n class=\"excel-table-wrapper\"\r\n :class=\"{ 'simple-mode': !showExcelFormat }\"\r\n >\r\n <table v-if=\"currentData.length > 0\" class=\"excel-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"row-number\">行号</th>\r\n <th\r\n v-for=\"(col, index) in currentColumns\"\r\n :key=\"col.key\"\r\n class=\"excel-header\"\r\n :style=\"{ minWidth: col.width + 'px' }\"\r\n >\r\n <div class=\"header-content\">\r\n <span class=\"column-letter\">{{\r\n getColumnLetter(index)\r\n }}</span>\r\n <span class=\"column-title\">{{ col.title }}</span>\r\n </div>\r\n </th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr\r\n v-for=\"(row, rowIndex) in paginatedData\"\r\n :key=\"rowIndex\"\r\n class=\"excel-row\"\r\n >\r\n <td class=\"row-number\">\r\n {{ (currentExcelPage - 1) * pageSize + rowIndex + 1 }}\r\n </td>\r\n <td\r\n v-for=\"col in currentColumns\"\r\n :key=\"col.key\"\r\n v-show=\"!row[col.key]?.hidden\"\r\n class=\"excel-cell\"\r\n :class=\"[\r\n getCellClass(row[col.key]?.value),\r\n row[col.key]?.merged ? 'merged-cell' : '',\r\n !showExcelFormat ? 'compact-cell' : '',\r\n ]\"\r\n :rowspan=\"row[col.key]?.rowspan || 1\"\r\n :colspan=\"row[col.key]?.colspan || 1\"\r\n :title=\"row[col.key]?.value\"\r\n >\r\n {{ formatCellValue(row[col.key]?.value) }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <div v-else class=\"excel-empty\">\r\n <NEmpty description=\"该工作表没有数据或解析失败\">\r\n <template #extra>\r\n <NButton size=\"small\" @click=\"$emit('reload')\">\r\n 重新解析\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n\r\n <!-- 分页 -->\r\n <div v-if=\"totalExcelPages > 1\" class=\"excel-pagination\">\r\n <NPagination\r\n v-model:page=\"currentExcelPage\"\r\n :page-count=\"totalExcelPages\"\r\n :page-size=\"pageSize\"\r\n show-size-picker\r\n :page-sizes=\"PAGE_SIZE_OPTIONS\"\r\n @update:page-size=\"handlePageSizeChange\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NButton, NTabs, NTabPane, NText, NEmpty, NPagination } from \"naive-ui\";\r\nimport {\r\n getColumnLetter,\r\n formatCellValue,\r\n getCellClass,\r\n PAGE_SIZE_OPTIONS,\r\n} from \"../../data\";\r\nimport type { ExcelSheet, ExcelRow, ExcelColumn } from \"../../types\";\r\n\r\nconst props = defineProps<{\r\n sheets: ExcelSheet[];\r\n}>();\r\n\r\ndefineEmits<{\r\n reload: [];\r\n}>();\r\n\r\n/* ==================== 内部状态 ==================== */\r\nconst activeSheet = ref(\"\");\r\nconst showExcelFormat = ref(false);\r\nconst currentExcelPage = ref(1);\r\nconst pageSize = ref(50);\r\n\r\n/* ==================== 当前工作表数据 ==================== */\r\nconst currentData = ref<ExcelRow[]>([]);\r\nconst currentColumns = ref<ExcelColumn[]>([]);\r\n\r\n/* ==================== 计算属性 ==================== */\r\nconst totalExcelPages = computed(() =>\r\n Math.ceil(currentData.value.length / pageSize.value),\r\n);\r\n\r\nconst paginatedData = computed(() => {\r\n const start = (currentExcelPage.value - 1) * pageSize.value;\r\n return currentData.value.slice(start, start + pageSize.value);\r\n});\r\n\r\n/* ==================== 方法 ==================== */\r\nconst handlePageSizeChange = (newPageSize: number) => {\r\n pageSize.value = newPageSize;\r\n currentExcelPage.value = 1;\r\n};\r\n\r\nconst syncSheetData = (sheetName: string) => {\r\n const sheet = props.sheets.find((s) => s.name === sheetName);\r\n if (sheet) {\r\n currentData.value = sheet.data;\r\n currentColumns.value = sheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n};\r\n\r\n/* ==================== 监听器 ==================== */\r\nwatch(activeSheet, (newSheet) => {\r\n syncSheetData(newSheet);\r\n});\r\n\r\n/* sheets 变化时初始化 */\r\nwatch(\r\n () => props.sheets,\r\n (newSheets) => {\r\n if (newSheets.length > 0) {\r\n const [firstSheet] = newSheets;\r\n activeSheet.value = firstSheet.name;\r\n currentData.value = firstSheet.data;\r\n currentColumns.value = firstSheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n ExcelViewer — Excel 表格预览子组件\r\n 负责: 工作表切换 + 表格渲染(合并单元格) + 分页 + 格式切换\r\n-->\r\n<template>\r\n <div class=\"file-container excel-container\">\r\n <!-- 工具栏 -->\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NTabs\r\n v-if=\"sheets.length > 1\"\r\n v-model:value=\"activeSheet\"\r\n type=\"card\"\r\n size=\"small\"\r\n >\r\n <NTabPane\r\n v-for=\"sheet in sheets\"\r\n :key=\"sheet.name\"\r\n :name=\"sheet.name\"\r\n :tab=\"sheet.name\"\r\n />\r\n </NTabs>\r\n <span v-else class=\"text-sm text-gray-600\">\r\n 工作表: {{ activeSheet }}\r\n </span>\r\n </div>\r\n\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showExcelFormat = !showExcelFormat\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-format-paint\" />\r\n </template>\r\n {{ showExcelFormat ? \"紧凑视图\" : \"完整格式\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"excel-viewer\">\r\n <!-- 信息面板 -->\r\n <div v-if=\"currentData.length > 0\" class=\"excel-info\">\r\n <NText depth=\"3\" class=\"text-sm\">\r\n 当前工作表: {{ activeSheet }} | 总行数: {{ currentData.length }} |\r\n 总列数: {{ currentColumns.length }} | 当前页:\r\n {{ currentExcelPage }}/{{ totalExcelPages }}\r\n </NText>\r\n </div>\r\n\r\n <!-- 表格 -->\r\n <div class=\"excel-table-container\">\r\n <div\r\n class=\"excel-table-wrapper\"\r\n :class=\"{ 'simple-mode': !showExcelFormat }\"\r\n >\r\n <table v-if=\"currentData.length > 0\" class=\"excel-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"row-number\">行号</th>\r\n <th\r\n v-for=\"(col, index) in currentColumns\"\r\n :key=\"col.key\"\r\n class=\"excel-header\"\r\n :style=\"{ minWidth: col.width + 'px' }\"\r\n >\r\n <div class=\"header-content\">\r\n <span class=\"column-letter\">{{\r\n getColumnLetter(index)\r\n }}</span>\r\n <span class=\"column-title\">{{ col.title }}</span>\r\n </div>\r\n </th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr\r\n v-for=\"(row, rowIndex) in paginatedData\"\r\n :key=\"rowIndex\"\r\n class=\"excel-row\"\r\n >\r\n <td class=\"row-number\">\r\n {{ (currentExcelPage - 1) * pageSize + rowIndex + 1 }}\r\n </td>\r\n <td\r\n v-for=\"col in currentColumns\"\r\n :key=\"col.key\"\r\n v-show=\"!row[col.key]?.hidden\"\r\n class=\"excel-cell\"\r\n :class=\"[\r\n getCellClass(row[col.key]?.value),\r\n row[col.key]?.merged ? 'merged-cell' : '',\r\n !showExcelFormat ? 'compact-cell' : '',\r\n ]\"\r\n :rowspan=\"row[col.key]?.rowspan || 1\"\r\n :colspan=\"row[col.key]?.colspan || 1\"\r\n :title=\"row[col.key]?.value\"\r\n >\r\n {{ formatCellValue(row[col.key]?.value) }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <div v-else class=\"excel-empty\">\r\n <NEmpty description=\"该工作表没有数据或解析失败\">\r\n <template #extra>\r\n <NButton size=\"small\" @click=\"$emit('reload')\">\r\n 重新解析\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n\r\n <!-- 分页 -->\r\n <div v-if=\"totalExcelPages > 1\" class=\"excel-pagination\">\r\n <NPagination\r\n v-model:page=\"currentExcelPage\"\r\n :page-count=\"totalExcelPages\"\r\n :page-size=\"pageSize\"\r\n show-size-picker\r\n :page-sizes=\"PAGE_SIZE_OPTIONS\"\r\n @update:page-size=\"handlePageSizeChange\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NButton, NTabs, NTabPane, NText, NEmpty, NPagination } from \"naive-ui\";\r\nimport {\r\n getColumnLetter,\r\n formatCellValue,\r\n getCellClass,\r\n PAGE_SIZE_OPTIONS,\r\n} from \"../../data\";\r\nimport type { ExcelSheet, ExcelRow, ExcelColumn } from \"../../types\";\r\n\r\nconst props = defineProps<{\r\n sheets: ExcelSheet[];\r\n}>();\r\n\r\ndefineEmits<{\r\n reload: [];\r\n}>();\r\n\r\n/* ==================== 内部状态 ==================== */\r\nconst activeSheet = ref(\"\");\r\nconst showExcelFormat = ref(false);\r\nconst currentExcelPage = ref(1);\r\nconst pageSize = ref(50);\r\n\r\n/* ==================== 当前工作表数据 ==================== */\r\nconst currentData = ref<ExcelRow[]>([]);\r\nconst currentColumns = ref<ExcelColumn[]>([]);\r\n\r\n/* ==================== 计算属性 ==================== */\r\nconst totalExcelPages = computed(() =>\r\n Math.ceil(currentData.value.length / pageSize.value),\r\n);\r\n\r\nconst paginatedData = computed(() => {\r\n const start = (currentExcelPage.value - 1) * pageSize.value;\r\n return currentData.value.slice(start, start + pageSize.value);\r\n});\r\n\r\n/* ==================== 方法 ==================== */\r\nconst handlePageSizeChange = (newPageSize: number) => {\r\n pageSize.value = newPageSize;\r\n currentExcelPage.value = 1;\r\n};\r\n\r\nconst syncSheetData = (sheetName: string) => {\r\n const sheet = props.sheets.find((s) => s.name === sheetName);\r\n if (sheet) {\r\n currentData.value = sheet.data;\r\n currentColumns.value = sheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n};\r\n\r\n/* ==================== 监听器 ==================== */\r\nwatch(activeSheet, (newSheet) => {\r\n syncSheetData(newSheet);\r\n});\r\n\r\n/* sheets 变化时初始化 */\r\nwatch(\r\n () => props.sheets,\r\n (newSheets) => {\r\n if (newSheets.length > 0) {\r\n const [firstSheet] = newSheets;\r\n activeSheet.value = firstSheet.name;\r\n currentData.value = firstSheet.data;\r\n currentColumns.value = firstSheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n ExcelViewer — Excel 表格预览子组件\r\n 负责: 工作表切换 + 表格渲染(合并单元格) + 分页 + 格式切换\r\n-->\r\n<template>\r\n <div class=\"file-container excel-container\">\r\n <!-- 工具栏 -->\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NTabs\r\n v-if=\"sheets.length > 1\"\r\n v-model:value=\"activeSheet\"\r\n type=\"card\"\r\n size=\"small\"\r\n >\r\n <NTabPane\r\n v-for=\"sheet in sheets\"\r\n :key=\"sheet.name\"\r\n :name=\"sheet.name\"\r\n :tab=\"sheet.name\"\r\n />\r\n </NTabs>\r\n <span v-else class=\"text-sm text-gray-600\">\r\n 工作表: {{ activeSheet }}\r\n </span>\r\n </div>\r\n\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showExcelFormat = !showExcelFormat\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-format-paint\" />\r\n </template>\r\n {{ showExcelFormat ? \"紧凑视图\" : \"完整格式\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"excel-viewer\">\r\n <!-- 信息面板 -->\r\n <div v-if=\"currentData.length > 0\" class=\"excel-info\">\r\n <NText depth=\"3\" class=\"text-sm\">\r\n 当前工作表: {{ activeSheet }} | 总行数: {{ currentData.length }} |\r\n 总列数: {{ currentColumns.length }} | 当前页:\r\n {{ currentExcelPage }}/{{ totalExcelPages }}\r\n </NText>\r\n </div>\r\n\r\n <!-- 表格 -->\r\n <div class=\"excel-table-container\">\r\n <div\r\n class=\"excel-table-wrapper\"\r\n :class=\"{ 'simple-mode': !showExcelFormat }\"\r\n >\r\n <table v-if=\"currentData.length > 0\" class=\"excel-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"row-number\">行号</th>\r\n <th\r\n v-for=\"(col, index) in currentColumns\"\r\n :key=\"col.key\"\r\n class=\"excel-header\"\r\n :style=\"{ minWidth: col.width + 'px' }\"\r\n >\r\n <div class=\"header-content\">\r\n <span class=\"column-letter\">{{\r\n getColumnLetter(index)\r\n }}</span>\r\n <span class=\"column-title\">{{ col.title }}</span>\r\n </div>\r\n </th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr\r\n v-for=\"(row, rowIndex) in paginatedData\"\r\n :key=\"rowIndex\"\r\n class=\"excel-row\"\r\n >\r\n <td class=\"row-number\">\r\n {{ (currentExcelPage - 1) * pageSize + rowIndex + 1 }}\r\n </td>\r\n <td\r\n v-for=\"col in currentColumns\"\r\n :key=\"col.key\"\r\n v-show=\"!row[col.key]?.hidden\"\r\n class=\"excel-cell\"\r\n :class=\"[\r\n getCellClass(row[col.key]?.value),\r\n row[col.key]?.merged ? 'merged-cell' : '',\r\n !showExcelFormat ? 'compact-cell' : '',\r\n ]\"\r\n :rowspan=\"row[col.key]?.rowspan || 1\"\r\n :colspan=\"row[col.key]?.colspan || 1\"\r\n :title=\"row[col.key]?.value\"\r\n >\r\n {{ formatCellValue(row[col.key]?.value) }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <div v-else class=\"excel-empty\">\r\n <NEmpty description=\"该工作表没有数据或解析失败\">\r\n <template #extra>\r\n <NButton size=\"small\" @click=\"$emit('reload')\">\r\n 重新解析\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n\r\n <!-- 分页 -->\r\n <div v-if=\"totalExcelPages > 1\" class=\"excel-pagination\">\r\n <NPagination\r\n v-model:page=\"currentExcelPage\"\r\n :page-count=\"totalExcelPages\"\r\n :page-size=\"pageSize\"\r\n show-size-picker\r\n :page-sizes=\"PAGE_SIZE_OPTIONS\"\r\n @update:page-size=\"handlePageSizeChange\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NButton, NTabs, NTabPane, NText, NEmpty, NPagination } from \"naive-ui\";\r\nimport {\r\n getColumnLetter,\r\n formatCellValue,\r\n getCellClass,\r\n PAGE_SIZE_OPTIONS,\r\n} from \"../../data\";\r\nimport type { ExcelSheet, ExcelRow, ExcelColumn } from \"../../types\";\r\n\r\nconst props = defineProps<{\r\n sheets: ExcelSheet[];\r\n}>();\r\n\r\ndefineEmits<{\r\n reload: [];\r\n}>();\r\n\r\n/* ==================== 内部状态 ==================== */\r\nconst activeSheet = ref(\"\");\r\nconst showExcelFormat = ref(false);\r\nconst currentExcelPage = ref(1);\r\nconst pageSize = ref(50);\r\n\r\n/* ==================== 当前工作表数据 ==================== */\r\nconst currentData = ref<ExcelRow[]>([]);\r\nconst currentColumns = ref<ExcelColumn[]>([]);\r\n\r\n/* ==================== 计算属性 ==================== */\r\nconst totalExcelPages = computed(() =>\r\n Math.ceil(currentData.value.length / pageSize.value),\r\n);\r\n\r\nconst paginatedData = computed(() => {\r\n const start = (currentExcelPage.value - 1) * pageSize.value;\r\n return currentData.value.slice(start, start + pageSize.value);\r\n});\r\n\r\n/* ==================== 方法 ==================== */\r\nconst handlePageSizeChange = (newPageSize: number) => {\r\n pageSize.value = newPageSize;\r\n currentExcelPage.value = 1;\r\n};\r\n\r\nconst syncSheetData = (sheetName: string) => {\r\n const sheet = props.sheets.find((s) => s.name === sheetName);\r\n if (sheet) {\r\n currentData.value = sheet.data;\r\n currentColumns.value = sheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n};\r\n\r\n/* ==================== 监听器 ==================== */\r\nwatch(activeSheet, (newSheet) => {\r\n syncSheetData(newSheet);\r\n});\r\n\r\n/* sheets 变化时初始化 */\r\nwatch(\r\n () => props.sheets,\r\n (newSheets) => {\r\n if (newSheets.length > 0) {\r\n const [firstSheet] = newSheets;\r\n activeSheet.value = firstSheet.name;\r\n currentData.value = firstSheet.data;\r\n currentColumns.value = firstSheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/**\r\n * useFilePreview — 文件预览核心 composable\r\n * 管理:文件状态、加载、类型检测、下载\r\n */\r\nimport { ref, computed, watch, onUnmounted, type Ref } from \"vue\";\r\nimport {\r\n extractFileNameFromUrl,\r\n getFileType,\r\n getFileConfig,\r\n loadPdf,\r\n loadWord,\r\n loadExcel,\r\n} from \"../data\";\r\nimport type { FilePreviewType, DocHeading, ExcelSheet } from \"../types\";\r\n\r\nexport interface UseFilePreviewOptions {\r\n file: Ref<File | undefined>;\r\n url: Ref<string | undefined>;\r\n fileName: Ref<string>;\r\n}\r\n\r\n/**\r\n * 文件预览核心 composable — 管理文件状态、加载、类型检测、下载\r\n * @param options - 文件源配置(file / url / fileName)\r\n * @param emit - 组件事件发射器\r\n */\r\nexport function useFilePreview(\r\n options: UseFilePreviewOptions,\r\n emit: {\r\n (e: \"preview\", file: File | string): void;\r\n (e: \"download\", file: File | string): void;\r\n },\r\n) {\r\n const { file, url, fileName } = options;\r\n\r\n /* ==================== 基础状态 ==================== */\r\n const loading = ref(false);\r\n const error = ref(\"\");\r\n const fileSize = ref(0);\r\n const showModal = ref(false);\r\n\r\n /* ==================== 解析后的数据 ==================== */\r\n const pdfUrl = ref(\"\");\r\n const pdfTotalPages = ref(0);\r\n\r\n const wordContent = ref(\"\");\r\n const wordHeadings = ref<DocHeading[]>([]);\r\n\r\n const excelSheets = ref<ExcelSheet[]>([]);\r\n\r\n /* ==================== 计算属性 ==================== */\r\n const displayFileName = computed(() => {\r\n if (fileName.value && fileName.value !== \"未知文件\") return fileName.value;\r\n if (file.value?.name) return file.value.name;\r\n if (url.value) return extractFileNameFromUrl(url.value);\r\n return \"未知文件\";\r\n });\r\n\r\n const fileType = computed<FilePreviewType>(() =>\r\n getFileType(displayFileName.value),\r\n );\r\n const fileConfig = computed(() => getFileConfig(fileType.value));\r\n\r\n /* ==================== 内部方法 ==================== */\r\n const clearState = () => {\r\n error.value = \"\";\r\n loading.value = false;\r\n pdfUrl.value = \"\";\r\n pdfTotalPages.value = 0;\r\n wordContent.value = \"\";\r\n wordHeadings.value = [];\r\n excelSheets.value = [];\r\n };\r\n\r\n const setError = (msg: string) => {\r\n error.value = msg;\r\n loading.value = false;\r\n };\r\n\r\n /* ==================== 文件解析 ==================== */\r\n const resolveFile = async (): Promise<File> => {\r\n if (file.value) {\r\n fileSize.value = file.value.size;\r\n return file.value;\r\n }\r\n if (url.value) {\r\n const response = await fetch(url.value);\r\n if (!response.ok)\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\r\n\r\n const blob = await response.blob();\r\n const resolved = new File([blob], displayFileName.value, {\r\n type: blob.type,\r\n });\r\n fileSize.value = resolved.size;\r\n return resolved;\r\n }\r\n throw new Error(\"未提供文件或URL\");\r\n };\r\n\r\n const loadByType = async (\r\n currentFile: File,\r\n type: FilePreviewType,\r\n ): Promise<void> => {\r\n switch (type) {\r\n case \"pdf\": {\r\n const result = await loadPdf(currentFile);\r\n pdfUrl.value = result.url;\r\n pdfTotalPages.value = result.totalPages;\r\n break;\r\n }\r\n case \"word\": {\r\n const result = await loadWord(currentFile);\r\n wordContent.value = result.content;\r\n wordHeadings.value = result.headings;\r\n break;\r\n }\r\n case \"excel\": {\r\n const result = await loadExcel(currentFile);\r\n excelSheets.value = result.sheets;\r\n break;\r\n }\r\n default:\r\n throw new Error(\"不支持的文件格式\");\r\n }\r\n };\r\n\r\n /* ==================== 核心方法 ==================== */\r\n const loadFile = async () => {\r\n if (!file.value && !url.value) {\r\n setError(\"未提供文件或URL\");\r\n return;\r\n }\r\n\r\n clearState();\r\n loading.value = true;\r\n\r\n try {\r\n const currentFile = await resolveFile();\r\n await loadByType(currentFile, fileType.value);\r\n loading.value = false;\r\n } catch (err) {\r\n const errorMessage = err instanceof Error ? err.message : \"未知错误\";\r\n setError(`${fileType.value.toUpperCase()}文件加载失败: ${errorMessage}`);\r\n }\r\n };\r\n\r\n const openPreview = async () => {\r\n showModal.value = true;\r\n await loadFile();\r\n emit(\"preview\", file.value || url.value!);\r\n };\r\n\r\n const downloadFile = () => {\r\n if (file.value) {\r\n const downloadUrl = URL.createObjectURL(file.value);\r\n const a = Object.assign(document.createElement(\"a\"), {\r\n href: downloadUrl,\r\n download: displayFileName.value,\r\n });\r\n a.click();\r\n URL.revokeObjectURL(downloadUrl);\r\n } else if (url.value) {\r\n window.open(url.value, \"_blank\");\r\n }\r\n emit(\"download\", file.value || url.value!);\r\n };\r\n\r\n /* ==================== 副作用 ==================== */\r\n watch(\r\n () => file.value?.size,\r\n (newSize) => {\r\n fileSize.value = newSize || 0;\r\n },\r\n { immediate: true },\r\n );\r\n\r\n onUnmounted(() => {\r\n if (pdfUrl.value?.startsWith(\"blob:\")) {\r\n URL.revokeObjectURL(pdfUrl.value.split(\"#\")[0]);\r\n }\r\n });\r\n\r\n return {\r\n /* 状态 */\r\n loading,\r\n error,\r\n fileSize,\r\n showModal,\r\n /* 解析数据 */\r\n pdfUrl,\r\n pdfTotalPages,\r\n wordContent,\r\n wordHeadings,\r\n excelSheets,\r\n /* 计算属性 */\r\n displayFileName,\r\n fileType,\r\n fileConfig,\r\n /* 方法 */\r\n loadFile,\r\n openPreview,\r\n downloadFile,\r\n };\r\n}\r\n","/**\r\n * useFullscreen — 全屏切换 composable\r\n */\r\nimport { ref, onMounted, onUnmounted, type Ref } from \"vue\";\r\nimport { FULLSCREEN_EVENTS } from \"../data\";\r\n\r\n/**\r\n * 全屏切换 composable — 管理全屏状态与跨浏览器兼容\r\n * @param containerRef - 需要全屏的容器元素引用\r\n */\r\nexport function useFullscreen(containerRef: Ref<HTMLElement | undefined>) {\r\n const isFullscreen = ref(false);\r\n\r\n const handleFullscreenChange = () => {\r\n isFullscreen.value = !!document.fullscreenElement;\r\n };\r\n\r\n const tryMethods = (\r\n methods: Array<() => Promise<void>>,\r\n index = 0,\r\n ): Promise<void> => {\r\n if (index >= methods.length) {\r\n console.warn(\"无法切换全屏状态\");\r\n return Promise.resolve();\r\n }\r\n return methods[index]().catch(() => tryMethods(methods, index + 1));\r\n };\r\n\r\n const toggleFullscreen = async () => {\r\n const element = containerRef.value;\r\n if (!element) return;\r\n\r\n const isCurrentlyFullscreen = !!document.fullscreenElement;\r\n\r\n const methods = isCurrentlyFullscreen\r\n ? [\r\n () => document.exitFullscreen(),\r\n () => (document as any).webkitExitFullscreen(),\r\n () => (document as any).mozCancelFullScreen(),\r\n () => (document as any).msExitFullscreen(),\r\n ]\r\n : [\r\n () => element.requestFullscreen(),\r\n () => (element as any).webkitRequestFullscreen(),\r\n () => (element as any).mozRequestFullScreen(),\r\n () => (element as any).msRequestFullscreen(),\r\n ];\r\n\r\n await tryMethods(methods);\r\n };\r\n\r\n const exitFullscreen = async () => {\r\n if (document.fullscreenElement) {\r\n try {\r\n await document.exitFullscreen();\r\n } catch {\r\n /* ignore */\r\n }\r\n }\r\n isFullscreen.value = false;\r\n };\r\n\r\n onMounted(() => {\r\n FULLSCREEN_EVENTS.forEach((event) =>\r\n document.addEventListener(event, handleFullscreenChange),\r\n );\r\n });\r\n\r\n onUnmounted(() => {\r\n FULLSCREEN_EVENTS.forEach((event) =>\r\n document.removeEventListener(event, handleFullscreenChange),\r\n );\r\n });\r\n\r\n return { isFullscreen, toggleFullscreen, exitFullscreen };\r\n}\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-07-01\r\n * @Description: 文件预览组件(PDF/Word/Excel)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-file-preview-wrapper\">\r\n <!-- 文件信息卡片模式 -->\r\n <div v-if=\"!autoPreview\" class=\"file-info-card\">\r\n <div class=\"file-info\">\r\n <div class=\"file-icon\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"40\"\r\n :color=\"fileConfig.color\"\r\n />\r\n </div>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">\r\n <NEllipsis style=\"max-width: 250px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n </div>\r\n\r\n <div class=\"file-meta\">\r\n <span class=\"file-size\">{{ formatFileSize(fileSize) }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-actions\">\r\n <NButton type=\"primary\" @click=\"openPreview\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-visibility\" />\r\n </template>\r\n 预览\r\n </NButton>\r\n\r\n <NButton type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 模态框预览 -->\r\n <NModal\r\n v-model:show=\"showModal\"\r\n :mask-closable=\"false\"\r\n :closable=\"false\"\r\n :auto-focus=\"false\"\r\n transform-origin=\"center\"\r\n style=\"\r\n width: 85vw;\r\n max-width: 1200px;\r\n min-width: 800px;\r\n height: 75vh;\r\n max-height: 700px;\r\n min-height: 500px;\r\n \"\r\n >\r\n <div ref=\"modalContainer\" class=\"modal-container\">\r\n <!-- 自定义头部 -->\r\n <div class=\"modal-header\">\r\n <div class=\"modal-title\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"20\"\r\n :color=\"fileConfig.color\"\r\n />\r\n <span>{{ displayFileName }}</span>\r\n </div>\r\n <div class=\"modal-actions\">\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"showModal = false\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容 -->\r\n <div class=\"modal-content\">\r\n <!-- 文件信息头部 -->\r\n <div class=\"preview-header\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex items-center gap-3\">\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n <template #icon>\r\n <C_Icon :name=\"fileConfig.icon\" />\r\n </template>\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n <NEllipsis style=\"max-width: 300px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <span class=\"text-sm text-gray-500\">{{\r\n formatFileSize(fileSize)\r\n }}</span>\r\n </div>\r\n\r\n <div class=\"flex gap-2\">\r\n <NButton\r\n size=\"small\"\r\n type=\"tertiary\"\r\n :disabled=\"loading\"\r\n @click=\"loadFile\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-refresh\" />\r\n </template>\r\n 刷新\r\n </NButton>\r\n <NButton size=\"small\" type=\"primary\" @click=\"toggleFullscreen\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n isFullscreen\r\n ? 'ic:outline-fullscreen-exit'\r\n : 'ic:outline-fullscreen'\r\n \"\r\n />\r\n </template>\r\n {{ isFullscreen ? \"退出全屏\" : \"全屏\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容区域 -->\r\n <div class=\"preview-content\">\r\n <!-- 加载和错误状态 -->\r\n <template v-if=\"loading || error\">\r\n <div class=\"status-container\">\r\n <NSpin v-if=\"loading\" size=\"large\">\r\n <template #description>\r\n 正在加载{{ fileType.toUpperCase() }}文件...\r\n </template>\r\n </NSpin>\r\n <NResult\r\n v-else\r\n status=\"error\"\r\n title=\"预览失败\"\r\n :description=\"error\"\r\n >\r\n <template #footer>\r\n <NButton @click=\"loadFile\">重试</NButton>\r\n </template>\r\n </NResult>\r\n </div>\r\n </template>\r\n\r\n <!-- 文件预览 — 委托给子组件 -->\r\n <template v-else>\r\n <PdfViewer\r\n v-if=\"fileType === 'pdf'\"\r\n :pdf-url=\"pdfUrl\"\r\n :total-pages=\"pdfTotalPages\"\r\n />\r\n\r\n <WordViewer\r\n v-else-if=\"fileType === 'word'\"\r\n :content=\"wordContent\"\r\n :headings=\"wordHeadings\"\r\n />\r\n\r\n <ExcelViewer\r\n v-else-if=\"fileType === 'excel'\"\r\n :sheets=\"excelSheets\"\r\n @reload=\"loadFile\"\r\n />\r\n\r\n <!-- 不支持的文件类型 -->\r\n <NResult\r\n v-else\r\n status=\"warning\"\r\n title=\"不支持的文件格式\"\r\n :description=\"`暂不支持预览 ${fileType.toUpperCase()} 格式文件`\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </NModal>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRefs, watch } from \"vue\";\r\nimport { NButton, NEllipsis, NTag, NModal, NSpin, NResult } from \"naive-ui\";\r\nimport { formatFileSize } from \"./data\";\r\nimport PdfViewer from \"./components/PdfViewer/index.vue\";\r\nimport WordViewer from \"./components/WordViewer/index.vue\";\r\nimport ExcelViewer from \"./components/ExcelViewer/index.vue\";\r\nimport { useFilePreview } from \"./composables/useFilePreview\";\r\nimport { useFullscreen } from \"./composables/useFullscreen\";\r\n\r\ndefineOptions({ name: \"C_FilePreview\" });\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n file?: File;\r\n url?: string;\r\n fileName?: string;\r\n autoPreview?: boolean;\r\n }>(),\r\n {\r\n fileName: \"未知文件\",\r\n autoPreview: false,\r\n },\r\n);\r\n\r\nconst { file, url, fileName: propFileName, autoPreview } = toRefs(props);\r\n\r\nconst emit = defineEmits<{\r\n preview: [file: File | string];\r\n download: [file: File | string];\r\n}>();\r\n\r\n/* ==================== Composables ==================== */\r\nconst {\r\n loading,\r\n error,\r\n fileSize,\r\n showModal,\r\n pdfUrl,\r\n pdfTotalPages,\r\n wordContent,\r\n wordHeadings,\r\n excelSheets,\r\n displayFileName,\r\n fileType,\r\n fileConfig,\r\n loadFile,\r\n openPreview,\r\n downloadFile,\r\n} = useFilePreview({ file, url, fileName: propFileName }, emit);\r\n\r\nconst modalContainer = ref<HTMLElement>();\r\nconst { isFullscreen, toggleFullscreen, exitFullscreen } =\r\n useFullscreen(modalContainer);\r\n\r\n/* ==================== Modal 关闭时退出全屏 ==================== */\r\nwatch(showModal, (isShow) => {\r\n if (!isShow) exitFullscreen();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-07-01\r\n * @Description: 文件预览组件(PDF/Word/Excel)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-file-preview-wrapper\">\r\n <!-- 文件信息卡片模式 -->\r\n <div v-if=\"!autoPreview\" class=\"file-info-card\">\r\n <div class=\"file-info\">\r\n <div class=\"file-icon\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"40\"\r\n :color=\"fileConfig.color\"\r\n />\r\n </div>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">\r\n <NEllipsis style=\"max-width: 250px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n </div>\r\n\r\n <div class=\"file-meta\">\r\n <span class=\"file-size\">{{ formatFileSize(fileSize) }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-actions\">\r\n <NButton type=\"primary\" @click=\"openPreview\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-visibility\" />\r\n </template>\r\n 预览\r\n </NButton>\r\n\r\n <NButton type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 模态框预览 -->\r\n <NModal\r\n v-model:show=\"showModal\"\r\n :mask-closable=\"false\"\r\n :closable=\"false\"\r\n :auto-focus=\"false\"\r\n transform-origin=\"center\"\r\n style=\"\r\n width: 85vw;\r\n max-width: 1200px;\r\n min-width: 800px;\r\n height: 75vh;\r\n max-height: 700px;\r\n min-height: 500px;\r\n \"\r\n >\r\n <div ref=\"modalContainer\" class=\"modal-container\">\r\n <!-- 自定义头部 -->\r\n <div class=\"modal-header\">\r\n <div class=\"modal-title\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"20\"\r\n :color=\"fileConfig.color\"\r\n />\r\n <span>{{ displayFileName }}</span>\r\n </div>\r\n <div class=\"modal-actions\">\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"showModal = false\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容 -->\r\n <div class=\"modal-content\">\r\n <!-- 文件信息头部 -->\r\n <div class=\"preview-header\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex items-center gap-3\">\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n <template #icon>\r\n <C_Icon :name=\"fileConfig.icon\" />\r\n </template>\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n <NEllipsis style=\"max-width: 300px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <span class=\"text-sm text-gray-500\">{{\r\n formatFileSize(fileSize)\r\n }}</span>\r\n </div>\r\n\r\n <div class=\"flex gap-2\">\r\n <NButton\r\n size=\"small\"\r\n type=\"tertiary\"\r\n :disabled=\"loading\"\r\n @click=\"loadFile\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-refresh\" />\r\n </template>\r\n 刷新\r\n </NButton>\r\n <NButton size=\"small\" type=\"primary\" @click=\"toggleFullscreen\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n isFullscreen\r\n ? 'ic:outline-fullscreen-exit'\r\n : 'ic:outline-fullscreen'\r\n \"\r\n />\r\n </template>\r\n {{ isFullscreen ? \"退出全屏\" : \"全屏\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容区域 -->\r\n <div class=\"preview-content\">\r\n <!-- 加载和错误状态 -->\r\n <template v-if=\"loading || error\">\r\n <div class=\"status-container\">\r\n <NSpin v-if=\"loading\" size=\"large\">\r\n <template #description>\r\n 正在加载{{ fileType.toUpperCase() }}文件...\r\n </template>\r\n </NSpin>\r\n <NResult\r\n v-else\r\n status=\"error\"\r\n title=\"预览失败\"\r\n :description=\"error\"\r\n >\r\n <template #footer>\r\n <NButton @click=\"loadFile\">重试</NButton>\r\n </template>\r\n </NResult>\r\n </div>\r\n </template>\r\n\r\n <!-- 文件预览 — 委托给子组件 -->\r\n <template v-else>\r\n <PdfViewer\r\n v-if=\"fileType === 'pdf'\"\r\n :pdf-url=\"pdfUrl\"\r\n :total-pages=\"pdfTotalPages\"\r\n />\r\n\r\n <WordViewer\r\n v-else-if=\"fileType === 'word'\"\r\n :content=\"wordContent\"\r\n :headings=\"wordHeadings\"\r\n />\r\n\r\n <ExcelViewer\r\n v-else-if=\"fileType === 'excel'\"\r\n :sheets=\"excelSheets\"\r\n @reload=\"loadFile\"\r\n />\r\n\r\n <!-- 不支持的文件类型 -->\r\n <NResult\r\n v-else\r\n status=\"warning\"\r\n title=\"不支持的文件格式\"\r\n :description=\"`暂不支持预览 ${fileType.toUpperCase()} 格式文件`\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </NModal>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRefs, watch } from \"vue\";\r\nimport { NButton, NEllipsis, NTag, NModal, NSpin, NResult } from \"naive-ui\";\r\nimport { formatFileSize } from \"./data\";\r\nimport PdfViewer from \"./components/PdfViewer/index.vue\";\r\nimport WordViewer from \"./components/WordViewer/index.vue\";\r\nimport ExcelViewer from \"./components/ExcelViewer/index.vue\";\r\nimport { useFilePreview } from \"./composables/useFilePreview\";\r\nimport { useFullscreen } from \"./composables/useFullscreen\";\r\n\r\ndefineOptions({ name: \"C_FilePreview\" });\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n file?: File;\r\n url?: string;\r\n fileName?: string;\r\n autoPreview?: boolean;\r\n }>(),\r\n {\r\n fileName: \"未知文件\",\r\n autoPreview: false,\r\n },\r\n);\r\n\r\nconst { file, url, fileName: propFileName, autoPreview } = toRefs(props);\r\n\r\nconst emit = defineEmits<{\r\n preview: [file: File | string];\r\n download: [file: File | string];\r\n}>();\r\n\r\n/* ==================== Composables ==================== */\r\nconst {\r\n loading,\r\n error,\r\n fileSize,\r\n showModal,\r\n pdfUrl,\r\n pdfTotalPages,\r\n wordContent,\r\n wordHeadings,\r\n excelSheets,\r\n displayFileName,\r\n fileType,\r\n fileConfig,\r\n loadFile,\r\n openPreview,\r\n downloadFile,\r\n} = useFilePreview({ file, url, fileName: propFileName }, emit);\r\n\r\nconst modalContainer = ref<HTMLElement>();\r\nconst { isFullscreen, toggleFullscreen, exitFullscreen } =\r\n useFullscreen(modalContainer);\r\n\r\n/* ==================== Modal 关闭时退出全屏 ==================== */\r\nwatch(showModal, (isShow) => {\r\n if (!isShow) exitFullscreen();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-07-01\r\n * @Description: 文件预览组件(PDF/Word/Excel)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-file-preview-wrapper\">\r\n <!-- 文件信息卡片模式 -->\r\n <div v-if=\"!autoPreview\" class=\"file-info-card\">\r\n <div class=\"file-info\">\r\n <div class=\"file-icon\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"40\"\r\n :color=\"fileConfig.color\"\r\n />\r\n </div>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">\r\n <NEllipsis style=\"max-width: 250px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n </div>\r\n\r\n <div class=\"file-meta\">\r\n <span class=\"file-size\">{{ formatFileSize(fileSize) }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-actions\">\r\n <NButton type=\"primary\" @click=\"openPreview\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-visibility\" />\r\n </template>\r\n 预览\r\n </NButton>\r\n\r\n <NButton type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 模态框预览 -->\r\n <NModal\r\n v-model:show=\"showModal\"\r\n :mask-closable=\"false\"\r\n :closable=\"false\"\r\n :auto-focus=\"false\"\r\n transform-origin=\"center\"\r\n style=\"\r\n width: 85vw;\r\n max-width: 1200px;\r\n min-width: 800px;\r\n height: 75vh;\r\n max-height: 700px;\r\n min-height: 500px;\r\n \"\r\n >\r\n <div ref=\"modalContainer\" class=\"modal-container\">\r\n <!-- 自定义头部 -->\r\n <div class=\"modal-header\">\r\n <div class=\"modal-title\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"20\"\r\n :color=\"fileConfig.color\"\r\n />\r\n <span>{{ displayFileName }}</span>\r\n </div>\r\n <div class=\"modal-actions\">\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"showModal = false\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容 -->\r\n <div class=\"modal-content\">\r\n <!-- 文件信息头部 -->\r\n <div class=\"preview-header\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex items-center gap-3\">\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n <template #icon>\r\n <C_Icon :name=\"fileConfig.icon\" />\r\n </template>\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n <NEllipsis style=\"max-width: 300px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <span class=\"text-sm text-gray-500\">{{\r\n formatFileSize(fileSize)\r\n }}</span>\r\n </div>\r\n\r\n <div class=\"flex gap-2\">\r\n <NButton\r\n size=\"small\"\r\n type=\"tertiary\"\r\n :disabled=\"loading\"\r\n @click=\"loadFile\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-refresh\" />\r\n </template>\r\n 刷新\r\n </NButton>\r\n <NButton size=\"small\" type=\"primary\" @click=\"toggleFullscreen\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n isFullscreen\r\n ? 'ic:outline-fullscreen-exit'\r\n : 'ic:outline-fullscreen'\r\n \"\r\n />\r\n </template>\r\n {{ isFullscreen ? \"退出全屏\" : \"全屏\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容区域 -->\r\n <div class=\"preview-content\">\r\n <!-- 加载和错误状态 -->\r\n <template v-if=\"loading || error\">\r\n <div class=\"status-container\">\r\n <NSpin v-if=\"loading\" size=\"large\">\r\n <template #description>\r\n 正在加载{{ fileType.toUpperCase() }}文件...\r\n </template>\r\n </NSpin>\r\n <NResult\r\n v-else\r\n status=\"error\"\r\n title=\"预览失败\"\r\n :description=\"error\"\r\n >\r\n <template #footer>\r\n <NButton @click=\"loadFile\">重试</NButton>\r\n </template>\r\n </NResult>\r\n </div>\r\n </template>\r\n\r\n <!-- 文件预览 — 委托给子组件 -->\r\n <template v-else>\r\n <PdfViewer\r\n v-if=\"fileType === 'pdf'\"\r\n :pdf-url=\"pdfUrl\"\r\n :total-pages=\"pdfTotalPages\"\r\n />\r\n\r\n <WordViewer\r\n v-else-if=\"fileType === 'word'\"\r\n :content=\"wordContent\"\r\n :headings=\"wordHeadings\"\r\n />\r\n\r\n <ExcelViewer\r\n v-else-if=\"fileType === 'excel'\"\r\n :sheets=\"excelSheets\"\r\n @reload=\"loadFile\"\r\n />\r\n\r\n <!-- 不支持的文件类型 -->\r\n <NResult\r\n v-else\r\n status=\"warning\"\r\n title=\"不支持的文件格式\"\r\n :description=\"`暂不支持预览 ${fileType.toUpperCase()} 格式文件`\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </NModal>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRefs, watch } from \"vue\";\r\nimport { NButton, NEllipsis, NTag, NModal, NSpin, NResult } from \"naive-ui\";\r\nimport { formatFileSize } from \"./data\";\r\nimport PdfViewer from \"./components/PdfViewer/index.vue\";\r\nimport WordViewer from \"./components/WordViewer/index.vue\";\r\nimport ExcelViewer from \"./components/ExcelViewer/index.vue\";\r\nimport { useFilePreview } from \"./composables/useFilePreview\";\r\nimport { useFullscreen } from \"./composables/useFullscreen\";\r\n\r\ndefineOptions({ name: \"C_FilePreview\" });\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n file?: File;\r\n url?: string;\r\n fileName?: string;\r\n autoPreview?: boolean;\r\n }>(),\r\n {\r\n fileName: \"未知文件\",\r\n autoPreview: false,\r\n },\r\n);\r\n\r\nconst { file, url, fileName: propFileName, autoPreview } = toRefs(props);\r\n\r\nconst emit = defineEmits<{\r\n preview: [file: File | string];\r\n download: [file: File | string];\r\n}>();\r\n\r\n/* ==================== Composables ==================== */\r\nconst {\r\n loading,\r\n error,\r\n fileSize,\r\n showModal,\r\n pdfUrl,\r\n pdfTotalPages,\r\n wordContent,\r\n wordHeadings,\r\n excelSheets,\r\n displayFileName,\r\n fileType,\r\n fileConfig,\r\n loadFile,\r\n openPreview,\r\n downloadFile,\r\n} = useFilePreview({ file, url, fileName: propFileName }, emit);\r\n\r\nconst modalContainer = ref<HTMLElement>();\r\nconst { isFullscreen, toggleFullscreen, exitFullscreen } =\r\n useFullscreen(modalContainer);\r\n\r\n/* ==================== Modal 关闭时退出全屏 ==================== */\r\nwatch(showModal, (isShow) => {\r\n if (!isShow) exitFullscreen();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"C_FilePreview2.js","names":["totalPages","pdfUrl","headings","content","sheets","$emit"],"sources":["../src/components/C_FilePreview/data.ts","../src/components/C_FilePreview/components/PdfViewer/index.vue","../src/components/C_FilePreview/components/PdfViewer/index.vue","../src/components/C_FilePreview/components/PdfViewer/index.vue","../src/components/C_FilePreview/components/WordViewer/index.vue","../src/components/C_FilePreview/components/WordViewer/index.vue","../src/components/C_FilePreview/components/WordViewer/index.vue","../src/components/C_FilePreview/components/ExcelViewer/index.vue","../src/components/C_FilePreview/components/ExcelViewer/index.vue","../src/components/C_FilePreview/components/ExcelViewer/index.vue","../src/components/C_FilePreview/composables/useFilePreview.ts","../src/components/C_FilePreview/composables/useFullscreen.ts","../src/components/C_FilePreview/index.vue","../src/components/C_FilePreview/index.vue","../src/components/C_FilePreview/index.vue"],"sourcesContent":["/**\r\n * C_FilePreview 常量配置与工具函数\r\n * 类型定义统一在 ./types.ts\r\n */\r\nimport { type Ref } from \"vue\";\r\nimport * as XLSX from \"xlsx\";\r\nimport mammoth from \"mammoth\";\r\nimport type {\r\n FilePreviewType,\r\n FileConfig,\r\n ZoomConfig,\r\n ExcelRow,\r\n ExcelColumn,\r\n ExcelSheet,\r\n DocHeading,\r\n PdfLoadResult,\r\n WordLoadResult,\r\n ExcelLoadResult,\r\n} from \"./types\";\r\n\r\n/* ==================== 常量配置 ==================== */\r\n\r\nexport const FILE_TYPE_MAP: Record<string, FilePreviewType> = {\r\n pdf: \"pdf\",\r\n doc: \"word\",\r\n docx: \"word\",\r\n xls: \"excel\",\r\n xlsx: \"excel\",\r\n};\r\n\r\nexport const FILE_CONFIGS: Record<string, FileConfig> = {\r\n pdf: {\r\n tagType: \"error\",\r\n icon: \"ic:outline-picture-as-pdf\",\r\n color: \"#dc2626\",\r\n },\r\n word: { tagType: \"info\", icon: \"ic:outline-description\", color: \"#2563eb\" },\r\n excel: { tagType: \"success\", icon: \"ic:outline-grid-on\", color: \"#16a34a\" },\r\n unknown: {\r\n tagType: \"default\",\r\n icon: \"ic:outline-insert-drive-file\",\r\n color: \"#6b7280\",\r\n },\r\n};\r\n\r\nexport const ZOOM_CONFIGS: Record<string, ZoomConfig> = {\r\n pdf: { min: 0.5, max: 3, step: 0.25, default: 1 },\r\n word: { min: 50, max: 200, step: 10, default: 100 },\r\n};\r\n\r\nexport const PAGE_SIZE_OPTIONS = [20, 50, 100, 200];\r\n\r\nexport const FULLSCREEN_EVENTS = [\r\n \"fullscreenchange\",\r\n \"webkitfullscreenchange\",\r\n \"mozfullscreenchange\",\r\n \"MSFullscreenChange\",\r\n];\r\n\r\n/* ==================== 文件信息工具函数 ==================== */\r\n\r\nexport const extractFileNameFromUrl = (url: string): string => {\r\n try {\r\n const urlPath = url.split(\"/\").pop() || url.split(\"\\\\\").pop();\r\n if (urlPath?.includes(\".\")) {\r\n const decodedName = decodeURIComponent(urlPath.split(\"?\")[0]);\r\n if (decodedName && decodedName !== url) return decodedName;\r\n }\r\n } catch (error) {\r\n console.warn(\"解析URL文件名失败:\", error);\r\n }\r\n return \"未知文件\";\r\n};\r\n\r\nexport const formatFileSize = (bytes: number): string => {\r\n if (bytes === 0) return \"0 B\";\r\n const k = 1024;\r\n const sizes = [\"B\", \"KB\", \"MB\", \"GB\"];\r\n const i = Math.floor(Math.log(bytes) / Math.log(k));\r\n return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + \" \" + sizes[i];\r\n};\r\n\r\nexport const getFileType = (fileName: string): FilePreviewType => {\r\n if (!fileName || fileName === \"未知文件\") return \"unknown\";\r\n const ext = fileName.toLowerCase().split(\".\").pop();\r\n if (!ext) return \"unknown\";\r\n return FILE_TYPE_MAP[ext] || \"unknown\";\r\n};\r\n\r\nexport const getFileConfig = (fileType: string): FileConfig => {\r\n return FILE_CONFIGS[fileType] || FILE_CONFIGS.unknown;\r\n};\r\n\r\n/* ==================== 缩放工具 ==================== */\r\n\r\nexport const createZoomHandler = (\r\n valueRef: Ref<number>,\r\n config: ZoomConfig,\r\n) => {\r\n return (action: \"in\" | \"out\" | \"reset\") => {\r\n const { min, max, step, default: defaultValue } = config;\r\n switch (action) {\r\n case \"reset\":\r\n valueRef.value = defaultValue;\r\n break;\r\n case \"in\":\r\n if (valueRef.value < max) valueRef.value += step;\r\n break;\r\n case \"out\":\r\n if (valueRef.value > min) valueRef.value -= step;\r\n break;\r\n }\r\n };\r\n};\r\n\r\n/* ==================== Excel 工具函数 ==================== */\r\n\r\nexport const getColumnLetter = (index: number): string => {\r\n let result = \"\";\r\n while (index >= 0) {\r\n result = String.fromCharCode(65 + (index % 26)) + result;\r\n index = Math.floor(index / 26) - 1;\r\n }\r\n return result;\r\n};\r\n\r\nexport const formatCellValue = (value: any): string => {\r\n if (!value) return \"\";\r\n const strValue = String(value).trim();\r\n\r\n if (!isNaN(Number(strValue)) && strValue !== \"\" && strValue.length < 15) {\r\n const num = Number(strValue);\r\n return Number.isInteger(num)\r\n ? num.toLocaleString()\r\n : num.toLocaleString(undefined, { maximumFractionDigits: 2 });\r\n }\r\n return strValue;\r\n};\r\n\r\nexport const getCellClass = (value: any): string => {\r\n if (!value) return \"cell-empty\";\r\n const strValue = String(value).trim();\r\n\r\n if (!isNaN(Number(strValue)) && strValue !== \"\") return \"cell-number\";\r\n if (strValue.match(/[■▬▪▫─━]/)) return \"cell-gantt\";\r\n if (\r\n strValue.match(/^\\d{4}[-/]\\d{2}[-/]\\d{2}/) ||\r\n strValue.match(/^\\d{2}[-/]\\d{2}[-/]\\d{4}/)\r\n )\r\n return \"cell-date\";\r\n if ([\"true\", \"false\"].includes(strValue.toLowerCase())) return \"cell-boolean\";\r\n return strValue.length > 20 ? \"cell-text cell-long\" : \"cell-text\";\r\n};\r\n\r\nexport const processExcelSheet = (\r\n worksheet: any,\r\n merges: any[],\r\n): { data: ExcelRow[]; columns: ExcelColumn[] } => {\r\n const range = XLSX.utils.decode_range(worksheet[\"!ref\"] || \"A1:A1\");\r\n const { c: maxCol, r: maxRow } = range.e;\r\n\r\n const columns: ExcelColumn[] = Array.from({ length: maxCol + 1 }, (_, c) => {\r\n const cellAddress = XLSX.utils.encode_cell({ r: 0, c });\r\n const cell = worksheet[cellAddress];\r\n const columnName = cell ? String(cell.v || \"\").trim() : `列${c + 1}`;\r\n return {\r\n title: columnName || `列${c + 1}`,\r\n key: `col_${c}`,\r\n width: Math.min(Math.max(columnName.length * 12, 80), 200),\r\n };\r\n });\r\n\r\n const mergeMap = new Map();\r\n merges.forEach(({ s: { r: sRow, c: sCol }, e: { r: eRow, c: eCol } }) => {\r\n for (let r = sRow; r <= eRow; r++) {\r\n for (let c = sCol; c <= eCol; c++) {\r\n mergeMap.set(`${r}-${c}`, {\r\n isMain: r === sRow && c === sCol,\r\n rowspan: eRow - sRow + 1,\r\n colspan: eCol - sCol + 1,\r\n });\r\n }\r\n }\r\n });\r\n\r\n const data: ExcelRow[] = Array.from({ length: maxRow + 1 }, (_, r) => {\r\n const row: ExcelRow = {};\r\n for (let c = 0; c <= maxCol; c++) {\r\n const cellAddress = XLSX.utils.encode_cell({ r, c });\r\n const cell = worksheet[cellAddress];\r\n const mergeInfo = mergeMap.get(`${r}-${c}`);\r\n\r\n row[`col_${c}`] = {\r\n value: cell ? cell.v || \"\" : \"\",\r\n merged: !!mergeInfo,\r\n hidden: mergeInfo && !mergeInfo.isMain,\r\n rowspan: mergeInfo?.isMain ? mergeInfo.rowspan : 1,\r\n colspan: mergeInfo?.isMain ? mergeInfo.colspan : 1,\r\n };\r\n }\r\n return row;\r\n });\r\n\r\n return { data, columns };\r\n};\r\n\r\n/* ==================== 文件加载器 ==================== */\r\n\r\nexport async function loadPdf(file: File): Promise<PdfLoadResult> {\r\n const fileUrl = URL.createObjectURL(file);\r\n return {\r\n url: fileUrl + \"#toolbar=1&navpanes=1&scrollbar=1&view=FitH\",\r\n totalPages: 1,\r\n };\r\n}\r\n\r\nexport async function loadWord(file: File): Promise<WordLoadResult> {\r\n const arrayBuffer = await file.arrayBuffer();\r\n const result = await mammoth.convertToHtml({\r\n arrayBuffer,\r\n styleMap: [\r\n \"p[style-name='Heading 1'] => h1:fresh\",\r\n \"p[style-name='Heading 2'] => h2:fresh\",\r\n \"p[style-name='Heading 3'] => h3:fresh\",\r\n \"p[style-name='Title'] => h1.title:fresh\",\r\n ],\r\n } as any);\r\n\r\n let content = result.value;\r\n const headings: DocHeading[] = [];\r\n\r\n /* 提取标题并注入 id */\r\n const tempDiv = document.createElement(\"div\");\r\n tempDiv.innerHTML = content;\r\n const headingElements = tempDiv.querySelectorAll(\"h1, h2, h3, h4, h5, h6\");\r\n\r\n Array.from(headingElements).forEach((el, index) => {\r\n const id = `heading-${index}`;\r\n const level = parseInt(el.tagName.charAt(1));\r\n const text = el.textContent || \"\";\r\n\r\n content = content.replace(\r\n el.outerHTML,\r\n el.outerHTML.replace(\r\n el.tagName.toLowerCase(),\r\n `${el.tagName.toLowerCase()} id=\"${id}\"`,\r\n ),\r\n );\r\n\r\n headings.push({ id, text, level });\r\n });\r\n\r\n return { content, headings };\r\n}\r\n\r\nexport async function loadExcel(file: File): Promise<ExcelLoadResult> {\r\n const arrayBuffer = await file.arrayBuffer();\r\n const workbook = XLSX.read(arrayBuffer, {\r\n type: \"array\",\r\n cellStyles: true,\r\n cellFormula: true,\r\n cellDates: true,\r\n });\r\n\r\n if (!workbook.SheetNames.length) {\r\n throw new Error(\"Excel文件中没有找到工作表\");\r\n }\r\n\r\n const sheets: ExcelSheet[] = workbook.SheetNames.map((name) => {\r\n const worksheet = workbook.Sheets[name];\r\n const merges = worksheet[\"!merges\"] || [];\r\n const { data, columns } = processExcelSheet(worksheet, merges);\r\n return { name, data, merges, columns };\r\n });\r\n\r\n return { sheets };\r\n}\r\n","/* unplugin-vue-components disabled */<!--\r\n PdfViewer — PDF 文件预览子组件\r\n 负责: 工具栏(翻页+缩放) + iframe 渲染\r\n-->\r\n<template>\r\n <div class=\"file-container pdf-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <!-- 翻页控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage <= 1\"\r\n @click=\"changePage('prev')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-left\" />\r\n </template>\r\n </NButton>\r\n <NInputNumber\r\n v-model:value=\"currentPage\"\r\n size=\"small\"\r\n :min=\"1\"\r\n :max=\"totalPages\"\r\n style=\"width: 80px\"\r\n @update:value=\"changePage\"\r\n />\r\n <span class=\"text-sm text-gray-600\">/ {{ totalPages }}</span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage >= totalPages\"\r\n @click=\"changePage('next')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-right\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n\r\n <!-- 缩放控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale <= 0.5\"\r\n @click=\"adjustZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ Math.round(scale * 100) }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale >= 3\"\r\n @click=\"adjustZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-viewer\">\r\n <iframe\r\n v-if=\"pdfUrl\"\r\n :src=\"pdfUrl\"\r\n class=\"w-full h-full border-0\"\r\n title=\"PDF预览\"\r\n frameborder=\"0\"\r\n allowfullscreen\r\n />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NInputNumber } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\n\r\nconst props = defineProps<{\r\n pdfUrl: string;\r\n totalPages: number;\r\n}>();\r\n\r\nconst currentPage = ref(1);\r\nconst scale = ref(1);\r\n\r\nconst adjustZoom = createZoomHandler(scale, ZOOM_CONFIGS.pdf);\r\n\r\nconst changePage = (action: \"prev\" | \"next\" | number) => {\r\n if (typeof action === \"number\") {\r\n currentPage.value = Math.max(1, Math.min(action, props.totalPages));\r\n } else {\r\n const delta = action === \"prev\" ? -1 : 1;\r\n currentPage.value = Math.max(\r\n 1,\r\n Math.min(currentPage.value + delta, props.totalPages),\r\n );\r\n }\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n PdfViewer — PDF 文件预览子组件\r\n 负责: 工具栏(翻页+缩放) + iframe 渲染\r\n-->\r\n<template>\r\n <div class=\"file-container pdf-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <!-- 翻页控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage <= 1\"\r\n @click=\"changePage('prev')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-left\" />\r\n </template>\r\n </NButton>\r\n <NInputNumber\r\n v-model:value=\"currentPage\"\r\n size=\"small\"\r\n :min=\"1\"\r\n :max=\"totalPages\"\r\n style=\"width: 80px\"\r\n @update:value=\"changePage\"\r\n />\r\n <span class=\"text-sm text-gray-600\">/ {{ totalPages }}</span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage >= totalPages\"\r\n @click=\"changePage('next')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-right\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n\r\n <!-- 缩放控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale <= 0.5\"\r\n @click=\"adjustZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ Math.round(scale * 100) }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale >= 3\"\r\n @click=\"adjustZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-viewer\">\r\n <iframe\r\n v-if=\"pdfUrl\"\r\n :src=\"pdfUrl\"\r\n class=\"w-full h-full border-0\"\r\n title=\"PDF预览\"\r\n frameborder=\"0\"\r\n allowfullscreen\r\n />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NInputNumber } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\n\r\nconst props = defineProps<{\r\n pdfUrl: string;\r\n totalPages: number;\r\n}>();\r\n\r\nconst currentPage = ref(1);\r\nconst scale = ref(1);\r\n\r\nconst adjustZoom = createZoomHandler(scale, ZOOM_CONFIGS.pdf);\r\n\r\nconst changePage = (action: \"prev\" | \"next\" | number) => {\r\n if (typeof action === \"number\") {\r\n currentPage.value = Math.max(1, Math.min(action, props.totalPages));\r\n } else {\r\n const delta = action === \"prev\" ? -1 : 1;\r\n currentPage.value = Math.max(\r\n 1,\r\n Math.min(currentPage.value + delta, props.totalPages),\r\n );\r\n }\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n PdfViewer — PDF 文件预览子组件\r\n 负责: 工具栏(翻页+缩放) + iframe 渲染\r\n-->\r\n<template>\r\n <div class=\"file-container pdf-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <!-- 翻页控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage <= 1\"\r\n @click=\"changePage('prev')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-left\" />\r\n </template>\r\n </NButton>\r\n <NInputNumber\r\n v-model:value=\"currentPage\"\r\n size=\"small\"\r\n :min=\"1\"\r\n :max=\"totalPages\"\r\n style=\"width: 80px\"\r\n @update:value=\"changePage\"\r\n />\r\n <span class=\"text-sm text-gray-600\">/ {{ totalPages }}</span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"currentPage >= totalPages\"\r\n @click=\"changePage('next')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-chevron-right\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n\r\n <!-- 缩放控制 -->\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale <= 0.5\"\r\n @click=\"adjustZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ Math.round(scale * 100) }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"scale >= 3\"\r\n @click=\"adjustZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 适应\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-viewer\">\r\n <iframe\r\n v-if=\"pdfUrl\"\r\n :src=\"pdfUrl\"\r\n class=\"w-full h-full border-0\"\r\n title=\"PDF预览\"\r\n frameborder=\"0\"\r\n allowfullscreen\r\n />\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NInputNumber } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\n\r\nconst props = defineProps<{\r\n pdfUrl: string;\r\n totalPages: number;\r\n}>();\r\n\r\nconst currentPage = ref(1);\r\nconst scale = ref(1);\r\n\r\nconst adjustZoom = createZoomHandler(scale, ZOOM_CONFIGS.pdf);\r\n\r\nconst changePage = (action: \"prev\" | \"next\" | number) => {\r\n if (typeof action === \"number\") {\r\n currentPage.value = Math.max(1, Math.min(action, props.totalPages));\r\n } else {\r\n const delta = action === \"prev\" ? -1 : 1;\r\n currentPage.value = Math.max(\r\n 1,\r\n Math.min(currentPage.value + delta, props.totalPages),\r\n );\r\n }\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n WordViewer — Word 文档预览子组件\r\n 负责: 目录导航 + 缩放 + 文档内容渲染\r\n-->\r\n<template>\r\n <div class=\"file-container word-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showOutline = !showOutline\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-list\" />\r\n </template>\r\n {{ showOutline ? \"隐藏\" : \"显示\" }}目录\r\n </NButton>\r\n <NDivider vertical />\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom <= 50\"\r\n @click=\"adjustWordZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ wordZoom }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom >= 200\"\r\n @click=\"adjustWordZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustWordZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 重置\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <div class=\"word-layout\">\r\n <div class=\"word-main\">\r\n <!-- 侧边栏目录 -->\r\n <div v-if=\"showOutline\" class=\"word-outline\">\r\n <div class=\"outline-header\">\r\n <h3 class=\"text-sm font-semibold\">文档目录</h3>\r\n </div>\r\n <div class=\"outline-content\">\r\n <div\r\n v-for=\"(heading, index) in headings\"\r\n :key=\"index\"\r\n class=\"outline-item\"\r\n :class=\"`level-${heading.level}`\"\r\n @click=\"scrollToHeading(heading.id)\"\r\n >\r\n {{ heading.text }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 主要内容区域 -->\r\n <div class=\"word-content\">\r\n <div\r\n class=\"word-document\"\r\n :style=\"{\r\n transform: `scale(${wordZoom / 100})`,\r\n transformOrigin: 'top center',\r\n }\"\r\n v-html=\"content\"\r\n ></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NDivider } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\nimport type { DocHeading } from \"../../types\";\r\n\r\ndefineProps<{\r\n content: string;\r\n headings: DocHeading[];\r\n}>();\r\n\r\nconst showOutline = ref(true);\r\nconst wordZoom = ref(100);\r\n\r\nconst adjustWordZoom = createZoomHandler(wordZoom, ZOOM_CONFIGS.word);\r\n\r\nconst scrollToHeading = (headingId: string) => {\r\n document\r\n .getElementById(headingId)\r\n ?.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n WordViewer — Word 文档预览子组件\r\n 负责: 目录导航 + 缩放 + 文档内容渲染\r\n-->\r\n<template>\r\n <div class=\"file-container word-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showOutline = !showOutline\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-list\" />\r\n </template>\r\n {{ showOutline ? \"隐藏\" : \"显示\" }}目录\r\n </NButton>\r\n <NDivider vertical />\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom <= 50\"\r\n @click=\"adjustWordZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ wordZoom }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom >= 200\"\r\n @click=\"adjustWordZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustWordZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 重置\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <div class=\"word-layout\">\r\n <div class=\"word-main\">\r\n <!-- 侧边栏目录 -->\r\n <div v-if=\"showOutline\" class=\"word-outline\">\r\n <div class=\"outline-header\">\r\n <h3 class=\"text-sm font-semibold\">文档目录</h3>\r\n </div>\r\n <div class=\"outline-content\">\r\n <div\r\n v-for=\"(heading, index) in headings\"\r\n :key=\"index\"\r\n class=\"outline-item\"\r\n :class=\"`level-${heading.level}`\"\r\n @click=\"scrollToHeading(heading.id)\"\r\n >\r\n {{ heading.text }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 主要内容区域 -->\r\n <div class=\"word-content\">\r\n <div\r\n class=\"word-document\"\r\n :style=\"{\r\n transform: `scale(${wordZoom / 100})`,\r\n transformOrigin: 'top center',\r\n }\"\r\n v-html=\"content\"\r\n ></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NDivider } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\nimport type { DocHeading } from \"../../types\";\r\n\r\ndefineProps<{\r\n content: string;\r\n headings: DocHeading[];\r\n}>();\r\n\r\nconst showOutline = ref(true);\r\nconst wordZoom = ref(100);\r\n\r\nconst adjustWordZoom = createZoomHandler(wordZoom, ZOOM_CONFIGS.word);\r\n\r\nconst scrollToHeading = (headingId: string) => {\r\n document\r\n .getElementById(headingId)\r\n ?.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n WordViewer — Word 文档预览子组件\r\n 负责: 目录导航 + 缩放 + 文档内容渲染\r\n-->\r\n<template>\r\n <div class=\"file-container word-container\">\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showOutline = !showOutline\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-list\" />\r\n </template>\r\n {{ showOutline ? \"隐藏\" : \"显示\" }}目录\r\n </NButton>\r\n <NDivider vertical />\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom <= 50\"\r\n @click=\"adjustWordZoom('out')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-out\" />\r\n </template>\r\n </NButton>\r\n <span class=\"text-sm text-gray-600 min-w-12 text-center\">\r\n {{ wordZoom }}%\r\n </span>\r\n <NButton\r\n size=\"small\"\r\n :disabled=\"wordZoom >= 200\"\r\n @click=\"adjustWordZoom('in')\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-zoom-in\" />\r\n </template>\r\n </NButton>\r\n <NButton size=\"small\" @click=\"adjustWordZoom('reset')\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-fit-screen\" />\r\n </template>\r\n 重置\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <div class=\"word-layout\">\r\n <div class=\"word-main\">\r\n <!-- 侧边栏目录 -->\r\n <div v-if=\"showOutline\" class=\"word-outline\">\r\n <div class=\"outline-header\">\r\n <h3 class=\"text-sm font-semibold\">文档目录</h3>\r\n </div>\r\n <div class=\"outline-content\">\r\n <div\r\n v-for=\"(heading, index) in headings\"\r\n :key=\"index\"\r\n class=\"outline-item\"\r\n :class=\"`level-${heading.level}`\"\r\n @click=\"scrollToHeading(heading.id)\"\r\n >\r\n {{ heading.text }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 主要内容区域 -->\r\n <div class=\"word-content\">\r\n <div\r\n class=\"word-document\"\r\n :style=\"{\r\n transform: `scale(${wordZoom / 100})`,\r\n transformOrigin: 'top center',\r\n }\"\r\n v-html=\"content\"\r\n ></div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref } from \"vue\";\r\nimport { NButton, NDivider } from \"naive-ui\";\r\nimport { createZoomHandler, ZOOM_CONFIGS } from \"../../data\";\r\nimport type { DocHeading } from \"../../types\";\r\n\r\ndefineProps<{\r\n content: string;\r\n headings: DocHeading[];\r\n}>();\r\n\r\nconst showOutline = ref(true);\r\nconst wordZoom = ref(100);\r\n\r\nconst adjustWordZoom = createZoomHandler(wordZoom, ZOOM_CONFIGS.word);\r\n\r\nconst scrollToHeading = (headingId: string) => {\r\n document\r\n .getElementById(headingId)\r\n ?.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n ExcelViewer — Excel 表格预览子组件\r\n 负责: 工作表切换 + 表格渲染(合并单元格) + 分页 + 格式切换\r\n-->\r\n<template>\r\n <div class=\"file-container excel-container\">\r\n <!-- 工具栏 -->\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NTabs\r\n v-if=\"sheets.length > 1\"\r\n v-model:value=\"activeSheet\"\r\n type=\"card\"\r\n size=\"small\"\r\n >\r\n <NTabPane\r\n v-for=\"sheet in sheets\"\r\n :key=\"sheet.name\"\r\n :name=\"sheet.name\"\r\n :tab=\"sheet.name\"\r\n />\r\n </NTabs>\r\n <span v-else class=\"text-sm text-gray-600\">\r\n 工作表: {{ activeSheet }}\r\n </span>\r\n </div>\r\n\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showExcelFormat = !showExcelFormat\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-format-paint\" />\r\n </template>\r\n {{ showExcelFormat ? \"紧凑视图\" : \"完整格式\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"excel-viewer\">\r\n <!-- 信息面板 -->\r\n <div v-if=\"currentData.length > 0\" class=\"excel-info\">\r\n <NText depth=\"3\" class=\"text-sm\">\r\n 当前工作表: {{ activeSheet }} | 总行数: {{ currentData.length }} |\r\n 总列数: {{ currentColumns.length }} | 当前页:\r\n {{ currentExcelPage }}/{{ totalExcelPages }}\r\n </NText>\r\n </div>\r\n\r\n <!-- 表格 -->\r\n <div class=\"excel-table-container\">\r\n <div\r\n class=\"excel-table-wrapper\"\r\n :class=\"{ 'simple-mode': !showExcelFormat }\"\r\n >\r\n <table v-if=\"currentData.length > 0\" class=\"excel-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"row-number\">行号</th>\r\n <th\r\n v-for=\"(col, index) in currentColumns\"\r\n :key=\"col.key\"\r\n class=\"excel-header\"\r\n :style=\"{ minWidth: col.width + 'px' }\"\r\n >\r\n <div class=\"header-content\">\r\n <span class=\"column-letter\">{{\r\n getColumnLetter(index)\r\n }}</span>\r\n <span class=\"column-title\">{{ col.title }}</span>\r\n </div>\r\n </th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr\r\n v-for=\"(row, rowIndex) in paginatedData\"\r\n :key=\"rowIndex\"\r\n class=\"excel-row\"\r\n >\r\n <td class=\"row-number\">\r\n {{ (currentExcelPage - 1) * pageSize + rowIndex + 1 }}\r\n </td>\r\n <td\r\n v-for=\"col in currentColumns\"\r\n :key=\"col.key\"\r\n v-show=\"!row[col.key]?.hidden\"\r\n class=\"excel-cell\"\r\n :class=\"[\r\n getCellClass(row[col.key]?.value),\r\n row[col.key]?.merged ? 'merged-cell' : '',\r\n !showExcelFormat ? 'compact-cell' : '',\r\n ]\"\r\n :rowspan=\"row[col.key]?.rowspan || 1\"\r\n :colspan=\"row[col.key]?.colspan || 1\"\r\n :title=\"row[col.key]?.value\"\r\n >\r\n {{ formatCellValue(row[col.key]?.value) }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <div v-else class=\"excel-empty\">\r\n <NEmpty description=\"该工作表没有数据或解析失败\">\r\n <template #extra>\r\n <NButton size=\"small\" @click=\"$emit('reload')\">\r\n 重新解析\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n\r\n <!-- 分页 -->\r\n <div v-if=\"totalExcelPages > 1\" class=\"excel-pagination\">\r\n <NPagination\r\n v-model:page=\"currentExcelPage\"\r\n :page-count=\"totalExcelPages\"\r\n :page-size=\"pageSize\"\r\n show-size-picker\r\n :page-sizes=\"PAGE_SIZE_OPTIONS\"\r\n @update:page-size=\"handlePageSizeChange\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NButton, NTabs, NTabPane, NText, NEmpty, NPagination } from \"naive-ui\";\r\nimport {\r\n getColumnLetter,\r\n formatCellValue,\r\n getCellClass,\r\n PAGE_SIZE_OPTIONS,\r\n} from \"../../data\";\r\nimport type { ExcelSheet, ExcelRow, ExcelColumn } from \"../../types\";\r\n\r\nconst props = defineProps<{\r\n sheets: ExcelSheet[];\r\n}>();\r\n\r\ndefineEmits<{\r\n reload: [];\r\n}>();\r\n\r\n/* ==================== 内部状态 ==================== */\r\nconst activeSheet = ref(\"\");\r\nconst showExcelFormat = ref(false);\r\nconst currentExcelPage = ref(1);\r\nconst pageSize = ref(50);\r\n\r\n/* ==================== 当前工作表数据 ==================== */\r\nconst currentData = ref<ExcelRow[]>([]);\r\nconst currentColumns = ref<ExcelColumn[]>([]);\r\n\r\n/* ==================== 计算属性 ==================== */\r\nconst totalExcelPages = computed(() =>\r\n Math.ceil(currentData.value.length / pageSize.value),\r\n);\r\n\r\nconst paginatedData = computed(() => {\r\n const start = (currentExcelPage.value - 1) * pageSize.value;\r\n return currentData.value.slice(start, start + pageSize.value);\r\n});\r\n\r\n/* ==================== 方法 ==================== */\r\nconst handlePageSizeChange = (newPageSize: number) => {\r\n pageSize.value = newPageSize;\r\n currentExcelPage.value = 1;\r\n};\r\n\r\nconst syncSheetData = (sheetName: string) => {\r\n const sheet = props.sheets.find((s) => s.name === sheetName);\r\n if (sheet) {\r\n currentData.value = sheet.data;\r\n currentColumns.value = sheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n};\r\n\r\n/* ==================== 监听器 ==================== */\r\nwatch(activeSheet, (newSheet) => {\r\n syncSheetData(newSheet);\r\n});\r\n\r\n/* sheets 变化时初始化 */\r\nwatch(\r\n () => props.sheets,\r\n (newSheets) => {\r\n if (newSheets.length > 0) {\r\n const [firstSheet] = newSheets;\r\n activeSheet.value = firstSheet.name;\r\n currentData.value = firstSheet.data;\r\n currentColumns.value = firstSheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n ExcelViewer — Excel 表格预览子组件\r\n 负责: 工作表切换 + 表格渲染(合并单元格) + 分页 + 格式切换\r\n-->\r\n<template>\r\n <div class=\"file-container excel-container\">\r\n <!-- 工具栏 -->\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NTabs\r\n v-if=\"sheets.length > 1\"\r\n v-model:value=\"activeSheet\"\r\n type=\"card\"\r\n size=\"small\"\r\n >\r\n <NTabPane\r\n v-for=\"sheet in sheets\"\r\n :key=\"sheet.name\"\r\n :name=\"sheet.name\"\r\n :tab=\"sheet.name\"\r\n />\r\n </NTabs>\r\n <span v-else class=\"text-sm text-gray-600\">\r\n 工作表: {{ activeSheet }}\r\n </span>\r\n </div>\r\n\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showExcelFormat = !showExcelFormat\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-format-paint\" />\r\n </template>\r\n {{ showExcelFormat ? \"紧凑视图\" : \"完整格式\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"excel-viewer\">\r\n <!-- 信息面板 -->\r\n <div v-if=\"currentData.length > 0\" class=\"excel-info\">\r\n <NText depth=\"3\" class=\"text-sm\">\r\n 当前工作表: {{ activeSheet }} | 总行数: {{ currentData.length }} |\r\n 总列数: {{ currentColumns.length }} | 当前页:\r\n {{ currentExcelPage }}/{{ totalExcelPages }}\r\n </NText>\r\n </div>\r\n\r\n <!-- 表格 -->\r\n <div class=\"excel-table-container\">\r\n <div\r\n class=\"excel-table-wrapper\"\r\n :class=\"{ 'simple-mode': !showExcelFormat }\"\r\n >\r\n <table v-if=\"currentData.length > 0\" class=\"excel-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"row-number\">行号</th>\r\n <th\r\n v-for=\"(col, index) in currentColumns\"\r\n :key=\"col.key\"\r\n class=\"excel-header\"\r\n :style=\"{ minWidth: col.width + 'px' }\"\r\n >\r\n <div class=\"header-content\">\r\n <span class=\"column-letter\">{{\r\n getColumnLetter(index)\r\n }}</span>\r\n <span class=\"column-title\">{{ col.title }}</span>\r\n </div>\r\n </th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr\r\n v-for=\"(row, rowIndex) in paginatedData\"\r\n :key=\"rowIndex\"\r\n class=\"excel-row\"\r\n >\r\n <td class=\"row-number\">\r\n {{ (currentExcelPage - 1) * pageSize + rowIndex + 1 }}\r\n </td>\r\n <td\r\n v-for=\"col in currentColumns\"\r\n :key=\"col.key\"\r\n v-show=\"!row[col.key]?.hidden\"\r\n class=\"excel-cell\"\r\n :class=\"[\r\n getCellClass(row[col.key]?.value),\r\n row[col.key]?.merged ? 'merged-cell' : '',\r\n !showExcelFormat ? 'compact-cell' : '',\r\n ]\"\r\n :rowspan=\"row[col.key]?.rowspan || 1\"\r\n :colspan=\"row[col.key]?.colspan || 1\"\r\n :title=\"row[col.key]?.value\"\r\n >\r\n {{ formatCellValue(row[col.key]?.value) }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <div v-else class=\"excel-empty\">\r\n <NEmpty description=\"该工作表没有数据或解析失败\">\r\n <template #extra>\r\n <NButton size=\"small\" @click=\"$emit('reload')\">\r\n 重新解析\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n\r\n <!-- 分页 -->\r\n <div v-if=\"totalExcelPages > 1\" class=\"excel-pagination\">\r\n <NPagination\r\n v-model:page=\"currentExcelPage\"\r\n :page-count=\"totalExcelPages\"\r\n :page-size=\"pageSize\"\r\n show-size-picker\r\n :page-sizes=\"PAGE_SIZE_OPTIONS\"\r\n @update:page-size=\"handlePageSizeChange\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NButton, NTabs, NTabPane, NText, NEmpty, NPagination } from \"naive-ui\";\r\nimport {\r\n getColumnLetter,\r\n formatCellValue,\r\n getCellClass,\r\n PAGE_SIZE_OPTIONS,\r\n} from \"../../data\";\r\nimport type { ExcelSheet, ExcelRow, ExcelColumn } from \"../../types\";\r\n\r\nconst props = defineProps<{\r\n sheets: ExcelSheet[];\r\n}>();\r\n\r\ndefineEmits<{\r\n reload: [];\r\n}>();\r\n\r\n/* ==================== 内部状态 ==================== */\r\nconst activeSheet = ref(\"\");\r\nconst showExcelFormat = ref(false);\r\nconst currentExcelPage = ref(1);\r\nconst pageSize = ref(50);\r\n\r\n/* ==================== 当前工作表数据 ==================== */\r\nconst currentData = ref<ExcelRow[]>([]);\r\nconst currentColumns = ref<ExcelColumn[]>([]);\r\n\r\n/* ==================== 计算属性 ==================== */\r\nconst totalExcelPages = computed(() =>\r\n Math.ceil(currentData.value.length / pageSize.value),\r\n);\r\n\r\nconst paginatedData = computed(() => {\r\n const start = (currentExcelPage.value - 1) * pageSize.value;\r\n return currentData.value.slice(start, start + pageSize.value);\r\n});\r\n\r\n/* ==================== 方法 ==================== */\r\nconst handlePageSizeChange = (newPageSize: number) => {\r\n pageSize.value = newPageSize;\r\n currentExcelPage.value = 1;\r\n};\r\n\r\nconst syncSheetData = (sheetName: string) => {\r\n const sheet = props.sheets.find((s) => s.name === sheetName);\r\n if (sheet) {\r\n currentData.value = sheet.data;\r\n currentColumns.value = sheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n};\r\n\r\n/* ==================== 监听器 ==================== */\r\nwatch(activeSheet, (newSheet) => {\r\n syncSheetData(newSheet);\r\n});\r\n\r\n/* sheets 变化时初始化 */\r\nwatch(\r\n () => props.sheets,\r\n (newSheets) => {\r\n if (newSheets.length > 0) {\r\n const [firstSheet] = newSheets;\r\n activeSheet.value = firstSheet.name;\r\n currentData.value = firstSheet.data;\r\n currentColumns.value = firstSheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n ExcelViewer — Excel 表格预览子组件\r\n 负责: 工作表切换 + 表格渲染(合并单元格) + 分页 + 格式切换\r\n-->\r\n<template>\r\n <div class=\"file-container excel-container\">\r\n <!-- 工具栏 -->\r\n <div class=\"file-toolbar\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex gap-2 items-center\">\r\n <NTabs\r\n v-if=\"sheets.length > 1\"\r\n v-model:value=\"activeSheet\"\r\n type=\"card\"\r\n size=\"small\"\r\n >\r\n <NTabPane\r\n v-for=\"sheet in sheets\"\r\n :key=\"sheet.name\"\r\n :name=\"sheet.name\"\r\n :tab=\"sheet.name\"\r\n />\r\n </NTabs>\r\n <span v-else class=\"text-sm text-gray-600\">\r\n 工作表: {{ activeSheet }}\r\n </span>\r\n </div>\r\n\r\n <div class=\"flex gap-2 items-center\">\r\n <NButton size=\"small\" @click=\"showExcelFormat = !showExcelFormat\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-format-paint\" />\r\n </template>\r\n {{ showExcelFormat ? \"紧凑视图\" : \"完整格式\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"excel-viewer\">\r\n <!-- 信息面板 -->\r\n <div v-if=\"currentData.length > 0\" class=\"excel-info\">\r\n <NText depth=\"3\" class=\"text-sm\">\r\n 当前工作表: {{ activeSheet }} | 总行数: {{ currentData.length }} |\r\n 总列数: {{ currentColumns.length }} | 当前页:\r\n {{ currentExcelPage }}/{{ totalExcelPages }}\r\n </NText>\r\n </div>\r\n\r\n <!-- 表格 -->\r\n <div class=\"excel-table-container\">\r\n <div\r\n class=\"excel-table-wrapper\"\r\n :class=\"{ 'simple-mode': !showExcelFormat }\"\r\n >\r\n <table v-if=\"currentData.length > 0\" class=\"excel-table\">\r\n <thead>\r\n <tr>\r\n <th class=\"row-number\">行号</th>\r\n <th\r\n v-for=\"(col, index) in currentColumns\"\r\n :key=\"col.key\"\r\n class=\"excel-header\"\r\n :style=\"{ minWidth: col.width + 'px' }\"\r\n >\r\n <div class=\"header-content\">\r\n <span class=\"column-letter\">{{\r\n getColumnLetter(index)\r\n }}</span>\r\n <span class=\"column-title\">{{ col.title }}</span>\r\n </div>\r\n </th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n <tr\r\n v-for=\"(row, rowIndex) in paginatedData\"\r\n :key=\"rowIndex\"\r\n class=\"excel-row\"\r\n >\r\n <td class=\"row-number\">\r\n {{ (currentExcelPage - 1) * pageSize + rowIndex + 1 }}\r\n </td>\r\n <td\r\n v-for=\"col in currentColumns\"\r\n :key=\"col.key\"\r\n v-show=\"!row[col.key]?.hidden\"\r\n class=\"excel-cell\"\r\n :class=\"[\r\n getCellClass(row[col.key]?.value),\r\n row[col.key]?.merged ? 'merged-cell' : '',\r\n !showExcelFormat ? 'compact-cell' : '',\r\n ]\"\r\n :rowspan=\"row[col.key]?.rowspan || 1\"\r\n :colspan=\"row[col.key]?.colspan || 1\"\r\n :title=\"row[col.key]?.value\"\r\n >\r\n {{ formatCellValue(row[col.key]?.value) }}\r\n </td>\r\n </tr>\r\n </tbody>\r\n </table>\r\n\r\n <div v-else class=\"excel-empty\">\r\n <NEmpty description=\"该工作表没有数据或解析失败\">\r\n <template #extra>\r\n <NButton size=\"small\" @click=\"$emit('reload')\">\r\n 重新解析\r\n </NButton>\r\n </template>\r\n </NEmpty>\r\n </div>\r\n </div>\r\n\r\n <!-- 分页 -->\r\n <div v-if=\"totalExcelPages > 1\" class=\"excel-pagination\">\r\n <NPagination\r\n v-model:page=\"currentExcelPage\"\r\n :page-count=\"totalExcelPages\"\r\n :page-size=\"pageSize\"\r\n show-size-picker\r\n :page-sizes=\"PAGE_SIZE_OPTIONS\"\r\n @update:page-size=\"handlePageSizeChange\"\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, computed, watch } from \"vue\";\r\nimport { NButton, NTabs, NTabPane, NText, NEmpty, NPagination } from \"naive-ui\";\r\nimport {\r\n getColumnLetter,\r\n formatCellValue,\r\n getCellClass,\r\n PAGE_SIZE_OPTIONS,\r\n} from \"../../data\";\r\nimport type { ExcelSheet, ExcelRow, ExcelColumn } from \"../../types\";\r\n\r\nconst props = defineProps<{\r\n sheets: ExcelSheet[];\r\n}>();\r\n\r\ndefineEmits<{\r\n reload: [];\r\n}>();\r\n\r\n/* ==================== 内部状态 ==================== */\r\nconst activeSheet = ref(\"\");\r\nconst showExcelFormat = ref(false);\r\nconst currentExcelPage = ref(1);\r\nconst pageSize = ref(50);\r\n\r\n/* ==================== 当前工作表数据 ==================== */\r\nconst currentData = ref<ExcelRow[]>([]);\r\nconst currentColumns = ref<ExcelColumn[]>([]);\r\n\r\n/* ==================== 计算属性 ==================== */\r\nconst totalExcelPages = computed(() =>\r\n Math.ceil(currentData.value.length / pageSize.value),\r\n);\r\n\r\nconst paginatedData = computed(() => {\r\n const start = (currentExcelPage.value - 1) * pageSize.value;\r\n return currentData.value.slice(start, start + pageSize.value);\r\n});\r\n\r\n/* ==================== 方法 ==================== */\r\nconst handlePageSizeChange = (newPageSize: number) => {\r\n pageSize.value = newPageSize;\r\n currentExcelPage.value = 1;\r\n};\r\n\r\nconst syncSheetData = (sheetName: string) => {\r\n const sheet = props.sheets.find((s) => s.name === sheetName);\r\n if (sheet) {\r\n currentData.value = sheet.data;\r\n currentColumns.value = sheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n};\r\n\r\n/* ==================== 监听器 ==================== */\r\nwatch(activeSheet, (newSheet) => {\r\n syncSheetData(newSheet);\r\n});\r\n\r\n/* sheets 变化时初始化 */\r\nwatch(\r\n () => props.sheets,\r\n (newSheets) => {\r\n if (newSheets.length > 0) {\r\n const [firstSheet] = newSheets;\r\n activeSheet.value = firstSheet.name;\r\n currentData.value = firstSheet.data;\r\n currentColumns.value = firstSheet.columns;\r\n currentExcelPage.value = 1;\r\n }\r\n },\r\n { immediate: true },\r\n);\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/**\r\n * useFilePreview — 文件预览核心 composable\r\n * 管理:文件状态、加载、类型检测、下载\r\n */\r\nimport { ref, computed, watch, onUnmounted, type Ref } from \"vue\";\r\nimport {\r\n extractFileNameFromUrl,\r\n getFileType,\r\n getFileConfig,\r\n loadPdf,\r\n loadWord,\r\n loadExcel,\r\n} from \"../data\";\r\nimport type { FilePreviewType, DocHeading, ExcelSheet } from \"../types\";\r\n\r\nexport interface UseFilePreviewOptions {\r\n file: Ref<File | undefined>;\r\n url: Ref<string | undefined>;\r\n fileName: Ref<string>;\r\n}\r\n\r\n/**\r\n * 文件预览核心 composable — 管理文件状态、加载、类型检测、下载\r\n * @param options - 文件源配置(file / url / fileName)\r\n * @param emit - 组件事件发射器\r\n */\r\nexport function useFilePreview(\r\n options: UseFilePreviewOptions,\r\n emit: {\r\n (e: \"preview\", file: File | string): void;\r\n (e: \"download\", file: File | string): void;\r\n },\r\n) {\r\n const { file, url, fileName } = options;\r\n\r\n /* ==================== 基础状态 ==================== */\r\n const loading = ref(false);\r\n const error = ref(\"\");\r\n const fileSize = ref(0);\r\n const showModal = ref(false);\r\n\r\n /* ==================== 解析后的数据 ==================== */\r\n const pdfUrl = ref(\"\");\r\n const pdfTotalPages = ref(0);\r\n\r\n const wordContent = ref(\"\");\r\n const wordHeadings = ref<DocHeading[]>([]);\r\n\r\n const excelSheets = ref<ExcelSheet[]>([]);\r\n\r\n /* ==================== 计算属性 ==================== */\r\n const displayFileName = computed(() => {\r\n if (fileName.value && fileName.value !== \"未知文件\") return fileName.value;\r\n if (file.value?.name) return file.value.name;\r\n if (url.value) return extractFileNameFromUrl(url.value);\r\n return \"未知文件\";\r\n });\r\n\r\n const fileType = computed<FilePreviewType>(() =>\r\n getFileType(displayFileName.value),\r\n );\r\n const fileConfig = computed(() => getFileConfig(fileType.value));\r\n\r\n /* ==================== 内部方法 ==================== */\r\n const clearState = () => {\r\n error.value = \"\";\r\n loading.value = false;\r\n pdfUrl.value = \"\";\r\n pdfTotalPages.value = 0;\r\n wordContent.value = \"\";\r\n wordHeadings.value = [];\r\n excelSheets.value = [];\r\n };\r\n\r\n const setError = (msg: string) => {\r\n error.value = msg;\r\n loading.value = false;\r\n };\r\n\r\n /* ==================== 文件解析 ==================== */\r\n const resolveFile = async (): Promise<File> => {\r\n if (file.value) {\r\n fileSize.value = file.value.size;\r\n return file.value;\r\n }\r\n if (url.value) {\r\n const response = await fetch(url.value);\r\n if (!response.ok)\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\r\n\r\n const blob = await response.blob();\r\n const resolved = new File([blob], displayFileName.value, {\r\n type: blob.type,\r\n });\r\n fileSize.value = resolved.size;\r\n return resolved;\r\n }\r\n throw new Error(\"未提供文件或URL\");\r\n };\r\n\r\n const loadByType = async (\r\n currentFile: File,\r\n type: FilePreviewType,\r\n ): Promise<void> => {\r\n switch (type) {\r\n case \"pdf\": {\r\n const result = await loadPdf(currentFile);\r\n pdfUrl.value = result.url;\r\n pdfTotalPages.value = result.totalPages;\r\n break;\r\n }\r\n case \"word\": {\r\n const result = await loadWord(currentFile);\r\n wordContent.value = result.content;\r\n wordHeadings.value = result.headings;\r\n break;\r\n }\r\n case \"excel\": {\r\n const result = await loadExcel(currentFile);\r\n excelSheets.value = result.sheets;\r\n break;\r\n }\r\n default:\r\n throw new Error(\"不支持的文件格式\");\r\n }\r\n };\r\n\r\n /* ==================== 核心方法 ==================== */\r\n const loadFile = async () => {\r\n if (!file.value && !url.value) {\r\n setError(\"未提供文件或URL\");\r\n return;\r\n }\r\n\r\n clearState();\r\n loading.value = true;\r\n\r\n try {\r\n const currentFile = await resolveFile();\r\n await loadByType(currentFile, fileType.value);\r\n loading.value = false;\r\n } catch (err) {\r\n const errorMessage = err instanceof Error ? err.message : \"未知错误\";\r\n setError(`${fileType.value.toUpperCase()}文件加载失败: ${errorMessage}`);\r\n }\r\n };\r\n\r\n const openPreview = async () => {\r\n showModal.value = true;\r\n await loadFile();\r\n emit(\"preview\", file.value || url.value!);\r\n };\r\n\r\n const downloadFile = () => {\r\n if (file.value) {\r\n const downloadUrl = URL.createObjectURL(file.value);\r\n const a = Object.assign(document.createElement(\"a\"), {\r\n href: downloadUrl,\r\n download: displayFileName.value,\r\n });\r\n a.click();\r\n URL.revokeObjectURL(downloadUrl);\r\n } else if (url.value) {\r\n window.open(url.value, \"_blank\");\r\n }\r\n emit(\"download\", file.value || url.value!);\r\n };\r\n\r\n /* ==================== 副作用 ==================== */\r\n watch(\r\n () => file.value?.size,\r\n (newSize) => {\r\n fileSize.value = newSize || 0;\r\n },\r\n { immediate: true },\r\n );\r\n\r\n onUnmounted(() => {\r\n if (pdfUrl.value?.startsWith(\"blob:\")) {\r\n URL.revokeObjectURL(pdfUrl.value.split(\"#\")[0]);\r\n }\r\n });\r\n\r\n return {\r\n /* 状态 */\r\n loading,\r\n error,\r\n fileSize,\r\n showModal,\r\n /* 解析数据 */\r\n pdfUrl,\r\n pdfTotalPages,\r\n wordContent,\r\n wordHeadings,\r\n excelSheets,\r\n /* 计算属性 */\r\n displayFileName,\r\n fileType,\r\n fileConfig,\r\n /* 方法 */\r\n loadFile,\r\n openPreview,\r\n downloadFile,\r\n };\r\n}\r\n","/**\r\n * useFullscreen — 全屏切换 composable\r\n */\r\nimport { ref, onMounted, onUnmounted, type Ref } from \"vue\";\r\nimport { FULLSCREEN_EVENTS } from \"../data\";\r\n\r\n/**\r\n * 全屏切换 composable — 管理全屏状态与跨浏览器兼容\r\n * @param containerRef - 需要全屏的容器元素引用\r\n */\r\nexport function useFullscreen(containerRef: Ref<HTMLElement | undefined>) {\r\n const isFullscreen = ref(false);\r\n\r\n const handleFullscreenChange = () => {\r\n isFullscreen.value = !!document.fullscreenElement;\r\n };\r\n\r\n const tryMethods = (\r\n methods: Array<() => Promise<void>>,\r\n index = 0,\r\n ): Promise<void> => {\r\n if (index >= methods.length) {\r\n console.warn(\"无法切换全屏状态\");\r\n return Promise.resolve();\r\n }\r\n return methods[index]().catch(() => tryMethods(methods, index + 1));\r\n };\r\n\r\n const toggleFullscreen = async () => {\r\n const element = containerRef.value;\r\n if (!element) return;\r\n\r\n const isCurrentlyFullscreen = !!document.fullscreenElement;\r\n\r\n const methods = isCurrentlyFullscreen\r\n ? [\r\n () => document.exitFullscreen(),\r\n () => (document as any).webkitExitFullscreen(),\r\n () => (document as any).mozCancelFullScreen(),\r\n () => (document as any).msExitFullscreen(),\r\n ]\r\n : [\r\n () => element.requestFullscreen(),\r\n () => (element as any).webkitRequestFullscreen(),\r\n () => (element as any).mozRequestFullScreen(),\r\n () => (element as any).msRequestFullscreen(),\r\n ];\r\n\r\n await tryMethods(methods);\r\n };\r\n\r\n const exitFullscreen = async () => {\r\n if (document.fullscreenElement) {\r\n try {\r\n await document.exitFullscreen();\r\n } catch {\r\n /* ignore */\r\n }\r\n }\r\n isFullscreen.value = false;\r\n };\r\n\r\n onMounted(() => {\r\n FULLSCREEN_EVENTS.forEach((event) =>\r\n document.addEventListener(event, handleFullscreenChange),\r\n );\r\n });\r\n\r\n onUnmounted(() => {\r\n FULLSCREEN_EVENTS.forEach((event) =>\r\n document.removeEventListener(event, handleFullscreenChange),\r\n );\r\n });\r\n\r\n return { isFullscreen, toggleFullscreen, exitFullscreen };\r\n}\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-07-01\r\n * @Description: 文件预览组件(PDF/Word/Excel)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-file-preview-wrapper\">\r\n <!-- 文件信息卡片模式 -->\r\n <div v-if=\"!autoPreview\" class=\"file-info-card\">\r\n <div class=\"file-info\">\r\n <div class=\"file-icon\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"40\"\r\n :color=\"fileConfig.color\"\r\n />\r\n </div>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">\r\n <NEllipsis style=\"max-width: 250px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n </div>\r\n\r\n <div class=\"file-meta\">\r\n <span class=\"file-size\">{{ formatFileSize(fileSize) }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-actions\">\r\n <NButton type=\"primary\" @click=\"openPreview\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-visibility\" />\r\n </template>\r\n 预览\r\n </NButton>\r\n\r\n <NButton type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 模态框预览 -->\r\n <NModal\r\n v-model:show=\"showModal\"\r\n :mask-closable=\"false\"\r\n :closable=\"false\"\r\n :auto-focus=\"false\"\r\n transform-origin=\"center\"\r\n style=\"\r\n width: 85vw;\r\n max-width: 1200px;\r\n min-width: 800px;\r\n height: 75vh;\r\n max-height: 700px;\r\n min-height: 500px;\r\n \"\r\n >\r\n <div ref=\"modalContainer\" class=\"modal-container\">\r\n <!-- 自定义头部 -->\r\n <div class=\"modal-header\">\r\n <div class=\"modal-title\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"20\"\r\n :color=\"fileConfig.color\"\r\n />\r\n <span>{{ displayFileName }}</span>\r\n </div>\r\n <div class=\"modal-actions\">\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"showModal = false\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容 -->\r\n <div class=\"modal-content\">\r\n <!-- 文件信息头部 -->\r\n <div class=\"preview-header\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex items-center gap-3\">\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n <template #icon>\r\n <C_Icon :name=\"fileConfig.icon\" />\r\n </template>\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n <NEllipsis style=\"max-width: 300px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <span class=\"text-sm text-gray-500\">{{\r\n formatFileSize(fileSize)\r\n }}</span>\r\n </div>\r\n\r\n <div class=\"flex gap-2\">\r\n <NButton\r\n size=\"small\"\r\n type=\"tertiary\"\r\n :disabled=\"loading\"\r\n @click=\"loadFile\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-refresh\" />\r\n </template>\r\n 刷新\r\n </NButton>\r\n <NButton size=\"small\" type=\"primary\" @click=\"toggleFullscreen\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n isFullscreen\r\n ? 'ic:outline-fullscreen-exit'\r\n : 'ic:outline-fullscreen'\r\n \"\r\n />\r\n </template>\r\n {{ isFullscreen ? \"退出全屏\" : \"全屏\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容区域 -->\r\n <div class=\"preview-content\">\r\n <!-- 加载和错误状态 -->\r\n <template v-if=\"loading || error\">\r\n <div class=\"status-container\">\r\n <NSpin v-if=\"loading\" size=\"large\">\r\n <template #description>\r\n 正在加载{{ fileType.toUpperCase() }}文件...\r\n </template>\r\n </NSpin>\r\n <NResult\r\n v-else\r\n status=\"error\"\r\n title=\"预览失败\"\r\n :description=\"error\"\r\n >\r\n <template #footer>\r\n <NButton @click=\"loadFile\">重试</NButton>\r\n </template>\r\n </NResult>\r\n </div>\r\n </template>\r\n\r\n <!-- 文件预览 — 委托给子组件 -->\r\n <template v-else>\r\n <PdfViewer\r\n v-if=\"fileType === 'pdf'\"\r\n :pdf-url=\"pdfUrl\"\r\n :total-pages=\"pdfTotalPages\"\r\n />\r\n\r\n <WordViewer\r\n v-else-if=\"fileType === 'word'\"\r\n :content=\"wordContent\"\r\n :headings=\"wordHeadings\"\r\n />\r\n\r\n <ExcelViewer\r\n v-else-if=\"fileType === 'excel'\"\r\n :sheets=\"excelSheets\"\r\n @reload=\"loadFile\"\r\n />\r\n\r\n <!-- 不支持的文件类型 -->\r\n <NResult\r\n v-else\r\n status=\"warning\"\r\n title=\"不支持的文件格式\"\r\n :description=\"`暂不支持预览 ${fileType.toUpperCase()} 格式文件`\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </NModal>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRefs, watch } from \"vue\";\r\nimport { NButton, NEllipsis, NTag, NModal, NSpin, NResult } from \"naive-ui\";\r\nimport { formatFileSize } from \"./data\";\r\nimport PdfViewer from \"./components/PdfViewer/index.vue\";\r\nimport WordViewer from \"./components/WordViewer/index.vue\";\r\nimport ExcelViewer from \"./components/ExcelViewer/index.vue\";\r\nimport { useFilePreview } from \"./composables/useFilePreview\";\r\nimport { useFullscreen } from \"./composables/useFullscreen\";\r\n\r\ndefineOptions({ name: \"C_FilePreview\" });\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n file?: File;\r\n url?: string;\r\n fileName?: string;\r\n autoPreview?: boolean;\r\n }>(),\r\n {\r\n fileName: \"未知文件\",\r\n autoPreview: false,\r\n },\r\n);\r\n\r\nconst { file, url, fileName: propFileName, autoPreview } = toRefs(props);\r\n\r\nconst emit = defineEmits<{\r\n preview: [file: File | string];\r\n download: [file: File | string];\r\n}>();\r\n\r\n/* ==================== Composables ==================== */\r\nconst {\r\n loading,\r\n error,\r\n fileSize,\r\n showModal,\r\n pdfUrl,\r\n pdfTotalPages,\r\n wordContent,\r\n wordHeadings,\r\n excelSheets,\r\n displayFileName,\r\n fileType,\r\n fileConfig,\r\n loadFile,\r\n openPreview,\r\n downloadFile,\r\n} = useFilePreview({ file, url, fileName: propFileName }, emit);\r\n\r\nconst modalContainer = ref<HTMLElement>();\r\nconst { isFullscreen, toggleFullscreen, exitFullscreen } =\r\n useFullscreen(modalContainer);\r\n\r\n/* ==================== Modal 关闭时退出全屏 ==================== */\r\nwatch(showModal, (isShow) => {\r\n if (!isShow) exitFullscreen();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","/* unplugin-vue-components disabled */<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-07-01\r\n * @Description: 文件预览组件(PDF/Word/Excel)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-file-preview-wrapper\">\r\n <!-- 文件信息卡片模式 -->\r\n <div v-if=\"!autoPreview\" class=\"file-info-card\">\r\n <div class=\"file-info\">\r\n <div class=\"file-icon\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"40\"\r\n :color=\"fileConfig.color\"\r\n />\r\n </div>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">\r\n <NEllipsis style=\"max-width: 250px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n </div>\r\n\r\n <div class=\"file-meta\">\r\n <span class=\"file-size\">{{ formatFileSize(fileSize) }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-actions\">\r\n <NButton type=\"primary\" @click=\"openPreview\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-visibility\" />\r\n </template>\r\n 预览\r\n </NButton>\r\n\r\n <NButton type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 模态框预览 -->\r\n <NModal\r\n v-model:show=\"showModal\"\r\n :mask-closable=\"false\"\r\n :closable=\"false\"\r\n :auto-focus=\"false\"\r\n transform-origin=\"center\"\r\n style=\"\r\n width: 85vw;\r\n max-width: 1200px;\r\n min-width: 800px;\r\n height: 75vh;\r\n max-height: 700px;\r\n min-height: 500px;\r\n \"\r\n >\r\n <div ref=\"modalContainer\" class=\"modal-container\">\r\n <!-- 自定义头部 -->\r\n <div class=\"modal-header\">\r\n <div class=\"modal-title\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"20\"\r\n :color=\"fileConfig.color\"\r\n />\r\n <span>{{ displayFileName }}</span>\r\n </div>\r\n <div class=\"modal-actions\">\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"showModal = false\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容 -->\r\n <div class=\"modal-content\">\r\n <!-- 文件信息头部 -->\r\n <div class=\"preview-header\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex items-center gap-3\">\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n <template #icon>\r\n <C_Icon :name=\"fileConfig.icon\" />\r\n </template>\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n <NEllipsis style=\"max-width: 300px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <span class=\"text-sm text-gray-500\">{{\r\n formatFileSize(fileSize)\r\n }}</span>\r\n </div>\r\n\r\n <div class=\"flex gap-2\">\r\n <NButton\r\n size=\"small\"\r\n type=\"tertiary\"\r\n :disabled=\"loading\"\r\n @click=\"loadFile\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-refresh\" />\r\n </template>\r\n 刷新\r\n </NButton>\r\n <NButton size=\"small\" type=\"primary\" @click=\"toggleFullscreen\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n isFullscreen\r\n ? 'ic:outline-fullscreen-exit'\r\n : 'ic:outline-fullscreen'\r\n \"\r\n />\r\n </template>\r\n {{ isFullscreen ? \"退出全屏\" : \"全屏\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容区域 -->\r\n <div class=\"preview-content\">\r\n <!-- 加载和错误状态 -->\r\n <template v-if=\"loading || error\">\r\n <div class=\"status-container\">\r\n <NSpin v-if=\"loading\" size=\"large\">\r\n <template #description>\r\n 正在加载{{ fileType.toUpperCase() }}文件...\r\n </template>\r\n </NSpin>\r\n <NResult\r\n v-else\r\n status=\"error\"\r\n title=\"预览失败\"\r\n :description=\"error\"\r\n >\r\n <template #footer>\r\n <NButton @click=\"loadFile\">重试</NButton>\r\n </template>\r\n </NResult>\r\n </div>\r\n </template>\r\n\r\n <!-- 文件预览 — 委托给子组件 -->\r\n <template v-else>\r\n <PdfViewer\r\n v-if=\"fileType === 'pdf'\"\r\n :pdf-url=\"pdfUrl\"\r\n :total-pages=\"pdfTotalPages\"\r\n />\r\n\r\n <WordViewer\r\n v-else-if=\"fileType === 'word'\"\r\n :content=\"wordContent\"\r\n :headings=\"wordHeadings\"\r\n />\r\n\r\n <ExcelViewer\r\n v-else-if=\"fileType === 'excel'\"\r\n :sheets=\"excelSheets\"\r\n @reload=\"loadFile\"\r\n />\r\n\r\n <!-- 不支持的文件类型 -->\r\n <NResult\r\n v-else\r\n status=\"warning\"\r\n title=\"不支持的文件格式\"\r\n :description=\"`暂不支持预览 ${fileType.toUpperCase()} 格式文件`\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </NModal>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRefs, watch } from \"vue\";\r\nimport { NButton, NEllipsis, NTag, NModal, NSpin, NResult } from \"naive-ui\";\r\nimport { formatFileSize } from \"./data\";\r\nimport PdfViewer from \"./components/PdfViewer/index.vue\";\r\nimport WordViewer from \"./components/WordViewer/index.vue\";\r\nimport ExcelViewer from \"./components/ExcelViewer/index.vue\";\r\nimport { useFilePreview } from \"./composables/useFilePreview\";\r\nimport { useFullscreen } from \"./composables/useFullscreen\";\r\n\r\ndefineOptions({ name: \"C_FilePreview\" });\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n file?: File;\r\n url?: string;\r\n fileName?: string;\r\n autoPreview?: boolean;\r\n }>(),\r\n {\r\n fileName: \"未知文件\",\r\n autoPreview: false,\r\n },\r\n);\r\n\r\nconst { file, url, fileName: propFileName, autoPreview } = toRefs(props);\r\n\r\nconst emit = defineEmits<{\r\n preview: [file: File | string];\r\n download: [file: File | string];\r\n}>();\r\n\r\n/* ==================== Composables ==================== */\r\nconst {\r\n loading,\r\n error,\r\n fileSize,\r\n showModal,\r\n pdfUrl,\r\n pdfTotalPages,\r\n wordContent,\r\n wordHeadings,\r\n excelSheets,\r\n displayFileName,\r\n fileType,\r\n fileConfig,\r\n loadFile,\r\n openPreview,\r\n downloadFile,\r\n} = useFilePreview({ file, url, fileName: propFileName }, emit);\r\n\r\nconst modalContainer = ref<HTMLElement>();\r\nconst { isFullscreen, toggleFullscreen, exitFullscreen } =\r\n useFullscreen(modalContainer);\r\n\r\n/* ==================== Modal 关闭时退出全屏 ==================== */\r\nwatch(showModal, (isShow) => {\r\n if (!isShow) exitFullscreen();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n","<!--\r\n * @Author: ChenYu ycyplus@gmail.com\r\n * @Date: 2025-07-01\r\n * @Description: 文件预览组件(PDF/Word/Excel)\r\n * @Migration: naive-ui-components 组件库迁移版本\r\n * Copyright (c) 2025 by CHENY, All Rights Reserved.\r\n-->\r\n\r\n<template>\r\n <div class=\"c-file-preview-wrapper\">\r\n <!-- 文件信息卡片模式 -->\r\n <div v-if=\"!autoPreview\" class=\"file-info-card\">\r\n <div class=\"file-info\">\r\n <div class=\"file-icon\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"40\"\r\n :color=\"fileConfig.color\"\r\n />\r\n </div>\r\n\r\n <div class=\"file-details\">\r\n <div class=\"file-name\">\r\n <NEllipsis style=\"max-width: 250px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n </div>\r\n\r\n <div class=\"file-meta\">\r\n <span class=\"file-size\">{{ formatFileSize(fileSize) }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"file-actions\">\r\n <NButton type=\"primary\" @click=\"openPreview\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-visibility\" />\r\n </template>\r\n 预览\r\n </NButton>\r\n\r\n <NButton type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 模态框预览 -->\r\n <NModal\r\n v-model:show=\"showModal\"\r\n :mask-closable=\"false\"\r\n :closable=\"false\"\r\n :auto-focus=\"false\"\r\n transform-origin=\"center\"\r\n style=\"\r\n width: 85vw;\r\n max-width: 1200px;\r\n min-width: 800px;\r\n height: 75vh;\r\n max-height: 700px;\r\n min-height: 500px;\r\n \"\r\n >\r\n <div ref=\"modalContainer\" class=\"modal-container\">\r\n <!-- 自定义头部 -->\r\n <div class=\"modal-header\">\r\n <div class=\"modal-title\">\r\n <C_Icon\r\n :name=\"fileConfig.icon\"\r\n :size=\"20\"\r\n :color=\"fileConfig.color\"\r\n />\r\n <span>{{ displayFileName }}</span>\r\n </div>\r\n <div class=\"modal-actions\">\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"downloadFile\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-download\" />\r\n </template>\r\n 下载\r\n </NButton>\r\n <NButton size=\"small\" type=\"tertiary\" @click=\"showModal = false\">\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-close\" />\r\n </template>\r\n </NButton>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容 -->\r\n <div class=\"modal-content\">\r\n <!-- 文件信息头部 -->\r\n <div class=\"preview-header\">\r\n <div class=\"flex justify-between items-center\">\r\n <div class=\"flex items-center gap-3\">\r\n <NTag :type=\"fileConfig.tagType\" size=\"small\">\r\n <template #icon>\r\n <C_Icon :name=\"fileConfig.icon\" />\r\n </template>\r\n {{ fileType.toUpperCase() }}\r\n </NTag>\r\n <NEllipsis style=\"max-width: 300px\">{{\r\n displayFileName\r\n }}</NEllipsis>\r\n <span class=\"text-sm text-gray-500\">{{\r\n formatFileSize(fileSize)\r\n }}</span>\r\n </div>\r\n\r\n <div class=\"flex gap-2\">\r\n <NButton\r\n size=\"small\"\r\n type=\"tertiary\"\r\n :disabled=\"loading\"\r\n @click=\"loadFile\"\r\n >\r\n <template #icon>\r\n <C_Icon name=\"ic:outline-refresh\" />\r\n </template>\r\n 刷新\r\n </NButton>\r\n <NButton size=\"small\" type=\"primary\" @click=\"toggleFullscreen\">\r\n <template #icon>\r\n <C_Icon\r\n :name=\"\r\n isFullscreen\r\n ? 'ic:outline-fullscreen-exit'\r\n : 'ic:outline-fullscreen'\r\n \"\r\n />\r\n </template>\r\n {{ isFullscreen ? \"退出全屏\" : \"全屏\" }}\r\n </NButton>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- 预览内容区域 -->\r\n <div class=\"preview-content\">\r\n <!-- 加载和错误状态 -->\r\n <template v-if=\"loading || error\">\r\n <div class=\"status-container\">\r\n <NSpin v-if=\"loading\" size=\"large\">\r\n <template #description>\r\n 正在加载{{ fileType.toUpperCase() }}文件...\r\n </template>\r\n </NSpin>\r\n <NResult\r\n v-else\r\n status=\"error\"\r\n title=\"预览失败\"\r\n :description=\"error\"\r\n >\r\n <template #footer>\r\n <NButton @click=\"loadFile\">重试</NButton>\r\n </template>\r\n </NResult>\r\n </div>\r\n </template>\r\n\r\n <!-- 文件预览 — 委托给子组件 -->\r\n <template v-else>\r\n <PdfViewer\r\n v-if=\"fileType === 'pdf'\"\r\n :pdf-url=\"pdfUrl\"\r\n :total-pages=\"pdfTotalPages\"\r\n />\r\n\r\n <WordViewer\r\n v-else-if=\"fileType === 'word'\"\r\n :content=\"wordContent\"\r\n :headings=\"wordHeadings\"\r\n />\r\n\r\n <ExcelViewer\r\n v-else-if=\"fileType === 'excel'\"\r\n :sheets=\"excelSheets\"\r\n @reload=\"loadFile\"\r\n />\r\n\r\n <!-- 不支持的文件类型 -->\r\n <NResult\r\n v-else\r\n status=\"warning\"\r\n title=\"不支持的文件格式\"\r\n :description=\"`暂不支持预览 ${fileType.toUpperCase()} 格式文件`\"\r\n />\r\n </template>\r\n </div>\r\n </div>\r\n </div>\r\n </NModal>\r\n </div>\r\n</template>\r\n\r\n<script setup lang=\"ts\">\r\nimport { ref, toRefs, watch } from \"vue\";\r\nimport { NButton, NEllipsis, NTag, NModal, NSpin, NResult } from \"naive-ui\";\r\nimport { formatFileSize } from \"./data\";\r\nimport PdfViewer from \"./components/PdfViewer/index.vue\";\r\nimport WordViewer from \"./components/WordViewer/index.vue\";\r\nimport ExcelViewer from \"./components/ExcelViewer/index.vue\";\r\nimport { useFilePreview } from \"./composables/useFilePreview\";\r\nimport { useFullscreen } from \"./composables/useFullscreen\";\r\n\r\ndefineOptions({ name: \"C_FilePreview\" });\r\n\r\nconst props = withDefaults(\r\n defineProps<{\r\n file?: File;\r\n url?: string;\r\n fileName?: string;\r\n autoPreview?: boolean;\r\n }>(),\r\n {\r\n fileName: \"未知文件\",\r\n autoPreview: false,\r\n },\r\n);\r\n\r\nconst { file, url, fileName: propFileName, autoPreview } = toRefs(props);\r\n\r\nconst emit = defineEmits<{\r\n preview: [file: File | string];\r\n download: [file: File | string];\r\n}>();\r\n\r\n/* ==================== Composables ==================== */\r\nconst {\r\n loading,\r\n error,\r\n fileSize,\r\n showModal,\r\n pdfUrl,\r\n pdfTotalPages,\r\n wordContent,\r\n wordHeadings,\r\n excelSheets,\r\n displayFileName,\r\n fileType,\r\n fileConfig,\r\n loadFile,\r\n openPreview,\r\n downloadFile,\r\n} = useFilePreview({ file, url, fileName: propFileName }, emit);\r\n\r\nconst modalContainer = ref<HTMLElement>();\r\nconst { isFullscreen, toggleFullscreen, exitFullscreen } =\r\n useFullscreen(modalContainer);\r\n\r\n/* ==================== Modal 关闭时退出全屏 ==================== */\r\nwatch(showModal, (isShow) => {\r\n if (!isShow) exitFullscreen();\r\n});\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@use \"./index.scss\";\r\n</style>\r\n"],"mappings":"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"}