@xlui/xux-ui 0.2.1 → 1.1.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.
- package/dist/assets/areas.worker-Ci--LHMH.js +1 -0
- package/dist/browser-Bfcp93e9.js +8 -0
- package/dist/browser-Dj1SWzn2.mjs +1456 -0
- package/dist/index.css +1 -1
- package/dist/index.js +32 -32
- package/dist/index.mjs +18021 -2703
- package/package.json +70 -63
- package/src/components/Button/index.vue +6 -6
- package/src/components/Card/index.vue +44 -11
- package/src/components/DateTimePicker/index.vue +121 -39
- package/src/components/Qrcode/index.vue +390 -0
- package/src/components/RegionCascader/areas.worker.ts +196 -0
- package/src/components/RegionCascader/data/china-areas.full.json +14464 -0
- package/src/components/RegionCascader/data/init.mjs +377 -0
- package/src/components/RegionCascader/index.vue +870 -0
- package/src/components/Select/index.vue +25 -22
- package/src/components/SpecialEffects/fireworks.vue +134 -0
- package/src/components/SpecialEffects/glow.vue +377 -0
- package/src/components/Switch/index.vue +335 -0
- package/src/index.ts +9 -0
- package/LICENSE +0 -194
@@ -0,0 +1,390 @@
|
|
1
|
+
<template>
|
2
|
+
<div :class="containerClass" :style="containerStyle">
|
3
|
+
<canvas ref="canvasRef" :width="canvasSize" :height="canvasSize" :style="canvasStyle"></canvas>
|
4
|
+
|
5
|
+
<!-- 加载状态 -->
|
6
|
+
<div v-if="loading" class="x-qrcode-loading">
|
7
|
+
<div class="x-qrcode-spinner"></div>
|
8
|
+
<span v-if="loadingText" class="x-qrcode-loading-text">{{ loadingText }}</span>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<!-- 错误状态 -->
|
12
|
+
<div v-if="error" class="x-qrcode-error">
|
13
|
+
<div class="x-qrcode-error-icon">⚠️</div>
|
14
|
+
<span v-if="errorText" class="x-qrcode-error-text">{{ errorText }}</span>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
</template>
|
18
|
+
|
19
|
+
<script setup lang="ts">
|
20
|
+
import { ref, computed, watch, onMounted, nextTick } from 'vue'
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Qrcode 二维码组件
|
24
|
+
* @displayName XQrcode
|
25
|
+
*/
|
26
|
+
|
27
|
+
export interface QrcodeProps {
|
28
|
+
/**
|
29
|
+
* 二维码内容
|
30
|
+
*/
|
31
|
+
value: string
|
32
|
+
/**
|
33
|
+
* 文本图标(支持emoji、文本)
|
34
|
+
*/
|
35
|
+
textIcon?: string
|
36
|
+
/**
|
37
|
+
* 图片图标(支持图片URL)
|
38
|
+
*/
|
39
|
+
icon?: string
|
40
|
+
/**
|
41
|
+
* 图标尺寸
|
42
|
+
*/
|
43
|
+
iconSize?: number
|
44
|
+
/**
|
45
|
+
* 图标颜色(仅对文本图标有效)
|
46
|
+
*/
|
47
|
+
iconColor?: string
|
48
|
+
/**
|
49
|
+
* 二维码尺寸
|
50
|
+
*/
|
51
|
+
size?: number
|
52
|
+
/**
|
53
|
+
* 二维码背景色
|
54
|
+
*/
|
55
|
+
backgroundColor?: string
|
56
|
+
/**
|
57
|
+
* 二维码前景色
|
58
|
+
*/
|
59
|
+
foregroundColor?: string
|
60
|
+
/**
|
61
|
+
* 二维码容错级别
|
62
|
+
* @values L, M, Q, H
|
63
|
+
*/
|
64
|
+
errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H'
|
65
|
+
/**
|
66
|
+
* 是否显示边框
|
67
|
+
*/
|
68
|
+
bordered?: boolean
|
69
|
+
/**
|
70
|
+
* 边框颜色
|
71
|
+
*/
|
72
|
+
borderColor?: string
|
73
|
+
/**
|
74
|
+
* 边框宽度
|
75
|
+
*/
|
76
|
+
borderWidth?: number
|
77
|
+
/**
|
78
|
+
* 加载文本
|
79
|
+
*/
|
80
|
+
loadingText?: string
|
81
|
+
/**
|
82
|
+
* 错误文本
|
83
|
+
*/
|
84
|
+
errorText?: string
|
85
|
+
}
|
86
|
+
|
87
|
+
const props = withDefaults(defineProps<QrcodeProps>(), {
|
88
|
+
size: 200,
|
89
|
+
iconSize: 40,
|
90
|
+
iconColor: '#000000',
|
91
|
+
backgroundColor: '#ffffff',
|
92
|
+
foregroundColor: '#1a1a1a',
|
93
|
+
errorCorrectionLevel: 'M',
|
94
|
+
bordered: true,
|
95
|
+
borderColor: '#d9d9d9',
|
96
|
+
borderWidth: 1,
|
97
|
+
loadingText: '生成中...',
|
98
|
+
errorText: '生成失败'
|
99
|
+
})
|
100
|
+
|
101
|
+
const emit = defineEmits<{
|
102
|
+
/**
|
103
|
+
* 二维码生成成功时触发
|
104
|
+
*/
|
105
|
+
(e: 'success'): void
|
106
|
+
/**
|
107
|
+
* 二维码生成失败时触发
|
108
|
+
*/
|
109
|
+
(e: 'error', error: Error): void
|
110
|
+
}>()
|
111
|
+
|
112
|
+
const canvasRef = ref<HTMLCanvasElement>()
|
113
|
+
const loading = ref(false)
|
114
|
+
const error = ref(false)
|
115
|
+
|
116
|
+
const canvasSize = computed(() => props.size)
|
117
|
+
const containerSize = computed(() => props.bordered ? props.size + props.borderWidth * 2 : props.size)
|
118
|
+
|
119
|
+
const containerClass = computed(() => [
|
120
|
+
'x-qrcode',
|
121
|
+
{
|
122
|
+
'x-qrcode-bordered': props.bordered,
|
123
|
+
'x-qrcode-loading': loading.value,
|
124
|
+
'x-qrcode-error': error.value
|
125
|
+
}
|
126
|
+
])
|
127
|
+
|
128
|
+
const containerStyle = computed(() => ({
|
129
|
+
width: `${containerSize.value}px`,
|
130
|
+
height: `${containerSize.value}px`
|
131
|
+
}))
|
132
|
+
|
133
|
+
const canvasStyle = computed(() => ({
|
134
|
+
width: `${props.size}px`,
|
135
|
+
height: `${props.size}px`,
|
136
|
+
border: props.bordered ? `${props.borderWidth}px solid ${props.borderColor}` : 'none',
|
137
|
+
borderRadius: '4px'
|
138
|
+
}))
|
139
|
+
|
140
|
+
// 使用qrcode库生成真正的二维码
|
141
|
+
async function generateQRCode(value: string, size: number): Promise<void> {
|
142
|
+
try {
|
143
|
+
// 动态导入qrcode库
|
144
|
+
const QRCode = (await import('qrcode')).default
|
145
|
+
const canvas = canvasRef.value
|
146
|
+
if (!canvas) {
|
147
|
+
throw new Error('Canvas not found')
|
148
|
+
}
|
149
|
+
|
150
|
+
// 配置二维码选项
|
151
|
+
const options = {
|
152
|
+
width: size,
|
153
|
+
margin: 1,
|
154
|
+
color: {
|
155
|
+
dark: props.foregroundColor,
|
156
|
+
light: props.backgroundColor
|
157
|
+
},
|
158
|
+
errorCorrectionLevel: props.errorCorrectionLevel
|
159
|
+
}
|
160
|
+
|
161
|
+
// 生成二维码到canvas
|
162
|
+
await QRCode.toCanvas(canvas, value, options)
|
163
|
+
|
164
|
+
// 如果有图标,在二维码中心绘制图标
|
165
|
+
if (props.textIcon || props.icon) {
|
166
|
+
await drawIconInCenter(canvas, size)
|
167
|
+
}
|
168
|
+
} catch (err) {
|
169
|
+
throw new Error(`Failed to generate QR code: ${err}`)
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
// 在二维码中心绘制图标
|
174
|
+
async function drawIconInCenter(canvas: HTMLCanvasElement, size: number): Promise<void> {
|
175
|
+
const ctx = canvas.getContext('2d')
|
176
|
+
if (!ctx) return
|
177
|
+
|
178
|
+
const iconSize = props.iconSize
|
179
|
+
const centerX = size / 2
|
180
|
+
const centerY = size / 2
|
181
|
+
const iconX = centerX - iconSize / 2
|
182
|
+
const iconY = centerY - iconSize / 2
|
183
|
+
|
184
|
+
// 优先处理图片图标
|
185
|
+
if (props.icon) {
|
186
|
+
// 图片图标处理
|
187
|
+
const img = new Image()
|
188
|
+
img.crossOrigin = 'anonymous'
|
189
|
+
|
190
|
+
return new Promise((resolve, reject) => {
|
191
|
+
img.onload = () => {
|
192
|
+
try {
|
193
|
+
// 绘制白色背景矩形
|
194
|
+
ctx.fillStyle = props.backgroundColor
|
195
|
+
ctx.beginPath()
|
196
|
+
ctx.rect(iconX - 4, iconY - 4, iconSize + 8, iconSize + 8)
|
197
|
+
ctx.fill()
|
198
|
+
|
199
|
+
ctx.drawImage(img, iconX, iconY, iconSize, iconSize)
|
200
|
+
resolve()
|
201
|
+
} catch (err) {
|
202
|
+
reject(err)
|
203
|
+
}
|
204
|
+
}
|
205
|
+
img.onerror = () => {
|
206
|
+
reject(new Error('Failed to load icon image'))
|
207
|
+
}
|
208
|
+
img.src = props.icon!
|
209
|
+
})
|
210
|
+
} else if (props.textIcon) {
|
211
|
+
// 文本图标处理(包括emoji)
|
212
|
+
ctx.font = `${iconSize}px Arial`
|
213
|
+
ctx.textAlign = 'center'
|
214
|
+
ctx.textBaseline = 'middle'
|
215
|
+
|
216
|
+
// 测量文本实际尺寸
|
217
|
+
const textMetrics = ctx.measureText(props.textIcon)
|
218
|
+
const textWidth = textMetrics.width
|
219
|
+
const textHeight = iconSize // 字体大小作为高度参考
|
220
|
+
|
221
|
+
// 计算背景矩形尺寸,添加适当的内边距
|
222
|
+
const padding = 8
|
223
|
+
const bgWidth = Math.max(textWidth + padding, iconSize)
|
224
|
+
const bgHeight = Math.max(textHeight + padding, iconSize)
|
225
|
+
const bgX = centerX - bgWidth / 2
|
226
|
+
const bgY = centerY - bgHeight / 2
|
227
|
+
|
228
|
+
// 绘制白色背景矩形
|
229
|
+
ctx.fillStyle = props.backgroundColor
|
230
|
+
ctx.beginPath()
|
231
|
+
ctx.rect(bgX, bgY, bgWidth, bgHeight)
|
232
|
+
ctx.fill()
|
233
|
+
|
234
|
+
// 绘制文本
|
235
|
+
ctx.fillStyle = props.iconColor
|
236
|
+
ctx.fillText(props.textIcon, centerX, centerY)
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
async function createQRCode() {
|
241
|
+
if (!props.value) {
|
242
|
+
error.value = true
|
243
|
+
emit('error', new Error('Value is required'))
|
244
|
+
return
|
245
|
+
}
|
246
|
+
|
247
|
+
loading.value = true
|
248
|
+
error.value = false
|
249
|
+
|
250
|
+
try {
|
251
|
+
await generateQRCode(props.value, props.size)
|
252
|
+
loading.value = false
|
253
|
+
emit('success')
|
254
|
+
} catch (err) {
|
255
|
+
loading.value = false
|
256
|
+
error.value = true
|
257
|
+
emit('error', err as Error)
|
258
|
+
}
|
259
|
+
}
|
260
|
+
|
261
|
+
// 监听属性变化
|
262
|
+
watch(
|
263
|
+
() => [props.value, props.size, props.backgroundColor, props.foregroundColor, props.textIcon, props.icon, props.iconSize, props.iconColor],
|
264
|
+
() => {
|
265
|
+
nextTick(() => {
|
266
|
+
createQRCode()
|
267
|
+
})
|
268
|
+
},
|
269
|
+
{ deep: true }
|
270
|
+
)
|
271
|
+
|
272
|
+
onMounted(() => {
|
273
|
+
createQRCode()
|
274
|
+
})
|
275
|
+
|
276
|
+
// 暴露方法
|
277
|
+
defineExpose({
|
278
|
+
/**
|
279
|
+
* 重新生成二维码
|
280
|
+
*/
|
281
|
+
refresh: createQRCode
|
282
|
+
})
|
283
|
+
</script>
|
284
|
+
|
285
|
+
<style scoped>
|
286
|
+
.x-qrcode {
|
287
|
+
position: relative;
|
288
|
+
display: inline-block;
|
289
|
+
background-color: var(--x-color-white, #ffffff);
|
290
|
+
border-radius: 8px;
|
291
|
+
overflow: hidden;
|
292
|
+
transition: all 0.3s ease;
|
293
|
+
}
|
294
|
+
|
295
|
+
.x-qrcode-bordered {
|
296
|
+
box-shadow: var(--x-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.05));
|
297
|
+
}
|
298
|
+
|
299
|
+
.x-qrcode canvas {
|
300
|
+
display: block;
|
301
|
+
transition: opacity 0.3s ease;
|
302
|
+
}
|
303
|
+
|
304
|
+
/* 加载状态 */
|
305
|
+
.x-qrcode-loading {
|
306
|
+
position: absolute;
|
307
|
+
top: 0;
|
308
|
+
left: 0;
|
309
|
+
right: 0;
|
310
|
+
bottom: 0;
|
311
|
+
display: flex;
|
312
|
+
flex-direction: column;
|
313
|
+
align-items: center;
|
314
|
+
justify-content: center;
|
315
|
+
background-color: rgba(255, 255, 255, 0.9);
|
316
|
+
backdrop-filter: blur(2px);
|
317
|
+
}
|
318
|
+
|
319
|
+
.x-qrcode-loading canvas {
|
320
|
+
opacity: 0.3;
|
321
|
+
}
|
322
|
+
|
323
|
+
.x-qrcode-spinner {
|
324
|
+
width: 24px;
|
325
|
+
height: 24px;
|
326
|
+
border: 2px solid var(--x-color-gray-200, #e5e5e5);
|
327
|
+
border-top: 2px solid var(--x-color-primary, #ff6b35);
|
328
|
+
border-radius: 50%;
|
329
|
+
animation: spin 1s linear infinite;
|
330
|
+
margin-bottom: 8px;
|
331
|
+
}
|
332
|
+
|
333
|
+
.x-qrcode-loading-text {
|
334
|
+
font-size: 12px;
|
335
|
+
color: var(--x-color-gray-600, #666666);
|
336
|
+
text-align: center;
|
337
|
+
}
|
338
|
+
|
339
|
+
/* 错误状态 */
|
340
|
+
.x-qrcode-error {
|
341
|
+
position: absolute;
|
342
|
+
top: 0;
|
343
|
+
left: 0;
|
344
|
+
right: 0;
|
345
|
+
bottom: 0;
|
346
|
+
display: flex;
|
347
|
+
flex-direction: column;
|
348
|
+
align-items: center;
|
349
|
+
justify-content: center;
|
350
|
+
background-color: rgba(255, 255, 255, 0.9);
|
351
|
+
backdrop-filter: blur(2px);
|
352
|
+
}
|
353
|
+
|
354
|
+
.x-qrcode-error canvas {
|
355
|
+
opacity: 0.1;
|
356
|
+
}
|
357
|
+
|
358
|
+
.x-qrcode-error-icon {
|
359
|
+
font-size: 24px;
|
360
|
+
margin-bottom: 8px;
|
361
|
+
}
|
362
|
+
|
363
|
+
.x-qrcode-error-text {
|
364
|
+
font-size: 12px;
|
365
|
+
color: var(--x-color-danger, #ef4444);
|
366
|
+
text-align: center;
|
367
|
+
}
|
368
|
+
|
369
|
+
@keyframes spin {
|
370
|
+
from {
|
371
|
+
transform: rotate(0deg);
|
372
|
+
}
|
373
|
+
|
374
|
+
to {
|
375
|
+
transform: rotate(360deg);
|
376
|
+
}
|
377
|
+
}
|
378
|
+
|
379
|
+
/* 响应式 */
|
380
|
+
@media (max-width: 768px) {
|
381
|
+
.x-qrcode {
|
382
|
+
border-radius: 6px;
|
383
|
+
}
|
384
|
+
|
385
|
+
.x-qrcode-loading-text,
|
386
|
+
.x-qrcode-error-text {
|
387
|
+
font-size: 11px;
|
388
|
+
}
|
389
|
+
}
|
390
|
+
</style>
|
@@ -0,0 +1,196 @@
|
|
1
|
+
// areas.worker.ts - Web Worker 用于处理大数据量的地区数据
|
2
|
+
interface AreaNode {
|
3
|
+
value: string
|
4
|
+
label: string
|
5
|
+
children?: AreaNode[]
|
6
|
+
}
|
7
|
+
|
8
|
+
interface AreaNodeSimple {
|
9
|
+
value: string
|
10
|
+
label: string
|
11
|
+
leaf: boolean
|
12
|
+
}
|
13
|
+
|
14
|
+
interface AreaNodeWithPath {
|
15
|
+
value: string
|
16
|
+
label: string
|
17
|
+
leaf: boolean
|
18
|
+
level: number // 1-省, 2-市, 3-区
|
19
|
+
path: string[] // 完整路径的value数组
|
20
|
+
pathLabels: string[] // 完整路径的label数组
|
21
|
+
}
|
22
|
+
|
23
|
+
// 索引:父级code -> 子节点数组
|
24
|
+
let indexByParent: Record<string, AreaNodeSimple[]> = {}
|
25
|
+
let rootNodes: AreaNodeSimple[] = []
|
26
|
+
let fullData: AreaNode[] = []
|
27
|
+
// 全局索引:code -> 完整路径信息
|
28
|
+
let globalIndex: Map<string, AreaNodeWithPath> = new Map()
|
29
|
+
|
30
|
+
// 构建索引
|
31
|
+
function buildIndex(tree: AreaNode[]) {
|
32
|
+
indexByParent = {}
|
33
|
+
globalIndex = new Map()
|
34
|
+
|
35
|
+
function traverse(nodes: AreaNode[], parentValue: string = '', level: number = 1, path: string[] = [], pathLabels: string[] = []) {
|
36
|
+
nodes.forEach(node => {
|
37
|
+
const currentPath = [...path, node.value]
|
38
|
+
const currentPathLabels = [...pathLabels, node.label]
|
39
|
+
|
40
|
+
const simpleNode: AreaNodeSimple = {
|
41
|
+
value: node.value,
|
42
|
+
label: node.label,
|
43
|
+
leaf: !node.children || node.children.length === 0
|
44
|
+
}
|
45
|
+
|
46
|
+
// 构建全局索引
|
47
|
+
const nodeWithPath: AreaNodeWithPath = {
|
48
|
+
value: node.value,
|
49
|
+
label: node.label,
|
50
|
+
leaf: simpleNode.leaf,
|
51
|
+
level,
|
52
|
+
path: currentPath,
|
53
|
+
pathLabels: currentPathLabels
|
54
|
+
}
|
55
|
+
globalIndex.set(node.value, nodeWithPath)
|
56
|
+
|
57
|
+
if (parentValue === '') {
|
58
|
+
// 根节点(省份)
|
59
|
+
rootNodes.push(simpleNode)
|
60
|
+
} else {
|
61
|
+
// 子节点
|
62
|
+
if (!indexByParent[parentValue]) {
|
63
|
+
indexByParent[parentValue] = []
|
64
|
+
}
|
65
|
+
indexByParent[parentValue].push(simpleNode)
|
66
|
+
}
|
67
|
+
|
68
|
+
// 递归处理子节点
|
69
|
+
if (node.children && node.children.length > 0) {
|
70
|
+
traverse(node.children, node.value, level + 1, currentPath, currentPathLabels)
|
71
|
+
}
|
72
|
+
})
|
73
|
+
}
|
74
|
+
|
75
|
+
traverse(tree)
|
76
|
+
}
|
77
|
+
|
78
|
+
// 监听主线程消息
|
79
|
+
self.onmessage = (e: MessageEvent) => {
|
80
|
+
const { type, payload } = e.data || {}
|
81
|
+
|
82
|
+
switch (type) {
|
83
|
+
case 'init':
|
84
|
+
// 初始化:接收完整数据并建立索引
|
85
|
+
fullData = payload.root
|
86
|
+
rootNodes = []
|
87
|
+
buildIndex(fullData)
|
88
|
+
console.log('[Worker] 索引构建完成,根节点数:', rootNodes.length)
|
89
|
+
console.log('[Worker] 索引键数:', Object.keys(indexByParent).length)
|
90
|
+
self.postMessage({ type: 'ready' })
|
91
|
+
break
|
92
|
+
|
93
|
+
case 'roots':
|
94
|
+
// 获取根节点(省份列表)
|
95
|
+
self.postMessage({
|
96
|
+
type: 'roots',
|
97
|
+
list: rootNodes
|
98
|
+
})
|
99
|
+
break
|
100
|
+
|
101
|
+
case 'children':
|
102
|
+
// 获取指定父节点的子节点
|
103
|
+
const { parent } = payload
|
104
|
+
const children = indexByParent[parent] || []
|
105
|
+
self.postMessage({
|
106
|
+
type: 'children',
|
107
|
+
parent,
|
108
|
+
list: children
|
109
|
+
})
|
110
|
+
break
|
111
|
+
|
112
|
+
case 'getByCode':
|
113
|
+
// 根据code获取单个节点信息
|
114
|
+
const { code } = payload
|
115
|
+
// 先在根节点中查找
|
116
|
+
let found = rootNodes.find(n => n.value === code)
|
117
|
+
if (!found) {
|
118
|
+
// 在索引中查找
|
119
|
+
for (const children of Object.values(indexByParent)) {
|
120
|
+
found = children.find(n => n.value === code)
|
121
|
+
if (found) break
|
122
|
+
}
|
123
|
+
}
|
124
|
+
self.postMessage({
|
125
|
+
type: 'nodeInfo',
|
126
|
+
code,
|
127
|
+
node: found || null
|
128
|
+
})
|
129
|
+
break
|
130
|
+
|
131
|
+
case 'search':
|
132
|
+
// 全局搜索功能(支持智能反填)
|
133
|
+
const { keyword, parentValue } = payload
|
134
|
+
if (!keyword || keyword.trim() === '') {
|
135
|
+
self.postMessage({
|
136
|
+
type: 'searchResults',
|
137
|
+
keyword,
|
138
|
+
results: []
|
139
|
+
})
|
140
|
+
break
|
141
|
+
}
|
142
|
+
|
143
|
+
const searchKeyword = keyword.toLowerCase().trim()
|
144
|
+
const searchResults: AreaNodeWithPath[] = []
|
145
|
+
|
146
|
+
// 如果指定了父节点,只在该父节点的子节点中搜索
|
147
|
+
if (parentValue) {
|
148
|
+
const children = indexByParent[parentValue] || []
|
149
|
+
for (const node of children) {
|
150
|
+
if (node.label.toLowerCase().includes(searchKeyword) ||
|
151
|
+
node.value.includes(searchKeyword)) {
|
152
|
+
const nodeWithPath = globalIndex.get(node.value)
|
153
|
+
if (nodeWithPath) {
|
154
|
+
searchResults.push(nodeWithPath)
|
155
|
+
}
|
156
|
+
}
|
157
|
+
}
|
158
|
+
} else {
|
159
|
+
// 全局搜索:在所有节点中搜索
|
160
|
+
for (const [code, node] of globalIndex.entries()) {
|
161
|
+
if (node.label.toLowerCase().includes(searchKeyword) ||
|
162
|
+
code.includes(searchKeyword)) {
|
163
|
+
searchResults.push(node)
|
164
|
+
// 限制搜索结果数量
|
165
|
+
if (searchResults.length >= 100) break
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
// 按层级排序:省 > 市 > 区
|
171
|
+
searchResults.sort((a, b) => a.level - b.level)
|
172
|
+
|
173
|
+
self.postMessage({
|
174
|
+
type: 'searchResults',
|
175
|
+
keyword,
|
176
|
+
results: searchResults
|
177
|
+
})
|
178
|
+
break
|
179
|
+
|
180
|
+
default:
|
181
|
+
console.warn('[Worker] Unknown message type:', type)
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
// 错误处理
|
186
|
+
self.onerror = (error) => {
|
187
|
+
console.error('[Worker] Error:', error)
|
188
|
+
const errorMessage = error instanceof ErrorEvent ? error.message : String(error)
|
189
|
+
self.postMessage({
|
190
|
+
type: 'error',
|
191
|
+
error: errorMessage
|
192
|
+
})
|
193
|
+
}
|
194
|
+
|
195
|
+
export {}
|
196
|
+
|