askbot-dragon 1.8.34-beta → 1.8.36-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
package/src/assets/js/common.js
CHANGED
|
@@ -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
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
<!-- <div v-for="(item,index) in processAction" :key="index">
|
|
82
82
|
<association-intention :msg="item" :msgType="item.type" :isOpen="isOpen"></association-intention>
|
|
83
83
|
</div> -->
|
|
84
|
+
<pdf-view :split_paragraphs="splitParagraph" :ossPath="ossPath" :isPC="true" ></pdf-view>
|
|
84
85
|
</div>
|
|
85
86
|
</template>
|
|
86
87
|
<script>
|
|
@@ -122,9 +123,11 @@ import WelcomeKnowledgeFile from "./welcomeKnowledgeFile";
|
|
|
122
123
|
import WelcomeLlmCard from "./welcomeLlmCard";
|
|
123
124
|
import welcomeSuggest from "./welcomeSuggest"
|
|
124
125
|
import QwFeedback from './QwFeedback';
|
|
126
|
+
import PdfView from "./preview/pdfView";
|
|
125
127
|
export default {
|
|
126
128
|
name: 'ConversationContainer',
|
|
127
129
|
components: {
|
|
130
|
+
PdfView,
|
|
128
131
|
WelcomeLlmCard,
|
|
129
132
|
WelcomeKnowledgeFile,
|
|
130
133
|
// ActionAlert,
|
|
@@ -159,6 +162,572 @@ export default {
|
|
|
159
162
|
},
|
|
160
163
|
data() {
|
|
161
164
|
return {
|
|
165
|
+
splitParagraph:[{
|
|
166
|
+
"paragraph": "AI 赋能累积超过 3000 家企业和组织 企业为什么要用 AI? 不用 AI,你正在被同行淘汰 AI 客服 东莞某制造业企业 用了 AI 客服。客户响 应时间从 30 分钟,减 AI 询盘 义乌贸易公司,用 AI 询盘,自动翻译产品 AI 质检 某头部物业管理集 团,用 AI 检查晨会是 否达标,规范。 少到 10 秒。订单转化 率提升了 30%。 客服人员从 3 人减少 到 1 个。 描述,海外询盘量增 加 50% 节省 300 万/年管理费 用。 不用 AI 的风险 人工成本高 人工客服 5000 月 薪,6 万年薪。 AI 客服,成本不超过 1 万一年。 效率跟不上 人工,一周 1 个招商 方案,还不满意。 AI,一周 6 个方案, 随便选。 错失机会 秒回复 客户早跑了 企业 AI 落地怎么做? 企业AI落地",
|
|
167
|
+
"edit_paragraph": null,
|
|
168
|
+
"original_paragraph": [
|
|
169
|
+
{
|
|
170
|
+
"text": "AI 赋能累积超过 3000 家企业和组织",
|
|
171
|
+
"title": {
|
|
172
|
+
"number": "35",
|
|
173
|
+
"level": 1,
|
|
174
|
+
"text": "AI 赋能累积超过 3000 家企业和组织"
|
|
175
|
+
},
|
|
176
|
+
"paragraph_id": "",
|
|
177
|
+
"images": [],
|
|
178
|
+
"page": 1,
|
|
179
|
+
"position": [
|
|
180
|
+
"91.5",
|
|
181
|
+
"419.37396240234375",
|
|
182
|
+
"318.358154296875",
|
|
183
|
+
"433.3689880371094"
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"text": "",
|
|
188
|
+
"title": null,
|
|
189
|
+
"paragraph_id": "",
|
|
190
|
+
"images": [
|
|
191
|
+
{
|
|
192
|
+
"id": "65",
|
|
193
|
+
"oss_image_url": "https://guoranwisdom.oss-cn-zhangjiakou.aliyuncs.com/ab0f90737c8b4f2d85ba2157e4473110/2025/12/25/10/53/24/694ca7050229e8779290cadf/page-1-image-65.png",
|
|
194
|
+
"ocr_image_text": null,
|
|
195
|
+
"is_deleted": null,
|
|
196
|
+
"position": null,
|
|
197
|
+
"page": 1
|
|
198
|
+
}
|
|
199
|
+
],
|
|
200
|
+
"page": 1,
|
|
201
|
+
"position": [
|
|
202
|
+
"380.484375",
|
|
203
|
+
"359.2138977050781",
|
|
204
|
+
"535.1282958984375",
|
|
205
|
+
"371.2138977050781"
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"text": "企业为什么要用 AI?",
|
|
210
|
+
"title": null,
|
|
211
|
+
"paragraph_id": "",
|
|
212
|
+
"images": [],
|
|
213
|
+
"page": 1,
|
|
214
|
+
"position": [
|
|
215
|
+
"131.90625",
|
|
216
|
+
"531.6300048828125",
|
|
217
|
+
"463.5747985839844",
|
|
218
|
+
"567.6300048828125"
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"text": "不用 AI,你正在被同行淘汰",
|
|
223
|
+
"title": null,
|
|
224
|
+
"paragraph_id": "",
|
|
225
|
+
"images": [],
|
|
226
|
+
"page": 1,
|
|
227
|
+
"position": [
|
|
228
|
+
"187.828125",
|
|
229
|
+
"656.1900024414062",
|
|
230
|
+
"407.6615295410156",
|
|
231
|
+
"674.1900024414062"
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"text": "AI 客服",
|
|
236
|
+
"title": null,
|
|
237
|
+
"paragraph_id": "",
|
|
238
|
+
"images": [],
|
|
239
|
+
"page": 1,
|
|
240
|
+
"position": [
|
|
241
|
+
"109.7578125",
|
|
242
|
+
"710.3739624023438",
|
|
243
|
+
"154.717529296875",
|
|
244
|
+
"724.3689575195312"
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"text": "东莞某制造业企业",
|
|
249
|
+
"title": null,
|
|
250
|
+
"paragraph_id": "",
|
|
251
|
+
"images": [],
|
|
252
|
+
"page": 1,
|
|
253
|
+
"position": [
|
|
254
|
+
"63.0",
|
|
255
|
+
"735.1239624023438",
|
|
256
|
+
"174.9403076171875",
|
|
257
|
+
"749.1189575195312"
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"text": "用了 AI 客服。客户响",
|
|
262
|
+
"title": null,
|
|
263
|
+
"paragraph_id": "",
|
|
264
|
+
"images": [],
|
|
265
|
+
"page": 1,
|
|
266
|
+
"position": [
|
|
267
|
+
"63.0",
|
|
268
|
+
"759.8739624023438",
|
|
269
|
+
"194.99111938476562",
|
|
270
|
+
"773.8689575195312"
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"text": "应时间从 30 分钟,减",
|
|
275
|
+
"title": null,
|
|
276
|
+
"paragraph_id": "",
|
|
277
|
+
"images": [],
|
|
278
|
+
"page": 1,
|
|
279
|
+
"position": [
|
|
280
|
+
"63.0",
|
|
281
|
+
"782.3739624023438",
|
|
282
|
+
"199.00686645507812",
|
|
283
|
+
"796.3689575195312"
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"text": "AI 询盘",
|
|
288
|
+
"title": null,
|
|
289
|
+
"paragraph_id": "",
|
|
290
|
+
"images": [],
|
|
291
|
+
"page": 1,
|
|
292
|
+
"position": [
|
|
293
|
+
"275.25",
|
|
294
|
+
"710.3739624023438",
|
|
295
|
+
"320.209716796875",
|
|
296
|
+
"724.3689575195312"
|
|
297
|
+
]
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"text": "义乌贸易公司,用 AI",
|
|
301
|
+
"title": null,
|
|
302
|
+
"paragraph_id": "",
|
|
303
|
+
"images": [],
|
|
304
|
+
"page": 1,
|
|
305
|
+
"position": [
|
|
306
|
+
"228.4921875",
|
|
307
|
+
"735.1239624023438",
|
|
308
|
+
"360.4811096191406",
|
|
309
|
+
"749.1189575195312"
|
|
310
|
+
]
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"text": "询盘,自动翻译产品",
|
|
314
|
+
"title": null,
|
|
315
|
+
"paragraph_id": "",
|
|
316
|
+
"images": [],
|
|
317
|
+
"page": 1,
|
|
318
|
+
"position": [
|
|
319
|
+
"228.4921875",
|
|
320
|
+
"757.6239624023438",
|
|
321
|
+
"354.4246826171875",
|
|
322
|
+
"771.6189575195312"
|
|
323
|
+
]
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"text": "AI 质检",
|
|
327
|
+
"title": null,
|
|
328
|
+
"paragraph_id": "",
|
|
329
|
+
"images": [],
|
|
330
|
+
"page": 1,
|
|
331
|
+
"position": [
|
|
332
|
+
"440.7421875",
|
|
333
|
+
"710.3739624023438",
|
|
334
|
+
"485.701904296875",
|
|
335
|
+
"724.3689575195312"
|
|
336
|
+
]
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"text": "某头部物业管理集",
|
|
340
|
+
"title": null,
|
|
341
|
+
"paragraph_id": "",
|
|
342
|
+
"images": [],
|
|
343
|
+
"page": 1,
|
|
344
|
+
"position": [
|
|
345
|
+
"393.984375",
|
|
346
|
+
"735.1239624023438",
|
|
347
|
+
"505.9246826171875",
|
|
348
|
+
"749.1189575195312"
|
|
349
|
+
]
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
"text": "团,用 AI 检查晨会是",
|
|
353
|
+
"title": null,
|
|
354
|
+
"paragraph_id": "",
|
|
355
|
+
"images": [],
|
|
356
|
+
"page": 1,
|
|
357
|
+
"position": [
|
|
358
|
+
"393.984375",
|
|
359
|
+
"757.6239624023438",
|
|
360
|
+
"525.9755249023438",
|
|
361
|
+
"771.6189575195312"
|
|
362
|
+
]
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"text": "否达标,规范。",
|
|
366
|
+
"title": null,
|
|
367
|
+
"paragraph_id": "",
|
|
368
|
+
"images": [],
|
|
369
|
+
"page": 1,
|
|
370
|
+
"position": [
|
|
371
|
+
"393.984375",
|
|
372
|
+
"780.8739624023438",
|
|
373
|
+
"491.9324951171875",
|
|
374
|
+
"794.8689575195312"
|
|
375
|
+
]
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"text": "少到 10 秒。订单转化",
|
|
379
|
+
"title": null,
|
|
380
|
+
"paragraph_id": "",
|
|
381
|
+
"images": [],
|
|
382
|
+
"page": 2,
|
|
383
|
+
"position": [
|
|
384
|
+
"63.0",
|
|
385
|
+
"40.62397766113281",
|
|
386
|
+
"199.00686645507812",
|
|
387
|
+
"54.61897659301758"
|
|
388
|
+
]
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
"text": "率提升了 30%。",
|
|
392
|
+
"title": null,
|
|
393
|
+
"paragraph_id": "",
|
|
394
|
+
"images": [],
|
|
395
|
+
"page": 2,
|
|
396
|
+
"position": [
|
|
397
|
+
"63.0",
|
|
398
|
+
"63.12397766113281",
|
|
399
|
+
"166.7548828125",
|
|
400
|
+
"77.11897277832031"
|
|
401
|
+
]
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"text": "客服人员从 3 人减少",
|
|
405
|
+
"title": null,
|
|
406
|
+
"paragraph_id": "",
|
|
407
|
+
"images": [],
|
|
408
|
+
"page": 2,
|
|
409
|
+
"position": [
|
|
410
|
+
"63.0",
|
|
411
|
+
"87.87397766113281",
|
|
412
|
+
"190.05186462402344",
|
|
413
|
+
"101.86897277832031"
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"text": "到 1 个。",
|
|
418
|
+
"title": null,
|
|
419
|
+
"paragraph_id": "",
|
|
420
|
+
"images": [],
|
|
421
|
+
"page": 2,
|
|
422
|
+
"position": [
|
|
423
|
+
"63.0",
|
|
424
|
+
"111.12397766113281",
|
|
425
|
+
"120.09093475341797",
|
|
426
|
+
"125.11897277832031"
|
|
427
|
+
]
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
"text": "描述,海外询盘量增",
|
|
431
|
+
"title": null,
|
|
432
|
+
"paragraph_id": "",
|
|
433
|
+
"images": [],
|
|
434
|
+
"page": 2,
|
|
435
|
+
"position": [
|
|
436
|
+
"228.4921875",
|
|
437
|
+
"40.62397766113281",
|
|
438
|
+
"354.4246826171875",
|
|
439
|
+
"54.61897659301758"
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"text": "加 50%",
|
|
444
|
+
"title": null,
|
|
445
|
+
"paragraph_id": "",
|
|
446
|
+
"images": [],
|
|
447
|
+
"page": 2,
|
|
448
|
+
"position": [
|
|
449
|
+
"228.4921875",
|
|
450
|
+
"63.12397766113281",
|
|
451
|
+
"276.278076171875",
|
|
452
|
+
"77.11897277832031"
|
|
453
|
+
]
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
"text": "节省 300 万/年管理费",
|
|
457
|
+
"title": null,
|
|
458
|
+
"paragraph_id": "",
|
|
459
|
+
"images": [],
|
|
460
|
+
"page": 2,
|
|
461
|
+
"position": [
|
|
462
|
+
"393.984375",
|
|
463
|
+
"40.62397766113281",
|
|
464
|
+
"530.6068725585938",
|
|
465
|
+
"54.61897659301758"
|
|
466
|
+
]
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"text": "用。",
|
|
470
|
+
"title": null,
|
|
471
|
+
"paragraph_id": "",
|
|
472
|
+
"images": [],
|
|
473
|
+
"page": 2,
|
|
474
|
+
"position": [
|
|
475
|
+
"393.984375",
|
|
476
|
+
"63.12397766113281",
|
|
477
|
+
"421.9715576171875",
|
|
478
|
+
"77.11897277832031"
|
|
479
|
+
]
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"text": "不用 AI 的风险",
|
|
483
|
+
"title": null,
|
|
484
|
+
"paragraph_id": "",
|
|
485
|
+
"images": [],
|
|
486
|
+
"page": 2,
|
|
487
|
+
"position": [
|
|
488
|
+
"239.84765625",
|
|
489
|
+
"191.19000244140625",
|
|
490
|
+
"355.641357421875",
|
|
491
|
+
"209.19000244140625"
|
|
492
|
+
]
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"text": "人工成本高",
|
|
496
|
+
"title": null,
|
|
497
|
+
"paragraph_id": "",
|
|
498
|
+
"images": [],
|
|
499
|
+
"page": 2,
|
|
500
|
+
"position": [
|
|
501
|
+
"97.265625",
|
|
502
|
+
"245.3739776611328",
|
|
503
|
+
"167.2293701171875",
|
|
504
|
+
"259.3689880371094"
|
|
505
|
+
]
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
"text": "人工客服 5000 月",
|
|
509
|
+
"title": null,
|
|
510
|
+
"paragraph_id": "",
|
|
511
|
+
"images": [],
|
|
512
|
+
"page": 2,
|
|
513
|
+
"position": [
|
|
514
|
+
"63.0",
|
|
515
|
+
"270.12396240234375",
|
|
516
|
+
"174.9403076171875",
|
|
517
|
+
"284.1189880371094"
|
|
518
|
+
]
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
"text": "薪,6 万年薪。",
|
|
522
|
+
"title": null,
|
|
523
|
+
"paragraph_id": "",
|
|
524
|
+
"images": [],
|
|
525
|
+
"page": 2,
|
|
526
|
+
"position": [
|
|
527
|
+
"63.0",
|
|
528
|
+
"292.62396240234375",
|
|
529
|
+
"158.9892120361328",
|
|
530
|
+
"306.6189880371094"
|
|
531
|
+
]
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"text": "AI 客服,成本不超过",
|
|
535
|
+
"title": null,
|
|
536
|
+
"paragraph_id": "",
|
|
537
|
+
"images": [],
|
|
538
|
+
"page": 2,
|
|
539
|
+
"position": [
|
|
540
|
+
"63.0",
|
|
541
|
+
"317.37396240234375",
|
|
542
|
+
"194.9889373779297",
|
|
543
|
+
"331.3689880371094"
|
|
544
|
+
]
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
"text": "1 万一年。",
|
|
548
|
+
"title": {
|
|
549
|
+
"number": "35.1",
|
|
550
|
+
"level": 2,
|
|
551
|
+
"text": "1 万一年。"
|
|
552
|
+
},
|
|
553
|
+
"paragraph_id": "",
|
|
554
|
+
"images": [],
|
|
555
|
+
"page": 2,
|
|
556
|
+
"position": [
|
|
557
|
+
"63.0",
|
|
558
|
+
"340.62396240234375",
|
|
559
|
+
"131.0048370361328",
|
|
560
|
+
"354.6189880371094"
|
|
561
|
+
]
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
"text": "效率跟不上",
|
|
565
|
+
"title": null,
|
|
566
|
+
"paragraph_id": "",
|
|
567
|
+
"images": [],
|
|
568
|
+
"page": 2,
|
|
569
|
+
"position": [
|
|
570
|
+
"262.7578125",
|
|
571
|
+
"245.3739776611328",
|
|
572
|
+
"332.7215576171875",
|
|
573
|
+
"259.3689880371094"
|
|
574
|
+
]
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
"text": "人工,一周 1 个招商",
|
|
578
|
+
"title": null,
|
|
579
|
+
"paragraph_id": "",
|
|
580
|
+
"images": [],
|
|
581
|
+
"page": 2,
|
|
582
|
+
"position": [
|
|
583
|
+
"228.4921875",
|
|
584
|
+
"270.12396240234375",
|
|
585
|
+
"355.5440673828125",
|
|
586
|
+
"284.1189880371094"
|
|
587
|
+
]
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
"text": "方案,还不满意。",
|
|
591
|
+
"title": null,
|
|
592
|
+
"paragraph_id": "",
|
|
593
|
+
"images": [],
|
|
594
|
+
"page": 2,
|
|
595
|
+
"position": [
|
|
596
|
+
"228.4921875",
|
|
597
|
+
"292.62396240234375",
|
|
598
|
+
"340.4324951171875",
|
|
599
|
+
"306.6189880371094"
|
|
600
|
+
]
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
"text": "AI,一周 6 个方案,",
|
|
604
|
+
"title": {
|
|
605
|
+
"number": "35.1.1",
|
|
606
|
+
"level": 3,
|
|
607
|
+
"text": "AI,一周 6 个方案,"
|
|
608
|
+
},
|
|
609
|
+
"paragraph_id": "",
|
|
610
|
+
"images": [],
|
|
611
|
+
"page": 2,
|
|
612
|
+
"position": [
|
|
613
|
+
"228.4921875",
|
|
614
|
+
"317.37396240234375",
|
|
615
|
+
"355.4461364746094",
|
|
616
|
+
"331.3689880371094"
|
|
617
|
+
]
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
"text": "随便选。",
|
|
621
|
+
"title": null,
|
|
622
|
+
"paragraph_id": "",
|
|
623
|
+
"images": [],
|
|
624
|
+
"page": 2,
|
|
625
|
+
"position": [
|
|
626
|
+
"228.4921875",
|
|
627
|
+
"340.62396240234375",
|
|
628
|
+
"284.4637451171875",
|
|
629
|
+
"354.6189880371094"
|
|
630
|
+
]
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
"text": "错失机会",
|
|
634
|
+
"title": null,
|
|
635
|
+
"paragraph_id": "",
|
|
636
|
+
"images": [],
|
|
637
|
+
"page": 2,
|
|
638
|
+
"position": [
|
|
639
|
+
"435.24609375",
|
|
640
|
+
"245.3739776611328",
|
|
641
|
+
"491.2176513671875",
|
|
642
|
+
"259.3689880371094"
|
|
643
|
+
]
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
"text": "秒回复",
|
|
647
|
+
"title": null,
|
|
648
|
+
"paragraph_id": "",
|
|
649
|
+
"images": [],
|
|
650
|
+
"page": 2,
|
|
651
|
+
"position": [
|
|
652
|
+
"393.984375",
|
|
653
|
+
"292.62396240234375",
|
|
654
|
+
"435.9637451171875",
|
|
655
|
+
"306.6189880371094"
|
|
656
|
+
]
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
"text": "客户早跑了",
|
|
660
|
+
"title": null,
|
|
661
|
+
"paragraph_id": "",
|
|
662
|
+
"images": [],
|
|
663
|
+
"page": 2,
|
|
664
|
+
"position": [
|
|
665
|
+
"393.984375",
|
|
666
|
+
"317.37396240234375",
|
|
667
|
+
"463.9481201171875",
|
|
668
|
+
"331.3689880371094"
|
|
669
|
+
]
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
"text": "企业 AI 落地怎么做?",
|
|
673
|
+
"title": null,
|
|
674
|
+
"paragraph_id": "",
|
|
675
|
+
"images": [],
|
|
676
|
+
"page": 2,
|
|
677
|
+
"position": [
|
|
678
|
+
"127.9453125",
|
|
679
|
+
"446.1300048828125",
|
|
680
|
+
"467.5339050292969",
|
|
681
|
+
"482.1300048828125"
|
|
682
|
+
]
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
"text": "企业AI落地",
|
|
686
|
+
"title": null,
|
|
687
|
+
"paragraph_id": "",
|
|
688
|
+
"images": [
|
|
689
|
+
{
|
|
690
|
+
"id": "152",
|
|
691
|
+
"oss_image_url": "https://guoranwisdom.oss-cn-zhangjiakou.aliyuncs.com/ab0f90737c8b4f2d85ba2157e4473110/2025/12/25/10/53/25/694ca7050229e8779290cadf/page-2-image-152.png",
|
|
692
|
+
"ocr_image_text": null,
|
|
693
|
+
"is_deleted": null,
|
|
694
|
+
"position": null,
|
|
695
|
+
"page": 2
|
|
696
|
+
}
|
|
697
|
+
],
|
|
698
|
+
"page": 2,
|
|
699
|
+
"position": [
|
|
700
|
+
"153.8671875",
|
|
701
|
+
"563.7802124023438",
|
|
702
|
+
"333.6154479980469",
|
|
703
|
+
"600.1043090820312"
|
|
704
|
+
]
|
|
705
|
+
}
|
|
706
|
+
],
|
|
707
|
+
"images": [
|
|
708
|
+
{
|
|
709
|
+
"id": "65",
|
|
710
|
+
"oss_image_url": "https://guoranwisdom.oss-cn-zhangjiakou.aliyuncs.com/ab0f90737c8b4f2d85ba2157e4473110/2025/12/25/10/53/24/694ca7050229e8779290cadf/page-1-image-65.png",
|
|
711
|
+
"ocr_image_text": null,
|
|
712
|
+
"is_deleted": null,
|
|
713
|
+
"position": null,
|
|
714
|
+
"page": 1
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
"id": "152",
|
|
718
|
+
"oss_image_url": "https://guoranwisdom.oss-cn-zhangjiakou.aliyuncs.com/ab0f90737c8b4f2d85ba2157e4473110/2025/12/25/10/53/25/694ca7050229e8779290cadf/page-2-image-152.png",
|
|
719
|
+
"ocr_image_text": null,
|
|
720
|
+
"is_deleted": null,
|
|
721
|
+
"position": null,
|
|
722
|
+
"page": 2
|
|
723
|
+
}
|
|
724
|
+
],
|
|
725
|
+
"html": null,
|
|
726
|
+
"html_result": null,
|
|
727
|
+
"page": null,
|
|
728
|
+
"paragraph_id": "7f3c8b9d-ab14-40e1-8975-b021447d3696"
|
|
729
|
+
}],
|
|
730
|
+
ossPath:"https://guoranwisdom.oss-cn-zhangjiakou.aliyuncs.com/ab0f90737c8b4f2d85ba2157e4473110/2025/12/25/10/52/de1dc26afbf782f49858056888d77db3/ede1dc26afbf782f.pdf?Expires=1766643431&OSSAccessKeyId=LTAI4Fr7hD1XCFb5zMNjMcGy&Signature=lzG%2BkN2WCAcG1UbZITWECiQSLng%3D",
|
|
162
731
|
black: '',
|
|
163
732
|
welcomeSuggest: {
|
|
164
733
|
content: {
|
|
@@ -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",
|
|
@@ -164,6 +164,7 @@ export default {
|
|
|
164
164
|
},
|
|
165
165
|
async initPdf() {
|
|
166
166
|
if (!this.pdfFile) return;
|
|
167
|
+
this.pdfFile = setTimestamp(this.pdfFile)
|
|
167
168
|
// this.pdf = await pdfjsLib.getDocument(this.pdfFile).promise;
|
|
168
169
|
this.loadingTask = pdfjsLib.getDocument(this.pdfFile);
|
|
169
170
|
await this.loadingTask.promise.then(async pdf => {
|
|
@@ -183,16 +184,23 @@ export default {
|
|
|
183
184
|
this.searchHighlights = [];
|
|
184
185
|
this.searchTermCount = 0;
|
|
185
186
|
this.currentSearchIndex = 0;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
// 确保 DOM 更新完成后再设置 canvas 引用
|
|
188
|
+
await this.$nextTick();
|
|
189
|
+
await this.setCanvasRef();
|
|
190
|
+
}).catch(error => {
|
|
191
|
+
console.error('PDF 加载失败:', error);
|
|
189
192
|
})
|
|
190
193
|
|
|
191
194
|
},
|
|
192
195
|
async loadPdf() {
|
|
193
196
|
if (!this.pdfFile) return;
|
|
194
197
|
await this.initPdf();
|
|
195
|
-
|
|
198
|
+
// 确保 canvas 引用设置完成后再初始化观察器
|
|
199
|
+
await this.$nextTick();
|
|
200
|
+
// 延迟初始化观察器,确保所有 canvas 都已准备好
|
|
201
|
+
setTimeout(() => {
|
|
202
|
+
this.initObserver();
|
|
203
|
+
}, 200);
|
|
196
204
|
},
|
|
197
205
|
initObserver(type) {
|
|
198
206
|
if (this.observer) {
|
|
@@ -204,8 +212,8 @@ export default {
|
|
|
204
212
|
if (entry.isIntersecting) {
|
|
205
213
|
const index = Number(entry.target.getAttribute('data-index'));
|
|
206
214
|
const page = this.pages[index];
|
|
207
|
-
if (!page.loaded && !page.loading) {
|
|
208
|
-
|
|
215
|
+
if (page && !page.loaded && !page.loading) {
|
|
216
|
+
this.renderPage(page.pageNumber);
|
|
209
217
|
}
|
|
210
218
|
this.updateCurrentPage();
|
|
211
219
|
}
|
|
@@ -217,7 +225,9 @@ export default {
|
|
|
217
225
|
threshold: 0.1,
|
|
218
226
|
}
|
|
219
227
|
);
|
|
220
|
-
|
|
228
|
+
// 确保 canvas 引用都已设置好后再开始观察
|
|
229
|
+
this.$nextTick(() => {
|
|
230
|
+
// 增加延迟确保所有 canvas 引用都已设置
|
|
221
231
|
setTimeout(() => {
|
|
222
232
|
this.pages.forEach((page, index) => {
|
|
223
233
|
const canvas = page.canvas;
|
|
@@ -226,7 +236,7 @@ export default {
|
|
|
226
236
|
this.observer.observe(canvas);
|
|
227
237
|
}
|
|
228
238
|
});
|
|
229
|
-
},
|
|
239
|
+
},500)
|
|
230
240
|
if (type !== 'changeScale' && type !== 'onResize'){
|
|
231
241
|
setTimeout(async () => {
|
|
232
242
|
let currentPage = this.split_paragraphs.length > 0 ? this.split_paragraphs[0] : null;
|
|
@@ -250,78 +260,160 @@ export default {
|
|
|
250
260
|
}
|
|
251
261
|
},
|
|
252
262
|
async renderPage(pageNumber) {
|
|
253
|
-
if (!this.pdf
|
|
263
|
+
if (!this.pdf) return;
|
|
254
264
|
const pageIndex = pageNumber - 1;
|
|
255
265
|
const pageData = this.pages[pageIndex];
|
|
256
|
-
if (pageData
|
|
266
|
+
if (!pageData) return;
|
|
267
|
+
// 如果已经在加载或已加载,避免重复渲染
|
|
268
|
+
if (pageData.loaded || pageData.loading) return;
|
|
269
|
+
|
|
257
270
|
this.pageNumber = pageNumber;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
// 标记为加载中,防止重复渲染
|
|
274
|
+
this.$set(pageData, 'loading', true);
|
|
275
|
+
|
|
276
|
+
const page = await this.pdf.getPage(pageNumber);
|
|
277
|
+
let pdfWidth = page.getViewport({ scale: 1 }).width;
|
|
278
|
+
const viewport = page.getViewport({ scale: this.calculateScale(pdfWidth) });
|
|
279
|
+
this.viewport = viewport;
|
|
280
|
+
|
|
281
|
+
const canvas = pageData.canvas;
|
|
282
|
+
if (!canvas) {
|
|
283
|
+
console.warn(`页面 ${pageNumber} 的 canvas 引用不存在`);
|
|
284
|
+
this.$set(pageData, 'loading', false);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const context = canvas.getContext("2d");
|
|
289
|
+
if (!context) {
|
|
290
|
+
console.warn(`页面 ${pageNumber} 无法获取 canvas context`);
|
|
291
|
+
this.$set(pageData, 'loading', false);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
this.context = context;
|
|
296
|
+
|
|
297
|
+
// 计算输出缩放比例(与 vuePdf 保持一致)
|
|
298
|
+
const outputScale = this.calculateScale(pdfWidth);
|
|
299
|
+
|
|
300
|
+
// 计算实际显示的尺寸
|
|
301
|
+
let domWidth = Math.floor(viewport.width);
|
|
302
|
+
let domHeight = Math.floor(viewport.height);
|
|
303
|
+
|
|
304
|
+
// 设置 canvas 尺寸(与 vuePdf 保持一致,使用 outputScale)
|
|
305
|
+
// 注意:vuePdf 不使用 devicePixelRatio,为了保持一致,我们也只使用 outputScale
|
|
306
|
+
canvas.width = Math.floor(viewport.width * outputScale);
|
|
307
|
+
canvas.height = Math.floor(viewport.height * outputScale);
|
|
308
|
+
canvas.style.width = `${domWidth}px`;
|
|
309
|
+
canvas.style.height = `${domHeight}px`;
|
|
310
|
+
|
|
311
|
+
// 创建 transform 矩阵(与 vuePdf 保持一致)
|
|
312
|
+
const transform = outputScale !== 1
|
|
313
|
+
? [outputScale, 0, 0, outputScale, 0, 0]
|
|
314
|
+
: null;
|
|
315
|
+
|
|
316
|
+
// 清除旧的文本层
|
|
317
|
+
let hasDoc = document.getElementById('textLayer' + pageNumber);
|
|
318
|
+
if (hasDoc) {
|
|
319
|
+
hasDoc.parentNode.removeChild(hasDoc);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// 渲染 PDF 页面到 canvas(传入 transform 确保正确渲染)
|
|
323
|
+
const renderTask = page.render({
|
|
324
|
+
canvasContext: context,
|
|
293
325
|
viewport: viewport,
|
|
326
|
+
transform: transform
|
|
294
327
|
});
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
328
|
+
await renderTask.promise;
|
|
329
|
+
|
|
330
|
+
// 渲染文本层(必须在高亮渲染之前,确保文本层在最上层)
|
|
331
|
+
try {
|
|
332
|
+
const textContent = await page.getTextContent();
|
|
333
|
+
const textDiv = document.createElement('div');
|
|
334
|
+
textDiv.setAttribute('class', 'textLayer');
|
|
335
|
+
textDiv.setAttribute('id', 'textLayer' + pageNumber);
|
|
336
|
+
// 关键:TextLayerBuilder 也需要传入 transform,确保文本位置正确
|
|
337
|
+
let textLayer = new TextLayerBuilder({
|
|
338
|
+
textLayerDiv: textDiv,
|
|
339
|
+
pageIndex: pageIndex,
|
|
340
|
+
viewport: viewport,
|
|
341
|
+
transform: transform, // 添加 transform 参数
|
|
342
|
+
});
|
|
343
|
+
// 使用 domWidth 和 domHeight(与 vuePdf 保持一致)
|
|
344
|
+
textDiv.style.width = `${domWidth}px`;
|
|
345
|
+
textDiv.style.height = `${domHeight}px`;
|
|
346
|
+
// 使用 hidden 但确保尺寸正确(与 vuePdf 保持一致)
|
|
347
|
+
textDiv.style.overflow = 'hidden';
|
|
348
|
+
textDiv.style.whiteSpace = 'pre-wrap';
|
|
349
|
+
textDiv.style.position = 'absolute';
|
|
350
|
+
textDiv.style.top = '0';
|
|
351
|
+
textDiv.style.left = '0';
|
|
352
|
+
textDiv.style.fontSize = '20px';
|
|
353
|
+
textDiv.style.color = 'black';
|
|
354
|
+
textLayer.setTextContent(textContent);
|
|
355
|
+
textLayer.render();
|
|
356
|
+
|
|
357
|
+
// 渲染完成后,检查文本层实际内容高度,如果超出则调整
|
|
358
|
+
await this.$nextTick();
|
|
359
|
+
const textLayerSpans = textDiv.querySelectorAll('span');
|
|
360
|
+
if (textLayerSpans.length > 0) {
|
|
361
|
+
let maxBottom = 0;
|
|
362
|
+
textLayerSpans.forEach(span => {
|
|
363
|
+
const rect = span.getBoundingClientRect();
|
|
364
|
+
const parentRect = textDiv.getBoundingClientRect();
|
|
365
|
+
const relativeBottom = rect.bottom - parentRect.top;
|
|
366
|
+
if (relativeBottom > maxBottom) {
|
|
367
|
+
maxBottom = relativeBottom;
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
// 如果文本内容超出当前高度,增加文本层高度(增加一些边距)
|
|
371
|
+
if (maxBottom > domHeight) {
|
|
372
|
+
textDiv.style.height = `${Math.ceil(maxBottom + 10)}px`;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 将文本 div 添加到覆盖层中
|
|
377
|
+
let pagesDiv = document.getElementById('canvas-wrapper' + pageIndex);
|
|
378
|
+
if (pagesDiv) {
|
|
379
|
+
pagesDiv.appendChild(textDiv);
|
|
380
|
+
}
|
|
381
|
+
} catch (textError) {
|
|
382
|
+
console.warn(`页面 ${pageNumber} 文本层渲染失败:`, textError);
|
|
383
|
+
// 文本层渲染失败不影响页面继续渲染
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 渲染已有高亮区域(静态highlights)
|
|
387
|
+
// 注意:高亮是在 canvas 上绘制的,不会覆盖文本层(文本层在 canvas 上方)
|
|
388
|
+
// 需要传入 outputScale,因为 canvas 使用了 transform 渲染
|
|
389
|
+
try {
|
|
390
|
+
this.renderHighlights(context, pageNumber, viewport, outputScale);
|
|
391
|
+
} catch (highlightError) {
|
|
392
|
+
console.warn(`页面 ${pageNumber} 高亮渲染失败:`, highlightError);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 渲染搜索高亮区域
|
|
396
|
+
try {
|
|
397
|
+
this.renderSearchHighlights(context, pageNumber, viewport, outputScale);
|
|
398
|
+
} catch (searchHighlightError) {
|
|
399
|
+
console.warn(`页面 ${pageNumber} 搜索高亮渲染失败:`, searchHighlightError);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// 标记为已加载
|
|
403
|
+
this.$set(pageData, 'loading', false);
|
|
404
|
+
this.$set(pageData, 'loaded', true);
|
|
405
|
+
} catch (error) {
|
|
406
|
+
console.error(`页面 ${pageNumber} 渲染失败:`, error);
|
|
407
|
+
// 渲染失败时重置状态,允许重试
|
|
408
|
+
this.$set(pageData, 'loading', false);
|
|
409
|
+
this.$set(pageData, 'loaded', false);
|
|
410
|
+
}
|
|
319
411
|
},
|
|
320
412
|
calculateScale(pdfWidth) {
|
|
321
413
|
const containerWidth = document.getElementById('pdfViewPage').clientWidth - 50;
|
|
322
414
|
return (containerWidth / pdfWidth) * this.scale;
|
|
323
415
|
},
|
|
324
|
-
async renderHighlights(context, number,viewport) {
|
|
416
|
+
async renderHighlights(context, number, viewport, outputScale = 1) {
|
|
325
417
|
const pageHighlights = this.highlights.filter(item => (item.page === number ))
|
|
326
418
|
let highlights = this.mergedGroups(pageHighlights);
|
|
327
419
|
this.mergedGroupsList = this.mergedGroupsList.concat(highlights)
|
|
@@ -335,36 +427,51 @@ export default {
|
|
|
335
427
|
groups.push(position.slice(i, i + 4));
|
|
336
428
|
}
|
|
337
429
|
for (let i = 0;i<groups.length;i++){
|
|
338
|
-
this.drawHighlight(context, viewport, groups[i], fillStyle, false);
|
|
430
|
+
this.drawHighlight(context, viewport, groups[i], fillStyle, false, outputScale);
|
|
339
431
|
}
|
|
340
432
|
});
|
|
341
433
|
}
|
|
342
434
|
},
|
|
343
|
-
renderSearchHighlights(context, pageNumber, viewport) {
|
|
435
|
+
renderSearchHighlights(context, pageNumber, viewport, outputScale = 1) {
|
|
344
436
|
const pageHighlights = this.searchHighlights.filter(h => h.pageNumber === pageNumber);
|
|
345
437
|
pageHighlights.forEach(({ position }) => {
|
|
346
|
-
this.drawHighlight(context, viewport, position, "rgba(255, 255, 0, 0.4)", true);
|
|
438
|
+
this.drawHighlight(context, viewport, position, "rgba(255, 255, 0, 0.4)", true, outputScale);
|
|
347
439
|
});
|
|
348
440
|
},
|
|
349
|
-
drawHighlight(context, viewport, position, fillStyle, fromSearch = false) {
|
|
441
|
+
drawHighlight(context, viewport, position, fillStyle, fromSearch = false, outputScale = 1) {
|
|
350
442
|
const [x1, y1, x2, y2] = position;
|
|
443
|
+
// 使用 viewport.scale 计算 PDF 坐标到 viewport 坐标的转换
|
|
351
444
|
const scale = viewport.scale;
|
|
445
|
+
// PDF 页面的实际高度(未缩放)
|
|
352
446
|
const pageHeight = viewport.height / viewport.scale;
|
|
353
447
|
|
|
354
|
-
|
|
355
|
-
let
|
|
356
|
-
let
|
|
448
|
+
// 计算在 viewport 坐标系中的位置(PDF 坐标 → viewport 坐标)
|
|
449
|
+
let viewportX1 = x1 * scale;
|
|
450
|
+
let viewportX2 = x2 * scale;
|
|
451
|
+
let viewportY1, viewportY2;
|
|
357
452
|
|
|
358
453
|
if (fromSearch) {
|
|
359
|
-
// 搜索高亮:PDF
|
|
360
|
-
|
|
361
|
-
|
|
454
|
+
// 搜索高亮:PDF坐标系(左下原点),需要转换为顶部原点
|
|
455
|
+
viewportY1 = (pageHeight - y1) * scale;
|
|
456
|
+
viewportY2 = (pageHeight - y2) * scale;
|
|
362
457
|
} else {
|
|
363
|
-
// 静态 highlights
|
|
364
|
-
|
|
365
|
-
|
|
458
|
+
// 静态 highlights:顶部坐标原点,直接使用
|
|
459
|
+
viewportY1 = y1 * scale;
|
|
460
|
+
viewportY2 = y2 * scale;
|
|
366
461
|
}
|
|
367
|
-
|
|
462
|
+
|
|
463
|
+
// 由于 canvas 使用了 transform 渲染,PDF.js 会在内部应用 transform
|
|
464
|
+
// 但 context 的坐标系统是 canvas 的实际尺寸(viewport.width * outputScale)
|
|
465
|
+
// 而 viewport 坐标是基于 viewport.width 的,所以需要转换为 canvas 坐标
|
|
466
|
+
// 注意:outputScale 和 viewport.scale 是相同的值,所以这里只需要乘以 outputScale
|
|
467
|
+
// 但实际上 viewport.scale 已经应用了,所以这里应该直接使用 viewport 坐标
|
|
468
|
+
// 但 canvas 的实际尺寸是 viewport.width * outputScale,所以需要缩放
|
|
469
|
+
let canvasX1 = viewportX1 * outputScale;
|
|
470
|
+
let canvasX2 = viewportX2 * outputScale;
|
|
471
|
+
let canvasY1 = viewportY1 * outputScale;
|
|
472
|
+
let canvasY2 = viewportY2 * outputScale;
|
|
473
|
+
|
|
474
|
+
// 清除当前路径
|
|
368
475
|
context.beginPath();
|
|
369
476
|
|
|
370
477
|
context.fillStyle = fillStyle;
|
|
@@ -384,11 +491,26 @@ export default {
|
|
|
384
491
|
}
|
|
385
492
|
},
|
|
386
493
|
async setCanvasRef() {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
494
|
+
// 等待 DOM 完全更新
|
|
495
|
+
await this.$nextTick();
|
|
496
|
+
|
|
497
|
+
if (!this.$refs.canvases || !Array.isArray(this.$refs.canvases)) {
|
|
498
|
+
console.warn('Canvas 引用未准备好,延迟重试');
|
|
499
|
+
// 如果 canvas 引用还没准备好,延迟重试
|
|
500
|
+
setTimeout(() => {
|
|
501
|
+
this.setCanvasRef();
|
|
502
|
+
}, 100);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// 确保所有 canvas 引用都已设置
|
|
507
|
+
for (let index = 0; index < this.pages.length; index++) {
|
|
508
|
+
if (this.$refs.canvases && this.$refs.canvases[index]) {
|
|
509
|
+
this.$set(this.pages[index], 'canvas', this.$refs.canvases[index]);
|
|
510
|
+
} else {
|
|
511
|
+
console.warn(`页面 ${index + 1} 的 canvas 引用不存在`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
392
514
|
},
|
|
393
515
|
clearPages() {
|
|
394
516
|
this.pages = [];
|
|
@@ -469,7 +591,8 @@ export default {
|
|
|
469
591
|
},
|
|
470
592
|
zoomIn() {
|
|
471
593
|
this.updateCurrentPage();
|
|
472
|
-
|
|
594
|
+
// 检查是否有页面正在加载
|
|
595
|
+
if (this.pages.some(p => p.loading)) return;
|
|
473
596
|
const container = this.$refs.pdfContainer;
|
|
474
597
|
const currentScroll = container.scrollTop;
|
|
475
598
|
this.scale += 0.1;
|
|
@@ -477,7 +600,8 @@ export default {
|
|
|
477
600
|
},
|
|
478
601
|
zoomOut() {
|
|
479
602
|
this.updateCurrentPage();
|
|
480
|
-
|
|
603
|
+
// 检查是否有页面正在加载
|
|
604
|
+
if (this.pages.some(p => p.loading)) return;
|
|
481
605
|
const container = this.$refs.pdfContainer;
|
|
482
606
|
const currentScroll = container.scrollTop;
|
|
483
607
|
this.scale = Math.max(this.scale - 0.1, 0.1);
|
|
@@ -522,7 +646,8 @@ export default {
|
|
|
522
646
|
},
|
|
523
647
|
async doSearch() {
|
|
524
648
|
if (!this.pdf || !this.searchQuery) return;
|
|
525
|
-
|
|
649
|
+
// 检查是否有页面正在加载
|
|
650
|
+
if (this.pages.some(p => p.loading)) return;
|
|
526
651
|
|
|
527
652
|
this.searchHighlights = [];
|
|
528
653
|
const numPages = this.numPages;
|
|
@@ -615,7 +740,8 @@ export default {
|
|
|
615
740
|
onResize() {
|
|
616
741
|
clearTimeout(this.resizeTimeout);
|
|
617
742
|
this.resizeTimeout = setTimeout(() => {
|
|
618
|
-
|
|
743
|
+
// 检查是否有页面正在加载
|
|
744
|
+
if (this.pages.some(p => p.loading)) return;
|
|
619
745
|
const container = this.$refs.pdfContainer;
|
|
620
746
|
const currentScroll = container.scrollTop;
|
|
621
747
|
const pages = Vue.observable(this.pages)
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="vue-office-pdf">
|
|
3
|
+
<div class="vue-office-pdf-main" id="vue-office-pdf-main" ref="rootRef"></div>
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import _ from "lodash";
|
|
9
|
+
|
|
10
|
+
const pdfjsLib = window['pdfjsLib']
|
|
11
|
+
if(pdfjsLib) {
|
|
12
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = window['pdfjs-dist/build/pdf.worker']
|
|
13
|
+
}
|
|
14
|
+
const { TextLayerBuilder } = window['pdfjs-dist/web/pdf_viewer']
|
|
15
|
+
import waterMark from "@/utils/common";
|
|
16
|
+
import { mapGetters } from 'vuex'
|
|
17
|
+
import { setTimestamp } from '@/assets/js/common'
|
|
18
|
+
export default {
|
|
19
|
+
name: "vuePdf",
|
|
20
|
+
data(){
|
|
21
|
+
return{
|
|
22
|
+
loadingTask:null,
|
|
23
|
+
transport:null,
|
|
24
|
+
numPages:0,
|
|
25
|
+
pdfDoc:null,
|
|
26
|
+
lazySize:3,
|
|
27
|
+
scale:1
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
props:{
|
|
31
|
+
src:{
|
|
32
|
+
type:String,
|
|
33
|
+
default:""
|
|
34
|
+
},
|
|
35
|
+
editType:{
|
|
36
|
+
type:String,
|
|
37
|
+
default:""
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
computed:{
|
|
41
|
+
...mapGetters('knowledge', {
|
|
42
|
+
textWatermarkStr: 'getTextWatermarkStr'
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
methods:{
|
|
46
|
+
async init(){
|
|
47
|
+
//将文件转换成blob解决pdfjs引起浏览器崩溃的问题测试
|
|
48
|
+
let src = this.src
|
|
49
|
+
if (this.editType !== 'copyNewVersion'){
|
|
50
|
+
// src += '?timestamp=' + new Date().getTime();
|
|
51
|
+
src = setTimestamp(src)
|
|
52
|
+
}
|
|
53
|
+
this.loadingTask = pdfjsLib.getDocument(src);
|
|
54
|
+
this.loadingTask.promise.then(async pdf => {
|
|
55
|
+
// 获取PDF的第一页
|
|
56
|
+
this.pdfDoc = pdf;
|
|
57
|
+
this.transport = pdf._transport;
|
|
58
|
+
// this.numPages = pdf._transport.numPages;
|
|
59
|
+
this.lazySize = pdf._transport.numPages;
|
|
60
|
+
this.numPages = Math.min(pdf._transport.numPages, this.lazySize);
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
this._renderPage(1);
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
},
|
|
66
|
+
async _renderPage(num){
|
|
67
|
+
await this.pdfDoc.getPage(num).then(async page => {
|
|
68
|
+
let pageMain = document.getElementById('vue-office-pdf-main');
|
|
69
|
+
const pageDom = document.createElement('div');
|
|
70
|
+
let id = 'pdf-canvas-' + num;
|
|
71
|
+
pageDom.setAttribute('id', id);
|
|
72
|
+
pageDom.setAttribute('class', 'pdf-page');
|
|
73
|
+
|
|
74
|
+
const canvas = document.createElement('canvas');
|
|
75
|
+
const context = canvas.getContext('2d');
|
|
76
|
+
let pdfWidth = page.getViewport({ scale: 1}).width
|
|
77
|
+
const viewport = page.getViewport({ scale: this.calculateScale(pdfWidth) });
|
|
78
|
+
// const viewport = page.getViewport({ scale: 2 })
|
|
79
|
+
let outputScale = this.calculateScale(pdfWidth);
|
|
80
|
+
|
|
81
|
+
canvas.width = Math.floor(viewport.width * outputScale);
|
|
82
|
+
canvas.height = Math.floor(viewport.height * outputScale);
|
|
83
|
+
|
|
84
|
+
let domWidth = Math.floor(viewport.width);
|
|
85
|
+
let domHeight = Math.floor(viewport.height);
|
|
86
|
+
|
|
87
|
+
let wrapperWidth = 0
|
|
88
|
+
if (document.getElementById('vue-office-pdf-main')){
|
|
89
|
+
wrapperWidth = document.getElementById('vue-office-pdf-main').clientWidth - 20
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (domWidth > wrapperWidth){
|
|
93
|
+
let scale = wrapperWidth / domWidth;
|
|
94
|
+
domWidth = Math.floor(wrapperWidth);
|
|
95
|
+
domHeight = Math.floor(viewport.height * scale);
|
|
96
|
+
}
|
|
97
|
+
// 根据缩放比例调整canvas大小
|
|
98
|
+
|
|
99
|
+
canvas.style.width = domWidth + 'px';
|
|
100
|
+
canvas.style.height = domHeight + 'px';
|
|
101
|
+
|
|
102
|
+
pageDom.style.width = domWidth + 'px';
|
|
103
|
+
pageDom.style.height = domHeight + 'px';
|
|
104
|
+
pageDom.style.position = 'relative';
|
|
105
|
+
pageDom.style.marginBottom = '10px'
|
|
106
|
+
pageDom.style.marginLeft = 'auto';
|
|
107
|
+
pageDom.style.marginRight = 'auto';
|
|
108
|
+
pageDom.style.backgroundColor = "#ffffff";
|
|
109
|
+
pageDom.style.boxShadow = "0px 0px 18px 0px rgba(29, 55, 129, 0.07)"
|
|
110
|
+
pageDom.appendChild(canvas);
|
|
111
|
+
|
|
112
|
+
const transform = outputScale !== 1
|
|
113
|
+
? [outputScale, 0, 0, outputScale, 0, 0]
|
|
114
|
+
: null;
|
|
115
|
+
|
|
116
|
+
await page.render({
|
|
117
|
+
canvasContext: context,
|
|
118
|
+
transform:transform,
|
|
119
|
+
viewport:viewport
|
|
120
|
+
}).promise.then(() => {
|
|
121
|
+
return page.getTextContent()
|
|
122
|
+
}).then(async (textContent) => {
|
|
123
|
+
// // 渲染PDF页面到canvas上
|
|
124
|
+
|
|
125
|
+
// var textlayerDiv = document.createElement("div");
|
|
126
|
+
// textlayerDiv.setAttribute('class', 'textLayer');
|
|
127
|
+
// let textlayer = new TextLayerBuilder({
|
|
128
|
+
// textLayerDiv: textlayerDiv,
|
|
129
|
+
// viewport: viewport,
|
|
130
|
+
// });
|
|
131
|
+
//
|
|
132
|
+
// textlayer.setTextContent(textContent);
|
|
133
|
+
// textlayer.render();
|
|
134
|
+
// pageDom.appendChild(textlayerDiv);
|
|
135
|
+
//
|
|
136
|
+
// pageMain.appendChild(pageDom);
|
|
137
|
+
|
|
138
|
+
const textDiv = document.createElement('div');
|
|
139
|
+
textDiv.setAttribute('class', 'textLayer');
|
|
140
|
+
let textLayer = new TextLayerBuilder({
|
|
141
|
+
textLayerDiv: textDiv,
|
|
142
|
+
viewport: viewport,
|
|
143
|
+
transform:transform,
|
|
144
|
+
});
|
|
145
|
+
textDiv.style.width = domWidth + 'px'; // 根据 viewport 调整宽度
|
|
146
|
+
textDiv.style.height = domHeight + 'px'; // 根据 viewport 调整高度(可能需要根据实际文本内容调整)
|
|
147
|
+
textDiv.style.overflow = 'hidden'; // 如果文本内容超出范围,则隐藏它
|
|
148
|
+
textDiv.style.whiteSpace = 'pre-wrap'; // 保留空白符和换行符
|
|
149
|
+
textDiv.style.position = 'absolute'; // 绝对定位以覆盖 canvas
|
|
150
|
+
textDiv.style.top = '0';
|
|
151
|
+
textDiv.style.left = '0';
|
|
152
|
+
// textDiv.textContent = textContent; // 设置文本内容
|
|
153
|
+
textLayer.setTextContent(textContent);
|
|
154
|
+
textLayer.render()
|
|
155
|
+
// 将文本 div 添加到覆盖层中
|
|
156
|
+
pageDom.appendChild(textDiv);
|
|
157
|
+
|
|
158
|
+
pageMain.appendChild(pageDom);
|
|
159
|
+
if (this.textWatermarkStr){
|
|
160
|
+
if(sessionStorage.getItem('isWeChat') == true || sessionStorage.getItem('isWeChat') == 'true') {
|
|
161
|
+
let str = `<ww-open-data type="userName" openid=${this.textWatermarkStr}></ww-open-data>`
|
|
162
|
+
waterMark.newInitWaterMark(id,str)
|
|
163
|
+
} else {
|
|
164
|
+
waterMark.newInitWaterMark(id,this.textWatermarkStr)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
// pageMain.appendChild(pageDom);
|
|
169
|
+
if (this.numPages > num){
|
|
170
|
+
this._renderPage(num + 1)
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
},
|
|
174
|
+
calculateScale(pdfWidth) {
|
|
175
|
+
const containerWidth = document.getElementById('vue-office-pdf-main').clientWidth;
|
|
176
|
+
return (containerWidth / pdfWidth) * this.scale;
|
|
177
|
+
},
|
|
178
|
+
onScrollPdf: _.debounce(function (e, that) {
|
|
179
|
+
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
|
180
|
+
if (scrollTop + clientHeight >= scrollHeight) {
|
|
181
|
+
if (that.numPages >= that.pdfDoc._transport.numPages) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
let oldNum = that.numPages;
|
|
185
|
+
that.numPages = Math.min(that.pdfDoc._transport.numPages, oldNum + that.lazySize);
|
|
186
|
+
if (that.numPages > oldNum) {
|
|
187
|
+
that._renderPage(oldNum + 1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}, 200),
|
|
191
|
+
},
|
|
192
|
+
mounted() {
|
|
193
|
+
this.init();
|
|
194
|
+
document.getElementById('KnowledgePreview').addEventListener('scroll',(e) => {
|
|
195
|
+
this.onScrollPdf(e,this)
|
|
196
|
+
})
|
|
197
|
+
},
|
|
198
|
+
destroyed() {
|
|
199
|
+
if (this.pdfDoc){
|
|
200
|
+
this.pdfDoc.destroy()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<style scoped lang="less">
|
|
207
|
+
.vue-office-pdf{
|
|
208
|
+
//height: 100%;
|
|
209
|
+
.vue-office-pdf-main{
|
|
210
|
+
//height: calc(100% - 20px);
|
|
211
|
+
//overflow-y: scroll;
|
|
212
|
+
//background: #ededf0;
|
|
213
|
+
//background: #ffffff;
|
|
214
|
+
//padding: 10px 0;
|
|
215
|
+
position: relative;
|
|
216
|
+
.pdf-page{
|
|
217
|
+
background: #ffffff;
|
|
218
|
+
box-shadow: 0px 0px 18px 0px rgba(29, 55, 129, 0.07)!important;
|
|
219
|
+
/deep/.textLayer{
|
|
220
|
+
opacity: 1;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
</style>
|