zydx-plus 1.36.641 → 1.36.643

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.
@@ -1,41 +1,53 @@
1
+ /**
2
+ * flip 组件 - PDF翻书/阅读器组件
3
+ * 基于 PDF.js 实现 PDF 文档的在线预览,支持单页/双页模式、画笔批注、
4
+ * 文字标注、链接添加、拓展阅读等功能。提供 Canvas 绘制能力,支持
5
+ * 画笔、矩形、圆形、直线、箭头、文字等批注工具。
6
+ */
1
7
  <template>
8
+ <!-- 文档视图主容器 -->
2
9
  <div class="docView" @click="extendRightDel" @mouseleave="mouseUpTest" @mouseup="mouseUpTest">
10
+ <!-- 全屏/视图切换工具栏(单/双页切换) -->
3
11
  <div class="full-screen" v-if="false">
4
12
  <div class="full-screen-img" @click.stop="fullScreen">
5
- <!-- <div class="full-screen-title" style="" v-if="showText">全屏查看</div>-->
6
13
  <img @mouseover="showText=true" @mouseout="showText=false" src="../img/full-screen.png" alt="" title="全屏查看">
7
14
  </div>
15
+ <!-- 双页视图下的切换按钮 -->
8
16
  <div class="full-screen-img" v-if="doublePage" @click.stop="changeView">
9
- <!-- <div class="full-screen-title" style="" v-if="showMultiText">多页查看</div>-->
10
17
  <img @mouseover="showMultiText=true" @mouseout="showMultiText=false" src="../img/multi-page.png" alt="" title="多页查看">
11
18
  </div>
19
+ <!-- 单页视图下的切换按钮 -->
12
20
  <div class="full-screen-img" v-else @click.stop="changeView">
13
- <!-- <div class="full-screen-title" style="" v-if="showSingleText">单页查看</div>-->
14
21
  <img @mouseover="showSingleText=true" @mouseout="showSingleText=false" src="../img/single-page.png" alt="" title="单页查看">
15
22
  </div>
23
+ <!-- 笔迹工具按钮 -->
16
24
  <div class="full-screen-img" v-if="!doublePage&&isShowPen" @click.stop="showPen">
17
- <!-- <div class="full-screen-title" style="" v-if="showSingleText">单页查看</div>-->
18
- <!-- <div class="full-screen-title" style="" v-if="showSingleText">单页查看</div>-->
19
25
  <i class="iconfont zydx-huabi1" title="笔迹"/>
20
26
  </div>
21
27
  </div>
28
+
29
+ <!-- PDF 画布区域 -->
22
30
  <div>
23
31
  <div class="book" ref="book" @click.right="rightMenu($event)">
24
32
  <div class="pages">
33
+ <!-- 双页模式渲染 -->
25
34
  <div class="wrap-box" ref="wrap-box" v-show="doublePage">
26
35
  <div class="wrap wrap-block" :ref="`wrap-${item.id}`" v-for="(item,index) in pageInfoArr" :key="index">
27
36
  <div class="wrap-can" v-show="!loading" ref="wrapCanBox">
37
+ <!-- 右键菜单:添加链接 -->
28
38
  <div class="extend-right" v-if="rightMenuShow" :style="extendStyle">
29
39
  <div class="extend-right-cont" @click.stop="extendRight(null)">
30
- <!-- <img src="../img/qubiezhen.svg" alt=""> -->
31
40
  <p>添加链接</p>
32
41
  </div>
33
42
  </div>
43
+ <!-- 文本层:显示 PDF 提取的文字内容 -->
34
44
  <div class="textLayer" ref="textLayer">
35
45
  <span :dir="item2.dir" :style="styleText(item2,item.styles, item.id)" v-for="(item2) in item.items">{{ item2.str }}</span>
36
46
  </div>
47
+ <!-- 批注层:显示链接和拓展阅读标注 -->
37
48
  <div class="annotationLayer" ref="annotationLayer">
38
49
  <template v-for="(item,index) in annotations">
50
+ <!-- 拓展阅读类型标注 -->
39
51
  <div :style="styleAnnotation(item)" class="magnifier" v-if="item.type === 'tag'">
40
52
  <div class="magnifier-head">
41
53
  <div class="magnifier-title">拓展阅读</div>
@@ -49,14 +61,18 @@
49
61
  <div class="magnifier-list-item" @click="annotationTap(item2)" v-for="(item2,index2) in item.list">{{ index2+1 }}.{{ item2?.title }}</div>
50
62
  </div>
51
63
  </div>
64
+ <!-- 普通链接类型标注 -->
52
65
  <section v-else tabindex="1000" @click="annotationTap(item,item.id)" :style="styleAnnotation(item)">
53
66
  <a :href="item.url" target="_blank"></a>
54
67
  </section>
55
68
  </template>
56
69
  </div>
70
+ <!-- 批注绘制画布(上层,可交互) -->
57
71
  <canvas @click.stop="turnText" class="wrap-can-one" :class="{isPointer: isPointer}" @mousedown.stop="mouseDown"
58
72
  @mousemove.stop="mouseMove" :id="item.id"></canvas>
73
+ <!-- PDF 渲染画布(底层) -->
59
74
  <canvas :id="`${item.id}2`"></canvas>
75
+ <!-- 文字批注输入框/拖拽框 -->
60
76
  <div
61
77
  v-show="state === 'text'&&textY2>0&&textX2>0"
62
78
  class="text-frame"
@@ -75,19 +91,24 @@
75
91
  </div>
76
92
  </div>
77
93
  </div>
94
+
95
+ <!-- 单页模式渲染 -->
78
96
  <div class="wrap" ref="wrap" v-show="!doublePage">
79
97
  <div class="wrap-can" v-show="!loading" ref="wrapCan">
98
+ <!-- 右键菜单:添加链接 -->
80
99
  <div class="extend-right" v-if="rightMenuShow" :style="extendStyle">
81
100
  <div class="extend-right-cont" @click.stop="extendRight(null)">
82
- <!-- <img src="../img/qubiezhen.svg" alt=""> -->
83
101
  <p>添加链接</p>
84
102
  </div>
85
103
  </div>
104
+ <!-- 文本层 -->
86
105
  <div class="textLayer" ref="textLayer">
87
106
  <span :dir="item.dir" :style="styleText(item,styles)" v-for="(item,index) in items">{{ item.str }}</span>
88
107
  </div>
108
+ <!-- 批注层 -->
89
109
  <div class="annotationLayer" ref="annotationLayer">
90
110
  <template v-for="(item,index) in annotations">
111
+ <!-- 拓展阅读类型标注(单页模式,带弹出列表) -->
91
112
  <div :style="styleAnnotation(item)" class="magnifier" v-if="item.type === 'tag'">
92
113
  <img @click.stop="magnifierTap($event,item)" src="../img/dingwei.png" alt="" />
93
114
  <div class="magnifier-list" v-if="item.show" :style="item.style">
@@ -104,14 +125,18 @@
104
125
  </div>
105
126
  </div>
106
127
  </div>
128
+ <!-- 普通链接标注 -->
107
129
  <section v-else tabindex="1000" @click="annotationTap(item)" :style="styleAnnotation(item)">
108
130
  <a :href="item.url" target="_blank"></a>
109
131
  </section>
110
132
  </template>
111
133
  </div>
134
+ <!-- 批注绘制画布 -->
112
135
  <canvas @click.stop="turnText" class="wrap-can-one" :class="{isPointer: isPointer}" @mousedown.stop="mouseDown"
113
136
  @mousemove.stop="mouseMove" :id="`${id}`"></canvas>
137
+ <!-- PDF 内容画布 -->
114
138
  <canvas :id="`${id}2`"></canvas>
139
+ <!-- 文字批注输入框 -->
115
140
  <div
116
141
  v-show="state === 'text'&&textY2>0&&textX2>0"
117
142
  class="text-frame"
@@ -129,20 +154,25 @@
129
154
  </div>
130
155
  </div>
131
156
  </div>
157
+
158
+ <!-- 左侧翻页按钮(上一页) -->
132
159
  <div class="page-but-left" :style="{cursor: pageIndex === 1?'default':'pointer'}" v-if="!loading&&!doublePage"
133
160
  @click.stop="flipRight(pageIndex-=1,false)">
134
161
  <img v-if="pageIndex !== 1"
135
- src="data:image/png;base64,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"
162
+ src="data:image/png;base64,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"
136
163
  alt=""/>
137
164
  </div>
165
+ <!-- 右侧翻页按钮(下一页) -->
138
166
  <div class="page-but-right" :style="{cursor: pageIndex === pdfPages?'default':'pointer'}" v-if="!loading&&!doublePage"
139
167
  @click.stop="flipLeft(pageIndex+=1,false)">
140
168
  <img v-if="pageIndex !== pdfPages"
141
- src="data:image/png;base64,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"
169
+ src="data:image/png;base64,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"
142
170
  alt=""/>
143
171
  </div>
144
172
  </div>
145
173
  </div>
174
+
175
+ <!-- 底部翻页按钮(上一页/下一页) -->
146
176
  <div class="but-wrap" v-if="butShow && !doublePage">
147
177
  <button class="but-brown" :disabled="pageIndex === 1" :class="{'but-act': pageIndex === 1}"
148
178
  @click.stop="flipRight(pageIndex-=1,false)">上一页
@@ -152,6 +182,8 @@
152
182
  </button>
153
183
  </div>
154
184
  </div>
185
+
186
+ <!-- 加载动画遮罩层 -->
155
187
  <div v-if="loading" class="loading-back">
156
188
  <div class="loading-cont">
157
189
  <div class="loadings">Loading</div>
@@ -161,6 +193,7 @@
161
193
  </template>
162
194
 
163
195
  <script>
196
+ // ==================== 依赖导入 ====================
164
197
  import Mess from '../../tipBox/index'
165
198
  import {debounce} from '../../utils'
166
199
  import * as PdfJs from 'pdfjs-dist'
@@ -168,154 +201,177 @@ import "pdfjs-dist/build/pdf.worker.mjs";
168
201
  import Range from "../../tagging/src/range";
169
202
 
170
203
  import img from '../img/huabi.png'
204
+ // 缓存已加载的 PDF 文档对象
171
205
  let pdfs = {};
172
206
 
173
207
  export default {
174
208
  name: "zydx-flip",
209
+
210
+ // ==================== 组件状态数据 ====================
175
211
  data() {
176
212
  return {
177
- mouseImg:img,
178
- pdfPages: 0,
179
- imgArr: '',
213
+ mouseImg:img, // 画笔光标图标
214
+ pdfPages: 0, // PDF 总页数
215
+ imgArr: '', // 当前页 Canvas Base64 数据
180
216
  hei: 0,
181
- pageIndex: this.page,
217
+ pageIndex: this.page, // 当前页码(从 props.page 初始化)
182
218
  show: false,
183
219
  load: 0,
184
220
  loadWidth: 0,
185
221
  pages: false,
186
222
  pagesData: [],
187
223
  opacity: 1,
188
- id: null,
189
- loading: false,
190
- canvasStyle: {},
224
+ id: null, // Canvas 元素唯一标识
225
+ loading: false, // 加载状态
226
+ canvasStyle: {}, // Canvas 容器样式配置
191
227
  oneShow: false,
192
- flipRights: false,
193
- flipLefts: false,
194
- oneIndex: 'right',
195
- scales: 1.35,
196
- timer: null,
228
+ flipRights: false, // 右翻动画状态
229
+ flipLefts: false, // 左翻动画状态
230
+ oneIndex: 'right', // 翻页方向(left/right)
231
+ scales: 1.35, // PDF 缩放比例
232
+ timer: null, // 防抖定时器
197
233
  resultImg: '',
198
- startX: 0,
199
- startY: 0,
200
- isDrawing: false,
201
- sratio: 2,
202
- ctx: null,
203
- lineWidth: 2,
204
- points: [],
234
+ startX: 0, // 画笔起始 X 坐标
235
+ startY: 0, // 画笔起始 Y 坐标
236
+ isDrawing: false, // 是否正在绘制
237
+ sratio: 2, // Canvas 清晰度系数(retina 屏适配)
238
+ ctx: null, // 批注画布 2D 上下文
239
+ lineWidth: 2, // 画笔线宽
240
+ points: [], // 所有批注数据
205
241
  setShow: false,
206
242
  flipToolShow2: false,
207
243
  flipToolShow: false,
208
- color: '#000',
244
+ color: '#000', // 画笔/文字颜色
209
245
  text: '',
210
- state: null,
211
- fontSize: 14,
212
- textX: 0,
213
- textY: 0,
214
- textX2: 0,
215
- textY2: 0,
216
- textStart: false,
217
- contenteditable: false,
218
- linkId: null,
219
- pointsSelect: [],
220
- checkDrawing: false, // 是否在画板以外画笔
221
- checkX: 0,
222
- checkY: 0,
223
- textWidth: 0,
224
- textHeight: 0,
225
- textDownX: 0,
226
- textDownY: 0,
227
- textMoveStart: false,
246
+ state: null, // 当前工具状态: null/mouse | brush | rectangle | circular | straight | arrow | text | del
247
+ fontSize: 14, // 文字字号
248
+ textX: 0, // 文字起始 X 坐标
249
+ textY: 0, // 文字起始 Y 坐标
250
+ textX2: 0, // 文字当前 X 坐标
251
+ textY2: 0, // 文字当前 Y 坐标
252
+ textStart: false, // 是否开始输入文字
253
+ contenteditable: false, // 文字框是否可编辑
254
+ linkId: null, // 当前绘制图形的链接 ID
255
+ pointsSelect: [], // 当前选中的图形数据
256
+ checkDrawing: false, // 是否在画板以外开始交互
257
+ checkX: 0, // 选中操作的起始 X
258
+ checkY: 0, // 选中操作的起始 Y
259
+ textWidth: 0, // 文字框最小宽度
260
+ textHeight: 0, // 文字框最小高度
261
+ textDownX: 0, // 文字框拖拽起始 X
262
+ textDownY: 0, // 文字框拖拽起始 Y
263
+ textMoveStart: false, // 是否开始拖拽文字框
228
264
  chartIndexID: null, // 当前点击的图形索引
229
- signStart: false,
230
- doublePage:false,
231
- doubleImgArr: [],
232
- pageInfoArr:[],
265
+ signStart: false, // 是否开始标记模式
266
+ doublePage:false, // 是否为双页视图
267
+ doubleImgArr: [], // 双页模式下各页的 Canvas Base64 数据
268
+ pageInfoArr:[], // 双页模式下各页的配置信息
233
269
  showText:false,
234
270
  showMultiText:false,
235
271
  showSingleText:false,
236
- boxWidth:'',
237
- resizeObserver: null,
272
+ boxWidth:'', // 容器宽度
273
+ resizeObserver: null, // 容器尺寸变化监听器
238
274
  totalPages: 0,
239
- items: [],
240
- styles: {},
241
- viewport: null,
242
- doc: null,
243
- viewWidth: 0,
244
- viewHeight: 0,
245
- annotations: [],
246
- annotationsInit: [],
275
+ items: [], // PDF 文本内容数组
276
+ styles: {}, // PDF 文本样式映射
277
+ viewport: null, // PDF 视口对象
278
+ doc: null, // PDF 页面对象
279
+ viewWidth: 0, // 视口宽度
280
+ viewHeight: 0, // 视口高度
281
+ annotations: [], // 标注数据(合并外部传入和PDF内置标注)
282
+ annotationsInit: [], // PDF 自带标注原始数据
247
283
  extendStyle: {
248
284
  left: '0px',
249
285
  top: '0px'
250
- },
251
- rightMenuShow: false,
252
- annData: {}
286
+ }, // 右键菜单定位样式
287
+ rightMenuShow: false, // 是否显示右键菜单
288
+ annData: {} // 预存的标注数据
253
289
  }
254
290
  },
291
+
292
+ // ==================== Props 属性定义 ====================
255
293
  props: {
294
+ // 是否禁用右键菜单
256
295
  isDisableRightClick: {
257
296
  type: Boolean,
258
297
  default: false
259
298
  },
299
+ // PDF 文件 URL 地址
260
300
  source: {
261
301
  type: String,
262
302
  default: ''
263
303
  },
304
+ // 是否为单页模式(已废弃,建议使用 isDoublePage)
264
305
  singlePage: {
265
306
  type: Boolean,
266
307
  default: false
267
308
  },
309
+ // 是否显示底部翻页按钮
268
310
  butShow: {
269
311
  type: Boolean,
270
312
  default: false
271
313
  },
314
+ // 初始页码
272
315
  page: {
273
316
  type: Number,
274
317
  default: 1
275
318
  },
319
+ // PDF 缩放比例
276
320
  scale: {
277
321
  type: Number,
278
322
  default: 1.35
279
323
  },
324
+ // 外部容器尺寸样式(用于自适应缩放计算)
280
325
  scaleStyle: {
281
326
  type: Object,
282
327
  default: () => {
283
328
  }
284
329
  },
330
+ // 外部传入的已有批注数据
285
331
  pointsData: {
286
332
  type: Array,
287
333
  default: () => []
288
334
  },
335
+ // 阅读笔模式
289
336
  readingPen: {
290
337
  type: Boolean,
291
338
  default: false
292
339
  },
340
+ // 是否为双页模式
293
341
  isDoublePage: {
294
342
  type: Boolean,
295
343
  default: false
296
344
  },
345
+ // 是否显示画笔工具
297
346
  isShowPen:{
298
347
  type: Boolean,
299
348
  default: true
300
349
  },
350
+ // 是否允许鼠标点击穿透(批注层穿透)
301
351
  isPointer: {
302
352
  type: Boolean,
303
353
  default: true
304
354
  },
355
+ // 外部传入的标注数据
305
356
  annotationsData: {
306
357
  type: Array,
307
358
  default: () => []
308
359
  },
360
+ // 是否允许右键交互
309
361
  rightClick: {
310
362
  type: Boolean,
311
363
  default: true
312
364
  },
365
+ // 是否为只读模式(禁止添加标注)
313
366
  readOnly: {
314
367
  type: Boolean,
315
368
  default: true
316
369
  }
317
370
  },
371
+
372
+ // ==================== 监听器 ====================
318
373
  watch: {
374
+ // 监听外部标注数据变化
319
375
  annotationsData: {
320
376
  handler: function (val) {
321
377
  if(typeof val === Object) {
@@ -340,27 +396,31 @@ export default {
340
396
  deep: true,
341
397
  immediate: true
342
398
  },
399
+ // 监听外部批注数据变化,重新渲染
343
400
  pointsData: {
344
401
  handler: function (val) {
345
402
  if (!val) return
346
403
  this.points = val
347
- this.processing()
404
+ this.processing() // 重绘所有批注
348
405
  },
349
406
  deep:true,
350
407
  immediate: true
351
408
  },
409
+ // 监听双页模式切换
352
410
  isDoublePage: {
353
411
  handler: function (val) {
354
412
  this.doublePage = val
355
413
  },
356
414
  immediate: true
357
415
  },
416
+ // 监听页码外部变化
358
417
  page: {
359
418
  handler: function (val) {
360
419
  this.pageIndex = val
361
420
  },
362
421
  immediate: true
363
422
  },
423
+ // 监听 PDF 源文件变化,重新加载
364
424
  source: {
365
425
  handler: function (v) {
366
426
  this.sourceUrl()
@@ -368,6 +428,7 @@ export default {
368
428
  deep: true,
369
429
  immediate: true
370
430
  },
431
+ // 监听容器尺寸变化,重新计算缩放比例
371
432
  scaleStyle: {
372
433
  handler: function (val) {
373
434
  if (!val) return
@@ -375,18 +436,21 @@ export default {
375
436
  clearTimeout(this.timer)
376
437
  }
377
438
  this.timer = setTimeout(() => {
439
+ // 根据容器宽度动态计算缩放比
378
440
  this.scales = val.width / 550
379
441
  if (this.scales >= this.scale) {
380
- this.scales = this.scale
442
+ this.scales = this.scale // 不超过最大缩放比
381
443
  }
382
444
  }, 200)
383
445
  },
384
446
  deep: true
385
447
  },
448
+ // 监听双页/单页切换,重新初始化页面信息
386
449
  doublePage:{
387
450
  handler: function (newVal,oldVal){
388
451
  if(oldVal !== undefined){
389
452
  if(newVal){
453
+ // 切换到双页模式
390
454
  this.pageInfoArr = []
391
455
  this.doubleImgArr = []
392
456
  this.pageIndex = this.page
@@ -398,6 +462,7 @@ export default {
398
462
  }
399
463
  this.sourceUrl()
400
464
  }else {
465
+ // 切换到单页模式
401
466
  this.id = 'canvas-wrap' + this.generateUniqueKey()
402
467
  this.sourceUrl()
403
468
  }
@@ -406,6 +471,7 @@ export default {
406
471
  deep: true,
407
472
  immediate: true
408
473
  },
474
+ // 监听缩放比例变化
409
475
  scale:{
410
476
  handler: function (val){
411
477
  this.scales = val
@@ -413,10 +479,12 @@ export default {
413
479
  deep: true,
414
480
  immediate: true
415
481
  },
482
+ // 监听工具状态变化,切换画笔光标样式
416
483
  state:{
417
484
  handler: function (val){
418
485
  this.$nextTick(()=>{
419
486
  if(val==='brush'){
487
+ // 画笔模式下使用自定义光标图标
420
488
  this.$refs.wrapCan.setAttribute('style',`cursor:url(${this.mouseImg}) 0 16, auto;`)
421
489
  }else {
422
490
  this.$refs.wrapCan.style.cursor = "auto"
@@ -427,31 +495,42 @@ export default {
427
495
  immediate: true
428
496
  }
429
497
  },
498
+
499
+ // ==================== 事件声明 ====================
430
500
  emits: ['flip','annotationTap', 'annotationsSend', 'empty'],
501
+
502
+ // ==================== 生命周期 ====================
431
503
  mounted() {
504
+ // 单页模式下生成 Canvas 唯一标识
432
505
  if (!this.doublePage) {
433
506
  this.id = 'canvas-wrap' + this.generateUniqueKey()
434
507
  }
508
+ // 监听键盘事件(用于删除批注)
435
509
  window.addEventListener('keydown', this.handleKeyEvent)
436
- // 在画板以外松开鼠标后冻结画笔
437
- // document.onmouseup = () => {
438
- // if (this.isDrawing || this.checkDrawing) this.mouseUp()
439
- // this.textMoveStart = false
440
- // }
510
+ // 注册容器尺寸变化监听(响应式重新渲染)
441
511
  this.setResizeObserver()
442
512
  },
443
513
  beforeDestroy() {
514
+ // 清理键盘事件监听
444
515
  window.removeEventListener('keydown', this.handleKeyEvent)
516
+ // 取消尺寸变化监听
445
517
  this.cancelResizeObserver()
446
518
  },
519
+
520
+ // ==================== 方法定义 ====================
447
521
  methods: {
522
+ // ----- 标注相关方法 -----
523
+
524
+ // 清空拓展阅读列表
448
525
  annEmpty(v) {
449
526
  this.$emit('empty',v)
450
527
  },
528
+ // 点击拓展阅读图标,显示/隐藏弹出列表
451
529
  magnifierTap(e,v) {
452
- if(!v.show) this.extendRightDel()
530
+ if(!v.show) this.extendRightDel() // 关闭其他弹出框
453
531
  v.show = !v.show
454
532
  setTimeout(() => {
533
+ // 计算弹出框位置:靠近右侧时向左弹出,靠近底部时向上弹出
455
534
  const x = (v.rect[0] * this.scales / this.viewport?.width) * 100
456
535
  const y = ((this.viewport?.height - (v.rect[1] * this.scales)) / this.viewport?.height) * 100
457
536
  if(x > 50) {
@@ -466,6 +545,7 @@ export default {
466
545
  }
467
546
  },20)
468
547
  },
548
+ // 关闭右键菜单
469
549
  extendRightDel() {
470
550
  this.rightMenuShow = false
471
551
  this.annotations.map(x => {
@@ -473,9 +553,11 @@ export default {
473
553
  return x
474
554
  })
475
555
  },
556
+ // 点击标注(链接跳转或添加标注)
476
557
  annotationTap(v, n ) {
477
- if(v.url) return
558
+ if(v.url) return // 有链接的标注直接导航
478
559
  if(n === 'add') {
560
+ // 触发添加标注事件(带回标注参数让外部保存)
479
561
  this.$emit('annotationsSend', {
480
562
  id: v.id,
481
563
  page: v.page,
@@ -487,7 +569,7 @@ export default {
487
569
  this.$emit('annotationTap',v)
488
570
  }
489
571
  },
490
- // 扩展点击
572
+ // 右键添加链接/标注
491
573
  extendRight(i) {
492
574
  this.extendRightDel()
493
575
  this.$emit('annotationsSend', {
@@ -496,7 +578,14 @@ export default {
496
578
  page: i? i + 1 : this.pageIndex,
497
579
  })
498
580
  },
499
- // 右键菜单
581
+ // 查看点是否存在
582
+ doesThereExistAPoint(rect) {
583
+ const currenRect = JSON.stringify(rect)
584
+ return this.annotationsData.some(item => {
585
+ return JSON.stringify(item.rect) === currenRect
586
+ })
587
+ },
588
+ // 右键菜单处理:在文本上右键时弹出"添加链接"
500
589
  rightMenu(e) {
501
590
  if (this.isDisableRightClick) {
502
591
  return
@@ -506,6 +595,7 @@ export default {
506
595
  if(!this.rightClick) return;
507
596
  if(this.doublePage) return;
508
597
  if(e.target.nodeName === 'SPAN') {
598
+ // 计算文本位置以生成标注矩形区域
509
599
  const wid = this.$refs.wrap.offsetWidth - 10
510
600
  const endOffset = this.getRange().endOffset
511
601
  const style = e.target.attributes.style.value
@@ -516,30 +606,36 @@ export default {
516
606
  const x2 = x1 + 15
517
607
  const y2 = y1 + 15
518
608
  const left = e.target.offsetLeft + e.offsetX
519
- this.rightMenuShow = true
609
+ // this.rightMenuShow = true
520
610
  this.extendStyle = {
521
611
  left: left + 'px',
522
- // top: `calc(${Number(topMatch) + '%'} - 50px)`
523
612
  top: `calc(${Number(topMatch) + '%'} - 30px)`
524
613
  }
614
+ // 预存标注数据
525
615
  this.annData = {
526
616
  rect: [x1/this.scales,y1/this.scales,x2/this.scales,y2/this.scales],
527
617
  type: 'tag',
528
618
  borderStyle: {width: 0},
529
619
  borderColor: [0,0,0]
530
620
  }
621
+ // 判断当前坐标点是否存在,存在不显示右键菜单
622
+ if(this.annotationsData.length > 0) {
623
+ this.rightMenuShow = !this.doesThereExistAPoint(this.annData.rect)
624
+ } else {
625
+ this.rightMenuShow = true
626
+ }
531
627
  }
532
628
  },
533
- // 添加标注
629
+ // 选中文本后添加标注(翻转文本标注)
534
630
  flipText(next) {
535
631
  let timestamp = 'flip' + Math.random().toString(36).substr(2)
536
- this.replaceSelectedStrByEle(timestamp)
632
+ this.replaceSelectedStrByEle(timestamp) // 给选中文本添加标记类名
537
633
  const elements = document.querySelectorAll(`.${timestamp}`)[0];
538
634
  if(!elements) return next(false)
539
635
  const wid = this.$refs.wrap.offsetWidth - 10
540
636
  const hei = this.$refs.wrap.offsetHeight - 10
541
637
  const hei2 = elements.offsetHeight - 8
542
- if(elements.offsetParent.nodeName === 'SPAN') { // 判断是否有上级标签
638
+ if(elements.offsetParent.nodeName === 'SPAN') { // 选中的文本有父级 span 标签
543
639
  const parent = elements.offsetParent
544
640
  const x1 = this.viewport.width * ((elements.offsetLeft + parent.offsetLeft + 3)/wid)
545
641
  const y1 = this.viewport.height * (1 - (parent.offsetTop + hei2)/hei)
@@ -555,7 +651,7 @@ export default {
555
651
  page: this.pageIndex,
556
652
  data: ann
557
653
  })
558
- }else {
654
+ }else { // 直接选中的文本节点
559
655
  const style = elements.firstChild.attributes.style.value
560
656
  const leftMatch = style.match(/left:\s*([^;]+)/);
561
657
  const topMatch = style.match(/top:\s*([^;]+)/);
@@ -581,7 +677,7 @@ export default {
581
677
  })
582
678
  }
583
679
  },
584
- // 选中文本添加样式
680
+ // 给选中文本包裹 inline 元素添加样式
585
681
  replaceSelectedStrByEle(className) {
586
682
  const range = this.getRange();
587
683
  range.applyInlineStyle('i', {
@@ -589,7 +685,7 @@ export default {
589
685
  });
590
686
  range.select();
591
687
  },
592
- // 获取选中文本内容
688
+ // 获取当前选中的文本 Range 对象
593
689
  getRange() {
594
690
  const range = new Range(window.document);
595
691
  const sel = window.getSelection();
@@ -600,6 +696,7 @@ export default {
600
696
  }
601
697
  return range
602
698
  },
699
+ // 根据标注数据计算 CSS 定位样式(百分比定位)
603
700
  styleAnnotation(v) {
604
701
  if(!v) return {}
605
702
  const x1 = (v.rect[0] * this.scales / this.viewport?.width) * 100
@@ -614,6 +711,7 @@ export default {
614
711
  'border': `${v.borderStyle.width}px solid rgb(${v.borderColor[0]},${v.borderColor[1]},${v.borderColor[2]})`
615
712
  }
616
713
  },
714
+ // 根据文本位置计算 CSS 定位样式(用于 textLayer 渲染)
617
715
  styleText(v,n, id) {
618
716
  if(!v) return {}
619
717
  const textLayer = id? this.$refs[`wrap-${id}`][0] : this.$refs.textLayer;
@@ -630,16 +728,26 @@ export default {
630
728
  'top': `${top}%`,
631
729
  }
632
730
  },
731
+
732
+ // ----- 批注数据管理方法 -----
733
+
734
+ // 添加批注到 points 数组
633
735
  pointsAdd(v) {
634
736
  this.points.push(v)
635
737
  },
738
+ // 获取当前页码
636
739
  getPage() {
637
740
  return this.pageIndex
638
741
  },
742
+ // 在画板外松开鼠标时结束绘制(从父组件 mouseleave/mouseup 调用)
639
743
  mouseUpTest(){
640
744
  if (this.isDrawing || this.checkDrawing) this.mouseUp()
641
745
  this.textMoveStart = false
642
746
  },
747
+
748
+ // ----- ResizeObserver 容器尺寸监听 -----
749
+
750
+ // 初始化容器尺寸监听,尺寸变化时重新渲染 PDF
643
751
  setResizeObserver() {
644
752
  setTimeout(() => {
645
753
  const el = this.$refs.book;
@@ -652,19 +760,24 @@ export default {
652
760
  } else {
653
761
  this.readPdf()
654
762
  }
655
- }, 150); // 根据需求调整延迟时间
763
+ }, 150);
656
764
  });
657
765
  this.resizeObserver.observe(el);
658
766
  }, 300)
659
767
  },
768
+ // 取消容器尺寸监听
660
769
  cancelResizeObserver() {
661
770
  const el = this.$refs.book;
662
771
  this.resizeObserver.unobserve(el);
663
772
  },
773
+
774
+ // ----- 视图控制方法 -----
775
+
776
+ // 显示画笔工具
664
777
  showPen() {
665
778
  this.$emit('showPen', true)
666
- // this.isDraw = true
667
779
  },
780
+ // 切换单页/双页视图
668
781
  changeView() {
669
782
  this.$emit('changeViewDoublePage', !this.doublePage)
670
783
  setTimeout(() => {
@@ -674,9 +787,13 @@ export default {
674
787
  }
675
788
  }, 500)
676
789
  },
790
+ // 全屏查看(在新窗口打开 PDF)
677
791
  fullScreen() {
678
792
  window.open(this.source)
679
793
  },
794
+
795
+ // ----- 工具配置方法 -----
796
+
680
797
  colorTap(v) {
681
798
  this.color = v
682
799
  },
@@ -686,32 +803,42 @@ export default {
686
803
  lineWidthTap(v) {
687
804
  this.lineWidth = Number(v)
688
805
  },
806
+
807
+ // ----- 键盘事件处理 -----
808
+
689
809
  handleKeyEvent(e) {
690
810
  const {key} = e
811
+ // Backspace/Delete 删除最后一个批注
691
812
  if (key === 'Backspace' || key === 'Delete') {
692
813
  if (this.contenteditable) return
693
814
  this.backward()
694
815
  }
695
816
  },
696
- // 删除
817
+
818
+ // ----- 工具状态切换方法 -----
819
+
820
+ // 删除模式
697
821
  delTap() {
698
822
  this.state = 'del'
699
823
  },
824
+ // 清空所有批注
700
825
  empty() {
701
826
  this.setShow = false
702
827
  this.points = []
703
828
  this.clearRect(true)
704
829
  this.$emit('drawEnd', this.points,'empty')
705
830
  },
831
+ // 持久化批注数据
706
832
  preserve() {
707
833
  this.setShow = false
708
834
  return this.points
709
835
  },
710
- // 后退
836
+ // 撤销最后一个批注(后退)
711
837
  backward() {
712
838
  this.setShow = false
713
839
  if (this.points.length === 0) return
714
840
  if (this.points[this.points.length - 1].type === 'line') {
841
+ // 线条类型需删除同 ID 的所有点
715
842
  const id = this.points[this.points.length - 1].id
716
843
  for (let i = 0; i < this.points.length; i++) {
717
844
  if (this.points[i].id === id) {
@@ -725,41 +852,41 @@ export default {
725
852
  this.$emit('drawEnd', this.points,'backward')
726
853
  this.clearRect()
727
854
  },
728
- // 鼠标
855
+ // 鼠标模式(选择/移动工具)
729
856
  mouse() {
730
857
  this.state = null
731
858
  this.setShow = false
732
859
  },
733
- // 画笔
860
+ // 画笔模式
734
861
  brush() {
735
862
  this.state = 'brush'
736
863
  this.setShow = true
737
864
  },
738
- // 文字字号
865
+ // 设置文字字号
739
866
  textSizeTap(v) {
740
867
  this.fontSize = Number(v)
741
868
  },
742
- // 矩形
869
+ // 矩形工具
743
870
  rectangleTap() {
744
871
  this.state = 'rectangle'
745
872
  this.setShow = true
746
873
  },
747
- // 圆形
874
+ // 圆形工具
748
875
  circularTap() {
749
876
  this.state = 'circular'
750
877
  this.setShow = true
751
878
  },
752
- // 直线
879
+ // 直线工具
753
880
  straightTap() {
754
881
  this.state = 'straight'
755
882
  this.setShow = true
756
883
  },
757
- // 箭头
884
+ // 箭头工具
758
885
  arrowTap() {
759
886
  this.state = 'arrow'
760
887
  this.setShow = true
761
888
  },
762
- // 文字
889
+ // 文字工具
763
890
  textTap(v) {
764
891
  this.turnText()
765
892
  this.state = 'text'
@@ -771,15 +898,20 @@ export default {
771
898
  this.textWidth = 100
772
899
  this.textHeight = this.fontSize + 1
773
900
  this.$refs.textInput.innerText = ''
774
- if (v === 'sign') this.signStart = true
901
+ if (v === 'sign') this.signStart = true // 签名标记模式
775
902
  },
903
+ // 设置画笔颜色
776
904
  strokeColTap(v) {
777
905
  this.color = v
778
906
  },
907
+ // 设置画笔线宽
779
908
  strokeTap(v) {
780
909
  this.lineWidth = Number(v)
781
910
  },
782
- // 查询当前点击的图形
911
+
912
+ // ----- 鼠标交互:选择/移动/删除图形 -----
913
+
914
+ // 按下时检测点击位置对应的图形
783
915
  checkDown(e) {
784
916
  e = e || event
785
917
  e.preventDefault()
@@ -794,7 +926,7 @@ export default {
794
926
  if (this.points[i].index === this.pageIndex) {
795
927
  const x1 = this.points[i].x * this.scales / this.points[i].scale
796
928
  const y1 = this.points[i].y * this.scales / this.points[i].scale
797
- // 判断点击的是否是圆
929
+ // 判断点击的是否是圆形
798
930
  if (this.points[i]?.type === 'circular') {
799
931
  const x2 = this.points[i].x * this.scales / this.points[i].scale + this.points[i].width * this.scales / this.points[i].scale
800
932
  const y2 = this.points[i].y * this.scales / this.points[i].scale + this.points[i].width * this.scales / this.points[i].scale
@@ -809,8 +941,8 @@ export default {
809
941
  this.points.splice(i, 1)
810
942
  }
811
943
  }
944
+ // 判断点击的是否是文字
812
945
  if (this.points[i]?.type === 'text') {
813
- // 取最长的一行文字
814
946
  let len = 0
815
947
  this.points[i].text.forEach(item => {
816
948
  if (item.length > len) len = item.length
@@ -851,19 +983,17 @@ export default {
851
983
  this.points.splice(i, 1)
852
984
  }
853
985
  }
854
- // 判断点击的是否是直线
986
+ // 判断点击的是否是直线/自由线条
855
987
  if (this.points[i]?.type === 'line') {
856
988
  const x2 = this.points[i + 1] ? this.points[i + 1].x * this.scales / this.points[i].scale : 0
857
989
  const y2 = this.points[i + 1] ? this.points[i + 1].y * this.scales / this.points[i].scale : 0
858
990
  const x3 = obj.x
859
991
  const y3 = obj.y
860
- // const a = Math.abs((x1-x2)*(y1-y3)-(x1-x3)*(y1-y2))
861
- // const b = Math.sqrt(Math.pow(x1-x2,2)+Math.pow(y1-y2,2))
862
992
  const c = Math.sqrt(Math.pow(x1 - x3, 2) + Math.pow(y1 - y3, 2))
863
993
  const d = Math.sqrt(Math.pow(x2 - x3, 2) + Math.pow(y2 - y3, 2))
864
994
  const scope = this.lineWidth / 2 + 15
865
- // if(a/b <= 5 && c <= b && d <= b) {
866
995
  if (c < scope && d < scope && this.points[i].id === this.points[i + 1].id) {
996
+ // 删除同 ID 的所有线条段
867
997
  const id = this.points[i].id
868
998
  for (let j = 0; j < this.points.length; j++) {
869
999
  if (this.points[j].id === id) {
@@ -876,7 +1006,7 @@ export default {
876
1006
  return
877
1007
  }
878
1008
  }
879
- // 判断点击的是否是箭头
1009
+ // 判断点击的是否是箭头或直线(两段式)
880
1010
  if (this.points[i]?.type === 'straight' || this.points[i]?.type === 'arrow') {
881
1011
  const x2 = this.points[i].x2 * this.scales / this.points[i].scale
882
1012
  const y2 = this.points[i].y2 * this.scales / this.points[i].scale
@@ -899,6 +1029,7 @@ export default {
899
1029
  }
900
1030
  }
901
1031
  },
1032
+ // 移动鼠标时平移选中的图形
902
1033
  checkMove(e) {
903
1034
  if (this.pointsSelect.length === 0) return
904
1035
  let obj = {
@@ -924,11 +1055,13 @@ export default {
924
1055
  }
925
1056
  }
926
1057
  },
1058
+ // 松开鼠标时提交选中的图形的新位置
927
1059
  checkUp(e) {
928
1060
  let obj = {
929
1061
  x: e.offsetX * this.sratio - this.checkX,
930
1062
  y: e.offsetY * this.sratio - this.checkY
931
1063
  }
1064
+ // 将移动后的像素坐标转换回原始比例坐标
932
1065
  for (let i = 0; i < this.pointsSelect.length; i++) {
933
1066
  this.pointsSelect[i].x = this.pointsSelect[i].x * this.scales / this.pointsSelect[i].scale + obj.x
934
1067
  this.pointsSelect[i].y = this.pointsSelect[i].y * this.scales / this.pointsSelect[i].scale + obj.y
@@ -947,6 +1080,10 @@ export default {
947
1080
  this.points.push(this.pointsSelect[i])
948
1081
  }
949
1082
  },
1083
+
1084
+ // ----- 鼠标事件:绘制流程入口 -----
1085
+
1086
+ // 鼠标按下:开始绘制或开始选中操作
950
1087
  mouseDown(e) {
951
1088
  if (this.doublePage) {
952
1089
  return;
@@ -955,11 +1092,13 @@ export default {
955
1092
  e = e || event
956
1093
  e.preventDefault()
957
1094
  if (this.state === null || this.state === 'del') {
1095
+ // 选择/删除模式 -> 进入选中交互
958
1096
  this.$emit('mouseClick')
959
1097
  this.checkDrawing = true
960
1098
  this.checkDown(e)
961
1099
  return
962
1100
  }
1101
+ // 绘制模式 -> 开始绘制
963
1102
  this.isDrawing = true
964
1103
  let obj = {
965
1104
  x: e.offsetX * this.sratio,
@@ -968,16 +1107,22 @@ export default {
968
1107
  this.drawStart(obj)
969
1108
  if (this.signStart) this.sign()
970
1109
  },
1110
+
1111
+ // ----- 文字框交互 -----
1112
+
1113
+ // 双击文字框进入编辑模式
971
1114
  textDblclick() {
972
1115
  if (!this.contenteditable) this.contenteditable = true
973
1116
  },
1117
+ // 文字框拖拽开始
974
1118
  textMouseDown(e) {
975
1119
  e = e || event
976
1120
  this.textDownX = e.offsetX
977
1121
  this.textDownY = e.offsetY
978
1122
  this.textMoveStart = true
979
1123
  },
980
- // 鼠标移动
1124
+
1125
+ // 鼠标移动:绘制中/拖拽文字框/选中移动中
981
1126
  mouseMove(e) {
982
1127
  if (this.doublePage) {
983
1128
  return;
@@ -985,6 +1130,7 @@ export default {
985
1130
  e = e || event
986
1131
  e.preventDefault()
987
1132
  if (this.textMoveStart) {
1133
+ // 拖拽文字框
988
1134
  this.textX2 = e.offsetX * this.sratio - this.textDownX
989
1135
  this.textY2 = e.offsetY * this.sratio - this.textDownY
990
1136
  if (this.points[this.points.length - 1].type === 'sign') {
@@ -994,10 +1140,11 @@ export default {
994
1140
  }
995
1141
  }
996
1142
  if (this.state === null) {
997
- if (this.checkDrawing) this.checkMove(e)
1143
+ if (this.checkDrawing) this.checkMove(e) // 选择模式平移
998
1144
  return
999
1145
  }
1000
1146
  if (this.isDrawing) {
1147
+ // 绘制模式下更新绘制
1001
1148
  let obj = {
1002
1149
  x: e.offsetX * this.sratio,
1003
1150
  y: e.offsetY * this.sratio
@@ -1005,7 +1152,8 @@ export default {
1005
1152
  this.drawMove(obj)
1006
1153
  }
1007
1154
  },
1008
- // 鼠标松开
1155
+
1156
+ // 鼠标松开:结束绘制或提交选中操作
1009
1157
  mouseUp(e) {
1010
1158
  e = e || event
1011
1159
  e.preventDefault()
@@ -1013,7 +1161,7 @@ export default {
1013
1161
  this.checkDrawing = false
1014
1162
  this.checkUp(e)
1015
1163
  if (this.state === 'del') {
1016
- this.delChart()
1164
+ this.delChart() // 删除选中的图形
1017
1165
  }
1018
1166
  return
1019
1167
  }
@@ -1026,9 +1174,12 @@ export default {
1026
1174
  this.signStart = false
1027
1175
  this.pointsSelect = []
1028
1176
  },
1177
+
1178
+ // 删除选中的图形
1029
1179
  delChart() {
1030
1180
  if (this.pointsSelect.length === 0) return
1031
1181
  const id = this.pointsSelect[0].id
1182
+ // 删除同 ID 的所有图形数据
1032
1183
  for (let i = 0; i < this.points.length; i++) {
1033
1184
  if (this.points[i].id === id) {
1034
1185
  this.points.splice(i, 1)
@@ -1038,14 +1189,18 @@ export default {
1038
1189
  this.$emit('drawEnd', this.points,'del')
1039
1190
  this.clearRect()
1040
1191
  },
1041
- // 绘制
1192
+
1193
+ // ----- 绘制核心方法 -----
1194
+
1195
+ // 开始绘制(记录起始点,初始化图形参数)
1042
1196
  drawStart(obj) {
1043
1197
  obj.index = this.pageIndex
1044
1198
  obj.start = 'start'
1045
- this.linkId = Math.random().toString(36).substr(2)
1199
+ this.linkId = Math.random().toString(36).substr(2) // 生成唯一 ID
1046
1200
  this.startX = obj.x
1047
1201
  this.startY = obj.y
1048
1202
  if (this.textStart) {
1203
+ // 文字工具:定位文字框位置
1049
1204
  this.textX2 = obj.x
1050
1205
  this.textY2 = obj.y
1051
1206
  this.contenteditable = true
@@ -1062,6 +1217,7 @@ export default {
1062
1217
  if (this.state === 'straight') this.straight(obj)
1063
1218
  if (this.state === 'arrow') this.arrow(obj)
1064
1219
  },
1220
+ // 结束绘制(保存最终图形数据)
1065
1221
  drawEnd(obj) {
1066
1222
  obj.index = this.pageIndex
1067
1223
  obj.start = 'end'
@@ -1071,6 +1227,7 @@ export default {
1071
1227
  if (this.state === 'straight') this.straight(obj, 'end')
1072
1228
  if (this.state === 'arrow') this.arrow(obj, 'end')
1073
1229
  },
1230
+ // 绘制过程中更新图形(实时预览)
1074
1231
  drawMove(obj) {
1075
1232
  obj.index = this.pageIndex
1076
1233
  obj.start = 'move'
@@ -1082,6 +1239,8 @@ export default {
1082
1239
  this.startY = obj.y
1083
1240
  this.startX = obj.x
1084
1241
  },
1242
+
1243
+ // 点击 Canvas 空白处时提交文字
1085
1244
  turnText() {
1086
1245
  if (this.textStart) return
1087
1246
  if (this.state === 'text') {
@@ -1096,7 +1255,10 @@ export default {
1096
1255
  this.addText(text)
1097
1256
  }
1098
1257
  },
1099
- // 画标记
1258
+
1259
+ // ----- 各图形绘制方法 -----
1260
+
1261
+ // 绘制标记(带编号的红色标记)
1100
1262
  sign() {
1101
1263
  const x = this.textX2
1102
1264
  const y = this.textY2
@@ -1107,9 +1269,9 @@ export default {
1107
1269
  this.ctx.fill();
1108
1270
  this.ctx.closePath();
1109
1271
  this.ctx.beginPath();
1110
- this.ctx.moveTo(x - 50, y + 15); // 左上角
1111
- this.ctx.lineTo(x - 10, y + 15); // 右上角
1112
- this.ctx.lineTo(x - 30, y + 40); // 下角
1272
+ this.ctx.moveTo(x - 50, y + 15);
1273
+ this.ctx.lineTo(x - 10, y + 15);
1274
+ this.ctx.lineTo(x - 30, y + 40);
1113
1275
  this.ctx.fillStyle = 'red';
1114
1276
  this.ctx.fill();
1115
1277
  this.ctx.closePath()
@@ -1130,6 +1292,7 @@ export default {
1130
1292
  num: num
1131
1293
  })
1132
1294
  },
1295
+ // 计算标记编号(自动递增)
1133
1296
  signNum() {
1134
1297
  let num = []
1135
1298
  this.points.forEach(item => {
@@ -1139,7 +1302,7 @@ export default {
1139
1302
  })
1140
1303
  return num.length === 0 ? 1 : Math.max(...num) + 1
1141
1304
  },
1142
- // 添加文字
1305
+ // 添加文字到 Canvas 并保存数据
1143
1306
  addText(v) {
1144
1307
  let obj = {}
1145
1308
  obj.index = this.pageIndex
@@ -1148,8 +1311,7 @@ export default {
1148
1311
  obj.color = this.color
1149
1312
  obj.size = this.fontSize
1150
1313
  obj.id = this.linkId
1151
- // 解析换行
1152
- const arr = v.split('\n')
1314
+ const arr = v.split('\n') // 按换行符分割
1153
1315
  for (let i = 0; i < arr.length; i++) {
1154
1316
  this.ctx.beginPath();
1155
1317
  this.ctx.font = `${this.fontSize * this.sratio}px 黑体`;
@@ -1159,7 +1321,7 @@ export default {
1159
1321
  this.ctx.closePath()
1160
1322
  }
1161
1323
  const poi = this.points[this.points.length - 1]
1162
- if (poi?.type === 'sign') poi.id = this.linkId
1324
+ if (poi?.type === 'sign') poi.id = this.linkId // 标记与文字共享 ID
1163
1325
  this.points.push({
1164
1326
  x: this.textX2,
1165
1327
  y: this.textY2,
@@ -1173,7 +1335,7 @@ export default {
1173
1335
  })
1174
1336
  this.$emit('drawEnd', this.points)
1175
1337
  },
1176
- // 画箭头
1338
+ // 绘制箭头(从起始点到鼠标位置,带箭头尖端)
1177
1339
  arrow(obj, type) {
1178
1340
  obj.index = this.pageIndex
1179
1341
  obj.type = 'arrow'
@@ -1181,6 +1343,7 @@ export default {
1181
1343
  obj.color = this.color
1182
1344
  obj.border = this.lineWidth
1183
1345
  obj.id = this.linkId
1346
+ // 计算箭头角度
1184
1347
  const angle = Math.atan2(this.textY - obj.y, this.textX - obj.x) * 180 / Math.PI
1185
1348
  const angle1 = (angle + 20) * Math.PI / 180
1186
1349
  const angle2 = (angle - 20) * Math.PI / 180
@@ -1205,6 +1368,7 @@ export default {
1205
1368
  this.ctx.stroke()
1206
1369
  this.ctx.closePath()
1207
1370
  if (type === 'end') {
1371
+ // 绘制结束时保存数据
1208
1372
  this.points.push({
1209
1373
  x: this.textX,
1210
1374
  y: this.textY,
@@ -1220,7 +1384,7 @@ export default {
1220
1384
  this.$emit('drawEnd', this.points)
1221
1385
  }
1222
1386
  },
1223
- // 画直线
1387
+ // 绘制直线
1224
1388
  straight(obj, type) {
1225
1389
  obj.index = this.pageIndex
1226
1390
  obj.type = 'straight'
@@ -1252,9 +1416,9 @@ export default {
1252
1416
  this.$emit('drawEnd', this.points)
1253
1417
  }
1254
1418
  },
1255
- // 画圆
1419
+ // 绘制圆形(从起点到鼠标位置的距离为半径)
1256
1420
  circular(obj, type) {
1257
- if (obj.x - this.textX <= 0) return
1421
+ if (obj.x - this.textX <= 0) return // 半径不能为负
1258
1422
  obj.index = this.pageIndex
1259
1423
  obj.type = 'circular'
1260
1424
  obj.scale = this.scales
@@ -1284,7 +1448,7 @@ export default {
1284
1448
  this.$emit('drawEnd', this.points)
1285
1449
  }
1286
1450
  },
1287
- // 画矩形
1451
+ // 绘制矩形
1288
1452
  rectangle(obj, type) {
1289
1453
  obj.index = this.pageIndex
1290
1454
  obj.type = 'rectangle'
@@ -1315,7 +1479,7 @@ export default {
1315
1479
  this.$emit('drawEnd', this.points)
1316
1480
  }
1317
1481
  },
1318
- // 画线
1482
+ // 绘制自由线条(画笔)
1319
1483
  line(obj) {
1320
1484
  obj.index = this.pageIndex
1321
1485
  obj.type = 'line'
@@ -1335,13 +1499,18 @@ export default {
1335
1499
  this.points.push(obj)
1336
1500
  this.$emit('drawEnd', obj,'line')
1337
1501
  },
1502
+
1503
+ // ----- Canvas 重绘相关方法 -----
1504
+
1505
+ // 清除 Canvas 并重绘所有批注
1338
1506
  clearRect(v) {
1339
1507
  const width = this.canvasStyle.widthCanvas
1340
1508
  const height = this.canvasStyle.heightCanvas
1341
1509
  this.ctx.clearRect(0, 0, width, height)
1342
- if (v) return
1510
+ if (v) return // 仅清空不重绘
1343
1511
  this.processing()
1344
1512
  },
1513
+ // 重绘线条
1345
1514
  lineProcessing(data, data2, v) {
1346
1515
  this.ctx.beginPath()
1347
1516
  this.ctx.moveTo(data.x * this.scales / data.scale + v.x, data.y * this.scales / data.scale + v.y)
@@ -1355,6 +1524,7 @@ export default {
1355
1524
  this.ctx.stroke()
1356
1525
  this.ctx.closePath()
1357
1526
  },
1527
+ // 重绘矩形
1358
1528
  rectangleProcessing(data, v) {
1359
1529
  this.ctx.beginPath()
1360
1530
  this.ctx.lineWidth = data.border * this.sratio
@@ -1363,6 +1533,7 @@ export default {
1363
1533
  this.ctx.stroke()
1364
1534
  this.ctx.closePath()
1365
1535
  },
1536
+ // 重绘圆形
1366
1537
  circularProcessing(data, v) {
1367
1538
  this.ctx.beginPath()
1368
1539
  this.ctx.lineWidth = data.border * this.sratio
@@ -1371,6 +1542,7 @@ export default {
1371
1542
  this.ctx.stroke()
1372
1543
  this.ctx.closePath()
1373
1544
  },
1545
+ // 重绘直线
1374
1546
  straightProcessing(data, v) {
1375
1547
  this.ctx.beginPath()
1376
1548
  this.ctx.lineWidth = data.border * this.sratio
@@ -1380,6 +1552,7 @@ export default {
1380
1552
  this.ctx.stroke()
1381
1553
  this.ctx.closePath()
1382
1554
  },
1555
+ // 重绘箭头
1383
1556
  arrowProcessing(data, v) {
1384
1557
  const angle = Math.atan2(data.y * this.scales / data.scale - data.y2 * this.scales / data.scale, data.x * this.scales / data.scale - data.x2 * this.scales / data.scale) * 180 / Math.PI
1385
1558
  const angle1 = (angle + 20) * Math.PI / 180
@@ -1404,6 +1577,7 @@ export default {
1404
1577
  this.ctx.stroke()
1405
1578
  this.ctx.closePath()
1406
1579
  },
1580
+ // 重绘文字
1407
1581
  textProcessing(data, v) {
1408
1582
  const arr = data.text
1409
1583
  for (let i = 0; i < arr.length; i++) {
@@ -1415,6 +1589,7 @@ export default {
1415
1589
  this.ctx.closePath()
1416
1590
  }
1417
1591
  },
1592
+ // 重绘标记
1418
1593
  signProcessing(data, v) {
1419
1594
  const x = data.x * this.scales / data.scale + v.x
1420
1595
  const y = data.y * this.scales / data.scale + v.y
@@ -1424,9 +1599,9 @@ export default {
1424
1599
  this.ctx.fill();
1425
1600
  this.ctx.closePath();
1426
1601
  this.ctx.beginPath();
1427
- this.ctx.moveTo(x - 50, y + 15); // 左上角
1428
- this.ctx.lineTo(x - 10, y + 15); // 右上角
1429
- this.ctx.lineTo(x - 30, y + 40); // 下角
1602
+ this.ctx.moveTo(x - 50, y + 15);
1603
+ this.ctx.lineTo(x - 10, y + 15);
1604
+ this.ctx.lineTo(x - 30, y + 40);
1430
1605
  this.ctx.fillStyle = 'red';
1431
1606
  this.ctx.fill();
1432
1607
  this.ctx.closePath()
@@ -1437,7 +1612,8 @@ export default {
1437
1612
  this.ctx.stroke();
1438
1613
  this.ctx.closePath()
1439
1614
  },
1440
- // 处理数据
1615
+
1616
+ // 处理所有批注数据并重绘到 Canvas
1441
1617
  processing() {
1442
1618
  for (let i = 0; i < this.points.length; i++) {
1443
1619
  if (this.points[i].index === this.pageIndex) {
@@ -1465,6 +1641,10 @@ export default {
1465
1641
  }
1466
1642
  }
1467
1643
  },
1644
+
1645
+ // ----- 翻页与PDF加载 -----
1646
+
1647
+ // 下一页(向左翻页)
1468
1648
  flipLeft(e, v) {
1469
1649
  this.extendRightDel()
1470
1650
  if (e > this.pdfPages) {
@@ -1486,6 +1666,7 @@ export default {
1486
1666
  })
1487
1667
  }
1488
1668
  },
1669
+ // 上一页(向右翻页)
1489
1670
  flipRight(e, v) {
1490
1671
  this.extendRightDel()
1491
1672
  if (e < 1) {
@@ -1507,18 +1688,19 @@ export default {
1507
1688
  })
1508
1689
  }
1509
1690
  },
1691
+ // 加载 PDF 源文件
1510
1692
  sourceUrl() {
1511
1693
  if (this.source === '' || this.source === undefined || this.source === null) return
1512
1694
  let that = this
1513
1695
  that.loading = true
1514
1696
  const loadingTask = PdfJs.getDocument({
1515
1697
  url: that.source,
1516
- cMapUrl: "/zydx/static/cmaps/",
1698
+ cMapUrl: "/zydx/static/cmaps/", // 中文字符映射表路径
1517
1699
  cMapPacked: true
1518
1700
  })
1519
1701
  loadingTask.promise.then((pdf) => {
1520
1702
  pdfs[that.id] = pdf
1521
- that.pdfPages = pdf.numPages // 获取pdf文件的总页数
1703
+ that.pdfPages = pdf.numPages
1522
1704
  that.$nextTick(() => {
1523
1705
  if (that.doublePage) {
1524
1706
  that.readDoublePdf(pdf)
@@ -1534,14 +1716,16 @@ export default {
1534
1716
  })
1535
1717
  })
1536
1718
  },
1719
+ // 获取字体信息
1537
1720
  fontName(v,n) {
1538
1721
  return n[v]
1539
1722
  },
1723
+ // 单页模式:渲染单页 PDF
1540
1724
  readPdf: debounce(function () {
1541
1725
  if(Object.keys(pdfs).length === 0) return
1542
1726
  setTimeout(() => {
1543
1727
  let that = this
1544
- that.mouse()
1728
+ that.mouse() // 重置工具状态
1545
1729
  that.$nextTick(() => {
1546
1730
  pdfs[that.id]?.getPage(that.pageIndex).then(function (page) {
1547
1731
  that.doc = page
@@ -1551,6 +1735,7 @@ export default {
1551
1735
  let canvas2 = document.getElementById(`${that.id}2`)
1552
1736
  that.ctx = canvas?.getContext('2d');
1553
1737
  let ctx2 = canvas2?.getContext('2d');
1738
+ // 计算合适的缩放比例
1554
1739
  let viewport1 = page.getViewport({scale: 1.5})
1555
1740
  const sc1 = (viewport1.width / viewport1.height).toFixed(2)
1556
1741
  that.boxWidth = that.$refs.wrap?.offsetWidth
@@ -1565,6 +1750,7 @@ export default {
1565
1750
  const sc = (viewport.width / viewport.height).toFixed(2)
1566
1751
  const wid = that.$refs.wrap?.offsetWidth - 10
1567
1752
  const hei = wid / sc
1753
+ // 设置 Canvas 尺寸(sratio倍提高清晰度)
1568
1754
  canvas.width = wid * that.sratio
1569
1755
  canvas.height = hei * that.sratio
1570
1756
  canvas.style.width = wid + 'px'
@@ -1583,13 +1769,15 @@ export default {
1583
1769
  let renderContext = {
1584
1770
  canvasContext: ctx2,
1585
1771
  viewport: viewport,
1586
- transform: [1, 0, 0, 1, 0, 0] //这里会进行放大,解决模糊问题
1772
+ transform: [1, 0, 0, 1, 0, 0] // 避免放大导致的模糊
1587
1773
  }
1588
1774
  page.render(renderContext)
1775
+ // 获取文本内容用于 textLayer 渲染
1589
1776
  page.getTextContent().then((res) => {
1590
1777
  that.items = res.items
1591
1778
  that.styles = res.styles
1592
1779
  })
1780
+ // 获取 PDF 内置的标注数据
1593
1781
  page.getAnnotations().then((res) => {
1594
1782
  that.annotationsInit = res
1595
1783
  const arr = that.annotationsData.map(x => {
@@ -1610,18 +1798,19 @@ export default {
1610
1798
  that.flipRights = false
1611
1799
  that.flipLefts = true
1612
1800
  }
1613
- // 获取canvas的base64数据
1801
+ // 延迟生成 Canvas Base64 数据用于翻页过渡动画
1614
1802
  setTimeout(() => {
1615
1803
  that.flipRights = false
1616
1804
  that.flipLefts = false
1617
1805
  that.oneShow = false
1618
1806
  that.imgArr = canvas2.toDataURL('image/jpeg', 1)
1619
- that.processing()
1807
+ that.processing() // 重绘批注
1620
1808
  }, 1000)
1621
1809
  })
1622
1810
  })
1623
1811
  }, 200)
1624
1812
  }, 200),
1813
+ // 双页模式:渲染所有页的 PDF
1625
1814
  readDoublePdf: debounce(function () {
1626
1815
  setTimeout(() => {
1627
1816
  let that = this
@@ -1668,7 +1857,7 @@ export default {
1668
1857
  let renderContext = {
1669
1858
  canvasContext: item.ctx2,
1670
1859
  viewport: viewport,
1671
- transform: [1, 0, 0, 1, 0, 0] //这里会进行放大,解决模糊问题
1860
+ transform: [1, 0, 0, 1, 0, 0]
1672
1861
  }
1673
1862
  page.render(renderContext)
1674
1863
  page.getTextContent().then((res) => {
@@ -1687,7 +1876,6 @@ export default {
1687
1876
  that.flipRights = false
1688
1877
  that.flipLefts = true
1689
1878
  }
1690
- // 获取canvas的base64数据
1691
1879
  setTimeout(() => {
1692
1880
  that.flipRights = false
1693
1881
  that.flipLefts = false
@@ -1700,8 +1888,8 @@ export default {
1700
1888
  })
1701
1889
  }, 200)
1702
1890
  }, 200),
1891
+ // 生成唯一 Canvas ID(时间戳+随机字符串)
1703
1892
  generateUniqueKey() {
1704
- // Math.random().toString(36).substr(2)
1705
1893
  return `${new Date().getTime()}${Math.random().toString(36).substr(2)}`;
1706
1894
  }
1707
1895
  }
@@ -1713,6 +1901,7 @@ export default {
1713
1901
  @import './icon.css';
1714
1902
  @import './annotationLayer.css';
1715
1903
  @import './textLayer.css';
1904
+ /* 拓展阅读批注容器 */
1716
1905
  .magnifier{
1717
1906
  position: absolute;
1718
1907
  text-align: initial;
@@ -1727,6 +1916,7 @@ export default {
1727
1916
  display: block;
1728
1917
  margin: 0 auto;
1729
1918
  }
1919
+ /* 拓展阅读弹出列表 */
1730
1920
  .magnifier-list{
1731
1921
  position: absolute;
1732
1922
  width: 200px;
@@ -1739,6 +1929,7 @@ export default {
1739
1929
  opacity: 0;
1740
1930
  overflow: hidden;
1741
1931
  }
1932
+ /* 拓展阅读列表头部 */
1742
1933
  .magnifier-head{
1743
1934
  height: 20px;
1744
1935
  width: 100%;
@@ -1760,6 +1951,7 @@ export default {
1760
1951
  font-size: 12px;
1761
1952
  color: #fff;
1762
1953
  }
1954
+ /* 拓展阅读内容区 */
1763
1955
  .magnifier-cont{
1764
1956
  padding: 5px;
1765
1957
  }
@@ -1783,12 +1975,10 @@ export default {
1783
1975
  opacity: 1;
1784
1976
  }
1785
1977
  }
1978
+ /* 右键菜单-添加链接 */
1786
1979
  .extend-right{
1787
1980
  position: absolute;
1788
1981
  z-index: 5;
1789
- /* border-radius: 20px; */
1790
- /* box-shadow: 0 0 10px rgba(0, 0, 0, 0.25); */
1791
- /* padding: 10px; */
1792
1982
  background-color: #DEDCDC;
1793
1983
  cursor: pointer;
1794
1984
  transform: scaleX(-1);
@@ -1799,17 +1989,6 @@ export default {
1799
1989
  display: flex;
1800
1990
  transform: scaleX(-1);
1801
1991
  }
1802
- /* .extend-right:after {
1803
- content: '';
1804
- position: absolute;
1805
- top: 54%;
1806
- right: 0;
1807
- width: 30px;
1808
- height: 30px;
1809
- background: url("../img/sanjiao.png") no-repeat 0 0;
1810
- background-size: 100% 100%;
1811
- transform: rotate(-85deg) scaleY(-1);
1812
- } */
1813
1992
  .extend-right-cont>img{
1814
1993
  width: 14px !important;
1815
1994
  height: 14px !important;
@@ -1820,8 +1999,8 @@ export default {
1820
1999
  line-height: 20px;
1821
2000
  font-size: 12px;
1822
2001
  color: #fff;
1823
- /* padding-left: 5px; */
1824
2002
  }
2003
+ /* 鼠标穿透*/
1825
2004
  .isPointer{
1826
2005
  pointer-events: none;
1827
2006
  }
@@ -1829,6 +2008,7 @@ export default {
1829
2008
  width: 20px !important;
1830
2009
  font-size: 12px !important;
1831
2010
  }
2011
+ /* 标注层 */
1832
2012
  .annotationLayer{
1833
2013
  width: 100%;
1834
2014
  height: 100%;
@@ -1848,12 +2028,14 @@ export default {
1848
2028
  width: 100%;
1849
2029
  height: 100%;
1850
2030
  }
2031
+ /* 文本层 */
1851
2032
  .textLayer>:is(i){
1852
2033
  position: absolute;
1853
2034
  }
1854
2035
  .textLayer :is(span){
1855
2036
  color: transparent;
1856
2037
  }
2038
+ /* 批注绘制画布 */
1857
2039
  .wrap-can-one {
1858
2040
  position: absolute;
1859
2041
  top: 0;
@@ -1861,6 +2043,7 @@ export default {
1861
2043
  z-index: 10;
1862
2044
  }
1863
2045
 
2046
+ /* 文字批注拖拽框 */
1864
2047
  .text-frame {
1865
2048
  position: absolute;
1866
2049
  top: 0;
@@ -1883,6 +2066,7 @@ export default {
1883
2066
  height: 100%;
1884
2067
  }
1885
2068
 
2069
+ /* 页面容器 */
1886
2070
  .wrap {
1887
2071
  width: 100%;
1888
2072
  height: calc(100% - 20px);
@@ -1892,6 +2076,7 @@ export default {
1892
2076
  padding: 5px;
1893
2077
  }
1894
2078
 
2079
+ /* 双页模式奇数偶数页间距 */
1895
2080
  .wrap-block:nth-child(2n+1) {
1896
2081
  padding: 5px 5px 5px 10px;
1897
2082
  }
@@ -1900,6 +2085,7 @@ export default {
1900
2085
  padding: 5px 10px 5px 5px;
1901
2086
  }
1902
2087
 
2088
+ /* Canvas 容器 */
1903
2089
  .wrap-can {
1904
2090
  width: 100%;
1905
2091
  /*display: inline-block;*/
@@ -1918,11 +2104,13 @@ export default {
1918
2104
  height: 100%;
1919
2105
  }
1920
2106
 
2107
+ /* 底部翻页按钮 */
1921
2108
  .but-wrap {
1922
2109
  text-align: center;
1923
2110
  margin-top: 20px;
1924
2111
  }
1925
2112
 
2113
+ /* pages 区域 */
1926
2114
  .pages {
1927
2115
  width: 100%;
1928
2116
  height: 100%;
@@ -1930,6 +2118,7 @@ export default {
1930
2118
  overflow-y: auto;
1931
2119
  }
1932
2120
 
2121
+ /* 左侧翻页按钮区域 */
1933
2122
  .page-but-left {
1934
2123
  position: absolute;
1935
2124
  top: 0;
@@ -1947,6 +2136,7 @@ export default {
1947
2136
  left: 0;
1948
2137
  }
1949
2138
 
2139
+ /* 右侧翻页按钮区域 */
1950
2140
  .page-but-right {
1951
2141
  position: absolute;
1952
2142
  top: 0;
@@ -1972,11 +2162,13 @@ export default {
1972
2162
  font-size: 14px;
1973
2163
  }
1974
2164
 
2165
+ /* 文档视图主容器 */
1975
2166
  .docView {
1976
2167
  height: 100%;
1977
2168
  position: relative;
1978
2169
  }
1979
2170
 
2171
+ /* 翻页按钮样式 */
1980
2172
  .but-brown {
1981
2173
  background-color: #c64c10;
1982
2174
  padding: 0 20px;
@@ -1990,25 +2182,24 @@ export default {
1990
2182
  margin-left: 10px;
1991
2183
  }
1992
2184
 
2185
+ /* PDF 书本容器 */
1993
2186
  .book {
1994
2187
  width: 100%;
1995
- /*min-height: 600px;*/
1996
- /*max-height: 1200px;
1997
- overflow-y: auto;*/
1998
2188
  }
1999
2189
 
2000
2190
  .book::-webkit-scrollbar {
2001
2191
  display: none;
2002
2192
  }
2003
2193
 
2194
+ /* 按钮禁用态 */
2004
2195
  .but-act {
2005
2196
  background-color: #bbbbbb;
2006
2197
  color: #fff;
2007
2198
  }
2008
2199
 
2200
+ /* 加载动画遮罩 */
2009
2201
  .loading-back {
2010
2202
  min-height: 600px;
2011
- /*position: absolute;*/
2012
2203
  top: 0;
2013
2204
  left: 0;
2014
2205
  right: 0;
@@ -2023,6 +2214,7 @@ export default {
2023
2214
  margin: 0 auto;
2024
2215
  }
2025
2216
 
2217
+ /* 加载文字动画 */
2026
2218
  .loadings {
2027
2219
  display: inline-block;
2028
2220
  font-size: 28px;
@@ -2056,6 +2248,7 @@ export default {
2056
2248
  }
2057
2249
  }
2058
2250
 
2251
+ /* 双页模式容器 */
2059
2252
  .wrap-box {
2060
2253
  display: flex;
2061
2254
  flex-flow: row wrap;
@@ -2070,6 +2263,7 @@ div {
2070
2263
  box-sizing: border-box;
2071
2264
  }
2072
2265
 
2266
+ /* 全屏查看工具栏 */
2073
2267
  .full-screen {
2074
2268
  position: absolute;
2075
2269
  top: 20px;
@@ -2104,6 +2298,5 @@ div {
2104
2298
  .full-screen-img > img {
2105
2299
  width: 20px;
2106
2300
  height: 20px;
2107
- /*margin-left: 5px;*/
2108
2301
  }
2109
2302
  </style>