Skip to content

InputTag 标签输入 ​

用于录入多个短文本项,如标签、关键词、邮箱列表。

何时使用 ​

  • 需要一次录入多个等价的短文本,例如关键词、收件人、分类名。
  • 录入后希望以标签形式回显,并允许逐个删除。
  • 需要限制展示数量、去重、前缀后缀或与 Form 校验联动时。

基础用法 ​

输入内容后按回车创建标签,点击标签上的关闭按钮删除,allow-clear 提供整组清空按钮。

VueTypeScriptVue, TypeScript
vue
<template>
  <x-input-tag v-model="tags" placeholder="输入后回车" allow-clear />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const tags = ref(['Vue', 'TypeScript']);
</script>

状态 ​

disabled 禁用输入与删除,readonly 保留展示但不可编辑,error 使用错误态边框与背景。切换状态后组件会立即响应。

VueTypeScript当前状态:normal
vue
<template>
  <x-input-tag
    v-model="tags"
    :disabled="state === 'disabled'"
    :readonly="state === 'readonly'"
    :error="state === 'error'"
    placeholder="输入后回车"
    allow-clear
  />
  <x-button size="small" @click="state = 'disabled'">禁用</x-button>
  <x-button size="small" @click="state = 'readonly'">只读</x-button>
  <x-button size="small" @click="state = 'error'">错误</x-button>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const tags = ref(['Vue', 'TypeScript']);
const state = ref<'normal' | 'disabled' | 'readonly' | 'error'>('normal');
</script>

尺寸与高度 ​

同一尺寸下,空态、单个标签、多个标签的外框高度保持一致;标签内容过长时标签自身换行增高。

mini空态Vue
small空态Vue
medium空态Vue
large空态Vue
vue
<template>
  <x-input-tag v-model="emptyTags" size="mini" placeholder="空态" />
  <x-input-tag v-model="singleTags" size="mini" />
  <x-input-tag v-model="emptyTags" size="small" placeholder="空态" />
  <x-input-tag v-model="singleTags" size="small" />
  <x-input-tag v-model="emptyTags" size="medium" placeholder="空态" />
  <x-input-tag v-model="singleTags" size="medium" />
  <x-input-tag v-model="emptyTags" size="large" placeholder="空态" />
  <x-input-tag v-model="singleTags" size="large" />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const emptyTags = ref<string[]>([]);
const singleTags = ref(['Vue']);
</script>

限制展示数量 ​

maxTagCount 只限制展示数量,多出的标签折叠为 +N...,不限制可创建数量,也不阻止删除。

onetwo+2...
vue
<template>
  <x-input-tag v-model="tags" :max-tag-count="2" allow-clear />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const tags = ref(['one', 'two', 'three', 'four']);
</script>

前缀和后缀 ​

prefix 用于输入框内部的前置装饰,suffix 用于后置内容;已有内置清空按钮时两者会共同占用右侧空间。

标签VueTypeScript2
vue
<template>
  <x-input-tag v-model="tags" allow-clear>
    <template #prefix>标签</template>
    <template #suffix>{{ tags.length }}</template>
  </x-input-tag>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const tags = ref(['Vue', 'TypeScript']);
</script>

唯一值 ​

uniqueValue 阻止创建已存在的标签,比较基于归一化后的 value,字符串与数字严格区分(1 与 '1' 视为不同)。被阻止时仍会触发 press-enter,输入内容保留。

Vue1对象 AVue, 1, 对象 A
vue
<template>
  <x-input-tag v-model="tags" unique-value placeholder="重复值不会再次创建" />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { TagData } from 'x-next';

const tags = ref<Array<string | number | TagData>>(['Vue', 1, { value: 'id-a', label: '对象 A' }]);
</script>

受控输入值 ​

v-model 控制标签数组,v-model:input-value 控制输入框中的未提交草稿。草稿受控时,组件的输入只上报变更,由使用方决定是否回写。

草稿Vue 标签:Vue;草稿:草稿
vue
<template>
  <x-input-tag v-model="tags" v-model:input-value="draft" placeholder="输入后回车" allow-clear />
  <p role="status">标签:{{ tags.join('、') || '无' }};草稿:{{ draft || '空' }}</p>
  <x-button size="small" @click="reset">重置为初始值</x-button>
</template>

<script setup lang="ts">
import { ref } from 'vue';

const tags = ref(['Vue']);
const draft = ref('草稿');
const reset = () => {
  tags.value = ['Vue'];
  draft.value = '草稿';
};
</script>

保留输入内容 ​

retainInputValue 默认 false,即创建标签后和失焦时都会清空草稿。传 true 两者都保留,也可用对象分别控制 create 与 blur。

draft布尔形式:draft;草稿 空draftblur: true:draft;草稿 空draftcreate: true:draft;草稿 空
vue
<template>
  <x-input-tag v-model="tags" v-model:input-value="draft" retain-input-value />
  <x-input-tag
    v-model="tags"
    v-model:input-value="blurDraft"
    :retain-input-value="{ create: false, blur: true }"
  />
  <x-input-tag
    v-model="tags"
    v-model:input-value="createDraft"
    :retain-input-value="{ create: true, blur: false }"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const tags = ref(['draft']);
const draft = ref('');
const blurDraft = ref('');
const createDraft = ref('');
</script>

对象标签 ​

对象标签用 fieldNames 映射 value、label、closable、tagProps 四个字段。label 缺失时回退为 String(value);缺少有效 value 的对象会被忽略。

#设计#研发
vue
<template>
  <x-input-tag v-model="tags" :field-names="fieldNames" :format-tag="formatTag" allow-clear />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { InputTagFieldNames, TagData } from 'x-next';

const tags = ref<Array<string | number | TagData>>([
  { id: 'a', name: '设计', canRemove: false },
  { id: 'b', name: '研发', canRemove: true },
]);
const fieldNames: InputTagFieldNames = {
  value: 'id',
  label: 'name',
  closable: 'canRemove',
};
// Enter 新建的字符串标签只有 value,需要回退取值
const formatTag = (data: TagData) => `#${data.name ?? data.label ?? data.value}`;
</script>

自定义标签 ​

tag 插槽接收归一化后的 data,可自定义标签渲染;对象标签与新建字符串标签混用时同样需要回退取值。

设计研发
vue
<template>
  <x-input-tag v-model="tags" :field-names="fieldNames">
    <template #tag="{ data }">
      {{ data.name ?? data.label ?? data.value }}
    </template>
  </x-input-tag>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { InputTagFieldNames, TagData } from 'x-next';

const tags = ref<Array<string | number | TagData>>([{ id: 'a', name: '设计' }]);
const fieldNames: InputTagFieldNames = { value: 'id', label: 'name' };
</script>

标签不换行 ​

tagNowrap 让单个标签的内容不换行,标签列表本身仍可换行。

这是一段很长很长的标签内容,用于检查不换行时的显示效果
vue
<template>
  <x-input-tag v-model="tags" tag-nowrap />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const tags = ref(['这是一段很长很长的标签内容,用于检查不换行时的显示效果']);
</script>

标签样式 ​

tagProps 透传给内部 Tag,可设置内置颜色、自定义颜色与边框。删除权限只收紧不放开:顶层 closable: false 时 tagProps.closable: true 无效;顶层可关闭时 tagProps.closable: false 会隐藏关闭按钮并让键盘删除跳过该项。

内置蓝色自定义颜色tagProps 禁止删除顶层禁止删除前两项可删除,后两项分别由 tagProps 与顶层字段禁止删除
vue
<template>
  <x-input-tag v-model="tags" allow-clear />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { TagData } from 'x-next';

const tags = ref<Array<string | number | TagData>>([
  { value: 'blue', label: '内置蓝色', tagProps: { color: 'blue', bordered: true } },
  { value: 'custom', label: '自定义颜色', tagProps: { color: '#7c3aed' } },
  { value: 'narrow', label: 'tagProps 禁止删除', closable: true, tagProps: { color: 'green', closable: false } },
  { value: 'locked', label: '顶层禁止删除', closable: false, tagProps: { color: 'red', closable: true } },
]);
</script>

事件回调 ​

创建标签依次触发 change、press-enter 和清空草稿的 input-value-change,因此示例用日志列表保留完整顺序。

日志
等待操作(输入后回车、删除标签或清空)
vue
<template>
  <x-input-tag
    v-model="tags"
    allow-clear
    @change="handleChange"
    @input-value-change="handleInputValueChange"
    @press-enter="handlePressEnter"
    @remove="handleRemove"
    @clear="handleClear"
  />
  <div role="status">
    <div v-for="log in logs" :key="log">{{ log }}</div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { TagData } from 'x-next';

const tags = ref(['日志']);
const logs = ref<string[]>([]);
let sequence = 0;
const append = (message: string) => {
  logs.value = [...logs.value, `${++sequence}. ${message}`].slice(-12);
};
const handleChange = (value: Array<string | number | TagData>, event: Event) => {
  append(`change (${event.type}):${value.length} 个标签`);
};
const handleInputValueChange = (value: string, event: Event) => {
  append(`input-value-change (${event.type}):${value || '空'}`);
};
const handlePressEnter = (value: string, event: KeyboardEvent) => {
  append(`press-enter (${event.type}):${value}`);
};
const handleRemove = (value: string | number, event: Event) => {
  append(`remove (${event.type}):${value}`);
};
const handleClear = (event: MouseEvent) => {
  append(`clear (${event.type})`);
};
</script>

方法 ​

focus、blur 通过组件实例调用。

VueTypeScript
vue
<template>
  <x-input-tag ref="inputTagRef" v-model="tags" />
  <x-button @click="inputTagRef?.focus()">聚焦</x-button>
  <x-button @click="inputTagRef?.blur()">失焦</x-button>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import type { InputTagInstance } from 'x-next';

const tags = ref(['Vue', 'TypeScript']);
const inputTagRef = ref<InputTagInstance>();
</script>

原生属性 ​

id、name、maxlength、aria-* 等原生属性会落到内部 input,而不是外层容器;表单类事件(input、keydown 等)同样绑定在原生 input 上。

Vue id=input-tag-native;name=keywords;maxlength=12;草稿长度 0
vue
<template>
  <x-input-tag
    v-model="tags"
    v-model:input-value="draft"
    id="input-tag-native"
    name="keywords"
    maxlength="12"
    aria-label="关键词标签输入"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';

const tags = ref(['Vue']);
const draft = ref('');
</script>

在 Form 中校验 ​

组件放进 x-form-item 后会自动继承 Form 的尺寸与禁用状态,并按 FormItem 的校验规则展示错误。默认 validateTrigger 为 change,即值变化时触发校验。

请输入标签后回车

vue
<template>
  <x-form ref="formRef" :model="formState" :rules="formRules">
    <x-form-item field="tags" label="标签">
      <x-input-tag v-model="formState.tags" placeholder="请输入标签后回车" />
    </x-form-item>
    <x-form-item>
      <x-space>
        <x-button type="primary" @click="handleValidate">校验</x-button>
        <x-button @click="handleReset">重置</x-button>
      </x-space>
    </x-form-item>
  </x-form>
  <p role="status">{{ validateResult }}</p>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue';
import type { FormInstance, FieldRule } from 'x-next';

const formState = reactive({ tags: [] as string[] });
const formRules: Record<string, FieldRule[]> = {
  tags: [{ type: 'array', required: true, message: '至少添加一个标签' }],
};
const formRef = ref<FormInstance>();
const validateResult = ref('');

const handleValidate = async () => {
  const errors = await formRef.value!.validate();
  validateResult.value = errors
    ? `校验失败:${Object.values(errors).map((error) => error.message).join(';')}`
    : '校验通过';
};
const handleReset = () => {
  formRef.value!.resetFields();
  validateResult.value = '已重置';
};
</script>

按需导入 ​

ts
import { InputTag } from 'x-next';

样式按需引入(base.css 为共享基础层,多个组件只需引入一次):

ts
import 'x-next/style/base.css';
import 'x-next/style/form-input-tag.css';

已知交互说明 ​

  • 输入为空时按 Backspace 删除最后一个可关闭标签;末尾标签不可关闭时继续向前查找。
  • 输入内容为空时按 Enter 不创建标签,也不触发 press-enter。
  • uniqueValue 比较归一化后的 value,基于严格相等,数字 1 与字符串 '1' 视为不同值。
  • maxTagCount 只影响展示,折叠出的 +N... 不可关闭;Backspace 删除的是最后一个真实可关闭标签。
  • 对象标签缺少 value(或 fieldNames.value 字段)时会被忽略,不会渲染,删除时也会跳过。
  • 标签删除权限只收紧不放开:顶层 closable 是总开关,tagProps.closable: false 可在其基础上禁用删除。
  • 关闭按钮的无障碍名称包含各标签文案(如“关闭标签 Vue”),便于屏幕阅读器区分。
  • 用键盘激活关闭按钮或清空按钮后,焦点会回到输入框,便于继续输入。
  • 直接使用 InputTag 时,输入法组合结束(compositionend)同样会触发 FormItem 的 input 校验,与普通输入一致。
  • 同名事件回调:inputAttrs 与组件透传的 attrs 都提供同一事件(如 onInput)时,inputAttrs 优先;组件内部处理器仍会执行。

Props ​

参数说明类型默认值
modelValue绑定值Array<string | number | TagData>-
defaultValue默认值,非受控模式Array<string | number | TagData>[]
inputValue输入框绑定值string-
defaultInputValue输入框默认值string''
placeholder占位提示string-
disabled是否禁用booleanfalse
error是否错误状态booleanfalse
readonly是否只读booleanfalse
allowClear是否允许清空booleanfalse
size输入框尺寸'mini' | 'small' | 'medium' | 'large'跟随全局配置
maxTagCount最多展示标签个数,0 不限制number0
retainInputValue创建或失焦后是否保留输入内容boolean | { create?: boolean; blur?: boolean }false
formatTag格式化标签内容(data: TagData) => string-
uniqueValue是否仅创建唯一值booleanfalse
fieldNames自定义 TagData 字段名InputTagFieldNames-
tagNowrap标签内容是否不换行booleanfalse

Events ​

事件名说明回调参数
update:modelValue值更新时触发value
update:inputValue输入值更新时触发inputValue
change标签值变化时触发value, event
input-value-change输入值变化时触发inputValue, event
press-enter按下回车且输入内容非空时触发inputValue, event
remove删除标签时触发removed, event
clear点击清除按钮时触发event
focus获取焦点时触发event
blur失去焦点时触发event
keydown按下键盘时触发event

Slots ​

插槽名说明参数
prefix前缀内容-
suffix后缀内容-
tag自定义标签内容{ data: TagData }

Methods ​

方法说明
focus使输入框获取焦点
blur使输入框失去焦点

TagData ​

字段说明类型默认值
value标签值,缺失时整项被忽略string | number-
label标签内容,缺失时回退为 String(value)string-
closable是否可删除booleantrue
tagProps透传给内部 Tag 的属性TagProps-

fieldNames ​

字段说明默认值
value取值字段名'value'
label内容字段名'label'
closable删除开关字段名'closable'
tagPropsTag 属性字段名'tagProps'

关联能力 ​

  • 多选 Select 内部复用 InputTag 渲染标签,maxTagCount、tagNowrap、标签删除等行为一致。
  • 需要校验、错误提示和布局时配合 x-form / x-form-item 使用。
  • InputTag、Input、Textarea 同属输入组件族,尺寸与状态命名一致。

类型导出 ​

ts
import type { InputTagFieldNames, InputTagInstance, TagData } from 'x-next';