Skip to content

Input Tag 标签输入框

概述

Input Tag 组件允许用户添加内容作为标签,是一个功能强大的标签输入组件。用户可以通过键盘输入来添加标签,支持自定义触发器、拖拽排序、数量限制等多种功能。

学习目标

  • 掌握 Input Tag 的基本概念和使用场景
  • 学会基础标签输入和管理
  • 了解自定义触发器的配置方法
  • 掌握标签数量限制和状态控制
  • 学会标签的拖拽排序功能
  • 了解分隔符和自定义样式的使用
  • 掌握 API 的完整使用方法

基础用法

基本标签输入

按 Enter 回车键添加输入内容为标签:

vue
<template>
  <div>
    <el-input-tag
      v-model="tags"
      placeholder="请输入标签,按回车添加"
    />
    <p>当前标签: {{ tags }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const tags = ref(['Vue', 'Element Plus'])
</script>

自定义触发器

您可以自定义用于触发输入标签的键位,默认是 Enter 回车键:

vue
<template>
  <div>
    <h4>Enter 键触发</h4>
    <el-input-tag
      v-model="enterTags"
      :trigger="['Enter']"
      placeholder="按 Enter 添加标签"
    />
    
    <h4>空格键触发</h4>
    <el-input-tag
      v-model="spaceTags"
      :trigger="['Space']"
      placeholder="按空格添加标签"
    />
    
    <h4>多键触发</h4>
    <el-input-tag
      v-model="multiTags"
      :trigger="['Enter', 'Space']"
      placeholder="按 Enter 或空格添加标签"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const enterTags = ref([])
const spaceTags = ref([])
const multiTags = ref([])
</script>

功能配置

最大标签数

您可以设置可以添加的标签数量限制:

vue
<template>
  <div>
    <el-input-tag
      v-model="limitedTags"
      :max="3"
      placeholder="最多添加3个标签"
    />
    <p>已添加 {{ limitedTags.length }}/3 个标签</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const limitedTags = ref(['tag1', 'tag2'])
</script>

禁用状态

您可以设置 Input Tag 被禁用:

vue
<template>
  <div>
    <el-input-tag
      v-model="disabledTags"
      disabled
      placeholder="禁用状态"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const disabledTags = ref(['tag1', 'tag2', 'tag3'])
</script>

可清空

您可以设置是否显示清除按钮:

vue
<template>
  <div>
    <el-input-tag
      v-model="clearableTags"
      clearable
      placeholder="可清空的标签输入框"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const clearableTags = ref(['tag1', 'tag2', 'tag3'])
</script>

可拖放

您可以设置是否可以拖动标签:

vue
<template>
  <div>
    <el-input-tag
      v-model="draggableTags"
      draggable
      placeholder="可拖拽排序的标签"
    />
    <p>拖拽标签可以改变顺序</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const draggableTags = ref(['tag1', 'tag2', 'tag3'])
</script>

高级功能

分隔符

当一个分隔符匹配时,您可以添加一个标签:

vue
<template>
  <div>
    <h4>逗号分隔符</h4>
    <el-input-tag
      v-model="commaTags"
      delimiter=","
      placeholder="输入逗号自动添加标签"
    />
    
    <h4>正则表达式分隔符</h4>
    <el-input-tag
      v-model="regexTags"
      :delimiter="/[,;]/"
      placeholder="输入逗号或分号自动添加标签"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const commaTags = ref([])
const regexTags = ref([])
</script>

尺寸

使用 size 属性改变输入框大小:

vue
<template>
  <div>
    <h4>大尺寸</h4>
    <el-input-tag
      v-model="largeTags"
      size="large"
      placeholder="大尺寸标签输入框"
    />
    
    <h4>默认尺寸</h4>
    <el-input-tag
      v-model="defaultTags"
      size="default"
      placeholder="默认尺寸标签输入框"
    />
    
    <h4>小尺寸</h4>
    <el-input-tag
      v-model="smallTags"
      size="small"
      placeholder="小尺寸标签输入框"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const largeTags = ref(['large'])
const defaultTags = ref(['default'])
const smallTags = ref(['small'])
</script>

自定义标签

您可以通过 tag 插槽自定义标签内容:

vue
<template>
  <div>
    <el-input-tag
      v-model="customTags"
      placeholder="自定义标签样式"
    >
      <template #tag="{ tag, index }">
        <el-tag
          :type="getTagType(index)"
          :effect="getTagEffect(index)"
          closable
          @close="removeTag(index)"
        >
          {{ tag }}
        </el-tag>
      </template>
    </el-input-tag>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const customTags = ref(['primary', 'success', 'info', 'warning', 'danger'])

const tagTypes = ['primary', 'success', 'info', 'warning', 'danger']
const tagEffects = ['light', 'dark', 'plain']

const getTagType = (index) => {
  return tagTypes[index % tagTypes.length]
}

const getTagEffect = (index) => {
  return tagEffects[index % tagEffects.length]
}

const removeTag = (index) => {
  customTags.value.splice(index, 1)
}
</script>

自定义前缀和后缀

您可以通过 prefix 和 suffix 插槽自定义 Input Tag 的前缀和后缀:

vue
<template>
  <div>
    <el-input-tag
      v-model="prefixSuffixTags"
      placeholder="带前缀和后缀的标签输入框"
    >
      <template #prefix>
        <el-icon><Search /></el-icon>
      </template>
      <template #suffix>
        <el-icon><Star /></el-icon>
      </template>
    </el-input-tag>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { Search, Star } from '@element-plus/icons-vue'

const prefixSuffixTags = ref(['Vue', 'React'])
</script>

事件处理

标签变化事件

vue
<template>
  <div>
    <el-input-tag
      v-model="eventTags"
      placeholder="监听事件的标签输入框"
      @change="handleChange"
      @add-tag="handleAddTag"
      @remove-tag="handleRemoveTag"
      @focus="handleFocus"
      @blur="handleBlur"
      @clear="handleClear"
    />
    
    <div style="margin-top: 20px;">
      <h4>事件日志:</h4>
      <ul>
        <li v-for="(log, index) in eventLogs" :key="index">
          {{ log }}
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const eventTags = ref(['初始标签'])
const eventLogs = ref([])

const addLog = (message) => {
  eventLogs.value.unshift(`${new Date().toLocaleTimeString()}: ${message}`)
  if (eventLogs.value.length > 10) {
    eventLogs.value.pop()
  }
}

const handleChange = (value) => {
  addLog(`标签变化: ${JSON.stringify(value)}`)
}

const handleAddTag = (tag) => {
  addLog(`添加标签: ${tag}`)
}

const handleRemoveTag = (tag) => {
  addLog(`移除标签: ${tag}`)
}

const handleFocus = () => {
  addLog('输入框获得焦点')
}

const handleBlur = () => {
  addLog('输入框失去焦点')
}

const handleClear = () => {
  addLog('清空所有标签')
}
</script>

实际应用示例

技能标签管理器

vue
<template>
  <div class="skill-manager">
    <h3>技能标签管理器</h3>
    
    <el-form :model="form" label-width="120px">
      <el-form-item label="前端技能">
        <el-input-tag
          v-model="form.frontendSkills"
          :max="10"
          placeholder="添加前端技能标签"
          clearable
          draggable
        >
          <template #tag="{ tag, index }">
            <el-tag
              type="primary"
              effect="light"
              closable
              @close="removeFrontendSkill(index)"
            >
              <el-icon style="margin-right: 4px;"><Star /></el-icon>
              {{ tag }}
            </el-tag>
          </template>
        </el-input-tag>
      </el-form-item>
      
      <el-form-item label="后端技能">
        <el-input-tag
          v-model="form.backendSkills"
          :max="10"
          placeholder="添加后端技能标签"
          clearable
          draggable
        >
          <template #tag="{ tag, index }">
            <el-tag
              type="success"
              effect="light"
              closable
              @close="removeBackendSkill(index)"
            >
              <el-icon style="margin-right: 4px;"><Tools /></el-icon>
              {{ tag }}
            </el-tag>
          </template>
        </el-input-tag>
      </el-form-item>
      
      <el-form-item label="兴趣爱好">
        <el-input-tag
          v-model="form.hobbies"
          delimiter=","
          placeholder="输入兴趣爱好,用逗号分隔"
          clearable
        >
          <template #tag="{ tag, index }">
            <el-tag
              type="info"
              effect="plain"
              closable
              @close="removeHobby(index)"
            >
              {{ tag }}
            </el-tag>
          </template>
        </el-input-tag>
      </el-form-item>
      
      <el-form-item>
        <el-button type="primary" @click="saveProfile">
          保存资料
        </el-button>
        <el-button @click="resetForm">
          重置
        </el-button>
      </el-form-item>
    </el-form>
    
    <div v-if="savedProfile" class="profile-preview">
      <h4>保存的资料:</h4>
      <pre>{{ JSON.stringify(savedProfile, null, 2) }}</pre>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { Star, Tools } from '@element-plus/icons-vue'

const form = reactive({
  frontendSkills: ['Vue', 'React', 'JavaScript'],
  backendSkills: ['Node.js', 'Python'],
  hobbies: ['阅读', '旅行']
})

const savedProfile = ref(null)

const removeFrontendSkill = (index) => {
  form.frontendSkills.splice(index, 1)
}

const removeBackendSkill = (index) => {
  form.backendSkills.splice(index, 1)
}

const removeHobby = (index) => {
  form.hobbies.splice(index, 1)
}

const saveProfile = () => {
  savedProfile.value = { ...form }
  ElMessage.success('资料保存成功!')
}

const resetForm = () => {
  form.frontendSkills = []
  form.backendSkills = []
  form.hobbies = []
  savedProfile.value = null
  ElMessage.info('表单已重置')
}
</script>

<style scoped>
.skill-manager {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.profile-preview {
  margin-top: 20px;
  padding: 15px;
  background-color: #f5f7fa;
  border-radius: 4px;
}

.profile-preview pre {
  margin: 0;
  font-size: 12px;
}
</style>

搜索关键词管理

vue
<template>
  <div class="search-manager">
    <h3>搜索关键词管理</h3>
    
    <el-card>
      <template #header>
        <span>热门搜索词</span>
      </template>
      
      <el-input-tag
        v-model="hotKeywords"
        :max="8"
        placeholder="添加热门搜索关键词"
        clearable
        draggable
        @add-tag="handleAddHotKeyword"
        @remove-tag="handleRemoveHotKeyword"
      >
        <template #tag="{ tag, index }">
          <el-tag
            :type="getHotTagType(index)"
            effect="dark"
            closable
            @close="removeHotKeyword(index)"
          >
            <el-icon style="margin-right: 4px;"><TrendCharts /></el-icon>
            {{ tag }}
          </el-tag>
        </template>
      </el-input-tag>
    </el-card>
    
    <el-card style="margin-top: 20px;">
      <template #header>
        <span>搜索历史</span>
      </template>
      
      <el-input-tag
        v-model="searchHistory"
        :max="20"
        placeholder="搜索历史记录"
        readonly
        clearable
      >
        <template #tag="{ tag, index }">
          <el-tag
            type="info"
            effect="plain"
            closable
            @close="removeSearchHistory(index)"
          >
            <el-icon style="margin-right: 4px;"><Clock /></el-icon>
            {{ tag }}
          </el-tag>
        </template>
      </el-input-tag>
    </el-card>
    
    <div class="actions" style="margin-top: 20px;">
      <el-button @click="addRandomKeyword">
        添加随机关键词
      </el-button>
      <el-button @click="clearHistory" type="danger">
        清空历史
      </el-button>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { TrendCharts, Clock } from '@element-plus/icons-vue'

const hotKeywords = ref(['Vue3', 'Element Plus', 'TypeScript', 'Vite'])
const searchHistory = ref(['前端开发', '组件库', '响应式设计', 'JavaScript'])

const randomKeywords = [
  'React', 'Angular', 'Svelte', 'Next.js', 'Nuxt.js',
  'Webpack', 'Rollup', 'ESLint', 'Prettier', 'Jest'
]

const hotTagTypes = ['', 'success', 'warning', 'danger']

const getHotTagType = (index) => {
  return hotTagTypes[index % hotTagTypes.length]
}

const handleAddHotKeyword = (keyword) => {
  ElMessage.success(`添加热门关键词: ${keyword}`)
}

const handleRemoveHotKeyword = (keyword) => {
  ElMessage.info(`移除热门关键词: ${keyword}`)
}

const removeHotKeyword = (index) => {
  hotKeywords.value.splice(index, 1)
}

const removeSearchHistory = (index) => {
  searchHistory.value.splice(index, 1)
}

const addRandomKeyword = () => {
  const randomKeyword = randomKeywords[Math.floor(Math.random() * randomKeywords.length)]
  if (!hotKeywords.value.includes(randomKeyword)) {
    hotKeywords.value.push(randomKeyword)
    ElMessage.success(`添加关键词: ${randomKeyword}`)
  } else {
    ElMessage.warning('关键词已存在')
  }
}

const clearHistory = () => {
  searchHistory.value = []
  ElMessage.success('搜索历史已清空')
}
</script>

<style scoped>
.search-manager {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.actions {
  text-align: center;
}
</style>

API 文档

Attributes

名称说明类型默认值
model-value / v-model绑定值array
max可添加标签的最大数量number
tag-type标签类型enuminfo
tag-effect标签效果enumlight
trigger触发输入标签的按键enumEnter
draggable是否可以拖动标签booleanfalse
delimiter在匹配分隔符时添加标签string / regex
size输入框尺寸enum
save-on-blur当输入失去焦点时是否保存输入值booleantrue
clearable是否显示清除按钮booleanfalse
disabled是否禁用booleanfalse
validate-event是否触发表单验证booleantrue
readonly等价于原生 readonly 属性booleanfalse
autofocus等价于原生 autofocus 属性booleanfalse
id等价于原生 input id 属性string
tabindex等价于原生 tabindex 属性string / number
maxlength等价于原生 maxlength 属性string / number
minlength等价于原生 minlength 属性string / number
placeholder输入框占位文本string
autocomplete等价于原生 autocomplete 属性stringoff
aria-label等价于原生 aria-label 属性string

Events

名称说明类型
change绑定值变化时触发的事件Function
input在 Input 值改变时触发Function
add-tagtag 被添加时触发Function
remove-tagtag 被移除时触发Function
focus在 Input 获得焦点时触发Function
blur在 Input 失去焦点时触发Function
clear点击清除图标时触发Function

Slots

名称说明类型
tag作为 tag 的内容object
prefixInputTag 头部内容
suffixInputTag 尾部内容

Exposes

名称说明类型
focus使 input 获取焦点Function
blur使 input 失去焦点Function

实践练习

练习1:标签分类管理器

创建一个支持多种分类的标签管理器:

vue
<template>
  <div class="tag-category-manager">
    <h3>标签分类管理器</h3>
    
    <el-tabs v-model="activeTab">
      <el-tab-pane
        v-for="category in categories"
        :key="category.key"
        :label="category.label"
        :name="category.key"
      >
        <el-input-tag
          v-model="category.tags"
          :placeholder="`添加${category.label}标签`"
          :max="category.max"
          clearable
          draggable
        >
          <template #tag="{ tag, index }">
            <el-tag
              :type="category.tagType"
              :effect="category.tagEffect"
              closable
              @close="removeTag(category.key, index)"
            >
              {{ tag }}
            </el-tag>
          </template>
        </el-input-tag>
      </el-tab-pane>
    </el-tabs>
    
    <div class="summary">
      <h4>标签统计:</h4>
      <ul>
        <li v-for="category in categories" :key="category.key">
          {{ category.label }}: {{ category.tags.length }} 个标签
        </li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'

const activeTab = ref('tech')

const categories = reactive([
  {
    key: 'tech',
    label: '技术',
    tags: ['Vue', 'React', 'JavaScript'],
    max: 15,
    tagType: 'primary',
    tagEffect: 'light'
  },
  {
    key: 'hobby',
    label: '爱好',
    tags: ['阅读', '旅行', '摄影'],
    max: 10,
    tagType: 'success',
    tagEffect: 'light'
  },
  {
    key: 'work',
    label: '工作',
    tags: ['项目管理', '团队协作'],
    max: 8,
    tagType: 'warning',
    tagEffect: 'light'
  }
])

const removeTag = (categoryKey, index) => {
  const category = categories.find(c => c.key === categoryKey)
  if (category) {
    category.tags.splice(index, 1)
  }
}
</script>

<style scoped>
.tag-category-manager {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.summary {
  margin-top: 20px;
  padding: 15px;
  background-color: #f5f7fa;
  border-radius: 4px;
}
</style>

练习2:智能标签推荐

创建一个带有智能推荐功能的标签输入器:

vue
<template>
  <div class="smart-tag-input">
    <h3>智能标签推荐</h3>
    
    <el-input-tag
      v-model="selectedTags"
      placeholder="输入标签,系统会智能推荐"
      @input="handleInput"
    />
    
    <div v-if="recommendations.length" class="recommendations">
      <h4>推荐标签:</h4>
      <div class="tag-list">
        <el-tag
          v-for="tag in recommendations"
          :key="tag"
          class="recommendation-tag"
          @click="addRecommendation(tag)"
        >
          {{ tag }}
        </el-tag>
      </div>
    </div>
    
    <div class="selected-tags">
      <h4>已选标签:</h4>
      <div v-if="selectedTags.length">
        <el-tag
          v-for="(tag, index) in selectedTags"
          :key="index"
          type="primary"
          closable
          @close="removeSelectedTag(index)"
        >
          {{ tag }}
        </el-tag>
      </div>
      <p v-else class="empty-text">暂无选中标签</p>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const selectedTags = ref([])
const inputValue = ref('')

// 模拟标签数据库
const tagDatabase = [
  'Vue', 'React', 'Angular', 'JavaScript', 'TypeScript',
  'Node.js', 'Express', 'Koa', 'Nest.js', 'Python',
  'Django', 'Flask', 'Java', 'Spring', 'MySQL',
  'MongoDB', 'Redis', 'Docker', 'Kubernetes', 'AWS',
  '前端开发', '后端开发', '全栈开发', '移动开发', '数据库',
  '云计算', '人工智能', '机器学习', '深度学习', '区块链'
]

const recommendations = computed(() => {
  if (!inputValue.value) return []
  
  return tagDatabase
    .filter(tag => 
      tag.toLowerCase().includes(inputValue.value.toLowerCase()) &&
      !selectedTags.value.includes(tag)
    )
    .slice(0, 6)
})

const handleInput = (value) => {
  inputValue.value = value
}

const addRecommendation = (tag) => {
  if (!selectedTags.value.includes(tag)) {
    selectedTags.value.push(tag)
    inputValue.value = ''
  }
}

const removeSelectedTag = (index) => {
  selectedTags.value.splice(index, 1)
}
</script>

<style scoped>
.smart-tag-input {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.recommendations {
  margin-top: 15px;
  padding: 15px;
  background-color: #f8f9fa;
  border-radius: 4px;
}

.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.recommendation-tag {
  cursor: pointer;
  transition: all 0.3s;
}

.recommendation-tag:hover {
  transform: translateY(-2px);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.selected-tags {
  margin-top: 20px;
  padding: 15px;
  background-color: #f5f7fa;
  border-radius: 4px;
}

.empty-text {
  color: #909399;
  font-style: italic;
}
</style>

常见问题

1. 标签重复添加

问题:用户可能会添加重复的标签

解决方案

vue
<script setup>
const handleAddTag = (tag) => {
  if (tags.value.includes(tag)) {
    ElMessage.warning('标签已存在')
    return false
  }
  return true
}
</script>

2. 标签长度限制

问题:需要限制单个标签的长度

解决方案

vue
<script setup>
const validateTag = (tag) => {
  if (tag.length > 20) {
    ElMessage.error('标签长度不能超过20个字符')
    return false
  }
  return true
}
</script>

3. 标签格式验证

问题:需要验证标签的格式(如不允许特殊字符)

解决方案

vue
<script setup>
const validateTagFormat = (tag) => {
  const regex = /^[a-zA-Z0-9\u4e00-\u9fa5]+$/
  if (!regex.test(tag)) {
    ElMessage.error('标签只能包含字母、数字和中文')
    return false
  }
  return true
}
</script>

最佳实践

  1. 用户体验优化:提供清晰的操作提示和反馈
  2. 数据验证:对标签内容进行合理的验证和限制
  3. 性能考虑:大量标签时考虑虚拟滚动或分页
  4. 可访问性:确保键盘导航和屏幕阅读器支持
  5. 样式一致性:保持标签样式与整体设计的一致性

总结

Input Tag 标签输入框是一个功能丰富的组件,支持:

  • 灵活的标签输入和管理
  • 自定义触发器和分隔符
  • 拖拽排序和数量限制
  • 丰富的自定义选项和事件
  • 良好的用户交互体验

掌握 Input Tag 组件的使用,能够帮助你构建更加友好和高效的标签管理界面。

参考资料

Element Plus Study Guide