@koi-br/ocr-web-sdk 1.0.0

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.
@@ -0,0 +1,362 @@
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
+ <a-tooltip mini position="bottom" content="重置">
18
+ <a-button
19
+ size="small"
20
+ type="outline"
21
+ @click="reset"
22
+ >
23
+ <RefreshCcw :size="16" />
24
+ </a-button>
25
+ </a-tooltip>
26
+ <a-tooltip
27
+ v-if="originalId"
28
+ mini
29
+ position="bottom"
30
+ content="查看原图"
31
+ >
32
+ <a-button
33
+ size="small"
34
+ type="outline"
35
+ @click="original"
36
+ >
37
+ <Maximize2 :size="16" />
38
+ </a-button>
39
+ </a-tooltip>
40
+ <a-tooltip mini position="bottom" content="缩小">
41
+ <a-button
42
+ size="small"
43
+ type="outline"
44
+ :disabled="scale <= minScale"
45
+ @click="zoom(-0.1)"
46
+ >
47
+ <ZoomOut :size="16" />
48
+ </a-button>
49
+ </a-tooltip>
50
+ <a-tooltip mini position="bottom" content="放大">
51
+ <a-button
52
+ size="small"
53
+ type="outline"
54
+ :disabled="scale >= maxScale"
55
+ @click="zoom(0.1)"
56
+ >
57
+ <ZoomIn :size="16" />
58
+ </a-button>
59
+ </a-tooltip>
60
+ <a-tooltip
61
+ v-if="isDownload"
62
+ mini
63
+ position="bottom"
64
+ content="下载"
65
+ >
66
+ <a-button
67
+ size="small"
68
+ type="outline"
69
+ @click="emit('download')"
70
+ >
71
+ <Download :size="16" />
72
+ </a-button>
73
+ </a-tooltip>
74
+ <div class="toolbar-divider"></div>
75
+ <a-tooltip mini position="bottom" content="向左旋转">
76
+ <a-button
77
+ size="small"
78
+ type="outline"
79
+ @click="rotateImage('left')"
80
+ >
81
+ <RotateCw :size="16" />
82
+ </a-button>
83
+ </a-tooltip>
84
+ <a-tooltip mini position="bottom" content="向右旋转">
85
+ <a-button
86
+ size="small"
87
+ type="outline"
88
+ @click="rotateImage('right')"
89
+ >
90
+ <RotateCcw :size="16" />
91
+ </a-button>
92
+ </a-tooltip>
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, Download, Maximize2 } from 'lucide-vue-next';
126
+
127
+ const props = defineProps({
128
+ url: {
129
+ type: String,
130
+ required: true
131
+ },
132
+ minScale: {
133
+ type: Number,
134
+ default: 0.1
135
+ },
136
+ maxScale: {
137
+ type: Number,
138
+ default: 5
139
+ },
140
+ clickStep: {
141
+ type: Number,
142
+ default: 0.25
143
+ },
144
+ wheelStep: {
145
+ type: Number,
146
+ default: 0.1
147
+ },
148
+ originalId: {
149
+ type: String,
150
+ default: ''
151
+ },
152
+ isDownload: {
153
+ type: Boolean,
154
+ default: false
155
+ }
156
+ });
157
+ const emit = defineEmits(['load', 'error', 'original', 'download']);
158
+
159
+ const isLoading = ref(false);
160
+ const error = ref('');
161
+ const imgDataUrl = ref('');
162
+ const imgRef = ref(null);
163
+
164
+ // 缩放、旋转和拖拽相关状态
165
+ const rotation = ref(0);
166
+ const scale = ref(1);
167
+ const position = ref({ x: 0, y: 0 });
168
+ const isPanning = ref(false);
169
+ const lastPosition = ref({ x: 0, y: 0 });
170
+
171
+ // 旋转功能
172
+ const rotateImage = direction => {
173
+ if (direction === 'left') {
174
+ rotation.value = (rotation.value - 90) % 360;
175
+ } else {
176
+ rotation.value = (rotation.value + 90) % 360;
177
+ }
178
+ };
179
+
180
+ // 缩放功能
181
+ const zoom = (delta, isWheel = false) => {
182
+ const step = isWheel ? props.wheelStep : props.clickStep;
183
+ const newScale = scale.value + delta * step;
184
+ if (newScale <= props.minScale) {
185
+ scale.value = props.minScale;
186
+ } else if (newScale >= props.maxScale) {
187
+ scale.value = props.maxScale;
188
+ } else {
189
+ scale.value = newScale;
190
+ }
191
+ };
192
+
193
+ // 鼠标滚轮缩放
194
+ const handleWheel = e => {
195
+ e.preventDefault();
196
+ const delta = e.deltaY > 0 ? -1 : 1;
197
+ zoom(delta, true);
198
+ };
199
+
200
+ // 拖拽功能
201
+ const startPan = e => {
202
+ if (e.button !== 0) return;
203
+ isPanning.value = true;
204
+ lastPosition.value = { x: e.clientX, y: e.clientY };
205
+ };
206
+
207
+ const pan = e => {
208
+ if (!isPanning.value) return;
209
+
210
+ const deltaX = e.clientX - lastPosition.value.x;
211
+ const deltaY = e.clientY - lastPosition.value.y;
212
+
213
+ position.value = {
214
+ x: position.value.x + deltaX,
215
+ y: position.value.y + deltaY
216
+ };
217
+
218
+ lastPosition.value = { x: e.clientX, y: e.clientY };
219
+ };
220
+
221
+ const stopPan = () => {
222
+ isPanning.value = false;
223
+ };
224
+
225
+ // 重置功能
226
+ const reset = () => {
227
+ rotation.value = 0;
228
+ scale.value = 1;
229
+ position.value = { x: 0, y: 0 };
230
+ };
231
+
232
+ // 查看原图功能
233
+ const original = () => {
234
+ emit('original');
235
+ };
236
+
237
+ // 清理资源
238
+ const cleanup = () => {
239
+ if (imgDataUrl.value) {
240
+ // 释放blob URL
241
+ if (imgDataUrl.value.startsWith('blob:')) {
242
+ URL.revokeObjectURL(imgDataUrl.value);
243
+ }
244
+ imgDataUrl.value = '';
245
+ }
246
+ error.value = '';
247
+ // 重置缩放和位置
248
+ reset();
249
+ };
250
+
251
+ // 加载TIFF文件
252
+ const loadTiff = async () => {
253
+ if (!props.url) return;
254
+ cleanup();
255
+ isLoading.value = true;
256
+ error.value = '';
257
+ try {
258
+ // 检查 TIFF 库是否已加载
259
+ if (typeof window.Tiff === 'undefined') {
260
+ throw new Error('TIFF 库未加载。请确保 tiff.min.js 已正确引入。');
261
+ }
262
+
263
+ const response = await fetch(props.url);
264
+ if (!response.ok) {
265
+ throw new Error(`HTTP error! status: ${response.status}`);
266
+ }
267
+ const arrayBuffer = await response.arrayBuffer();
268
+
269
+ // 创建TIFF实例
270
+ const tiff = new window.Tiff({ buffer: arrayBuffer });
271
+ // 获取图像数据
272
+ const canvas = tiff.toCanvas();
273
+ // 转换为DataURL
274
+ imgDataUrl.value = canvas.toDataURL('image/png');
275
+ error.value = '';
276
+
277
+ // 清理 TIFF 实例
278
+ tiff.close();
279
+
280
+ emit('load', {
281
+ width: canvas.width,
282
+ height: canvas.height
283
+ });
284
+ } catch (err) {
285
+ console.error('加载TIFF文件失败:', err);
286
+ error.value = `加载TIFF文件失败: ${err.message}`;
287
+ emit('error', err);
288
+ } finally {
289
+ isLoading.value = false;
290
+ }
291
+ };
292
+
293
+ // 图片加载成功回调
294
+ const onImageLoad = () => {
295
+ console.log('TIFF图片渲染成功');
296
+ };
297
+
298
+ // 图片加载失败回调
299
+ const onImageError = () => {
300
+ error.value = '图片渲染失败';
301
+ };
302
+
303
+ // 监听URL变化
304
+ watch(
305
+ () => props.url,
306
+ (newUrl, oldUrl) => {
307
+ if (newUrl !== oldUrl) {
308
+ loadTiff();
309
+ }
310
+ }
311
+ );
312
+
313
+ // 挂载时加载
314
+ onMounted(() => {
315
+ if (props.url) {
316
+ loadTiff();
317
+ }
318
+ });
319
+
320
+ // 组件销毁时清理
321
+ onBeforeUnmount(() => {
322
+ cleanup();
323
+ });
324
+
325
+ // 暴露方法给父组件
326
+ defineExpose({
327
+ cleanup,
328
+ reload: loadTiff,
329
+ reset
330
+ });
331
+ </script>
332
+
333
+ <style lang="less" scoped>
334
+ // 样式已统一到公共样式文件,这里只保留组件特定样式
335
+ .img-container {
336
+ flex: 1;
337
+ width: 100%;
338
+ height: 100%;
339
+ display: flex;
340
+ flex-direction: column;
341
+ overflow: hidden;
342
+ position: relative;
343
+ }
344
+
345
+ .img-container .relative {
346
+ flex: 1;
347
+ position: relative;
348
+ width: 100%;
349
+ height: 100%;
350
+ display: flex;
351
+ align-items: center;
352
+ justify-content: center;
353
+ overflow: hidden;
354
+ }
355
+
356
+ .img-container img {
357
+ max-width: 100%;
358
+ max-height: 100%;
359
+ object-fit: contain;
360
+ transition: transform 0.1s ease;
361
+ }
362
+ </style>
@@ -0,0 +1,168 @@
1
+ <template>
2
+ <div class="preview-container">
3
+ <!-- 工具栏 -->
4
+ <div class="preview-toolbar preview-toolbar-right">
5
+ <div class="toolbar-group">
6
+ <span class="scale-text">
7
+ {{ Math.round(scale * 100) }}%
8
+ </span>
9
+ <div class="toolbar-divider"></div>
10
+ <a-tooltip mini position="bottom" content="重置">
11
+ <a-button
12
+ size="small"
13
+ type="outline"
14
+ @click="reset"
15
+ >
16
+ <RefreshCcw :size="16" />
17
+ </a-button>
18
+ </a-tooltip>
19
+ <a-tooltip mini position="bottom" content="缩小">
20
+ <a-button
21
+ size="small"
22
+ type="outline"
23
+ :disabled="scale <= 0.5"
24
+ @click="zoom(-0.1)"
25
+ >
26
+ <ZoomOut :size="16" />
27
+ </a-button>
28
+ </a-tooltip>
29
+ <a-tooltip mini position="bottom" content="放大">
30
+ <a-button
31
+ size="small"
32
+ type="outline"
33
+ :disabled="scale >= 2"
34
+ @click="zoom(0.1)"
35
+ >
36
+ <ZoomIn :size="16" />
37
+ </a-button>
38
+ </a-tooltip>
39
+ <a-tooltip
40
+ v-if="isDownload"
41
+ mini
42
+ position="bottom"
43
+ content="下载"
44
+ >
45
+ <a-button
46
+ size="small"
47
+ type="outline"
48
+ @click="emit('download')"
49
+ >
50
+ <Download :size="16" />
51
+ </a-button>
52
+ </a-tooltip>
53
+ </div>
54
+ </div>
55
+ <div
56
+ class="excel-wrapper"
57
+ :style="{ transform: `scale(${scale})`, transformOrigin: 'top left' }"
58
+ >
59
+ <VueOfficeExcel
60
+ v-if="excel"
61
+ :src="excel"
62
+ style="width: 100%; height: 75vh"
63
+ @rendered="onRendered"
64
+ @error="onError"
65
+ />
66
+ </div>
67
+ </div>
68
+ </template>
69
+
70
+ <script setup>
71
+ import { ref, watch } from 'vue';
72
+ import { Message } from '@arco-design/web-vue';
73
+ import { ZoomIn, ZoomOut, RefreshCcw, Download } from 'lucide-vue-next';
74
+ import VueOfficeExcel from '@vue-office/excel/lib/v3/index';
75
+ import '@vue-office/excel/lib/v3/index.css';
76
+
77
+ const props = defineProps({
78
+ url: {
79
+ type: String,
80
+ required: true
81
+ },
82
+ isDownload: {
83
+ type: Boolean,
84
+ default: false
85
+ }
86
+ });
87
+
88
+ const emit = defineEmits(['download']);
89
+
90
+ const excel = ref('');
91
+ const loading = ref(false);
92
+ const scale = ref(1);
93
+
94
+ // 缩放方法
95
+ const zoom = delta => {
96
+ const newScale = scale.value + delta;
97
+ if (newScale >= 0.5 && newScale <= 2) {
98
+ scale.value = newScale;
99
+ }
100
+ };
101
+
102
+ // 重置方法
103
+ const reset = () => {
104
+ scale.value = 1;
105
+ };
106
+
107
+ // 放大
108
+ const zoomIn = () => {
109
+ zoom(0.1);
110
+ };
111
+
112
+ // 缩小
113
+ const zoomOut = () => {
114
+ zoom(-0.1);
115
+ };
116
+
117
+ // 重置缩放
118
+ const resetZoom = () => {
119
+ reset();
120
+ };
121
+
122
+ // 文件加载成功
123
+ const onRendered = () => {
124
+ loading.value = false;
125
+ };
126
+
127
+ // 文件加载失败
128
+ const onError = error => {
129
+ loading.value = false;
130
+ Message.error('Excel文件加载失败');
131
+ console.error('Excel文件加载失败:', error);
132
+ };
133
+
134
+ // 监听URL变化
135
+ watch(
136
+ () => props.url,
137
+ newUrl => {
138
+ if (newUrl) {
139
+ loading.value = true;
140
+ excel.value = newUrl;
141
+ }
142
+ },
143
+ { immediate: true }
144
+ );
145
+
146
+ // 清理函数
147
+ const cleanup = () => {
148
+ loading.value = false;
149
+ excel.value = '';
150
+ scale.value = 1;
151
+ };
152
+
153
+ // 暴露清理方法给父组件
154
+ defineExpose({
155
+ cleanup
156
+ });
157
+ </script>
158
+
159
+ <style lang="less" scoped>
160
+ // 样式已统一到公共样式文件,这里只保留组件特定样式
161
+ .excel-wrapper {
162
+ flex: 1;
163
+ display: inline-block;
164
+ min-width: 100%;
165
+ overflow: auto;
166
+ transition: transform 0.2s ease;
167
+ }
168
+ </style>