@koi-br/ocr-web-sdk 1.0.13 → 1.0.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/README.md +2 -0
- package/dist/index-5IMQOcsf.js +215 -0
- package/dist/index-CN8cqjVg.mjs +44886 -0
- package/dist/index.cjs.js +2 -202
- package/dist/index.esm.js +11 -44802
- package/dist/src/index.d.ts +2 -0
- package/dist/tiff.min-C3LBZ-yg.js +6 -0
- package/dist/tiff.min-Dzh69tcl.mjs +195485 -0
- package/package.json +2 -1
- package/preview/ofdPreview.vue +1 -26
- package/preview/tifPreview.vue +447 -363
package/preview/tifPreview.vue
CHANGED
|
@@ -1,363 +1,447 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="preview-container" @wheel.prevent="handleWheel">
|
|
3
|
-
<div v-if="isLoading" class="loading-container">
|
|
4
|
-
<div>正在加载TIFF文件...</div>
|
|
5
|
-
</div>
|
|
6
|
-
<div v-else-if="error" class="preview-error-container">
|
|
7
|
-
<div>{{ error }}</div>
|
|
8
|
-
</div>
|
|
9
|
-
<div v-else class="img-container">
|
|
10
|
-
<!-- 工具栏 -->
|
|
11
|
-
<div class="preview-toolbar preview-toolbar-right">
|
|
12
|
-
<div class="toolbar-group">
|
|
13
|
-
<span class="scale-text">
|
|
14
|
-
{{ Math.round(scale * 100) }}%
|
|
15
|
-
</span>
|
|
16
|
-
<div class="toolbar-divider"></div>
|
|
17
|
-
<ATooltip mini position="bottom" content="重置">
|
|
18
|
-
<AButton
|
|
19
|
-
size="small"
|
|
20
|
-
type="outline"
|
|
21
|
-
@click="reset"
|
|
22
|
-
>
|
|
23
|
-
<RefreshCcw :size="16" />
|
|
24
|
-
</AButton>
|
|
25
|
-
</ATooltip>
|
|
26
|
-
<ATooltip
|
|
27
|
-
v-if="originalId"
|
|
28
|
-
mini
|
|
29
|
-
position="bottom"
|
|
30
|
-
content="查看原图"
|
|
31
|
-
>
|
|
32
|
-
<AButton
|
|
33
|
-
size="small"
|
|
34
|
-
type="outline"
|
|
35
|
-
@click="original"
|
|
36
|
-
>
|
|
37
|
-
<Maximize2 :size="16" />
|
|
38
|
-
</AButton>
|
|
39
|
-
</ATooltip>
|
|
40
|
-
<ATooltip mini position="bottom" content="缩小">
|
|
41
|
-
<AButton
|
|
42
|
-
size="small"
|
|
43
|
-
type="outline"
|
|
44
|
-
:disabled="scale <= minScale"
|
|
45
|
-
@click="zoom(-0.1)"
|
|
46
|
-
>
|
|
47
|
-
<ZoomOut :size="16" />
|
|
48
|
-
</AButton>
|
|
49
|
-
</ATooltip>
|
|
50
|
-
<ATooltip mini position="bottom" content="放大">
|
|
51
|
-
<AButton
|
|
52
|
-
size="small"
|
|
53
|
-
type="outline"
|
|
54
|
-
:disabled="scale >= maxScale"
|
|
55
|
-
@click="zoom(0.1)"
|
|
56
|
-
>
|
|
57
|
-
<ZoomIn :size="16" />
|
|
58
|
-
</AButton>
|
|
59
|
-
</ATooltip>
|
|
60
|
-
<ATooltip
|
|
61
|
-
v-if="isDownload"
|
|
62
|
-
mini
|
|
63
|
-
position="bottom"
|
|
64
|
-
content="下载"
|
|
65
|
-
>
|
|
66
|
-
<AButton
|
|
67
|
-
size="small"
|
|
68
|
-
type="outline"
|
|
69
|
-
@click="emit('download')"
|
|
70
|
-
>
|
|
71
|
-
<Download :size="16" />
|
|
72
|
-
</AButton>
|
|
73
|
-
</ATooltip>
|
|
74
|
-
<div class="toolbar-divider"></div>
|
|
75
|
-
<ATooltip mini position="bottom" content="向左旋转">
|
|
76
|
-
<AButton
|
|
77
|
-
size="small"
|
|
78
|
-
type="outline"
|
|
79
|
-
@click="rotateImage('left')"
|
|
80
|
-
>
|
|
81
|
-
<RotateCw :size="16" />
|
|
82
|
-
</AButton>
|
|
83
|
-
</ATooltip>
|
|
84
|
-
<ATooltip mini position="bottom" content="向右旋转">
|
|
85
|
-
<AButton
|
|
86
|
-
size="small"
|
|
87
|
-
type="outline"
|
|
88
|
-
@click="rotateImage('right')"
|
|
89
|
-
>
|
|
90
|
-
<RotateCcw :size="16" />
|
|
91
|
-
</AButton>
|
|
92
|
-
</ATooltip>
|
|
93
|
-
</div>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
<!-- 图片容器 -->
|
|
97
|
-
<div
|
|
98
|
-
class="relative w-full h-full flex items-center justify-center overflow-hidden"
|
|
99
|
-
@mousedown="startPan"
|
|
100
|
-
@mousemove="pan"
|
|
101
|
-
@mouseup="stopPan"
|
|
102
|
-
@mouseleave="stopPan"
|
|
103
|
-
>
|
|
104
|
-
<img
|
|
105
|
-
ref="imgRef"
|
|
106
|
-
:src="imgDataUrl"
|
|
107
|
-
alt="TIFF Preview"
|
|
108
|
-
class="w-full"
|
|
109
|
-
:style="{
|
|
110
|
-
transform: `translate(${position.x}px, ${position.y}px) rotate(${rotation}deg) scale(${scale})`,
|
|
111
|
-
cursor: isPanning ? 'grabbing' : 'grab'
|
|
112
|
-
}"
|
|
113
|
-
@load="onImageLoad"
|
|
114
|
-
@error="onImageError"
|
|
115
|
-
@contextmenu.prevent
|
|
116
|
-
@mousedown.prevent
|
|
117
|
-
/>
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
</template>
|
|
122
|
-
|
|
123
|
-
<script setup>
|
|
124
|
-
import { ref, onMounted, watch, onBeforeUnmount } from 'vue';
|
|
125
|
-
import { ZoomIn, ZoomOut, RefreshCcw, RotateCw, RotateCcw, Download, Maximize2 } from 'lucide-vue-next';
|
|
126
|
-
import { Button as AButton, Tooltip as ATooltip } from '@arco-design/web-vue';
|
|
127
|
-
|
|
128
|
-
const props = defineProps({
|
|
129
|
-
url: {
|
|
130
|
-
type: String,
|
|
131
|
-
required: true
|
|
132
|
-
},
|
|
133
|
-
minScale: {
|
|
134
|
-
type: Number,
|
|
135
|
-
default: 0.1
|
|
136
|
-
},
|
|
137
|
-
maxScale: {
|
|
138
|
-
type: Number,
|
|
139
|
-
default: 5
|
|
140
|
-
},
|
|
141
|
-
clickStep: {
|
|
142
|
-
type: Number,
|
|
143
|
-
default: 0.25
|
|
144
|
-
},
|
|
145
|
-
wheelStep: {
|
|
146
|
-
type: Number,
|
|
147
|
-
default: 0.1
|
|
148
|
-
},
|
|
149
|
-
originalId: {
|
|
150
|
-
type: String,
|
|
151
|
-
default: ''
|
|
152
|
-
},
|
|
153
|
-
isDownload: {
|
|
154
|
-
type: Boolean,
|
|
155
|
-
default: false
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
const emit = defineEmits(['load', 'error', 'original', 'download']);
|
|
159
|
-
|
|
160
|
-
const isLoading = ref(false);
|
|
161
|
-
const error = ref('');
|
|
162
|
-
const imgDataUrl = ref('');
|
|
163
|
-
const imgRef = ref(null);
|
|
164
|
-
|
|
165
|
-
// 缩放、旋转和拖拽相关状态
|
|
166
|
-
const rotation = ref(0);
|
|
167
|
-
const scale = ref(1);
|
|
168
|
-
const position = ref({ x: 0, y: 0 });
|
|
169
|
-
const isPanning = ref(false);
|
|
170
|
-
const lastPosition = ref({ x: 0, y: 0 });
|
|
171
|
-
|
|
172
|
-
// 旋转功能
|
|
173
|
-
const rotateImage = direction => {
|
|
174
|
-
if (direction === 'left') {
|
|
175
|
-
rotation.value = (rotation.value - 90) % 360;
|
|
176
|
-
} else {
|
|
177
|
-
rotation.value = (rotation.value + 90) % 360;
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// 缩放功能
|
|
182
|
-
const zoom = (delta, isWheel = false) => {
|
|
183
|
-
const step = isWheel ? props.wheelStep : props.clickStep;
|
|
184
|
-
const newScale = scale.value + delta * step;
|
|
185
|
-
if (newScale <= props.minScale) {
|
|
186
|
-
scale.value = props.minScale;
|
|
187
|
-
} else if (newScale >= props.maxScale) {
|
|
188
|
-
scale.value = props.maxScale;
|
|
189
|
-
} else {
|
|
190
|
-
scale.value = newScale;
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// 鼠标滚轮缩放
|
|
195
|
-
const handleWheel = e => {
|
|
196
|
-
e.preventDefault();
|
|
197
|
-
const delta = e.deltaY > 0 ? -1 : 1;
|
|
198
|
-
zoom(delta, true);
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// 拖拽功能
|
|
202
|
-
const startPan = e => {
|
|
203
|
-
if (e.button !== 0) return;
|
|
204
|
-
isPanning.value = true;
|
|
205
|
-
lastPosition.value = { x: e.clientX, y: e.clientY };
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const pan = e => {
|
|
209
|
-
if (!isPanning.value) return;
|
|
210
|
-
|
|
211
|
-
const deltaX = e.clientX - lastPosition.value.x;
|
|
212
|
-
const deltaY = e.clientY - lastPosition.value.y;
|
|
213
|
-
|
|
214
|
-
position.value = {
|
|
215
|
-
x: position.value.x + deltaX,
|
|
216
|
-
y: position.value.y + deltaY
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
lastPosition.value = { x: e.clientX, y: e.clientY };
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const stopPan = () => {
|
|
223
|
-
isPanning.value = false;
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// 重置功能
|
|
227
|
-
const reset = () => {
|
|
228
|
-
rotation.value = 0;
|
|
229
|
-
scale.value = 1;
|
|
230
|
-
position.value = { x: 0, y: 0 };
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
// 查看原图功能
|
|
234
|
-
const original = () => {
|
|
235
|
-
emit('original');
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
// 清理资源
|
|
239
|
-
const cleanup = () => {
|
|
240
|
-
if (imgDataUrl.value) {
|
|
241
|
-
// 释放blob URL
|
|
242
|
-
if (imgDataUrl.value.startsWith('blob:')) {
|
|
243
|
-
URL.revokeObjectURL(imgDataUrl.value);
|
|
244
|
-
}
|
|
245
|
-
imgDataUrl.value = '';
|
|
246
|
-
}
|
|
247
|
-
error.value = '';
|
|
248
|
-
// 重置缩放和位置
|
|
249
|
-
reset();
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
// 加载TIFF文件
|
|
253
|
-
const loadTiff = async () => {
|
|
254
|
-
if (!props.url) return;
|
|
255
|
-
cleanup();
|
|
256
|
-
isLoading.value = true;
|
|
257
|
-
error.value = '';
|
|
258
|
-
try {
|
|
259
|
-
// 检查 TIFF
|
|
260
|
-
if (typeof window.Tiff === 'undefined') {
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
//
|
|
336
|
-
.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<div class="preview-container" @wheel.prevent="handleWheel">
|
|
3
|
+
<div v-if="isLoading" class="loading-container">
|
|
4
|
+
<div>正在加载TIFF文件...</div>
|
|
5
|
+
</div>
|
|
6
|
+
<div v-else-if="error" class="preview-error-container">
|
|
7
|
+
<div>{{ error }}</div>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-else class="img-container">
|
|
10
|
+
<!-- 工具栏 -->
|
|
11
|
+
<div class="preview-toolbar preview-toolbar-right">
|
|
12
|
+
<div class="toolbar-group">
|
|
13
|
+
<span class="scale-text">
|
|
14
|
+
{{ Math.round(scale * 100) }}%
|
|
15
|
+
</span>
|
|
16
|
+
<div class="toolbar-divider"></div>
|
|
17
|
+
<ATooltip mini position="bottom" content="重置">
|
|
18
|
+
<AButton
|
|
19
|
+
size="small"
|
|
20
|
+
type="outline"
|
|
21
|
+
@click="reset"
|
|
22
|
+
>
|
|
23
|
+
<RefreshCcw :size="16" />
|
|
24
|
+
</AButton>
|
|
25
|
+
</ATooltip>
|
|
26
|
+
<ATooltip
|
|
27
|
+
v-if="originalId"
|
|
28
|
+
mini
|
|
29
|
+
position="bottom"
|
|
30
|
+
content="查看原图"
|
|
31
|
+
>
|
|
32
|
+
<AButton
|
|
33
|
+
size="small"
|
|
34
|
+
type="outline"
|
|
35
|
+
@click="original"
|
|
36
|
+
>
|
|
37
|
+
<Maximize2 :size="16" />
|
|
38
|
+
</AButton>
|
|
39
|
+
</ATooltip>
|
|
40
|
+
<ATooltip mini position="bottom" content="缩小">
|
|
41
|
+
<AButton
|
|
42
|
+
size="small"
|
|
43
|
+
type="outline"
|
|
44
|
+
:disabled="scale <= minScale"
|
|
45
|
+
@click="zoom(-0.1)"
|
|
46
|
+
>
|
|
47
|
+
<ZoomOut :size="16" />
|
|
48
|
+
</AButton>
|
|
49
|
+
</ATooltip>
|
|
50
|
+
<ATooltip mini position="bottom" content="放大">
|
|
51
|
+
<AButton
|
|
52
|
+
size="small"
|
|
53
|
+
type="outline"
|
|
54
|
+
:disabled="scale >= maxScale"
|
|
55
|
+
@click="zoom(0.1)"
|
|
56
|
+
>
|
|
57
|
+
<ZoomIn :size="16" />
|
|
58
|
+
</AButton>
|
|
59
|
+
</ATooltip>
|
|
60
|
+
<ATooltip
|
|
61
|
+
v-if="isDownload"
|
|
62
|
+
mini
|
|
63
|
+
position="bottom"
|
|
64
|
+
content="下载"
|
|
65
|
+
>
|
|
66
|
+
<AButton
|
|
67
|
+
size="small"
|
|
68
|
+
type="outline"
|
|
69
|
+
@click="emit('download')"
|
|
70
|
+
>
|
|
71
|
+
<Download :size="16" />
|
|
72
|
+
</AButton>
|
|
73
|
+
</ATooltip>
|
|
74
|
+
<div class="toolbar-divider"></div>
|
|
75
|
+
<ATooltip mini position="bottom" content="向左旋转">
|
|
76
|
+
<AButton
|
|
77
|
+
size="small"
|
|
78
|
+
type="outline"
|
|
79
|
+
@click="rotateImage('left')"
|
|
80
|
+
>
|
|
81
|
+
<RotateCw :size="16" />
|
|
82
|
+
</AButton>
|
|
83
|
+
</ATooltip>
|
|
84
|
+
<ATooltip mini position="bottom" content="向右旋转">
|
|
85
|
+
<AButton
|
|
86
|
+
size="small"
|
|
87
|
+
type="outline"
|
|
88
|
+
@click="rotateImage('right')"
|
|
89
|
+
>
|
|
90
|
+
<RotateCcw :size="16" />
|
|
91
|
+
</AButton>
|
|
92
|
+
</ATooltip>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<!-- 图片容器 -->
|
|
97
|
+
<div
|
|
98
|
+
class="relative w-full h-full flex items-center justify-center overflow-hidden"
|
|
99
|
+
@mousedown="startPan"
|
|
100
|
+
@mousemove="pan"
|
|
101
|
+
@mouseup="stopPan"
|
|
102
|
+
@mouseleave="stopPan"
|
|
103
|
+
>
|
|
104
|
+
<img
|
|
105
|
+
ref="imgRef"
|
|
106
|
+
:src="imgDataUrl"
|
|
107
|
+
alt="TIFF Preview"
|
|
108
|
+
class="w-full"
|
|
109
|
+
:style="{
|
|
110
|
+
transform: `translate(${position.x}px, ${position.y}px) rotate(${rotation}deg) scale(${scale})`,
|
|
111
|
+
cursor: isPanning ? 'grabbing' : 'grab'
|
|
112
|
+
}"
|
|
113
|
+
@load="onImageLoad"
|
|
114
|
+
@error="onImageError"
|
|
115
|
+
@contextmenu.prevent
|
|
116
|
+
@mousedown.prevent
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</template>
|
|
122
|
+
|
|
123
|
+
<script setup>
|
|
124
|
+
import { ref, onMounted, watch, onBeforeUnmount } from 'vue';
|
|
125
|
+
import { ZoomIn, ZoomOut, RefreshCcw, RotateCw, RotateCcw, Download, Maximize2 } from 'lucide-vue-next';
|
|
126
|
+
import { Button as AButton, Tooltip as ATooltip } from '@arco-design/web-vue';
|
|
127
|
+
|
|
128
|
+
const props = defineProps({
|
|
129
|
+
url: {
|
|
130
|
+
type: String,
|
|
131
|
+
required: true
|
|
132
|
+
},
|
|
133
|
+
minScale: {
|
|
134
|
+
type: Number,
|
|
135
|
+
default: 0.1
|
|
136
|
+
},
|
|
137
|
+
maxScale: {
|
|
138
|
+
type: Number,
|
|
139
|
+
default: 5
|
|
140
|
+
},
|
|
141
|
+
clickStep: {
|
|
142
|
+
type: Number,
|
|
143
|
+
default: 0.25
|
|
144
|
+
},
|
|
145
|
+
wheelStep: {
|
|
146
|
+
type: Number,
|
|
147
|
+
default: 0.1
|
|
148
|
+
},
|
|
149
|
+
originalId: {
|
|
150
|
+
type: String,
|
|
151
|
+
default: ''
|
|
152
|
+
},
|
|
153
|
+
isDownload: {
|
|
154
|
+
type: Boolean,
|
|
155
|
+
default: false
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
const emit = defineEmits(['load', 'error', 'original', 'download']);
|
|
159
|
+
|
|
160
|
+
const isLoading = ref(false);
|
|
161
|
+
const error = ref('');
|
|
162
|
+
const imgDataUrl = ref('');
|
|
163
|
+
const imgRef = ref(null);
|
|
164
|
+
|
|
165
|
+
// 缩放、旋转和拖拽相关状态
|
|
166
|
+
const rotation = ref(0);
|
|
167
|
+
const scale = ref(1);
|
|
168
|
+
const position = ref({ x: 0, y: 0 });
|
|
169
|
+
const isPanning = ref(false);
|
|
170
|
+
const lastPosition = ref({ x: 0, y: 0 });
|
|
171
|
+
|
|
172
|
+
// 旋转功能
|
|
173
|
+
const rotateImage = direction => {
|
|
174
|
+
if (direction === 'left') {
|
|
175
|
+
rotation.value = (rotation.value - 90) % 360;
|
|
176
|
+
} else {
|
|
177
|
+
rotation.value = (rotation.value + 90) % 360;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// 缩放功能
|
|
182
|
+
const zoom = (delta, isWheel = false) => {
|
|
183
|
+
const step = isWheel ? props.wheelStep : props.clickStep;
|
|
184
|
+
const newScale = scale.value + delta * step;
|
|
185
|
+
if (newScale <= props.minScale) {
|
|
186
|
+
scale.value = props.minScale;
|
|
187
|
+
} else if (newScale >= props.maxScale) {
|
|
188
|
+
scale.value = props.maxScale;
|
|
189
|
+
} else {
|
|
190
|
+
scale.value = newScale;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// 鼠标滚轮缩放
|
|
195
|
+
const handleWheel = e => {
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
const delta = e.deltaY > 0 ? -1 : 1;
|
|
198
|
+
zoom(delta, true);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// 拖拽功能
|
|
202
|
+
const startPan = e => {
|
|
203
|
+
if (e.button !== 0) return;
|
|
204
|
+
isPanning.value = true;
|
|
205
|
+
lastPosition.value = { x: e.clientX, y: e.clientY };
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const pan = e => {
|
|
209
|
+
if (!isPanning.value) return;
|
|
210
|
+
|
|
211
|
+
const deltaX = e.clientX - lastPosition.value.x;
|
|
212
|
+
const deltaY = e.clientY - lastPosition.value.y;
|
|
213
|
+
|
|
214
|
+
position.value = {
|
|
215
|
+
x: position.value.x + deltaX,
|
|
216
|
+
y: position.value.y + deltaY
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
lastPosition.value = { x: e.clientX, y: e.clientY };
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const stopPan = () => {
|
|
223
|
+
isPanning.value = false;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// 重置功能
|
|
227
|
+
const reset = () => {
|
|
228
|
+
rotation.value = 0;
|
|
229
|
+
scale.value = 1;
|
|
230
|
+
position.value = { x: 0, y: 0 };
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// 查看原图功能
|
|
234
|
+
const original = () => {
|
|
235
|
+
emit('original');
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// 清理资源
|
|
239
|
+
const cleanup = () => {
|
|
240
|
+
if (imgDataUrl.value) {
|
|
241
|
+
// 释放blob URL
|
|
242
|
+
if (imgDataUrl.value.startsWith('blob:')) {
|
|
243
|
+
URL.revokeObjectURL(imgDataUrl.value);
|
|
244
|
+
}
|
|
245
|
+
imgDataUrl.value = '';
|
|
246
|
+
}
|
|
247
|
+
error.value = '';
|
|
248
|
+
// 重置缩放和位置
|
|
249
|
+
reset();
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// 加载TIFF文件
|
|
253
|
+
const loadTiff = async () => {
|
|
254
|
+
if (!props.url) return;
|
|
255
|
+
cleanup();
|
|
256
|
+
isLoading.value = true;
|
|
257
|
+
error.value = '';
|
|
258
|
+
try {
|
|
259
|
+
// 检查 TIFF 库是否已加载,如果没有则自动加载
|
|
260
|
+
if (typeof window.Tiff === 'undefined') {
|
|
261
|
+
// 尝试使用 SDK 提供的加载函数
|
|
262
|
+
const loadTiffLibrary = window.__loadTiffLibrary;
|
|
263
|
+
if (loadTiffLibrary && typeof loadTiffLibrary === 'function') {
|
|
264
|
+
await loadTiffLibrary();
|
|
265
|
+
} else {
|
|
266
|
+
// 如果 SDK 加载函数不可用,直接动态导入
|
|
267
|
+
const tiffModule = await import('tiff.js');
|
|
268
|
+
|
|
269
|
+
let Tiff = null;
|
|
270
|
+
|
|
271
|
+
// 优先检查 window.Tiff(UMD 方式)
|
|
272
|
+
if (typeof window.Tiff !== 'undefined') {
|
|
273
|
+
Tiff = window.Tiff;
|
|
274
|
+
}
|
|
275
|
+
// 检查模块的 default 导出
|
|
276
|
+
else if (tiffModule.default && typeof tiffModule.default === 'function') {
|
|
277
|
+
Tiff = tiffModule.default;
|
|
278
|
+
window.Tiff = Tiff;
|
|
279
|
+
}
|
|
280
|
+
// 检查模块的直接导出
|
|
281
|
+
else if (typeof tiffModule === 'function') {
|
|
282
|
+
Tiff = tiffModule;
|
|
283
|
+
window.Tiff = Tiff;
|
|
284
|
+
}
|
|
285
|
+
// 等待 UMD 挂载
|
|
286
|
+
else {
|
|
287
|
+
let retries = 0;
|
|
288
|
+
while (typeof window.Tiff === 'undefined' && retries < 50) {
|
|
289
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
290
|
+
retries++;
|
|
291
|
+
}
|
|
292
|
+
Tiff = window.Tiff;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!Tiff || typeof Tiff !== 'function') {
|
|
296
|
+
throw new Error(`TIFF 库加载失败:Tiff 不是一个函数。类型: ${typeof Tiff}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 确保 Tiff 已初始化
|
|
300
|
+
if (Tiff.initialize && typeof Tiff.initialize === 'function') {
|
|
301
|
+
// 如果 Module 为 null 或 undefined,需要初始化
|
|
302
|
+
if (Tiff.Module === null || Tiff.Module === undefined) {
|
|
303
|
+
Tiff.initialize({});
|
|
304
|
+
|
|
305
|
+
// 等待 Module 初始化完成(从 null/undefined 变为对象)
|
|
306
|
+
let retries = 0;
|
|
307
|
+
while ((Tiff.Module === null || Tiff.Module === undefined) && retries < 100) {
|
|
308
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
309
|
+
retries++;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (Tiff.Module === null || Tiff.Module === undefined) {
|
|
313
|
+
throw new Error('TIFF 库初始化失败:Tiff.Module 未定义');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const response = await fetch(props.url);
|
|
321
|
+
if (!response.ok) {
|
|
322
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
323
|
+
}
|
|
324
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
325
|
+
|
|
326
|
+
// 创建TIFF实例
|
|
327
|
+
// tiff.js 的 Tiff 是一个构造函数,需要使用 new 关键字
|
|
328
|
+
const Tiff = window.Tiff;
|
|
329
|
+
if (!Tiff || typeof Tiff !== 'function') {
|
|
330
|
+
console.error('window.Tiff:', window.Tiff);
|
|
331
|
+
console.error('typeof window.Tiff:', typeof window.Tiff);
|
|
332
|
+
throw new Error(`TIFF 库未正确加载:Tiff 不是一个函数。实际类型: ${typeof Tiff}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 确保 Tiff 已初始化(构造函数内部也会检查,但提前初始化更安全)
|
|
336
|
+
if (Tiff.initialize && typeof Tiff.initialize === 'function') {
|
|
337
|
+
// 如果 Module 为 null 或 undefined,需要初始化
|
|
338
|
+
if (Tiff.Module === null || Tiff.Module === undefined) {
|
|
339
|
+
Tiff.initialize({});
|
|
340
|
+
|
|
341
|
+
// 等待 Module 初始化完成(从 null/undefined 变为对象)
|
|
342
|
+
let retries = 0;
|
|
343
|
+
while ((Tiff.Module === null || Tiff.Module === undefined) && retries < 100) {
|
|
344
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
345
|
+
retries++;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (Tiff.Module === null || Tiff.Module === undefined) {
|
|
349
|
+
throw new Error('TIFF 库初始化失败:Tiff.Module 未定义');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 使用 new 关键字创建 Tiff 实例
|
|
355
|
+
const tiff = new Tiff({ buffer: arrayBuffer });
|
|
356
|
+
// 获取图像数据
|
|
357
|
+
const canvas = tiff.toCanvas();
|
|
358
|
+
// 转换为DataURL
|
|
359
|
+
imgDataUrl.value = canvas.toDataURL('image/png');
|
|
360
|
+
error.value = '';
|
|
361
|
+
|
|
362
|
+
// 清理 TIFF 实例
|
|
363
|
+
tiff.close();
|
|
364
|
+
|
|
365
|
+
emit('load', {
|
|
366
|
+
width: canvas.width,
|
|
367
|
+
height: canvas.height
|
|
368
|
+
});
|
|
369
|
+
} catch (err) {
|
|
370
|
+
console.error('加载TIFF文件失败:', err);
|
|
371
|
+
error.value = `加载TIFF文件失败: ${err.message}`;
|
|
372
|
+
emit('error', err);
|
|
373
|
+
} finally {
|
|
374
|
+
isLoading.value = false;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// 图片加载成功回调
|
|
379
|
+
const onImageLoad = () => {
|
|
380
|
+
console.log('TIFF图片渲染成功');
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// 图片加载失败回调
|
|
384
|
+
const onImageError = () => {
|
|
385
|
+
error.value = '图片渲染失败';
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// 监听URL变化
|
|
389
|
+
watch(
|
|
390
|
+
() => props.url,
|
|
391
|
+
(newUrl, oldUrl) => {
|
|
392
|
+
if (newUrl !== oldUrl) {
|
|
393
|
+
loadTiff();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// 挂载时加载
|
|
399
|
+
onMounted(() => {
|
|
400
|
+
if (props.url) {
|
|
401
|
+
loadTiff();
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// 组件销毁时清理
|
|
406
|
+
onBeforeUnmount(() => {
|
|
407
|
+
cleanup();
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// 暴露方法给父组件
|
|
411
|
+
defineExpose({
|
|
412
|
+
cleanup,
|
|
413
|
+
reload: loadTiff,
|
|
414
|
+
reset
|
|
415
|
+
});
|
|
416
|
+
</script>
|
|
417
|
+
|
|
418
|
+
<style lang="less" scoped>
|
|
419
|
+
// 样式已统一到公共样式文件,这里只保留组件特定样式
|
|
420
|
+
.img-container {
|
|
421
|
+
flex: 1;
|
|
422
|
+
width: 100%;
|
|
423
|
+
height: 100%;
|
|
424
|
+
display: flex;
|
|
425
|
+
flex-direction: column;
|
|
426
|
+
overflow: hidden;
|
|
427
|
+
position: relative;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.img-container .relative {
|
|
431
|
+
flex: 1;
|
|
432
|
+
position: relative;
|
|
433
|
+
width: 100%;
|
|
434
|
+
height: 100%;
|
|
435
|
+
display: flex;
|
|
436
|
+
align-items: center;
|
|
437
|
+
justify-content: center;
|
|
438
|
+
overflow: hidden;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.img-container img {
|
|
442
|
+
max-width: 100%;
|
|
443
|
+
max-height: 100%;
|
|
444
|
+
object-fit: contain;
|
|
445
|
+
transition: transform 0.1s ease;
|
|
446
|
+
}
|
|
447
|
+
</style>
|