☰
el-tag组件实战:从基础用法到高阶玩法,避开后台开发那些坑
2026/10/13 17:11:02 网站建设 项目流程

用了这么多年前端框架,<el-tag>大概是我在管理后台项目里见到频率最高的组件之一。小到一条订单的状态标记,大到一套完整的标签管理系统,它都能参与其中。但多数人只把它当成一个带颜色的圆角块,真正遇到动态增删、样式定制、事件穿透这些实际需求时,还是会踩不少坑。这篇文章从我实际做过的几个后台项目出发,把<el-tag>从基础用法到高阶玩法完整拆一遍,适合正在用 Vue + Element 体系开发后台,尤其是刚接触标签业务场景的开发者参考。

1. 先说清楚:el-tag到底解决什么问题

1.1 标签在业务里的真实角色

标签这个交互形式,本质上是把“状态”或“分类”这种离散信息,用一种低成本的视觉方式呈现出来。你可以回想一下自己做过的后台页面:订单列表有“已支付、待发货、已退款”,审批流里有“待审核、已通过、已驳回”,内容管理里有“草稿、发布中、已下线”。这些状态如果全用纯文字描述,界面会变得平淡且难以快速扫视;如果用大篇幅的色块又太重。<el-tag>正好卡在这个中间地带——小尺寸、高信息密度、颜色就能表达一个语义。

这也是它被高频使用但不被重视的原因。它看起来太简单了,简单到容易让人忽略细节。但真正把它的属性、事件和扩展用法吃透之后,你会发现它还能做很多事,不只是拿来展示一个红红绿绿的标签。

1.2 为什么选el-tag而不自己写

有人会说,一个标签不就是一行CSS加个span吗,何必用组件库?我在早期也确实写过一阵子自定义标签,后来被两条教训教育了:第一,自定义标签的样式边界很难统一。同一个后台系统里,A页面写的圆角是4px,B页面写的圆角是8px,视觉上很快就乱了。第二,交互逻辑比看起来复杂。标签关闭时的动效、动态增减时的状态同步、键盘操作的可达性,自己实现一遍的工时成本并不低。

<el-tag>的存在相当于把这些坑提前填了。Element体系帮我们封装好了类型、尺寸、主题、可关闭、过渡动画这些高频能力,而且它的API设计足够简洁,几乎不需要查文档就能上手。选择它,本质上是在为团队的统一性和开发效率买单。

2. 基础用法拆解:一次搞懂表单里的这些属性

<el-tag>的API数量不算多,但每个属性在真实项目里都有切切实实的用途。我按使用频率和踩坑概率,挑几个重点展开。

2.1 颜色语义:type和effect怎么搭配

最常被问到的一组属性是type和effect。type控制标签的语义颜色,可选值是primary、success、warning、danger和info。默认情况下表示中性信息,不传type时的灰色标签适合用来展示“默认状态”。

effect是很多人容易忽略的一个属性,它控制的是标签的展现形态。默认值dark是纯色填充底、白色字的样式;light是浅色背景底、深色字的样式;plain则更淡,只保留一条浅色描边和深色字。三种效果可以跟每种type任意组合,在实际业务里我有自己的固定搭配习惯:列表里的关键状态用dark,强调高优先级;表单附近的辅助信息用light;筛选条件里已选中的项目用plain,这样可以弱化单个标签的视觉重量,让整体界面更透气。

这里给一个参考写法:

<el-tag type="success">已通过</el-tag> <el-tag type="success" effect="light">v2.3.0 已发布</el-tag> <el-tag type="danger" effect="plain">已驳回</el-tag>

我试过一段时间后发现,effect="plain"在某些场景下有轻微的问题:当标签背景色和页面背景色很接近时,视觉区分度会下降,尤其在高分屏上更明显。所以如果你的页面背景不是纯白而是一种浅浅的灰,建议尽量用light而不是plain。

2.2 尺寸、圆角、描边,细节里的小坑

size属性控制标签的整体大小,可选值是large、default和small。这个属性在多个表单组件一起使用时特别容易出问题——比如el-input的尺寸是small,而旁边放一个默认尺寸的el-tag,两者高度不一致,视觉上就得很难受。所以在表单内嵌标签的场景,我一般统一按size="small"来写,保证和其他输入类组件对齐。

hit属性是一个相对冷门但很有用的属性,它会给标签增加一个描边效果,让标签看起来有一个“边框”。如果标签是放在一个白色卡片容器里,背景色又很浅,不加边框时标签边缘会“融”进背景里。加上hit之后,边界感立马上来了。这个属性适合用在状态卡片、标签面板这类承载颜色较多的区域。

round属性控制是否圆角,默认false时是常规的小圆角,设为true后变成胶囊形状。注意一个细节:胶囊形标签在视觉上更“活泼”,在偏正式的后台管理系统里慎用,否则容易让界面显得不够克制。这属于设计风格统一性的问题,最好跟设计师对齐后再决定。

还有一个细节比较隐晦:disable-transitions属性。默认情况下标签出现、消失时会有一个淡入淡出或缩放动画。在极少数情况下这个动画会干扰我们通过v-for批量渲染标签的稳定性表现,如果发现标签切换时有奇怪的短暂闪烁,可以给标签加上disable-transitions试试。

2.3 从事件到交互:怎么让标签“动”起来

标签本身不是一个高交互组件,但closable属性能让它具备“被删除”的交互能力。布尔类型,传入true后标签右侧会出现一个关闭图标。

<el-tag v-for="(item, index) in tagList" :key="item.id" closable :disable-transitions="true" @close="removeTag(index)" > {{ item.name }} </el-tag>

@close是关闭时触发的事件,也就是删除标签这个业务动作的入口。我见过一些同事在这里踩坑:想着在@close里把标签从数组里过滤掉,但完全没意识到事件里拿不到当前数据项。实际项目里通常的处理方式是给@close传索引或唯一id,然后在方法里通过索引或id去操作原数组。这一节后面会详细演示,这里先记住一个原则:任何列表型组件的事件处理,都不要依赖闭包变量做直接修改,要基于明确的下标或标识。另外,@click也可以绑定在标签上。比如“点击标签跳转某个筛选页面”“点击标签打开详情面板”,在管理后台里都是很常见的设计。

3. 动态增删标签:管理后台最常用的完整玩法

标签如果只是静态展示,那价值有限。真正让它“活”起来的场景是动态增删。比如用户给文章打标签、商品设置属性标签、任务标注自定义分类,这些都需要让标签可以新增、删除,有时还要能被用户直接输入生成。这一节我把最常见的两种玩法完整实现一遍。

3.1 先搭一个标签管理的小Demo

先从一个简单的开始:标签列表 + 关闭按钮,这就构成了一个可删除的标签集合。

<template> <div class="tag-demo"> <el-tag v-for="tag in tags" :key="tag.id" closable @close="handleClose(tag)" > {{ tag.name }} </el-tag> </div> </template> <script setup> import { ref } from 'vue' const tags = ref([ { id: 1, name: '前端' }, { id: 2, name: 'Vue3' }, { id: 3, name: '组件实践' }, ]) const handleClose = (tag) => { tags.value = tags.value.filter((item) => item.id !== tag.id) } </script>

这里的关键点是handleClose的写法。直接传入当前tag对象,然后在方法里用filter过滤掉这个对象。这样写比“通过index去splice”更直观,而且从数据的角度来说,“删除某个唯一标识对应的数据”在业务语义上更清晰。如果你的数据里没有id这样的唯一标识,也可以用索引下标:

<el-tag v-for="(tag, index) in tags" :key="index" closable @close="handleClose(index)" > {{ tag }} </el-tag> <script setup> const handleClose = (index) => { tags.value.splice(index, 1) } </script>

两种方式都可以,但我更推荐有唯一标识的方案。原因很实际:列表排序、筛选、搜索之后,索引可能会变化,而唯一标识不会变。

3.2 再做一个“输入框+按钮”的标签添加器

如果说删除是基础操作,新增输入则是把el-tag推向“功能组件”的关键。常见的方案是用el-input和el-button组合,点击按钮或按回车后把输入值转换成新的标签。

<template> <div> <el-tag v-for="tag in tags" :key="tag" closable @close="removeTag(tag)" > {{ tag }} </el-tag> <el-input v-if="inputVisible" ref="inputRef" v-model="newTag" size="small" class="tag-input" @keyup.enter="addTag" @blur="addTag" /> <el-button v-else size="small" @click="showInput">+ 新增标签</el-button> </div> </template> <script setup> import { ref, nextTick } from 'vue' const tags = ref(['Vue', 'Element']) const newTag = ref('') const inputVisible = ref(false) const showInput = async () => { inputVisible.value = true await nextTick() // 动态渲染的输入框需要等DOM挂载后再聚焦 inputRef.value.focus() } const addTag = () => { const value = newTag.value.trim() if (!value) { inputVisible.value = false return } tags.value.push(value) newTag.value = '' inputVisible.value = false } const removeTag = (tag) => { tags.value = tags.value.filter((item) => item !== tag) } </script>

这个方案里有两个细节我要重点提醒一下。

第一,@keyup.enter和@blur绑定的是同一个addTag方法,会有一个隐藏的问题:当用户按下回车时,输入框紧接着也会触发失焦,此时addTag可能被调用两次。解决办法是给addTag加一个“锁”,或者使用一个标志位来判断当前是否已经处理过这次输入。比如:

let addLock = false const addTag = () => { if (addLock) return const value = newTag.value.trim() if (!value) { addLock = true inputVisible.value = false setTimeout(() => { addLock = false }, 0) return } tags.value.push(value) newTag.value = '' inputVisible.value = false }

这个方式简单粗暴但有效。我在项目里遇到过一次这个bug,场景是:用户快速回车两次,结果同一个标签被添加了两遍,还得手动删掉,体验非常差。

第二,输入框的聚焦问题。inputVisible变为true后,el-input不会自动聚焦,必须显式调用focus()方法。由于Vue的DOM更新是异步的,需要在nextTick()之后再聚焦,否则会取不到ref。

3.3 批量删除、去重与长度限制的处理细节

上面的Demo能满足基本要求,但真实业务往往会要求更多细节:标签不能重复、标签长度有限制、一次删除多个标签、标签数量上限。这些需求看似零散,其实都是对addTag和removeTag这两个方法做的增强。

去重逻辑,我一般直接在addTag里加一层判断:

const addTag = () => { const value = newTag.value.trim() if (!value) return if (tags.value.includes(value)) { // 已存在,清空输入并直接关闭 newTag.value = '' inputVisible.value = false return } tags.value.push(value) newTag.value = '' inputVisible.value = false }

长度限制也在这里处理,比如限制在10个字符以内:

const MAX_TAG_LENGTH = 10 const addTag = () => { const value = newTag.value.trim() if (!value) return if (value.length > MAX_TAG_LENGTH) { // 提示或者截断 return } // ... }

标签数量上限可以单独用一个常量控制:

const MAX_TAG_COUNT = 20 const showInput = () => { if (tags.value.length >= MAX_TAG_COUNT) return inputVisible.value = true }

批量删除的使用场景通常是“管理后台的标签管理页”,用户勾选多个标签后点击“删除”。这时更通用的做法是给标签加一个勾选按钮,或者用el-check-tag之类的组件替代。这里提一下el-check-tag:它是Element Plus里一个独立的标签式选择组件,适合单选或多选状态切换的场景。如果你发现“可点击选中、再点取消”这个需求已经很明确,直接用el-check-tag会比在el-tag上手动维护状态省很多事。

<el-check-tag v-model="checked" @change="onChange"> 可勾选标签 </el-check-tag>

注意el-check-tag和el-tag的API并不完全一致,它没有closable、没有type,它的核心交互是选中态。两者适合的场景不同,别强行替换。

4. 高频率场景:从状态标记到分类管理

标签这种控件最大的魅力在于它“轻”。轻到可以在表格里、筛选区、卡片视图等大量场景中组合使用。下面是我在真实项目中反复用到的高频组合。

4.1 订单状态、审核状态的展示套路

后台列表页里最常见的诉求就是状态展示。订单状态字段在数据层往往是数字或英文字符串,展示层要变成用户能看懂的文案和颜色。我通常会封装一个状态映射字典:

const orderStatusMap = { 10: { text: '待付款', type: 'warning' }, 20: { text: '已付款', type: 'success' }, 30: { text: '已发货', type: 'primary' }, 40: { text: '已完成', type: 'info' }, 50: { text: '已取消', type: 'danger' }, }

然后在表格列模板里这样用:

<el-table-column prop="status" label="状态"> <template #default="{ row }"> <el-tag :type="orderStatusMap[row.status]?.type || 'info'"> {{ orderStatusMap[row.status]?.text || '未知' }} </el-tag> </template> </el-table-column>

这样写有两个好处:第一,表格数据和展示逻辑完全分离,后面如果后端改了状态值,只需要改映射字典,不用去翻模板;第二,视觉语义统一,“危险”永远是红色,“成功”永远是绿色,不会出现A页面用绿色表示删除、B页面用绿色表示通过这种混乱情况。

4.2 标签在筛选和查询条件里的应用

标签另一个容易被忽视的用法是作为“已选筛选条件”的展示容器。很多后台的筛选区会有多个条件(状态、分类、时间、关键字),用户设置完条件后,点击查询按钮,页面顶部会出现一行“已选条件”的区域,用el-tag逐个展示,并支持点击close图标单独移除条件。这种方式对用户的友好程度远高于“让用户回到筛选区重新操作”。

<template> <div class="filter-tags"> <el-tag v-for="cond in activeConditions" :key="cond.key" closable effect="light" @close="removeCondition(cond)" > {{ cond.label }}: {{ cond.value }} </el-tag> </div> </template>

当移除全部条件时,一般要重新拉取一次列表数据,或者清空当前结果集回到默认查询。这里注意一个细节:effect="light"加上closable的组合,在视觉上比dark更柔和,更适合出现在页面头部的高频区域,不会抢主内容视觉。

4.3 图表、列表里tags数据的联动显示

还有一些场景是把标签当作“多值字段”的展示方式。比如一篇文章可以归属多个栏目,一个商品可以打多个属性标签。后端返回的是一个数组,前端直接循环渲染即可。但这时最容易出现一个问题:直接v-for渲染一个数组时,如果没有唯一key,删除其中一个标签可能导致渲染错乱,尤其是在后续有动画的情况下。

推荐做法是:如果后端没有返回专门的id,可以自己组合一个“值+下标”的复合key,或者干脆在拿到数据后为每条tag补充一个临时id字段。

还有一点要特别留意:如果在同一个组件中,一个el-tag的列表里同时出现了“可删除”和“不可删除”的数据,建议拆成两个渲染区域,或者通过一个字段控制closable的布尔值,避免交互语义混乱。

5. 常见问题与避坑记录

这部分是我最想分享的内容。代码写一遍是很容易的,但遇到问题能快速定位才是更值钱的技能。我把这些年用<el-tag>期间真实遇到过的坑,按排查思路整理一下。

5.1 移除事件不生效的排查顺序

如果你的@close事件完全没有触发,先别急着怀疑组件坏了。按这个顺序排查:

第一,确认是否传了closable属性。这个属性是控制关闭图标是否显示的,没有它,关闭图标压根不渲染,事件当然也没法触发。

第二,确认事件名是不是写错了。el-tag关闭事件在Vue3 + Element Plus中是@close,在Vue2的Element UI中也是@close。但如果你是自定义封装了一层组件,把原生事件透传忘了处理,外层就监听不到。

第三,排查按钮是否被覆盖。自定义样式的容器中,关闭图标可能被其他元素的z-index或背景遮挡,实际上你点击的根本不是关闭按钮。这个问题在标签放入带overflow属性的容器里时尤其容易遇到。

第四,确认数据是否真的被过滤掉。有时关闭事件确实触发了,但数组更新后页面没变化。这种情况大概率是直接修改了props来的数组,或者在Vue3的<script setup>中用了普通变量而非ref/reactive,数据变化没有驱动视图更新。

5.2 动态标签闪烁与key重复问题

这是我最常被问到的“标签闪了一下”的问题。现象是:添加或删除标签时,其它不相干的标签位置发生了错乱,或者整组标签重新渲染了一遍。根本原因几乎全是key设置不当。

错误示例:

<el-tag v-for="(tag, index) in tags" :key="index">

用index做key,当数组中间删除一项时,被删除项后面的所有标签的索引都会变化,Vue的diff算法会认为整个列表都变了,从而触发不必要的重建,视觉上就表现为闪烁或者动画错乱。正确的做法是使用数据里的唯一标识:

<el-tag v-for="tag in tags" :key="tag.id">

如果数据真的没有唯一标识,至少要保证key里包含业务内容,比如:

<el-tag v-for="tag in tags" :key="`${tag.name}-${tag.code}`">

省事不是偷懒的理由,key是列表性能和精神状态的保证。

另外还有一个容易踩的坑:当标签需要被“重新创建”时,比如切换编辑模式后标签的样式需要刷新,这时用v-if切换一个大容器会让内部的标签全部重新挂载,反而会破坏动画。这时直接把disable-transitions设为true更稳妥。

5.3 修改默认样式三件套:层级、变量、class穿透

默认样式在业务里总是不够用的。改el-tag的样式有几个层次,按优先级从低到高依次处理:

第一层,自带属性。调整size、type、effect、round这些内置属性,能不改样式就不改。

第二层,使用全局变量。Element Plus支持通过CSS变量定制主题,比如想改标签文字和背景的颜色,可以覆盖实例配置,或者在项目中做样式变量映射。这个方案适合全局统一调整。

第三层,自定义class。给el-tag加一个class属性,在它的父级作用域里配合:deep()选择器修改内部结构样式。比如把默认的小圆角改成直角:

<style scoped> :deep(.custom-tag) { border-radius: 0; } </style>

但是注意一点::deep()会极大降低样式隔离性,改的时候要控制影响范围,最好用独特的class前缀。不要一上来就写Element的类名去覆盖全局样式,很容易把别处的标签也带偏。我吃过一次亏:为了改“某个页面的标签高度”,写了一行全局样式,结果所有的el-tag都变了,上线第二天就被用户反馈说视觉异常。

5.4 其它高频坑位速查表

  • closable标签需要配合唯一key,否则多处复用时删除事件会串数据。
  • v-model在el-tag上并不存在,它的选中态或关闭态都是通过事件对外通知,不要试图用v-model绑定。
  • 在表格列模板里使用el-tag时,建议把条件判断封装成一个函数或映射对象,避免模板里塞过多三目运算符。
  • 如果标签文本比较长,建议在后端就限制长度,或在前端通过CSS控制溢出省略,而不是让标签无限撑宽。
  • el-tag可以内嵌图标实现更丰富的展示,如el-icon加文字组合,但记得给通知类图标设置足够的间距,不要图标紧贴文字。
  • 当标签出现在弹窗、抽屉内时,需要留意弹窗自身的层级,如果标签的关闭图标被遮住,优先检查外层弹窗的z-index,而不是改标签的样式。

6. 从组件到脚手架的进阶思路

前面讲的基本都是“会用”“用好”的问题。真正让我对一个团队的组件使用水平刮目相看的,是看他们有没有把常见模式抽象成公共组件。el-tag虽然简单,但如果你发现项目里有十几个地方都在写“状态映射字典 + v-for + el-tag”,那就是一个明显信号:该封装了。

我的做法是做一个StatusTag组件,接收一个status值和一份映射配置,内部完成颜色、文案、图标的渲染,外部只关心业务数据。这样一个组件落地后,新页面接入时只需要写一行代码,视觉一致性还提高了。如果你做的是大型后台项目,这一点时间投资绝对值回票价。

此外,如果你用的是Element Plus,它其实在持续丰富标签相关的组件。前面提过的el-check-tag就是其中一员。保持对组件库更新内容的关注,有时候一个新组件能帮你省掉几十行自定义逻辑。

7. 我对el-tag使用的一些体会

回到文章开头那句话:el-tag太容易被低估了。我见过很多前端开发者对它停留在“会显示标签”的层面,直到业务要求“标签可新增、可删除、可筛选、可联动”,才开始手忙脚乱地补实现。如果你正在负责一个后台类项目,我建议你花半天时间把el-tag的API完整过一遍,再结合自己项目里的实际场景写一个小Demo,提前踩一遍这些坑。比起将来在业务代码里反复调试,这半天时间的投入是非常值的。

最近一次项目重构,我把所有订单状态和审核状态的展示都收敛到了统一的标签组件里,改样式只动一个文件,新增状态只改一份映射数据,说句实话,运维舒服了不少。希望这篇文章能让你少走几段弯路,也欢迎你在实际使用中遇到奇怪问题时再回来对照这篇速查记录。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询