vue3-components-plus 3.0.30 → 3.0.33

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.
@@ -1,590 +0,0 @@
1
- <template>
2
- <div class="pdf-reader-page">
3
- <div class="toolbar">
4
- <div class="file-info">
5
- <strong>{{ fileTitle }}</strong>
6
- <span>{{ statusText }}</span>
7
- </div>
8
- <button type="button" :disabled="isPrintDisabled" @click="printPdf">
9
- {{ isPrinting ? '等待渲染...' : '打印 PDF 区域' }}
10
- </button>
11
- </div>
12
-
13
- <div ref="pdfWrapperRef" class="pdf-wrapper">
14
- <div v-for="pdf in pdfFiles" :key="pdf.id" class="pdf-item">
15
- <div class="pdf-item-header">
16
- <strong>{{ pdf.name }}</strong>
17
- <span>{{ getPdfStatusText(pdf) }}</span>
18
- </div>
19
-
20
- <PDF
21
- v-if="pdf.data"
22
- :key="pdf.viewerKey"
23
- :src="pdf.data"
24
- pdf-width="100%"
25
- :row-gap="12"
26
- :show-progress="true"
27
- :show-page-tooltip="true"
28
- :show-back-to-top-btn="false"
29
- @onPdfInit="handlePdfInit(pdf, $event)"
30
- @onProgress="handleProgress(pdf, $event)"
31
- @onComplete="handleDownloadComplete(pdf)"
32
- />
33
-
34
- <div v-else class="loading-state">
35
- {{ getPdfStatusText(pdf) }}
36
- </div>
37
- </div>
38
- </div>
39
- </div>
40
- </template>
41
-
42
- <script setup lang="ts">
43
- import { computed, nextTick, onBeforeMount, onMounted, ref } from 'vue'
44
- import PDF from 'pdf-vue3'
45
-
46
- interface PdfFileState {
47
- id: number
48
- name: string
49
- url: string
50
- delay: number
51
- data: Uint8Array | null
52
- totalPages: number
53
- progress: number
54
- isDownloadComplete: boolean
55
- error: string
56
- viewerKey: number
57
- }
58
-
59
- const fileTitle = '多个 PDF 异步加载打印测试'
60
- const pdfWrapperRef = ref<HTMLElement | null>(null)
61
- const isPrinting = ref(false)
62
- const pdfAssetUrl = new URL('../assets/驿云通 (11).pdf', import.meta.url).href
63
-
64
- const pdfFiles = ref<PdfFileState[]>([
65
- createPdfFile(1, '异步 PDF 1 - 驿云通 (11).pdf', 300),
66
- createPdfFile(2, '异步 PDF 2 - 驿云通 (11).pdf', 1200),
67
- createPdfFile(3, '异步 PDF 3 - 驿云通 (11).pdf', 2200),
68
- ])
69
-
70
- const loadedPdfCount = computed(() => pdfFiles.value.filter((pdf) => pdf.data && !pdf.error).length)
71
- const readyPdfCount = computed(
72
- () =>
73
- pdfFiles.value.filter(
74
- (pdf) => pdf.data && pdf.totalPages > 0 && pdf.isDownloadComplete && !pdf.error,
75
- ).length,
76
- )
77
- const totalPages = computed(() =>
78
- pdfFiles.value.reduce((pageCount, pdf) => pageCount + pdf.totalPages, 0),
79
- )
80
- const hasLoadError = computed(() => pdfFiles.value.some((pdf) => pdf.error))
81
- const isAllPdfReady = computed(
82
- () =>
83
- pdfFiles.value.length > 0 &&
84
- pdfFiles.value.every(
85
- (pdf) => pdf.data && pdf.totalPages > 0 && pdf.isDownloadComplete && !pdf.error,
86
- ),
87
- )
88
- const isPrintDisabled = computed(() => isPrinting.value || !isAllPdfReady.value)
89
-
90
- const statusText = computed(() => {
91
- if (hasLoadError.value) {
92
- return '有 PDF 加载失败,请查看单个文件状态'
93
- }
94
-
95
- if (isPrinting.value) {
96
- return '正在等待全部 PDF 页面渲染完成'
97
- }
98
-
99
- if (!isAllPdfReady.value) {
100
- return `异步加载中:${readyPdfCount.value}/${pdfFiles.value.length} 个 PDF 就绪`
101
- }
102
-
103
- return `已就绪:${loadedPdfCount.value} 个 PDF,共 ${totalPages.value} 页`
104
- })
105
-
106
- onBeforeMount(() => {
107
- resetLegacyPdfWorker()
108
- })
109
-
110
- onMounted(() => {
111
- void loadPdfFiles()
112
- })
113
-
114
- function createPdfFile(id: number, name: string, delay: number): PdfFileState {
115
- return {
116
- id,
117
- name,
118
- url: pdfAssetUrl,
119
- delay,
120
- data: null,
121
- totalPages: 0,
122
- progress: 0,
123
- isDownloadComplete: false,
124
- error: '',
125
- viewerKey: 0,
126
- }
127
- }
128
-
129
- async function loadPdfFiles() {
130
- await Promise.all(pdfFiles.value.map((pdf) => loadLocalPdf(pdf)))
131
- }
132
-
133
- async function loadLocalPdf(pdf: PdfFileState) {
134
- pdf.error = ''
135
- pdf.progress = 0
136
- pdf.totalPages = 0
137
- pdf.isDownloadComplete = false
138
- pdf.data = null
139
-
140
- try {
141
- await sleep(pdf.delay)
142
- pdf.progress = 10
143
- resetLegacyPdfWorker()
144
-
145
- const response = await fetch(pdf.url)
146
- if (!response.ok) {
147
- throw new Error(`HTTP ${response.status}`)
148
- }
149
-
150
- pdf.progress = 60
151
- const buffer = await response.arrayBuffer()
152
-
153
- resetLegacyPdfWorker()
154
- pdf.viewerKey += 1
155
- pdf.data = new Uint8Array(buffer)
156
- pdf.progress = 100
157
- } catch (error) {
158
- console.error(error)
159
- pdf.error = 'PDF 文件读取失败'
160
- }
161
- }
162
-
163
- function resetLegacyPdfWorker() {
164
- delete (window as Window & { pdfjsWorker?: unknown }).pdfjsWorker
165
- }
166
-
167
- function getPdfStatusText(pdf: PdfFileState) {
168
- if (pdf.error) {
169
- return pdf.error
170
- }
171
-
172
- if (!pdf.data) {
173
- return pdf.progress > 0 ? `异步读取中 ${Math.floor(pdf.progress)}%` : '等待异步加载'
174
- }
175
-
176
- if (!pdf.totalPages) {
177
- return `解析中 ${Math.floor(pdf.progress)}%`
178
- }
179
-
180
- if (!pdf.isDownloadComplete) {
181
- return `渲染中 ${Math.floor(pdf.progress)}%`
182
- }
183
-
184
- return `${pdf.totalPages} 页,已渲染`
185
- }
186
-
187
- function handlePdfInit(file: PdfFileState, pdf: { numPages?: number }) {
188
- file.totalPages = pdf.numPages ?? 0
189
- }
190
-
191
- function handleProgress(file: PdfFileState, progress: number) {
192
- file.progress = progress
193
- }
194
-
195
- function handleDownloadComplete(file: PdfFileState) {
196
- file.isDownloadComplete = true
197
- file.progress = 100
198
- }
199
-
200
- async function printPdf() {
201
- if (isPrinting.value) return
202
-
203
- isPrinting.value = true
204
-
205
- try {
206
- await waitForAllPagesRendered()
207
- await printWrapperCanvases()
208
- } catch (error) {
209
- console.error(error)
210
- window.alert('PDF 页面加载超时,请稍后再试')
211
- } finally {
212
- isPrinting.value = false
213
- }
214
- }
215
-
216
- async function waitForAllPagesRendered(timeout = 30000) {
217
- const startTime = Date.now()
218
-
219
- while (Date.now() - startTime < timeout) {
220
- await nextTick()
221
- await waitFrame()
222
-
223
- if (isAllPdfReady.value && totalPages.value > 0 && areAllCanvasesPainted()) {
224
- await waitForStableLayout()
225
- return
226
- }
227
-
228
- await sleep(100)
229
- }
230
-
231
- throw new Error('PDF render timeout')
232
- }
233
-
234
- async function waitForStableLayout() {
235
- let previousHeight = -1
236
-
237
- for (let index = 0; index < 5; index += 1) {
238
- await waitFrame()
239
- await sleep(80)
240
-
241
- const currentHeight = getCanvasContainerHeight()
242
- if (currentHeight > 0 && currentHeight === previousHeight) {
243
- await waitFrame()
244
- await waitFrame()
245
- return
246
- }
247
-
248
- previousHeight = currentHeight
249
- }
250
- }
251
-
252
- function areAllCanvasesPainted() {
253
- const canvases = getCanvases()
254
-
255
- return (
256
- canvases.length >= totalPages.value &&
257
- canvases.every((canvas) => canvas.width > 0 && canvas.height > 0 && isCanvasPainted(canvas))
258
- )
259
- }
260
-
261
- function getCanvases() {
262
- return Array.from(
263
- pdfWrapperRef.value?.querySelectorAll<HTMLCanvasElement>(
264
- '.pdf-vue3-canvas-container canvas',
265
- ) ?? [],
266
- )
267
- }
268
-
269
- async function printWrapperCanvases() {
270
- const canvases = getCanvases()
271
- if (!canvases.length) {
272
- throw new Error('No PDF canvas found')
273
- }
274
-
275
- const printFrame = document.createElement('iframe')
276
- printFrame.title = 'pdf-wrapper-print'
277
- printFrame.style.position = 'fixed'
278
- printFrame.style.right = '0'
279
- printFrame.style.bottom = '0'
280
- printFrame.style.width = '0'
281
- printFrame.style.height = '0'
282
- printFrame.style.border = '0'
283
- document.body.append(printFrame)
284
-
285
- const printDocument = printFrame.contentDocument
286
- const printWindow = printFrame.contentWindow
287
- if (!printDocument || !printWindow) {
288
- printFrame.remove()
289
- throw new Error('Print frame unavailable')
290
- }
291
-
292
- const pages = canvases
293
- .map((canvas) => canvas.toDataURL('image/png'))
294
- .map((src) => `<img class="pdf-page" src="${src}" alt="" />`)
295
- .join('')
296
-
297
- printDocument.open()
298
- printDocument.write(`
299
- <!doctype html>
300
- <html>
301
- <head>
302
- <meta charset="utf-8" />
303
- <title>PDF Print</title>
304
- <style>
305
- @page {
306
- margin: 0;
307
- }
308
-
309
- html,
310
- body {
311
- margin: 0;
312
- padding: 0;
313
- background: #fff;
314
- }
315
-
316
- .pdf-page {
317
- display: block;
318
- width: 100%;
319
- height: auto;
320
- break-after: page;
321
- page-break-after: always;
322
- }
323
-
324
- .pdf-page:last-child {
325
- break-after: auto;
326
- page-break-after: auto;
327
- }
328
- </style>
329
- </head>
330
- <body>${pages}</body>
331
- </html>
332
- `)
333
- printDocument.close()
334
-
335
- await waitForPrintFrameImages(printDocument)
336
- printWindow.focus()
337
- printWindow.print()
338
-
339
- window.setTimeout(() => {
340
- printFrame.remove()
341
- }, 1000)
342
- }
343
-
344
- function waitForPrintFrameImages(printDocument: Document) {
345
- const images = Array.from(printDocument.images)
346
-
347
- return Promise.all(
348
- images.map((image) => {
349
- if (image.complete) {
350
- return Promise.resolve()
351
- }
352
-
353
- return new Promise<void>((resolve, reject) => {
354
- image.onload = () => resolve()
355
- image.onerror = () => reject(new Error('Print image failed to load'))
356
- })
357
- }),
358
- )
359
- }
360
-
361
- function getCanvasContainerHeight() {
362
- return pdfWrapperRef.value?.querySelector('.pdf-vue3-canvas-container')?.scrollHeight ?? 0
363
- }
364
-
365
- function isCanvasPainted(canvas: HTMLCanvasElement) {
366
- const context = canvas.getContext('2d')
367
- if (!context) return false
368
-
369
- const points = [
370
- [0.5, 0.5],
371
- [0.25, 0.25],
372
- [0.75, 0.25],
373
- [0.25, 0.75],
374
- [0.75, 0.75],
375
- ]
376
-
377
- try {
378
- return points.some(([xRatio, yRatio]) => {
379
- const x = Math.max(0, Math.min(canvas.width - 1, Math.floor(canvas.width * xRatio)))
380
- const y = Math.max(0, Math.min(canvas.height - 1, Math.floor(canvas.height * yRatio)))
381
- return context.getImageData(x, y, 1, 1).data[3] > 0
382
- })
383
- } catch {
384
- return canvas.width > 0 && canvas.height > 0
385
- }
386
- }
387
-
388
- function waitFrame() {
389
- return new Promise<void>((resolve) => {
390
- requestAnimationFrame(() => resolve())
391
- })
392
- }
393
-
394
- function sleep(duration: number) {
395
- return new Promise<void>((resolve) => {
396
- window.setTimeout(resolve, duration)
397
- })
398
- }
399
- </script>
400
-
401
- <style scoped lang="scss">
402
- .pdf-reader-page {
403
- display: flex;
404
- flex-direction: column;
405
- height: 100%;
406
- min-height: 720px;
407
- background: #f3f5f8;
408
- }
409
-
410
- .toolbar {
411
- flex: none;
412
- display: flex;
413
- align-items: center;
414
- justify-content: space-between;
415
- gap: 16px;
416
- padding: 14px 18px;
417
- border-bottom: 1px solid #d8dee8;
418
- background: #ffffff;
419
-
420
- .file-info {
421
- display: flex;
422
- flex-direction: column;
423
- gap: 4px;
424
- min-width: 0;
425
- color: #1f2937;
426
-
427
- strong,
428
- span {
429
- overflow: hidden;
430
- text-overflow: ellipsis;
431
- white-space: nowrap;
432
- }
433
-
434
- span {
435
- color: #64748b;
436
- font-size: 13px;
437
- }
438
- }
439
-
440
- button {
441
- flex: none;
442
- min-width: 88px;
443
- height: 36px;
444
- padding: 0 16px;
445
- border: 1px solid #2563eb;
446
- border-radius: 4px;
447
- background: #2563eb;
448
- color: #ffffff;
449
- cursor: pointer;
450
-
451
- &:disabled {
452
- border-color: #b8c2d6;
453
- background: #b8c2d6;
454
- cursor: not-allowed;
455
- }
456
- }
457
- }
458
-
459
- .pdf-wrapper {
460
- flex: 1;
461
- min-height: 0;
462
- overflow: auto;
463
- }
464
-
465
- .pdf-item {
466
- background: #e9edf3;
467
-
468
- & + & {
469
- border-top: 1px solid #cbd5e1;
470
- }
471
- }
472
-
473
- .pdf-item-header {
474
- position: sticky;
475
- top: 0;
476
- z-index: 1;
477
- display: flex;
478
- align-items: center;
479
- justify-content: space-between;
480
- gap: 16px;
481
- padding: 10px 18px;
482
- border-bottom: 1px solid #d8dee8;
483
- background: #ffffff;
484
- color: #1f2937;
485
-
486
- strong,
487
- span {
488
- overflow: hidden;
489
- text-overflow: ellipsis;
490
- white-space: nowrap;
491
- }
492
-
493
- span {
494
- flex: none;
495
- color: #64748b;
496
- font-size: 13px;
497
- }
498
- }
499
-
500
- .loading-state {
501
- display: flex;
502
- align-items: center;
503
- justify-content: center;
504
- height: 100%;
505
- min-height: 360px;
506
- color: #475569;
507
- font-size: 14px;
508
- }
509
-
510
- :deep(.pdf-vue3-scroller) {
511
- background: #e9edf3;
512
- }
513
-
514
- :deep(.pdf-vue3-canvas-container) {
515
- padding: 18px 0;
516
- }
517
-
518
- @media (max-width: 768px) {
519
- .pdf-reader-page {
520
- min-height: 100vh;
521
- }
522
-
523
- .toolbar {
524
- align-items: stretch;
525
- flex-direction: column;
526
-
527
- button {
528
- width: 100%;
529
- }
530
- }
531
- }
532
-
533
- @media print {
534
- .pdf-reader-page {
535
- display: block;
536
- height: auto;
537
- min-height: auto;
538
- background: #ffffff;
539
- }
540
-
541
- .toolbar {
542
- display: none !important;
543
- }
544
-
545
- .pdf-item {
546
- background: #ffffff !important;
547
- break-after: page;
548
- page-break-after: always;
549
-
550
- &:last-child {
551
- break-after: auto;
552
- page-break-after: auto;
553
- }
554
- }
555
-
556
- .pdf-item-header {
557
- display: none !important;
558
- }
559
-
560
- .pdf-wrapper,
561
- :deep(.pdf-vue3-main),
562
- :deep(.pdf-vue3-container),
563
- :deep(.pdf-vue3-scroller) {
564
- height: auto !important;
565
- max-height: none !important;
566
- overflow: visible !important;
567
- background: #ffffff !important;
568
- }
569
-
570
- :deep(.pdf-vue3-canvas-container) {
571
- width: 100% !important;
572
- padding: 0 !important;
573
- }
574
-
575
- :deep(.pdf-vue3-canvas-container canvas) {
576
- width: 100% !important;
577
- height: auto !important;
578
- margin: 0 auto !important;
579
- box-shadow: none !important;
580
- break-after: page;
581
- page-break-after: always;
582
- }
583
-
584
- :deep(.pdf-vue3-progress),
585
- :deep(.pdf-vue3-pageTooltip),
586
- :deep(.pdf-vue3-backToTopBtn) {
587
- display: none !important;
588
- }
589
- }
590
- </style>
package/dist/favicon.ico DELETED
Binary file