@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.
@@ -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
- throw new Error('TIFF 库未加载。请确保 tiff.min.js 已正确引入。');
262
- }
263
-
264
- const response = await fetch(props.url);
265
- if (!response.ok) {
266
- throw new Error(`HTTP error! status: ${response.status}`);
267
- }
268
- const arrayBuffer = await response.arrayBuffer();
269
-
270
- // 创建TIFF实例
271
- const tiff = new window.Tiff({ buffer: arrayBuffer });
272
- // 获取图像数据
273
- const canvas = tiff.toCanvas();
274
- // 转换为DataURL
275
- imgDataUrl.value = canvas.toDataURL('image/png');
276
- error.value = '';
277
-
278
- // 清理 TIFF 实例
279
- tiff.close();
280
-
281
- emit('load', {
282
- width: canvas.width,
283
- height: canvas.height
284
- });
285
- } catch (err) {
286
- console.error('加载TIFF文件失败:', err);
287
- error.value = `加载TIFF文件失败: ${err.message}`;
288
- emit('error', err);
289
- } finally {
290
- isLoading.value = false;
291
- }
292
- };
293
-
294
- // 图片加载成功回调
295
- const onImageLoad = () => {
296
- console.log('TIFF图片渲染成功');
297
- };
298
-
299
- // 图片加载失败回调
300
- const onImageError = () => {
301
- error.value = '图片渲染失败';
302
- };
303
-
304
- // 监听URL变化
305
- watch(
306
- () => props.url,
307
- (newUrl, oldUrl) => {
308
- if (newUrl !== oldUrl) {
309
- loadTiff();
310
- }
311
- }
312
- );
313
-
314
- // 挂载时加载
315
- onMounted(() => {
316
- if (props.url) {
317
- loadTiff();
318
- }
319
- });
320
-
321
- // 组件销毁时清理
322
- onBeforeUnmount(() => {
323
- cleanup();
324
- });
325
-
326
- // 暴露方法给父组件
327
- defineExpose({
328
- cleanup,
329
- reload: loadTiff,
330
- reset
331
- });
332
- </script>
333
-
334
- <style lang="less" scoped>
335
- // 样式已统一到公共样式文件,这里只保留组件特定样式
336
- .img-container {
337
- flex: 1;
338
- width: 100%;
339
- height: 100%;
340
- display: flex;
341
- flex-direction: column;
342
- overflow: hidden;
343
- position: relative;
344
- }
345
-
346
- .img-container .relative {
347
- flex: 1;
348
- position: relative;
349
- width: 100%;
350
- height: 100%;
351
- display: flex;
352
- align-items: center;
353
- justify-content: center;
354
- overflow: hidden;
355
- }
356
-
357
- .img-container img {
358
- max-width: 100%;
359
- max-height: 100%;
360
- object-fit: contain;
361
- transition: transform 0.1s ease;
362
- }
363
- </style>
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>