@ruixinkeji/prism-ui 1.0.0 → 1.0.2
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 +1 -1
- package/components/PrismAIAssist/PrismAIAssist.vue +98 -98
- package/components/PrismAddressInput/PrismAddressInput.vue +597 -597
- package/components/PrismCityCascadeSelect/PrismCityCascadeSelect.vue +793 -793
- package/components/PrismCityPicker/PrismCityPicker.vue +1008 -1008
- package/components/PrismCitySelect/PrismCitySelect.vue +435 -435
- package/components/PrismCode/PrismCode.vue +749 -749
- package/components/PrismCodeInput/PrismCodeInput.vue +156 -156
- package/components/PrismDateTimePicker/PrismDateTimePicker.vue +953 -953
- package/components/PrismDropdown/PrismDropdown.vue +77 -77
- package/components/PrismGroupSticky/PrismGroupSticky.vue +352 -352
- package/components/PrismIdCardInput/PrismIdCardInput.vue +253 -253
- package/components/PrismImagePicker/PrismImagePicker.vue +457 -457
- package/components/PrismIndexBar/PrismIndexBar.vue +243 -243
- package/components/PrismLicensePlateInput/PrismLicensePlateInput.vue +1100 -1100
- package/components/PrismMusicPlayer/PrismMusicPlayer.vue +530 -530
- package/components/PrismNavBar/PrismNavBar.vue +199 -199
- package/components/PrismSecureInput/PrismSecureInput.vue +360 -360
- package/components/PrismSticky/PrismSticky.vue +173 -173
- package/components/PrismSwiper/PrismSwiper.vue +338 -338
- package/components/PrismSwitch/PrismSwitch.vue +202 -202
- package/components/PrismTabBar/PrismTabBar.vue +147 -147
- package/components/PrismTabs/PrismTabs.vue +49 -49
- package/components/PrismVoiceInput/PrismVoiceInput.vue +529 -529
- package/fonts/fa-brands-400.woff2 +0 -0
- package/fonts/fa-regular-400.woff2 +0 -0
- package/fonts/fa-solid-900.woff2 +0 -0
- package/fonts/fa-v4compatibility.woff2 +0 -0
- package/fonts/font-awesome.css +913 -0
- package/fonts/iconfont.woff2 +0 -0
- package/package.json +7 -1
- package/store/app.d.ts +21 -0
- package/store/app.js +68 -0
- package/styles/base.scss +2 -2
|
@@ -1,1100 +1,1100 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<view class="prism-license-plate-input" :class="{ 'dark-mode': appStore.isDarkMode }">
|
|
3
|
-
<!-- 车牌号输入框 -->
|
|
4
|
-
<view class="plate-boxes">
|
|
5
|
-
<!-- 第1位 -->
|
|
6
|
-
<view
|
|
7
|
-
class="plate-box province"
|
|
8
|
-
:class="{
|
|
9
|
-
'active': activeIndex === 0,
|
|
10
|
-
'filled': plateArray[0],
|
|
11
|
-
'special-white': getSpecialCharType(plateArray[0]) === 'white',
|
|
12
|
-
'special-black': getSpecialCharType(plateArray[0]) === 'black',
|
|
13
|
-
'special-yellow': getSpecialCharType(plateArray[0]) === 'yellow'
|
|
14
|
-
}"
|
|
15
|
-
@click="showKeyboardAt(0)"
|
|
16
|
-
>
|
|
17
|
-
<text class="plate-text">{{ plateArray[0] || '' }}</text>
|
|
18
|
-
</view>
|
|
19
|
-
|
|
20
|
-
<!-- 第2位 -->
|
|
21
|
-
<view
|
|
22
|
-
class="plate-box letter"
|
|
23
|
-
:class="{
|
|
24
|
-
'active': activeIndex === 1,
|
|
25
|
-
'filled': plateArray[1],
|
|
26
|
-
'special-white': getSpecialCharType(plateArray[1]) === 'white',
|
|
27
|
-
'special-black': getSpecialCharType(plateArray[1]) === 'black',
|
|
28
|
-
'special-yellow': getSpecialCharType(plateArray[1]) === 'yellow'
|
|
29
|
-
}"
|
|
30
|
-
@click="showKeyboardAt(1)"
|
|
31
|
-
>
|
|
32
|
-
<text class="plate-text">{{ plateArray[1] || '' }}</text>
|
|
33
|
-
</view>
|
|
34
|
-
|
|
35
|
-
<!-- 分隔点 -->
|
|
36
|
-
<view class="plate-dot"></view>
|
|
37
|
-
|
|
38
|
-
<!-- 后5位 -->
|
|
39
|
-
<view
|
|
40
|
-
v-for="i in 5"
|
|
41
|
-
:key="i"
|
|
42
|
-
class="plate-box"
|
|
43
|
-
:class="{
|
|
44
|
-
'active': activeIndex === i + 1,
|
|
45
|
-
'filled': plateArray[i + 1],
|
|
46
|
-
'special-white': getSpecialCharType(plateArray[i + 1]) === 'white',
|
|
47
|
-
'special-black': getSpecialCharType(plateArray[i + 1]) === 'black',
|
|
48
|
-
'special-yellow': getSpecialCharType(plateArray[i + 1]) === 'yellow'
|
|
49
|
-
}"
|
|
50
|
-
@click="showKeyboardAt(i + 1)"
|
|
51
|
-
>
|
|
52
|
-
<text class="plate-text">{{ plateArray[i + 1] || '' }}</text>
|
|
53
|
-
</view>
|
|
54
|
-
|
|
55
|
-
<!-- 新能源第8位(始终显示,可选) -->
|
|
56
|
-
<view class="plate-box-wrapper new-energy-wrapper">
|
|
57
|
-
<text class="new-energy-label" :class="{ 'hidden': getSpecialCharType(plateArray[7]) }">新能源</text>
|
|
58
|
-
<view
|
|
59
|
-
class="plate-box"
|
|
60
|
-
:class="{
|
|
61
|
-
'new-energy': isNewEnergy && !getSpecialCharType(plateArray[7]),
|
|
62
|
-
'new-energy-optional': !isNewEnergy,
|
|
63
|
-
'active': activeIndex === 7,
|
|
64
|
-
'filled': plateArray[7],
|
|
65
|
-
'special-white': getSpecialCharType(plateArray[7]) === 'white',
|
|
66
|
-
'special-black': getSpecialCharType(plateArray[7]) === 'black',
|
|
67
|
-
'special-yellow': getSpecialCharType(plateArray[7]) === 'yellow'
|
|
68
|
-
}"
|
|
69
|
-
@click="handleNewEnergyClick"
|
|
70
|
-
>
|
|
71
|
-
<text class="plate-text" v-if="isNewEnergy">{{ plateArray[7] || '' }}</text>
|
|
72
|
-
<text class="optional-icon fa fa-plus" v-else></text>
|
|
73
|
-
</view>
|
|
74
|
-
</view>
|
|
75
|
-
</view>
|
|
76
|
-
|
|
77
|
-
<!-- 新能源切换(隐藏,通过点击第8位框自动启用) -->
|
|
78
|
-
<!-- <view class="energy-switch" @click="isNewEnergy = !isNewEnergy">
|
|
79
|
-
<view class="prism-checkbox" :class="{ checked: isNewEnergy }">
|
|
80
|
-
<text v-if="isNewEnergy" class="check-icon fa fa-check"></text>
|
|
81
|
-
</view>
|
|
82
|
-
<text class="energy-text">新能源车牌</text>
|
|
83
|
-
</view> -->
|
|
84
|
-
|
|
85
|
-
<!-- 统一键盘(底部弹出,无遮罩) -->
|
|
86
|
-
<view class="keyboard-modal" v-if="showKeyboard">
|
|
87
|
-
<view class="keyboard-content">
|
|
88
|
-
<view class="keyboard-header">
|
|
89
|
-
<!-- 左侧:标题 + 切换按钮 -->
|
|
90
|
-
<view class="header-left">
|
|
91
|
-
<text class="keyboard-title">{{ keyboardTitle }}</text>
|
|
92
|
-
<view
|
|
93
|
-
v-if="activeIndex === 0"
|
|
94
|
-
class="switch-btn"
|
|
95
|
-
@click="toggleFirstKeyboard"
|
|
96
|
-
>
|
|
97
|
-
<text>{{ firstKeyboardMode === 'province' ? '切换字母' : '切换省份' }}</text>
|
|
98
|
-
</view>
|
|
99
|
-
</view>
|
|
100
|
-
<!-- 右侧:关闭按钮 -->
|
|
101
|
-
<text class="keyboard-close fa fa-times" @click="closeKeyboard"></text>
|
|
102
|
-
</view>
|
|
103
|
-
|
|
104
|
-
<scroll-view class="keyboard-body" scroll-y>
|
|
105
|
-
<!-- 省份区域 -->
|
|
106
|
-
<view class="keyboard-section" v-if="showProvinceSection && !showLetterInFirst">
|
|
107
|
-
<!-- 字母索引栏(支持滑动选择) -->
|
|
108
|
-
<view
|
|
109
|
-
class="keyboard-row letter-index-row"
|
|
110
|
-
@touchstart="onLetterIndexTouchStart"
|
|
111
|
-
@touchmove="onLetterIndexTouchMove"
|
|
112
|
-
@touchend="onLetterIndexTouchEnd"
|
|
113
|
-
>
|
|
114
|
-
<view
|
|
115
|
-
v-for="(group, idx) in letterGroups"
|
|
116
|
-
:key="idx"
|
|
117
|
-
class="key-item letter-index"
|
|
118
|
-
:class="{ 'active': isGroupSelected(group) }"
|
|
119
|
-
:data-index="idx"
|
|
120
|
-
@click="selectLetterGroup(group)"
|
|
121
|
-
>
|
|
122
|
-
{{ group.join('/') }}
|
|
123
|
-
</view>
|
|
124
|
-
</view>
|
|
125
|
-
<!-- 省份键盘(4行) -->
|
|
126
|
-
<view class="keyboard-qwerty province-qwerty">
|
|
127
|
-
<view class="keyboard-row">
|
|
128
|
-
<view
|
|
129
|
-
v-for="p in provinces.slice(0, 8)"
|
|
130
|
-
:key="p"
|
|
131
|
-
class="key-item"
|
|
132
|
-
:class="{ 'highlight': isProvinceHighlight(p) }"
|
|
133
|
-
@click="inputChar(p)"
|
|
134
|
-
>
|
|
135
|
-
{{ p }}
|
|
136
|
-
</view>
|
|
137
|
-
</view>
|
|
138
|
-
<view class="keyboard-row">
|
|
139
|
-
<view
|
|
140
|
-
v-for="p in provinces.slice(8, 16)"
|
|
141
|
-
:key="p"
|
|
142
|
-
class="key-item"
|
|
143
|
-
:class="{ 'highlight': isProvinceHighlight(p) }"
|
|
144
|
-
@click="inputChar(p)"
|
|
145
|
-
>
|
|
146
|
-
{{ p }}
|
|
147
|
-
</view>
|
|
148
|
-
</view>
|
|
149
|
-
<view class="keyboard-row">
|
|
150
|
-
<view
|
|
151
|
-
v-for="p in provinces.slice(16, 24)"
|
|
152
|
-
:key="p"
|
|
153
|
-
class="key-item"
|
|
154
|
-
:class="{ 'highlight': isProvinceHighlight(p) }"
|
|
155
|
-
@click="inputChar(p)"
|
|
156
|
-
>
|
|
157
|
-
{{ p }}
|
|
158
|
-
</view>
|
|
159
|
-
</view>
|
|
160
|
-
<view class="keyboard-row">
|
|
161
|
-
<view
|
|
162
|
-
v-for="p in provinces.slice(24, 31)"
|
|
163
|
-
:key="p"
|
|
164
|
-
class="key-item"
|
|
165
|
-
:class="{ 'highlight': isProvinceHighlight(p) }"
|
|
166
|
-
@click="inputChar(p)"
|
|
167
|
-
>
|
|
168
|
-
{{ p }}
|
|
169
|
-
</view>
|
|
170
|
-
<!-- 删除按钮 -->
|
|
171
|
-
<view class="key-item delete" @click="deleteChar">
|
|
172
|
-
<text class="delete-text">删除</text>
|
|
173
|
-
</view>
|
|
174
|
-
</view>
|
|
175
|
-
</view>
|
|
176
|
-
</view>
|
|
177
|
-
|
|
178
|
-
<!-- 数字区域(放在字母上面) -->
|
|
179
|
-
<view class="keyboard-section" v-if="showNumberSection">
|
|
180
|
-
<view class="keyboard-row number-row">
|
|
181
|
-
<view
|
|
182
|
-
v-for="n in numberRow"
|
|
183
|
-
:key="n"
|
|
184
|
-
class="key-item"
|
|
185
|
-
@click="inputChar(n)"
|
|
186
|
-
>
|
|
187
|
-
{{ n }}
|
|
188
|
-
</view>
|
|
189
|
-
</view>
|
|
190
|
-
</view>
|
|
191
|
-
|
|
192
|
-
<!-- 字母区域 -->
|
|
193
|
-
<view class="keyboard-section" v-if="showLetterSection || showLetterInFirst">
|
|
194
|
-
<view class="keyboard-qwerty">
|
|
195
|
-
<view class="keyboard-row" v-for="(row, idx) in letterRows" :key="idx">
|
|
196
|
-
<view
|
|
197
|
-
v-for="l in row"
|
|
198
|
-
:key="l"
|
|
199
|
-
class="key-item"
|
|
200
|
-
:class="{ disabled: shouldDisableIO && (l === 'I' || l === 'O') }"
|
|
201
|
-
@click="inputChar(l)"
|
|
202
|
-
>
|
|
203
|
-
{{ l }}
|
|
204
|
-
</view>
|
|
205
|
-
<!-- 删除按钮放在最后一行 -->
|
|
206
|
-
<view v-if="idx === 2" class="key-item delete" @click="deleteChar">
|
|
207
|
-
<text class="delete-text">删除</text>
|
|
208
|
-
</view>
|
|
209
|
-
</view>
|
|
210
|
-
</view>
|
|
211
|
-
</view>
|
|
212
|
-
|
|
213
|
-
<!-- 特殊字符区域 -->
|
|
214
|
-
<view class="keyboard-section" v-if="showSpecialSection">
|
|
215
|
-
<view class="keyboard-row special-row">
|
|
216
|
-
<view
|
|
217
|
-
v-for="item in specialChars"
|
|
218
|
-
:key="item.char"
|
|
219
|
-
class="key-item special"
|
|
220
|
-
:class="item.type"
|
|
221
|
-
@click="inputChar(item.char)"
|
|
222
|
-
>
|
|
223
|
-
{{ item.char }}
|
|
224
|
-
</view>
|
|
225
|
-
<!-- 最后一位显示完成按钮 -->
|
|
226
|
-
<view v-if="showDoneButton" class="key-item done" @click="closeKeyboard">
|
|
227
|
-
<text class="done-text">完成</text>
|
|
228
|
-
</view>
|
|
229
|
-
</view>
|
|
230
|
-
</view>
|
|
231
|
-
</scroll-view>
|
|
232
|
-
</view>
|
|
233
|
-
</view>
|
|
234
|
-
</view>
|
|
235
|
-
</template>
|
|
236
|
-
|
|
237
|
-
<script setup>
|
|
238
|
-
import { ref, computed, watch } from 'vue';
|
|
239
|
-
import { useAppStore } from '@/store/app';
|
|
240
|
-
|
|
241
|
-
const props = defineProps({
|
|
242
|
-
modelValue: {
|
|
243
|
-
type: String,
|
|
244
|
-
default: ''
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const emit = defineEmits(['update:modelValue', 'complete']);
|
|
249
|
-
|
|
250
|
-
const appStore = useAppStore();
|
|
251
|
-
|
|
252
|
-
const isNewEnergy = ref(false);
|
|
253
|
-
const showKeyboard = ref(false);
|
|
254
|
-
const activeIndex = ref(-1);
|
|
255
|
-
const firstKeyboardMode = ref('province'); // 'province' or 'letter'
|
|
256
|
-
|
|
257
|
-
// 省份简称(31个)
|
|
258
|
-
const provinces = [
|
|
259
|
-
'京', '津', '沪', '渝', '冀', '豫', '云', '辽', '黑', '湘',
|
|
260
|
-
'皖', '鲁', '新', '苏', '浙', '赣', '鄂', '桂', '甘', '晋',
|
|
261
|
-
'蒙', '陕', '吉', '闽', '贵', '粤', '川', '青', '藏', '琼', '宁'
|
|
262
|
-
];
|
|
263
|
-
|
|
264
|
-
// 省份拼音首字母映射(用于字母检索高亮)
|
|
265
|
-
const provincePinyinMap = {
|
|
266
|
-
'京': 'J', '津': 'J', '沪': 'H', '渝': 'Y', '冀': 'J',
|
|
267
|
-
'豫': 'Y', '云': 'Y', '辽': 'L', '黑': 'H', '湘': 'X',
|
|
268
|
-
'皖': 'W', '鲁': 'L', '新': 'X', '苏': 'S', '浙': 'Z',
|
|
269
|
-
'赣': 'G', '鄂': 'E', '桂': 'G', '甘': 'G', '晋': 'J',
|
|
270
|
-
'蒙': 'M', '陕': 'S', '吉': 'J', '闽': 'M', '贵': 'G',
|
|
271
|
-
'粤': 'Y', '川': 'C', '青': 'Q', '藏': 'Z', '琼': 'Q', '宁': 'N'
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
// 省份字母索引(两个字母一组,方便筛选)
|
|
275
|
-
const letterGroups = [
|
|
276
|
-
['C', 'E'], // 川、鄂
|
|
277
|
-
['G', 'H'], // 赣桂甘贵、沪黑
|
|
278
|
-
['J', 'L'], // 京津冀晋吉、辽鲁
|
|
279
|
-
['M', 'N'], // 蒙闽、宁
|
|
280
|
-
['Q', 'S'], // 青琼、苏陕
|
|
281
|
-
['W', 'X'], // 皖、新湘
|
|
282
|
-
['Y', 'Z'], // 渝豫云粤、浙藏
|
|
283
|
-
];
|
|
284
|
-
|
|
285
|
-
// 当前选中的字母组
|
|
286
|
-
const selectedLetters = ref([]);
|
|
287
|
-
|
|
288
|
-
// 选择字母组(单选)
|
|
289
|
-
function selectLetterGroup(group) {
|
|
290
|
-
// 如果是触摸事件刚选中的同一个组,跳过click处理
|
|
291
|
-
if (touchSelectedGroup && touchSelectedGroup.every(l => group.includes(l))) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// 检查是否已选中这个组
|
|
296
|
-
const isSelected = group.every(l => selectedLetters.value.includes(l));
|
|
297
|
-
if (isSelected) {
|
|
298
|
-
// 取消选中
|
|
299
|
-
selectedLetters.value = [];
|
|
300
|
-
} else {
|
|
301
|
-
// 选中(替换)
|
|
302
|
-
selectedLetters.value = [...group];
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// 判断字母组是否选中
|
|
307
|
-
function isGroupSelected(group) {
|
|
308
|
-
return group.every(l => selectedLetters.value.includes(l));
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// 清除字母筛选
|
|
312
|
-
function clearLetterFilter() {
|
|
313
|
-
selectedLetters.value = [];
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// 判断省份是否应该高亮
|
|
317
|
-
function isProvinceHighlight(province) {
|
|
318
|
-
if (selectedLetters.value.length === 0) return false;
|
|
319
|
-
return selectedLetters.value.includes(provincePinyinMap[province]);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// 字母索引滑动选择相关
|
|
323
|
-
let isTouchingLetterIndex = false;
|
|
324
|
-
let touchSelectedGroup = null; // 记录触摸选中的组,防止click重复处理
|
|
325
|
-
|
|
326
|
-
function onLetterIndexTouchStart(e) {
|
|
327
|
-
isTouchingLetterIndex = true;
|
|
328
|
-
touchSelectedGroup = null;
|
|
329
|
-
const touch = e.touches[0];
|
|
330
|
-
updateLetterIndexByTouch(touch);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function onLetterIndexTouchMove(e) {
|
|
334
|
-
if (!isTouchingLetterIndex) return;
|
|
335
|
-
const touch = e.touches[0];
|
|
336
|
-
updateLetterIndexByTouch(touch);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function onLetterIndexTouchEnd() {
|
|
340
|
-
isTouchingLetterIndex = false;
|
|
341
|
-
// 延迟清除,让click事件能检测到
|
|
342
|
-
setTimeout(() => {
|
|
343
|
-
touchSelectedGroup = null;
|
|
344
|
-
}, 100);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function updateLetterIndexByTouch(touch) {
|
|
348
|
-
const x = touch.clientX;
|
|
349
|
-
const y = touch.clientY;
|
|
350
|
-
|
|
351
|
-
// #ifdef H5
|
|
352
|
-
// H5端使用 document.elementFromPoint
|
|
353
|
-
const element = document.elementFromPoint(x, y);
|
|
354
|
-
if (element && element.classList.contains('letter-index')) {
|
|
355
|
-
const index = element.dataset.index;
|
|
356
|
-
if (index !== undefined) {
|
|
357
|
-
const group = letterGroups[parseInt(index)];
|
|
358
|
-
if (group && !isGroupSelected(group)) {
|
|
359
|
-
selectedLetters.value = [...group];
|
|
360
|
-
touchSelectedGroup = group;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
// #endif
|
|
365
|
-
|
|
366
|
-
// #ifdef APP-PLUS || MP-WEIXIN
|
|
367
|
-
// 小程序/APP端通过计算位置来判断
|
|
368
|
-
uni.createSelectorQuery()
|
|
369
|
-
.selectAll('.letter-index')
|
|
370
|
-
.boundingClientRect((rects) => {
|
|
371
|
-
if (!rects) return;
|
|
372
|
-
for (let i = 0; i < rects.length; i++) {
|
|
373
|
-
const rect = rects[i];
|
|
374
|
-
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
|
|
375
|
-
const group = letterGroups[i];
|
|
376
|
-
if (group && !isGroupSelected(group)) {
|
|
377
|
-
selectedLetters.value = [...group];
|
|
378
|
-
touchSelectedGroup = group;
|
|
379
|
-
}
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
})
|
|
384
|
-
.exec();
|
|
385
|
-
// #endif
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// 特殊字符(7个)
|
|
389
|
-
const specialChars = [
|
|
390
|
-
{ char: '警', type: 'white' },
|
|
391
|
-
{ char: '领', type: 'black' },
|
|
392
|
-
{ char: '港', type: 'black' },
|
|
393
|
-
{ char: '澳', type: 'black' },
|
|
394
|
-
{ char: '学', type: 'yellow' },
|
|
395
|
-
{ char: '挂', type: 'yellow' },
|
|
396
|
-
{ char: '使', type: 'black' },
|
|
397
|
-
];
|
|
398
|
-
|
|
399
|
-
// QWERTY键盘布局
|
|
400
|
-
const letterRows = [
|
|
401
|
-
['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
|
|
402
|
-
['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'],
|
|
403
|
-
['Z', 'X', 'C', 'V', 'B', 'N', 'M']
|
|
404
|
-
];
|
|
405
|
-
|
|
406
|
-
// 数字行
|
|
407
|
-
const numberRow = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
|
|
408
|
-
|
|
409
|
-
const plateLength = computed(() => isNewEnergy.value ? 8 : 7);
|
|
410
|
-
|
|
411
|
-
// 获取特殊字符类型
|
|
412
|
-
function getSpecialCharType(char) {
|
|
413
|
-
if (!char) return null;
|
|
414
|
-
const found = specialChars.find(item => item.char === char);
|
|
415
|
-
return found ? found.type : null;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// 解析车牌号为数组
|
|
419
|
-
const plateArray = computed(() => {
|
|
420
|
-
const value = props.modelValue;
|
|
421
|
-
if (!value) return [];
|
|
422
|
-
return value.split('').slice(0, plateLength.value);
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
// 判断前两位是否都是字母
|
|
426
|
-
const isFirstTwoLetters = computed(() => {
|
|
427
|
-
const arr = plateArray.value;
|
|
428
|
-
if (arr.length < 2) return false;
|
|
429
|
-
const letterRegex = /^[A-Z]$/;
|
|
430
|
-
return letterRegex.test(arr[0]) && letterRegex.test(arr[1]);
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
// 键盘标题
|
|
434
|
-
const keyboardTitle = computed(() => {
|
|
435
|
-
if (activeIndex.value === 0) {
|
|
436
|
-
return firstKeyboardMode.value === 'province' ? '选择省份' : '选择字母';
|
|
437
|
-
}
|
|
438
|
-
if (activeIndex.value === 1) return '选择字母';
|
|
439
|
-
if (activeIndex.value === 2 && isFirstTwoLetters.value) return '选择省份';
|
|
440
|
-
return '输入字符';
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// 第一位是否显示字母(通过切换按钮控制)
|
|
444
|
-
const showLetterInFirst = computed(() => {
|
|
445
|
-
return activeIndex.value === 0 && firstKeyboardMode.value === 'letter';
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
// 显示省份区域:第1位(省份模式),或前两位是字母时的第3位
|
|
449
|
-
const showProvinceSection = computed(() => {
|
|
450
|
-
if (activeIndex.value === 0) return firstKeyboardMode.value === 'province';
|
|
451
|
-
if (activeIndex.value === 2 && isFirstTwoLetters.value) return true;
|
|
452
|
-
return false;
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// 显示字母区域:第1位(字母模式)、第2位,或第3位及以后
|
|
456
|
-
const showLetterSection = computed(() => {
|
|
457
|
-
if (activeIndex.value === 0 && firstKeyboardMode.value === 'letter') return true; // 第1位切换到字母
|
|
458
|
-
if (activeIndex.value === 1) return true; // 第2位
|
|
459
|
-
if (activeIndex.value === 2 && isFirstTwoLetters.value) return false; // 军牌第3位是省份
|
|
460
|
-
if (activeIndex.value >= 2) return true; // 后面位置
|
|
461
|
-
return false;
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
// 显示数字区域:第1位(字母模式)、第2位、第3位及以后
|
|
465
|
-
const showNumberSection = computed(() => {
|
|
466
|
-
if (activeIndex.value === 0 && firstKeyboardMode.value === 'letter') return true; // 第1位切换到字母
|
|
467
|
-
if (activeIndex.value === 1) return true; // 第2位
|
|
468
|
-
if (activeIndex.value === 2 && isFirstTwoLetters.value) return false; // 军牌第3位是省份
|
|
469
|
-
return activeIndex.value >= 2;
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
// 显示特殊字符:第1位(字母模式)、第2位、第3位及以后
|
|
473
|
-
const showSpecialSection = computed(() => {
|
|
474
|
-
if (activeIndex.value === 0 && firstKeyboardMode.value === 'letter') return true; // 第1位切换到字母
|
|
475
|
-
if (activeIndex.value === 1) return true; // 第2位
|
|
476
|
-
if (activeIndex.value === 2 && isFirstTwoLetters.value) return false; // 军牌第3位是省份
|
|
477
|
-
return activeIndex.value >= 2;
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
// 是否禁用 I 和 O(只有第3位及以后才禁用,前两位不禁用)
|
|
481
|
-
const shouldDisableIO = computed(() => {
|
|
482
|
-
return activeIndex.value >= 2;
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
// 是否显示完成按钮(普通车牌第7位,或新能源第8位)
|
|
486
|
-
const showDoneButton = computed(() => {
|
|
487
|
-
// 普通车牌:在第7位(索引6)时显示
|
|
488
|
-
if (!isNewEnergy.value && activeIndex.value === 6) return true;
|
|
489
|
-
// 新能源:在第8位(索引7)时显示
|
|
490
|
-
if (isNewEnergy.value && activeIndex.value === 7) return true;
|
|
491
|
-
return false;
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
function showKeyboardAt(index) {
|
|
495
|
-
activeIndex.value = index;
|
|
496
|
-
// 重置第一位键盘模式
|
|
497
|
-
if (index === 0) {
|
|
498
|
-
firstKeyboardMode.value = 'province';
|
|
499
|
-
}
|
|
500
|
-
showKeyboard.value = true;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function toggleFirstKeyboard() {
|
|
504
|
-
firstKeyboardMode.value = firstKeyboardMode.value === 'province' ? 'letter' : 'province';
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
function handleNewEnergyClick() {
|
|
508
|
-
if (!isNewEnergy.value) {
|
|
509
|
-
// 未启用新能源,点击时启用并打开键盘
|
|
510
|
-
isNewEnergy.value = true;
|
|
511
|
-
}
|
|
512
|
-
showKeyboardAt(7);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function closeKeyboard() {
|
|
516
|
-
showKeyboard.value = false;
|
|
517
|
-
activeIndex.value = -1;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function inputChar(c) {
|
|
521
|
-
// 第3位及以后禁用 I 和 O
|
|
522
|
-
if (shouldDisableIO.value && (c === 'I' || c === 'O')) return;
|
|
523
|
-
|
|
524
|
-
const currentValue = props.modelValue;
|
|
525
|
-
let arr = currentValue.split('');
|
|
526
|
-
|
|
527
|
-
// 填充空位
|
|
528
|
-
while (arr.length < activeIndex.value) {
|
|
529
|
-
arr.push('');
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// 更新指定位置
|
|
533
|
-
arr[activeIndex.value] = c;
|
|
534
|
-
|
|
535
|
-
emit('update:modelValue', arr.join(''));
|
|
536
|
-
|
|
537
|
-
// 自动跳到下一位
|
|
538
|
-
if (activeIndex.value < 6) {
|
|
539
|
-
// 前6位,跳到下一位
|
|
540
|
-
activeIndex.value = activeIndex.value + 1;
|
|
541
|
-
} else if (activeIndex.value === 6) {
|
|
542
|
-
// 第7位输入完,自动启用新能源并跳到第8位
|
|
543
|
-
isNewEnergy.value = true;
|
|
544
|
-
activeIndex.value = 7;
|
|
545
|
-
} else if (activeIndex.value === 7) {
|
|
546
|
-
// 新能源第8位输入完成后自动关闭键盘
|
|
547
|
-
closeKeyboard();
|
|
548
|
-
if (arr.join('').length === 8) {
|
|
549
|
-
emit('complete', arr.join(''));
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function deleteChar() {
|
|
555
|
-
const currentValue = props.modelValue;
|
|
556
|
-
let arr = currentValue.split('');
|
|
557
|
-
|
|
558
|
-
if (activeIndex.value >= 0 && arr[activeIndex.value]) {
|
|
559
|
-
// 当前位有内容,删除当前位
|
|
560
|
-
arr[activeIndex.value] = '';
|
|
561
|
-
// 移除末尾空字符
|
|
562
|
-
while (arr.length > 0 && arr[arr.length - 1] === '') {
|
|
563
|
-
arr.pop();
|
|
564
|
-
}
|
|
565
|
-
emit('update:modelValue', arr.join(''));
|
|
566
|
-
} else if (activeIndex.value > 0) {
|
|
567
|
-
// 当前位无内容,删除前一位并跳转
|
|
568
|
-
const prevIndex = activeIndex.value - 1;
|
|
569
|
-
arr[prevIndex] = '';
|
|
570
|
-
while (arr.length > 0 && arr[arr.length - 1] === '') {
|
|
571
|
-
arr.pop();
|
|
572
|
-
}
|
|
573
|
-
emit('update:modelValue', arr.join(''));
|
|
574
|
-
activeIndex.value = prevIndex;
|
|
575
|
-
} else if (activeIndex.value === 0 && arr[0]) {
|
|
576
|
-
emit('update:modelValue', '');
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// 监听新能源切换,截断
|
|
581
|
-
watch(isNewEnergy, (val) => {
|
|
582
|
-
if (!val && props.modelValue.length > 7) {
|
|
583
|
-
emit('update:modelValue', props.modelValue.slice(0, 7));
|
|
584
|
-
}
|
|
585
|
-
});
|
|
586
|
-
</script>
|
|
587
|
-
|
|
588
|
-
<style lang="scss">
|
|
589
|
-
// 业务固定色定义
|
|
590
|
-
$plate-new-energy: #00B42A; // 新能源车牌绿色
|
|
591
|
-
$plate-new-energy-light: #00D68F;
|
|
592
|
-
$plate-yellow: #FADC19; // 黄牌
|
|
593
|
-
$plate-yellow-dark: #E6C900;
|
|
594
|
-
$plate-black: #1D2129; // 黑牌
|
|
595
|
-
$plate-white: #FFFFFF; // 白牌
|
|
596
|
-
|
|
597
|
-
.prism-license-plate-input {
|
|
598
|
-
.plate-boxes {
|
|
599
|
-
display: flex;
|
|
600
|
-
align-items: flex-end;
|
|
601
|
-
gap: 8rpx;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
.plate-box {
|
|
605
|
-
width: 72rpx;
|
|
606
|
-
height: 88rpx;
|
|
607
|
-
background: var(--prism-input-bg, #EBEEF2);
|
|
608
|
-
border-radius: 12rpx;
|
|
609
|
-
display: flex;
|
|
610
|
-
align-items: center;
|
|
611
|
-
justify-content: center;
|
|
612
|
-
border: 2rpx solid transparent;
|
|
613
|
-
transition: all 0.2s ease;
|
|
614
|
-
|
|
615
|
-
&.active {
|
|
616
|
-
border-color: var(--prism-primary-color, #3478F6);
|
|
617
|
-
background: var(--prism-primary-light, rgba(52, 120, 246, 0.08));
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
&.filled {
|
|
621
|
-
background: var(--prism-primary-light, rgba(52, 120, 246, 0.08));
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
&.province, &.letter {
|
|
625
|
-
width: 80rpx;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// 新能源车牌样式(业务固定色)
|
|
629
|
-
&.new-energy {
|
|
630
|
-
background: rgba($plate-new-energy, 0.15);
|
|
631
|
-
border: 2rpx solid $plate-new-energy;
|
|
632
|
-
|
|
633
|
-
&.active {
|
|
634
|
-
border-color: $plate-new-energy;
|
|
635
|
-
box-shadow: 0 0 0 4rpx rgba($plate-new-energy, 0.3);
|
|
636
|
-
background: rgba($plate-new-energy, 0.2);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
&.filled {
|
|
640
|
-
background: linear-gradient(135deg, $plate-new-energy 0%, $plate-new-energy-light 100%);
|
|
641
|
-
|
|
642
|
-
.plate-text {
|
|
643
|
-
color: $plate-white;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
.plate-text {
|
|
648
|
-
color: $plate-new-energy;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
&.new-energy-optional {
|
|
653
|
-
background: transparent;
|
|
654
|
-
border: 2rpx dashed $plate-new-energy;
|
|
655
|
-
|
|
656
|
-
.optional-icon {
|
|
657
|
-
font-size: 28rpx;
|
|
658
|
-
color: $plate-new-energy;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
&:active {
|
|
662
|
-
background: rgba($plate-new-energy, 0.1);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
.plate-text {
|
|
667
|
-
font-size: 36rpx;
|
|
668
|
-
font-weight: 600;
|
|
669
|
-
color: var(--prism-text-primary, #1D2129);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// 特殊车牌字符输入框样式(业务固定色)
|
|
673
|
-
&.special-white {
|
|
674
|
-
background: $plate-white;
|
|
675
|
-
border: 2rpx solid var(--prism-border-color-light, #E5E6EB);
|
|
676
|
-
|
|
677
|
-
&.active {
|
|
678
|
-
border-color: var(--prism-primary-color, #3478F6);
|
|
679
|
-
box-shadow: 0 0 0 4rpx rgba(52, 120, 246, 0.3);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
.plate-text {
|
|
683
|
-
color: $plate-black;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
&.special-black {
|
|
688
|
-
background: $plate-black;
|
|
689
|
-
border: 2rpx solid $plate-black;
|
|
690
|
-
|
|
691
|
-
&.active {
|
|
692
|
-
border-color: var(--prism-primary-color, #3478F6);
|
|
693
|
-
box-shadow: 0 0 0 4rpx rgba(52, 120, 246, 0.3);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
.plate-text {
|
|
697
|
-
color: $plate-white;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
&.special-yellow {
|
|
702
|
-
background: $plate-yellow;
|
|
703
|
-
border: 2rpx solid $plate-yellow-dark;
|
|
704
|
-
|
|
705
|
-
&.active {
|
|
706
|
-
border-color: var(--prism-primary-color, #3478F6);
|
|
707
|
-
box-shadow: 0 0 0 4rpx rgba(52, 120, 246, 0.3);
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
.plate-text {
|
|
711
|
-
color: $plate-black;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
.plate-dot {
|
|
717
|
-
width: 12rpx;
|
|
718
|
-
height: 12rpx;
|
|
719
|
-
background: var(--prism-text-secondary, #86909C);
|
|
720
|
-
border-radius: 50%;
|
|
721
|
-
margin: 0 8rpx;
|
|
722
|
-
margin-bottom: 38rpx;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
.plate-box-wrapper {
|
|
726
|
-
display: flex;
|
|
727
|
-
flex-direction: column;
|
|
728
|
-
align-items: center;
|
|
729
|
-
|
|
730
|
-
&.new-energy-wrapper {
|
|
731
|
-
.new-energy-label {
|
|
732
|
-
font-size: 18rpx;
|
|
733
|
-
color: $plate-new-energy;
|
|
734
|
-
font-weight: 500;
|
|
735
|
-
margin-bottom: 6rpx;
|
|
736
|
-
white-space: nowrap;
|
|
737
|
-
|
|
738
|
-
&.hidden {
|
|
739
|
-
visibility: hidden;
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
.energy-switch {
|
|
746
|
-
display: flex;
|
|
747
|
-
align-items: center;
|
|
748
|
-
gap: 12rpx;
|
|
749
|
-
margin-top: 24rpx;
|
|
750
|
-
|
|
751
|
-
.energy-text {
|
|
752
|
-
font-size: 28rpx;
|
|
753
|
-
color: var(--prism-text-secondary, #86909C);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
.keyboard-modal {
|
|
758
|
-
position: fixed;
|
|
759
|
-
left: 0;
|
|
760
|
-
right: 0;
|
|
761
|
-
bottom: 0;
|
|
762
|
-
z-index: 9999;
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
.keyboard-content {
|
|
766
|
-
width: 100%;
|
|
767
|
-
max-height: 70vh;
|
|
768
|
-
background: var(--prism-bg-color-card, #FFFFFF);
|
|
769
|
-
border-radius: 24rpx 24rpx 0 0;
|
|
770
|
-
padding: 24rpx;
|
|
771
|
-
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
772
|
-
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
|
|
773
|
-
display: flex;
|
|
774
|
-
flex-direction: column;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
.keyboard-body {
|
|
778
|
-
flex: 1;
|
|
779
|
-
overflow-y: auto;
|
|
780
|
-
height: 480rpx; // 统一键盘高度:5行 * (80rpx高度 + 12rpx间距) + 余量
|
|
781
|
-
min-height: 480rpx;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
.keyboard-header {
|
|
785
|
-
display: flex;
|
|
786
|
-
justify-content: space-between;
|
|
787
|
-
align-items: center;
|
|
788
|
-
margin-bottom: 24rpx;
|
|
789
|
-
|
|
790
|
-
.header-left {
|
|
791
|
-
display: flex;
|
|
792
|
-
align-items: center;
|
|
793
|
-
gap: 16rpx;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
.keyboard-title {
|
|
797
|
-
font-size: 32rpx;
|
|
798
|
-
font-weight: 600;
|
|
799
|
-
color: var(--prism-text-primary, #1D2129);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
.switch-btn {
|
|
803
|
-
padding: 6rpx 12rpx;
|
|
804
|
-
background: var(--prism-primary-color, #3478F6);
|
|
805
|
-
border-radius: 6rpx;
|
|
806
|
-
|
|
807
|
-
text {
|
|
808
|
-
font-size: 22rpx;
|
|
809
|
-
color: $plate-white;
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
&:active {
|
|
813
|
-
opacity: 0.8;
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
.keyboard-close {
|
|
818
|
-
font-size: 36rpx;
|
|
819
|
-
color: var(--prism-text-secondary, #86909C);
|
|
820
|
-
padding: 8rpx;
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
.keyboard-section {
|
|
825
|
-
margin-bottom: 20rpx;
|
|
826
|
-
|
|
827
|
-
.section-title {
|
|
828
|
-
font-size: 24rpx;
|
|
829
|
-
color: var(--prism-text-secondary, #86909C);
|
|
830
|
-
margin-bottom: 12rpx;
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
.keyboard-qwerty {
|
|
835
|
-
display: flex;
|
|
836
|
-
flex-direction: column;
|
|
837
|
-
gap: 12rpx;
|
|
838
|
-
|
|
839
|
-
// 省份键盘样式
|
|
840
|
-
&.province-qwerty .keyboard-row {
|
|
841
|
-
justify-content: flex-start;
|
|
842
|
-
|
|
843
|
-
.key-item {
|
|
844
|
-
flex: 1;
|
|
845
|
-
max-width: none;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
.keyboard-row {
|
|
851
|
-
display: flex;
|
|
852
|
-
justify-content: center;
|
|
853
|
-
gap: 8rpx;
|
|
854
|
-
|
|
855
|
-
&.number-row {
|
|
856
|
-
justify-content: flex-start;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
&.special-row {
|
|
860
|
-
justify-content: flex-start;
|
|
861
|
-
flex-wrap: wrap;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// 字母索引行
|
|
865
|
-
&.letter-index-row {
|
|
866
|
-
justify-content: flex-start;
|
|
867
|
-
width: 100%;
|
|
868
|
-
margin-bottom: 12rpx;
|
|
869
|
-
|
|
870
|
-
.key-item {
|
|
871
|
-
flex: 1;
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
.key-item {
|
|
877
|
-
min-width: 56rpx;
|
|
878
|
-
height: 80rpx;
|
|
879
|
-
padding: 0 12rpx;
|
|
880
|
-
background: var(--prism-input-bg, #EBEEF2);
|
|
881
|
-
border-radius: 12rpx;
|
|
882
|
-
display: flex;
|
|
883
|
-
align-items: center;
|
|
884
|
-
justify-content: center;
|
|
885
|
-
font-size: 32rpx;
|
|
886
|
-
font-weight: 500;
|
|
887
|
-
color: var(--prism-text-primary, #1D2129);
|
|
888
|
-
transition: all 0.15s ease;
|
|
889
|
-
flex: 1;
|
|
890
|
-
max-width: 72rpx;
|
|
891
|
-
|
|
892
|
-
&:active {
|
|
893
|
-
background: var(--prism-primary-color, #3478F6);
|
|
894
|
-
color: $plate-white;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
&.disabled {
|
|
898
|
-
opacity: 0.3;
|
|
899
|
-
pointer-events: none;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// 字母索引按钮样式
|
|
903
|
-
&.letter-index {
|
|
904
|
-
font-size: 24rpx;
|
|
905
|
-
background: var(--prism-bg-color, #F7F8FA);
|
|
906
|
-
color: var(--prism-text-secondary, #86909C);
|
|
907
|
-
max-width: none;
|
|
908
|
-
min-width: auto;
|
|
909
|
-
padding: 0 8rpx;
|
|
910
|
-
|
|
911
|
-
&.active {
|
|
912
|
-
background: var(--prism-primary-color, #3478F6);
|
|
913
|
-
color: $plate-white;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
// 省份高亮样式(拼音首字母匹配)
|
|
918
|
-
&.highlight {
|
|
919
|
-
background: var(--prism-primary-color, #3478F6);
|
|
920
|
-
color: $plate-white;
|
|
921
|
-
box-shadow: 0 0 0 4rpx rgba(52, 120, 246, 0.3);
|
|
922
|
-
transform: scale(1.05);
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
// 删除按钮样式
|
|
926
|
-
&.delete {
|
|
927
|
-
background: var(--prism-danger-color, #F53F3F);
|
|
928
|
-
color: $plate-white;
|
|
929
|
-
min-width: 120rpx;
|
|
930
|
-
max-width: 120rpx;
|
|
931
|
-
flex: none;
|
|
932
|
-
|
|
933
|
-
.delete-text {
|
|
934
|
-
font-size: 28rpx;
|
|
935
|
-
font-weight: 500;
|
|
936
|
-
color: $plate-white;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
&:active {
|
|
940
|
-
background: var(--prism-danger-color-active, #D93636);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// 完成按钮样式
|
|
945
|
-
&.done {
|
|
946
|
-
background: var(--prism-primary-color, #3478F6);
|
|
947
|
-
color: $plate-white;
|
|
948
|
-
flex: 1;
|
|
949
|
-
min-width: 80rpx;
|
|
950
|
-
max-width: none;
|
|
951
|
-
|
|
952
|
-
.done-text {
|
|
953
|
-
font-size: 28rpx;
|
|
954
|
-
font-weight: 500;
|
|
955
|
-
color: $plate-white;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
&:active {
|
|
959
|
-
background: var(--prism-primary-color-active, #2563EB);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// 特殊车牌字符样式(业务固定色)
|
|
964
|
-
&.special {
|
|
965
|
-
font-weight: 600;
|
|
966
|
-
flex: none;
|
|
967
|
-
min-width: 72rpx;
|
|
968
|
-
max-width: 72rpx;
|
|
969
|
-
|
|
970
|
-
&.black {
|
|
971
|
-
background: $plate-black;
|
|
972
|
-
color: $plate-white;
|
|
973
|
-
|
|
974
|
-
&:active {
|
|
975
|
-
background: #000000;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
&.white {
|
|
980
|
-
background: $plate-white;
|
|
981
|
-
color: $plate-black;
|
|
982
|
-
border: 2rpx solid var(--prism-border-color-light, #E5E6EB);
|
|
983
|
-
|
|
984
|
-
&:active {
|
|
985
|
-
background: var(--prism-bg-color, #F2F3F5);
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
&.yellow {
|
|
990
|
-
background: $plate-yellow;
|
|
991
|
-
color: $plate-black;
|
|
992
|
-
|
|
993
|
-
&:active {
|
|
994
|
-
background: $plate-yellow-dark;
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
.dark-mode .prism-license-plate-input {
|
|
1002
|
-
.plate-box {
|
|
1003
|
-
background: var(--prism-input-bg, #2A2A2A);
|
|
1004
|
-
|
|
1005
|
-
&.active {
|
|
1006
|
-
background: rgba(52, 120, 246, 0.15);
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
&.filled {
|
|
1010
|
-
background: rgba(52, 120, 246, 0.15);
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
.plate-text {
|
|
1014
|
-
color: var(--prism-text-primary, #E5E6EB);
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// 深色模式特殊字符样式
|
|
1018
|
-
&.special-white {
|
|
1019
|
-
background: rgba(255, 255, 255, 0.9);
|
|
1020
|
-
border: 2rpx solid rgba(255, 255, 255, 0.3);
|
|
1021
|
-
|
|
1022
|
-
.plate-text {
|
|
1023
|
-
color: #1D2129;
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
&.special-black {
|
|
1028
|
-
background: #000000;
|
|
1029
|
-
border: 2rpx solid #333333;
|
|
1030
|
-
|
|
1031
|
-
.plate-text {
|
|
1032
|
-
color: #FFFFFF;
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
&.special-yellow {
|
|
1037
|
-
background: #FADC19;
|
|
1038
|
-
border: 2rpx solid #E6C900;
|
|
1039
|
-
|
|
1040
|
-
.plate-text {
|
|
1041
|
-
color: #1D2129;
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
.keyboard-content {
|
|
1047
|
-
background: var(--prism-bg-color-card, #242424);
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
.keyboard-header .keyboard-title {
|
|
1051
|
-
color: var(--prism-text-primary, #E5E6EB);
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
.section-title {
|
|
1055
|
-
color: var(--prism-text-secondary, #6B7785);
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
.key-item {
|
|
1059
|
-
background: var(--prism-input-bg, #2A2A2A);
|
|
1060
|
-
color: var(--prism-text-primary, #E5E6EB);
|
|
1061
|
-
|
|
1062
|
-
// 字母索引按钮深色模式
|
|
1063
|
-
&.letter-index {
|
|
1064
|
-
background: rgba(255, 255, 255, 0.08);
|
|
1065
|
-
color: var(--prism-text-secondary, #6B7785);
|
|
1066
|
-
|
|
1067
|
-
&.active {
|
|
1068
|
-
background: var(--prism-primary-color, #3478F6);
|
|
1069
|
-
color: #FFFFFF;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
// 省份高亮保持蓝色
|
|
1074
|
-
&.highlight {
|
|
1075
|
-
background: var(--prism-primary-color, #3478F6);
|
|
1076
|
-
color: #FFFFFF;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// 删除按钮保持红色
|
|
1080
|
-
&.delete {
|
|
1081
|
-
background: var(--prism-danger-color, #F53F3F);
|
|
1082
|
-
color: #FFFFFF;
|
|
1083
|
-
|
|
1084
|
-
.delete-text {
|
|
1085
|
-
color: #FFFFFF;
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// 完成按钮保持蓝色
|
|
1090
|
-
&.done {
|
|
1091
|
-
background: var(--prism-primary-color, #3478F6);
|
|
1092
|
-
color: #FFFFFF;
|
|
1093
|
-
|
|
1094
|
-
.done-text {
|
|
1095
|
-
color: #FFFFFF;
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<view class="prism-license-plate-input" :class="{ 'dark-mode': appStore.isDarkMode }">
|
|
3
|
+
<!-- 车牌号输入框 -->
|
|
4
|
+
<view class="plate-boxes">
|
|
5
|
+
<!-- 第1位 -->
|
|
6
|
+
<view
|
|
7
|
+
class="plate-box province"
|
|
8
|
+
:class="{
|
|
9
|
+
'active': activeIndex === 0,
|
|
10
|
+
'filled': plateArray[0],
|
|
11
|
+
'special-white': getSpecialCharType(plateArray[0]) === 'white',
|
|
12
|
+
'special-black': getSpecialCharType(plateArray[0]) === 'black',
|
|
13
|
+
'special-yellow': getSpecialCharType(plateArray[0]) === 'yellow'
|
|
14
|
+
}"
|
|
15
|
+
@click="showKeyboardAt(0)"
|
|
16
|
+
>
|
|
17
|
+
<text class="plate-text">{{ plateArray[0] || '' }}</text>
|
|
18
|
+
</view>
|
|
19
|
+
|
|
20
|
+
<!-- 第2位 -->
|
|
21
|
+
<view
|
|
22
|
+
class="plate-box letter"
|
|
23
|
+
:class="{
|
|
24
|
+
'active': activeIndex === 1,
|
|
25
|
+
'filled': plateArray[1],
|
|
26
|
+
'special-white': getSpecialCharType(plateArray[1]) === 'white',
|
|
27
|
+
'special-black': getSpecialCharType(plateArray[1]) === 'black',
|
|
28
|
+
'special-yellow': getSpecialCharType(plateArray[1]) === 'yellow'
|
|
29
|
+
}"
|
|
30
|
+
@click="showKeyboardAt(1)"
|
|
31
|
+
>
|
|
32
|
+
<text class="plate-text">{{ plateArray[1] || '' }}</text>
|
|
33
|
+
</view>
|
|
34
|
+
|
|
35
|
+
<!-- 分隔点 -->
|
|
36
|
+
<view class="plate-dot"></view>
|
|
37
|
+
|
|
38
|
+
<!-- 后5位 -->
|
|
39
|
+
<view
|
|
40
|
+
v-for="i in 5"
|
|
41
|
+
:key="i"
|
|
42
|
+
class="plate-box"
|
|
43
|
+
:class="{
|
|
44
|
+
'active': activeIndex === i + 1,
|
|
45
|
+
'filled': plateArray[i + 1],
|
|
46
|
+
'special-white': getSpecialCharType(plateArray[i + 1]) === 'white',
|
|
47
|
+
'special-black': getSpecialCharType(plateArray[i + 1]) === 'black',
|
|
48
|
+
'special-yellow': getSpecialCharType(plateArray[i + 1]) === 'yellow'
|
|
49
|
+
}"
|
|
50
|
+
@click="showKeyboardAt(i + 1)"
|
|
51
|
+
>
|
|
52
|
+
<text class="plate-text">{{ plateArray[i + 1] || '' }}</text>
|
|
53
|
+
</view>
|
|
54
|
+
|
|
55
|
+
<!-- 新能源第8位(始终显示,可选) -->
|
|
56
|
+
<view class="plate-box-wrapper new-energy-wrapper">
|
|
57
|
+
<text class="new-energy-label" :class="{ 'hidden': getSpecialCharType(plateArray[7]) }">新能源</text>
|
|
58
|
+
<view
|
|
59
|
+
class="plate-box"
|
|
60
|
+
:class="{
|
|
61
|
+
'new-energy': isNewEnergy && !getSpecialCharType(plateArray[7]),
|
|
62
|
+
'new-energy-optional': !isNewEnergy,
|
|
63
|
+
'active': activeIndex === 7,
|
|
64
|
+
'filled': plateArray[7],
|
|
65
|
+
'special-white': getSpecialCharType(plateArray[7]) === 'white',
|
|
66
|
+
'special-black': getSpecialCharType(plateArray[7]) === 'black',
|
|
67
|
+
'special-yellow': getSpecialCharType(plateArray[7]) === 'yellow'
|
|
68
|
+
}"
|
|
69
|
+
@click="handleNewEnergyClick"
|
|
70
|
+
>
|
|
71
|
+
<text class="plate-text" v-if="isNewEnergy">{{ plateArray[7] || '' }}</text>
|
|
72
|
+
<text class="optional-icon fa fa-plus" v-else></text>
|
|
73
|
+
</view>
|
|
74
|
+
</view>
|
|
75
|
+
</view>
|
|
76
|
+
|
|
77
|
+
<!-- 新能源切换(隐藏,通过点击第8位框自动启用) -->
|
|
78
|
+
<!-- <view class="energy-switch" @click="isNewEnergy = !isNewEnergy">
|
|
79
|
+
<view class="prism-checkbox" :class="{ checked: isNewEnergy }">
|
|
80
|
+
<text v-if="isNewEnergy" class="check-icon fa fa-check"></text>
|
|
81
|
+
</view>
|
|
82
|
+
<text class="energy-text">新能源车牌</text>
|
|
83
|
+
</view> -->
|
|
84
|
+
|
|
85
|
+
<!-- 统一键盘(底部弹出,无遮罩) -->
|
|
86
|
+
<view class="keyboard-modal" v-if="showKeyboard">
|
|
87
|
+
<view class="keyboard-content">
|
|
88
|
+
<view class="keyboard-header">
|
|
89
|
+
<!-- 左侧:标题 + 切换按钮 -->
|
|
90
|
+
<view class="header-left">
|
|
91
|
+
<text class="keyboard-title">{{ keyboardTitle }}</text>
|
|
92
|
+
<view
|
|
93
|
+
v-if="activeIndex === 0"
|
|
94
|
+
class="switch-btn"
|
|
95
|
+
@click="toggleFirstKeyboard"
|
|
96
|
+
>
|
|
97
|
+
<text>{{ firstKeyboardMode === 'province' ? '切换字母' : '切换省份' }}</text>
|
|
98
|
+
</view>
|
|
99
|
+
</view>
|
|
100
|
+
<!-- 右侧:关闭按钮 -->
|
|
101
|
+
<text class="keyboard-close fa fa-times" @click="closeKeyboard"></text>
|
|
102
|
+
</view>
|
|
103
|
+
|
|
104
|
+
<scroll-view class="keyboard-body" scroll-y>
|
|
105
|
+
<!-- 省份区域 -->
|
|
106
|
+
<view class="keyboard-section" v-if="showProvinceSection && !showLetterInFirst">
|
|
107
|
+
<!-- 字母索引栏(支持滑动选择) -->
|
|
108
|
+
<view
|
|
109
|
+
class="keyboard-row letter-index-row"
|
|
110
|
+
@touchstart="onLetterIndexTouchStart"
|
|
111
|
+
@touchmove="onLetterIndexTouchMove"
|
|
112
|
+
@touchend="onLetterIndexTouchEnd"
|
|
113
|
+
>
|
|
114
|
+
<view
|
|
115
|
+
v-for="(group, idx) in letterGroups"
|
|
116
|
+
:key="idx"
|
|
117
|
+
class="key-item letter-index"
|
|
118
|
+
:class="{ 'active': isGroupSelected(group) }"
|
|
119
|
+
:data-index="idx"
|
|
120
|
+
@click="selectLetterGroup(group)"
|
|
121
|
+
>
|
|
122
|
+
{{ group.join('/') }}
|
|
123
|
+
</view>
|
|
124
|
+
</view>
|
|
125
|
+
<!-- 省份键盘(4行) -->
|
|
126
|
+
<view class="keyboard-qwerty province-qwerty">
|
|
127
|
+
<view class="keyboard-row">
|
|
128
|
+
<view
|
|
129
|
+
v-for="p in provinces.slice(0, 8)"
|
|
130
|
+
:key="p"
|
|
131
|
+
class="key-item"
|
|
132
|
+
:class="{ 'highlight': isProvinceHighlight(p) }"
|
|
133
|
+
@click="inputChar(p)"
|
|
134
|
+
>
|
|
135
|
+
{{ p }}
|
|
136
|
+
</view>
|
|
137
|
+
</view>
|
|
138
|
+
<view class="keyboard-row">
|
|
139
|
+
<view
|
|
140
|
+
v-for="p in provinces.slice(8, 16)"
|
|
141
|
+
:key="p"
|
|
142
|
+
class="key-item"
|
|
143
|
+
:class="{ 'highlight': isProvinceHighlight(p) }"
|
|
144
|
+
@click="inputChar(p)"
|
|
145
|
+
>
|
|
146
|
+
{{ p }}
|
|
147
|
+
</view>
|
|
148
|
+
</view>
|
|
149
|
+
<view class="keyboard-row">
|
|
150
|
+
<view
|
|
151
|
+
v-for="p in provinces.slice(16, 24)"
|
|
152
|
+
:key="p"
|
|
153
|
+
class="key-item"
|
|
154
|
+
:class="{ 'highlight': isProvinceHighlight(p) }"
|
|
155
|
+
@click="inputChar(p)"
|
|
156
|
+
>
|
|
157
|
+
{{ p }}
|
|
158
|
+
</view>
|
|
159
|
+
</view>
|
|
160
|
+
<view class="keyboard-row">
|
|
161
|
+
<view
|
|
162
|
+
v-for="p in provinces.slice(24, 31)"
|
|
163
|
+
:key="p"
|
|
164
|
+
class="key-item"
|
|
165
|
+
:class="{ 'highlight': isProvinceHighlight(p) }"
|
|
166
|
+
@click="inputChar(p)"
|
|
167
|
+
>
|
|
168
|
+
{{ p }}
|
|
169
|
+
</view>
|
|
170
|
+
<!-- 删除按钮 -->
|
|
171
|
+
<view class="key-item delete" @click="deleteChar">
|
|
172
|
+
<text class="delete-text">删除</text>
|
|
173
|
+
</view>
|
|
174
|
+
</view>
|
|
175
|
+
</view>
|
|
176
|
+
</view>
|
|
177
|
+
|
|
178
|
+
<!-- 数字区域(放在字母上面) -->
|
|
179
|
+
<view class="keyboard-section" v-if="showNumberSection">
|
|
180
|
+
<view class="keyboard-row number-row">
|
|
181
|
+
<view
|
|
182
|
+
v-for="n in numberRow"
|
|
183
|
+
:key="n"
|
|
184
|
+
class="key-item"
|
|
185
|
+
@click="inputChar(n)"
|
|
186
|
+
>
|
|
187
|
+
{{ n }}
|
|
188
|
+
</view>
|
|
189
|
+
</view>
|
|
190
|
+
</view>
|
|
191
|
+
|
|
192
|
+
<!-- 字母区域 -->
|
|
193
|
+
<view class="keyboard-section" v-if="showLetterSection || showLetterInFirst">
|
|
194
|
+
<view class="keyboard-qwerty">
|
|
195
|
+
<view class="keyboard-row" v-for="(row, idx) in letterRows" :key="idx">
|
|
196
|
+
<view
|
|
197
|
+
v-for="l in row"
|
|
198
|
+
:key="l"
|
|
199
|
+
class="key-item"
|
|
200
|
+
:class="{ disabled: shouldDisableIO && (l === 'I' || l === 'O') }"
|
|
201
|
+
@click="inputChar(l)"
|
|
202
|
+
>
|
|
203
|
+
{{ l }}
|
|
204
|
+
</view>
|
|
205
|
+
<!-- 删除按钮放在最后一行 -->
|
|
206
|
+
<view v-if="idx === 2" class="key-item delete" @click="deleteChar">
|
|
207
|
+
<text class="delete-text">删除</text>
|
|
208
|
+
</view>
|
|
209
|
+
</view>
|
|
210
|
+
</view>
|
|
211
|
+
</view>
|
|
212
|
+
|
|
213
|
+
<!-- 特殊字符区域 -->
|
|
214
|
+
<view class="keyboard-section" v-if="showSpecialSection">
|
|
215
|
+
<view class="keyboard-row special-row">
|
|
216
|
+
<view
|
|
217
|
+
v-for="item in specialChars"
|
|
218
|
+
:key="item.char"
|
|
219
|
+
class="key-item special"
|
|
220
|
+
:class="item.type"
|
|
221
|
+
@click="inputChar(item.char)"
|
|
222
|
+
>
|
|
223
|
+
{{ item.char }}
|
|
224
|
+
</view>
|
|
225
|
+
<!-- 最后一位显示完成按钮 -->
|
|
226
|
+
<view v-if="showDoneButton" class="key-item done" @click="closeKeyboard">
|
|
227
|
+
<text class="done-text">完成</text>
|
|
228
|
+
</view>
|
|
229
|
+
</view>
|
|
230
|
+
</view>
|
|
231
|
+
</scroll-view>
|
|
232
|
+
</view>
|
|
233
|
+
</view>
|
|
234
|
+
</view>
|
|
235
|
+
</template>
|
|
236
|
+
|
|
237
|
+
<script setup>
|
|
238
|
+
import { ref, computed, watch } from 'vue';
|
|
239
|
+
import { useAppStore } from '@/store/app';
|
|
240
|
+
|
|
241
|
+
const props = defineProps({
|
|
242
|
+
modelValue: {
|
|
243
|
+
type: String,
|
|
244
|
+
default: ''
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const emit = defineEmits(['update:modelValue', 'complete']);
|
|
249
|
+
|
|
250
|
+
const appStore = useAppStore();
|
|
251
|
+
|
|
252
|
+
const isNewEnergy = ref(false);
|
|
253
|
+
const showKeyboard = ref(false);
|
|
254
|
+
const activeIndex = ref(-1);
|
|
255
|
+
const firstKeyboardMode = ref('province'); // 'province' or 'letter'
|
|
256
|
+
|
|
257
|
+
// 省份简称(31个)
|
|
258
|
+
const provinces = [
|
|
259
|
+
'京', '津', '沪', '渝', '冀', '豫', '云', '辽', '黑', '湘',
|
|
260
|
+
'皖', '鲁', '新', '苏', '浙', '赣', '鄂', '桂', '甘', '晋',
|
|
261
|
+
'蒙', '陕', '吉', '闽', '贵', '粤', '川', '青', '藏', '琼', '宁'
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
// 省份拼音首字母映射(用于字母检索高亮)
|
|
265
|
+
const provincePinyinMap = {
|
|
266
|
+
'京': 'J', '津': 'J', '沪': 'H', '渝': 'Y', '冀': 'J',
|
|
267
|
+
'豫': 'Y', '云': 'Y', '辽': 'L', '黑': 'H', '湘': 'X',
|
|
268
|
+
'皖': 'W', '鲁': 'L', '新': 'X', '苏': 'S', '浙': 'Z',
|
|
269
|
+
'赣': 'G', '鄂': 'E', '桂': 'G', '甘': 'G', '晋': 'J',
|
|
270
|
+
'蒙': 'M', '陕': 'S', '吉': 'J', '闽': 'M', '贵': 'G',
|
|
271
|
+
'粤': 'Y', '川': 'C', '青': 'Q', '藏': 'Z', '琼': 'Q', '宁': 'N'
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// 省份字母索引(两个字母一组,方便筛选)
|
|
275
|
+
const letterGroups = [
|
|
276
|
+
['C', 'E'], // 川、鄂
|
|
277
|
+
['G', 'H'], // 赣桂甘贵、沪黑
|
|
278
|
+
['J', 'L'], // 京津冀晋吉、辽鲁
|
|
279
|
+
['M', 'N'], // 蒙闽、宁
|
|
280
|
+
['Q', 'S'], // 青琼、苏陕
|
|
281
|
+
['W', 'X'], // 皖、新湘
|
|
282
|
+
['Y', 'Z'], // 渝豫云粤、浙藏
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
// 当前选中的字母组
|
|
286
|
+
const selectedLetters = ref([]);
|
|
287
|
+
|
|
288
|
+
// 选择字母组(单选)
|
|
289
|
+
function selectLetterGroup(group) {
|
|
290
|
+
// 如果是触摸事件刚选中的同一个组,跳过click处理
|
|
291
|
+
if (touchSelectedGroup && touchSelectedGroup.every(l => group.includes(l))) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 检查是否已选中这个组
|
|
296
|
+
const isSelected = group.every(l => selectedLetters.value.includes(l));
|
|
297
|
+
if (isSelected) {
|
|
298
|
+
// 取消选中
|
|
299
|
+
selectedLetters.value = [];
|
|
300
|
+
} else {
|
|
301
|
+
// 选中(替换)
|
|
302
|
+
selectedLetters.value = [...group];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 判断字母组是否选中
|
|
307
|
+
function isGroupSelected(group) {
|
|
308
|
+
return group.every(l => selectedLetters.value.includes(l));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 清除字母筛选
|
|
312
|
+
function clearLetterFilter() {
|
|
313
|
+
selectedLetters.value = [];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 判断省份是否应该高亮
|
|
317
|
+
function isProvinceHighlight(province) {
|
|
318
|
+
if (selectedLetters.value.length === 0) return false;
|
|
319
|
+
return selectedLetters.value.includes(provincePinyinMap[province]);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// 字母索引滑动选择相关
|
|
323
|
+
let isTouchingLetterIndex = false;
|
|
324
|
+
let touchSelectedGroup = null; // 记录触摸选中的组,防止click重复处理
|
|
325
|
+
|
|
326
|
+
function onLetterIndexTouchStart(e) {
|
|
327
|
+
isTouchingLetterIndex = true;
|
|
328
|
+
touchSelectedGroup = null;
|
|
329
|
+
const touch = e.touches[0];
|
|
330
|
+
updateLetterIndexByTouch(touch);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function onLetterIndexTouchMove(e) {
|
|
334
|
+
if (!isTouchingLetterIndex) return;
|
|
335
|
+
const touch = e.touches[0];
|
|
336
|
+
updateLetterIndexByTouch(touch);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function onLetterIndexTouchEnd() {
|
|
340
|
+
isTouchingLetterIndex = false;
|
|
341
|
+
// 延迟清除,让click事件能检测到
|
|
342
|
+
setTimeout(() => {
|
|
343
|
+
touchSelectedGroup = null;
|
|
344
|
+
}, 100);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function updateLetterIndexByTouch(touch) {
|
|
348
|
+
const x = touch.clientX;
|
|
349
|
+
const y = touch.clientY;
|
|
350
|
+
|
|
351
|
+
// #ifdef H5
|
|
352
|
+
// H5端使用 document.elementFromPoint
|
|
353
|
+
const element = document.elementFromPoint(x, y);
|
|
354
|
+
if (element && element.classList.contains('letter-index')) {
|
|
355
|
+
const index = element.dataset.index;
|
|
356
|
+
if (index !== undefined) {
|
|
357
|
+
const group = letterGroups[parseInt(index)];
|
|
358
|
+
if (group && !isGroupSelected(group)) {
|
|
359
|
+
selectedLetters.value = [...group];
|
|
360
|
+
touchSelectedGroup = group;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// #endif
|
|
365
|
+
|
|
366
|
+
// #ifdef APP-PLUS || MP-WEIXIN
|
|
367
|
+
// 小程序/APP端通过计算位置来判断
|
|
368
|
+
uni.createSelectorQuery()
|
|
369
|
+
.selectAll('.letter-index')
|
|
370
|
+
.boundingClientRect((rects) => {
|
|
371
|
+
if (!rects) return;
|
|
372
|
+
for (let i = 0; i < rects.length; i++) {
|
|
373
|
+
const rect = rects[i];
|
|
374
|
+
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
|
|
375
|
+
const group = letterGroups[i];
|
|
376
|
+
if (group && !isGroupSelected(group)) {
|
|
377
|
+
selectedLetters.value = [...group];
|
|
378
|
+
touchSelectedGroup = group;
|
|
379
|
+
}
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
})
|
|
384
|
+
.exec();
|
|
385
|
+
// #endif
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 特殊字符(7个)
|
|
389
|
+
const specialChars = [
|
|
390
|
+
{ char: '警', type: 'white' },
|
|
391
|
+
{ char: '领', type: 'black' },
|
|
392
|
+
{ char: '港', type: 'black' },
|
|
393
|
+
{ char: '澳', type: 'black' },
|
|
394
|
+
{ char: '学', type: 'yellow' },
|
|
395
|
+
{ char: '挂', type: 'yellow' },
|
|
396
|
+
{ char: '使', type: 'black' },
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
// QWERTY键盘布局
|
|
400
|
+
const letterRows = [
|
|
401
|
+
['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
|
|
402
|
+
['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'],
|
|
403
|
+
['Z', 'X', 'C', 'V', 'B', 'N', 'M']
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
// 数字行
|
|
407
|
+
const numberRow = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
|
|
408
|
+
|
|
409
|
+
const plateLength = computed(() => isNewEnergy.value ? 8 : 7);
|
|
410
|
+
|
|
411
|
+
// 获取特殊字符类型
|
|
412
|
+
function getSpecialCharType(char) {
|
|
413
|
+
if (!char) return null;
|
|
414
|
+
const found = specialChars.find(item => item.char === char);
|
|
415
|
+
return found ? found.type : null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 解析车牌号为数组
|
|
419
|
+
const plateArray = computed(() => {
|
|
420
|
+
const value = props.modelValue;
|
|
421
|
+
if (!value) return [];
|
|
422
|
+
return value.split('').slice(0, plateLength.value);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// 判断前两位是否都是字母
|
|
426
|
+
const isFirstTwoLetters = computed(() => {
|
|
427
|
+
const arr = plateArray.value;
|
|
428
|
+
if (arr.length < 2) return false;
|
|
429
|
+
const letterRegex = /^[A-Z]$/;
|
|
430
|
+
return letterRegex.test(arr[0]) && letterRegex.test(arr[1]);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// 键盘标题
|
|
434
|
+
const keyboardTitle = computed(() => {
|
|
435
|
+
if (activeIndex.value === 0) {
|
|
436
|
+
return firstKeyboardMode.value === 'province' ? '选择省份' : '选择字母';
|
|
437
|
+
}
|
|
438
|
+
if (activeIndex.value === 1) return '选择字母';
|
|
439
|
+
if (activeIndex.value === 2 && isFirstTwoLetters.value) return '选择省份';
|
|
440
|
+
return '输入字符';
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// 第一位是否显示字母(通过切换按钮控制)
|
|
444
|
+
const showLetterInFirst = computed(() => {
|
|
445
|
+
return activeIndex.value === 0 && firstKeyboardMode.value === 'letter';
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// 显示省份区域:第1位(省份模式),或前两位是字母时的第3位
|
|
449
|
+
const showProvinceSection = computed(() => {
|
|
450
|
+
if (activeIndex.value === 0) return firstKeyboardMode.value === 'province';
|
|
451
|
+
if (activeIndex.value === 2 && isFirstTwoLetters.value) return true;
|
|
452
|
+
return false;
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// 显示字母区域:第1位(字母模式)、第2位,或第3位及以后
|
|
456
|
+
const showLetterSection = computed(() => {
|
|
457
|
+
if (activeIndex.value === 0 && firstKeyboardMode.value === 'letter') return true; // 第1位切换到字母
|
|
458
|
+
if (activeIndex.value === 1) return true; // 第2位
|
|
459
|
+
if (activeIndex.value === 2 && isFirstTwoLetters.value) return false; // 军牌第3位是省份
|
|
460
|
+
if (activeIndex.value >= 2) return true; // 后面位置
|
|
461
|
+
return false;
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// 显示数字区域:第1位(字母模式)、第2位、第3位及以后
|
|
465
|
+
const showNumberSection = computed(() => {
|
|
466
|
+
if (activeIndex.value === 0 && firstKeyboardMode.value === 'letter') return true; // 第1位切换到字母
|
|
467
|
+
if (activeIndex.value === 1) return true; // 第2位
|
|
468
|
+
if (activeIndex.value === 2 && isFirstTwoLetters.value) return false; // 军牌第3位是省份
|
|
469
|
+
return activeIndex.value >= 2;
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// 显示特殊字符:第1位(字母模式)、第2位、第3位及以后
|
|
473
|
+
const showSpecialSection = computed(() => {
|
|
474
|
+
if (activeIndex.value === 0 && firstKeyboardMode.value === 'letter') return true; // 第1位切换到字母
|
|
475
|
+
if (activeIndex.value === 1) return true; // 第2位
|
|
476
|
+
if (activeIndex.value === 2 && isFirstTwoLetters.value) return false; // 军牌第3位是省份
|
|
477
|
+
return activeIndex.value >= 2;
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// 是否禁用 I 和 O(只有第3位及以后才禁用,前两位不禁用)
|
|
481
|
+
const shouldDisableIO = computed(() => {
|
|
482
|
+
return activeIndex.value >= 2;
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// 是否显示完成按钮(普通车牌第7位,或新能源第8位)
|
|
486
|
+
const showDoneButton = computed(() => {
|
|
487
|
+
// 普通车牌:在第7位(索引6)时显示
|
|
488
|
+
if (!isNewEnergy.value && activeIndex.value === 6) return true;
|
|
489
|
+
// 新能源:在第8位(索引7)时显示
|
|
490
|
+
if (isNewEnergy.value && activeIndex.value === 7) return true;
|
|
491
|
+
return false;
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
function showKeyboardAt(index) {
|
|
495
|
+
activeIndex.value = index;
|
|
496
|
+
// 重置第一位键盘模式
|
|
497
|
+
if (index === 0) {
|
|
498
|
+
firstKeyboardMode.value = 'province';
|
|
499
|
+
}
|
|
500
|
+
showKeyboard.value = true;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function toggleFirstKeyboard() {
|
|
504
|
+
firstKeyboardMode.value = firstKeyboardMode.value === 'province' ? 'letter' : 'province';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function handleNewEnergyClick() {
|
|
508
|
+
if (!isNewEnergy.value) {
|
|
509
|
+
// 未启用新能源,点击时启用并打开键盘
|
|
510
|
+
isNewEnergy.value = true;
|
|
511
|
+
}
|
|
512
|
+
showKeyboardAt(7);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function closeKeyboard() {
|
|
516
|
+
showKeyboard.value = false;
|
|
517
|
+
activeIndex.value = -1;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function inputChar(c) {
|
|
521
|
+
// 第3位及以后禁用 I 和 O
|
|
522
|
+
if (shouldDisableIO.value && (c === 'I' || c === 'O')) return;
|
|
523
|
+
|
|
524
|
+
const currentValue = props.modelValue;
|
|
525
|
+
let arr = currentValue.split('');
|
|
526
|
+
|
|
527
|
+
// 填充空位
|
|
528
|
+
while (arr.length < activeIndex.value) {
|
|
529
|
+
arr.push('');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// 更新指定位置
|
|
533
|
+
arr[activeIndex.value] = c;
|
|
534
|
+
|
|
535
|
+
emit('update:modelValue', arr.join(''));
|
|
536
|
+
|
|
537
|
+
// 自动跳到下一位
|
|
538
|
+
if (activeIndex.value < 6) {
|
|
539
|
+
// 前6位,跳到下一位
|
|
540
|
+
activeIndex.value = activeIndex.value + 1;
|
|
541
|
+
} else if (activeIndex.value === 6) {
|
|
542
|
+
// 第7位输入完,自动启用新能源并跳到第8位
|
|
543
|
+
isNewEnergy.value = true;
|
|
544
|
+
activeIndex.value = 7;
|
|
545
|
+
} else if (activeIndex.value === 7) {
|
|
546
|
+
// 新能源第8位输入完成后自动关闭键盘
|
|
547
|
+
closeKeyboard();
|
|
548
|
+
if (arr.join('').length === 8) {
|
|
549
|
+
emit('complete', arr.join(''));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function deleteChar() {
|
|
555
|
+
const currentValue = props.modelValue;
|
|
556
|
+
let arr = currentValue.split('');
|
|
557
|
+
|
|
558
|
+
if (activeIndex.value >= 0 && arr[activeIndex.value]) {
|
|
559
|
+
// 当前位有内容,删除当前位
|
|
560
|
+
arr[activeIndex.value] = '';
|
|
561
|
+
// 移除末尾空字符
|
|
562
|
+
while (arr.length > 0 && arr[arr.length - 1] === '') {
|
|
563
|
+
arr.pop();
|
|
564
|
+
}
|
|
565
|
+
emit('update:modelValue', arr.join(''));
|
|
566
|
+
} else if (activeIndex.value > 0) {
|
|
567
|
+
// 当前位无内容,删除前一位并跳转
|
|
568
|
+
const prevIndex = activeIndex.value - 1;
|
|
569
|
+
arr[prevIndex] = '';
|
|
570
|
+
while (arr.length > 0 && arr[arr.length - 1] === '') {
|
|
571
|
+
arr.pop();
|
|
572
|
+
}
|
|
573
|
+
emit('update:modelValue', arr.join(''));
|
|
574
|
+
activeIndex.value = prevIndex;
|
|
575
|
+
} else if (activeIndex.value === 0 && arr[0]) {
|
|
576
|
+
emit('update:modelValue', '');
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// 监听新能源切换,截断
|
|
581
|
+
watch(isNewEnergy, (val) => {
|
|
582
|
+
if (!val && props.modelValue.length > 7) {
|
|
583
|
+
emit('update:modelValue', props.modelValue.slice(0, 7));
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
</script>
|
|
587
|
+
|
|
588
|
+
<style lang="scss">
|
|
589
|
+
// 业务固定色定义
|
|
590
|
+
$plate-new-energy: #00B42A; // 新能源车牌绿色
|
|
591
|
+
$plate-new-energy-light: #00D68F;
|
|
592
|
+
$plate-yellow: #FADC19; // 黄牌
|
|
593
|
+
$plate-yellow-dark: #E6C900;
|
|
594
|
+
$plate-black: #1D2129; // 黑牌
|
|
595
|
+
$plate-white: #FFFFFF; // 白牌
|
|
596
|
+
|
|
597
|
+
.prism-license-plate-input {
|
|
598
|
+
.plate-boxes {
|
|
599
|
+
display: flex;
|
|
600
|
+
align-items: flex-end;
|
|
601
|
+
gap: 8rpx;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.plate-box {
|
|
605
|
+
width: 72rpx;
|
|
606
|
+
height: 88rpx;
|
|
607
|
+
background: var(--prism-input-bg, #EBEEF2);
|
|
608
|
+
border-radius: 12rpx;
|
|
609
|
+
display: flex;
|
|
610
|
+
align-items: center;
|
|
611
|
+
justify-content: center;
|
|
612
|
+
border: 2rpx solid transparent;
|
|
613
|
+
transition: all 0.2s ease;
|
|
614
|
+
|
|
615
|
+
&.active {
|
|
616
|
+
border-color: var(--prism-primary-color, #3478F6);
|
|
617
|
+
background: var(--prism-primary-light, rgba(52, 120, 246, 0.08));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
&.filled {
|
|
621
|
+
background: var(--prism-primary-light, rgba(52, 120, 246, 0.08));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
&.province, &.letter {
|
|
625
|
+
width: 80rpx;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// 新能源车牌样式(业务固定色)
|
|
629
|
+
&.new-energy {
|
|
630
|
+
background: rgba($plate-new-energy, 0.15);
|
|
631
|
+
border: 2rpx solid $plate-new-energy;
|
|
632
|
+
|
|
633
|
+
&.active {
|
|
634
|
+
border-color: $plate-new-energy;
|
|
635
|
+
box-shadow: 0 0 0 4rpx rgba($plate-new-energy, 0.3);
|
|
636
|
+
background: rgba($plate-new-energy, 0.2);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
&.filled {
|
|
640
|
+
background: linear-gradient(135deg, $plate-new-energy 0%, $plate-new-energy-light 100%);
|
|
641
|
+
|
|
642
|
+
.plate-text {
|
|
643
|
+
color: $plate-white;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.plate-text {
|
|
648
|
+
color: $plate-new-energy;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
&.new-energy-optional {
|
|
653
|
+
background: transparent;
|
|
654
|
+
border: 2rpx dashed $plate-new-energy;
|
|
655
|
+
|
|
656
|
+
.optional-icon {
|
|
657
|
+
font-size: 28rpx;
|
|
658
|
+
color: $plate-new-energy;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
&:active {
|
|
662
|
+
background: rgba($plate-new-energy, 0.1);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.plate-text {
|
|
667
|
+
font-size: 36rpx;
|
|
668
|
+
font-weight: 600;
|
|
669
|
+
color: var(--prism-text-primary, #1D2129);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// 特殊车牌字符输入框样式(业务固定色)
|
|
673
|
+
&.special-white {
|
|
674
|
+
background: $plate-white;
|
|
675
|
+
border: 2rpx solid var(--prism-border-color-light, #E5E6EB);
|
|
676
|
+
|
|
677
|
+
&.active {
|
|
678
|
+
border-color: var(--prism-primary-color, #3478F6);
|
|
679
|
+
box-shadow: 0 0 0 4rpx rgba(52, 120, 246, 0.3);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.plate-text {
|
|
683
|
+
color: $plate-black;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
&.special-black {
|
|
688
|
+
background: $plate-black;
|
|
689
|
+
border: 2rpx solid $plate-black;
|
|
690
|
+
|
|
691
|
+
&.active {
|
|
692
|
+
border-color: var(--prism-primary-color, #3478F6);
|
|
693
|
+
box-shadow: 0 0 0 4rpx rgba(52, 120, 246, 0.3);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.plate-text {
|
|
697
|
+
color: $plate-white;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
&.special-yellow {
|
|
702
|
+
background: $plate-yellow;
|
|
703
|
+
border: 2rpx solid $plate-yellow-dark;
|
|
704
|
+
|
|
705
|
+
&.active {
|
|
706
|
+
border-color: var(--prism-primary-color, #3478F6);
|
|
707
|
+
box-shadow: 0 0 0 4rpx rgba(52, 120, 246, 0.3);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.plate-text {
|
|
711
|
+
color: $plate-black;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.plate-dot {
|
|
717
|
+
width: 12rpx;
|
|
718
|
+
height: 12rpx;
|
|
719
|
+
background: var(--prism-text-secondary, #86909C);
|
|
720
|
+
border-radius: 50%;
|
|
721
|
+
margin: 0 8rpx;
|
|
722
|
+
margin-bottom: 38rpx;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.plate-box-wrapper {
|
|
726
|
+
display: flex;
|
|
727
|
+
flex-direction: column;
|
|
728
|
+
align-items: center;
|
|
729
|
+
|
|
730
|
+
&.new-energy-wrapper {
|
|
731
|
+
.new-energy-label {
|
|
732
|
+
font-size: 18rpx;
|
|
733
|
+
color: $plate-new-energy;
|
|
734
|
+
font-weight: 500;
|
|
735
|
+
margin-bottom: 6rpx;
|
|
736
|
+
white-space: nowrap;
|
|
737
|
+
|
|
738
|
+
&.hidden {
|
|
739
|
+
visibility: hidden;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.energy-switch {
|
|
746
|
+
display: flex;
|
|
747
|
+
align-items: center;
|
|
748
|
+
gap: 12rpx;
|
|
749
|
+
margin-top: 24rpx;
|
|
750
|
+
|
|
751
|
+
.energy-text {
|
|
752
|
+
font-size: 28rpx;
|
|
753
|
+
color: var(--prism-text-secondary, #86909C);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.keyboard-modal {
|
|
758
|
+
position: fixed;
|
|
759
|
+
left: 0;
|
|
760
|
+
right: 0;
|
|
761
|
+
bottom: 0;
|
|
762
|
+
z-index: 9999;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.keyboard-content {
|
|
766
|
+
width: 100%;
|
|
767
|
+
max-height: 70vh;
|
|
768
|
+
background: var(--prism-bg-color-card, #FFFFFF);
|
|
769
|
+
border-radius: 24rpx 24rpx 0 0;
|
|
770
|
+
padding: 24rpx;
|
|
771
|
+
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
772
|
+
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
|
|
773
|
+
display: flex;
|
|
774
|
+
flex-direction: column;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
.keyboard-body {
|
|
778
|
+
flex: 1;
|
|
779
|
+
overflow-y: auto;
|
|
780
|
+
height: 480rpx; // 统一键盘高度:5行 * (80rpx高度 + 12rpx间距) + 余量
|
|
781
|
+
min-height: 480rpx;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.keyboard-header {
|
|
785
|
+
display: flex;
|
|
786
|
+
justify-content: space-between;
|
|
787
|
+
align-items: center;
|
|
788
|
+
margin-bottom: 24rpx;
|
|
789
|
+
|
|
790
|
+
.header-left {
|
|
791
|
+
display: flex;
|
|
792
|
+
align-items: center;
|
|
793
|
+
gap: 16rpx;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
.keyboard-title {
|
|
797
|
+
font-size: 32rpx;
|
|
798
|
+
font-weight: 600;
|
|
799
|
+
color: var(--prism-text-primary, #1D2129);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.switch-btn {
|
|
803
|
+
padding: 6rpx 12rpx;
|
|
804
|
+
background: var(--prism-primary-color, #3478F6);
|
|
805
|
+
border-radius: 6rpx;
|
|
806
|
+
|
|
807
|
+
text {
|
|
808
|
+
font-size: 22rpx;
|
|
809
|
+
color: $plate-white;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
&:active {
|
|
813
|
+
opacity: 0.8;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
.keyboard-close {
|
|
818
|
+
font-size: 36rpx;
|
|
819
|
+
color: var(--prism-text-secondary, #86909C);
|
|
820
|
+
padding: 8rpx;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
.keyboard-section {
|
|
825
|
+
margin-bottom: 20rpx;
|
|
826
|
+
|
|
827
|
+
.section-title {
|
|
828
|
+
font-size: 24rpx;
|
|
829
|
+
color: var(--prism-text-secondary, #86909C);
|
|
830
|
+
margin-bottom: 12rpx;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
.keyboard-qwerty {
|
|
835
|
+
display: flex;
|
|
836
|
+
flex-direction: column;
|
|
837
|
+
gap: 12rpx;
|
|
838
|
+
|
|
839
|
+
// 省份键盘样式
|
|
840
|
+
&.province-qwerty .keyboard-row {
|
|
841
|
+
justify-content: flex-start;
|
|
842
|
+
|
|
843
|
+
.key-item {
|
|
844
|
+
flex: 1;
|
|
845
|
+
max-width: none;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.keyboard-row {
|
|
851
|
+
display: flex;
|
|
852
|
+
justify-content: center;
|
|
853
|
+
gap: 8rpx;
|
|
854
|
+
|
|
855
|
+
&.number-row {
|
|
856
|
+
justify-content: flex-start;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
&.special-row {
|
|
860
|
+
justify-content: flex-start;
|
|
861
|
+
flex-wrap: wrap;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// 字母索引行
|
|
865
|
+
&.letter-index-row {
|
|
866
|
+
justify-content: flex-start;
|
|
867
|
+
width: 100%;
|
|
868
|
+
margin-bottom: 12rpx;
|
|
869
|
+
|
|
870
|
+
.key-item {
|
|
871
|
+
flex: 1;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.key-item {
|
|
877
|
+
min-width: 56rpx;
|
|
878
|
+
height: 80rpx;
|
|
879
|
+
padding: 0 12rpx;
|
|
880
|
+
background: var(--prism-input-bg, #EBEEF2);
|
|
881
|
+
border-radius: 12rpx;
|
|
882
|
+
display: flex;
|
|
883
|
+
align-items: center;
|
|
884
|
+
justify-content: center;
|
|
885
|
+
font-size: 32rpx;
|
|
886
|
+
font-weight: 500;
|
|
887
|
+
color: var(--prism-text-primary, #1D2129);
|
|
888
|
+
transition: all 0.15s ease;
|
|
889
|
+
flex: 1;
|
|
890
|
+
max-width: 72rpx;
|
|
891
|
+
|
|
892
|
+
&:active {
|
|
893
|
+
background: var(--prism-primary-color, #3478F6);
|
|
894
|
+
color: $plate-white;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
&.disabled {
|
|
898
|
+
opacity: 0.3;
|
|
899
|
+
pointer-events: none;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// 字母索引按钮样式
|
|
903
|
+
&.letter-index {
|
|
904
|
+
font-size: 24rpx;
|
|
905
|
+
background: var(--prism-bg-color, #F7F8FA);
|
|
906
|
+
color: var(--prism-text-secondary, #86909C);
|
|
907
|
+
max-width: none;
|
|
908
|
+
min-width: auto;
|
|
909
|
+
padding: 0 8rpx;
|
|
910
|
+
|
|
911
|
+
&.active {
|
|
912
|
+
background: var(--prism-primary-color, #3478F6);
|
|
913
|
+
color: $plate-white;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// 省份高亮样式(拼音首字母匹配)
|
|
918
|
+
&.highlight {
|
|
919
|
+
background: var(--prism-primary-color, #3478F6);
|
|
920
|
+
color: $plate-white;
|
|
921
|
+
box-shadow: 0 0 0 4rpx rgba(52, 120, 246, 0.3);
|
|
922
|
+
transform: scale(1.05);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// 删除按钮样式
|
|
926
|
+
&.delete {
|
|
927
|
+
background: var(--prism-danger-color, #F53F3F);
|
|
928
|
+
color: $plate-white;
|
|
929
|
+
min-width: 120rpx;
|
|
930
|
+
max-width: 120rpx;
|
|
931
|
+
flex: none;
|
|
932
|
+
|
|
933
|
+
.delete-text {
|
|
934
|
+
font-size: 28rpx;
|
|
935
|
+
font-weight: 500;
|
|
936
|
+
color: $plate-white;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
&:active {
|
|
940
|
+
background: var(--prism-danger-color-active, #D93636);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// 完成按钮样式
|
|
945
|
+
&.done {
|
|
946
|
+
background: var(--prism-primary-color, #3478F6);
|
|
947
|
+
color: $plate-white;
|
|
948
|
+
flex: 1;
|
|
949
|
+
min-width: 80rpx;
|
|
950
|
+
max-width: none;
|
|
951
|
+
|
|
952
|
+
.done-text {
|
|
953
|
+
font-size: 28rpx;
|
|
954
|
+
font-weight: 500;
|
|
955
|
+
color: $plate-white;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
&:active {
|
|
959
|
+
background: var(--prism-primary-color-active, #2563EB);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// 特殊车牌字符样式(业务固定色)
|
|
964
|
+
&.special {
|
|
965
|
+
font-weight: 600;
|
|
966
|
+
flex: none;
|
|
967
|
+
min-width: 72rpx;
|
|
968
|
+
max-width: 72rpx;
|
|
969
|
+
|
|
970
|
+
&.black {
|
|
971
|
+
background: $plate-black;
|
|
972
|
+
color: $plate-white;
|
|
973
|
+
|
|
974
|
+
&:active {
|
|
975
|
+
background: #000000;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
&.white {
|
|
980
|
+
background: $plate-white;
|
|
981
|
+
color: $plate-black;
|
|
982
|
+
border: 2rpx solid var(--prism-border-color-light, #E5E6EB);
|
|
983
|
+
|
|
984
|
+
&:active {
|
|
985
|
+
background: var(--prism-bg-color, #F2F3F5);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
&.yellow {
|
|
990
|
+
background: $plate-yellow;
|
|
991
|
+
color: $plate-black;
|
|
992
|
+
|
|
993
|
+
&:active {
|
|
994
|
+
background: $plate-yellow-dark;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.dark-mode .prism-license-plate-input {
|
|
1002
|
+
.plate-box {
|
|
1003
|
+
background: var(--prism-input-bg, #2A2A2A);
|
|
1004
|
+
|
|
1005
|
+
&.active {
|
|
1006
|
+
background: rgba(52, 120, 246, 0.15);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
&.filled {
|
|
1010
|
+
background: rgba(52, 120, 246, 0.15);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
.plate-text {
|
|
1014
|
+
color: var(--prism-text-primary, #E5E6EB);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// 深色模式特殊字符样式
|
|
1018
|
+
&.special-white {
|
|
1019
|
+
background: rgba(255, 255, 255, 0.9);
|
|
1020
|
+
border: 2rpx solid rgba(255, 255, 255, 0.3);
|
|
1021
|
+
|
|
1022
|
+
.plate-text {
|
|
1023
|
+
color: #1D2129;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
&.special-black {
|
|
1028
|
+
background: #000000;
|
|
1029
|
+
border: 2rpx solid #333333;
|
|
1030
|
+
|
|
1031
|
+
.plate-text {
|
|
1032
|
+
color: #FFFFFF;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
&.special-yellow {
|
|
1037
|
+
background: #FADC19;
|
|
1038
|
+
border: 2rpx solid #E6C900;
|
|
1039
|
+
|
|
1040
|
+
.plate-text {
|
|
1041
|
+
color: #1D2129;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
.keyboard-content {
|
|
1047
|
+
background: var(--prism-bg-color-card, #242424);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
.keyboard-header .keyboard-title {
|
|
1051
|
+
color: var(--prism-text-primary, #E5E6EB);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
.section-title {
|
|
1055
|
+
color: var(--prism-text-secondary, #6B7785);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
.key-item {
|
|
1059
|
+
background: var(--prism-input-bg, #2A2A2A);
|
|
1060
|
+
color: var(--prism-text-primary, #E5E6EB);
|
|
1061
|
+
|
|
1062
|
+
// 字母索引按钮深色模式
|
|
1063
|
+
&.letter-index {
|
|
1064
|
+
background: rgba(255, 255, 255, 0.08);
|
|
1065
|
+
color: var(--prism-text-secondary, #6B7785);
|
|
1066
|
+
|
|
1067
|
+
&.active {
|
|
1068
|
+
background: var(--prism-primary-color, #3478F6);
|
|
1069
|
+
color: #FFFFFF;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// 省份高亮保持蓝色
|
|
1074
|
+
&.highlight {
|
|
1075
|
+
background: var(--prism-primary-color, #3478F6);
|
|
1076
|
+
color: #FFFFFF;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// 删除按钮保持红色
|
|
1080
|
+
&.delete {
|
|
1081
|
+
background: var(--prism-danger-color, #F53F3F);
|
|
1082
|
+
color: #FFFFFF;
|
|
1083
|
+
|
|
1084
|
+
.delete-text {
|
|
1085
|
+
color: #FFFFFF;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// 完成按钮保持蓝色
|
|
1090
|
+
&.done {
|
|
1091
|
+
background: var(--prism-primary-color, #3478F6);
|
|
1092
|
+
color: #FFFFFF;
|
|
1093
|
+
|
|
1094
|
+
.done-text {
|
|
1095
|
+
color: #FFFFFF;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
</style>
|