Appearance
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 | 是否禁用 | boolean | false |
error | 是否错误状态 | boolean | false |
readonly | 是否只读 | boolean | false |
allowClear | 是否允许清空 | boolean | false |
size | 输入框尺寸 | 'mini' | 'small' | 'medium' | 'large' | 跟随全局配置 |
maxTagCount | 最多展示标签个数,0 不限制 | number | 0 |
retainInputValue | 创建或失焦后是否保留输入内容 | boolean | { create?: boolean; blur?: boolean } | false |
formatTag | 格式化标签内容 | (data: TagData) => string | - |
uniqueValue | 是否仅创建唯一值 | boolean | false |
fieldNames | 自定义 TagData 字段名 | InputTagFieldNames | - |
tagNowrap | 标签内容是否不换行 | boolean | false |
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 | 是否可删除 | boolean | true |
tagProps | 透传给内部 Tag 的属性 | TagProps | - |
fieldNames
| 字段 | 说明 | 默认值 |
|---|---|---|
value | 取值字段名 | 'value' |
label | 内容字段名 | 'label' |
closable | 删除开关字段名 | 'closable' |
tagProps | Tag 属性字段名 | 'tagProps' |
关联能力
- 多选 Select 内部复用 InputTag 渲染标签,
maxTagCount、tagNowrap、标签删除等行为一致。 - 需要校验、错误提示和布局时配合
x-form/x-form-item使用。 InputTag、Input、Textarea同属输入组件族,尺寸与状态命名一致。
类型导出
ts
import type { InputTagFieldNames, InputTagInstance, TagData } from 'x-next';