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>

 

posted @ 2022-05-16 09:41  哎哟喂~  阅读(2025)  评论(0编辑  收藏  举报