简版车牌号
Hello,大家好,我是幕呈。
最近这两天那个开使馆牌照车的某负责人不是上热搜了,懂什么叫使馆车吗?。可笑,可悲,可叹!!!
好了,我们回归正题,趁着这个机会,给大家讲讲我们最近开发的一个输入车牌号组件,不过可惜她的车牌号输入不了,我们实现的组件针对是个人的,先来看看效果
一共有两个页面,一个省份页面
一个数字字母页面
接下来我们讲讲是如何实现的,首先需要了解下车牌号的规则
车牌号的规则
普通规则
- 规则一 号牌第一位是汉字,为省、自治区、直辖市的简称,或者是使开头
- 规则二 号牌第二位是字母,是发牌机关代号(也可以理解为市区代号),除直辖市外,其他省份或自治区的号牌没有字母 I 和 O。
- 规则三 号牌第三位至第七位,是车牌序号
- 序号编码规则有三种,分别是:
- a) 序号中的每一位都使用阿拉伯数字;
- b) 序号中使用 1 位英文字母,其他位为阿拉伯数字,26 个英文字母中 O 和 I 不能使用
- c) 序号中使用 2 位英文字母,其他位为阿拉伯数字,26 个英文字母中 O 和 I 不能使用
专用号牌规则【此次实现的组件功能不包括这一部分,后续会支持】
专用号牌 第七位位使用了汉字
- 使馆汽车号牌和使馆摩托车号牌为“使”
- 警用汽车号牌和警用摩托车号牌为“警”
- 教练汽车号牌和教练摩托车号牌为“学”
- 挂车号牌为“挂”
- 香港特别行政区入出内地车辆号牌为“港”
- 澳门特别行政区入出内地车辆号牌为“澳”
新能源规则
号牌位数为 8 位
小型新能源汽车
第三位固定为字母 "D" 或 "F",其中 D 代表纯电动,F 代表油电混动。第四位到第八位为序号,第四位可能为字母(没有字母 I 和 O),其他的皆为数字。(目前大部分车牌后五位皆为数字,第四位为字母的情况极少)
大型新能源汽车
(如公交车)第八位固定为字母 "D" 或 "F",其中 D 代表纯电动,F 代表油电混动。中间五位序号基本可以认为全是数字。(按照编规则,先排数字,才会排字母,大型新能源车比小型新能源车数量要少很多)
特殊【此次实现的组件功能不包括这一部分,后续会支持】
驻华使领馆车辆号牌
- XXX·XXX 使(“使”字由红色变成白色、从第一位挪至最后一位)
- 省 XXX·XX 领
在了解了具体的规则接下来进入实际开发
实现
我们是实现了 react 和 vue 版本的,这里只列举 vue 版本的代码示例
三个基础页面分别对应三个页面数据结构分别如下
export const provinceSelect = [
['京', '津', '渝', '沪', '冀', '晋', '辽', '吉'],
['浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘'],
['川', '贵', '云', '陕', '甘', '青', '蒙', '桂'],
[ '黑', '苏', '藏', '粤', '琼', '宁', '新','']
];
export const numberAbcSelect = [
['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'],
['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Z'],
['X', 'C', 'V', 'B', 'N', 'M']
];
键盘按键的代码
这部分代码主要实现的是一个按钮的样式
<template>
<section
:class="[ TypeToStyle[type], { 'cell-disabled': props.disabled, 'cell-no-disabled': !props.disabled, 'keyboard-cell' : props.cell } ]"
role="button"
@click="handleClick"
:style="cellDivStyle"
>
<span class="cell-text" :style="cellTextStyle">{{ props.cell }}</span>
</section>
</template>
<script setup lang="ts">
import { defineProps, StyleValue } from 'vue';
// Define props
const props = defineProps<{
cell: String;
disabled?: Boolean;
type: 'province' | 'normal' | 'number' | 'character';
onClick: Function;
cellTextStyle?: StyleValue;
cellDivStyle?: StyleValue;
}>();
// Object mapping cell type to CSS class
const TypeToStyle = {
province: 'province-cell',
character: 'character-cell',
normal: 'normal-cell',
number: 'number-cell'
};
// Handle click event
const handleClick = () => {
if (!props.disabled && typeof props.onClick === 'function' && props.cell) {
props.onClick(props.cell);
}
};
</script>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'KeyCell',
// 组件的其余逻辑...
});
</script>
省/直辖市的代码页面
这部分代码主要实现的是省/直辖市页面以及交互, 这里简称为第一屏Page1
<template>
<article class="keyboard-container">
<section v-for="(row, index) in props.firstScreen" :key="index" class="keyboard-row">
<KeyCell
v-for="province in row"
:key="province"
:cell="province"
:onClick="() => handleEnter(province)"
:cellTextStyle="cellTextStyle"
:cellDivStyle="cellDivStyle"
type="province"
/>
</section>
</article>
</template>
<script setup lang="ts">
import { defineProps, StyleValue } from 'vue';
const props = defineProps<{
firstScreen: string[][];
secondScreen: string[][];
value: string;
cellTextStyle?: StyleValue;
cellDivStyle?: StyleValue;
}>();
const emit = defineEmits([ "handleEnter", "handleDelete" ]);
const handleEnter = (cell:string) => {
emit('handleEnter', cell);
}
</script>
<script lang="ts">
import { defineComponent } from 'vue';
import KeyCell from './KeyCell.vue';
export default defineComponent({
name: 'RenderProvinceSelect',
components: {
KeyCell,
},
// 组件的其余逻辑...
});
</script>
<!-- 在这里添加样式 -->
字母数字键盘代码
这部分代码主要实现的是字母数字页面以及交互, 这里简称为数字屏Page2
<template>
<article class="keyboard-container keyboard-container-noprovince">
<section v-for="(row, index) in secondScreen" :key="index" class="keyboard-row">
<KeyCell
v-for="(cell, ind) in row" :key="ind"
:cellTextStyle="cellTextStyle"
:cellDivStyle="cellDivStyle"
:cell="cell"
@click="() => handleEnter(cell)"
:disabled="!judgeInput(cell, type)"
:type="index === 4 ? 'character' : 'normal'"
/>
<!-- 在最后一行添加返回按钮 -->
<template v-if="index === 3">
<!-- 渲染返回按钮 -->
<RenderBackBtn
:value="value"
@handleDelete="handleDelete"
/>
<RenderDoneBtn
:value="value"
@handleDone="handleDone"
/>
</template>
</section>
</article>
</template>
<script setup lang="ts">
import { defineProps, StyleValue } from 'vue';
import { judgeInput, secondScreenType } from './utils'
const props = defineProps<{
cellTextStyle?: StyleValue;
cellDivStyle?: StyleValue;
secondScreen: string[][];
type: secondScreenType;
value: string;
}>();
const emit = defineEmits([ "handleEnter", "handleDelete", "handleDone" ]);
const handleEnter = (cell:string) => {
emit('handleEnter', cell);
}
const handleDelete = () => {
emit('handleDelete');
}
const handleDone = () =>{
emit('handleDone');
}
</script>
<script lang="ts">
import { defineComponent } from 'vue';
import KeyCell from './KeyCell.vue';
import RenderBackBtn from './renderBackBtn.vue';
import RenderDoneBtn from './renderDoneBtn.vue';
export default defineComponent({
name: 'renderNumberABCSelect',
components: {
KeyCell,
RenderBackBtn,
RenderDoneBtn
},
// 组件的其余逻辑...
});
</script>
以上是两个主要页面的代码,除了其包含有页面渲染逻辑之外,在键盘组件中还通过父组件传入的disabled值控制键盘是否可点击
这里就不在详细的阐述了,主要说下页面的整体交互逻辑
主页面交互逻辑
首先在页面初始化的状态下显示的是Page1, 当点击了Page1中的省/直辖市按钮之后,会切换到Page2页面,而根据Page1页面中点击的词来决定Page2页面中应该显示什么内容,这就要用到我们的规则列表了,第二位要显示的是各地车辆号牌机关代号, 我们这一版本是全部的字母,这个后续可以针对单独的省出具体的准确的机关代号,以下是规则列表
export const rules: RulesType = {
京: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
津: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
冀: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
晋: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
蒙: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
辽: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
吉: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
黑: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
沪: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
苏: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
浙: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
皖: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
闽: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
赣: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
鲁: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
豫: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
鄂: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
湘: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
粤: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
桂: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
琼: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
渝: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
川: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
贵: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
云: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
藏: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
陕: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
甘: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
青: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
宁: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
新: {
rule: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
},
};
用户会依次输入3-7/8位车牌号,我们会根据前面输入的数字、字母来判断下一位是什么数字/字母, 下边是安位次设置的规则
export const getRuleConfig = (key: string) => { // key 省/直辖市
return [
[
rules[key]?.rule, // 机关代号
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第3位
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第4位
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第5位
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第6位
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第7位
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' // 第8位
]
];
};
目前实现的逻辑是除了第一位和第二位有控制之外,其余的都是可以输入数字和字母的,如果你想要专门针对输入新能源规则的车牌号增加输入校验,拓展新增规则就OK,比如说
export const getRuleConfig = (key: string) => { // key 省/直辖市
return [
[
rules[key]?.rule, // 机关代号
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第3位
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第4位
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第5位
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第6位
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // 第7位
],
[ // 新能源规则
rules[key]?.rule, // 机关代号
'0123456789', // 第3位
'0123456789', // 第4位
'0123456789', // 第5位
'0123456789', // 第6位
'0123456789', // 第7位
'ABCDEFGHJK', // 第8位
],
[ // 新能源规则
rules[key]?.rule, // 机关代号
'ABCDEFGHJK', // 第3位
'0123456789', // 第4位
'0123456789', // 第5位
'0123456789', // 第6位
'0123456789', // 第7位
'0123456789', // 第8位
],
];
};
在这个过程中每输入一位就会根据配置的规则对下一位要输入的内容进行计算,计算完成之后对应的页面只显示下一位可以显示的内容,该计算通过judgeRules方法来实现该项功能
const judgeRules = ({ key, len, currentValue }: any): string => {
const provinceArr = (defaultConfig as DefaultConfig)[key] || [];
const arr: string[] = [];
const newArr: string[] = [];
provinceArr?.forEach((item) => {
arr.push(item[len]);
if (len > 0) {
let flag = false;
currentValue
?.slice(1)
?.split('')
?.forEach((ites:string, ind:number) => {
if (item[ind] && item[ind].indexOf(ites) === -1) {
flag = true;
}
});
if (!flag) {
newArr.push(item[len]);
}
}
});
const rules = mergeAndRemoveDuplicates(newArr?.length > 0 ? newArr : arr);
return rules;
};
到这里基本上把我们实现一个简易的车牌号输入逻辑说清楚了,后续我们会在此基础上,将规则细化,准确化。
附录
项目的git地址: github.com/afu-fe/afu-…
参考项目: github.com/LiuuY/vehic…