在pc端后台系统有个需求是填车牌号。需求是可以不用输。点击输入框弹出个可以选的。就像交管APP哪种车牌号选中。因此基于自己的场景进行封装控件
交管效果如下
实现效果如下
组件结构和功能
-
键盘组件:
- 包含中文和英文字符的键盘。
- 用户可以通过点击按键输入字符,或点击删除键删除字符。
- 切换按键可以在中文和英文键盘之间切换。
-
输入框组件:
- 显示当前车牌号码的每一位。
- 用户可以点击车牌号码的任意一位来选择输入位置。
组件交互流程
- 用户点击输入框中的某一位车牌号码,激活该位置。
- 键盘组件接收输入,并更新到输入框组件中激活的位置。
- 用户可以切换输入类型(中文/英文),继续输入或删除字符。
- 用户完成输入,点击完成按钮。
输入框组件
-
定义属性和事件:
-
props
接收父组件传递的value
,表示当前输入的键值。 -
emit
用于向父组件发送更新事件。
-
-
状态管理:
plateNumber
数组。定义8个''的字符串且作为渲染8个input输入框
-
事件处理:
handleClickItem
点击输入框时emit事件activeIndex
<template>
<ul class="input-box">
<li
v-for="(item, index) in plateNumber"
:key="index"
:class="{ active: props.activeIndex === index }"
@click="handleClickItem(index)"
>
<span>{{ item }}</span>
<span v-if="props.activeIndex === index" class="active_border"></span>
</li>
</ul>
</template>
<script lang="ts" setup>
import { reactive,defineProps, defineEmits ,watch} from "vue";
// 定义 props 类型
interface Props {
value: string;
activeIndex: number;
}
// 定义 emit 事件类型
const emit = defineEmits<{
(event: "update:activeIndex", value: number): void;
}>();
const props = defineProps<Props>();
let plateNumber = reactive<string[]>(Array(8).fill("")); //车牌是8位数
const handleClickItem = (index:number) => {
//选择框的下标
emit("update:activeIndex", index);
};
watch(() => props.value, (newValue) => {
const currentValue = newValue.split("");
plateNumber = currentValue.concat(Array(plateNumber.length - currentValue.length).fill(""));
});
</script>
<style lang="less" scoped>
.input-box {
flex:1;
display: flex;
list-style: none; /* 去掉 ul 的默认点样式 */
padding-left: 0; /* 去掉 ul 的默认内边距 */
> li {
list-style-type: none; /* 去掉 li 的默认点样式 */
flex: 1;
height: 28px;
line-height: 28px;
border: 1px solid #ccc;
margin: 5px;
cursor: pointer;
text-align: center;
}
>li.active{
position: relative;
.active_border{
display: block;
position: absolute;
bottom: 0;
left: 0;
width:100%;
height: 1px;
background: red;
}
}
}
</style>
键盘组件
-
定义属性和事件:
props
接收父组件传递的modelValue
,表示当前输入的键值。emit
用于向父组件发送更新事件。
-
状态管理:
activeIndex
:记录当前激活的键值索引。keyValue
:当前输入的键值。type
:键盘类型(中文cn
或英文en
)。
-
键盘布局:
- 使用
reactive
创建中文和英文键盘布局数组。 - 使用
computed
根据当前类型选择相应的键盘布局。
- 使用
-
切换类型:
handleChangeType
函数切换键盘类型,并更新键盘布局。
-
监听变化:
- 监听
modelValue
和activeIndex
的变化,同步更新keyValue
和键盘类型。
- 监听
-
事件处理:
finish
:完成输入,向父组件发送更新事件。handleDel
:删除当前激活索引的字符。handleClickKey
:输入按键字符,并更新激活索引。
<template>
<div class="key-box">
<div class="key-box-top">
<key-board-item :value="keyValue" v-model:activeIndex="activeIndex"></key-board-item>
<el-button type="primary" size="mini" @click="finish">完成</el-button>
</div>
<div class="key-box-container">
<div class="key-box-row" v-for="(item, index) in list" :key="index">
<div
class="key-box-row-wrapper"
v-for="(val, index) in item"
:key="index"
:class="{
'key-box-row-del-wrapper': val === 'del',
'key-box-row-type-wrapper': val === 'type'
}"
>
<el-button v-if="val === 'type'" class="key-box-row-btn-key" @click="handleChangeType">
<span v-if="type === 'cn'">中/<span class="key-box-row-smaller">英</span></span>
<span v-else><span class="key-box-row-smaller">中</span>/英</span>
</el-button>
<el-button v-else-if="val === 'del'" class="key-box-row-btn-key" type="text" @click="handleDel" :icon="CloseBold" />
<el-button v-else class="key-box-row-btn-key" :class="{'key-box-row-btn-empty': !val}" @click="handleClickKey(val)">
{{ val }}
</el-button
>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { CloseBold} from '@element-plus/icons-vue'
import keyBoardItem from './keyBoardItem.vue'
import { ref,reactive, defineProps,computed , defineEmits, watch } from "vue";
import { fa } from 'element-plus/es/locale';
// 定义 props 类型
const props = defineProps({
modelValue: String
});
//定义emit事件
const emit = defineEmits(['update:modelValue']);
//定义active点击事件
let activeIndex = ref<number>(0);
//定义输入值的值
let keyValue = ref<string>(props.modelValue)
//定义类型
let type = ref<string>("cn");
const cn = reactive<string[][]>([
["京", "津", "沪", "渝", "冀", "豫", "云", "辽", "黑", "湘"],
["皖", "鲁", "新", "苏", "浙", "赣", "鄂", "桂", "甘", "晋"],
["蒙", "陕", "吉", "闽", "贵", "粤", "青", "藏", "川", "宁"],
["type", "琼", "使", "领", "学", "警", "", "", "del"]
]);
const en = reactive<string[][]>([
["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
["Q", "W", "E", "R", "T", "Y", "U", "O", "P"],
["A", "S", "D", "F", "G", "H", "J", "K", "L"],
["type", "Z", "X", "C", "V", "B", "N", "M", "del"]
])
// 定义计算属性
const list = computed(() => type.value== "en" ? en:cn);
//切换类型
const handleChangeType = ()=>{
type.value = type.value=="cn"?"en":"cn";
}
//wath外部的值变化时同步keyValue
watch(() => props.modelValue, (newValue:string) => {
keyValue.value = newValue;
});
//watch监听input框的值
watch(() => activeIndex.value, (newValue:number) => {
if(newValue==0){
type.value = "cn";
}else{
type.value = "en";
}
});
const finish = ()=>{
//如果要加验证。可自己定义传多参数在完成前抛出
emit('update:modelValue',keyValue.value);
}
const handleDel = ()=>{
if (activeIndex.value === 0 && !keyValue.value) return;
let currentValue = keyValue.value.split("");
if (currentValue[activeIndex.value]) {
currentValue.splice(activeIndex.value, 1);
}
if (activeIndex.value > 0) {
activeIndex.value--;
}
keyValue.value = currentValue.join('');
}
//输入框赋值事件
const handleClickKey = (val:string)=>{
const currentValue = keyValue.value.split("");
currentValue[activeIndex.value] = val;
keyValue.value = currentValue.join('');
// 仅在 activeIndex 小于最大值时递增
if (activeIndex.value < 7) {
activeIndex.value++;
}
}
</script>
<style scoped lang="less">
.key-box{
width:100%;
.key-box-top{
display: flex;
align-items: center;
.key-box-top{
border-bottom: 1px solid #ccc;
}
}
.key-box-container {
padding: 3px;
padding-bottom: 22px;
.key-box-row {
display: flex;
justify-content: center;
}
.key-box-row-wrapper {
flex: 0 1 calc((100% - 6px * 10) / 10);
padding: 3px;
box-sizing: content-box;
margin:1px;
border:1px solid #999;
display: flex;
align-items: center;
text-align: center;
cursor: pointer;
&.key-box-row-del-wrapper,
&.key-box-row-type-wrapper {
flex: 1;
}
&.key-box-row-type-wrapper {
.key-box-row-smaller {
color: #999;
font-size: 12px;
}
}
.key-box-row-btn-key {
padding: 0;
width: 100%;
border-radius: 4px;
}
.key-box-row-btn-empty {
background: transparent;
border: none;
}
.key-box-row-delete-icon {
width: 18px;
vertical-align: middle;
}
}
}
}
</style>
完整使用
<el-popover placement="top" :width="370" trigger="click">
<template #reference>
<el-input
readonly
v-model="PlateNumber"
style="width: 200px"
placeholder="输入车牌号"
class="input-with-select"
>
<template #append>
<el-button :icon="Operation" />
</template>
</el-input>
</template>
<key-board v-model=PlateNumber ></key-board>
</el-popover>
如果你想实时跟使用的input框绑定。手动把keyValue换成prop.modelValue