@veltra/desktop 1.7.13 → 1.7.15
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-form.js +9 -3
- package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
- package/dist/components/batch-edit/batch-edit-list.js +2 -1
- package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.js +57 -30
- package/dist/components/batch-edit/batch-edit.js.map +1 -1
- package/dist/components/batch-edit/style.d.ts +1 -0
- package/dist/components/batch-edit/style.js +1 -0
- package/dist/components/batch-edit/style2.css +14 -0
- package/dist/components/batch-edit/use-dialog-effect.js +20 -0
- package/dist/components/batch-edit/use-dialog-effect.js.map +1 -0
- package/dist/components/batch-edit/use-edit-state.js +22 -3
- package/dist/components/batch-edit/use-edit-state.js.map +1 -1
- package/dist/components/batch-edit/use-handlers.js +5 -0
- package/dist/components/batch-edit/use-handlers.js.map +1 -1
- package/dist/components/batch-edit/use-shortcut-key.js +5 -2
- package/dist/components/batch-edit/use-shortcut-key.js.map +1 -1
- package/dist/components/file-viewer/file-viewer.js +21 -14
- package/dist/components/file-viewer/file-viewer.js.map +1 -1
- package/dist/components/file-viewer/helper.js +2 -0
- package/dist/components/file-viewer/helper.js.map +1 -1
- package/dist/components/file-viewer/previewers/ofd-previewer.js +196 -0
- package/dist/components/file-viewer/previewers/ofd-previewer.js.map +1 -0
- package/dist/components/file-viewer/style2.css +27 -0
- package/dist/components/table-editor/style2.css +23 -3
- package/dist/components/table-editor/table-editor.js +213 -63
- package/dist/components/table-editor/table-editor.js.map +1 -1
- package/dist/components/tip/style2.css +63 -0
- package/dist/components/tip/tip.js +6 -1
- package/dist/components/tip/tip.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/packages/ofd-core/dist/color.js +69 -0
- package/dist/packages/ofd-core/dist/color.js.map +1 -0
- package/dist/packages/ofd-core/dist/ctm.js +55 -0
- package/dist/packages/ofd-core/dist/ctm.js.map +1 -0
- package/dist/packages/ofd-core/dist/error.js +14 -0
- package/dist/packages/ofd-core/dist/error.js.map +1 -0
- package/dist/packages/ofd-core/dist/font.js +384 -0
- package/dist/packages/ofd-core/dist/font.js.map +1 -0
- package/dist/packages/ofd-core/dist/index.js +6 -0
- package/dist/packages/ofd-core/dist/number.js +13 -0
- package/dist/packages/ofd-core/dist/number.js.map +1 -0
- package/dist/packages/ofd-core/dist/page-objects.js +181 -0
- package/dist/packages/ofd-core/dist/page-objects.js.map +1 -0
- package/dist/packages/ofd-core/dist/parse.js +65 -0
- package/dist/packages/ofd-core/dist/parse.js.map +1 -0
- package/dist/packages/ofd-core/dist/render.js +361 -0
- package/dist/packages/ofd-core/dist/render.js.map +1 -0
- package/dist/packages/ofd-core/dist/xml.js +199 -0
- package/dist/packages/ofd-core/dist/xml.js.map +1 -0
- package/dist/packages/ofd-core/dist/zip.js +93 -0
- package/dist/packages/ofd-core/dist/zip.js.map +1 -0
- package/dist/style.d.ts +1 -1
- package/dist/style.js +1 -1
- package/dist/types/batch-edit.d.ts +6 -0
- package/dist/types/file-viewer.d.ts +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/table-editor.d.ts +28 -7
- package/dist/types/tip.d.ts +5 -1
- package/package.json +8 -7
- package/src/components/batch-edit/__test__/batch-edit.test.ts +191 -4
- package/src/components/batch-edit/batch-edit-form.vue +17 -3
- package/src/components/batch-edit/batch-edit-list.vue +2 -1
- package/src/components/batch-edit/batch-edit.vue +22 -4
- package/src/components/batch-edit/style.scss +20 -0
- package/src/components/batch-edit/style.ts +1 -0
- package/src/components/batch-edit/use-dialog-effect.ts +9 -3
- package/src/components/batch-edit/use-edit-state.ts +32 -4
- package/src/components/batch-edit/use-handlers.ts +10 -0
- package/src/components/batch-edit/use-shortcut-key.ts +6 -2
- package/src/components/file-viewer/__test__/helper.test.ts +13 -0
- package/src/components/file-viewer/file-viewer.vue +23 -16
- package/src/components/file-viewer/helper.ts +2 -0
- package/src/components/file-viewer/previewers/ofd-previewer.vue +216 -0
- package/src/components/file-viewer/style.scss +33 -0
- package/src/components/table-editor/__test__/table-editor.test.ts +471 -0
- package/src/components/table-editor/style.scss +42 -4
- package/src/components/table-editor/table-editor.vue +265 -38
- package/src/components/tip/style.scss +15 -0
- package/src/components/tip/tip.vue +1 -1
- package/src/types/batch-edit.ts +6 -0
- package/src/types/file-viewer.ts +1 -1
- package/src/types/table-editor.ts +29 -7
- package/src/types/tip.ts +6 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="cls.e('ofd')">
|
|
3
|
+
<div ref="scroll" :class="cls.e('ofd-scroll')">
|
|
4
|
+
<div
|
|
5
|
+
v-for="(page, i) in pages"
|
|
6
|
+
:key="i"
|
|
7
|
+
:data-index="i"
|
|
8
|
+
:class="cls.e('ofd-page')"
|
|
9
|
+
:style="{
|
|
10
|
+
width: page.widthPx * zoomLevel + 'px',
|
|
11
|
+
height: page.heightPx * zoomLevel + 'px'
|
|
12
|
+
}"
|
|
13
|
+
>
|
|
14
|
+
<div v-if="page.svg" :class="cls.e('ofd-page-body')" v-html="page.svg" />
|
|
15
|
+
</div>
|
|
16
|
+
<div v-if="!pages.length && !loading" :class="cls.e('empty')">
|
|
17
|
+
<u-empty text="该文件没有可预览的页面" :size="32" />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<div v-if="loading" :class="cls.e('loading')">正在解析 OFD…</div>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script lang="ts" setup>
|
|
25
|
+
import { bem } from '@veltra/utils'
|
|
26
|
+
import { nextTick, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue'
|
|
27
|
+
|
|
28
|
+
import type { FileViewerItem } from '../../../types/file-viewer'
|
|
29
|
+
import { UEmpty } from '../../empty'
|
|
30
|
+
import { toArrayBuffer } from '../helper'
|
|
31
|
+
|
|
32
|
+
defineOptions({ name: 'UFileViewerOfdPreviewer' })
|
|
33
|
+
|
|
34
|
+
/** 仅类型查询;运行时动态 import 按需加载,构建时随 alwaysBundle 内联进 dist */
|
|
35
|
+
type OfdCoreModule = typeof import('@veltra/ofd-core')
|
|
36
|
+
type OfdContainer = import('@veltra/ofd-core').OfdContainer
|
|
37
|
+
type OfdZip = import('@veltra/ofd-core').OfdZip
|
|
38
|
+
|
|
39
|
+
/** OFD 毫米 → CSS 像素(96dpi),与内核 SVG 输出的换算一致 */
|
|
40
|
+
const PX_PER_MM = 96 / 25.4
|
|
41
|
+
|
|
42
|
+
const MIN_ZOOM = 0.5
|
|
43
|
+
const MAX_ZOOM = 3
|
|
44
|
+
/** 每次缩放固定增减 10%,对齐 pdf 缩放配置 */
|
|
45
|
+
const ZOOM_STEP = 0.1
|
|
46
|
+
/** 可视区上下各预渲染一个视口高度的页 */
|
|
47
|
+
const RENDER_MARGIN = '100% 0px'
|
|
48
|
+
|
|
49
|
+
interface OfdPageView {
|
|
50
|
+
docIndex: number
|
|
51
|
+
pageIndex: number
|
|
52
|
+
/** 页面基准尺寸(缩放 1 时),毫米按 96dpi 换算为像素 */
|
|
53
|
+
widthPx: number
|
|
54
|
+
heightPx: number
|
|
55
|
+
/** null = 未渲染;'' = 已尝试但渲染失败,不再重试 */
|
|
56
|
+
svg: string | null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const props = defineProps<{ file: FileViewerItem }>()
|
|
60
|
+
|
|
61
|
+
const emit = defineEmits<{
|
|
62
|
+
(e: 'error', err: unknown): void
|
|
63
|
+
(e: 'zoom-change', level: number): void
|
|
64
|
+
}>()
|
|
65
|
+
|
|
66
|
+
const cls = bem('file-viewer')
|
|
67
|
+
const scrollEl = useTemplateRef<HTMLDivElement>('scroll')
|
|
68
|
+
const loading = ref(true)
|
|
69
|
+
const pages = ref<OfdPageView[]>([])
|
|
70
|
+
const zoomLevel = ref(1)
|
|
71
|
+
|
|
72
|
+
let controller: AbortController | undefined
|
|
73
|
+
/** 递增 token:防止切文件后旧解析/渲染结果落地 */
|
|
74
|
+
let loadToken = 0
|
|
75
|
+
let ofdCore: OfdCoreModule | undefined
|
|
76
|
+
let observer: IntersectionObserver | undefined
|
|
77
|
+
let activeZip: OfdZip | undefined
|
|
78
|
+
let activeContainer: OfdContainer | undefined
|
|
79
|
+
/** 已进入渲染流程(进行中或已完成)的页,防止滚动重复触发 */
|
|
80
|
+
const scheduledPages = new Set<number>()
|
|
81
|
+
|
|
82
|
+
// ---- 缩放:作用于页面容器尺寸,滚动高度随之自适应 ----
|
|
83
|
+
|
|
84
|
+
function clampZoom(value: number): number {
|
|
85
|
+
return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Math.round(value * 100) / 100))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function setZoom(value: number) {
|
|
89
|
+
const next = clampZoom(value)
|
|
90
|
+
if (zoomLevel.value === next) return
|
|
91
|
+
zoomLevel.value = next
|
|
92
|
+
emit('zoom-change', next)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function zoomIn() {
|
|
96
|
+
setZoom(zoomLevel.value + ZOOM_STEP)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function zoomOut() {
|
|
100
|
+
setZoom(zoomLevel.value - ZOOM_STEP)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resetZoom() {
|
|
104
|
+
setZoom(1)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
defineExpose({ zoomIn, zoomOut, resetZoom })
|
|
108
|
+
|
|
109
|
+
// ---- 解析与懒渲染 ----
|
|
110
|
+
|
|
111
|
+
/** 动态加载 ofd-core chunk;首次加载后缓存模块复用 */
|
|
112
|
+
async function resolveOfdCore(): Promise<OfdCoreModule> {
|
|
113
|
+
const mod = ofdCore ?? (await import('@veltra/ofd-core'))
|
|
114
|
+
ofdCore = mod
|
|
115
|
+
return mod
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function teardownRender() {
|
|
119
|
+
observer?.disconnect()
|
|
120
|
+
observer = undefined
|
|
121
|
+
activeZip = undefined
|
|
122
|
+
activeContainer = undefined
|
|
123
|
+
scheduledPages.clear()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function observePages() {
|
|
127
|
+
const root = scrollEl.value
|
|
128
|
+
if (!root || typeof IntersectionObserver === 'undefined') return
|
|
129
|
+
observer = new IntersectionObserver(
|
|
130
|
+
(entries) => {
|
|
131
|
+
const token = loadToken
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
if (!entry.isIntersecting) continue
|
|
134
|
+
observer?.unobserve(entry.target)
|
|
135
|
+
const index = Number((entry.target as HTMLElement).dataset.index)
|
|
136
|
+
if (Number.isNaN(index) || scheduledPages.has(index)) continue
|
|
137
|
+
scheduledPages.add(index)
|
|
138
|
+
void renderPage(index, token)
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{ root, rootMargin: RENDER_MARGIN }
|
|
142
|
+
)
|
|
143
|
+
for (const el of root.querySelectorAll<HTMLElement>('[data-index]')) {
|
|
144
|
+
observer.observe(el)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 渲染单页 SVG;失败置空串避免滚动时反复重试同一页 */
|
|
149
|
+
async function renderPage(index: number, token: number) {
|
|
150
|
+
const page = pages.value[index]
|
|
151
|
+
const core = ofdCore
|
|
152
|
+
if (!page || page.svg !== null || !core || !activeZip || !activeContainer) return
|
|
153
|
+
try {
|
|
154
|
+
const svg = await core.pageToSvg(activeZip, activeContainer, page.docIndex, page.pageIndex)
|
|
155
|
+
if (token !== loadToken) return
|
|
156
|
+
pages.value[index] = { ...page, svg }
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (token !== loadToken) return
|
|
159
|
+
pages.value[index] = { ...page, svg: '' }
|
|
160
|
+
emit('error', err)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function load() {
|
|
165
|
+
const token = ++loadToken
|
|
166
|
+
controller?.abort()
|
|
167
|
+
controller = new AbortController()
|
|
168
|
+
teardownRender()
|
|
169
|
+
loading.value = true
|
|
170
|
+
pages.value = []
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const core = await resolveOfdCore()
|
|
174
|
+
if (token !== loadToken) return
|
|
175
|
+
|
|
176
|
+
const buf = await toArrayBuffer(props.file.src, controller.signal)
|
|
177
|
+
if (token !== loadToken) return
|
|
178
|
+
const zip = core.openOfdZip(new Uint8Array(buf))
|
|
179
|
+
const container = await core.parseOfdContainer(zip)
|
|
180
|
+
if (token !== loadToken) return
|
|
181
|
+
|
|
182
|
+
activeZip = zip
|
|
183
|
+
activeContainer = container
|
|
184
|
+
// 未声明尺寸时按 A4 兜底,与内核 pageToSvg 的兜底一致
|
|
185
|
+
pages.value = container.docs.flatMap((doc, docIndex) =>
|
|
186
|
+
doc.pages.map((page) => {
|
|
187
|
+
const size = page.size ?? doc.pageSize ?? { width: 210, height: 297 }
|
|
188
|
+
return {
|
|
189
|
+
docIndex,
|
|
190
|
+
pageIndex: page.index,
|
|
191
|
+
widthPx: Math.round(size.width * PX_PER_MM),
|
|
192
|
+
heightPx: Math.round(size.height * PX_PER_MM),
|
|
193
|
+
svg: null
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
)
|
|
197
|
+
await nextTick()
|
|
198
|
+
if (token !== loadToken) return
|
|
199
|
+
observePages()
|
|
200
|
+
} catch (err) {
|
|
201
|
+
if (token !== loadToken) return
|
|
202
|
+
if ((err as { name?: string })?.name === 'AbortError') return
|
|
203
|
+
emit('error', err)
|
|
204
|
+
} finally {
|
|
205
|
+
if (token === loadToken) loading.value = false
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
watch(() => props.file, load, { immediate: true })
|
|
210
|
+
|
|
211
|
+
onBeforeUnmount(() => {
|
|
212
|
+
controller?.abort()
|
|
213
|
+
teardownRender()
|
|
214
|
+
pages.value = []
|
|
215
|
+
})
|
|
216
|
+
</script>
|
|
@@ -302,6 +302,12 @@
|
|
|
302
302
|
--u-file-viewer-kind-bg: rgba(100, 116, 139, 0.1);
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
@include m.em(badge, ofd) {
|
|
306
|
+
--u-file-viewer-kind: #7c3aed;
|
|
307
|
+
--u-file-viewer-kind-border: rgba(124, 58, 237, 0.28);
|
|
308
|
+
--u-file-viewer-kind-bg: rgba(124, 58, 237, 0.1);
|
|
309
|
+
}
|
|
310
|
+
|
|
305
311
|
@include m.e(stage) {
|
|
306
312
|
display: flex;
|
|
307
313
|
flex: 1;
|
|
@@ -686,6 +692,33 @@
|
|
|
686
692
|
0 0 0 1px rgba(255, 255, 255, 0.04);
|
|
687
693
|
}
|
|
688
694
|
|
|
695
|
+
@include m.e(ofd) {
|
|
696
|
+
position: absolute;
|
|
697
|
+
inset: 0;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
@include m.e(ofd-scroll) {
|
|
701
|
+
width: 100%;
|
|
702
|
+
height: 100%;
|
|
703
|
+
overflow: auto;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
@include m.e(ofd-page) {
|
|
707
|
+
margin: 24px auto;
|
|
708
|
+
background: #fff;
|
|
709
|
+
box-shadow: var(--u-file-viewer-page-shadow);
|
|
710
|
+
|
|
711
|
+
svg {
|
|
712
|
+
display: block;
|
|
713
|
+
width: 100%;
|
|
714
|
+
height: 100%;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
&.is-modal &__ofd-page {
|
|
719
|
+
margin: clamp(24px, 3vw, 44px) auto;
|
|
720
|
+
}
|
|
721
|
+
|
|
689
722
|
@include m.e(docx) {
|
|
690
723
|
position: absolute;
|
|
691
724
|
inset: 0;
|