Vue3基于Ant Design of Vue 2通过Composition API封装动态添加删除标签组件

1,046 阅读2分钟

动态标签组件

Ant Design of Vue现在已经支持vue3,虽然动态添加删除标签组件,在Ant Design of Vue2.0.0官网的文档,标签Tag中是有的,但是不是使用vue3的composition API实现的。而本文的目标就是通过vue3的composition API 实现封装动态添加删除标签组件。

一、安装Ant Design of Vue 2.0

首先新建vue3.0的步骤就不赘述,网上有很多相关文章,直接开始安装依赖,安装好后请注意引入项目,具体可参考Ant Design of Vue 2.0官方文档。

//默认安装命令 v2可安不上
npm install ant-design-vue --save
//检查版本如果不是v2 可执行升级
npm i --save ant-design-vue@next
//main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

//ant-design-vue 组件库
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';

const app = createApp(App);

app.use(router);
app.use(Antd);
app.mount('#app');

二、新建文件夹和文件

在components新建DynamicTags目录

//路径:src/components/DynamicTags

在DynamicTags新建js目录

//路径:src/components/DynamicTags/js

在DynamicTags下js目录新建index.ts

//路径:src/components/DynamicTags/js/index.ts

在DynamicTags新建index.vue文件

//路径:src/components/DynamicTags/index.vue

DynamicTags下的index.vue基础结构

//src/components/DynamicTags/index.vue
<template>
    <a-row>

    </a-row>
</template>

<script lang="ts">
    import {defineComponent} from 'vue'
    export default defineComponent({
        components:{},
        props:{},
        emits: [],
        setup(){
            return {

            }
        }
    })
</script>

<style scoped>

</style>

新建src/view/DynamicTagsPage.vue

//src/vie/DynamicTagsPage.vue
<template>
    <div>
        <DynamicTags/>
    </div>
</template>

<script>
    import DynamicTags from '@/components/DynamicTags/index.vue'
    export default {
        components:{
            DynamicTags,
        }
    }
</script>

<style scoped>

</style>

三、功能实现

(1)实现tag遍历显示

<template>
    <a-row type="flex" align="middle">
        <a-tag
                v-for="(item, index) in tags"
                closable
                color="blue"
                :key="index"
        >
            {{ item }}
        </a-tag>
    </a-row>
</template>

<script lang="ts">
    import {defineComponent,ref} from 'vue'
    export default defineComponent({
        components:{},
        props:{},
        emits: [],
        setup(){
            //标签数据
            const tags = ref<string[]>(['111','222']);
            return {
                tags
            }
        }
    })
</script>

<style scoped>

</style>

(2)实现添加按钮和输入框显示

<template>
    <a-row type="flex" align="middle">
        <a-tag
                v-for="(item, index) in tags"
                closable
                color="blue"
                :key="index"
        >
            {{ item }}
        </a-tag>
        <a-input
                type="text"
                size="small"
                v-if="inputVisible"
                :style="{ width: '110px' }"
        />
        <a-tag v-else-if="btnVisible">
            <plus-outlined/> 添加标签
        </a-tag>
    </a-row>
</template>

<script lang="ts">
    import {defineComponent,ref} from 'vue'
    import {PlusOutlined} from '@ant-design/icons-vue';
    export default defineComponent({
        components:{
            PlusOutlined
        },
        props:{},
        emits: [],
        setup(){
            //标签数据
            const tags = ref<string[]>(['111','222']);
            //按钮显隐
            const btnVisible = ref(true);
            //输入框显隐
            const inputVisible = ref(false);

            return {
                tags,
                btnVisible,
                inputVisible
            }
        }
    })
</script>

<style scoped>

</style>

(3)实现点击添加按钮显示输入框

<template>
    <a-row type="flex" align="middle">
        <a-tag
                v-for="(item, index) in tags"
                closable
                color="blue"
                :key="index"
        >
            {{ item }}
        </a-tag>
        <a-input
                ref="inputDom"
                type="text"
                size="small"
                v-if="inputVisible"
                :style="{ width: '110px' }"
        />
        <a-tag v-else-if="btnVisible" @click="showInput">
            <plus-outlined/> 添加标签
        </a-tag>
    </a-row>
</template>

<script lang="ts">
    import {defineComponent,ref,nextTick} from 'vue'
    import {PlusOutlined} from '@ant-design/icons-vue';
    export default defineComponent({
        components:{
            PlusOutlined
        },
        props:{},
        emits: [],
        setup(){
            //输入框Dom
            const inputDom: any = ref(null);
            //标签数据
            const tags = ref<string[]>(['111','222']);
            //按钮显隐
            const btnVisible = ref(true);
            //输入框显隐
            const inputVisible = ref(false);

            //显示输入框
            const showInput = () => {
                inputVisible.value = true;
                btnVisible.value = true;
                nextTick(() => {
                    inputDom.value.focus();
                })
            };

            return {
                inputDom,
                tags,
                btnVisible,
                inputVisible,
                showInput
            }
        }
    })
</script>

<style scoped>

</style>

(4)实现输入文字新增标签

<template>
    <a-row type="flex" align="middle">
        <a-tag
                v-for="(item, index) in tags"
                closable
                color="blue"
                :key="index"
        >
            {{ item }}
        </a-tag>
        <a-input
                ref="inputDom"
                type="text"
                size="small"
                v-if="inputVisible"
                v-model:value="inputValue"
                :style="{ width: '110px' }"
                @blur="handleInputConfirm"
                @keyup.enter="handleInputConfirm"
        />
        <a-tag v-else-if="btnVisible" @click="showInput">
            <plus-outlined/> 添加标签
        </a-tag>
    </a-row>
</template>

<script lang="ts">
    import {defineComponent,ref,nextTick} from 'vue'
    import {PlusOutlined} from '@ant-design/icons-vue';
    export default defineComponent({
        components:{
            PlusOutlined
        },
        props:{},
        emits: [],
        setup(){
            //输入框Dom
            const inputDom: any = ref(null);
            //标签数据
            const tags = ref<string[]>(['111','222']);
            //按钮显隐
            const btnVisible = ref(true);
            //输入框显隐
            const inputVisible = ref(false);
            //当前输入框的值
            const inputValue = ref('');

            //显示输入框
            const showInput = () => {
                inputVisible.value = true;
                btnVisible.value = true;
                nextTick(() => {
                    inputDom.value.focus();
                })
            };

            //完成输入
            const handleInputConfirm = () => {
                if (inputValue.value.trim()) {
                    tags.value.push(inputValue.value);
                }
                inputVisible.value = false;
                inputValue.value = '';
            };

            return {
                inputDom,
                tags,
                btnVisible,
                inputVisible,
                inputValue,
                showInput,
                handleInputConfirm
            }
        }
    })
</script>

<style scoped>

</style>

(5)实现点击删除当前标签

<template>
    <a-row type="flex" align="middle">
        <a-tag
                v-for="(item, index) in tags"
                closable
                color="blue"
                :visible="true"
                :key="index"
                @close="handleClose(index)"
        >
            {{ item }}
        </a-tag>
        <a-input
                ref="inputDom"
                type="text"
                size="small"
                v-if="inputVisible"
                v-model:value="inputValue"
                :style="{ width: '110px' }"
                @blur="handleInputConfirm"
                @keyup.enter="handleInputConfirm"
        />
        <a-tag v-else-if="btnVisible" @click="showInput">
            <plus-outlined/> 添加标签
        </a-tag>
    </a-row>
</template>

<script lang="ts">
    import {defineComponent,ref,nextTick} from 'vue'
    import {PlusOutlined} from '@ant-design/icons-vue';
    export default defineComponent({
        components:{
            PlusOutlined
        },
        props:{},
        emits: [],
        setup(){
            //输入框Dom
            const inputDom: any = ref(null);
            //标签数据
            const tags = ref<string[]>(['111','222']);
            //按钮显隐
            const btnVisible = ref(true);
            //输入框显隐
            const inputVisible = ref(false);
            //当前输入框的值
            const inputValue = ref('');

            //删除某一个
            const handleClose = (index: number) => {
                tags.value.splice(index, 1);
                btnVisible.value = true;
            };
            //显示输入框
            const showInput = () => {
                inputVisible.value = true;
                btnVisible.value = true;
                nextTick(() => {
                    inputDom.value.focus();
                })
            };

            //完成输入
            const handleInputConfirm = () => {
                if (inputValue.value.trim()) {
                    tags.value.push(inputValue.value);
                }
                inputVisible.value = false;
                inputValue.value = '';
            };

            return {
                inputDom,
                tags,
                btnVisible,
                inputVisible,
                inputValue,
                handleClose,
                showInput,
                handleInputConfirm,
            }
        }
    })
</script>

<style scoped>

</style>

(6)实现监听tags数据变化通过change事件返回给父组件

//src/vie/DynamicTagsPage.vue
<template>
    <div>
        <DynamicTags @change="tagsChange"/>
    </div>
</template>

<script>
    import DynamicTags from '@/components/DynamicTags/index.vue'
    export default {
        components:{
            DynamicTags,
        },
        setup(){
            const  tagsChange = e=>{
                console.log(e)
            };
            return{
                tagsChange
            }
        }
    }
</script>

<style scoped>

</style>

<template>
    <a-row type="flex" align="middle">
        <a-tag
                v-for="(item, index) in tags"
                closable
                color="blue"
                :visible="true"
                :key="index"
                @close="handleClose(index)"
        >
            {{ item }}
        </a-tag>
        <a-input
                ref="inputDom"
                type="text"
                size="small"
                v-if="inputVisible"
                v-model:value="inputValue"
                :style="{ width: '110px' }"
                @blur="handleInputConfirm"
                @keyup.enter="handleInputConfirm"
        />
        <a-tag v-else-if="btnVisible" @click="showInput">
            <plus-outlined/> 添加标签
        </a-tag>
    </a-row>
</template>

<script lang="ts">
    import {defineComponent,ref,toRaw,nextTick,watch} from 'vue'
    import {PlusOutlined} from '@ant-design/icons-vue';
    export default defineComponent({
        components:{
            PlusOutlined
        },
        props:{},
        emits: ["change"],
        setup(props,context){
            //输入框Dom
            const inputDom: any = ref(null);
            //标签数据
            const tags = ref<string[]>(['111','222']);
            //按钮显隐
            const btnVisible = ref(true);
            //输入框显隐
            const inputVisible = ref(false);
            //当前输入框的值
            const inputValue = ref('');

            //删除某一个
            const handleClose = (index: number) => {
                tags.value.splice(index, 1);
                btnVisible.value = true;
            };
            //显示输入框
            const showInput = () => {
                inputVisible.value = true;
                btnVisible.value = true;
                nextTick(() => {
                    inputDom.value.focus();
                })
            };

            //完成输入
            const handleInputConfirm = () => {
                if (inputValue.value.trim()) {
                    tags.value.push(inputValue.value);
                }
                inputVisible.value = false;
                inputValue.value = '';
            };

            watch(tags.value, (newValue, oldValue) => {
                context.emit("change", toRaw(tags.value));
            });

            return {
                inputDom,
                tags,
                btnVisible,
                inputVisible,
                inputValue,
                handleClose,
                showInput,
                handleInputConfirm,
            }
        }
    })
</script>

<style scoped>

</style>

(7)增加组件props参数,方便在不同场景使用

//src/view/DynamicTagsPage.vue
<template>
    <div>
        <DynamicTags 
            :maxLength="3" 
            :inputMaxLenght="6" 
            @change="tagsChange" 
        />
    </div>
</template>

<script>
    import DynamicTags from '@/components/DynamicTags/index.vue'
    export default {
        components:{
            DynamicTags,
        },
        setup(){
            const  tagsChange = e=>{
                console.log(e)
            };
            return{
                tagsChange
            }
        }
    }
</script>

<style scoped>

</style>


<template>
    <a-row type="flex" align="middle">
        <a-tag
                v-for="(item, index) in tags"
                closable
                color="blue"
                :visible="true"
                :key="index"
                @close="handleClose(index)"
        >
            {{ item }}
        </a-tag>
        <a-input
                ref="inputDom"
                type="text"
                size="small"
                v-if="inputVisible"
                v-model:value="inputValue"
                :style="{ width: '110px' }"
                @blur="handleInputConfirm"
                @keyup.enter="handleInputConfirm"
        />
        <a-tag v-else-if="btnVisible" @click="showInput">
            <plus-outlined/> 添加标签
        </a-tag>
    </a-row>
</template>

<script lang="ts">
    import {defineComponent,ref,toRaw,nextTick,watch} from 'vue'
    import {PlusOutlined} from '@ant-design/icons-vue';
    export default defineComponent({
        components:{
            PlusOutlined
        },
        props:{
            maxLength: Number,
            inputMaxLenght: Number
        },
        emits: ["change"],
        setup(props: any,context: any){
            //输入框Dom
            const inputDom: any = ref(null);
            //标签数据
            const tags = ref<string[]>([]);
            //按钮显隐
            const btnVisible = ref(true);
            //输入框显隐
            const inputVisible = ref(false);
            //当前输入框的值
            const inputValue = ref('');
            //输入框最大长度
            const itemMaxLenght = ref(props.inputMaxLenght);

            //删除某一个
            const handleClose = (index: number) => {
                tags.value.splice(index, 1);
                if (tags.value.length + 1 > props.maxLength) {
                    btnVisible.value = false;
                }
                btnVisible.value = true;
            };
            //显示输入框
            const showInput = () => {
                if (tags.value.length + 1 > props.maxLength) {
                    inputVisible.value = false;
                    btnVisible.value = false;
                    return false;
                }
                inputVisible.value = true;
                btnVisible.value = true;
                nextTick(() => {
                    inputDom.value.focus();
                })
            };
            //完成输入
            const handleInputConfirm = () => {
                if (inputValue.value.trim()) {
                    tags.value.push(inputValue.value);
                }
                if (tags.value.length + 1 > props.maxLength) {
                    btnVisible.value = false;
                }
                inputVisible.value = false;
                inputValue.value = '';
            };

            watch(tags.value, (newValue, oldValue) => {
                context.emit("change",toRaw(tags.value));
            });

            return {
                inputDom,//输入框Dom
                tags,
                inputVisible,
                inputValue,
                itemMaxLenght,
                btnVisible,
                handleClose,
                showInput,
                handleInputConfirm,
            }
        }
    })
</script>

<style scoped>

</style>

四、完整代码

DynamicTags/index.vue

<template>
    <a-row type="flex" align="middle" class="addTags">
        <a-tag 
            :key="index" 
            v-for="(item, index) in tags" 
            closable 
            color="blue" 
            :visible="true" 
            @close="handleClose(index)">
            {{ item }}
        </a-tag>
        <a-input
                ref="inputDom"
                type="text"
                size="small"
                v-if="inputVisible"
                v-model:value="inputValue"
                :maxlength="itemMaxLenght"
                :style="{ width: '110px' }"
                @blur="handleInputConfirm"
                @keyup.enter="handleInputConfirm"
        />
        <a-tag v-else-if="btnVisible" @click="showInput">
            <plus-outlined /> 添加标签
        </a-tag>
    </a-row>
</template>

<script lang="ts">
    import {defineComponent,ref,toRaw,nextTick,watch} from 'vue'
    import {PlusOutlined} from '@ant-design/icons-vue';

    export default defineComponent({
        components:{
            PlusOutlined
        },
        props:{
            maxLength: Number,
            inputMaxLenght: Number
        },
        emits: ["change"],
        setup(props: any,context: any){
            //输入框Dom
            const inputDom: any = ref(null);
            //标签数据
            const tags = ref<string[]>([]);
            //按钮显隐
            const btnVisible = ref(true);
            //输入框显隐
            const inputVisible = ref(false);
            //当前输入框的值
            const inputValue = ref('');
            //输入框最大长度
            const itemMaxLenght = ref(props.inputMaxLenght);
        
            //删除某一个
            const handleClose = (index: number) => {
                tags.value.splice(index, 1);
                if (tags.value.length + 1 > props.maxLength) {
                    btnVisible.value = false;
                }
                btnVisible.value = true;
            };
            //显示输入框
            const showInput = () => {
                if (tags.value.length + 1 > props.maxLength) {
                    inputVisible.value = false;
                    btnVisible.value = false;
                    return false;
                }
                inputVisible.value = true;
                btnVisible.value = true;
                nextTick(() => {
                    inputDom.value.focus();
                })
            };
            //完成输入
            const handleInputConfirm = () => {
                if (inputValue.value.trim()) {
                    tags.value.push(inputValue.value);
                }
                if (tags.value.length + 1 > props.maxLength) {
                    btnVisible.value = false;
                }
                inputVisible.value = false;
                inputValue.value = '';
            };
        
            watch(tags.value, (newValue, oldValue) => {
                context.emit("change", toRaw(tags.value));
            });
        
            return {
                inputDom,//输入框Dom
                tags,
                inputVisible,
                inputValue,
                itemMaxLenght,
                btnVisible,
                handleClose,
                showInput,
                handleInputConfirm,
            }
        }
    })
</script>

<style scoped>

</style>

五、抽离逻辑代码到单独文件

DynamicTags/js/index.ts

import {ref,toRaw,nextTick,watch} from 'vue'

export default function dynamicTags(props: any,context: any) {
    //输入框Dom
    const inputDom: any = ref(null);
    //标签数据
    const tags = ref<string[]>([]);
    //按钮显隐
    const btnVisible = ref(true);
    //输入框显隐
    const inputVisible = ref(false);
    //当前输入框的值
    const inputValue = ref('');
    //输入框最大长度
    const itemMaxLenght = ref(props.inputMaxLenght);

    //删除某一个
    const handleClose = (index: number) => {
        tags.value.splice(index, 1);
        if (tags.value.length + 1 > props.maxLength) {
            btnVisible.value = false;
        }
        btnVisible.value = true;
    };
    //显示输入框
    const showInput = () => {
        if (tags.value.length + 1 > props.maxLength) {
            inputVisible.value = false;
            btnVisible.value = false;
            return false;
        }
        inputVisible.value = true;
        btnVisible.value = true;
        nextTick(() => {
            inputDom.value.focus();
        })
    };
    //完成输入
    const handleInputConfirm = () => {
        if (inputValue.value.trim()) {
            tags.value.push(inputValue.value);
        }
        if (tags.value.length + 1 > props.maxLength) {
            btnVisible.value = false;
        }
        inputVisible.value = false;
        inputValue.value = '';
    };

    watch(tags.value, (newValue, oldValue) => {
        context.emit("change", toRaw(tags.value));
    });

    return {
        inputDom,//输入框Dom
        tags,
        inputVisible,
        inputValue,
        itemMaxLenght,
        btnVisible,
        handleClose,
        showInput,
        handleInputConfirm,
    }
}

DynamicTags/index.vue

<template>
    <a-row type="flex" align="middle" class="addTags">
        <a-tag 
            :key="index" 
            v-for="(item, index) in tags" 
            closable color="blue" 
            :visible="true" 
            @close="handleClose(index)"
        >
            {{ item }}
        </a-tag>
        <a-input
                ref="inputDom"
                type="text"
                size="small"
                v-if="inputVisible"
                v-model:value="inputValue"
                :maxlength="itemMaxLenght"
                :style="{ width: '110px' }"
                @blur="handleInputConfirm"
                @keyup.enter="handleInputConfirm"
        />
        <a-tag v-else-if="btnVisible" @click="showInput">
            <plus-outlined /> 添加标签
        </a-tag>
    </a-row>
</template>

<script lang="ts">
    import {defineComponent} from 'vue';
    import {PlusOutlined} from '@ant-design/icons-vue';
    import dynamicTags from './js/index';
    export default defineComponent({
        components:{
            PlusOutlined
        },
        props:{
            maxLength: Number,
            inputMaxLenght: Number
        },
        emits: ["change"],
        setup(props,context){
            const {
                inputDom,
                tags,
                inputVisible,
                inputValue,
                itemMaxLenght,
                btnVisible,
                handleClose,
                showInput,
                handleInputConfirm,
            } = dynamicTags(props,context);

            return {
                inputDom,
                tags,
                inputVisible,
                inputValue,
                itemMaxLenght,
                btnVisible,
                handleClose,
                showInput,
                handleInputConfirm,
            }
        }
    })
</script>

<style scoped>

</style>

本文演示视频:点击浏览

项目源代码:点击浏览

更多前端内容欢迎关注公众号:天小天个人网