@veltra/desktop 1.1.12 → 1.1.14
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/dist/components/batch-edit/batch-edit.js +1 -1
- package/dist/components/batch-edit/batch-edit.js.map +1 -1
- package/dist/components/batch-edit/style2.css +7 -1
- package/dist/components/cascade/cascade.vue.d.ts +1 -1
- package/dist/components/file-viewer/file-viewer.js +62 -19
- package/dist/components/file-viewer/file-viewer.js.map +1 -1
- package/dist/components/file-viewer/previewers/image-previewer.js +3 -2
- package/dist/components/file-viewer/previewers/image-previewer.js.map +1 -1
- package/dist/components/file-viewer/previewers/pdf-previewer.js +60 -17
- package/dist/components/file-viewer/previewers/pdf-previewer.js.map +1 -1
- package/dist/components/file-viewer/previewers/pdf-viewport-scroll.js +83 -0
- package/dist/components/file-viewer/previewers/pdf-viewport-scroll.js.map +1 -0
- package/dist/components/file-viewer/previewers/pdf-zoom-bridge.js +32 -0
- package/dist/components/file-viewer/previewers/pdf-zoom-bridge.js.map +1 -0
- package/dist/components/file-viewer/style2.css +14 -1
- package/dist/components/multi-select/multi-select.vue.d.ts +1 -1
- package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +1 -1
- package/dist/components/scroll/scroll.js +94 -30
- package/dist/components/scroll/scroll.js.map +1 -1
- package/dist/components/scroll/style2.css +22 -0
- package/dist/components/tabs/tabs-horizontal.vue.d.ts +2 -2
- package/dist/components/tabs/tabs-vertical.vue.d.ts +2 -2
- package/dist/components/tabs/tabs.vue.d.ts +2 -2
- package/dist/components/tree/tree.vue.d.ts +1 -1
- package/dist/components/tree-select/tree-select.vue.d.ts +1 -1
- package/dist/types/scroll.d.ts +6 -1
- package/package.json +12 -11
- package/src/components/batch-edit/batch-edit.vue +1 -1
- package/src/components/batch-edit/style.scss +9 -1
- package/src/components/file-viewer/file-viewer.vue +73 -16
- package/src/components/file-viewer/previewers/image-previewer.vue +1 -0
- package/src/components/file-viewer/previewers/pdf-previewer.vue +67 -14
- package/src/components/file-viewer/previewers/pdf-viewport-scroll.vue +112 -0
- package/src/components/file-viewer/previewers/pdf-zoom-bridge.vue +33 -0
- package/src/components/file-viewer/style.scss +16 -1
- package/src/components/scroll/scroll.vue +124 -75
- package/src/components/scroll/style.scss +22 -0
- package/src/types/scroll.ts +7 -1
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
</u-icon>
|
|
79
79
|
</button>
|
|
80
80
|
</span>
|
|
81
|
-
<span v-if="
|
|
81
|
+
<span v-if="isZoomable" :class="cls.e('action-group')">
|
|
82
82
|
<button
|
|
83
83
|
:class="[cls.e('action'), cls.em('action', 'icon')]"
|
|
84
84
|
:disabled="zoomOutDisabled"
|
|
@@ -154,19 +154,22 @@
|
|
|
154
154
|
bem.is('pannable', canPan),
|
|
155
155
|
bem.is('dragging', isDragging)
|
|
156
156
|
]"
|
|
157
|
-
@pointerdown="handleViewportPointerDown"
|
|
158
|
-
@pointermove="handleViewportPointerMove"
|
|
159
|
-
@pointerup="handleViewportPointerEnd"
|
|
160
|
-
@pointercancel="handleViewportPointerEnd"
|
|
157
|
+
@pointerdown.capture="handleViewportPointerDown"
|
|
158
|
+
@pointermove.capture="handleViewportPointerMove"
|
|
159
|
+
@pointerup.capture="handleViewportPointerEnd"
|
|
160
|
+
@pointercancel.capture="handleViewportPointerEnd"
|
|
161
|
+
@wheel="handleViewportWheel"
|
|
161
162
|
@dblclick="handleViewportDblclick"
|
|
162
163
|
>
|
|
163
164
|
<component
|
|
164
165
|
:is="PreviewerMap[activeFile.kind]"
|
|
166
|
+
ref="previewerRef"
|
|
165
167
|
:file="activeFile"
|
|
166
168
|
:max-rows="sheetMaxRows"
|
|
167
169
|
:class="isTransformable ? cls.e('previewer') : undefined"
|
|
168
170
|
:style="previewerStyle"
|
|
169
171
|
@error="handleChildError"
|
|
172
|
+
@zoom-change="handlePdfZoomChange"
|
|
170
173
|
/>
|
|
171
174
|
</div>
|
|
172
175
|
<div v-else :class="cls.e('empty')">
|
|
@@ -236,14 +239,24 @@ const cls = bem('file-viewer')
|
|
|
236
239
|
|
|
237
240
|
const MIN_SCALE = 0.5
|
|
238
241
|
const MAX_SCALE = 3
|
|
239
|
-
|
|
240
|
-
const
|
|
242
|
+
/** 每次缩放固定增减 10% */
|
|
243
|
+
const SCALE_STEP = 0.1
|
|
244
|
+
/** 使用 CSS transform 缩放的类型(PDF 由 EmbedPDF zoom 插件处理) */
|
|
245
|
+
const TRANSFORMABLE_KINDS = new Set<FileViewerKind>(['image'])
|
|
246
|
+
/** 工具栏显示缩放控件的类型 */
|
|
247
|
+
const ZOOMABLE_KINDS = new Set<FileViewerKind>(['image', 'pdf'])
|
|
241
248
|
|
|
242
249
|
const activeId = defineModel<string | undefined>('modelValue', { default: undefined })
|
|
243
250
|
const openModel = defineModel<boolean | undefined>('open', { default: undefined })
|
|
244
251
|
|
|
245
252
|
const rootRef = shallowRef<HTMLDivElement>()
|
|
253
|
+
const previewerRef = shallowRef<{
|
|
254
|
+
zoomIn?: () => void
|
|
255
|
+
zoomOut?: () => void
|
|
256
|
+
resetZoom?: () => void
|
|
257
|
+
}>()
|
|
246
258
|
const scale = ref(1)
|
|
259
|
+
const pdfZoomLevel = ref(1)
|
|
247
260
|
const offsetX = ref(0)
|
|
248
261
|
const offsetY = ref(0)
|
|
249
262
|
const isDragging = ref(false)
|
|
@@ -310,17 +323,26 @@ const isTransformable = computed(
|
|
|
310
323
|
() => !!activeFile.value && TRANSFORMABLE_KINDS.has(activeFile.value.kind)
|
|
311
324
|
)
|
|
312
325
|
|
|
326
|
+
const isZoomable = computed(() => !!activeFile.value && ZOOMABLE_KINDS.has(activeFile.value.kind))
|
|
327
|
+
|
|
328
|
+
const isPdfActive = computed(() => activeFile.value?.kind === 'pdf')
|
|
329
|
+
|
|
313
330
|
const canPan = computed(() => isTransformable.value && scale.value > 1)
|
|
314
331
|
|
|
315
|
-
const
|
|
332
|
+
const displayZoomLevel = computed(() => (isPdfActive.value ? pdfZoomLevel.value : scale.value))
|
|
316
333
|
|
|
317
|
-
const
|
|
334
|
+
const zoomPercent = computed(() => `${Math.round(displayZoomLevel.value * 100)}%`)
|
|
318
335
|
|
|
319
|
-
const
|
|
336
|
+
const zoomInDisabled = computed(() => !isZoomable.value || displayZoomLevel.value >= MAX_SCALE)
|
|
320
337
|
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
)
|
|
338
|
+
const zoomOutDisabled = computed(() => !isZoomable.value || displayZoomLevel.value <= MIN_SCALE)
|
|
339
|
+
|
|
340
|
+
const isTransformReset = computed(() => {
|
|
341
|
+
if (isPdfActive.value) {
|
|
342
|
+
return Math.abs(pdfZoomLevel.value - 1) < 0.02
|
|
343
|
+
}
|
|
344
|
+
return scale.value === 1 && offsetX.value === 0 && offsetY.value === 0
|
|
345
|
+
})
|
|
324
346
|
|
|
325
347
|
const previewerStyle = computed<CSSProperties | undefined>(() => {
|
|
326
348
|
if (!isTransformable.value) return undefined
|
|
@@ -368,16 +390,29 @@ function setScale(value: number) {
|
|
|
368
390
|
}
|
|
369
391
|
|
|
370
392
|
function zoomIn() {
|
|
371
|
-
if (!
|
|
393
|
+
if (!isZoomable.value) return
|
|
394
|
+
if (isPdfActive.value) {
|
|
395
|
+
previewerRef.value?.zoomIn?.()
|
|
396
|
+
return
|
|
397
|
+
}
|
|
372
398
|
setScale(scale.value + SCALE_STEP)
|
|
373
399
|
}
|
|
374
400
|
|
|
375
401
|
function zoomOut() {
|
|
376
|
-
if (!
|
|
402
|
+
if (!isZoomable.value) return
|
|
403
|
+
if (isPdfActive.value) {
|
|
404
|
+
previewerRef.value?.zoomOut?.()
|
|
405
|
+
return
|
|
406
|
+
}
|
|
377
407
|
setScale(scale.value - SCALE_STEP)
|
|
378
408
|
}
|
|
379
409
|
|
|
380
410
|
function resetTransform() {
|
|
411
|
+
if (isPdfActive.value) {
|
|
412
|
+
previewerRef.value?.resetZoom?.()
|
|
413
|
+
pdfZoomLevel.value = 1
|
|
414
|
+
return
|
|
415
|
+
}
|
|
381
416
|
scale.value = 1
|
|
382
417
|
offsetX.value = 0
|
|
383
418
|
offsetY.value = 0
|
|
@@ -385,6 +420,23 @@ function resetTransform() {
|
|
|
385
420
|
isDragging.value = false
|
|
386
421
|
}
|
|
387
422
|
|
|
423
|
+
function handlePdfZoomChange(level: number) {
|
|
424
|
+
pdfZoomLevel.value = level
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function isZoomWheel(e: WheelEvent): boolean {
|
|
428
|
+
return e.ctrlKey || e.metaKey
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function handleViewportWheel(e: WheelEvent) {
|
|
432
|
+
if (!activeFile.value || activeFile.value.kind !== 'image') return
|
|
433
|
+
if (!isZoomWheel(e)) return
|
|
434
|
+
if (isInteractiveTarget(e.target)) return
|
|
435
|
+
e.preventDefault()
|
|
436
|
+
const delta = e.deltaY > 0 ? -SCALE_STEP : SCALE_STEP
|
|
437
|
+
setScale(scale.value + delta)
|
|
438
|
+
}
|
|
439
|
+
|
|
388
440
|
function isInteractiveTarget(target: EventTarget | null): boolean {
|
|
389
441
|
if (!(target instanceof HTMLElement)) return false
|
|
390
442
|
return !!target.closest('button, a, input, textarea, select, video, [contenteditable="true"]')
|
|
@@ -423,6 +475,7 @@ function handleViewportPointerEnd(e: PointerEvent) {
|
|
|
423
475
|
}
|
|
424
476
|
|
|
425
477
|
function handleViewportDblclick() {
|
|
478
|
+
if (isPdfActive.value) return
|
|
426
479
|
if (!isTransformable.value) return
|
|
427
480
|
if (scale.value === 1) {
|
|
428
481
|
setScale(2)
|
|
@@ -480,7 +533,11 @@ watch(
|
|
|
480
533
|
|
|
481
534
|
watch(
|
|
482
535
|
() => activeFile.value?.id,
|
|
483
|
-
() =>
|
|
536
|
+
async () => {
|
|
537
|
+
pdfZoomLevel.value = 1
|
|
538
|
+
await nextTick()
|
|
539
|
+
resetTransform()
|
|
540
|
+
}
|
|
484
541
|
)
|
|
485
542
|
|
|
486
543
|
// 模态打开时:锁定 body 滚动 + 让容器获得焦点以便接收 ESC
|
|
@@ -2,22 +2,31 @@
|
|
|
2
2
|
<div :class="cls.e('pdf')" v-bind="attrs">
|
|
3
3
|
<div v-if="!engine || isLoading" :class="cls.e('loading')">正在加载 PDF 引擎…</div>
|
|
4
4
|
<EmbedPDF v-else-if="pdfUrl" :engine="engine" :plugins="plugins" v-slot="{ activeDocumentId }">
|
|
5
|
+
<PdfZoomBridge
|
|
6
|
+
v-if="activeDocumentId"
|
|
7
|
+
:document-id="activeDocumentId"
|
|
8
|
+
@zoom-change="onZoomChange"
|
|
9
|
+
/>
|
|
5
10
|
<DocumentContent
|
|
6
11
|
v-if="activeDocumentId"
|
|
7
12
|
:document-id="activeDocumentId"
|
|
8
13
|
v-slot="{ isLoaded }"
|
|
9
14
|
>
|
|
10
15
|
<Viewport v-if="isLoaded" :document-id="activeDocumentId" :class="cls.e('pdf-viewport')">
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
<PdfViewportScroll>
|
|
17
|
+
<ZoomGestureWrapper :document-id="activeDocumentId" :enable-wheel="true">
|
|
18
|
+
<Scroller :document-id="activeDocumentId">
|
|
19
|
+
<template #default="{ page }">
|
|
20
|
+
<div
|
|
21
|
+
:class="cls.e('pdf-page')"
|
|
22
|
+
:style="{ width: page.width + 'px', height: page.height + 'px' }"
|
|
23
|
+
>
|
|
24
|
+
<RenderLayer :document-id="activeDocumentId" :page-index="page.pageIndex" />
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
</Scroller>
|
|
28
|
+
</ZoomGestureWrapper>
|
|
29
|
+
</PdfViewportScroll>
|
|
21
30
|
</Viewport>
|
|
22
31
|
</DocumentContent>
|
|
23
32
|
</EmbedPDF>
|
|
@@ -35,17 +44,28 @@ import {
|
|
|
35
44
|
import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/vue'
|
|
36
45
|
import { ScrollPluginPackage, Scroller } from '@embedpdf/plugin-scroll/vue'
|
|
37
46
|
import { ViewportPluginPackage, Viewport } from '@embedpdf/plugin-viewport/vue'
|
|
47
|
+
import {
|
|
48
|
+
ZoomGestureWrapper,
|
|
49
|
+
ZoomMode,
|
|
50
|
+
ZoomPluginPackage,
|
|
51
|
+
type ZoomScope
|
|
52
|
+
} from '@embedpdf/plugin-zoom/vue'
|
|
38
53
|
import { bem } from '@veltra/utils'
|
|
39
54
|
import { computed, onBeforeUnmount, shallowRef, useAttrs, watch } from 'vue'
|
|
40
55
|
|
|
41
56
|
import type { FileViewerItem } from '../../../types/file-viewer'
|
|
42
57
|
import { toBlobUrl } from '../helper'
|
|
58
|
+
import PdfViewportScroll from './pdf-viewport-scroll.vue'
|
|
59
|
+
import PdfZoomBridge from './pdf-zoom-bridge.vue'
|
|
43
60
|
|
|
44
61
|
defineOptions({ name: 'FileViewerPdfPreviewer', inheritAttrs: false })
|
|
45
62
|
|
|
46
63
|
const props = defineProps<{ file: FileViewerItem }>()
|
|
47
64
|
|
|
48
|
-
const emit = defineEmits<{
|
|
65
|
+
const emit = defineEmits<{
|
|
66
|
+
(e: 'error', err: unknown): void
|
|
67
|
+
(e: 'zoom-change', level: number): void
|
|
68
|
+
}>()
|
|
49
69
|
|
|
50
70
|
const cls = bem('file-viewer')
|
|
51
71
|
const attrs = useAttrs()
|
|
@@ -55,6 +75,9 @@ const { engine, isLoading, error } = usePdfiumEngine()
|
|
|
55
75
|
const pdfUrl = shallowRef<string>('')
|
|
56
76
|
let revoke: (() => void) | undefined
|
|
57
77
|
|
|
78
|
+
/** 当前文档的 zoom API(由 PdfZoomBridge 注入) */
|
|
79
|
+
let zoomScope: ZoomScope | null = null
|
|
80
|
+
|
|
58
81
|
function load() {
|
|
59
82
|
revoke?.()
|
|
60
83
|
const r = toBlobUrl(props.file.src, props.file.mime ?? 'application/pdf')
|
|
@@ -68,12 +91,35 @@ const plugins = computed(() => {
|
|
|
68
91
|
createPluginRegistration(DocumentManagerPluginPackage, {
|
|
69
92
|
initialDocuments: [{ url: pdfUrl.value }]
|
|
70
93
|
}),
|
|
71
|
-
createPluginRegistration(ViewportPluginPackage),
|
|
72
|
-
createPluginRegistration(ScrollPluginPackage),
|
|
73
|
-
createPluginRegistration(RenderPluginPackage)
|
|
94
|
+
createPluginRegistration(ViewportPluginPackage, { viewportGap: 0 }),
|
|
95
|
+
createPluginRegistration(ScrollPluginPackage, { defaultBufferSize: 2 }),
|
|
96
|
+
createPluginRegistration(RenderPluginPackage),
|
|
97
|
+
createPluginRegistration(ZoomPluginPackage, {
|
|
98
|
+
defaultZoomLevel: ZoomMode.FitPage,
|
|
99
|
+
minZoom: 0.5,
|
|
100
|
+
maxZoom: 3,
|
|
101
|
+
zoomStep: 0.1
|
|
102
|
+
})
|
|
74
103
|
]
|
|
75
104
|
})
|
|
76
105
|
|
|
106
|
+
function onZoomChange(payload: { level: number; scope: ZoomScope | null }) {
|
|
107
|
+
zoomScope = payload.scope
|
|
108
|
+
emit('zoom-change', payload.level)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function zoomIn() {
|
|
112
|
+
zoomScope?.zoomIn()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function zoomOut() {
|
|
116
|
+
zoomScope?.zoomOut()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function resetZoom() {
|
|
120
|
+
zoomScope?.requestZoom(ZoomMode.FitPage)
|
|
121
|
+
}
|
|
122
|
+
|
|
77
123
|
watch(
|
|
78
124
|
() => props.file,
|
|
79
125
|
() => load(),
|
|
@@ -87,5 +133,12 @@ watch(error, (err) => {
|
|
|
87
133
|
onBeforeUnmount(() => {
|
|
88
134
|
revoke?.()
|
|
89
135
|
pdfUrl.value = ''
|
|
136
|
+
zoomScope = null
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
defineExpose({
|
|
140
|
+
zoomIn,
|
|
141
|
+
zoomOut,
|
|
142
|
+
resetZoom
|
|
90
143
|
})
|
|
91
144
|
</script>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- EmbedPDF Viewport 自带滚动;u-scroll 仅同步自定义滚动条 -->
|
|
3
|
+
<u-scroll :target="viewportEl">
|
|
4
|
+
<slot />
|
|
5
|
+
</u-scroll>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script lang="ts" setup>
|
|
9
|
+
import { computed, inject, onBeforeUnmount, type Ref, watch } from 'vue'
|
|
10
|
+
|
|
11
|
+
import { UScroll } from '../../scroll'
|
|
12
|
+
|
|
13
|
+
defineOptions({ name: 'FileViewerPdfViewportScroll' })
|
|
14
|
+
|
|
15
|
+
/** EmbedPDF Viewport 向子树注入的滚动容器 ref */
|
|
16
|
+
const VIEWPORT_ELEMENT_KEY = 'viewport-element'
|
|
17
|
+
|
|
18
|
+
const viewportRef = inject<Ref<HTMLDivElement | null>>(VIEWPORT_ELEMENT_KEY)
|
|
19
|
+
const viewportEl = computed(() => viewportRef?.value ?? null)
|
|
20
|
+
|
|
21
|
+
let panState:
|
|
22
|
+
| {
|
|
23
|
+
pointerId: number
|
|
24
|
+
startX: number
|
|
25
|
+
startY: number
|
|
26
|
+
scrollLeft: number
|
|
27
|
+
scrollTop: number
|
|
28
|
+
}
|
|
29
|
+
| undefined
|
|
30
|
+
|
|
31
|
+
function isInteractiveTarget(target: EventTarget | null): boolean {
|
|
32
|
+
if (!(target instanceof HTMLElement)) return false
|
|
33
|
+
return !!target.closest(
|
|
34
|
+
'button, a, input, textarea, select, video, [contenteditable="true"], .u-scroll__bar-x, .u-scroll__bar-y'
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handlePointerDown(e: PointerEvent) {
|
|
39
|
+
if (e.button !== 0 || isInteractiveTarget(e.target)) return
|
|
40
|
+
|
|
41
|
+
const el = viewportEl.value
|
|
42
|
+
if (!el) return
|
|
43
|
+
|
|
44
|
+
panState = {
|
|
45
|
+
pointerId: e.pointerId,
|
|
46
|
+
startX: e.clientX,
|
|
47
|
+
startY: e.clientY,
|
|
48
|
+
scrollLeft: el.scrollLeft,
|
|
49
|
+
scrollTop: el.scrollTop
|
|
50
|
+
}
|
|
51
|
+
el.classList.add('is-panning')
|
|
52
|
+
el.setPointerCapture(e.pointerId)
|
|
53
|
+
e.preventDefault()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function handlePointerMove(e: PointerEvent) {
|
|
57
|
+
if (!panState || panState.pointerId !== e.pointerId) return
|
|
58
|
+
|
|
59
|
+
const el = viewportEl.value
|
|
60
|
+
if (!el) return
|
|
61
|
+
|
|
62
|
+
el.scrollLeft = panState.scrollLeft - (e.clientX - panState.startX)
|
|
63
|
+
el.scrollTop = panState.scrollTop - (e.clientY - panState.startY)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function endPan(e: PointerEvent) {
|
|
67
|
+
if (!panState || panState.pointerId !== e.pointerId) return
|
|
68
|
+
|
|
69
|
+
const el = viewportEl.value
|
|
70
|
+
if (el?.hasPointerCapture(e.pointerId)) {
|
|
71
|
+
el.releasePointerCapture(e.pointerId)
|
|
72
|
+
}
|
|
73
|
+
el?.classList.remove('is-panning')
|
|
74
|
+
panState = undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let detachPan: (() => void) | undefined
|
|
78
|
+
|
|
79
|
+
watch(
|
|
80
|
+
viewportEl,
|
|
81
|
+
(el, _, onCleanup) => {
|
|
82
|
+
detachPan?.()
|
|
83
|
+
detachPan = undefined
|
|
84
|
+
if (!el) return
|
|
85
|
+
|
|
86
|
+
el.addEventListener('pointerdown', handlePointerDown, { capture: true })
|
|
87
|
+
el.addEventListener('pointermove', handlePointerMove, { capture: true })
|
|
88
|
+
el.addEventListener('pointerup', endPan, { capture: true })
|
|
89
|
+
el.addEventListener('pointercancel', endPan, { capture: true })
|
|
90
|
+
|
|
91
|
+
detachPan = () => {
|
|
92
|
+
el.removeEventListener('pointerdown', handlePointerDown, { capture: true })
|
|
93
|
+
el.removeEventListener('pointermove', handlePointerMove, { capture: true })
|
|
94
|
+
el.removeEventListener('pointerup', endPan, { capture: true })
|
|
95
|
+
el.removeEventListener('pointercancel', endPan, { capture: true })
|
|
96
|
+
el.classList.remove('is-panning')
|
|
97
|
+
panState = undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onCleanup(() => {
|
|
101
|
+
detachPan?.()
|
|
102
|
+
detachPan = undefined
|
|
103
|
+
})
|
|
104
|
+
},
|
|
105
|
+
{ immediate: true }
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
onBeforeUnmount(() => {
|
|
109
|
+
detachPan?.()
|
|
110
|
+
detachPan = undefined
|
|
111
|
+
})
|
|
112
|
+
</script>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span hidden aria-hidden="true" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script lang="ts" setup>
|
|
6
|
+
import { useZoom, ZoomMode, type ZoomScope } from '@embedpdf/plugin-zoom/vue'
|
|
7
|
+
import { watch } from 'vue'
|
|
8
|
+
|
|
9
|
+
defineOptions({ name: 'FileViewerPdfZoomBridge' })
|
|
10
|
+
|
|
11
|
+
const props = defineProps<{ documentId: string }>()
|
|
12
|
+
|
|
13
|
+
const emit = defineEmits<{
|
|
14
|
+
(e: 'zoom-change', payload: { level: number; scope: ZoomScope | null }): void
|
|
15
|
+
}>()
|
|
16
|
+
|
|
17
|
+
const { provides: zoom, state } = useZoom(() => props.documentId)
|
|
18
|
+
|
|
19
|
+
watch(
|
|
20
|
+
[zoom, () => state.value.currentZoomLevel],
|
|
21
|
+
() => {
|
|
22
|
+
emit('zoom-change', {
|
|
23
|
+
level: state.value.currentZoomLevel,
|
|
24
|
+
scope: zoom.value
|
|
25
|
+
})
|
|
26
|
+
},
|
|
27
|
+
{ immediate: true }
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
defineExpose({
|
|
31
|
+
resetZoom: () => zoom.value?.requestZoom(ZoomMode.FitPage)
|
|
32
|
+
})
|
|
33
|
+
</script>
|
|
@@ -547,7 +547,6 @@
|
|
|
547
547
|
|
|
548
548
|
&__viewport.is-dragging &__previewer {
|
|
549
549
|
transition: none;
|
|
550
|
-
pointer-events: none;
|
|
551
550
|
}
|
|
552
551
|
|
|
553
552
|
@include m.e(empty) {
|
|
@@ -638,9 +637,25 @@
|
|
|
638
637
|
}
|
|
639
638
|
|
|
640
639
|
@include m.e(pdf-viewport) {
|
|
640
|
+
position: relative;
|
|
641
641
|
width: 100%;
|
|
642
642
|
height: 100%;
|
|
643
643
|
background: transparent;
|
|
644
|
+
cursor: grab;
|
|
645
|
+
|
|
646
|
+
&.is-panning {
|
|
647
|
+
cursor: grabbing;
|
|
648
|
+
user-select: none;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// 隐藏原生滚动条,由 PdfViewportScroll 通过 u-scroll target 挂载自定义滚动条
|
|
652
|
+
scrollbar-width: none;
|
|
653
|
+
scrollbar-color: transparent transparent;
|
|
654
|
+
|
|
655
|
+
&::-webkit-scrollbar {
|
|
656
|
+
width: 0;
|
|
657
|
+
height: 0;
|
|
658
|
+
}
|
|
644
659
|
}
|
|
645
660
|
|
|
646
661
|
@include m.e(pdf-page) {
|