vue3 input中回车生成标签
参考链接: https://blog.csdn.net/feiyu_may/article/details/117130871
效果展示:
参考上面的链接改为了vue3+ts的写法。封装了InputLabel组件,改为了即可以回车生成标签,也可以直接选择标签的形式。
具体的实现原理等可参考上面的链接
属性说明:
属性 | 类型 | 默认值 | 说明 |
reg
|
any
|
/^[\u4E00-\u9FA5A-Za-z0-9_]{1,10}$/
|
标签内容的正则表达式 默认标签只能输入中文、大小写、数字,1-10位的数据
|
limit
|
number
|
无
|
最多能输入几个标签
|
placeholder
|
string
|
请输入
|
输入框提示信息
|
fixedTags
|
{}[]
|
[] |
若存在固定标签格式如下
{
name: xxx, // 固定标签的内容
show: xxx, // 固定标签的显示或隐藏
}
|
fixClickShow
|
boolean
|
true
|
固定标签点击选中后,是否隐藏该固定标签; true:隐藏 false: 固定在原位
|
方法说明:
方法名 | 说明 |
onValidateTag
|
验证输入标签的内容,长度,以及标签是否重复
LIMIT: 最多只能输入XXX个标签
REG: 正则校验标签内容失败
REPEAT: 标签内容重复
|
change
|
当新增或删除标签后,取得全部选中的标签数据
|
InputLabel.vue组件代码如下:
<!-- 组件功能:按压enter键后,生成自定义标签。还可以同时选择固定标签 --> <template> <!-- 固定标签 把固定标签移到组件中 --> <div class="fixed-layout"> <div v-for="(item, index) in fixedTags" :key="index" @click="tagClick(index, item)" class="label-box"> <span class="fixed-label-title">{{ item.name }}</span> </div> </div> <div class="layout"> <!-- 自定义标签样式 --> <div v-for="(item, index) in tagsArr" :key="index" class="label-box"> <span class="label-title">{{ item.name }}</span> <i class="label-close" @click="removeTag(index, item)"></i> </div> <!-- 输入框 --> <input v-model="currentVal" :placeholder="placeholder" @keyup.enter="addTags" class="input-tag" ref="inputTagRef" type="text" /> </div> </template> <script setup lang='ts'> import { ref, reactive, toRefs, toRaw, watch } from 'vue' // 定义标签验证内容 enum VALIDATE { REG, // 正则表达式验证 LIMIT, // 标签数量验证 REPEAT // 标签重复验证 } // 类型定义 interface LabelModel { name: string, } interface SubModel { tagsArr: LabelModel[] } // 定义类型, widthDefaults不支持外部导入 interface PropsModel { fixClickShow?: boolean, // 固定标签点击选中后,是否隐藏 true:隐藏 false: 固定在原位置不动 reg?: any, // 标签内容正则表达式 limit?: number, // 最多能输入几个标签 placeholder?: string, // 提示信息 fixedTags?: { // 固定标签参数 name: string, // 固定标签的名称 show: boolean, // 是否显示隐藏 }[] } // 接收父组件参数 const props = withDefaults(defineProps<PropsModel>(), { // 设置参数默认值 fixClickShow: true, // 默认点击固定标签后隐藏 reg: /^[\u4E00-\u9FA5A-Za-z0-9_]{1,10}$/, // 默认标签只能输入中文、大小写、数字,1-10位的数据 placeholder: "请输入", // 提示信息 fixedTags: () => [] // 固定标签数组 }) // 参数定义 let currentVal = ref(""); // 输入的标签内容 let state = reactive<SubModel>({ tagsArr: [ // 设置默认值,可直接显示在输入框内 // { // name: "VIP", // } ], // 输入的标签数组 }) // 数据解构 let { tagsArr } = toRefs(state); // const emits = defineEmits(['onRemoveTag']); const emits = defineEmits<{ // (event: 'onRemoveTag', name: string): void // 删除标签 (event: 'onValidateTag', params: number): void // 数据验证 (event: 'change', params: LabelModel[]): void // 标签值改变 }>() // 数据监听 watch([() => state.tagsArr], ([newTagsArr], [oldTagsArr]) => { // 监听输入框内值改变 // state.tagsArr = newParentArr.length ? newParentArr : []; emits("change", toRaw(state.tagsArr)); }, { deep: true }) // 方法定义 // 点击固定标签,显示到输入框内 const tagClick = (index: number, item: LabelModel) => { let result = validateFn(item.name); if (result) { // 验证通过 if (props.fixClickShow) { // 点击固定标签后隐藏 props.fixedTags[index].show = false; } let tag = { name: item.name, } state.tagsArr.push(tag); } } // 自定义输入标签,添加到输入框内 const addTags = () => { let result = validateFn(currentVal.value); if (result) { // 手动输入的标签,判断标签内容和固定标签内容是否一致,一致替换为固定标签的颜色 for (let i in props.fixedTags) { if (props.fixedTags[i].name === currentVal.value) { if (props.fixClickShow) { // 点击固定标签后隐藏 props.fixedTags[i].show = false; } break; } } let tag = { name: currentVal.value, } state.tagsArr.push(tag); currentVal.value = ""; } } /** * 验证数据 * @param validateName:验证标签内容是否重复 * @param from: 来源 custom: 手动输入 fixed: 固定标签 */ const validateFn = (validateName: string): boolean => { if (props.reg) { // 正则验证标签内容 if (!props.reg.test(validateName)) { emits("onValidateTag", VALIDATE.REG); return false; } } if (props.limit) { if (state.tagsArr.length + 1 > props.limit) { // 限制标签个数 emits("onValidateTag", VALIDATE.LIMIT); return false; } } for (let i in state.tagsArr) { if (state.tagsArr[i].name === validateName) { // 判断输入标签是否重复 emits("onValidateTag", VALIDATE.REPEAT); return false; } } return true; } // 删除标签方法 const removeTag = (index: number, item: { name: string }) => { if (props.fixClickShow) { // 点击固定标签后显示 for (let i in props.fixedTags) { if (props.fixedTags[i].name === item.name) { props.fixedTags[i].show = true; break; } } } state.tagsArr.splice(index, 1); } </script> <style scoped lang='scss'> /** 固定标签 */ .fixed-layout { width: 300px; box-sizing: border-box; background-color: white; border-radius: 4px; font-size: 12px; text-align: left; padding-left: 5px; word-wrap: break-word; overflow: hidden; margin-bottom: 10px; } .fixed-label-title { height: 24px; line-height: 22px; position: relative; display: inline-block; padding: 0 8px; color: #495060; font-size: 12px; cursor: pointer; opacity: 1; vertical-align: middle; overflow: hidden; transition: 0.25s linear; } /* 外层div */ .layout { width: 300px; box-sizing: border-box; background-color: white; border: 1px solid #dcdee2; border-radius: 4px; font-size: 12px; text-align: left; padding-left: 5px; word-wrap: break-word; overflow: hidden; } /* 标签 */ .label-box { display: inline-block; font-size: 14px; margin: 3px 4px 3px 0; background-color: #f7f7f7; border: 1px solid #e8eaec; border-radius: 3px; } .label-title { height: 24px; line-height: 22px; max-width: 99%; position: relative; display: inline-block; padding-left: 8px; color: #495060; font-size: 12px; cursor: pointer; opacity: 1; vertical-align: middle; overflow: hidden; transition: 0.25s linear; } .label-close { padding: 0 10px 5px 0; opacity: 1; -webkit-filter: none; filter: none; } .label-close:after { content: "x"; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; line-height: 27px; } /* input */ .input-tag { font-size: 12px; border: none; box-shadow: none; outline: none; background-color: transparent; padding: 0; width: auto; width: 300px; // 设置宽度和外层layout一致。保证了光标在输入框最下面 // min-width: 150px; vertical-align: top; height: 32px; color: #495060; line-height: 32px; } </style>
组件调用代码如下:
<!-- InputLabel组件使用 --> <template> <div style="margin-top: 30px"> <!-- 标签组件 --> <InputLabel :fixedTags="fixedTags" :limit="20" @onValidateTag="onValidateTag" :fixClickShow="false" @change="change"></InputLabel> </div> </template> <script lang="ts" setup> import { reactive, ref, toRefs } from 'vue' import InputLabel from '@c/inputLabel/InputLabel.vue'; // 定义标签验证内容 enum VALIDATE { REG, // 正则表达式验证 LIMIT, // 标签数量验证 REPEAT // 标签重复验证 } // 类型定义 interface LabelModel { name: string, } interface FixedTagsModel { fixedTags: { name: string, // 固定标签的名称 show: boolean, // 是否显示隐藏 }[] } // 参数定义 const state = reactive<FixedTagsModel>({ fixedTags: [], // 固定标签 }) // 解构数据 let { fixedTags } = toRefs(state); // 固定标签数据 state.fixedTags = [ { name: "固定标签1", show: true, }, { name: "固定标签2", show: true, }, { name: "固定标签3", show: true, } ] const change = (params: LabelModel[]) => { // 标签值改变 console.log("获取值改变"); } const onValidateTag = (params: number) => { // 标签验证方法 if (params === VALIDATE.LIMIT) { console.log("限制标签数量"); } else if (params === VALIDATE.REG) { console.log("判断标签内容是否符合规则"); } else if (params === VALIDATE.REPEAT) { console.log("判断标签内容是否重复"); } } </script> <style scoped lang='scss'> </style>