@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,235 @@
1
+ <template>
2
+ <div
3
+ class="preview-container"
4
+ @wheel.prevent="handleWheel"
5
+ >
6
+ <!-- 工具栏 -->
7
+ <div class="preview-toolbar preview-toolbar-right">
8
+ <div class="toolbar-group">
9
+ <span class="scale-text">
10
+ {{ Math.round(scale * 100) }}%
11
+ </span>
12
+ <div class="toolbar-divider"></div>
13
+ <a-tooltip mini position="bottom" content="重置">
14
+ <a-button
15
+ size="small"
16
+ type="outline"
17
+ @click="reset"
18
+ >
19
+ <RefreshCcw :size="16" />
20
+ </a-button>
21
+ </a-tooltip>
22
+ <a-tooltip
23
+ v-if="originalId"
24
+ mini
25
+ position="bottom"
26
+ content="查看原图"
27
+ >
28
+ <a-button
29
+ size="small"
30
+ type="outline"
31
+ @click="original"
32
+ >
33
+ <Maximize2 :size="16" />
34
+ </a-button>
35
+ </a-tooltip>
36
+ <a-tooltip mini position="bottom" content="缩小">
37
+ <a-button
38
+ size="small"
39
+ type="outline"
40
+ :disabled="scale <= minScale"
41
+ @click="zoom(-0.1)"
42
+ >
43
+ <ZoomOut :size="16" />
44
+ </a-button>
45
+ </a-tooltip>
46
+ <a-tooltip mini position="bottom" content="放大">
47
+ <a-button
48
+ size="small"
49
+ type="outline"
50
+ :disabled="scale >= maxScale"
51
+ @click="zoom(0.1)"
52
+ >
53
+ <ZoomIn :size="16" />
54
+ </a-button>
55
+ </a-tooltip>
56
+ <a-tooltip
57
+ v-if="isDownload"
58
+ mini
59
+ position="bottom"
60
+ content="下载"
61
+ >
62
+ <a-button
63
+ size="small"
64
+ type="outline"
65
+ @click="emit('download')"
66
+ >
67
+ <Download :size="16" />
68
+ </a-button>
69
+ </a-tooltip>
70
+ <div class="toolbar-divider"></div>
71
+ <a-tooltip mini position="bottom" content="向左旋转">
72
+ <a-button
73
+ size="small"
74
+ type="outline"
75
+ @click="rotateImage('left')"
76
+ >
77
+ <RotateCw :size="16" />
78
+ </a-button>
79
+ </a-tooltip>
80
+ <a-tooltip mini position="bottom" content="向右旋转">
81
+ <a-button
82
+ size="small"
83
+ type="outline"
84
+ @click="rotateImage('right')"
85
+ >
86
+ <RotateCcw :size="16" />
87
+ </a-button>
88
+ </a-tooltip>
89
+ </div>
90
+ </div>
91
+
92
+ <div
93
+ class="relative w-full h-full flex items-center justify-center overflow-hidden"
94
+ @mousedown="startPan"
95
+ @mousemove="pan"
96
+ @mouseup="stopPan"
97
+ @mouseleave="stopPan"
98
+ >
99
+ <img
100
+ v-if="url"
101
+ :src="url"
102
+ alt="预览图片"
103
+ :style="{
104
+ transform: `translate(${position.x}px, ${position.y}px) rotate(${rotation}deg) scale(${scale})`,
105
+ cursor: isPanning ? 'grabbing' : 'grab'
106
+ }"
107
+ @contextmenu.prevent
108
+ @mousedown.prevent
109
+ @load="onImageLoad"
110
+ />
111
+ </div>
112
+ </div>
113
+ </template>
114
+
115
+ <script setup>
116
+ import { ref } from 'vue';
117
+ import { ZoomIn, ZoomOut, RefreshCcw, RotateCw, Download, Maximize2 } from 'lucide-vue-next';
118
+
119
+ const props = defineProps({
120
+ url: {
121
+ type: String,
122
+ required: true
123
+ },
124
+ minScale: {
125
+ type: Number,
126
+ default: 0.1
127
+ },
128
+ maxScale: {
129
+ type: Number,
130
+ default: 5
131
+ },
132
+ clickStep: {
133
+ type: Number,
134
+ default: 0.25
135
+ },
136
+ wheelStep: {
137
+ type: Number,
138
+ default: 0.1
139
+ },
140
+ originalId: {
141
+ type: String,
142
+ default: ''
143
+ },
144
+ isDownload: {
145
+ type: Boolean,
146
+ default: false
147
+ }
148
+ });
149
+
150
+ const emit = defineEmits(['original', 'download']);
151
+
152
+ const rotation = ref(0);
153
+ const scale = ref(1);
154
+ const position = ref({ x: 0, y: 0 });
155
+ const isPanning = ref(false);
156
+ const lastPosition = ref({ x: 0, y: 0 });
157
+
158
+ const rotateImage = direction => {
159
+ if (direction === 'left') {
160
+ rotation.value = (rotation.value - 90) % 360;
161
+ } else {
162
+ rotation.value = (rotation.value + 90) % 360;
163
+ }
164
+ };
165
+
166
+ const zoom = (delta, isWheel = false) => {
167
+ const step = isWheel ? props.wheelStep : props.clickStep; // 滚轮缩放使用更小的步长
168
+ const newScale = scale.value + delta * step;
169
+ if (newScale <= props.minScale) {
170
+ scale.value = props.minScale;
171
+ } else if (newScale >= props.maxScale) {
172
+ scale.value = props.maxScale;
173
+ } else {
174
+ scale.value = newScale;
175
+ }
176
+ };
177
+
178
+ const handleWheel = e => {
179
+ e.preventDefault();
180
+ const delta = e.deltaY > 0 ? -1 : 1;
181
+ zoom(delta, true);
182
+ };
183
+
184
+ const startPan = e => {
185
+ if (e.button !== 0) return;
186
+ isPanning.value = true;
187
+ lastPosition.value = { x: e.clientX, y: e.clientY };
188
+ };
189
+
190
+ const pan = e => {
191
+ if (!isPanning.value) return;
192
+
193
+ const deltaX = e.clientX - lastPosition.value.x;
194
+ const deltaY = e.clientY - lastPosition.value.y;
195
+
196
+ position.value = {
197
+ x: position.value.x + deltaX,
198
+ y: position.value.y + deltaY
199
+ };
200
+
201
+ lastPosition.value = { x: e.clientX, y: e.clientY };
202
+ };
203
+
204
+ const stopPan = () => {
205
+ isPanning.value = false;
206
+ };
207
+
208
+ const reset = () => {
209
+ rotation.value = 0;
210
+ scale.value = 1;
211
+ position.value = { x: 0, y: 0 };
212
+ };
213
+
214
+ const original = () => {
215
+ emit('original');
216
+ };
217
+
218
+ defineExpose({
219
+ reset
220
+ });
221
+ </script>
222
+
223
+ <style lang="less" scoped>
224
+ // 样式已统一到公共样式文件,这里只保留组件特定样式
225
+ .preview-container .relative {
226
+ flex: 1;
227
+ position: relative;
228
+ width: 100%;
229
+ height: 100%;
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: center;
233
+ overflow: hidden;
234
+ }
235
+ </style>