askbot-dragon 1.8.34-beta → 1.8.35-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "askbot-dragon",
3
- "version": "1.8.34-beta",
3
+ "version": "1.8.35-beta",
4
4
  "scripts": {
5
5
  "serve": "vue-cli-service serve",
6
6
  "build": "vue-cli-service build",
@@ -256,6 +256,36 @@ function newInitWaterMark(elId, textValue) {
256
256
  })
257
257
  document.getElementById(elId).appendChild(oTemp);
258
258
  }
259
+ const getAllParams = (href) => {
260
+ let query = href.substring(href.indexOf("?") + 1);
261
+ let vars = query.split("&");
262
+ let obj = {};
263
+ for (let i = 0; i < vars.length; i++) {
264
+ let pair = vars[i].split("=");
265
+ // 将参数名和参数值分别作为对象的属性名和属性值
266
+ obj[pair[0]] = pair[1];
267
+ }
268
+ return obj;
269
+ }
270
+ export const setTimestamp = (url) => {
271
+ if(url.indexOf('?') !== -1) {
272
+ let newUrl = url.split('?')[0];
273
+ let params = getAllParams(url)
274
+ params['timestamp'] = new Date().getTime();
275
+ let count = 0;
276
+ for(let key in params) {
277
+ if(count === 0) {
278
+ newUrl += '?' + key + '=' + params[key];
279
+ } else {
280
+ newUrl += '&' + key + '=' + params[key];
281
+ }
282
+ count++;
283
+ }
284
+ return newUrl;
285
+ } else {
286
+ return url
287
+ }
288
+ }
259
289
  export {
260
290
  imageTypeObj,
261
291
  newInitWaterMark
@@ -78,7 +78,7 @@ if(pdfjsLib) {
78
78
  pdfjsLib.GlobalWorkerOptions.workerSrc = window['pdfjs-dist/build/pdf.worker']
79
79
  }
80
80
  const { TextLayerBuilder } = window['pdfjs-dist/web/pdf_viewer']
81
- import { newInitWaterMark } from "../../assets/js/common.js";
81
+ import { newInitWaterMark,setTimestamp } from "../../assets/js/common.js";
82
82
  import Vue from 'vue';
83
83
  export default {
84
84
  name: "PdfViewer",
@@ -88,8 +88,7 @@ export default {
88
88
  pdf: null,
89
89
  numPages: 0,
90
90
  scale: 1, // 初始缩小比例
91
- renderInProgress: false,
92
- pages: [], // { pageNumber: number, loaded: boolean, canvas: ref, loading: boolean }
91
+ pages: [], // { pageNumber: number, loaded: boolean, canvas: ref, loading: boolean, retryCount: number }
93
92
  // 静态高亮(坐标采用顶部为原点)
94
93
  highlights: [],
95
94
  searchQuery: "",
@@ -164,6 +163,7 @@ export default {
164
163
  },
165
164
  async initPdf() {
166
165
  if (!this.pdfFile) return;
166
+ this.pdfFile = setTimestamp(this.pdfFile)
167
167
  // this.pdf = await pdfjsLib.getDocument(this.pdfFile).promise;
168
168
  this.loadingTask = pdfjsLib.getDocument(this.pdfFile);
169
169
  await this.loadingTask.promise.then(async pdf => {
@@ -178,6 +178,7 @@ export default {
178
178
  loaded: false,
179
179
  canvas: null,
180
180
  loading: false,
181
+ retryCount: 0,
181
182
  }));
182
183
  // 清空搜索高亮
183
184
  this.searchHighlights = [];
@@ -204,7 +205,7 @@ export default {
204
205
  if (entry.isIntersecting) {
205
206
  const index = Number(entry.target.getAttribute('data-index'));
206
207
  const page = this.pages[index];
207
- if (!page.loaded && !page.loading) {
208
+ if (page && !page.loaded && !page.loading) {
208
209
  this.renderPage(page.pageNumber);
209
210
  }
210
211
  this.updateCurrentPage();
@@ -249,73 +250,189 @@ export default {
249
250
  await this.updateCurrentPage();
250
251
  }
251
252
  },
252
- async renderPage(pageNumber) {
253
- if (!this.pdf || this.renderInProgress) return;
253
+ async renderPage(pageNumber, retry = false) {
254
+ if (!this.pdf) return;
254
255
  const pageIndex = pageNumber - 1;
255
256
  const pageData = this.pages[pageIndex];
256
- if (pageData.loaded) return;
257
- this.pageNumber = pageNumber;
258
- const page = await this.pdf.getPage(pageNumber);
259
- let pdfWidth = page.getViewport({ scale: 1}).width
260
- const viewport = page.getViewport({ scale: this.calculateScale(pdfWidth) });
261
- this.viewport = viewport;
262
- const canvas = pageData.canvas;
263
- if (!canvas) return;
264
-
265
- const context = canvas.getContext("2d");
266
- this.context = context;
267
- const devicePixelRatio = window.devicePixelRatio || 1;
268
- canvas.width = (viewport.width) * devicePixelRatio;
269
- canvas.height = viewport.height * devicePixelRatio;
270
- canvas.style.width = `${viewport.width}px`;
271
- canvas.style.height = `${viewport.height}px`;
272
- context.scale(devicePixelRatio, devicePixelRatio);
273
-
274
- this.$set(pageData,'loading',true)
275
- this.renderInProgress = true;
276
- const renderTask = page.render({ canvasContext: context, viewport });
277
- // await renderTask.promise;
278
- let hasDoc = document.getElementById('textLayer' + pageNumber)
279
- if (hasDoc){
280
- hasDoc.parentNode.removeChild(hasDoc);
281
- // await renderTask.promise;
282
- // return
257
+ if (!pageData) return;
258
+
259
+ // 如果正在加载且不是重试,避免重复渲染
260
+ if (pageData.loading && !retry) return;
261
+ // 如果已加载且不是重试,直接返回
262
+ if (pageData.loaded && !retry) return;
263
+
264
+ // 重试机制:最多重试 3 次
265
+ const maxRetries = 3;
266
+ if (retry && pageData.retryCount >= maxRetries) {
267
+ console.error(`Max retries reached for page ${pageNumber}`);
268
+ this.$set(pageData, 'loading', false);
269
+ return;
283
270
  }
284
- await renderTask.promise.then(() => {
285
- return page.getTextContent()
286
- }).then((textContent) => {
287
- const textDiv = document.createElement('div');
288
- textDiv.setAttribute('class', 'textLayer');
289
- textDiv.setAttribute('id', 'textLayer' + pageNumber);
290
- let textLayer = new TextLayerBuilder({
291
- textLayerDiv: textDiv,
292
- pageIndex: pageIndex,
293
- viewport: viewport,
271
+
272
+ this.pageNumber = pageNumber;
273
+
274
+ try {
275
+ const page = await this.pdf.getPage(pageNumber);
276
+ let pdfWidth = page.getViewport({ scale: 1}).width;
277
+ const viewport = page.getViewport({ scale: this.calculateScale(pdfWidth) });
278
+ this.viewport = viewport;
279
+
280
+ const canvas = pageData.canvas;
281
+ if (!canvas) {
282
+ console.warn(`Canvas not found for page ${pageNumber}, will retry...`);
283
+ // 如果 canvas 不存在,增加重试计数并延迟重试
284
+ if (!retry) {
285
+ this.$set(pageData, 'retryCount', (pageData.retryCount || 0) + 1);
286
+ setTimeout(() => {
287
+ this.renderPage(pageNumber, true);
288
+ }, 200);
289
+ }
290
+ return;
291
+ }
292
+
293
+ const context = canvas.getContext("2d");
294
+ if (!context) {
295
+ console.warn(`Failed to get context for page ${pageNumber}, will retry...`);
296
+ // 如果无法获取 context,增加重试计数并延迟重试
297
+ if (!retry) {
298
+ this.$set(pageData, 'retryCount', (pageData.retryCount || 0) + 1);
299
+ setTimeout(() => {
300
+ this.renderPage(pageNumber, true);
301
+ }, 200);
302
+ }
303
+ return;
304
+ }
305
+
306
+ this.context = context;
307
+ const devicePixelRatio = window.devicePixelRatio || 1;
308
+ canvas.width = (viewport.width) * devicePixelRatio;
309
+ canvas.height = viewport.height * devicePixelRatio;
310
+ canvas.style.width = `${viewport.width}px`;
311
+ canvas.style.height = `${viewport.height}px`;
312
+ context.scale(devicePixelRatio, devicePixelRatio);
313
+
314
+ // 设置加载状态
315
+ this.$set(pageData, 'loading', true);
316
+
317
+ // 清理旧的文本层
318
+ let hasDoc = document.getElementById('textLayer' + pageNumber);
319
+ if (hasDoc && hasDoc.parentNode) {
320
+ hasDoc.parentNode.removeChild(hasDoc);
321
+ }
322
+
323
+ // 渲染 PDF 页面
324
+ const renderTask = page.render({ canvasContext: context, viewport });
325
+
326
+ // 等待渲染完成后再处理文本层和高亮
327
+ await renderTask.promise.then(async () => {
328
+ // 获取文本内容
329
+ const textContent = await page.getTextContent();
330
+
331
+ // 创建文本层
332
+ const textDiv = document.createElement('div');
333
+ textDiv.setAttribute('class', 'textLayer');
334
+ textDiv.setAttribute('id', 'textLayer' + pageNumber);
335
+ let textLayer = new TextLayerBuilder({
336
+ textLayerDiv: textDiv,
337
+ pageIndex: pageIndex,
338
+ viewport: viewport,
339
+ });
340
+
341
+ // 设置文本层样式 - 移除高度限制和 overflow hidden,避免文字被裁剪
342
+ textDiv.style.width = `${viewport.width}px`;
343
+ // 不设置固定高度,让内容自然展开,或者设置为足够大的值
344
+ // textDiv.style.height = `${viewport.height}px`; // 移除固定高度
345
+ textDiv.style.minHeight = `${viewport.height}px`; // 使用最小高度
346
+ textDiv.style.overflow = 'visible'; // 改为 visible,避免裁剪文字
347
+ textDiv.style.whiteSpace = 'pre-wrap';
348
+ textDiv.style.position = 'absolute';
349
+ textDiv.style.top = '0';
350
+ textDiv.style.left = '0';
351
+ textDiv.style.fontSize = '20px';
352
+ textDiv.style.color = 'black';
353
+ textDiv.style.pointerEvents = 'none'; // 确保文本层不阻挡交互
354
+
355
+ // 将文本 div 先添加到覆盖层中(TextLayerBuilder 需要 DOM 元素存在)
356
+ let pagesDiv = document.getElementById('canvas-wrapper' + pageIndex);
357
+ if (!pagesDiv) {
358
+ console.warn(`Canvas wrapper not found for page ${pageNumber}`);
359
+ return;
360
+ }
361
+ pagesDiv.appendChild(textDiv);
362
+
363
+ // 设置文本内容并渲染
364
+ textLayer.setTextContent(textContent);
365
+ textLayer.render();
366
+
367
+ // 等待文本层渲染完成(使用 nextTick 和 setTimeout 确保 DOM 完全更新)
368
+ await this.$nextTick();
369
+ // 额外等待一小段时间,确保 TextLayerBuilder 完成渲染
370
+ await new Promise(resolve => setTimeout(resolve, 50));
371
+
372
+ // 确保容器有足够的高度来显示所有文本
373
+ // 获取文本层的实际高度(包括所有子元素)
374
+ const textLayerHeight = Math.max(
375
+ textDiv.scrollHeight,
376
+ textDiv.offsetHeight,
377
+ viewport.height
378
+ );
379
+
380
+ // 如果文本层实际高度超过 viewport,调整容器和 canvas 的高度
381
+ if (textLayerHeight > viewport.height) {
382
+ // 调整 canvas wrapper 的最小高度
383
+ if (pagesDiv) {
384
+ pagesDiv.style.minHeight = `${textLayerHeight}px`;
385
+ }
386
+ // 调整 canvas 的显示高度(如果需要)
387
+ const canvas = pageData.canvas;
388
+ if (canvas && canvas.parentElement) {
389
+ canvas.parentElement.style.minHeight = `${textLayerHeight}px`;
390
+ }
391
+ }
392
+
393
+ // 在文本层渲染完成后,再渲染高亮区域
394
+ // 注意:高亮是在 canvas 上绘制的,不会遮挡文本层(文本层在 canvas 上方)
395
+ // 渲染已有高亮区域(静态highlights)
396
+ this.renderHighlights(context, pageNumber, viewport);
397
+ // 渲染搜索高亮区域
398
+ this.renderSearchHighlights(context, pageNumber, viewport);
399
+
400
+ // 重置重试计数
401
+ this.$set(pageData, 'retryCount', 0);
402
+ // 更新页面状态
403
+ this.$set(pageData, 'loading', false);
404
+ this.$set(pageData, 'loaded', true);
405
+ }).catch((error) => {
406
+ console.error(`Error rendering page ${pageNumber}:`, error);
407
+ // 如果渲染失败,尝试重试
408
+ if (!retry && (pageData.retryCount || 0) < maxRetries) {
409
+ this.$set(pageData, 'retryCount', (pageData.retryCount || 0) + 1);
410
+ this.$set(pageData, 'loading', false);
411
+ // 延迟后重试
412
+ setTimeout(() => {
413
+ this.renderPage(pageNumber, true);
414
+ }, 500);
415
+ } else {
416
+ // 确保在错误时也更新状态
417
+ this.$set(pageData, 'loading', false);
418
+ this.$set(pageData, 'loaded', false);
419
+ }
294
420
  });
295
- textDiv.style.width = `${viewport.width}px`; // 根据 viewport 调整宽度
296
- textDiv.style.height = `${viewport.height}px`; // 根据 viewport 调整高度(可能需要根据实际文本内容调整)
297
- textDiv.style.overflow = 'hidden'; // 如果文本内容超出范围,则隐藏它
298
- textDiv.style.whiteSpace = 'pre-wrap'; // 保留空白符和换行符
299
- textDiv.style.position = 'absolute'; // 绝对定位以覆盖 canvas
300
- textDiv.style.top = '0';
301
- textDiv.style.left = '0';
302
- textDiv.style.fontSize = '20px'; // 设置字体大小(可能需要根据实际情况调整)
303
- textDiv.style.color = 'black'; // 设置文本颜色(可能需要根据实际情况调整)
304
- // textDiv.textContent = textContent; // 设置文本内容
305
- textLayer.setTextContent(textContent);
306
- textLayer.render()
307
- // 将文本 div 添加到覆盖层中
308
- let pagesDiv = document.getElementById('canvas-wrapper' + pageIndex);
309
- pagesDiv.appendChild(textDiv)
310
- })
311
- this.renderInProgress = false;
312
- this.$set(pageData,'loading',false)
313
- this.$set(pageData,'loaded',true)
314
-
315
- // 渲染已有高亮区域(静态highlights)
316
- this.renderHighlights(context, pageNumber, viewport);
317
- // 渲染搜索高亮区域
318
- this.renderSearchHighlights(context, pageNumber, viewport);
421
+ } catch (error) {
422
+ console.error(`Error loading page ${pageNumber}:`, error);
423
+ // 如果加载失败,尝试重试
424
+ if (!retry && (pageData.retryCount || 0) < maxRetries) {
425
+ this.$set(pageData, 'retryCount', (pageData.retryCount || 0) + 1);
426
+ this.$set(pageData, 'loading', false);
427
+ // 延迟后重试
428
+ setTimeout(() => {
429
+ this.renderPage(pageNumber, true);
430
+ }, 500);
431
+ } else {
432
+ this.$set(pageData, 'loading', false);
433
+ this.$set(pageData, 'loaded', false);
434
+ }
435
+ }
319
436
  },
320
437
  calculateScale(pdfWidth) {
321
438
  const containerWidth = document.getElementById('pdfViewPage').clientWidth - 50;
@@ -347,6 +464,8 @@ export default {
347
464
  });
348
465
  },
349
466
  drawHighlight(context, viewport, position, fillStyle, fromSearch = false) {
467
+ if (!context || !position || position.length < 4) return;
468
+
350
469
  const [x1, y1, x2, y2] = position;
351
470
  const scale = viewport.scale;
352
471
  const pageHeight = viewport.height / viewport.scale;
@@ -364,16 +483,32 @@ export default {
364
483
  canvasY1 = y1 * scale;
365
484
  canvasY2 = y2 * scale;
366
485
  }
367
- // 清除当前路径(如果需要)
368
- context.beginPath();
369
-
370
- context.fillStyle = fillStyle;
371
- context.fillRect(
372
- Math.min(canvasX1, canvasX2),
373
- Math.min(canvasY1, canvasY2),
374
- Math.abs(canvasX2 - canvasX1),
375
- Math.abs(canvasY2 - canvasY1)
376
- );
486
+
487
+ // 确保坐标在有效范围内
488
+ const minX = Math.min(canvasX1, canvasX2);
489
+ const minY = Math.min(canvasY1, canvasY2);
490
+ const width = Math.abs(canvasX2 - canvasX1);
491
+ const height = Math.abs(canvasY2 - canvasY1);
492
+
493
+ // 检查坐标是否在 canvas 范围内
494
+ if (minX < 0 || minY < 0 || minX + width > viewport.width || minY + height > viewport.height) {
495
+ // 如果超出范围,进行裁剪
496
+ const clippedX = Math.max(0, minX);
497
+ const clippedY = Math.max(0, minY);
498
+ const clippedWidth = Math.min(width, viewport.width - clippedX);
499
+ const clippedHeight = Math.min(height, viewport.height - clippedY);
500
+
501
+ if (clippedWidth > 0 && clippedHeight > 0) {
502
+ context.beginPath();
503
+ context.fillStyle = fillStyle;
504
+ context.fillRect(clippedX, clippedY, clippedWidth, clippedHeight);
505
+ }
506
+ } else {
507
+ // 正常绘制
508
+ context.beginPath();
509
+ context.fillStyle = fillStyle;
510
+ context.fillRect(minX, minY, width, height);
511
+ }
377
512
  },
378
513
  handleFileUpload(event) {
379
514
  const file = event.target.files[0];
@@ -384,10 +519,26 @@ export default {
384
519
  }
385
520
  },
386
521
  async setCanvasRef() {
387
- await this.pages.forEach((page, index) => {
388
- this.$set(this.pages[index],'canvas',this.$refs.canvases[index])
389
- // 注意:这里我们直接将 DOM 元素赋值给 page.canvas,
390
- // 但通常你可能想要对 DOM 元素进行进一步的处理或封装。
522
+ // 使用 $nextTick 确保 DOM 已更新
523
+ await this.$nextTick();
524
+
525
+ // 如果 refs 还没有准备好,等待一段时间后重试
526
+ if (!this.$refs.canvases || this.$refs.canvases.length === 0) {
527
+ return new Promise((resolve) => {
528
+ setTimeout(async () => {
529
+ await this.setCanvasRef();
530
+ resolve();
531
+ }, 100);
532
+ });
533
+ }
534
+
535
+ // 设置 canvas 引用
536
+ this.pages.forEach((page, index) => {
537
+ if (this.$refs.canvases && this.$refs.canvases[index]) {
538
+ this.$set(this.pages[index], 'canvas', this.$refs.canvases[index]);
539
+ } else {
540
+ console.warn(`Canvas ref not found for page ${index + 1}`);
541
+ }
391
542
  });
392
543
  },
393
544
  clearPages() {
@@ -469,7 +620,9 @@ export default {
469
620
  },
470
621
  zoomIn() {
471
622
  this.updateCurrentPage();
472
- if (this.renderInProgress) return;
623
+ // 检查是否有页面正在加载
624
+ const hasLoadingPage = this.pages.some(p => p.loading);
625
+ if (hasLoadingPage) return;
473
626
  const container = this.$refs.pdfContainer;
474
627
  const currentScroll = container.scrollTop;
475
628
  this.scale += 0.1;
@@ -477,7 +630,9 @@ export default {
477
630
  },
478
631
  zoomOut() {
479
632
  this.updateCurrentPage();
480
- if (this.renderInProgress) return;
633
+ // 检查是否有页面正在加载
634
+ const hasLoadingPage = this.pages.some(p => p.loading);
635
+ if (hasLoadingPage) return;
481
636
  const container = this.$refs.pdfContainer;
482
637
  const currentScroll = container.scrollTop;
483
638
  this.scale = Math.max(this.scale - 0.1, 0.1);
@@ -487,10 +642,11 @@ export default {
487
642
  const pages = Vue.observable(this.pages)
488
643
  pages.forEach((p) => {
489
644
  p.loaded = false;
490
- this.$set(p,'loading',false)
645
+ this.$set(p, 'loading', false);
646
+ this.$set(p, 'retryCount', 0);
491
647
  if (p.canvas) {
492
648
  const ctx = p.canvas.getContext('2d');
493
- ctx.clearRect(0,0,p.canvas.width,p.canvas.height);
649
+ ctx.clearRect(0, 0, p.canvas.width, p.canvas.height);
494
650
  }
495
651
  });
496
652
 
@@ -522,7 +678,9 @@ export default {
522
678
  },
523
679
  async doSearch() {
524
680
  if (!this.pdf || !this.searchQuery) return;
525
- if (this.renderInProgress) return;
681
+ // 检查是否有页面正在加载
682
+ const hasLoadingPage = this.pages.some(p => p.loading);
683
+ if (hasLoadingPage) return;
526
684
 
527
685
  this.searchHighlights = [];
528
686
  const numPages = this.numPages;
@@ -585,7 +743,8 @@ export default {
585
743
  const currentScroll = container.scrollTop;
586
744
  this.pages.forEach((p) => {
587
745
  p.loaded = false;
588
- this.$set(p,'loading',false)
746
+ this.$set(p, 'loading', false);
747
+ this.$set(p, 'retryCount', 0);
589
748
  if (p.canvas) {
590
749
  const ctx = p.canvas.getContext("2d");
591
750
  ctx.clearRect(0, 0, p.canvas.width, p.canvas.height);
@@ -615,16 +774,19 @@ export default {
615
774
  onResize() {
616
775
  clearTimeout(this.resizeTimeout);
617
776
  this.resizeTimeout = setTimeout(() => {
618
- if (this.renderInProgress) return;
777
+ // 检查是否有页面正在加载
778
+ const hasLoadingPage = this.pages.some(p => p.loading);
779
+ if (hasLoadingPage) return;
619
780
  const container = this.$refs.pdfContainer;
620
781
  const currentScroll = container.scrollTop;
621
782
  const pages = Vue.observable(this.pages)
622
783
  pages.forEach((p) => {
623
784
  p.loaded = false;
624
- this.$set(p,'loading',false)
785
+ this.$set(p, 'loading', false);
786
+ this.$set(p, 'retryCount', 0);
625
787
  if (p.canvas) {
626
788
  const ctx = p.canvas.getContext('2d');
627
- ctx.clearRect(0,0,p.canvas.width,p.canvas.height);
789
+ ctx.clearRect(0, 0, p.canvas.width, p.canvas.height);
628
790
  }
629
791
  });
630
792
  this.$nextTick(() => {
@@ -712,11 +874,15 @@ export default {
712
874
  display: flex;
713
875
  justify-content: center;
714
876
  width: 100%;
877
+ min-height: fit-content; /* 确保容器能容纳所有内容 */
878
+ overflow: visible; /* 允许内容超出,避免裁剪 */
715
879
  }
716
880
 
717
881
  .canvas-wrapper {
718
882
  position: relative;
719
883
  width: 100%;
884
+ min-height: 100%; /* 确保容器有足够高度 */
885
+ overflow: visible; /* 允许内容超出,避免裁剪 */
720
886
  }
721
887
 
722
888
  /* 在loading时覆盖canvas显示加载动画 */