askbot-dragon 1.7.41-beta → 1.7.44-beta

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 (95) hide show
  1. package/README.md +27 -27
  2. package/babel.config.js +6 -6
  3. package/dragon.iml +7 -7
  4. package/package.json +56 -55
  5. package/public/index.html +73 -75
  6. package/src/App.vue +31 -31
  7. package/src/api/index.js +1 -1
  8. package/src/api/mock.http +2 -2
  9. package/src/api/requestUrl.js +185 -185
  10. package/src/assets/js/AliyunlssUtil.js +141 -117
  11. package/src/assets/js/Base64Util.js +22 -22
  12. package/src/assets/js/common.js +252 -252
  13. package/src/assets/js/hammer.js +100 -89
  14. package/src/assets/js/script.js +36 -36
  15. package/src/assets/less/common.css +6773 -6773
  16. package/src/assets/less/converSationContainer/common.less +199 -192
  17. package/src/assets/less/converSationContainer/converSatonContainer.less +493 -493
  18. package/src/assets/less/iconfont.css +37 -37
  19. package/src/assets/less/ticketMessage.less +294 -294
  20. package/src/components/ActionAlertIframe.vue +177 -154
  21. package/src/components/AiGuide.vue +438 -471
  22. package/src/components/AnswerDocknowledge.vue +1091 -1087
  23. package/src/components/AnswerVoice.vue +285 -285
  24. package/src/components/AskIFrame.vue +15 -15
  25. package/src/components/ConversationContainer.vue +10766 -10875
  26. package/src/components/FileType.vue +86 -86
  27. package/src/components/Message.vue +27 -27
  28. package/src/components/MyEditor.vue +342 -341
  29. package/src/components/QwFeedback.vue +301 -301
  30. package/src/components/actionSatisfaction.vue +107 -107
  31. package/src/components/actionSendToBot.vue +62 -62
  32. package/src/components/answerDissatisfaction.vue +62 -62
  33. package/src/components/answerRadio.vue +211 -211
  34. package/src/components/ask-components/DissatisfactionOptions.vue +57 -57
  35. package/src/components/ask-components/Msgloading.vue +37 -37
  36. package/src/components/ask-components/SatisfactionV2.vue +15 -15
  37. package/src/components/askVideo.vue +162 -139
  38. package/src/components/assetDetails.vue +378 -378
  39. package/src/components/assetMessage.vue +228 -228
  40. package/src/components/associationIntention.vue +378 -374
  41. package/src/components/attachmentPreview.vue +90 -90
  42. package/src/components/botActionSatisfactor.vue +68 -68
  43. package/src/components/chatContent.vue +513 -513
  44. package/src/components/feedBack.vue +136 -136
  45. package/src/components/fielListView.vue +351 -351
  46. package/src/components/file/AliyunOssComponents.vue +108 -108
  47. package/src/components/formTemplate.vue +3501 -3497
  48. package/src/components/imgView.vue +31 -31
  49. package/src/components/intelligentSummary.vue +231 -227
  50. package/src/components/kkview.vue +1138 -1138
  51. package/src/components/loadingProcess.vue +164 -164
  52. package/src/components/markDownText.vue +197 -197
  53. package/src/components/message/ActionAlertIframe.vue +112 -112
  54. package/src/components/message/ShopMessage.vue +164 -164
  55. package/src/components/message/TextMessage.vue +928 -924
  56. package/src/components/message/TicketMessage.vue +201 -201
  57. package/src/components/message/swiper/index.js +4 -4
  58. package/src/components/message/swiper/ticketSwiper.vue +503 -503
  59. package/src/components/message/swiper/ticketSwiperItem.vue +61 -61
  60. package/src/components/msgLoading.vue +231 -231
  61. package/src/components/myPopup.vue +70 -70
  62. package/src/components/newPdfPosition.vue +878 -0
  63. package/src/components/pagination.vue +129 -0
  64. package/src/components/pdfPosition.vue +1514 -1334
  65. package/src/components/popup.vue +227 -227
  66. package/src/components/preview/docView.vue +107 -0
  67. package/src/components/preview/excelView.vue +177 -0
  68. package/src/components/preview/newPositionPreview.vue +351 -0
  69. package/src/components/preview/pdfView.vue +760 -0
  70. package/src/components/previewDoc.vue +251 -247
  71. package/src/components/previewPdf.vue +1069 -779
  72. package/src/components/receiverMessagePlatform.vue +65 -65
  73. package/src/components/recommend.vue +80 -80
  74. package/src/components/selector/hOption.vue +20 -20
  75. package/src/components/selector/hSelector.vue +199 -199
  76. package/src/components/selector/hWrapper.vue +216 -216
  77. package/src/components/senderMessagePlatform.vue +50 -50
  78. package/src/components/source/BotMessage.vue +24 -24
  79. package/src/components/source/CustomMessage.vue +24 -24
  80. package/src/components/test.vue +260 -260
  81. package/src/components/tree.vue +307 -307
  82. package/src/components/utils/AliyunIssUtil.js +103 -103
  83. package/src/components/utils/ckeditor.js +185 -185
  84. package/src/components/utils/format_date.js +25 -25
  85. package/src/components/utils/index.js +6 -6
  86. package/src/components/utils/math_utils.js +29 -29
  87. package/src/components/voiceComponent.vue +119 -119
  88. package/src/components/welcomeKnowledgeFile.vue +344 -340
  89. package/src/components/welcomeLlmCard.vue +144 -140
  90. package/src/components/welcomeSuggest.vue +97 -97
  91. package/src/locales/cn.json +72 -0
  92. package/src/locales/en.json +73 -0
  93. package/src/locales/jp.json +73 -0
  94. package/src/main.js +75 -57
  95. package/vue.config.js +54 -54
@@ -0,0 +1,760 @@
1
+ <template>
2
+ <div class="pdfViewPage" id="pdfViewPage">
3
+ <!-- 按钮容器 -->
4
+ <div class="button-container" v-if="false">
5
+ <!-- 文件上传 -->
6
+ <label for="file-upload" class="file-upload-label">
7
+ 上传 PDF 文件
8
+ <input
9
+ id="file-upload"
10
+ type="file"
11
+ accept="application/pdf"
12
+ @change="handleFileUpload"
13
+ class="file-input"
14
+ />
15
+ </label>
16
+
17
+ <!-- &lt;!&ndash; 跳转按钮(已有高亮) &ndash;&gt;-->
18
+ <!-- <button @click="jumpToHighlight" class="action-button">-->
19
+ <!-- 跳转到第一个高亮区域-->
20
+ <!-- </button>-->
21
+ <!-- -->
22
+ <!-- &lt;!&ndash; 缩放按钮 &ndash;&gt;-->
23
+ <!-- <button @click="zoomIn" class="action-button">放大</button>-->
24
+ <!-- <button @click="zoomOut" class="action-button">缩小</button>-->
25
+ <!-- -->
26
+ <!-- &lt;!&ndash; 搜索框和按钮 &ndash;&gt;-->
27
+ <!-- <input v-model="searchQuery" placeholder="输入搜索关键词" type="text" />-->
28
+ <!-- <button @click="doSearch">搜索</button>-->
29
+ <!-- -->
30
+ <!-- &lt;!&ndash; 搜索结果导航 &ndash;&gt;-->
31
+ <!-- <div v-if="searchTermCount > 0" class="search-nav">-->
32
+ <!-- <span>共找到 {{ searchTermCount }} 个匹配</span>-->
33
+ <!-- <template v-if="searchTermCount > 1">-->
34
+ <!-- <button @click="prevSearchResult">上一个</button>-->
35
+ <!-- <button @click="nextSearchResult">下一个</button>-->
36
+ <!-- </template>-->
37
+ <!-- </div>-->
38
+ </div>
39
+
40
+ <!-- PDF 渲染容器 -->
41
+ <div class="pdf-container" ref="pdfContainer">
42
+ <div
43
+ v-for="(page, index) in pages"
44
+ :key="index"
45
+ class="page-container"
46
+ >
47
+ <div class="canvas-wrapper" :id="'canvas-wrapper' + index">
48
+ <canvas ref="canvases"></canvas>
49
+ <!-- 在页面渲染中时显示loading -->
50
+ <div v-if="!page.loaded && !page.loading" class="page-loading-overlay">
51
+ <div class="spinner"></div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </template>
58
+
59
+ <script>
60
+ // import { getDocument } from "pdfjs-dist/webpack";
61
+ // import pdfWorker from "pdfjs-dist/build/pdf.worker.entry";
62
+ const pdfjsLib = window['pdfjsLib']
63
+ if(pdfjsLib) {
64
+ pdfjsLib.GlobalWorkerOptions.workerSrc = window['pdfjs-dist/build/pdf.worker']
65
+ }
66
+ const { TextLayerBuilder } = window['pdfjs-dist/web/pdf_viewer']
67
+ export default {
68
+ name: "PdfViewer",
69
+ data() {
70
+ return {
71
+ pdfFile: null,
72
+ pdf: null,
73
+ numPages: 0,
74
+ scale: 1, // 初始缩小比例
75
+ renderInProgress: false,
76
+ pages: [], // { pageNumber: number, loaded: boolean, canvas: ref, loading: boolean }
77
+ // 静态高亮(坐标采用顶部为原点)
78
+ highlights: [],
79
+ searchQuery: "",
80
+ searchHighlights: [], // 搜索高亮(PDF文本坐标,自下往上)
81
+ observer: null,
82
+ currentPage: 1, // 当前可视区最接近顶部的页面
83
+ resizeTimeout: null,
84
+ currentSearchIndex: 0,
85
+ searchTermCount: 0,
86
+ loadingTask:null,
87
+ context:null,
88
+ viewport:null,
89
+ colors: ['0, 245, 255', '54, 106, 255', '33, 136, 104','255, 255, 51','0, 255, 127'],
90
+ pageNumber:1,
91
+ mergedGroupsList:[]
92
+ };
93
+ },
94
+ props:{
95
+ split_paragraphs:{
96
+ type:Array,
97
+ default(){
98
+ return []
99
+ }
100
+ },
101
+ ossPath:{
102
+ type:String,
103
+ default:""
104
+ }
105
+ },
106
+ methods: {
107
+ setHighlights(arr){
108
+ this.highlights = [];
109
+ for (let i =0;i<arr.length;i++){
110
+ if (arr[i].original_paragraph){
111
+ for (let j=0;j<arr[i].original_paragraph.length;j++){
112
+ let item = arr[i].original_paragraph[j];
113
+ let obj = {
114
+ position:item.position,
115
+ pageNumber: i+1,
116
+ page:item.page
117
+ }
118
+ this.highlights.push(obj);
119
+ }
120
+ }
121
+ }
122
+ },
123
+ mergedGroups(arr) {
124
+ const groupsMap = {};
125
+ let newArr = arr.filter(item => item.position && item.page)
126
+ newArr.forEach(group => {
127
+ if (!groupsMap[group.pageNumber]) {
128
+ groupsMap[group.pageNumber] = {
129
+ pageNumber: group.pageNumber,
130
+ position: [],
131
+ page:group.page
132
+ };
133
+ }
134
+ groupsMap[group.pageNumber].position = [
135
+ ...groupsMap[group.pageNumber].position,
136
+ ...group.position
137
+ ];
138
+ });
139
+ return Object.values(groupsMap);
140
+ },
141
+ async initPdf() {
142
+ if (!this.pdfFile) return;
143
+ // this.pdf = await pdfjsLib.getDocument(this.pdfFile).promise;
144
+ this.loadingTask = pdfjsLib.getDocument(this.pdfFile);
145
+ await this.loadingTask.promise.then(async pdf => {
146
+ // 获取PDF的第一页
147
+ this.pdf = pdf;
148
+ // this.numPages = pdf._transport.numPages;
149
+ let lazySize = pdf._transport.numPages;
150
+ this.numPages = Math.min(pdf._transport.numPages, lazySize);
151
+ // 初始化页面数据
152
+ this.pages = Array.from({ length: this.numPages }, (_, i) => ({
153
+ pageNumber: i + 1,
154
+ loaded: false,
155
+ canvas: null,
156
+ loading: false,
157
+ }));
158
+ // 清空搜索高亮
159
+ this.searchHighlights = [];
160
+ this.searchTermCount = 0;
161
+ this.currentSearchIndex = 0;
162
+ setTimeout(async () => {
163
+ await this.setCanvasRef();
164
+ },100)
165
+ })
166
+
167
+ },
168
+ async loadPdf() {
169
+ if (!this.pdfFile) return;
170
+ await this.initPdf();
171
+ this.initObserver();
172
+ },
173
+ initObserver() {
174
+ if (this.observer) {
175
+ this.observer.disconnect();
176
+ }
177
+ this.observer = new IntersectionObserver(
178
+ (entries) => {
179
+ entries.forEach( (entry) => {
180
+ if (entry.isIntersecting) {
181
+ const index = Number(entry.target.getAttribute('data-index'));
182
+ const page = this.pages[index];
183
+ if (!page.loaded && !page.loading) {
184
+ this.renderPage(page.pageNumber);
185
+ }
186
+ this.updateCurrentPage();
187
+ }
188
+ });
189
+ },
190
+ {
191
+ root: this.$refs.pdfContainer,
192
+ rootMargin: '0px 0px 0px 0px',
193
+ threshold: 0.1,
194
+ }
195
+ );
196
+ this.$nextTick( () => {
197
+ setTimeout(() => {
198
+ this.pages.forEach((page, index) => {
199
+ const canvas = page.canvas;
200
+ if (canvas) {
201
+ canvas.setAttribute('data-index', index);
202
+ this.observer.observe(canvas);
203
+ }
204
+ });
205
+ },300)
206
+ setTimeout(async () => {
207
+ let currentPage = this.split_paragraphs[0];
208
+ let page = 1;
209
+ if (currentPage){
210
+ page = currentPage.original_paragraph[0].page
211
+ }
212
+ await this.loadPage(page);
213
+ this.jumpToHighlight(0);
214
+ },1300)
215
+ });
216
+ },
217
+ async loadPage(number){
218
+ for (let i=0;i<number;i++){
219
+ const page = this.pages[i];
220
+ if (!page.loaded && !page.loading) {
221
+ await this.renderPage(page.pageNumber);
222
+ }
223
+ await this.updateCurrentPage();
224
+ }
225
+ },
226
+ async renderPage(pageNumber) {
227
+ if (!this.pdf || this.renderInProgress) return;
228
+ const pageIndex = pageNumber - 1;
229
+ const pageData = this.pages[pageIndex];
230
+ if (pageData.loaded) return;
231
+ this.pageNumber = pageNumber;
232
+ const page = await this.pdf.getPage(pageNumber);
233
+ let pdfWidth = page.getViewport({ scale: 1}).width
234
+ const viewport = page.getViewport({ scale: this.calculateScale(pdfWidth) });
235
+ this.viewport = viewport;
236
+ const canvas = pageData.canvas;
237
+ if (!canvas) return;
238
+
239
+ const context = canvas.getContext("2d");
240
+ this.context = context;
241
+ const devicePixelRatio = window.devicePixelRatio || 1;
242
+ canvas.width = (viewport.width) * devicePixelRatio;
243
+ canvas.height = viewport.height * devicePixelRatio;
244
+ canvas.style.width = `${viewport.width}px`;
245
+ canvas.style.height = `${viewport.height}px`;
246
+ context.scale(devicePixelRatio, devicePixelRatio);
247
+
248
+ this.$set(pageData,'loading',true)
249
+ this.renderInProgress = true;
250
+ const renderTask = page.render({ canvasContext: context, viewport });
251
+ // await renderTask.promise;
252
+ let hasDoc = document.getElementById('textLayer' + pageNumber)
253
+ if (hasDoc){
254
+ await renderTask.promise;
255
+ return
256
+ }
257
+ await renderTask.promise.then(() => {
258
+ return page.getTextContent()
259
+ }).then((textContent) => {
260
+ const textDiv = document.createElement('div');
261
+ textDiv.setAttribute('class', 'textLayer');
262
+ textDiv.setAttribute('id', 'textLayer' + pageNumber);
263
+ let textLayer = new TextLayerBuilder({
264
+ textLayerDiv: textDiv,
265
+ pageIndex: pageIndex,
266
+ viewport: viewport,
267
+ });
268
+ textDiv.style.width = `${viewport.width}px`; // 根据 viewport 调整宽度
269
+ textDiv.style.height = `${viewport.height}px`; // 根据 viewport 调整高度(可能需要根据实际文本内容调整)
270
+ textDiv.style.overflow = 'hidden'; // 如果文本内容超出范围,则隐藏它
271
+ textDiv.style.whiteSpace = 'pre-wrap'; // 保留空白符和换行符
272
+ textDiv.style.position = 'absolute'; // 绝对定位以覆盖 canvas
273
+ textDiv.style.top = '0';
274
+ textDiv.style.left = '0';
275
+ textDiv.style.fontSize = '20px'; // 设置字体大小(可能需要根据实际情况调整)
276
+ textDiv.style.color = 'black'; // 设置文本颜色(可能需要根据实际情况调整)
277
+ // textDiv.textContent = textContent; // 设置文本内容
278
+ textLayer.setTextContent(textContent);
279
+ textLayer.render()
280
+ // 将文本 div 添加到覆盖层中
281
+ let pagesDiv = document.getElementById('canvas-wrapper' + pageIndex);
282
+ pagesDiv.appendChild(textDiv)
283
+ })
284
+ this.renderInProgress = false;
285
+ this.$set(pageData,'loading',false)
286
+ this.$set(pageData,'loaded',true)
287
+
288
+ // 渲染已有高亮区域(静态highlights)
289
+ this.renderHighlights(context, pageNumber, viewport);
290
+ // 渲染搜索高亮区域
291
+ this.renderSearchHighlights(context, pageNumber, viewport);
292
+ },
293
+ calculateScale(pdfWidth) {
294
+ const containerWidth = document.getElementById('pdfViewPage').clientWidth - 50;
295
+ return (containerWidth / pdfWidth) * this.scale;
296
+ },
297
+ async renderHighlights(context, number,viewport) {
298
+ const pageHighlights = this.highlights.filter(item => (item.page === number ))
299
+ let highlights = this.mergedGroups(pageHighlights);
300
+ this.mergedGroupsList = this.mergedGroupsList.concat(highlights)
301
+ if (context){
302
+ highlights.forEach(({ position,pageNumber }) => {
303
+ // this.drawHighlight(context, viewport, position, fillStyle, false);
304
+ const groups = [];
305
+ const colorIndex = pageNumber % this.colors.length;
306
+ let fillStyle = 'rgba(' + this.colors[colorIndex] + ',0.1)';
307
+ for (let i = 0; i < position.length; i += 4) {
308
+ groups.push(position.slice(i, i + 4));
309
+ }
310
+ for (let i = 0;i<groups.length;i++){
311
+ this.drawHighlight(context, viewport, groups[i], fillStyle, false);
312
+ }
313
+ });
314
+ }
315
+ },
316
+ renderSearchHighlights(context, pageNumber, viewport) {
317
+ const pageHighlights = this.searchHighlights.filter(h => h.pageNumber === pageNumber);
318
+ pageHighlights.forEach(({ position }) => {
319
+ this.drawHighlight(context, viewport, position, "rgba(255, 255, 0, 0.4)", true);
320
+ });
321
+ },
322
+ drawHighlight(context, viewport, position, fillStyle, fromSearch = false) {
323
+ const [x1, y1, x2, y2] = position;
324
+ const scale = viewport.scale;
325
+ const pageHeight = viewport.height / viewport.scale;
326
+
327
+ let canvasX1 = x1 * scale;
328
+ let canvasX2 = x2 * scale;
329
+ let canvasY1, canvasY2;
330
+
331
+ if (fromSearch) {
332
+ // 搜索高亮:PDF坐标系(左下原点)
333
+ canvasY1 = (pageHeight - y1) * scale;
334
+ canvasY2 = (pageHeight - y2) * scale;
335
+ } else {
336
+ // 静态 highlights 假设顶部坐标原点不需翻转
337
+ canvasY1 = y1 * scale;
338
+ canvasY2 = y2 * scale;
339
+ }
340
+ // 清除当前路径(如果需要)
341
+ context.beginPath();
342
+
343
+ context.fillStyle = fillStyle;
344
+ context.fillRect(
345
+ Math.min(canvasX1, canvasX2),
346
+ Math.min(canvasY1, canvasY2),
347
+ Math.abs(canvasX2 - canvasX1),
348
+ Math.abs(canvasY2 - canvasY1)
349
+ );
350
+ },
351
+ handleFileUpload(event) {
352
+ const file = event.target.files[0];
353
+ if (file) {
354
+ this.pdfFile = URL.createObjectURL(file);
355
+ this.clearPages();
356
+ this.loadPdf();
357
+ }
358
+ },
359
+ async setCanvasRef() {
360
+ await this.pages.forEach((page, index) => {
361
+ this.$set(this.pages[index],'canvas',this.$refs.canvases[index])
362
+ // 注意:这里我们直接将 DOM 元素赋值给 page.canvas,
363
+ // 但通常你可能想要对 DOM 元素进行进一步的处理或封装。
364
+ });
365
+ },
366
+ clearPages() {
367
+ this.pages = [];
368
+ if (this.observer) {
369
+ this.observer.disconnect();
370
+ }
371
+ },
372
+ jumpToHighlight(currentPage) {
373
+ let highlight = null;
374
+ if (this.split_paragraphs[currentPage] && this.split_paragraphs[currentPage].original_paragraph){
375
+ highlight = this.split_paragraphs[currentPage].original_paragraph[0];
376
+ }
377
+ if (!highlight) return;
378
+ this.scrollToHighlight(highlight);
379
+ },
380
+ scrollToHighlight(highlight) {
381
+ if (!highlight) return;
382
+ const { page, position } = highlight;
383
+
384
+ const fromSearch = this.searchHighlights.some(h => h === highlight);
385
+
386
+ this.$nextTick(() => {
387
+ const pageIndex = page - 1;
388
+ const pageData = this.pages[pageIndex];
389
+ const container = this.$refs.pdfContainer;
390
+ const canvas = pageData.canvas;
391
+ if (!canvas || !container) return;
392
+
393
+ const rect = canvas.getBoundingClientRect();
394
+ const containerRect = container.getBoundingClientRect();
395
+ this.pdf.getPage(page).then(pages => {
396
+ let pdfWidth = pages.getViewport({ scale: 1}).width
397
+ const scale = this.calculateScale(pdfWidth);
398
+ const viewport = pages.getViewport({ scale });
399
+ const pageHeight = viewport.height / viewport.scale;
400
+
401
+ const [x1, y1, x2, y2] = position;
402
+ let targetYPdf, targetXPdf;
403
+ if (fromSearch) {
404
+ // 搜索高亮使用上边界作为参考点
405
+ const highlightTopYPdf = Math.max(y1, y2);
406
+ const highlightLeftXPdf = (x1 + x2) / 2;
407
+ targetYPdf = highlightTopYPdf;
408
+ targetXPdf = highlightLeftXPdf;
409
+ } else {
410
+ // 静态高亮使用中心点
411
+ const highlightCenterYPdf = Number(Number(y1) + Number(y2)) / 2;
412
+ const highlightCenterXPdf = Number(Number(x1) + Number(x2)) / 2;
413
+ targetYPdf = highlightCenterYPdf;
414
+ targetXPdf = highlightCenterXPdf;
415
+ }
416
+
417
+ let targetYCanvas, targetXCanvas;
418
+ if (fromSearch) {
419
+ targetYCanvas = (pageHeight - targetYPdf) * scale;
420
+ } else {
421
+ targetYCanvas = targetYPdf * scale;
422
+ }
423
+ targetXCanvas = targetXPdf * scale;
424
+ // 将高亮位置转换为相对于container滚动坐标体系的绝对位置
425
+ // rect.top/left - containerRect.top/left为canvas相对container视口顶部/左侧的偏移
426
+ // 加上container当前的scrollTop/scrollLeft可获得绝对滚动系下的位置
427
+ const highlightAbsoluteY = (rect.top - containerRect.top) + container.scrollTop + targetYCanvas;
428
+ const highlightAbsoluteX = (rect.left - containerRect.left) + container.scrollLeft + targetXCanvas;
429
+
430
+ const topOffset = container.clientHeight / 2;
431
+ const leftOffset = container.clientWidth / 2;
432
+
433
+ const scrollTop = Math.max(0, highlightAbsoluteY - topOffset);
434
+ const scrollLeft = Math.max(0, highlightAbsoluteX - leftOffset);
435
+ container.scrollTo({
436
+ top: scrollTop,
437
+ left: scrollLeft,
438
+ behavior: "smooth",
439
+ });
440
+ });
441
+ });
442
+ },
443
+ zoomIn() {
444
+ this.updateCurrentPage();
445
+ if (this.renderInProgress) return;
446
+ const container = this.$refs.pdfContainer;
447
+ const currentScroll = container.scrollTop;
448
+ this.scale += 0.1;
449
+ this.reloadAllPages(currentScroll);
450
+ },
451
+ zoomOut() {
452
+ this.updateCurrentPage();
453
+ if (this.renderInProgress) return;
454
+ const container = this.$refs.pdfContainer;
455
+ const currentScroll = container.scrollTop;
456
+ this.scale = Math.max(this.scale - 0.1, 0.1);
457
+ this.reloadAllPages(currentScroll);
458
+ },
459
+ reloadAllPages(scrollPosition) {
460
+ this.pages.forEach((p) => {
461
+ p.loaded = false;
462
+ this.$set(p,'loading',false)
463
+ if (p.canvas) {
464
+ const ctx = p.canvas.getContext('2d');
465
+ ctx.clearRect(0,0,p.canvas.width,p.canvas.height);
466
+ }
467
+ });
468
+
469
+ const container = this.$refs.pdfContainer;
470
+ this.$nextTick(() => {
471
+ container.scrollTop = scrollPosition;
472
+ this.initObserver();
473
+ container.scrollTop = container.scrollTop + 1;
474
+ container.scrollTop = container.scrollTop - 1;
475
+ });
476
+ },
477
+ updateCurrentPage() {
478
+ const container = this.$refs.pdfContainer;
479
+ if (!container) return;
480
+ let closestPage = 1;
481
+ let closestDistance = Infinity;
482
+ this.pages.forEach((p) => {
483
+ if (p.canvas) {
484
+ const rect = p.canvas.getBoundingClientRect();
485
+ const containerRect = container.getBoundingClientRect();
486
+ const distance = Math.abs(rect.top - containerRect.top);
487
+ if (distance < closestDistance) {
488
+ closestDistance = distance;
489
+ closestPage = p.pageNumber;
490
+ }
491
+ }
492
+ });
493
+ this.currentPage = closestPage;
494
+ },
495
+ async doSearch() {
496
+ if (!this.pdf || !this.searchQuery) return;
497
+ if (this.renderInProgress) return;
498
+
499
+ this.searchHighlights = [];
500
+ const numPages = this.numPages;
501
+ const searchTerm = this.searchQuery;
502
+
503
+ for (let pageNumber = 1; pageNumber <= numPages; pageNumber++) {
504
+ const page = await this.pdf.getPage(pageNumber);
505
+ const textContent = await page.getTextContent();
506
+ const items = textContent.items;
507
+ let fullText = "";
508
+ let offsetPositions = [];
509
+ let currentPos = 0;
510
+ items.forEach((item) => {
511
+ offsetPositions.push({
512
+ str: item.str,
513
+ start: currentPos,
514
+ end: currentPos + item.str.length,
515
+ transform: item.transform,
516
+ width: item.width,
517
+ });
518
+ currentPos += item.str.length;
519
+ fullText += item.str;
520
+ });
521
+
522
+ let startIndex = 0;
523
+ while ((startIndex = fullText.indexOf(searchTerm, startIndex)) !== -1) {
524
+ const endIndex = startIndex + searchTerm.length;
525
+ const matchedItems = offsetPositions.filter(
526
+ (op) => op.start <= startIndex && op.end >= endIndex
527
+ );
528
+
529
+ if (matchedItems.length > 0) {
530
+ const matchItem = matchedItems[0];
531
+ const substringLength = searchTerm.length;
532
+ const avgCharWidth = matchItem.width / matchItem.str.length;
533
+
534
+ const substringOffset = startIndex - matchItem.start;
535
+
536
+ const lineHeight = Math.abs(matchItem.transform[3]);
537
+ const y1 = matchItem.transform[5];
538
+ const y2 = y1 + lineHeight;
539
+
540
+ const xStart =
541
+ matchItem.transform[4] + substringOffset * avgCharWidth;
542
+ const xEnd = xStart + substringLength * avgCharWidth;
543
+
544
+ this.searchHighlights.push({
545
+ pageNumber,
546
+ position: [xStart, y1, xEnd, y2],
547
+ });
548
+ }
549
+ startIndex = endIndex;
550
+ }
551
+ }
552
+
553
+ this.searchTermCount = this.searchHighlights.length;
554
+ this.currentSearchIndex = 0;
555
+
556
+ const container = this.$refs.pdfContainer;
557
+ const currentScroll = container.scrollTop;
558
+ this.pages.forEach((p) => {
559
+ p.loaded = false;
560
+ this.$set(p,'loading',false)
561
+ if (p.canvas) {
562
+ const ctx = p.canvas.getContext("2d");
563
+ ctx.clearRect(0, 0, p.canvas.width, p.canvas.height);
564
+ }
565
+ });
566
+ this.$nextTick(() => {
567
+ container.scrollTop = currentScroll;
568
+ this.initObserver();
569
+ container.scrollTop = container.scrollTop + 1;
570
+ container.scrollTop = container.scrollTop - 1;
571
+
572
+ if (this.searchTermCount > 0) {
573
+ this.scrollToHighlight(this.searchHighlights[0]);
574
+ }
575
+ });
576
+ },
577
+ prevSearchResult() {
578
+ if (this.searchTermCount <= 1) return;
579
+ this.currentSearchIndex = (this.currentSearchIndex - 1 + this.searchTermCount) % this.searchTermCount;
580
+ this.scrollToHighlight(this.searchHighlights[this.currentSearchIndex]);
581
+ },
582
+ nextSearchResult() {
583
+ if (this.searchTermCount <= 1) return;
584
+ this.currentSearchIndex = (this.currentSearchIndex + 1) % this.searchTermCount;
585
+ this.scrollToHighlight(this.searchHighlights[this.currentSearchIndex]);
586
+ },
587
+ onResize() {
588
+ this.clearPages();
589
+ this.loadPdf();
590
+ // clearTimeout(this.resizeTimeout);
591
+ // this.resizeTimeout = setTimeout(() => {
592
+ // if (this.renderInProgress) return;
593
+ // const container = this.$refs.pdfContainer;
594
+ // const currentScroll = container.scrollTop;
595
+ // this.pages.forEach((p) => {
596
+ // p.loaded = false;
597
+ // this.$set(p,'loading',false)
598
+ // if (p.canvas) {
599
+ // const ctx = p.canvas.getContext('2d');
600
+ // ctx.clearRect(0,0,p.canvas.width,p.canvas.height);
601
+ // }
602
+ // });
603
+ // this.$nextTick(() => {
604
+ // container.scrollTop = currentScroll;
605
+ // this.initObserver();
606
+ // container.scrollTop = container.scrollTop + 1;
607
+ // container.scrollTop = container.scrollTop - 1;
608
+ // });
609
+ // }, 300);
610
+ },
611
+ },
612
+ async mounted() {
613
+ window.addEventListener("resize", this.onResize);
614
+ this.pdfFile = this.ossPath;
615
+ console.log('ossPath',this.ossPath)
616
+ // this.clearPages();
617
+ // this.loadPdf();
618
+ },
619
+ beforeDestroy() {
620
+ window.removeEventListener("resize", this.onResize);
621
+ if (this.observer) {
622
+ this.observer.disconnect();
623
+ }
624
+ },
625
+ watch:{
626
+ split_paragraphs:{
627
+ async handler(val){
628
+ this.highlights = [];
629
+ if (val.length > 0){
630
+ await this.setHighlights(val);
631
+ }
632
+ this.clearPages();
633
+ this.loadPdf();
634
+ },
635
+ immediate:true,
636
+ deep:true
637
+ },
638
+ ossPath:{
639
+ handler(val){
640
+ if (val){
641
+ this.pdfFile = this.ossPath;
642
+ }
643
+ },
644
+ immediate:true
645
+ }
646
+ }
647
+ };
648
+ </script>
649
+
650
+ <style lang="less" scoped>
651
+ .pdfViewPage{
652
+ width: 100%;
653
+ height: 100%;
654
+ }
655
+ .pdf-container {
656
+ display: flex;
657
+ flex-direction: column;
658
+ gap: 10px;
659
+ height: 100%;
660
+ overflow-y: auto;
661
+ overflow-x: hidden;
662
+ margin: 0 auto;
663
+ position: relative;
664
+ border-radius: 10px;
665
+ background: #ffffff;
666
+ width: 100%;
667
+ .textLayer{
668
+ span{
669
+ position: absolute;
670
+ color: transparent;
671
+ white-space:pre;
672
+ cursor: text;
673
+ transform-origin:0,0
674
+ }
675
+ }
676
+ }
677
+
678
+ .page-container {
679
+ position: relative;
680
+ display: flex;
681
+ justify-content: center;
682
+ }
683
+
684
+ .canvas-wrapper {
685
+ position: relative;
686
+ }
687
+
688
+ /* 在loading时覆盖canvas显示加载动画 */
689
+ .page-loading-overlay {
690
+ position: absolute;
691
+ top: 0;
692
+ left: 0;
693
+ right: 0;
694
+ bottom: 0;
695
+ background: rgba(255, 255, 255, 0.6);
696
+ display: flex;
697
+ align-items: center;
698
+ justify-content: center;
699
+ }
700
+
701
+ .spinner {
702
+ border: 6px solid #f3f3f3;
703
+ border-top: 6px solid #555;
704
+ border-radius: 50%;
705
+ width: 40px;
706
+ height: 40px;
707
+ animation: spin 1s linear infinite;
708
+ }
709
+
710
+ canvas {
711
+ border: 1px solid #ddd;
712
+ width: 100%;
713
+ height: auto;
714
+ margin-bottom: 10px;
715
+ }
716
+
717
+ .button-container {
718
+ display: flex;
719
+ flex-wrap: wrap;
720
+ gap: 10px;
721
+ margin: 10px 0;
722
+ justify-content: center;
723
+ }
724
+
725
+ .search-nav {
726
+ display: flex;
727
+ align-items: center;
728
+ gap: 10px;
729
+ }
730
+
731
+ input[type="file"],
732
+ button,
733
+ input[type="text"] {
734
+ padding: 10px 15px;
735
+ font-size: 16px;
736
+ border: 1px solid #ddd;
737
+ border-radius: 5px;
738
+ background-color: #f5f5f5;
739
+ cursor: pointer;
740
+ transition: all 0.2s ease-in-out;
741
+ }
742
+
743
+ button:hover,
744
+ input[type="file"]:hover,
745
+ input[type="text"]:hover {
746
+ background-color: #e0e0e0;
747
+ }
748
+
749
+ button:active,
750
+ input[type="file"]:active,
751
+ input[type="text"]:active {
752
+ background-color: #d0d0d0;
753
+ }
754
+
755
+
756
+ @keyframes spin {
757
+ 0% { transform: rotate(0deg); }
758
+ 100% { transform: rotate(360deg); }
759
+ }
760
+ </style>