adtec-core-package 3.1.6 → 3.1.8

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 (31) hide show
  1. package/adtec-core-package/adtec-core-package.css +1 -0
  2. package/adtec-core-package/adtec-core-package.js +41216 -0
  3. package/adtec-core-package/adtec-core-package.umd.cjs +87 -0
  4. package/adtec-core-package/favicon.ico +0 -0
  5. package/adtec-core-package-3.1.7.tgz +0 -0
  6. package/package/.editorconfig +6 -0
  7. package/package/adtec-core-package/adtec-core-package.css +1 -0
  8. package/package/adtec-core-package/adtec-core-package.js +41216 -0
  9. package/package/adtec-core-package/adtec-core-package.umd.cjs +87 -0
  10. package/package/adtec-core-package/favicon.ico +0 -0
  11. package/package/index.html +13 -0
  12. package/package/prebuilt/umo-editor/favicon.ico +0 -0
  13. package/package/prebuilt/umo-editor/umo-editor.css +1 -0
  14. package/package/public/favicon.ico +0 -0
  15. package/package/src/assets/base.css +86 -0
  16. package/package/src/assets/main.css +35 -0
  17. package/package/src/components/editor-main/src/extensions/bookmark.js +110 -0
  18. package/package.json +4 -2
  19. package/src/api/DocumentApi.ts +11 -1
  20. package/src/components/upload/DocxJsViewer.vue +286 -0
  21. package/src/components/upload/ExcelJsViewer.vue +444 -0
  22. package/src/components/upload/FileView.vue +102 -159
  23. package/src/components/upload/OfficePreview.vue +481 -0
  24. package/src/components/upload/OfficePreviewHeaderBar.vue +315 -0
  25. package/src/components/upload/OfficePreviewToolbar.vue +203 -0
  26. package/src/components/upload/PdfJsViewer.vue +920 -0
  27. package/src/utils/docxPreviewUtil.ts +108 -0
  28. package/src/utils/excelPreviewUtil.ts +307 -0
  29. package/src/utils/officePreviewUtil.ts +122 -0
  30. package/src/utils/pdfSearchUtil.ts +127 -0
  31. package/src/utils/toSameOriginFileUrl.ts +1 -1
@@ -0,0 +1,481 @@
1
+ <template>
2
+ <div class="office-preview-root" :class="{ 'is-rich-preview': isRichPreview }">
3
+ <template v-if="isRichPreview">
4
+ <office-preview-header-bar
5
+ v-if="showHeader"
6
+ :title="headerTitle"
7
+ :zoom="scale"
8
+ :current-page="previewCurrentPage"
9
+ :total-pages="previewTotalPages"
10
+ :search-query="searchQuery"
11
+ :search-index="searchMatchIndex"
12
+ :search-total="searchMatchTotal"
13
+ :show-search="showSearch"
14
+ :show-page-nav="showPageNav && officeType !== 'excel'"
15
+ :show-zoom="showZoom"
16
+ :show-download="showDownload"
17
+ :show-refresh="showRefresh"
18
+ :show-fullscreen="showFullscreen"
19
+ :show-close="showClose"
20
+ :fullscreen="fullscreen"
21
+ :download-loading="downloadLoading"
22
+ :min-zoom="MIN_OFFICE_ZOOM"
23
+ :max-zoom="MAX_RICH_PREVIEW_ZOOM"
24
+ @zoom-in="zoomIn"
25
+ @zoom-out="zoomOut"
26
+ @zoom-reset="resetZoom"
27
+ @download="emit('download')"
28
+ @refresh="emit('refresh')"
29
+ @toggle-fullscreen="emit('toggle-fullscreen')"
30
+ @close="emit('close')"
31
+ @page-prev="goPrevPage"
32
+ @page-next="goNextPage"
33
+ @search="handleSearch"
34
+ @search-clear="handleSearchClear"
35
+ @search-prev="goPrevSearchMatch"
36
+ @search-next="goNextSearchMatch"
37
+ />
38
+ <div class="office-rich-wrap">
39
+ <pdf-js-viewer
40
+ v-if="officeType === 'pdf' || previewAsPdf"
41
+ ref="pdfViewerRef"
42
+ :src="fileSrc"
43
+ :scale="scale"
44
+ class="office-rich-viewer"
45
+ @loaded="handleRendered"
46
+ @error="handleError"
47
+ @page-change="previewCurrentPage = $event"
48
+ />
49
+ <excel-js-viewer
50
+ v-else-if="officeType === 'excel'"
51
+ ref="excelViewerRef"
52
+ :src="fileSrc"
53
+ :scale="scale"
54
+ class="office-rich-viewer"
55
+ @loaded="handleRendered"
56
+ @error="handleError"
57
+ @page-change="previewCurrentPage = $event"
58
+ @zoom-in="zoomIn"
59
+ @zoom-out="zoomOut"
60
+ />
61
+ <docx-js-viewer
62
+ v-else-if="officeType === 'docx' && !previewAsPdf"
63
+ ref="docxViewerRef"
64
+ :src="fileSrc"
65
+ :scale="scale"
66
+ :file-name="fileName"
67
+ :file-type="fileType"
68
+ :file-url="url"
69
+ class="office-rich-viewer"
70
+ @loaded="handleRendered"
71
+ @error="handleError"
72
+ @page-change="previewCurrentPage = $event"
73
+ />
74
+ <div v-if="isPreviewLoading" class="office-rich-loading">
75
+ <div class="loading-card">
76
+ <span class="loading-spinner" />
77
+ <p class="loading-title">{{ loadingMessage }}</p>
78
+ <p v-if="headerTitle" class="loading-sub">{{ headerTitle }}</p>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </template>
83
+ <div v-else-if="isUnsupportedType" class="unsupported-preview">
84
+ <el-empty :description="unsupportedMessage">
85
+ <el-button v-if="showDownload" type="primary" @click="emit('download')">
86
+ 下载文件
87
+ </el-button>
88
+ </el-empty>
89
+ </div>
90
+ <pre v-else-if="officeType === 'txt'" class="txt-preview">{{ txtContent }}</pre>
91
+ </div>
92
+ </template>
93
+ <script setup lang="ts">
94
+ import { computed, ref, watch, nextTick } from 'vue'
95
+ import { ElMessage } from 'element-plus'
96
+ import request from '../../utils/AxiosConfig'
97
+ import documentApi from '../../api/DocumentApi'
98
+ import frameworkUtils from '../../utils/FrameworkUtils'
99
+ import { toSameOriginFileUrl } from '../../utils/toSameOriginFileUrl'
100
+ import OfficePreviewHeaderBar from './OfficePreviewHeaderBar.vue'
101
+ import PdfJsViewer from './PdfJsViewer.vue'
102
+ import ExcelJsViewer from './ExcelJsViewer.vue'
103
+ import DocxJsViewer from './DocxJsViewer.vue'
104
+ import {
105
+ type OfficePreviewType,
106
+ clampRichPreviewZoom,
107
+ isSupportedPreviewType,
108
+ isLegacyDocFile,
109
+ MAX_RICH_PREVIEW_ZOOM,
110
+ MIN_OFFICE_ZOOM,
111
+ toOfficePreviewType,
112
+ OFFICE_ZOOM_STEP,
113
+ } from '../../utils/officePreviewUtil.ts'
114
+
115
+ const props = withDefaults(
116
+ defineProps<{
117
+ url: string
118
+ fileId?: string
119
+ fileName?: string
120
+ fileType?: string
121
+ showHeader?: boolean
122
+ showSearch?: boolean
123
+ showPageNav?: boolean
124
+ showZoom?: boolean
125
+ showDownload?: boolean
126
+ showRefresh?: boolean
127
+ showFullscreen?: boolean
128
+ showClose?: boolean
129
+ fullscreen?: boolean
130
+ downloadLoading?: boolean
131
+ }>(),
132
+ {
133
+ showHeader: true,
134
+ showSearch: true,
135
+ showPageNav: true,
136
+ showZoom: true,
137
+ showDownload: false,
138
+ showRefresh: false,
139
+ showFullscreen: false,
140
+ showClose: false,
141
+ fullscreen: false,
142
+ downloadLoading: false,
143
+ },
144
+ )
145
+
146
+ const scale = defineModel<number>('scale', { default: 1 })
147
+
148
+ const emit = defineEmits<{
149
+ loaded: []
150
+ error: [err?: unknown]
151
+ download: []
152
+ refresh: []
153
+ 'toggle-fullscreen': []
154
+ close: []
155
+ }>()
156
+
157
+ const fileSrc = ref<Blob | ArrayBuffer>()
158
+ const txtContent = ref('')
159
+ const previewAsPdf = ref(false)
160
+ const pdfViewerRef = ref<InstanceType<typeof PdfJsViewer> | null>(null)
161
+ const excelViewerRef = ref<InstanceType<typeof ExcelJsViewer> | null>(null)
162
+ const docxViewerRef = ref<InstanceType<typeof DocxJsViewer> | null>(null)
163
+ const previewCurrentPage = ref(1)
164
+ const previewTotalPages = ref(0)
165
+ const searchQuery = ref('')
166
+ const searchMatchIndex = ref(-1)
167
+ const searchMatchTotal = ref(0)
168
+ const isPreviewLoading = ref(false)
169
+ const loadingPhase = ref<'fetch' | 'open'>('fetch')
170
+
171
+ type RichViewer = {
172
+ totalPages?: number
173
+ scrollToPage?: (page: number) => Promise<void>
174
+ runSearch?: (query: string) => Promise<{ total: number; index: number } | undefined>
175
+ clearSearch?: () => void
176
+ gotoSearchMatch?: (direction: 1 | -1) => Promise<{ total: number; index: number } | undefined>
177
+ refitLayout?: () => void
178
+ }
179
+
180
+ const loadingMessage = computed(() =>
181
+ loadingPhase.value === 'fetch' ? '正在获取文档,请稍候…' : '正在打开文档,请稍候…',
182
+ )
183
+
184
+ const officeType = computed<OfficePreviewType>(() =>
185
+ toOfficePreviewType(props.fileType, props.fileName, props.url),
186
+ )
187
+
188
+ const isLegacyDoc = computed(() =>
189
+ isLegacyDocFile(props.fileType, props.fileName, props.url),
190
+ )
191
+
192
+ const isRichPreview = computed(
193
+ () =>
194
+ officeType.value === 'pdf' ||
195
+ officeType.value === 'excel' ||
196
+ officeType.value === 'docx',
197
+ )
198
+
199
+ const isUnsupportedType = computed(
200
+ () => !!officeType.value && !isSupportedPreviewType(officeType.value),
201
+ )
202
+
203
+ const unsupportedMessage = computed(() => {
204
+ const ext = officeType.value || '未知'
205
+ return `暂不支持在线预览 ${ext} 格式,请下载后查看`
206
+ })
207
+
208
+ const headerTitle = computed(() => props.fileName || '')
209
+
210
+ const activeViewer = computed<RichViewer | null>(() => {
211
+ if (officeType.value === 'pdf' || previewAsPdf.value) return pdfViewerRef.value
212
+ if (officeType.value === 'excel') return excelViewerRef.value
213
+ if (officeType.value === 'docx') return docxViewerRef.value
214
+ return null
215
+ })
216
+
217
+ const resetPreview = () => {
218
+ fileSrc.value = undefined
219
+ txtContent.value = ''
220
+ previewAsPdf.value = false
221
+ scale.value = 1
222
+ previewCurrentPage.value = 1
223
+ previewTotalPages.value = 0
224
+ searchQuery.value = ''
225
+ searchMatchIndex.value = -1
226
+ searchMatchTotal.value = 0
227
+ isPreviewLoading.value = false
228
+ loadingPhase.value = 'fetch'
229
+ pdfViewerRef.value?.clearSearch()
230
+ excelViewerRef.value?.clearSearch()
231
+ docxViewerRef.value?.clearSearch()
232
+ }
233
+
234
+ const loadPreview = async () => {
235
+ if (!props.url || !officeType.value) {
236
+ return
237
+ }
238
+ if (isUnsupportedType.value) {
239
+ isPreviewLoading.value = false
240
+ emit('loaded')
241
+ return
242
+ }
243
+ isPreviewLoading.value = true
244
+ loadingPhase.value = 'fetch'
245
+ try {
246
+ if (isLegacyDoc.value) {
247
+ if (!props.fileId) {
248
+ isPreviewLoading.value = false
249
+ ElMessage.warning('无法预览该 .doc 文件,请下载后查看')
250
+ emit('loaded')
251
+ return
252
+ }
253
+ loadingPhase.value = 'open'
254
+ const pdfBlob = await documentApi.previewWordAsPdf(props.fileId)
255
+ previewAsPdf.value = true
256
+ fileSrc.value = pdfBlob
257
+ return
258
+ }
259
+ const res = await request<Blob>({
260
+ url: toSameOriginFileUrl(props.url),
261
+ method: 'get',
262
+ responseType: 'blob',
263
+ })
264
+ if (officeType.value === 'txt') {
265
+ txtContent.value = await res.data.text()
266
+ isPreviewLoading.value = false
267
+ emit('loaded')
268
+ } else {
269
+ loadingPhase.value = 'open'
270
+ fileSrc.value = res.data
271
+ }
272
+ } catch (err) {
273
+ isPreviewLoading.value = false
274
+ frameworkUtils.messageError(err)
275
+ emit('error', err)
276
+ }
277
+ }
278
+
279
+ const handleRendered = async () => {
280
+ await nextTick()
281
+ previewTotalPages.value = Number(activeViewer.value?.totalPages ?? 0)
282
+ isPreviewLoading.value = false
283
+ emit('loaded')
284
+ }
285
+
286
+ const handleError = (err?: unknown) => {
287
+ isPreviewLoading.value = false
288
+ emit('error', err)
289
+ }
290
+
291
+ const zoomIn = () => {
292
+ scale.value = clampRichPreviewZoom(scale.value + OFFICE_ZOOM_STEP)
293
+ }
294
+
295
+ const zoomOut = () => {
296
+ scale.value = clampRichPreviewZoom(scale.value - OFFICE_ZOOM_STEP)
297
+ }
298
+
299
+ const resetZoom = () => {
300
+ scale.value = 1
301
+ }
302
+
303
+ const goPrevPage = () => {
304
+ void activeViewer.value?.scrollToPage?.(previewCurrentPage.value - 1)
305
+ }
306
+
307
+ const goNextPage = () => {
308
+ void activeViewer.value?.scrollToPage?.(previewCurrentPage.value + 1)
309
+ }
310
+
311
+ const handleSearch = async (query: string) => {
312
+ searchQuery.value = query
313
+ if (!query) {
314
+ handleSearchClear()
315
+ return
316
+ }
317
+ const result = await activeViewer.value?.runSearch?.(query)
318
+ if (!result || result.total === 0) {
319
+ searchMatchIndex.value = -1
320
+ searchMatchTotal.value = 0
321
+ ElMessage.warning('未找到匹配内容')
322
+ return
323
+ }
324
+ searchMatchIndex.value = result.index
325
+ searchMatchTotal.value = result.total
326
+ }
327
+
328
+ const handleSearchClear = () => {
329
+ searchQuery.value = ''
330
+ searchMatchIndex.value = -1
331
+ searchMatchTotal.value = 0
332
+ activeViewer.value?.clearSearch?.()
333
+ }
334
+
335
+ const goPrevSearchMatch = async () => {
336
+ const result = await activeViewer.value?.gotoSearchMatch?.(-1)
337
+ if (!result) return
338
+ searchMatchIndex.value = result.index
339
+ searchMatchTotal.value = result.total
340
+ }
341
+
342
+ const goNextSearchMatch = async () => {
343
+ const result = await activeViewer.value?.gotoSearchMatch?.(1)
344
+ if (!result) return
345
+ searchMatchIndex.value = result.index
346
+ searchMatchTotal.value = result.total
347
+ }
348
+
349
+ watch(
350
+ () => [props.url, props.fileName, props.fileType] as const,
351
+ ([fileUrl]) => {
352
+ resetPreview()
353
+ if (fileUrl) {
354
+ loadPreview()
355
+ }
356
+ },
357
+ { immediate: true },
358
+ )
359
+
360
+ watch(
361
+ () => props.fullscreen,
362
+ () => {
363
+ void nextTick(() => {
364
+ activeViewer.value?.refitLayout?.()
365
+ })
366
+ },
367
+ )
368
+
369
+ defineExpose({
370
+ zoomIn,
371
+ zoomOut,
372
+ resetZoom,
373
+ officeType,
374
+ })
375
+ </script>
376
+ <style scoped lang="scss">
377
+ .office-preview-root {
378
+ width: 100%;
379
+ height: 100%;
380
+ min-height: 0;
381
+ overflow: auto;
382
+ background: #f5f5f5;
383
+
384
+ &.is-rich-preview {
385
+ display: flex;
386
+ flex-direction: column;
387
+ overflow: hidden;
388
+ }
389
+ }
390
+
391
+ .office-rich-viewer {
392
+ flex: 1;
393
+ width: 100%;
394
+ min-height: 0;
395
+ }
396
+
397
+ .office-rich-wrap {
398
+ position: relative;
399
+ flex: 1;
400
+ width: 100%;
401
+ min-height: 0;
402
+ display: flex;
403
+ flex-direction: column;
404
+ }
405
+
406
+ .office-rich-loading {
407
+ position: absolute;
408
+ inset: 0;
409
+ z-index: 3;
410
+ display: flex;
411
+ align-items: center;
412
+ justify-content: center;
413
+ background: #f5f5f5;
414
+ }
415
+
416
+ .loading-card {
417
+ display: flex;
418
+ flex-direction: column;
419
+ align-items: center;
420
+ gap: 12px;
421
+ padding: 28px 36px;
422
+ border-radius: 12px;
423
+ background: #fff;
424
+ box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
425
+ }
426
+
427
+ .loading-spinner {
428
+ width: 36px;
429
+ height: 36px;
430
+ border: 3px solid #e5e7eb;
431
+ border-top-color: #409eff;
432
+ border-radius: 50%;
433
+ animation: office-preview-spin 0.9s linear infinite;
434
+ }
435
+
436
+ .loading-title {
437
+ margin: 0;
438
+ font-size: 14px;
439
+ font-weight: 600;
440
+ color: #374151;
441
+ }
442
+
443
+ .loading-sub {
444
+ margin: 0;
445
+ max-width: 280px;
446
+ font-size: 12px;
447
+ color: #9ca3af;
448
+ text-align: center;
449
+ white-space: nowrap;
450
+ overflow: hidden;
451
+ text-overflow: ellipsis;
452
+ }
453
+
454
+ @keyframes office-preview-spin {
455
+ to {
456
+ transform: rotate(360deg);
457
+ }
458
+ }
459
+
460
+ .txt-preview {
461
+ height: 100%;
462
+ margin: 0;
463
+ padding: 16px;
464
+ overflow: auto;
465
+ text-align: left;
466
+ white-space: pre-wrap;
467
+ word-break: break-word;
468
+ background: #fff;
469
+ font-size: 14px;
470
+ line-height: 1.6;
471
+ }
472
+
473
+ .unsupported-preview {
474
+ display: flex;
475
+ align-items: center;
476
+ justify-content: center;
477
+ height: 100%;
478
+ min-height: 240px;
479
+ background: #fff;
480
+ }
481
+ </style>