adtec-core-package 3.1.7 → 3.1.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.
- package/adtec-core-package-3.1.7.tgz +0 -0
- package/package/.editorconfig +6 -0
- package/package/adtec-core-package/adtec-core-package.css +1 -0
- package/package/adtec-core-package/adtec-core-package.js +41216 -0
- package/package/adtec-core-package/adtec-core-package.umd.cjs +87 -0
- package/package/adtec-core-package/favicon.ico +0 -0
- package/package/index.html +13 -0
- package/package/prebuilt/umo-editor/favicon.ico +0 -0
- package/package/prebuilt/umo-editor/umo-editor.css +1 -0
- package/package/public/favicon.ico +0 -0
- package/package/src/assets/base.css +86 -0
- package/package/src/assets/main.css +35 -0
- package/package/src/components/editor-main/src/extensions/bookmark.js +110 -0
- package/package.json +2 -1
- package/src/components/upload/ExcelJsViewer.vue +211 -37
- package/src/components/upload/FileView.vue +7 -2
- package/src/components/upload/OfficePreview.vue +4 -2
- package/src/components/upload/PdfJsViewer.vue +2 -2
- package/src/utils/excelPreviewUtil.ts +387 -7
- package/src/utils/officePreviewUtil.ts +17 -0
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs'
|
|
44
44
|
import pdfWorker from 'pdfjs-dist/legacy/build/pdf.worker.mjs?url'
|
|
45
45
|
import { searchPdfDocument, type PdfSearchMatch } from '../../utils/pdfSearchUtil'
|
|
46
|
-
import { computePdfDefaultFitScale } from '../../utils/officePreviewUtil.ts'
|
|
46
|
+
import { computePdfDefaultFitScale, buildPdfJsLoadOptions } from '../../utils/officePreviewUtil.ts'
|
|
47
47
|
|
|
48
48
|
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorker
|
|
49
49
|
|
|
@@ -667,7 +667,7 @@ const loadPdf = async (src: Blob | ArrayBuffer) => {
|
|
|
667
667
|
const data = await toArrayBuffer(src)
|
|
668
668
|
if (token !== loadToken) return
|
|
669
669
|
|
|
670
|
-
const loadingTask = pdfjsLib.getDocument(
|
|
670
|
+
const loadingTask = pdfjsLib.getDocument(buildPdfJsLoadOptions(data))
|
|
671
671
|
activeTask = loadingTask
|
|
672
672
|
const doc = await loadingTask.promise
|
|
673
673
|
if (token !== loadToken) return
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import * as XLSX from 'xlsx-js-style'
|
|
2
|
+
import { unzipSync } from 'fflate'
|
|
3
|
+
|
|
4
|
+
export interface ExcelPreviewCellStyle {
|
|
5
|
+
backgroundColor?: string
|
|
6
|
+
color?: string
|
|
7
|
+
fontWeight?: string
|
|
8
|
+
fontStyle?: string
|
|
9
|
+
textDecoration?: string
|
|
10
|
+
textAlign?: 'left' | 'center' | 'right' | 'justify'
|
|
11
|
+
verticalAlign?: 'top' | 'middle' | 'bottom'
|
|
12
|
+
borderTop?: string
|
|
13
|
+
borderRight?: string
|
|
14
|
+
borderBottom?: string
|
|
15
|
+
borderLeft?: string
|
|
16
|
+
fontSize?: string
|
|
17
|
+
whiteSpace?: string
|
|
18
|
+
}
|
|
2
19
|
|
|
3
20
|
export interface ExcelPreviewCell {
|
|
4
21
|
value: string
|
|
@@ -6,11 +23,16 @@ export interface ExcelPreviewCell {
|
|
|
6
23
|
col: number
|
|
7
24
|
rowSpan?: number
|
|
8
25
|
colSpan?: number
|
|
26
|
+
style?: ExcelPreviewCellStyle
|
|
27
|
+
width?: number
|
|
9
28
|
}
|
|
10
29
|
|
|
11
30
|
export interface ExcelPreviewSheet {
|
|
12
31
|
name: string
|
|
13
32
|
rows: ExcelPreviewCell[][]
|
|
33
|
+
startCol: number
|
|
34
|
+
endCol: number
|
|
35
|
+
colWidths: Record<number, number>
|
|
14
36
|
}
|
|
15
37
|
|
|
16
38
|
export interface ExcelSearchMatch {
|
|
@@ -20,6 +42,326 @@ export interface ExcelSearchMatch {
|
|
|
20
42
|
text: string
|
|
21
43
|
}
|
|
22
44
|
|
|
45
|
+
type ColorStyle = { rgb?: string; theme?: number; tint?: number; indexed?: number }
|
|
46
|
+
|
|
47
|
+
type WorkbookStyles = {
|
|
48
|
+
Fonts?: Array<Record<string, unknown>>
|
|
49
|
+
Fills?: Array<Record<string, unknown>>
|
|
50
|
+
Borders?: Array<Record<string, unknown>>
|
|
51
|
+
CellXf?: Array<Record<string, unknown>>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Excel 默认主题色(Office 2013+,含 accent / hyperlink) */
|
|
55
|
+
const EXCEL_THEME_COLORS: Record<number, string> = {
|
|
56
|
+
0: '#FFFFFF',
|
|
57
|
+
1: '#000000',
|
|
58
|
+
2: '#E7E6E6',
|
|
59
|
+
3: '#44546A',
|
|
60
|
+
4: '#4472C4',
|
|
61
|
+
5: '#ED7D31',
|
|
62
|
+
6: '#A5A5A5',
|
|
63
|
+
7: '#FFC000',
|
|
64
|
+
8: '#5B9BD5',
|
|
65
|
+
9: '#70AD47',
|
|
66
|
+
10: '#0563C1',
|
|
67
|
+
11: '#954F72',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Excel 标准 indexed 色板(常用前 16 色) */
|
|
71
|
+
const INDEXED_COLORS: Record<number, string> = {
|
|
72
|
+
0: '#000000',
|
|
73
|
+
1: '#FFFFFF',
|
|
74
|
+
2: '#FF0000',
|
|
75
|
+
3: '#00FF00',
|
|
76
|
+
4: '#0000FF',
|
|
77
|
+
5: '#FFFF00',
|
|
78
|
+
6: '#FF00FF',
|
|
79
|
+
7: '#00FFFF',
|
|
80
|
+
8: '#000000',
|
|
81
|
+
9: '#FFFFFF',
|
|
82
|
+
10: '#FF0000',
|
|
83
|
+
11: '#00FF00',
|
|
84
|
+
12: '#0000FF',
|
|
85
|
+
13: '#FFFF00',
|
|
86
|
+
14: '#FF00FF',
|
|
87
|
+
15: '#00FFFF',
|
|
88
|
+
16: '#800000',
|
|
89
|
+
17: '#008000',
|
|
90
|
+
18: '#000080',
|
|
91
|
+
19: '#808000',
|
|
92
|
+
20: '#800080',
|
|
93
|
+
21: '#008080',
|
|
94
|
+
22: '#C0C0C0',
|
|
95
|
+
23: '#808080',
|
|
96
|
+
24: '#9999FF',
|
|
97
|
+
25: '#993366',
|
|
98
|
+
26: '#FFFFCC',
|
|
99
|
+
27: '#CCFFFF',
|
|
100
|
+
28: '#660066',
|
|
101
|
+
29: '#FF8080',
|
|
102
|
+
30: '#0066CC',
|
|
103
|
+
31: '#CCCCFF',
|
|
104
|
+
40: '#00CCFF',
|
|
105
|
+
41: '#CCFFFF',
|
|
106
|
+
42: '#CCFFCC',
|
|
107
|
+
43: '#FFFF99',
|
|
108
|
+
44: '#99CCFF',
|
|
109
|
+
45: '#FF99CC',
|
|
110
|
+
46: '#CC99FF',
|
|
111
|
+
47: '#FFCC99',
|
|
112
|
+
48: '#3366FF',
|
|
113
|
+
49: '#33CCCC',
|
|
114
|
+
50: '#99CC00',
|
|
115
|
+
51: '#FFCC00',
|
|
116
|
+
52: '#FF9900',
|
|
117
|
+
53: '#FF6600',
|
|
118
|
+
54: '#666699',
|
|
119
|
+
55: '#969696',
|
|
120
|
+
56: '#003366',
|
|
121
|
+
57: '#339966',
|
|
122
|
+
58: '#003300',
|
|
123
|
+
59: '#333300',
|
|
124
|
+
60: '#993300',
|
|
125
|
+
61: '#993366',
|
|
126
|
+
62: '#333399',
|
|
127
|
+
63: '#333333',
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const BORDER_WIDTH: Record<string, string> = {
|
|
131
|
+
hair: '1px',
|
|
132
|
+
thin: '1px',
|
|
133
|
+
medium: '2px',
|
|
134
|
+
thick: '3px',
|
|
135
|
+
dashed: '1px',
|
|
136
|
+
dotted: '1px',
|
|
137
|
+
double: '3px',
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const DEFAULT_COL_WIDTH = 72
|
|
141
|
+
|
|
142
|
+
const isOleXls = (buffer: ArrayBuffer) => {
|
|
143
|
+
const sig = new Uint8Array(buffer, 0, 4)
|
|
144
|
+
return sig[0] === 0xd0 && sig[1] === 0xcf && sig[2] === 0x11 && sig[3] === 0xe0
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const isZipXlsx = (buffer: ArrayBuffer) => {
|
|
148
|
+
const sig = new Uint8Array(buffer, 0, 2)
|
|
149
|
+
return sig[0] === 0x50 && sig[1] === 0x4b
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const hexToRgb = (hex: string) => {
|
|
153
|
+
const normalized = hex.replace('#', '')
|
|
154
|
+
const value =
|
|
155
|
+
normalized.length === 8 ? normalized.slice(2) : normalized.length === 6 ? normalized : ''
|
|
156
|
+
if (value.length !== 6) return null
|
|
157
|
+
return {
|
|
158
|
+
r: parseInt(value.slice(0, 2), 16),
|
|
159
|
+
g: parseInt(value.slice(2, 4), 16),
|
|
160
|
+
b: parseInt(value.slice(4, 6), 16),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const rgbToHex = (r: number, g: number, b: number) =>
|
|
165
|
+
`#${[r, g, b]
|
|
166
|
+
.map((v) => Math.min(255, Math.max(0, Math.round(v))).toString(16).padStart(2, '0'))
|
|
167
|
+
.join('')}`
|
|
168
|
+
|
|
169
|
+
const applyTint = (hex: string, tint: number) => {
|
|
170
|
+
const rgb = hexToRgb(hex)
|
|
171
|
+
if (!rgb) return hex
|
|
172
|
+
const mix = tint < 0 ? { r: 0, g: 0, b: 0 } : { r: 255, g: 255, b: 255 }
|
|
173
|
+
const ratio = Math.abs(tint)
|
|
174
|
+
return rgbToHex(
|
|
175
|
+
rgb.r + (mix.r - rgb.r) * ratio,
|
|
176
|
+
rgb.g + (mix.g - rgb.g) * ratio,
|
|
177
|
+
rgb.b + (mix.b - rgb.b) * ratio,
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const resolveColor = (color?: ColorStyle): string | undefined => {
|
|
182
|
+
if (!color) return undefined
|
|
183
|
+
if (color.rgb) {
|
|
184
|
+
const raw = color.rgb.replace(/[^0-9a-f]/gi, '')
|
|
185
|
+
if (raw.length === 8) return `#${raw.slice(2)}`
|
|
186
|
+
if (raw.length === 6) return `#${raw}`
|
|
187
|
+
return undefined
|
|
188
|
+
}
|
|
189
|
+
if (color.indexed != null) return INDEXED_COLORS[color.indexed]
|
|
190
|
+
if (color.theme != null) {
|
|
191
|
+
const base = EXCEL_THEME_COLORS[color.theme] ?? '#000000'
|
|
192
|
+
return color.tint ? applyTint(base, color.tint) : base
|
|
193
|
+
}
|
|
194
|
+
return undefined
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const resolveBorderSide = (side?: { style?: string; color?: ColorStyle }) => {
|
|
198
|
+
if (!side?.style || side.style === 'none') return undefined
|
|
199
|
+
const width = BORDER_WIDTH[side.style] ?? '1px'
|
|
200
|
+
const color = resolveColor(side.color) ?? '#000000'
|
|
201
|
+
const lineStyle = side.style === 'dashed' ? 'dashed' : side.style === 'dotted' ? 'dotted' : 'solid'
|
|
202
|
+
return `${width} ${lineStyle} ${color}`
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const toNumber = (value: unknown, fallback = 0) => {
|
|
206
|
+
const num = Number(value)
|
|
207
|
+
return Number.isFinite(num) ? num : fallback
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const isTruthy = (value: unknown) => value === true || value === 1 || value === '1'
|
|
211
|
+
|
|
212
|
+
const styleFromParts = (parts: {
|
|
213
|
+
font?: Record<string, unknown>
|
|
214
|
+
fill?: Record<string, unknown>
|
|
215
|
+
border?: Record<string, unknown>
|
|
216
|
+
alignment?: Record<string, unknown>
|
|
217
|
+
}): ExcelPreviewCellStyle | undefined => {
|
|
218
|
+
const css: ExcelPreviewCellStyle = {}
|
|
219
|
+
|
|
220
|
+
const fill = parts.fill
|
|
221
|
+
if (fill && fill.patternType !== 'none') {
|
|
222
|
+
const fg = fill.fgColor as ColorStyle | undefined
|
|
223
|
+
const bg = fill.bgColor as ColorStyle | undefined
|
|
224
|
+
const background = resolveColor(fg) || resolveColor(bg)
|
|
225
|
+
if (background) css.backgroundColor = background
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const font = parts.font
|
|
229
|
+
if (font) {
|
|
230
|
+
if (isTruthy(font.bold)) css.fontWeight = 'bold'
|
|
231
|
+
if (isTruthy(font.italic)) css.fontStyle = 'italic'
|
|
232
|
+
if (font.underline) css.textDecoration = 'underline'
|
|
233
|
+
if (font.sz) css.fontSize = `${font.sz}pt`
|
|
234
|
+
const color = resolveColor(font.color as ColorStyle | undefined)
|
|
235
|
+
if (color) css.color = color
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const alignment = parts.alignment
|
|
239
|
+
if (alignment) {
|
|
240
|
+
const horizontal = alignment.horizontal as string | undefined
|
|
241
|
+
if (horizontal && horizontal !== 'general') {
|
|
242
|
+
css.textAlign = horizontal as ExcelPreviewCellStyle['textAlign']
|
|
243
|
+
}
|
|
244
|
+
const vertical = alignment.vertical as string | undefined
|
|
245
|
+
if (vertical) {
|
|
246
|
+
css.verticalAlign =
|
|
247
|
+
vertical === 'center' ? 'middle' : (vertical as ExcelPreviewCellStyle['verticalAlign'])
|
|
248
|
+
}
|
|
249
|
+
if (isTruthy(alignment.wrapText)) css.whiteSpace = 'pre-wrap'
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const border = parts.border
|
|
253
|
+
if (border) {
|
|
254
|
+
const top = resolveBorderSide(border.top as { style?: string; color?: ColorStyle })
|
|
255
|
+
const right = resolveBorderSide(border.right as { style?: string; color?: ColorStyle })
|
|
256
|
+
const bottom = resolveBorderSide(border.bottom as { style?: string; color?: ColorStyle })
|
|
257
|
+
const left = resolveBorderSide(border.left as { style?: string; color?: ColorStyle })
|
|
258
|
+
if (top) css.borderTop = top
|
|
259
|
+
if (right) css.borderRight = right
|
|
260
|
+
if (bottom) css.borderBottom = bottom
|
|
261
|
+
if (left) css.borderLeft = left
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return Object.keys(css).length > 0 ? css : undefined
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const resolveXfStyle = (
|
|
268
|
+
styles: WorkbookStyles | undefined,
|
|
269
|
+
xfIndex: number,
|
|
270
|
+
): ExcelPreviewCellStyle | undefined => {
|
|
271
|
+
if (!styles?.CellXf?.length || xfIndex < 0) return undefined
|
|
272
|
+
const xf = styles.CellXf[xfIndex]
|
|
273
|
+
if (!xf) return undefined
|
|
274
|
+
|
|
275
|
+
const fontId = toNumber(xf.fontId ?? xf.fontid, 0)
|
|
276
|
+
const fillId = toNumber(xf.fillId ?? xf.fillid, 0)
|
|
277
|
+
const borderId = toNumber(xf.borderId ?? xf.borderid, 0)
|
|
278
|
+
|
|
279
|
+
return styleFromParts({
|
|
280
|
+
font: styles.Fonts?.[fontId] as Record<string, unknown> | undefined,
|
|
281
|
+
fill: styles.Fills?.[fillId] as Record<string, unknown> | undefined,
|
|
282
|
+
border: styles.Borders?.[borderId] as Record<string, unknown> | undefined,
|
|
283
|
+
alignment: xf.alignment as Record<string, unknown> | undefined,
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** xlsx-js-style 读入后 cell.s 往往只有 fill,字体/边框需用 XML 中的 s 索引关联 CellXf */
|
|
288
|
+
const parseXlsxStyleIndexMaps = (buffer: ArrayBuffer, sheetCount: number) => {
|
|
289
|
+
if (!isZipXlsx(buffer) || sheetCount <= 0) return [] as Map<string, number>[]
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
const files = unzipSync(new Uint8Array(buffer))
|
|
293
|
+
const decoder = new TextDecoder('utf-8')
|
|
294
|
+
const maps: Map<string, number>[] = []
|
|
295
|
+
|
|
296
|
+
for (let i = 0; i < sheetCount; i++) {
|
|
297
|
+
const path = `xl/worksheets/sheet${i + 1}.xml`
|
|
298
|
+
const xmlBytes = files[path]
|
|
299
|
+
const map = new Map<string, number>()
|
|
300
|
+
if (xmlBytes) {
|
|
301
|
+
const xml = decoder.decode(xmlBytes)
|
|
302
|
+
const cellTagRe = /<c\b([^>/]*)(?:\/>|>)/g
|
|
303
|
+
let match: RegExpExecArray | null
|
|
304
|
+
while ((match = cellTagRe.exec(xml))) {
|
|
305
|
+
const attrs = match[1]
|
|
306
|
+
const addr = attrs.match(/\br="([^"]+)"/)?.[1]
|
|
307
|
+
const styleIndex = attrs.match(/\bs="(\d+)"/)?.[1]
|
|
308
|
+
if (addr && styleIndex != null) {
|
|
309
|
+
map.set(addr, Number(styleIndex))
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
maps.push(map)
|
|
314
|
+
}
|
|
315
|
+
return maps
|
|
316
|
+
} catch {
|
|
317
|
+
return Array.from({ length: sheetCount }, () => new Map<string, number>())
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const extractCellStyle = (
|
|
322
|
+
cell?: XLSX.CellObject,
|
|
323
|
+
styles?: WorkbookStyles,
|
|
324
|
+
xfIndex?: number,
|
|
325
|
+
): ExcelPreviewCellStyle | undefined => {
|
|
326
|
+
if (styles && xfIndex != null && xfIndex >= 0) {
|
|
327
|
+
const resolved = resolveXfStyle(styles, xfIndex)
|
|
328
|
+
if (resolved) return resolved
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (typeof cell?.s === 'number' && styles) {
|
|
332
|
+
return resolveXfStyle(styles, cell.s)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const rawStyle = cell?.s
|
|
336
|
+
if (!rawStyle || typeof rawStyle !== 'object') return undefined
|
|
337
|
+
|
|
338
|
+
// cell.s 对象通常仅含 fill,作为兜底
|
|
339
|
+
return styleFromParts({
|
|
340
|
+
fill: rawStyle as Record<string, unknown>,
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export const cellStyleToCss = (style?: ExcelPreviewCellStyle): Record<string, string> => {
|
|
345
|
+
if (!style) return {}
|
|
346
|
+
const css: Record<string, string> = {}
|
|
347
|
+
if (style.backgroundColor) css.backgroundColor = style.backgroundColor
|
|
348
|
+
if (style.color) css.color = style.color
|
|
349
|
+
if (style.fontWeight) css.fontWeight = style.fontWeight
|
|
350
|
+
if (style.fontStyle) css.fontStyle = style.fontStyle
|
|
351
|
+
if (style.textDecoration) css.textDecoration = style.textDecoration
|
|
352
|
+
if (style.textAlign) css.textAlign = style.textAlign
|
|
353
|
+
if (style.verticalAlign) css.verticalAlign = style.verticalAlign
|
|
354
|
+
if (style.borderTop) css.borderTop = style.borderTop
|
|
355
|
+
if (style.borderRight) css.borderRight = style.borderRight
|
|
356
|
+
if (style.borderBottom) css.borderBottom = style.borderBottom
|
|
357
|
+
if (style.borderLeft) css.borderLeft = style.borderLeft
|
|
358
|
+
if (style.fontSize) css.fontSize = style.fontSize
|
|
359
|
+
if (style.whiteSpace) css.whiteSpace = style.whiteSpace
|
|
360
|
+
return css
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export const toExcelColumnLabel = (colIndex: number) => XLSX.utils.encode_col(colIndex)
|
|
364
|
+
|
|
23
365
|
const formatCellValue = (cell?: XLSX.CellObject) => {
|
|
24
366
|
if (!cell) return ''
|
|
25
367
|
if (cell.w != null && cell.w !== '') return String(cell.w)
|
|
@@ -31,18 +373,52 @@ const formatCellValue = (cell?: XLSX.CellObject) => {
|
|
|
31
373
|
return String(cell.v)
|
|
32
374
|
}
|
|
33
375
|
|
|
376
|
+
const parseColWidths = (sheet: XLSX.WorkSheet, startCol: number, endCol: number) => {
|
|
377
|
+
const cols = sheet['!cols'] || []
|
|
378
|
+
const widths: Record<number, number> = {}
|
|
379
|
+
for (let c = startCol; c <= endCol; c++) {
|
|
380
|
+
const col = cols[c]
|
|
381
|
+
if (col?.wpx) widths[c] = col.wpx
|
|
382
|
+
else if (col?.wch) widths[c] = Math.max(48, col.wch * 7)
|
|
383
|
+
else widths[c] = DEFAULT_COL_WIDTH
|
|
384
|
+
}
|
|
385
|
+
return widths
|
|
386
|
+
}
|
|
387
|
+
|
|
34
388
|
export const parseExcelWorkbook = async (src: Blob | ArrayBuffer): Promise<ExcelPreviewSheet[]> => {
|
|
35
389
|
const buffer = src instanceof Blob ? await src.arrayBuffer() : src
|
|
36
|
-
const workbook = XLSX.read(buffer, {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
390
|
+
const workbook = XLSX.read(buffer, {
|
|
391
|
+
type: 'array',
|
|
392
|
+
cellDates: true,
|
|
393
|
+
cellStyles: true,
|
|
394
|
+
...(isOleXls(buffer) ? {} : {}),
|
|
395
|
+
})
|
|
396
|
+
const styles = workbook.Styles as WorkbookStyles | undefined
|
|
397
|
+
const styleIndexMaps = isZipXlsx(buffer)
|
|
398
|
+
? parseXlsxStyleIndexMaps(buffer, workbook.SheetNames.length)
|
|
399
|
+
: []
|
|
400
|
+
|
|
401
|
+
return workbook.SheetNames.map((name, sheetIndex) => {
|
|
402
|
+
const sheet = workbook.Sheets[name]
|
|
403
|
+
const range = sheet?.['!ref'] ? XLSX.utils.decode_range(sheet['!ref']) : null
|
|
404
|
+
return {
|
|
405
|
+
name,
|
|
406
|
+
rows: buildSheetRows(sheet, styles, styleIndexMaps[sheetIndex]),
|
|
407
|
+
startCol: range?.s.c ?? 0,
|
|
408
|
+
endCol: range?.e.c ?? 0,
|
|
409
|
+
colWidths: range ? parseColWidths(sheet, range.s.c, range.e.c) : {},
|
|
410
|
+
}
|
|
411
|
+
})
|
|
41
412
|
}
|
|
42
413
|
|
|
43
|
-
const buildSheetRows = (
|
|
414
|
+
const buildSheetRows = (
|
|
415
|
+
sheet?: XLSX.WorkSheet,
|
|
416
|
+
styles?: WorkbookStyles,
|
|
417
|
+
styleIndexMap?: Map<string, number>,
|
|
418
|
+
): ExcelPreviewCell[][] => {
|
|
44
419
|
if (!sheet || !sheet['!ref']) return []
|
|
45
420
|
const range = XLSX.utils.decode_range(sheet['!ref'])
|
|
421
|
+
const colWidths = parseColWidths(sheet, range.s.c, range.e.c)
|
|
46
422
|
const mergeStarts = new Map<string, { rowSpan: number; colSpan: number }>()
|
|
47
423
|
const covered = new Set<string>()
|
|
48
424
|
|
|
@@ -63,13 +439,17 @@ const buildSheetRows = (sheet?: XLSX.WorkSheet): ExcelPreviewCell[][] => {
|
|
|
63
439
|
for (let c = range.s.c; c <= range.e.c; c++) {
|
|
64
440
|
if (covered.has(`${r},${c}`)) continue
|
|
65
441
|
const addr = XLSX.utils.encode_cell({ r, c })
|
|
442
|
+
const cellObj = sheet[addr]
|
|
66
443
|
const merge = mergeStarts.get(`${r},${c}`)
|
|
444
|
+
const xfIndex = styleIndexMap?.get(addr)
|
|
67
445
|
row.push({
|
|
68
|
-
value: formatCellValue(
|
|
446
|
+
value: formatCellValue(cellObj),
|
|
69
447
|
row: r,
|
|
70
448
|
col: c,
|
|
71
449
|
rowSpan: merge?.rowSpan,
|
|
72
450
|
colSpan: merge?.colSpan,
|
|
451
|
+
style: extractCellStyle(cellObj, styles, xfIndex),
|
|
452
|
+
width: colWidths[c],
|
|
73
453
|
})
|
|
74
454
|
}
|
|
75
455
|
if (row.length > 0) rows.push(row)
|
|
@@ -120,3 +120,20 @@ export const isLegacyDocFile = (type?: string, name?: string, fileUrl?: string)
|
|
|
120
120
|
/** 弹层等场景:使用 OfficePreview 内置标题栏(PDF / Word / Excel) */
|
|
121
121
|
export const isHeaderPreviewFile = (type?: string, name?: string, fileUrl?: string) =>
|
|
122
122
|
HEADER_PREVIEW_TYPES.has(toOfficePreviewType(type, name, fileUrl))
|
|
123
|
+
|
|
124
|
+
const pdfAssetBase = () => {
|
|
125
|
+
const base = import.meta.env.BASE_URL || '/'
|
|
126
|
+
return base.endsWith('/') ? base : `${base}/`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** pdf.js 加载参数:CJK 预览需 cMap;缺嵌入字体时尝试系统字体 */
|
|
130
|
+
export const buildPdfJsLoadOptions = (data: ArrayBuffer) => {
|
|
131
|
+
const assetBase = pdfAssetBase()
|
|
132
|
+
return {
|
|
133
|
+
data,
|
|
134
|
+
useSystemFonts: true,
|
|
135
|
+
cMapUrl: `${assetBase}pdfjs/cmaps/`,
|
|
136
|
+
cMapPacked: true,
|
|
137
|
+
standardFontDataUrl: `${assetBase}pdfjs/standard_fonts/`,
|
|
138
|
+
}
|
|
139
|
+
}
|