@epxing/core 5.3.0-beta.2 → 5.3.0-beta.4

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 (100) hide show
  1. package/API.md +715 -714
  2. package/README.md +3 -1
  3. package/dist/.cache/route-chart-release/frontend/frame/public/js/ecStat.min.js +4 -0
  4. package/dist/.cache/route-chart-release/frontend/frame/public/js/echarts-wordcloud.min.js +6 -0
  5. package/dist/.cache/route-chart-release/frontend/frame/public/js/echarts.min.js +49 -0
  6. package/dist/_virtual/_plugin-vue_export-helper.js +10 -0
  7. package/dist/_virtual/jsencrypt.min.js +1 -1
  8. package/dist/assets/role_select-DTqRlFNz.webp +0 -0
  9. package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/jsencrypt@3.5.4/_vendor/jsencrypt/bin/jsencrypt.min.js +1 -1
  10. package/dist/src/RootApp.js +54 -0
  11. package/dist/src/api/createHttp.js +6 -0
  12. package/dist/src/app-ui-settings.js +49 -0
  13. package/dist/src/axios.js +10 -15
  14. package/dist/src/chart-runtime-assets.js +19 -0
  15. package/dist/src/chart-runtime-sources.js +8 -0
  16. package/dist/src/create-app.js +3 -6
  17. package/dist/src/index.js +2 -0
  18. package/dist/src/loadComponent.js +2 -2
  19. package/dist/src/menu-route.js +11 -0
  20. package/dist/src/mixins/component.js +3 -0
  21. package/dist/src/navigation-context.js +47 -0
  22. package/dist/src/navigation.js +43 -12
  23. package/dist/src/package-install.js +3 -2
  24. package/dist/src/plugins/element-plus.js +8 -8
  25. package/dist/src/public-api.js +4 -2
  26. package/dist/src/route-components.js +9 -0
  27. package/dist/src/router/config.js +7 -8
  28. package/dist/src/runtime-services.js +1 -1
  29. package/dist/src/runtime-utils/meta.js +3 -0
  30. package/dist/src/runtime-utils/pdf.viewer.js +31 -15
  31. package/dist/src/runtime-utils/util.js +28 -5
  32. package/dist/src/shared-packages.js +1 -3
  33. package/dist/src/store/modules/list.js +25 -3
  34. package/dist/src/store/modules/meta.js +49 -7
  35. package/dist/src/style-presets.js +4 -4
  36. package/dist/src/ui-settings.js +90 -18
  37. package/dist/src/views/App.js +48 -0
  38. package/dist/src/views/AppRoleSelect.js +233 -0
  39. package/dist/src/views/Error.js +210 -0
  40. package/dist/src/views/Module.js +173 -0
  41. package/dist/style.css +251 -0
  42. package/package.json +4 -4
  43. package/src/RootApp.vue +75 -0
  44. package/src/api/createHttp.js +7 -0
  45. package/src/api/index.js +47 -47
  46. package/src/app-ui-settings.js +31 -0
  47. package/src/axios.js +311 -318
  48. package/src/chart-runtime-assets.js +13 -0
  49. package/src/chart-runtime-sources.js +5 -0
  50. package/src/create-app.js +3 -7
  51. package/src/index.js +28 -28
  52. package/src/install.js +157 -157
  53. package/src/loadComponent.js +187 -187
  54. package/src/loadLibrary.js +99 -99
  55. package/src/menu-route.js +12 -1
  56. package/src/mixins/component.js +1464 -1461
  57. package/src/navigation-context.js +49 -0
  58. package/src/navigation.js +208 -176
  59. package/src/package-install.js +2 -1
  60. package/src/plugins/element-plus.js +302 -302
  61. package/src/plugins/frame.js +178 -178
  62. package/src/public-api.js +1 -0
  63. package/src/route-components.js +7 -0
  64. package/src/router/config.js +861 -864
  65. package/src/router/public.js +1 -1
  66. package/src/runtime-services.js +1 -1
  67. package/src/runtime-utils/fieldFactory.js +179 -179
  68. package/src/runtime-utils/meta.js +399 -378
  69. package/src/runtime-utils/pdf.viewer.js +672 -644
  70. package/src/runtime-utils/util.js +1359 -1330
  71. package/src/shared-packages.js +1 -3
  72. package/src/store/modules/config.js +351 -351
  73. package/src/store/modules/list.js +30 -3
  74. package/src/store/modules/locale.js +236 -236
  75. package/src/store/modules/meta.js +314 -265
  76. package/src/style-presets.js +4 -4
  77. package/src/ui-settings.js +84 -23
  78. package/src/views/App.vue +53 -0
  79. package/src/views/AppRoleSelect.vue +334 -0
  80. package/src/views/Error.vue +63 -0
  81. package/src/views/Module.vue +210 -0
  82. package/src/xss.js +26 -26
  83. package/types/app.d.ts +10 -10
  84. package/types/axios.d.ts +1 -1
  85. package/types/component-mixin.d.ts +1 -1
  86. package/types/field-factory.d.ts +1 -1
  87. package/types/global-mixin.d.ts +1 -1
  88. package/types/http.d.ts +1 -1
  89. package/types/index.d.ts +3 -0
  90. package/types/meta.d.ts +1 -1
  91. package/types/navigation.d.ts +21 -21
  92. package/types/pdf-viewer.d.ts +1 -1
  93. package/types/runtime.d.ts +13 -13
  94. package/types/store.d.ts +1 -1
  95. package/types/util.d.ts +2 -2
  96. /package/dist/{build → .cache/route-chart-release/frontend/frame/build}/stubs/empty-components.js +0 -0
  97. /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-icons.names.js +0 -0
  98. /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-plus.lazy.js +0 -0
  99. /package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/element-plus@2.14.3_vue@3.5.39_typescript@5.9.3_/_vendor/element-plus/dist/locale/en.js +0 -0
  100. /package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/element-plus@2.14.3_vue@3.5.39_typescript@5.9.3_/_vendor/element-plus/dist/locale/zh-cn.js +0 -0
@@ -1,644 +1,672 @@
1
- import util from '@epxing/core/src/runtime-utils/util'
2
- const DEFAULT_CACHE_SIZE = 10
3
-
4
- function binarySearchFirstItem (items, condition) {
5
- var minIndex = 0
6
- var maxIndex = items.length - 1
7
-
8
- if (items.length === 0 || !condition(items[maxIndex])) {
9
- return items.length
10
- }
11
-
12
- if (condition(items[minIndex])) {
13
- return minIndex
14
- }
15
-
16
- while (minIndex < maxIndex) {
17
- var currentIndex = minIndex + maxIndex >> 1
18
- var currentItem = items[currentIndex]
19
-
20
- if (condition(currentItem)) {
21
- maxIndex = currentIndex
22
- } else {
23
- minIndex = currentIndex + 1
24
- }
25
- }
26
-
27
- return minIndex
28
- }
29
- function backtrackBeforeAllVisibleElements (index, views, top) {
30
- if (index < 2) {
31
- return index
32
- }
33
-
34
- var elt = views[index].div
35
- var pageTop = elt.offsetTop + elt.clientTop
36
-
37
- if (pageTop >= top) {
38
- elt = views[index - 1].div
39
- pageTop = elt.offsetTop + elt.clientTop
40
- }
41
-
42
- for (var i = index - 2; i >= 0; --i) {
43
- elt = views[i].div
44
-
45
- if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
46
- break
47
- }
48
-
49
- index = i
50
- }
51
-
52
- return index
53
- }
54
-
55
- function getVisibleElements (scrollEl, views) {
56
- var sortByVisibility = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false
57
- var horizontal = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false
58
- var top = scrollEl.scrollTop
59
- var bottom = top + scrollEl.clientHeight
60
- var left = scrollEl.scrollLeft
61
- var right = left + scrollEl.clientWidth
62
-
63
- function isElementBottomAfterViewTop (view) {
64
- var element = view.div
65
- var elementBottom = element.offsetTop + element.clientTop + element.clientHeight
66
- return elementBottom > top
67
- }
68
-
69
- function isElementRightAfterViewLeft (view) {
70
- var element = view.div
71
- var elementRight = element.offsetLeft + element.clientLeft + element.clientWidth
72
- return elementRight > left
73
- }
74
-
75
- var visible = []
76
- var numViews = views.length
77
- var firstVisibleElementInd = numViews === 0 ? 0 : binarySearchFirstItem(views, horizontal ? isElementRightAfterViewLeft : isElementBottomAfterViewTop)
78
-
79
- if (firstVisibleElementInd > 0 && firstVisibleElementInd < numViews && !horizontal) {
80
- firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top)
81
- }
82
-
83
- var lastEdge = horizontal ? right : -1
84
-
85
- for (var i = firstVisibleElementInd; i < numViews; i++) {
86
- var view = views[i]
87
- var element = view.div
88
- var currentWidth = element.offsetLeft + element.clientLeft
89
- var currentHeight = element.offsetTop + element.clientTop
90
- var viewWidth = element.clientWidth
91
- var viewHeight = element.clientHeight
92
- var viewRight = currentWidth + viewWidth
93
- var viewBottom = currentHeight + viewHeight
94
-
95
- if (lastEdge === -1) {
96
- if (viewBottom >= bottom) {
97
- lastEdge = viewBottom
98
- }
99
- } else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
100
- break
101
- }
102
-
103
- if (viewBottom <= top || currentHeight >= bottom || viewRight <= left || currentWidth >= right) {
104
- continue
105
- }
106
-
107
- var hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom)
108
- var hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right)
109
- var percent = (viewHeight - hiddenHeight) * (viewWidth - hiddenWidth) * 100 / viewHeight / viewWidth | 0
110
- visible.push({
111
- id: view.id,
112
- x: currentWidth,
113
- y: currentHeight,
114
- view: view,
115
- percent: percent
116
- })
117
- }
118
-
119
- var first = visible[0]
120
- var last = visible[visible.length - 1]
121
-
122
- if (sortByVisibility) {
123
- visible.sort(function (a, b) {
124
- var pc = a.percent - b.percent
125
-
126
- if (Math.abs(pc) > 0.001) {
127
- return -pc
128
- }
129
-
130
- return a.id - b.id
131
- })
132
- }
133
-
134
- return {
135
- first: first,
136
- last: last,
137
- views: visible
138
- }
139
- }
140
- function watchScroll (viewAreaElement, callback) {
141
- var debounceScroll = function debounceScroll (evt) {
142
- if (rAF) {
143
- return
144
- }
145
-
146
- rAF = window.requestAnimationFrame(function viewAreaElementScrolled () {
147
- rAF = null
148
- var currentX = viewAreaElement.scrollLeft
149
- var lastX = state.lastX
150
-
151
- if (currentX !== lastX) {
152
- state.right = currentX > lastX
153
- }
154
-
155
- state.lastX = currentX
156
- var currentY = viewAreaElement.scrollTop
157
- var lastY = state.lastY
158
-
159
- if (currentY !== lastY) {
160
- state.down = currentY > lastY
161
- }
162
-
163
- state.lastY = currentY
164
- callback(state)
165
- })
166
- }
167
-
168
- var state = {
169
- right: true,
170
- down: true,
171
- lastX: viewAreaElement.scrollLeft,
172
- lastY: viewAreaElement.scrollTop,
173
- _eventHandler: debounceScroll
174
- }
175
- var rAF = null
176
- viewAreaElement.addEventListener('scroll', debounceScroll, true)
177
- return state
178
- }
179
-
180
- class PdfViewer {
181
- constructor () {
182
- this.pages = []
183
- this.loadViews = []
184
- this.searchText = ''
185
- this.searchTextArr = []
186
- this.scrollState = null
187
- this.destroyed = false
188
- }
189
-
190
- // 页面 div 是 append 进容器的,实例不销毁就会跟下一份预览叠在一起;
191
- // scroll 监听和 ResizeObserver 也得一并摘掉,否则每预览一次多留一份。
192
- destroy () {
193
- this.destroyed = true
194
- const container = this.pdfConfig && this.pdfConfig.viewerContainer
195
- if (this.resizeObserver) {
196
- this.resizeObserver.disconnect()
197
- this.resizeObserver = null
198
- }
199
- if (container && this.scrollState && this.scrollState._eventHandler) {
200
- container.removeEventListener('scroll', this.scrollState._eventHandler, true)
201
- }
202
- this.scrollState = null
203
- // 只摘自己建的页面节点,容器里可能还有调用方渲染的工具条
204
- for (const item of this.pages) {
205
- if (item.div && item.div.parentNode) item.div.parentNode.removeChild(item.div)
206
- }
207
- this.pages = []
208
- this.loadViews = []
209
- this.searchTextArr = []
210
- if (this.pdfDocument) {
211
- try {
212
- this.pdfDocument.destroy()
213
- } catch (e) {
214
- // 文档还没加载完就 destroy,pdf.js 会抛,忽略
215
- }
216
- this.pdfDocument = null
217
- }
218
- }
219
-
220
- async searchTextInPDF (searchText) {
221
- const numPages = this.pdfDocument.numPages
222
- let currentPage = 1
223
- this.clearHighlights()
224
- this.searchTextArr = []
225
- if (!searchText) return
226
- this.searchText = searchText
227
- const searchPage = async (pageNum) => {
228
- try {
229
- const page = await this.pdfDocument.getPage(pageNum)
230
- const initViewport = page.getViewport({ scale: 1 })
231
- let scale = 1
232
- if (this.pdfConfig.zoom === 'page-width') {
233
- const num = this.pdfConfig.viewerContainer.offsetWidth / initViewport.width
234
- scale = num
235
- // scale = Math.round(this.pdfConfig.viewerContainer.offsetWidth / initViewport.width)
236
- } else {
237
- scale = this.pdfConfig.zoom
238
- }
239
- const viewport = page.getViewport({ scale: 1 })
240
- const textContent = await page.getTextContent()
241
-
242
- const textItems = textContent.items
243
- for (let i = 0; i < textItems.length; i++) {
244
- const textItem = textItems[i]
245
- const text = textItem.str
246
-
247
- // 查找搜索文字在当前文本项中的位置
248
- let startIndex = text.indexOf(searchText)
249
- while (startIndex !== -1) {
250
- // 找到匹配的搜索文字
251
- const endIndex = startIndex + searchText.length
252
- // 计算搜索文字的边界框
253
- const { x, y, width, height } = this.calculateTextBoundingBox(textItem, startIndex, endIndex, viewport, scale)
254
-
255
- this.searchTextArr.push({
256
- pageNum,
257
- x: x * scale,
258
- y: y * scale,
259
- width: width * scale,
260
- height: height * scale
261
- })
262
- // this.searchTextArr.push({
263
- // pageNum,
264
- // x,
265
- // y,
266
- // width,
267
- // height
268
- // })
269
-
270
- // 继续查找下一个匹配
271
- startIndex = text.indexOf(searchText, endIndex)
272
- }
273
- }
274
- } catch (error) {
275
- console.error('Error processing page:', error)
276
- }
277
- }
278
-
279
- // 使用for循环替代递归实现同步执行
280
- for (currentPage = 1; currentPage <= numPages; currentPage++) {
281
- await searchPage(currentPage)
282
- }
283
- if (this.searchTextArr.length > 0) {
284
- this.scrollToPage(this.searchTextArr[0])
285
- }
286
- }
287
-
288
- calculateTextBoundingBox (textItem, startIndex, endIndex, viewport, currentScale) {
289
- const transform = textItem.transform
290
- const textWidth = textItem.width
291
- const textHeight = textItem.height
292
-
293
- // 计算搜索文字的起始位置
294
- const startX = transform[4] + (startIndex / textItem.str.length) * textWidth
295
- const startY = viewport.height - transform[5]
296
-
297
- // 计算搜索文字的宽度
298
- const highlightWidth = ((endIndex - startIndex) / textItem.str.length) * textWidth
299
-
300
- // return {
301
- // x: startX * currentScale, // 乘以当前缩放比例
302
- // y: (startY - textHeight) * currentScale, // 乘以当前缩放比例
303
- // width: highlightWidth * currentScale, // 乘以当前缩放比例
304
- // height: textHeight * currentScale // 乘以当前缩放比例
305
- // }
306
- return {
307
- x: startX,
308
- y: startY - textHeight, // PDF 的 y 坐标是倒置的
309
- width: highlightWidth,
310
- height: textHeight
311
- }
312
- }
313
-
314
- highlightText (pageNum, x, y, width, height) {
315
- const viewer = this.pdfConfig.viewerContainer
316
- const pageElement = viewer.querySelector(`.pdf-viewer-page[data-page-number="${pageNum}"]`)
317
-
318
- if (pageElement) {
319
- const highlight = document.createElement('div')
320
- highlight.className = 'search-highlight' // 添加类名
321
- highlight.style.position = 'absolute'
322
- highlight.style.left = `${x}px`
323
- highlight.style.top = `${y}px`
324
- highlight.style.width = `${width}px`
325
- highlight.style.height = `${height}px`
326
- highlight.style.backgroundColor = 'rgba(255, 255, 0, 0.5)'
327
- highlight.style.pointerEvents = 'none'
328
-
329
- pageElement.appendChild(highlight)
330
- }
331
- }
332
-
333
- clearHighlights () {
334
- const highlights = document.querySelectorAll('.search-highlight')
335
- highlights.forEach(highlight => {
336
- highlight.remove() // 移除高亮元素
337
- })
338
- }
339
-
340
- scrollToPage ({ pageNum, x, y, width, height }) {
341
- this.clearHighlights()
342
- const scrollY = y > 60 ? y - 60 : y
343
- // 来显示 PDF 页面的容器
344
- const viewer = this.pdfConfig.viewerContainer
345
- const pageElement = viewer.querySelector(`.pdf-viewer-page[data-page-number="${pageNum}"]`)
346
- if (pageElement) {
347
- viewer.scrollTo({
348
- top: pageElement.offsetTop + scrollY, // 计算滚动位置
349
- behavior: 'smooth' // 平滑滚动
350
- })
351
- // 高亮显示
352
- this.highlightText(pageNum, x, y, width, height)
353
- }
354
- }
355
-
356
- loadPdf () {
357
- return util.usePlugin('pdf').then(pdf => {
358
- this.pdfJs = pdf
359
- })
360
- }
361
-
362
- async run (config) {
363
- // 加载pdf
364
- this.pdfConfig = {
365
- viewerContainer: config.container,
366
- url: config.url,
367
- cMapUrl: config.cMapUrl || (util.getPublicPath() === './' ? './cmaps/' : util.getPublicPath() + 'cmaps/'),
368
- cMapPacked: config.cMapPacked !== null ? config.cMapPacked : true,
369
- zoom: config.zoom || 'page-width',
370
- pageRotate: 0
371
- }
372
- await this.loadPdf()
373
- return this.pdfJs.getDocument({
374
- url: this.pdfConfig.url,
375
- cMapUrl: this.pdfConfig.cMapUrl,
376
- cMapPacked: this.pdfConfig.cMapPacked
377
- }).promise.then((pdfDocument) => {
378
- this.load(pdfDocument)
379
- })
380
- }
381
-
382
- load (pdfDocument) {
383
- // 文档还在下载时实例已被销毁(快速切换预览),这份结果直接丢掉,不能再往容器里塞页面
384
- if (this.destroyed) {
385
- try {
386
- pdfDocument.destroy()
387
- } catch (e) {
388
- // ignore
389
- }
390
- return
391
- }
392
- this.pdfDocument = pdfDocument
393
- this.pdfDocument.getPage(1).then((firstPage) => {
394
- if (this.destroyed) return
395
- const pagesCount = pdfDocument.numPages
396
- const viewport = firstPage.getViewport({
397
- scale: 1
398
- })
399
- for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
400
- this.createPageView({
401
- container: this.pdfConfig.viewerContainer,
402
- id: pageNum,
403
- viewport: viewport.clone()
404
- })
405
- }
406
- this.scrollState = watchScroll(this.pdfConfig.viewerContainer, this.scrollUpdate.bind(this))
407
- setTimeout(() => {
408
- if (this.destroyed) return
409
- this.scrollUpdate()
410
- this.resizeObserver = new ResizeObserver((entries) => {
411
- this.resetPageStyle()
412
- })
413
- this.resizeObserver.observe(this.pdfConfig.viewerContainer)
414
- }, 10)
415
- })
416
- }
417
-
418
- createPageView ({ container, id }) {
419
- this.pdfDocument.getPage(id).then((page) => {
420
- if (this.destroyed) return
421
- const viewport = page.getViewport({
422
- scale: 1
423
- })
424
- const pageWidth = viewport.width
425
- const pageHeight = viewport.height
426
- const pageRatio = pageWidth / pageHeight
427
- const canvasWidth = container.offsetWidth
428
- const canvasHeight = canvasWidth / pageRatio | 0
429
- const div = document.createElement('div')
430
- div.className = 'pdf-viewer-page'
431
- div.style.width = Math.floor(canvasWidth) + 'px'
432
- div.style.height = Math.floor(canvasHeight) + 'px'
433
- div.setAttribute('data-page-number', id)
434
- const loading = document.createElement('div')
435
- loading.className = 'el-icon-loading pdf-viewer-loading'
436
- div.appendChild(loading)
437
- container.appendChild(div)
438
- this.pages.push({ id, div: div, viewport })
439
- this.viewport = viewport
440
- })
441
- }
442
-
443
- resetPageStyle () {
444
- for (const item of this.pages) {
445
- if (item.div) {
446
- const viewport = item.viewport || this.viewport
447
- const pageWidth = viewport.width
448
- const pageHeight = viewport.height
449
- const pageRatio = pageWidth / pageHeight
450
- const canvasWidth = this.pdfConfig.zoom === 'page-width' ? this.pdfConfig.viewerContainer.offsetWidth : viewport.width * (this.pdfConfig.zoom || 1)
451
- const canvasHeight = canvasWidth / pageRatio | 0
452
- item.div.style.width = Math.floor(canvasWidth) + 'px'
453
- item.div.style.height = Math.floor(canvasHeight) + 'px'
454
- }
455
- }
456
- }
457
-
458
- updatePageView () {
459
- var visible = this.getVisiblePages()
460
- var visiblePages = visible.views
461
- var numVisiblePages = visiblePages.length
462
- if (numVisiblePages === 0) {
463
- return
464
- }
465
- this.resetPageStyle()
466
- var newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1)
467
- this.resizeVisiblePages(newCacheSize, visiblePages)
468
- for (const item of this.loadViews) {
469
- this.destroyPageView(item)
470
- }
471
- this.loadViews = []
472
- this.renderPageView(visible, () => {
473
- setTimeout(() => {
474
- const page = visiblePages[0].view.div
475
- this.pdfConfig.viewerContainer.scrollTop = page.offsetTop
476
- }, 100)
477
- })
478
- }
479
-
480
- scrollUpdate () {
481
- var visible = this.getVisiblePages()
482
- var visiblePages = visible.views
483
- var numVisiblePages = visiblePages.length
484
-
485
- if (numVisiblePages === 0) {
486
- return
487
- }
488
-
489
- var newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1)
490
-
491
- this.resizeVisiblePages(newCacheSize, visiblePages)
492
- this.renderPageView(visible)
493
- }
494
-
495
- getVisiblePages () {
496
- return getVisibleElements(this.pdfConfig.viewerContainer, this.pages)
497
- }
498
-
499
- resizeVisiblePages (newCacheSize) {
500
- if (this.loadViews.length > newCacheSize) {
501
- this.destroyPageView(this.loadViews.shift())
502
- }
503
- }
504
-
505
- destroyPageView ({ id }) {
506
- const page = this.pages.find(item => item.id === id)
507
- const div = page.div
508
- var childNodes = div.childNodes
509
- for (var i = childNodes.length - 1; i >= 0; i--) {
510
- var node = childNodes[i]
511
- if (node.className.includes('pdf-viewer-loading')) {
512
- node.style.display = 'block'
513
- } else {
514
- div.removeChild(node)
515
- }
516
- }
517
- div.removeAttribute('data-loaded')
518
- }
519
-
520
- renderPageView (visible, cb) {
521
- for (const item of visible.views) {
522
- const div = item.view.div
523
- if (div.getAttribute('data-loaded')) continue
524
- div.setAttribute('data-loaded', true)
525
- this.pdfDocument.getPage(item.view.id)
526
- .then((page) => {
527
- const canvas = document.createElement('canvas')
528
- const ctx = canvas.getContext('2d', {
529
- alpha: false
530
- })
531
- const initViewport = page.getViewport({ scale: 1 })
532
- let scale = 1
533
- if (this.pdfConfig.zoom === 'page-width') {
534
- scale = Math.ceil(document.body.offsetWidth / initViewport.width)
535
- } else {
536
- scale = this.pdfConfig.zoom
537
- }
538
- const rotation = (this.pdfConfig.pageRotate + initViewport.rotation) % 360
539
- const viewport = page.getViewport({ scale, rotation })
540
- canvas.width = viewport.width // 画布大小,默认值是width:300px,height:150px
541
- canvas.height = viewport.height
542
- canvas.style.width = '100%' // 画布的框架大小
543
- const renderContext = {
544
- canvasContext: ctx,
545
- viewport: viewport,
546
- enableWebGL: false,
547
- renderInteractiveForms: false
548
- }
549
- var renderTask = page.render(renderContext)
550
- renderTask.promise.then(() => {
551
- const loading = item.view.div.querySelector('.pdf-viewer-loading')
552
- loading.style.display = 'none'
553
- item.view.div.appendChild(canvas)
554
- this.loadViews.push(item.view)
555
- cb && cb()
556
- })
557
- })
558
- }
559
- }
560
-
561
- drawerCanvas (num) {
562
- return new Promise(resolve => {
563
- this.pdfDocument.getPage(num)
564
- .then(pdfPage => {
565
- var canvas = document.createElement('canvas')
566
- const ctx = canvas.getContext('2d')
567
- const initViewport = this.viewport
568
- const scale = Math.ceil(document.body.offsetWidth / initViewport.width)
569
-
570
- const viewport = pdfPage.getViewport({ scale: scale })
571
- canvas.width = viewport.width // 画布大小,默认值是width:300px,height:150px
572
- canvas.height = viewport.height
573
- canvas.style.width = '100%' // 画布的框架大小
574
- const renderContext = {
575
- canvasContext: ctx,
576
- viewport: viewport,
577
- enableWebGL: false,
578
- renderInteractiveForms: false
579
- }
580
- var renderTask = pdfPage.render(renderContext)
581
- renderTask.promise.then(() => {
582
- resolve(canvas)
583
- })
584
- })
585
- })
586
- }
587
-
588
- scaleChanged (scale) {
589
- this.pdfConfig.zoom = scale
590
- this.updatePageView()
591
- }
592
-
593
- rotateChanged (delta) {
594
- this.pdfConfig.pageRotate += delta
595
- const rotation = this.pdfConfig.pageRotate % 360
596
- this.pdfDocument.getPage(1).then((firstPage) => {
597
- this.viewport = firstPage.getViewport({
598
- scale: 1,
599
- rotation
600
- })
601
- this.updatePageView()
602
- })
603
- }
604
-
605
- print () {
606
- const pagesCount = this.pdfDocument.numPages
607
- var iframe = document.getElementById('print-iframe')
608
- if (!iframe) {
609
- iframe = document.createElement('IFRAME')
610
- var doc = null
611
- iframe.setAttribute('id', 'print-iframe')
612
- iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:-500px;top:-500px;')
613
- document.body.appendChild(iframe)
614
- }
615
- doc = iframe.contentWindow.document
616
- doc.body.innerHTML = ''
617
- let pageStyleSheet = null
618
- const drawerCanvas = (pageNum) => {
619
- this.drawerCanvas(pageNum).then(canvas => {
620
- if (!pageStyleSheet) {
621
- pageStyleSheet = document.createElement('style')
622
- pageStyleSheet.textContent = '@supports ((size:A4) and (size:1pt 1pt)) {' + '@page { size: auto;}' + '};' +
623
- '@media print {' +
624
- 'body { margin: 0 }' +
625
- 'canvas { page-break-before: always; page-break-after: always; page-break-inside: avoid }' +
626
- '}'
627
- doc.head.appendChild(pageStyleSheet)
628
- }
629
- doc.body.appendChild(canvas)
630
- if (pageNum >= pagesCount) {
631
- setTimeout(() => {
632
- iframe.contentWindow.focus()
633
- iframe.contentWindow.print()
634
- }, 100)
635
- } else {
636
- drawerCanvas(++pageNum)
637
- }
638
- })
639
- }
640
- drawerCanvas(1)
641
- }
642
- }
643
-
644
- export default PdfViewer
1
+ import util from '@epxing/core/src/runtime-utils/util'
2
+ const DEFAULT_CACHE_SIZE = 10
3
+ // 单页画布的像素上限(与 pdf.js 官方 viewer 的 maxCanvasPixels 默认值一致),高倍率下防止一页画布就吃掉上百 MB
4
+ const MAX_CANVAS_PIXELS = 16777216
5
+
6
+ // 脱离 DOM 的 canvas 位图要等元素本身被 GC 才归还,连续预览多份 PDF 时会堆出上 GB 的待回收位图;
7
+ // 先把尺寸清零能让浏览器立刻释放(pdf.js 官方 viewer 的做法)
8
+ function releaseCanvas (canvas) {
9
+ canvas.width = 0
10
+ canvas.height = 0
11
+ }
12
+
13
+ function binarySearchFirstItem (items, condition) {
14
+ var minIndex = 0
15
+ var maxIndex = items.length - 1
16
+
17
+ if (items.length === 0 || !condition(items[maxIndex])) {
18
+ return items.length
19
+ }
20
+
21
+ if (condition(items[minIndex])) {
22
+ return minIndex
23
+ }
24
+
25
+ while (minIndex < maxIndex) {
26
+ var currentIndex = minIndex + maxIndex >> 1
27
+ var currentItem = items[currentIndex]
28
+
29
+ if (condition(currentItem)) {
30
+ maxIndex = currentIndex
31
+ } else {
32
+ minIndex = currentIndex + 1
33
+ }
34
+ }
35
+
36
+ return minIndex
37
+ }
38
+ function backtrackBeforeAllVisibleElements (index, views, top) {
39
+ if (index < 2) {
40
+ return index
41
+ }
42
+
43
+ var elt = views[index].div
44
+ var pageTop = elt.offsetTop + elt.clientTop
45
+
46
+ if (pageTop >= top) {
47
+ elt = views[index - 1].div
48
+ pageTop = elt.offsetTop + elt.clientTop
49
+ }
50
+
51
+ for (var i = index - 2; i >= 0; --i) {
52
+ elt = views[i].div
53
+
54
+ if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
55
+ break
56
+ }
57
+
58
+ index = i
59
+ }
60
+
61
+ return index
62
+ }
63
+
64
+ function getVisibleElements (scrollEl, views) {
65
+ var sortByVisibility = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false
66
+ var horizontal = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false
67
+ var top = scrollEl.scrollTop
68
+ var bottom = top + scrollEl.clientHeight
69
+ var left = scrollEl.scrollLeft
70
+ var right = left + scrollEl.clientWidth
71
+
72
+ function isElementBottomAfterViewTop (view) {
73
+ var element = view.div
74
+ var elementBottom = element.offsetTop + element.clientTop + element.clientHeight
75
+ return elementBottom > top
76
+ }
77
+
78
+ function isElementRightAfterViewLeft (view) {
79
+ var element = view.div
80
+ var elementRight = element.offsetLeft + element.clientLeft + element.clientWidth
81
+ return elementRight > left
82
+ }
83
+
84
+ var visible = []
85
+ var numViews = views.length
86
+ var firstVisibleElementInd = numViews === 0 ? 0 : binarySearchFirstItem(views, horizontal ? isElementRightAfterViewLeft : isElementBottomAfterViewTop)
87
+
88
+ if (firstVisibleElementInd > 0 && firstVisibleElementInd < numViews && !horizontal) {
89
+ firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top)
90
+ }
91
+
92
+ var lastEdge = horizontal ? right : -1
93
+
94
+ for (var i = firstVisibleElementInd; i < numViews; i++) {
95
+ var view = views[i]
96
+ var element = view.div
97
+ var currentWidth = element.offsetLeft + element.clientLeft
98
+ var currentHeight = element.offsetTop + element.clientTop
99
+ var viewWidth = element.clientWidth
100
+ var viewHeight = element.clientHeight
101
+ var viewRight = currentWidth + viewWidth
102
+ var viewBottom = currentHeight + viewHeight
103
+
104
+ if (lastEdge === -1) {
105
+ if (viewBottom >= bottom) {
106
+ lastEdge = viewBottom
107
+ }
108
+ } else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
109
+ break
110
+ }
111
+
112
+ if (viewBottom <= top || currentHeight >= bottom || viewRight <= left || currentWidth >= right) {
113
+ continue
114
+ }
115
+
116
+ var hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom)
117
+ var hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right)
118
+ var percent = (viewHeight - hiddenHeight) * (viewWidth - hiddenWidth) * 100 / viewHeight / viewWidth | 0
119
+ visible.push({
120
+ id: view.id,
121
+ x: currentWidth,
122
+ y: currentHeight,
123
+ view: view,
124
+ percent: percent
125
+ })
126
+ }
127
+
128
+ var first = visible[0]
129
+ var last = visible[visible.length - 1]
130
+
131
+ if (sortByVisibility) {
132
+ visible.sort(function (a, b) {
133
+ var pc = a.percent - b.percent
134
+
135
+ if (Math.abs(pc) > 0.001) {
136
+ return -pc
137
+ }
138
+
139
+ return a.id - b.id
140
+ })
141
+ }
142
+
143
+ return {
144
+ first: first,
145
+ last: last,
146
+ views: visible
147
+ }
148
+ }
149
+ function watchScroll (viewAreaElement, callback) {
150
+ var debounceScroll = function debounceScroll (evt) {
151
+ if (rAF) {
152
+ return
153
+ }
154
+
155
+ rAF = window.requestAnimationFrame(function viewAreaElementScrolled () {
156
+ rAF = null
157
+ var currentX = viewAreaElement.scrollLeft
158
+ var lastX = state.lastX
159
+
160
+ if (currentX !== lastX) {
161
+ state.right = currentX > lastX
162
+ }
163
+
164
+ state.lastX = currentX
165
+ var currentY = viewAreaElement.scrollTop
166
+ var lastY = state.lastY
167
+
168
+ if (currentY !== lastY) {
169
+ state.down = currentY > lastY
170
+ }
171
+
172
+ state.lastY = currentY
173
+ callback(state)
174
+ })
175
+ }
176
+
177
+ var state = {
178
+ right: true,
179
+ down: true,
180
+ lastX: viewAreaElement.scrollLeft,
181
+ lastY: viewAreaElement.scrollTop,
182
+ _eventHandler: debounceScroll
183
+ }
184
+ var rAF = null
185
+ viewAreaElement.addEventListener('scroll', debounceScroll, true)
186
+ return state
187
+ }
188
+
189
+ class PdfViewer {
190
+ constructor () {
191
+ this.pages = []
192
+ this.loadViews = []
193
+ this.searchText = ''
194
+ this.searchTextArr = []
195
+ this.scrollState = null
196
+ this.destroyed = false
197
+ }
198
+
199
+ // 页面 div 是 append 进容器的,实例不销毁就会跟下一份预览叠在一起;
200
+ // scroll 监听和 ResizeObserver 也得一并摘掉,否则每预览一次多留一份。
201
+ destroy () {
202
+ this.destroyed = true
203
+ const container = this.pdfConfig && this.pdfConfig.viewerContainer
204
+ if (this.resizeObserver) {
205
+ this.resizeObserver.disconnect()
206
+ this.resizeObserver = null
207
+ }
208
+ if (container && this.scrollState && this.scrollState._eventHandler) {
209
+ container.removeEventListener('scroll', this.scrollState._eventHandler, true)
210
+ }
211
+ this.scrollState = null
212
+ // 只摘自己建的页面节点,容器里可能还有调用方渲染的工具条
213
+ for (const item of this.pages) {
214
+ if (!item.div) continue
215
+ for (const canvas of item.div.querySelectorAll('canvas')) releaseCanvas(canvas)
216
+ if (item.div.parentNode) item.div.parentNode.removeChild(item.div)
217
+ }
218
+ this.pages = []
219
+ this.loadViews = []
220
+ this.searchTextArr = []
221
+ // 销毁 loadingTask 会连带销毁文档和 worker;文档还在下载时关闭预览,也能就此中止下载而不是等它下完再释放。
222
+ // 销毁失败属于清理路径的异常,不吞掉,让它照常暴露在控制台便于排障
223
+ if (this.loadingTask) {
224
+ this.loadingTask.destroy()
225
+ this.loadingTask = null
226
+ }
227
+ this.pdfDocument = null
228
+ }
229
+
230
+ async searchTextInPDF (searchText) {
231
+ const numPages = this.pdfDocument.numPages
232
+ let currentPage = 1
233
+ this.clearHighlights()
234
+ this.searchTextArr = []
235
+ if (!searchText) return
236
+ this.searchText = searchText
237
+ const searchPage = async (pageNum) => {
238
+ try {
239
+ const page = await this.pdfDocument.getPage(pageNum)
240
+ const initViewport = page.getViewport({ scale: 1 })
241
+ let scale = 1
242
+ if (this.pdfConfig.zoom === 'page-width') {
243
+ const num = this.pdfConfig.viewerContainer.offsetWidth / initViewport.width
244
+ scale = num
245
+ // scale = Math.round(this.pdfConfig.viewerContainer.offsetWidth / initViewport.width)
246
+ } else {
247
+ scale = this.pdfConfig.zoom
248
+ }
249
+ const viewport = page.getViewport({ scale: 1 })
250
+ const textContent = await page.getTextContent()
251
+
252
+ const textItems = textContent.items
253
+ for (let i = 0; i < textItems.length; i++) {
254
+ const textItem = textItems[i]
255
+ const text = textItem.str
256
+
257
+ // 查找搜索文字在当前文本项中的位置
258
+ let startIndex = text.indexOf(searchText)
259
+ while (startIndex !== -1) {
260
+ // 找到匹配的搜索文字
261
+ const endIndex = startIndex + searchText.length
262
+ // 计算搜索文字的边界框
263
+ const { x, y, width, height } = this.calculateTextBoundingBox(textItem, startIndex, endIndex, viewport, scale)
264
+
265
+ this.searchTextArr.push({
266
+ pageNum,
267
+ x: x * scale,
268
+ y: y * scale,
269
+ width: width * scale,
270
+ height: height * scale
271
+ })
272
+ // this.searchTextArr.push({
273
+ // pageNum,
274
+ // x,
275
+ // y,
276
+ // width,
277
+ // height
278
+ // })
279
+
280
+ // 继续查找下一个匹配
281
+ startIndex = text.indexOf(searchText, endIndex)
282
+ }
283
+ }
284
+ } catch (error) {
285
+ console.error('Error processing page:', error)
286
+ }
287
+ }
288
+
289
+ // 使用for循环替代递归实现同步执行
290
+ for (currentPage = 1; currentPage <= numPages; currentPage++) {
291
+ await searchPage(currentPage)
292
+ }
293
+ if (this.searchTextArr.length > 0) {
294
+ this.scrollToPage(this.searchTextArr[0])
295
+ }
296
+ }
297
+
298
+ calculateTextBoundingBox (textItem, startIndex, endIndex, viewport, currentScale) {
299
+ const transform = textItem.transform
300
+ const textWidth = textItem.width
301
+ const textHeight = textItem.height
302
+
303
+ // 计算搜索文字的起始位置
304
+ const startX = transform[4] + (startIndex / textItem.str.length) * textWidth
305
+ const startY = viewport.height - transform[5]
306
+
307
+ // 计算搜索文字的宽度
308
+ const highlightWidth = ((endIndex - startIndex) / textItem.str.length) * textWidth
309
+
310
+ // return {
311
+ // x: startX * currentScale, // 乘以当前缩放比例
312
+ // y: (startY - textHeight) * currentScale, // 乘以当前缩放比例
313
+ // width: highlightWidth * currentScale, // 乘以当前缩放比例
314
+ // height: textHeight * currentScale // 乘以当前缩放比例
315
+ // }
316
+ return {
317
+ x: startX,
318
+ y: startY - textHeight, // PDF 的 y 坐标是倒置的
319
+ width: highlightWidth,
320
+ height: textHeight
321
+ }
322
+ }
323
+
324
+ highlightText (pageNum, x, y, width, height) {
325
+ const viewer = this.pdfConfig.viewerContainer
326
+ const pageElement = viewer.querySelector(`.pdf-viewer-page[data-page-number="${pageNum}"]`)
327
+
328
+ if (pageElement) {
329
+ const highlight = document.createElement('div')
330
+ highlight.className = 'search-highlight' // 添加类名
331
+ highlight.style.position = 'absolute'
332
+ highlight.style.left = `${x}px`
333
+ highlight.style.top = `${y}px`
334
+ highlight.style.width = `${width}px`
335
+ highlight.style.height = `${height}px`
336
+ highlight.style.backgroundColor = 'rgba(255, 255, 0, 0.5)'
337
+ highlight.style.pointerEvents = 'none'
338
+
339
+ pageElement.appendChild(highlight)
340
+ }
341
+ }
342
+
343
+ clearHighlights () {
344
+ const highlights = document.querySelectorAll('.search-highlight')
345
+ highlights.forEach(highlight => {
346
+ highlight.remove() // 移除高亮元素
347
+ })
348
+ }
349
+
350
+ scrollToPage ({ pageNum, x, y, width, height }) {
351
+ this.clearHighlights()
352
+ const scrollY = y > 60 ? y - 60 : y
353
+ // 来显示 PDF 页面的容器
354
+ const viewer = this.pdfConfig.viewerContainer
355
+ const pageElement = viewer.querySelector(`.pdf-viewer-page[data-page-number="${pageNum}"]`)
356
+ if (pageElement) {
357
+ viewer.scrollTo({
358
+ top: pageElement.offsetTop + scrollY, // 计算滚动位置
359
+ behavior: 'smooth' // 平滑滚动
360
+ })
361
+ // 高亮显示
362
+ this.highlightText(pageNum, x, y, width, height)
363
+ }
364
+ }
365
+
366
+ loadPdf () {
367
+ return util.usePlugin('pdf').then(pdf => {
368
+ this.pdfJs = pdf
369
+ })
370
+ }
371
+
372
+ async run (config) {
373
+ // 加载pdf
374
+ this.pdfConfig = {
375
+ viewerContainer: config.container,
376
+ url: config.url,
377
+ cMapUrl: config.cMapUrl || (util.getPublicPath() === './' ? './cmaps/' : util.getPublicPath() + 'cmaps/'),
378
+ cMapPacked: config.cMapPacked !== null ? config.cMapPacked : true,
379
+ zoom: config.zoom || 'page-width',
380
+ pageRotate: 0
381
+ }
382
+ await this.loadPdf()
383
+ // pdf.js 脚本还在加载时实例就被销毁了,不要再发起下载
384
+ if (this.destroyed) return
385
+ this.loadingTask = this.pdfJs.getDocument({
386
+ url: this.pdfConfig.url,
387
+ cMapUrl: this.pdfConfig.cMapUrl,
388
+ cMapPacked: this.pdfConfig.cMapPacked
389
+ })
390
+ return this.loadingTask.promise.then((pdfDocument) => {
391
+ this.load(pdfDocument)
392
+ }).catch(err => {
393
+ // 被 destroy 中止的加载不算错误,其它异常照常抛给调用方
394
+ if (!this.destroyed) throw err
395
+ })
396
+ }
397
+
398
+ load (pdfDocument) {
399
+ // 文档还在下载时实例已被销毁(快速切换预览),这份结果直接丢掉,不能再往容器里塞页面
400
+ if (this.destroyed) {
401
+ try {
402
+ pdfDocument.destroy()
403
+ } catch (e) {
404
+ // ignore
405
+ }
406
+ return
407
+ }
408
+ this.pdfDocument = pdfDocument
409
+ this.pdfDocument.getPage(1).then((firstPage) => {
410
+ if (this.destroyed) return
411
+ const pagesCount = pdfDocument.numPages
412
+ const viewport = firstPage.getViewport({
413
+ scale: 1
414
+ })
415
+ for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
416
+ this.createPageView({
417
+ container: this.pdfConfig.viewerContainer,
418
+ id: pageNum,
419
+ viewport: viewport.clone()
420
+ })
421
+ }
422
+ this.scrollState = watchScroll(this.pdfConfig.viewerContainer, this.scrollUpdate.bind(this))
423
+ setTimeout(() => {
424
+ if (this.destroyed) return
425
+ this.scrollUpdate()
426
+ this.resizeObserver = new ResizeObserver((entries) => {
427
+ this.resetPageStyle()
428
+ })
429
+ this.resizeObserver.observe(this.pdfConfig.viewerContainer)
430
+ }, 10)
431
+ })
432
+ }
433
+
434
+ createPageView ({ container, id }) {
435
+ this.pdfDocument.getPage(id).then((page) => {
436
+ if (this.destroyed) return
437
+ const viewport = page.getViewport({
438
+ scale: 1
439
+ })
440
+ const pageWidth = viewport.width
441
+ const pageHeight = viewport.height
442
+ const pageRatio = pageWidth / pageHeight
443
+ const canvasWidth = container.offsetWidth
444
+ const canvasHeight = canvasWidth / pageRatio | 0
445
+ const div = document.createElement('div')
446
+ div.className = 'pdf-viewer-page'
447
+ div.style.width = Math.floor(canvasWidth) + 'px'
448
+ div.style.height = Math.floor(canvasHeight) + 'px'
449
+ div.setAttribute('data-page-number', id)
450
+ const loading = document.createElement('div')
451
+ loading.className = 'el-icon-loading pdf-viewer-loading'
452
+ div.appendChild(loading)
453
+ container.appendChild(div)
454
+ this.pages.push({ id, div: div, viewport })
455
+ this.viewport = viewport
456
+ })
457
+ }
458
+
459
+ resetPageStyle () {
460
+ for (const item of this.pages) {
461
+ if (item.div) {
462
+ const viewport = item.viewport || this.viewport
463
+ const pageWidth = viewport.width
464
+ const pageHeight = viewport.height
465
+ const pageRatio = pageWidth / pageHeight
466
+ const canvasWidth = this.pdfConfig.zoom === 'page-width' ? this.pdfConfig.viewerContainer.offsetWidth : viewport.width * (this.pdfConfig.zoom || 1)
467
+ const canvasHeight = canvasWidth / pageRatio | 0
468
+ item.div.style.width = Math.floor(canvasWidth) + 'px'
469
+ item.div.style.height = Math.floor(canvasHeight) + 'px'
470
+ }
471
+ }
472
+ }
473
+
474
+ updatePageView () {
475
+ var visible = this.getVisiblePages()
476
+ var visiblePages = visible.views
477
+ var numVisiblePages = visiblePages.length
478
+ if (numVisiblePages === 0) {
479
+ return
480
+ }
481
+ this.resetPageStyle()
482
+ var newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1)
483
+ this.resizeVisiblePages(newCacheSize, visiblePages)
484
+ for (const item of this.loadViews) {
485
+ this.destroyPageView(item)
486
+ }
487
+ this.loadViews = []
488
+ this.renderPageView(visible, () => {
489
+ setTimeout(() => {
490
+ const page = visiblePages[0].view.div
491
+ this.pdfConfig.viewerContainer.scrollTop = page.offsetTop
492
+ }, 100)
493
+ })
494
+ }
495
+
496
+ scrollUpdate () {
497
+ var visible = this.getVisiblePages()
498
+ var visiblePages = visible.views
499
+ var numVisiblePages = visiblePages.length
500
+
501
+ if (numVisiblePages === 0) {
502
+ return
503
+ }
504
+
505
+ var newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1)
506
+
507
+ this.resizeVisiblePages(newCacheSize, visiblePages)
508
+ this.renderPageView(visible)
509
+ }
510
+
511
+ getVisiblePages () {
512
+ return getVisibleElements(this.pdfConfig.viewerContainer, this.pages)
513
+ }
514
+
515
+ resizeVisiblePages (newCacheSize) {
516
+ if (this.loadViews.length > newCacheSize) {
517
+ this.destroyPageView(this.loadViews.shift())
518
+ }
519
+ }
520
+
521
+ destroyPageView ({ id }) {
522
+ const page = this.pages.find(item => item.id === id)
523
+ const div = page.div
524
+ var childNodes = div.childNodes
525
+ for (var i = childNodes.length - 1; i >= 0; i--) {
526
+ var node = childNodes[i]
527
+ if (node.className.includes('pdf-viewer-loading')) {
528
+ node.style.display = 'block'
529
+ } else {
530
+ if (node.tagName === 'CANVAS') releaseCanvas(node)
531
+ div.removeChild(node)
532
+ }
533
+ }
534
+ div.removeAttribute('data-loaded')
535
+ }
536
+
537
+ renderPageView (visible, cb) {
538
+ for (const item of visible.views) {
539
+ const div = item.view.div
540
+ if (div.getAttribute('data-loaded')) continue
541
+ div.setAttribute('data-loaded', true)
542
+ this.pdfDocument.getPage(item.view.id)
543
+ .then((page) => {
544
+ if (this.destroyed) return
545
+ const canvas = document.createElement('canvas')
546
+ const ctx = canvas.getContext('2d', {
547
+ alpha: false
548
+ })
549
+ const initViewport = page.getViewport({ scale: 1 })
550
+ const rotation = (this.pdfConfig.pageRotate + initViewport.rotation) % 360
551
+ const baseViewport = page.getViewport({ scale: 1, rotation })
552
+ // 渲染倍率 = 画布实际显示宽度 / 页面宽度 × 设备像素比,再按总像素数兜底。
553
+ // 原来按 Math.ceil(body 宽 / 页宽) 取整,1920 宽屏上 A4 会画成 4 倍(一页约 32MB),
554
+ // 十来页缓存就是几百 MB,连续预览多份必然溢出
555
+ const cssWidth = div.clientWidth || this.pdfConfig.viewerContainer.offsetWidth || baseViewport.width
556
+ let scale = cssWidth / baseViewport.width * (window.devicePixelRatio || 1)
557
+ const pixels = baseViewport.width * baseViewport.height * scale * scale
558
+ if (pixels > MAX_CANVAS_PIXELS) scale *= Math.sqrt(MAX_CANVAS_PIXELS / pixels)
559
+ const viewport = page.getViewport({ scale, rotation })
560
+ canvas.width = viewport.width // 画布大小,默认值是width:300px,height:150px
561
+ canvas.height = viewport.height
562
+ canvas.style.width = '100%' // 画布的框架大小
563
+ const renderContext = {
564
+ canvasContext: ctx,
565
+ viewport: viewport,
566
+ enableWebGL: false,
567
+ renderInteractiveForms: false
568
+ }
569
+ var renderTask = page.render(renderContext)
570
+ renderTask.promise.then(() => {
571
+ // 渲染期间预览已关闭,这块画布不会再上屏,立刻释放
572
+ if (this.destroyed) {
573
+ releaseCanvas(canvas)
574
+ return
575
+ }
576
+ const loading = item.view.div.querySelector('.pdf-viewer-loading')
577
+ loading.style.display = 'none'
578
+ item.view.div.appendChild(canvas)
579
+ this.loadViews.push(item.view)
580
+ cb && cb()
581
+ }, () => {
582
+ // destroy 时 pdf.js 会 cancel 渲染中的任务,画布同样要释放
583
+ releaseCanvas(canvas)
584
+ })
585
+ })
586
+ }
587
+ }
588
+
589
+ drawerCanvas (num) {
590
+ return new Promise(resolve => {
591
+ this.pdfDocument.getPage(num)
592
+ .then(pdfPage => {
593
+ var canvas = document.createElement('canvas')
594
+ const ctx = canvas.getContext('2d')
595
+ const initViewport = this.viewport
596
+ const scale = Math.ceil(document.body.offsetWidth / initViewport.width)
597
+
598
+ const viewport = pdfPage.getViewport({ scale: scale })
599
+ canvas.width = viewport.width // 画布大小,默认值是width:300px,height:150px
600
+ canvas.height = viewport.height
601
+ canvas.style.width = '100%' // 画布的框架大小
602
+ const renderContext = {
603
+ canvasContext: ctx,
604
+ viewport: viewport,
605
+ enableWebGL: false,
606
+ renderInteractiveForms: false
607
+ }
608
+ var renderTask = pdfPage.render(renderContext)
609
+ renderTask.promise.then(() => {
610
+ resolve(canvas)
611
+ })
612
+ })
613
+ })
614
+ }
615
+
616
+ scaleChanged (scale) {
617
+ this.pdfConfig.zoom = scale
618
+ this.updatePageView()
619
+ }
620
+
621
+ rotateChanged (delta) {
622
+ this.pdfConfig.pageRotate += delta
623
+ const rotation = this.pdfConfig.pageRotate % 360
624
+ this.pdfDocument.getPage(1).then((firstPage) => {
625
+ this.viewport = firstPage.getViewport({
626
+ scale: 1,
627
+ rotation
628
+ })
629
+ this.updatePageView()
630
+ })
631
+ }
632
+
633
+ print () {
634
+ const pagesCount = this.pdfDocument.numPages
635
+ var iframe = document.getElementById('print-iframe')
636
+ if (!iframe) {
637
+ iframe = document.createElement('IFRAME')
638
+ var doc = null
639
+ iframe.setAttribute('id', 'print-iframe')
640
+ iframe.setAttribute('style', 'position:absolute;width:0px;height:0px;left:-500px;top:-500px;')
641
+ document.body.appendChild(iframe)
642
+ }
643
+ doc = iframe.contentWindow.document
644
+ doc.body.innerHTML = ''
645
+ let pageStyleSheet = null
646
+ const drawerCanvas = (pageNum) => {
647
+ this.drawerCanvas(pageNum).then(canvas => {
648
+ if (!pageStyleSheet) {
649
+ pageStyleSheet = document.createElement('style')
650
+ pageStyleSheet.textContent = '@supports ((size:A4) and (size:1pt 1pt)) {' + '@page { size: auto;}' + '};' +
651
+ '@media print {' +
652
+ 'body { margin: 0 }' +
653
+ 'canvas { page-break-before: always; page-break-after: always; page-break-inside: avoid }' +
654
+ '}'
655
+ doc.head.appendChild(pageStyleSheet)
656
+ }
657
+ doc.body.appendChild(canvas)
658
+ if (pageNum >= pagesCount) {
659
+ setTimeout(() => {
660
+ iframe.contentWindow.focus()
661
+ iframe.contentWindow.print()
662
+ }, 100)
663
+ } else {
664
+ drawerCanvas(++pageNum)
665
+ }
666
+ })
667
+ }
668
+ drawerCanvas(1)
669
+ }
670
+ }
671
+
672
+ export default PdfViewer