askbot-dragon 88.0.43 → 88.1.1

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