@veltra/desktop 1.0.7 → 1.0.9

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 (247) hide show
  1. package/dist/components/action/action-group.vue.d.ts +1 -1
  2. package/dist/components/action/action.vue.d.ts +3 -3
  3. package/dist/components/action/style.js +3 -0
  4. package/dist/components/auto-complete/auto-complete.vue.d.ts +2 -2
  5. package/dist/components/auto-complete/style.js +5 -0
  6. package/dist/components/batch-edit/style.js +9 -0
  7. package/dist/components/breadcrumb/style2.css +0 -6
  8. package/dist/components/button/style.js +1 -0
  9. package/dist/components/cascade/cascade.js +15 -13
  10. package/dist/components/cascade/cascade.js.map +1 -1
  11. package/dist/components/cascade/cascade.vue.d.ts +3 -3
  12. package/dist/components/cascade/node.js.map +1 -1
  13. package/dist/components/cascade/style.js +7 -0
  14. package/dist/components/cascade/use-check.js +3 -2
  15. package/dist/components/cascade/use-check.js.map +1 -1
  16. package/dist/components/cascade/use-select.js +3 -3
  17. package/dist/components/cascade/use-select.js.map +1 -1
  18. package/dist/components/checkbox/style.js +1 -0
  19. package/dist/components/checkbox/style2.css +0 -8
  20. package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
  21. package/dist/components/checkbox-group/style.js +1 -0
  22. package/dist/components/context-menu/style.js +1 -0
  23. package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
  24. package/dist/components/date-picker/date-picker.js +9 -9
  25. package/dist/components/date-picker/date-picker.js.map +1 -1
  26. package/dist/components/date-picker/date-picker.vue.d.ts +2 -2
  27. package/dist/components/date-picker/style.js +3 -0
  28. package/dist/components/date-range-picker/date-range-picker.js +9 -9
  29. package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
  30. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +2 -2
  31. package/dist/components/date-range-picker/style.js +3 -0
  32. package/dist/components/dialog/style.js +2 -0
  33. package/dist/components/drawer/style.js +1 -0
  34. package/dist/components/drawer/style2.css +4 -16
  35. package/dist/components/empty/style.js +1 -0
  36. package/dist/components/expression-editor/style.js +6 -0
  37. package/dist/components/file-picker/file-picker.vue.d.ts +1 -1
  38. package/dist/components/file-picker/helper.js +19 -7
  39. package/dist/components/file-picker/helper.js.map +1 -1
  40. package/dist/components/file-viewer/file-viewer.js +294 -0
  41. package/dist/components/file-viewer/file-viewer.js.map +1 -0
  42. package/dist/components/file-viewer/file-viewer.vue.d.ts +42 -0
  43. package/dist/components/file-viewer/helper.js +185 -0
  44. package/dist/components/file-viewer/helper.js.map +1 -0
  45. package/dist/components/file-viewer/index.d.ts +1 -0
  46. package/dist/components/file-viewer/previewers/docx-previewer.js +76 -0
  47. package/dist/components/file-viewer/previewers/docx-previewer.js.map +1 -0
  48. package/dist/components/file-viewer/previewers/image-previewer.js +64 -0
  49. package/dist/components/file-viewer/previewers/image-previewer.js.map +1 -0
  50. package/dist/components/file-viewer/previewers/pdf-previewer.js +90 -0
  51. package/dist/components/file-viewer/previewers/pdf-previewer.js.map +1 -0
  52. package/dist/components/file-viewer/previewers/sheet-previewer.js +175 -0
  53. package/dist/components/file-viewer/previewers/sheet-previewer.js.map +1 -0
  54. package/dist/components/file-viewer/previewers/text-previewer.js +74 -0
  55. package/dist/components/file-viewer/previewers/text-previewer.js.map +1 -0
  56. package/dist/components/file-viewer/previewers/video-previewer.js +62 -0
  57. package/dist/components/file-viewer/previewers/video-previewer.js.map +1 -0
  58. package/dist/components/file-viewer/style.d.ts +1 -0
  59. package/dist/components/file-viewer/style.js +4 -0
  60. package/dist/components/file-viewer/style2.css +445 -0
  61. package/dist/components/float-button/style.js +1 -0
  62. package/dist/components/form/style.js +2 -0
  63. package/dist/components/form-item/style.js +1 -0
  64. package/dist/components/form-item/style2.css +2 -3
  65. package/dist/components/grid-input/grid-input.vue.d.ts +1 -1
  66. package/dist/components/index.d.ts +12 -9
  67. package/dist/components/input/input.vue.d.ts +2 -2
  68. package/dist/components/input/style.js +1 -0
  69. package/dist/components/layout/style.js +1 -0
  70. package/dist/components/list/style.js +1 -0
  71. package/dist/components/loading/style2.css +16 -20
  72. package/dist/components/menu/style.js +2 -0
  73. package/dist/components/menu/style2.css +0 -78
  74. package/dist/components/multi-select/multi-select-option.js +14 -10
  75. package/dist/components/multi-select/multi-select-option.js.map +1 -1
  76. package/dist/components/multi-select/multi-select.js +16 -19
  77. package/dist/components/multi-select/multi-select.js.map +1 -1
  78. package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
  79. package/dist/components/multi-select/style.js +7 -0
  80. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +4 -4
  81. package/dist/components/multi-tree-select/style.js +7 -0
  82. package/dist/components/notification/notification.vue.d.ts +1 -1
  83. package/dist/components/number-input/number-input.vue.d.ts +2 -2
  84. package/dist/components/number-input/style.js +1 -0
  85. package/dist/components/number-range-input/number-range-input.vue.d.ts +2 -2
  86. package/dist/components/number-range-input/style.js +1 -0
  87. package/dist/components/paginator/style.js +3 -0
  88. package/dist/components/palette/di.js.map +1 -1
  89. package/dist/components/palette/palette-alpha.js +7 -10
  90. package/dist/components/palette/palette-alpha.js.map +1 -1
  91. package/dist/components/palette/palette-hue.js +5 -7
  92. package/dist/components/palette/palette-hue.js.map +1 -1
  93. package/dist/components/palette/palette-sv.js +8 -10
  94. package/dist/components/palette/palette-sv.js.map +1 -1
  95. package/dist/components/palette/palette.js +22 -24
  96. package/dist/components/palette/palette.js.map +1 -1
  97. package/dist/components/palette/style.js +3 -0
  98. package/dist/components/password-input/password-input.vue.d.ts +1 -1
  99. package/dist/components/password-input/style.js +2 -0
  100. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  101. package/dist/components/pop-confirm/style.js +3 -0
  102. package/dist/components/progress-nodes/progress-nodes.vue.d.ts +2 -2
  103. package/dist/components/progress-nodes/style2.css +79 -33
  104. package/dist/components/radio/style2.css +0 -8
  105. package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
  106. package/dist/components/radio-group/style.js +1 -0
  107. package/dist/components/rich-text-editor/rich-text-editor.vue.d.ts +2 -2
  108. package/dist/components/select/select.js +14 -12
  109. package/dist/components/select/select.js.map +1 -1
  110. package/dist/components/select/select.vue.d.ts +2 -2
  111. package/dist/components/select/style.js +5 -0
  112. package/dist/components/select/style2.css +0 -20
  113. package/dist/components/slider/slider-thumb.js +10 -13
  114. package/dist/components/slider/slider-thumb.js.map +1 -1
  115. package/dist/components/slider/slider.js +12 -15
  116. package/dist/components/slider/slider.js.map +1 -1
  117. package/dist/components/steps/style.js +2 -0
  118. package/dist/components/steps/style2.css +28 -35
  119. package/dist/components/switch/style2.css +0 -3
  120. package/dist/components/table/di.js.map +1 -1
  121. package/dist/components/table/node/row.d.ts +10 -1
  122. package/dist/components/table/node/row.js +9 -0
  123. package/dist/components/table/node/row.js.map +1 -1
  124. package/dist/components/table/style.js +7 -0
  125. package/dist/components/table/style2.css +0 -41
  126. package/dist/components/table/table-body.js +25 -26
  127. package/dist/components/table/table-body.js.map +1 -1
  128. package/dist/components/table/table-cell.js +16 -9
  129. package/dist/components/table/table-cell.js.map +1 -1
  130. package/dist/components/table/table-row.js +78 -19
  131. package/dist/components/table/table-row.js.map +1 -1
  132. package/dist/components/table/table.js +76 -34
  133. package/dist/components/table/table.js.map +1 -1
  134. package/dist/components/table/use-fixed-columns.js +27 -8
  135. package/dist/components/table/use-fixed-columns.js.map +1 -1
  136. package/dist/components/table/use-rows.js +2 -2
  137. package/dist/components/table/use-rows.js.map +1 -1
  138. package/dist/components/table/use-table.js +25 -3
  139. package/dist/components/table/use-table.js.map +1 -1
  140. package/dist/components/table-editor/style.js +2 -0
  141. package/dist/components/tabs/index.d.ts +3 -1
  142. package/dist/components/tabs/style.js +2 -0
  143. package/dist/components/tabs/style2.css +169 -107
  144. package/dist/components/tabs/tabs-horizontal.js +133 -0
  145. package/dist/components/tabs/tabs-horizontal.js.map +1 -0
  146. package/dist/components/tabs/tabs-horizontal.vue.d.ts +29 -0
  147. package/dist/components/tabs/tabs-vertical.js +89 -0
  148. package/dist/components/tabs/tabs-vertical.js.map +1 -0
  149. package/dist/components/tabs/tabs-vertical.vue.d.ts +28 -0
  150. package/dist/components/tabs/tabs.js +75 -105
  151. package/dist/components/tabs/tabs.js.map +1 -1
  152. package/dist/components/tabs/tabs.vue.d.ts +9 -23
  153. package/dist/components/tabs/use-tabs-bar.js +105 -0
  154. package/dist/components/tabs/use-tabs-bar.js.map +1 -0
  155. package/dist/components/tag/style2.css +0 -6
  156. package/dist/components/textarea/style.js +1 -0
  157. package/dist/components/textarea/textarea.vue.d.ts +2 -2
  158. package/dist/components/theme/style.js +5 -0
  159. package/dist/components/tip/tip.vue.d.ts +2 -2
  160. package/dist/components/tree/style.js +4 -0
  161. package/dist/components/tree/style2.css +45 -23
  162. package/dist/components/tree/tree-node.js +50 -35
  163. package/dist/components/tree/tree-node.js.map +1 -1
  164. package/dist/components/tree/tree.js +27 -20
  165. package/dist/components/tree/tree.js.map +1 -1
  166. package/dist/components/tree/tree.vue.d.ts +1 -1
  167. package/dist/components/tree/use-check.js +52 -14
  168. package/dist/components/tree/use-check.js.map +1 -1
  169. package/dist/components/tree/use-tree-nodes.js +51 -24
  170. package/dist/components/tree/use-tree-nodes.js.map +1 -1
  171. package/dist/components/tree-select/style.js +4 -0
  172. package/dist/components/tree-select/tree-select.vue.d.ts +3 -3
  173. package/dist/components/watermark/watermark.vue.d.ts +1 -1
  174. package/dist/index.d.ts +57 -53
  175. package/dist/index.js +51 -48
  176. package/dist/types/file-viewer.d.ts +71 -0
  177. package/dist/types/index.d.ts +4 -3
  178. package/dist/types/tabs.d.ts +106 -14
  179. package/dist/types/tree.d.ts +6 -1
  180. package/package.json +32 -31
  181. package/src/components/breadcrumb/style.scss +0 -6
  182. package/src/components/cascade/cascade.vue +18 -18
  183. package/src/components/cascade/node.ts +4 -1
  184. package/src/components/cascade/use-check.ts +5 -4
  185. package/src/components/cascade/use-select.ts +4 -5
  186. package/src/components/checkbox/style.scss +0 -4
  187. package/src/components/date-picker/date-picker.vue +10 -9
  188. package/src/components/date-range-picker/date-range-picker.vue +12 -12
  189. package/src/components/drawer/style.scss +18 -11
  190. package/src/components/file-picker/helper.ts +21 -10
  191. package/src/components/file-viewer/file-viewer.vue +311 -0
  192. package/src/components/file-viewer/helper.ts +193 -0
  193. package/src/components/file-viewer/index.ts +1 -0
  194. package/src/components/file-viewer/previewers/docx-previewer.vue +78 -0
  195. package/src/components/file-viewer/previewers/image-previewer.vue +64 -0
  196. package/src/components/file-viewer/previewers/pdf-previewer.vue +90 -0
  197. package/src/components/file-viewer/previewers/sheet-previewer.vue +210 -0
  198. package/src/components/file-viewer/previewers/text-previewer.vue +76 -0
  199. package/src/components/file-viewer/previewers/video-previewer.vue +72 -0
  200. package/src/components/file-viewer/style.scss +504 -0
  201. package/src/components/file-viewer/style.ts +4 -0
  202. package/src/components/form-item/style.scss +2 -3
  203. package/src/components/index.ts +2 -0
  204. package/src/components/loading/style.scss +125 -126
  205. package/src/components/menu/style.scss +0 -31
  206. package/src/components/multi-select/multi-select-option.vue +16 -3
  207. package/src/components/multi-select/multi-select.vue +17 -9
  208. package/src/components/palette/di.ts +5 -3
  209. package/src/components/palette/palette-alpha.vue +7 -10
  210. package/src/components/palette/palette-hue.vue +5 -7
  211. package/src/components/palette/palette-sv.vue +12 -14
  212. package/src/components/palette/palette.vue +19 -21
  213. package/src/components/progress-nodes/style.scss +28 -22
  214. package/src/components/radio/style.scss +0 -4
  215. package/src/components/select/select.vue +21 -19
  216. package/src/components/select/style.scss +0 -12
  217. package/src/components/slider/slider-thumb.vue +11 -14
  218. package/src/components/slider/slider.vue +15 -15
  219. package/src/components/steps/style.scss +10 -10
  220. package/src/components/switch/style.scss +0 -4
  221. package/src/components/table/di.ts +20 -3
  222. package/src/components/table/node/row.ts +10 -1
  223. package/src/components/table/style.scss +0 -26
  224. package/src/components/table/table-body.vue +28 -33
  225. package/src/components/table/table-cell.vue +17 -9
  226. package/src/components/table/table-row.tsx +83 -17
  227. package/src/components/table/table.vue +73 -45
  228. package/src/components/table/use-fixed-columns.ts +46 -12
  229. package/src/components/table/use-rows.ts +14 -3
  230. package/src/components/table/use-table.ts +27 -3
  231. package/src/components/tabs/index.ts +2 -0
  232. package/src/components/tabs/style.scss +217 -134
  233. package/src/components/tabs/tabs-horizontal.vue +125 -0
  234. package/src/components/tabs/tabs-vertical.vue +75 -0
  235. package/src/components/tabs/tabs.vue +56 -127
  236. package/src/components/tabs/use-tabs-bar.ts +119 -0
  237. package/src/components/tag/style.scss +0 -6
  238. package/src/components/tree/style.scss +74 -29
  239. package/src/components/tree/tree-node.vue +10 -4
  240. package/src/components/tree/tree.vue +39 -12
  241. package/src/components/tree/use-check.ts +79 -23
  242. package/src/components/tree/use-tree-nodes.ts +85 -34
  243. package/src/types/file-viewer.ts +69 -0
  244. package/src/types/index.ts +1 -0
  245. package/src/types/tabs.ts +113 -19
  246. package/src/types/tree.ts +6 -1
  247. package/dist/types/index.js +0 -0
@@ -0,0 +1,193 @@
1
+ import type { FileViewerItem, FileViewerKind } from '../../types/file-viewer'
2
+
3
+ const IMAGE_EXTS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg', 'avif', 'ico'])
4
+
5
+ const VIDEO_EXTS = new Set(['mp4', 'webm', 'mov', 'm4v', 'ogv', 'mkv'])
6
+
7
+ const SHEET_EXTS = new Set(['xlsx', 'xlsm', 'xlsb', 'csv'])
8
+
9
+ const TEXT_EXTS = new Set([
10
+ 'txt',
11
+ 'log',
12
+ 'md',
13
+ 'markdown',
14
+ 'json',
15
+ 'yml',
16
+ 'yaml',
17
+ 'xml',
18
+ 'js',
19
+ 'ts',
20
+ 'tsx',
21
+ 'jsx',
22
+ 'css',
23
+ 'scss',
24
+ 'sass',
25
+ 'less',
26
+ 'html',
27
+ 'htm',
28
+ 'ini',
29
+ 'toml',
30
+ 'sh',
31
+ 'bash',
32
+ 'zsh',
33
+ 'env',
34
+ 'sql'
35
+ ])
36
+
37
+ export function getExtension(name: string): string {
38
+ const idx = name.lastIndexOf('.')
39
+ return idx === -1 ? '' : name.slice(idx + 1).toLowerCase()
40
+ }
41
+
42
+ export function inferKind(name: string, explicit?: FileViewerKind): FileViewerKind {
43
+ if (explicit) return explicit
44
+ const ext = getExtension(name)
45
+ if (IMAGE_EXTS.has(ext)) return 'image'
46
+ if (VIDEO_EXTS.has(ext)) return 'video'
47
+ if (ext === 'pdf') return 'pdf'
48
+ if (SHEET_EXTS.has(ext)) return 'sheet'
49
+ if (ext === 'docx') return 'docx'
50
+ if (TEXT_EXTS.has(ext)) return 'text'
51
+ return 'text'
52
+ }
53
+
54
+ export const FILE_VIEWER_KIND_LABEL: Record<FileViewerKind, string> = {
55
+ image: 'IMG',
56
+ video: 'MP4',
57
+ pdf: 'PDF',
58
+ sheet: 'XLS',
59
+ docx: 'DOC',
60
+ text: 'TXT'
61
+ }
62
+
63
+ /** 规范化数据源为 ArrayBuffer,可被 AbortController 中断(仅 URL fetch 场景生效) */
64
+ export async function toArrayBuffer(
65
+ src: FileViewerItem['src'],
66
+ signal?: AbortSignal
67
+ ): Promise<ArrayBuffer> {
68
+ if (typeof src === 'string') {
69
+ const res = await fetch(src, { signal })
70
+ if (!res.ok) {
71
+ throw new Error(`Fetch failed: ${res.status} ${res.statusText}`)
72
+ }
73
+ return await res.arrayBuffer()
74
+ }
75
+ if (src instanceof ArrayBuffer) return src
76
+ if (src instanceof Uint8Array) {
77
+ // 复制底层以切断共享引用,防止外部修改影响预览
78
+ return src.slice().buffer as ArrayBuffer
79
+ }
80
+ // File/Blob
81
+ return await src.arrayBuffer()
82
+ }
83
+
84
+ /** 返回用于 <img> / <video> 等标签 src 的 URL;二进制会创建 ObjectURL 并返回 revoke 钩子 */
85
+ export function toBlobUrl(
86
+ src: FileViewerItem['src'],
87
+ mime?: string
88
+ ): { url: string; revoke: () => void } {
89
+ if (typeof src === 'string') {
90
+ return { url: src, revoke: () => {} }
91
+ }
92
+ let blob: Blob
93
+ if (src instanceof Blob) {
94
+ blob = src
95
+ } else if (src instanceof ArrayBuffer) {
96
+ blob = new Blob([src], { type: mime ?? 'application/octet-stream' })
97
+ } else {
98
+ // Uint8Array: 复制到新的 ArrayBuffer,避免与外部共享底层
99
+ const copy = new Uint8Array(src.byteLength)
100
+ copy.set(src)
101
+ blob = new Blob([copy.buffer], { type: mime ?? 'application/octet-stream' })
102
+ }
103
+ const url = URL.createObjectURL(blob)
104
+ return { url, revoke: () => URL.revokeObjectURL(url) }
105
+ }
106
+
107
+ /**
108
+ * RFC-4180 子集 CSV 解析:支持 "" 转义、\r\n 与 \n 换行、, 分隔。
109
+ * 为性能起见采用状态机而非 split。
110
+ */
111
+ export function parseCsv(text: string): string[][] {
112
+ const rows: string[][] = []
113
+ let row: string[] = []
114
+ let field = ''
115
+ let inQuotes = false
116
+ const len = text.length
117
+
118
+ for (let i = 0; i < len; i++) {
119
+ const ch = text[i]
120
+
121
+ if (inQuotes) {
122
+ if (ch === '"') {
123
+ if (text[i + 1] === '"') {
124
+ field += '"'
125
+ i++
126
+ } else {
127
+ inQuotes = false
128
+ }
129
+ } else {
130
+ field += ch
131
+ }
132
+ continue
133
+ }
134
+
135
+ if (ch === '"') {
136
+ inQuotes = true
137
+ } else if (ch === ',') {
138
+ row.push(field)
139
+ field = ''
140
+ } else if (ch === '\n') {
141
+ row.push(field)
142
+ rows.push(row)
143
+ row = []
144
+ field = ''
145
+ } else if (ch === '\r') {
146
+ // swallow CR; LF will commit the row
147
+ } else {
148
+ field += ch
149
+ }
150
+ }
151
+
152
+ // 最后一行
153
+ if (field.length > 0 || row.length > 0) {
154
+ row.push(field)
155
+ rows.push(row)
156
+ }
157
+ return rows
158
+ }
159
+
160
+ export function formatBytes(bytes?: number): string {
161
+ if (bytes === undefined || bytes === null || Number.isNaN(bytes)) return '-'
162
+ if (bytes < 1024) return `${bytes} B`
163
+ const units = ['KB', 'MB', 'GB', 'TB']
164
+ let v = bytes / 1024
165
+ let i = 0
166
+ while (v >= 1024 && i < units.length - 1) {
167
+ v /= 1024
168
+ i++
169
+ }
170
+ return `${v.toFixed(v >= 10 ? 0 : 1)} ${units[i]}`
171
+ }
172
+
173
+ /** 触发浏览器下载 */
174
+ export async function downloadFile(file: FileViewerItem): Promise<void> {
175
+ let href: string
176
+ let revoke: (() => void) | undefined
177
+ if (typeof file.src === 'string') {
178
+ href = file.src
179
+ } else {
180
+ const url = toBlobUrl(file.src, file.mime)
181
+ href = url.url
182
+ revoke = url.revoke
183
+ }
184
+ const a = document.createElement('a')
185
+ a.href = href
186
+ a.download = file.name
187
+ a.rel = 'noopener'
188
+ a.style.display = 'none'
189
+ document.body.appendChild(a)
190
+ a.click()
191
+ document.body.removeChild(a)
192
+ if (revoke) setTimeout(revoke, 4000)
193
+ }
@@ -0,0 +1 @@
1
+ export { default as UFileViewer } from './file-viewer.vue'
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <u-scroll tag="div" :class="cls.e('docx')">
3
+ <div ref="container" :class="cls.e('docx-body')" />
4
+ <div v-if="loading" :class="cls.e('loading')">加载中…</div>
5
+ </u-scroll>
6
+ </template>
7
+
8
+ <script lang="ts" setup>
9
+ import { bem } from '@veltra/utils'
10
+ import { onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'
11
+
12
+ import type { FileViewerItem } from '../../../types/file-viewer'
13
+ import { UScroll } from '../../scroll'
14
+
15
+ defineOptions({ name: 'FileViewerDocxPreviewer' })
16
+
17
+ const props = defineProps<{ file: FileViewerItem }>()
18
+
19
+ const emit = defineEmits<{ (e: 'error', err: unknown): void }>()
20
+
21
+ const cls = bem('file-viewer')
22
+
23
+ const container = useTemplateRef<HTMLDivElement>('container')
24
+ const loading = ref(true)
25
+
26
+ let controller: AbortController | undefined
27
+
28
+ async function load() {
29
+ controller?.abort()
30
+ controller = new AbortController()
31
+ const signal = controller.signal
32
+ loading.value = true
33
+
34
+ try {
35
+ const [{ toArrayBuffer }, docx] = await Promise.all([
36
+ import('../helper'),
37
+ import('docx-preview')
38
+ ])
39
+
40
+ const buf = await toArrayBuffer(props.file.src, signal)
41
+ if (signal.aborted) return
42
+ const el = container.value
43
+ if (!el) return
44
+ el.innerHTML = ''
45
+
46
+ await docx.renderAsync(buf, el, undefined, {
47
+ className: 'u-file-viewer__docx-doc',
48
+ inWrapper: true,
49
+ ignoreWidth: false,
50
+ ignoreHeight: false,
51
+ ignoreFonts: false,
52
+ breakPages: true,
53
+ experimental: false,
54
+ useBase64URL: false,
55
+ renderChanges: false,
56
+ renderHeaders: true,
57
+ renderFooters: true,
58
+ renderFootnotes: true
59
+ })
60
+ } catch (err) {
61
+ if ((err as { name?: string })?.name === 'AbortError') return
62
+ emit('error', err)
63
+ } finally {
64
+ loading.value = false
65
+ }
66
+ }
67
+
68
+ watch(
69
+ () => props.file,
70
+ () => load(),
71
+ { immediate: true }
72
+ )
73
+
74
+ onBeforeUnmount(() => {
75
+ controller?.abort()
76
+ if (container.value) container.value.innerHTML = ''
77
+ })
78
+ </script>
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <div :class="[cls.e('image'), bem.is('actual', actual)]">
3
+ <img
4
+ v-if="url"
5
+ :src="url"
6
+ :alt="file.name"
7
+ draggable="false"
8
+ @load="loading = false"
9
+ @error="onImgError"
10
+ @dblclick="actual = !actual"
11
+ />
12
+ <div v-if="loading" :class="cls.e('loading')">加载中…</div>
13
+ <div v-if="failed" :class="cls.e('loading')">图片加载失败</div>
14
+ </div>
15
+ </template>
16
+
17
+ <script lang="ts" setup>
18
+ import { bem } from '@veltra/utils'
19
+ import { onBeforeUnmount, ref, shallowRef, watch } from 'vue'
20
+
21
+ import type { FileViewerItem } from '../../../types/file-viewer'
22
+
23
+ defineOptions({ name: 'FileViewerImagePreviewer' })
24
+
25
+ const props = defineProps<{ file: FileViewerItem }>()
26
+
27
+ const emit = defineEmits<{ (e: 'error', err: unknown): void }>()
28
+
29
+ const cls = bem('file-viewer')
30
+
31
+ const url = shallowRef<string>('')
32
+ const loading = ref(true)
33
+ const failed = ref(false)
34
+ const actual = ref(false)
35
+
36
+ let revoke: (() => void) | undefined
37
+
38
+ async function load() {
39
+ loading.value = true
40
+ failed.value = false
41
+ revoke?.()
42
+ const { toBlobUrl } = await import('../helper')
43
+ const r = toBlobUrl(props.file.src, props.file.mime)
44
+ url.value = r.url
45
+ revoke = r.revoke
46
+ }
47
+
48
+ function onImgError(e: Event) {
49
+ loading.value = false
50
+ failed.value = true
51
+ emit('error', e)
52
+ }
53
+
54
+ watch(
55
+ () => props.file,
56
+ () => load(),
57
+ { immediate: true }
58
+ )
59
+
60
+ onBeforeUnmount(() => {
61
+ revoke?.()
62
+ url.value = ''
63
+ })
64
+ </script>
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <div :class="cls.e('pdf')">
3
+ <div v-if="!engine || isLoading" :class="cls.e('loading')">正在加载 PDF 引擎…</div>
4
+ <EmbedPDF v-else-if="pdfUrl" :engine="engine" :plugins="plugins" v-slot="{ activeDocumentId }">
5
+ <DocumentContent
6
+ v-if="activeDocumentId"
7
+ :document-id="activeDocumentId"
8
+ v-slot="{ isLoaded }"
9
+ >
10
+ <Viewport v-if="isLoaded" :document-id="activeDocumentId" :class="cls.e('pdf-viewport')">
11
+ <Scroller :document-id="activeDocumentId">
12
+ <template #default="{ page }">
13
+ <div
14
+ :class="cls.e('pdf-page')"
15
+ :style="{ width: page.width + 'px', height: page.height + 'px' }"
16
+ >
17
+ <RenderLayer :document-id="activeDocumentId" :page-index="page.pageIndex" />
18
+ </div>
19
+ </template>
20
+ </Scroller>
21
+ </Viewport>
22
+ </DocumentContent>
23
+ </EmbedPDF>
24
+ </div>
25
+ </template>
26
+
27
+ <script lang="ts" setup>
28
+ import { createPluginRegistration } from '@embedpdf/core'
29
+ import { EmbedPDF } from '@embedpdf/core/vue'
30
+ import { usePdfiumEngine } from '@embedpdf/engines/vue'
31
+ import {
32
+ DocumentContent,
33
+ DocumentManagerPluginPackage
34
+ } from '@embedpdf/plugin-document-manager/vue'
35
+ import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/vue'
36
+ import { ScrollPluginPackage, Scroller } from '@embedpdf/plugin-scroll/vue'
37
+ import { ViewportPluginPackage, Viewport } from '@embedpdf/plugin-viewport/vue'
38
+ import { bem } from '@veltra/utils'
39
+ import { computed, onBeforeUnmount, shallowRef, watch } from 'vue'
40
+
41
+ import type { FileViewerItem } from '../../../types/file-viewer'
42
+
43
+ defineOptions({ name: 'FileViewerPdfPreviewer' })
44
+
45
+ const props = defineProps<{ file: FileViewerItem }>()
46
+
47
+ const emit = defineEmits<{ (e: 'error', err: unknown): void }>()
48
+
49
+ const cls = bem('file-viewer')
50
+
51
+ const { engine, isLoading, error } = usePdfiumEngine()
52
+
53
+ const pdfUrl = shallowRef<string>('')
54
+ let revoke: (() => void) | undefined
55
+
56
+ async function load() {
57
+ revoke?.()
58
+ const { toBlobUrl } = await import('../helper')
59
+ const r = toBlobUrl(props.file.src, props.file.mime ?? 'application/pdf')
60
+ pdfUrl.value = r.url
61
+ revoke = r.revoke
62
+ }
63
+
64
+ const plugins = computed(() => {
65
+ if (!pdfUrl.value) return []
66
+ return [
67
+ createPluginRegistration(DocumentManagerPluginPackage, {
68
+ initialDocuments: [{ url: pdfUrl.value }]
69
+ }),
70
+ createPluginRegistration(ViewportPluginPackage),
71
+ createPluginRegistration(ScrollPluginPackage),
72
+ createPluginRegistration(RenderPluginPackage)
73
+ ]
74
+ })
75
+
76
+ watch(
77
+ () => props.file,
78
+ () => load(),
79
+ { immediate: true }
80
+ )
81
+
82
+ watch(error, (err) => {
83
+ if (err) emit('error', err)
84
+ })
85
+
86
+ onBeforeUnmount(() => {
87
+ revoke?.()
88
+ pdfUrl.value = ''
89
+ })
90
+ </script>
@@ -0,0 +1,210 @@
1
+ <template>
2
+ <div :class="cls.e('sheet')">
3
+ <div v-if="truncated" :class="cls.e('sheet-note')">
4
+ 仅展示前 {{ renderedRows.toLocaleString() }} 行(共 {{ totalRows.toLocaleString() }} 行)
5
+ </div>
6
+ <div v-if="sheets.length > 1" :class="cls.e('sheet-tabs')">
7
+ <button
8
+ v-for="(s, i) in sheets"
9
+ :key="s.name"
10
+ :class="[cls.e('sheet-tab'), bem.is('active', i === activeSheetIndex)]"
11
+ @click="activeSheetIndex = i"
12
+ >
13
+ {{ s.name }}
14
+ </button>
15
+ </div>
16
+ <div ref="container" :class="cls.e('sheet-stage')" />
17
+ <div v-if="loading" :class="cls.e('loading')">加载中…</div>
18
+ </div>
19
+ </template>
20
+
21
+ <script lang="ts" setup>
22
+ import { bem } from '@veltra/utils'
23
+ import { onBeforeUnmount, ref, shallowRef, useTemplateRef, watch } from 'vue'
24
+
25
+ import type { FileViewerItem } from '../../../types/file-viewer'
26
+
27
+ defineOptions({ name: 'FileViewerSheetPreviewer' })
28
+
29
+ const props = withDefaults(
30
+ defineProps<{
31
+ file: FileViewerItem
32
+ maxRows?: number
33
+ }>(),
34
+ { maxRows: 50_000 }
35
+ )
36
+
37
+ const emit = defineEmits<{ (e: 'error', err: unknown): void }>()
38
+
39
+ const cls = bem('file-viewer')
40
+
41
+ const container = useTemplateRef<HTMLDivElement>('container')
42
+ const loading = ref(true)
43
+ const truncated = ref(false)
44
+ const totalRows = ref(0)
45
+ const renderedRows = ref(0)
46
+
47
+ interface SheetData {
48
+ name: string
49
+ headers: string[]
50
+ rows: string[][]
51
+ totalCount: number
52
+ }
53
+
54
+ const sheets = shallowRef<SheetData[]>([])
55
+ const activeSheetIndex = ref(0)
56
+
57
+ let controller: AbortController | undefined
58
+ let instance: { release?: () => void; resize?: () => void } | undefined
59
+ let resizeObserver: ResizeObserver | undefined
60
+
61
+ async function load() {
62
+ controller?.abort()
63
+ controller = new AbortController()
64
+ const signal = controller.signal
65
+ loading.value = true
66
+ sheets.value = []
67
+ activeSheetIndex.value = 0
68
+
69
+ try {
70
+ const ext = props.file.name.slice(props.file.name.lastIndexOf('.') + 1).toLowerCase()
71
+ const { toArrayBuffer, parseCsv } = await import('../helper')
72
+ const buf = await toArrayBuffer(props.file.src, signal)
73
+ if (signal.aborted) return
74
+
75
+ if (ext === 'csv') {
76
+ const text = new TextDecoder('utf-8', { fatal: false }).decode(buf)
77
+ const parsed = parseCsv(text).filter((row) => row.some((c) => c.length > 0))
78
+ const headers = parsed[0] ?? []
79
+ const rows = parsed.slice(1)
80
+ sheets.value = [
81
+ {
82
+ name: props.file.name,
83
+ headers: headers.map((h, i) => String(h) || `Column ${i + 1}`),
84
+ rows,
85
+ totalCount: rows.length
86
+ }
87
+ ]
88
+ } else {
89
+ const { readWorkbook } = await import('@cat-kit/excel')
90
+ const workbook = await readWorkbook(new Uint8Array(buf))
91
+ if (signal.aborted) return
92
+ sheets.value = workbook.worksheets.map((ws) => {
93
+ const wsRows = ws.getRows()
94
+ const firstRow = wsRows[0]
95
+ const headers = firstRow ? firstRow.toValues().map((v) => cellToString(v)) : []
96
+ const rows = wsRows.slice(1).map((r) => r.toValues().map(cellToString))
97
+ return {
98
+ name: ws.name,
99
+ headers: headers.map((h, i) => h || `Column ${i + 1}`),
100
+ rows,
101
+ totalCount: rows.length
102
+ }
103
+ })
104
+ }
105
+
106
+ await renderActive()
107
+ } catch (err) {
108
+ if ((err as { name?: string })?.name === 'AbortError') return
109
+ emit('error', err)
110
+ } finally {
111
+ loading.value = false
112
+ }
113
+ }
114
+
115
+ function cellToString(v: unknown): string {
116
+ if (v === null || v === undefined) return ''
117
+ if (typeof v === 'string') return v
118
+ if (typeof v === 'number' || typeof v === 'boolean') return String(v)
119
+ if (v instanceof Date) return v.toLocaleString()
120
+ if (typeof v === 'object' && v !== null && 'formula' in v) {
121
+ const f = v as { formula: string; result?: unknown }
122
+ if (f.result !== undefined && f.result !== null) return cellToString(f.result)
123
+ return `=${f.formula}`
124
+ }
125
+ return String(v)
126
+ }
127
+
128
+ async function renderActive() {
129
+ const sheet = sheets.value[activeSheetIndex.value]
130
+ if (!sheet || !container.value) return
131
+
132
+ releaseInstance()
133
+
134
+ const limit = props.maxRows > 0 ? Math.min(props.maxRows, sheet.rows.length) : sheet.rows.length
135
+ totalRows.value = sheet.totalCount
136
+ renderedRows.value = limit
137
+ truncated.value = props.maxRows > 0 && sheet.rows.length > props.maxRows
138
+
139
+ const columns = sheet.headers.map((h, i) => ({
140
+ field: 'c' + i,
141
+ title: h,
142
+ width: 'auto' as const
143
+ }))
144
+
145
+ const records: Record<string, string>[] = []
146
+ for (let i = 0; i < limit; i++) {
147
+ const row = sheet.rows[i]
148
+ if (!row) continue
149
+ const record: Record<string, string> = {}
150
+ for (let j = 0; j < columns.length; j++) {
151
+ record['c' + j] = row[j] ?? ''
152
+ }
153
+ records.push(record)
154
+ }
155
+
156
+ const VTable = await import('@visactor/vtable')
157
+ if (!container.value) return
158
+
159
+ instance = new VTable.ListTable(container.value, {
160
+ records,
161
+ columns,
162
+ widthMode: 'standard',
163
+ heightMode: 'standard',
164
+ defaultRowHeight: 32,
165
+ defaultHeaderRowHeight: 36,
166
+ autoFillWidth: true,
167
+ frozenColCount: 0,
168
+ theme: (VTable.themes.ARCO ?? VTable.themes.DEFAULT).extends({
169
+ defaultStyle: {
170
+ borderLineWidth: 1
171
+ }
172
+ })
173
+ })
174
+ }
175
+
176
+ function releaseInstance() {
177
+ if (instance && typeof instance.release === 'function') {
178
+ try {
179
+ instance.release()
180
+ } catch {
181
+ /* noop */
182
+ }
183
+ }
184
+ instance = undefined
185
+ }
186
+
187
+ watch(
188
+ () => props.file,
189
+ () => load(),
190
+ { immediate: true }
191
+ )
192
+
193
+ watch(activeSheetIndex, () => renderActive())
194
+
195
+ watch(container, (el) => {
196
+ resizeObserver?.disconnect()
197
+ if (!el) return
198
+ resizeObserver = new ResizeObserver(() => {
199
+ instance?.resize?.()
200
+ })
201
+ resizeObserver.observe(el)
202
+ })
203
+
204
+ onBeforeUnmount(() => {
205
+ controller?.abort()
206
+ resizeObserver?.disconnect()
207
+ releaseInstance()
208
+ sheets.value = []
209
+ })
210
+ </script>