
Element Plus Mention 组件完整指南从基本用法到源码级原理解析【免费下载链接】element-plus A Vue.js 3 UI Library made by Element team项目地址: https://gitcode.com/GitHub_Trending/el/element-plus本篇指南围绕 Element PlusVue 3 UI 组件库中的ElMention组件展开讲解如何在输入框与文本域中实现 提及交互覆盖基本用法、props字段映射、textarea 模式、自定义 label、异步加载、多前缀触发、整块删除以及与el-form的联动并结合组件源码packages/components/mention与官方文档docs/en-US/component/mention.md剖析其底层实现原理。读完本文你将掌握ElMention的全部配置项、事件、插槽与暴露方法能够独立实现一套完整、可复用的提及功能。组件定位与设计思路Mention组件用于在输入内容中提及某人或某物。它以内置的el-input为基础在用户输入触发字符默认时从options候选列表中筛选并弹出下拉面板选中后把选项值回填到输入文本中并自动补全分隔符。从源码结构看该组件由三个核心文件组成mention.ts声明全部 props、emits 与类型mention.vue主组件负责输入监听、光标定位、下拉显隐与选中回填mention-dropdown.vue下拉面板负责选项渲染、键盘导航与滚动定位。mention.vue的模板将el-input与el-tooltip承载下拉面板组合在一起el-input负责文本输入el-tooltip的content插槽内渲染ElMentionDropdown。这种输入框 浮层的组合让 Mention 天然继承了输入框的全部属性、事件与插槽能力。快速上手基本用法最基本的用法是传入options列表配合v-model绑定输入值然后在输入框中输入触发下拉候选。template el-mention v-modelvalue :optionsoptions stylewidth: 320px placeholderPlease input / /template script setup langts import { ref } from vue const value ref() const options ref([ { label: Fuphoenixes, value: Fuphoenixes }, { label: kooriookami, value: kooriookami }, { label: Jeremy, value: Jeremy }, { label: btea, value: btea }, ]) /script对应官方示例见 docs/examples/mention/basic.vue。候选条目中value是选中后回填到输入框的文本label是下拉面板中展示的文本。当选中某一项后value会连同默认分隔符 空格一起插入到输入内容中。组件的触发与回填流程从 mention.vue 源码可以还原整个交互链路用户输入触发handleInputChange同时派发update:modelValue与input事件并调用syncAfterCursorMove()syncAfterCursorMove内通过syncCursor()计算光标位置、syncDropdownVisible()判断是否应显示下拉面板syncDropdownVisible调用getMentionCtx实现于 helper.ts从光标位置向前扫描查找最近的触发前缀与分隔符提取出当前的pattern 后面的关键字命中后发出search事件并显示面板用户选择选项后触发handleSelect将pattern部分替换为选项value 分隔符派发select事件并把光标移动到合适位置。getMentionCtx的扫描逻辑值得注意它从selectionEnd向前逐个字符遍历遇到分隔符split、\n、\r记录splitIndex后继续向前找前缀字符找到前缀后即截取(prefixIndex, end]区间作为pattern。这也是同一个输入框中出现多个 提及也能正确识别当前正在编辑的那一个的原因。通过 props 自定义字段映射 ^(2.11.3)当后端返回的数据结构与默认的{ value, label, disabled }不一致时可通过props属性自定义字段名映射该能力自 2.11.3 版本引入。template el-mention v-modelvalue :optionsoptions :propsprops stylewidth: 320px placeholderPlease input / /template script setup langts import { ref } from vue const value ref() const props { label: name, value: id, disabled: unable } const options ref([ { name: Fuphoenixes, id: Fuphoenixes, unable: true }, { name: kooriookami, id: kooriookami }, { name: Jeremy, id: Jeremy, unable: true }, { name: btea, id: btea }, ]) /script对应官方示例见 docs/examples/mention/props.vue。这里的disabled字段被映射为unable映射后unable: true的选项在下拉面板中处于禁用状态无法被选中。在源码中mention.vue通过aliasProps合并默认映射与用户传入的props再由mapOption将原始选项统一转换为{ label, value, disabled }结构之后所有渲染与筛选逻辑都基于规范化后的结构运行// packages/components/mention/src/mention.vue const aliasProps computed(() ({ ...mentionDefaultProps, ...props.props, })) const mapOption (option: T) { const base { label: option[aliasProps.value.label], value: option[aliasProps.value.value], disabled: option[aliasProps.value.disabled], } return { ...option, ...base } }需要说明的是最终回填到输入框以及select事件抛出的仍然是用户传入的原始选项对象映射只发生在内部展示与筛选环节对应getOriginalOption的实现。textarea 模式在文本域中提及将type设置为textarea即可让 Mention 工作在多行文本域中适用于评论、文档协作等场景。template el-mention v-modelvalue typetextarea :optionsoptions stylewidth: 320px placeholderPlease input / /template script setup langts import { ref } from vue const value ref() // options 定义同 basic.vue此处省略 /script对应官方示例见 docs/examples/mention/textarea.vue。由于 Mention 继承自el-inputtextarea 模式下的rows、autosize、maxlength、show-word-limit等文本域属性同样可用。在getMentionCtx中换行符\n、\r与split一样被视为提及边界因此在多行文本中切换行后上一行的 内容不会干扰当前行的识别。值得一提的是光标定位逻辑getCursorPositionfork 自 textarea-caret-position 项目见 helper.ts会复制文本域的字体、内边距、边框等样式到一个隐藏的镜像 div中从而精确计算文本域中光标处的坐标确保下拉面板能够贴合在光标附近弹出——这对单行input与多行textarea同样有效。自定义选项 label使用 label 插槽通过label插槽可以完全自定义下拉面板中每个选项的展示内容。插槽作用域提供{ item, index }item为规范化后的选项对象。template el-mention v-modelvalue :optionsoptions stylewidth: 320px placeholderPlease input template #label{ item } div styledisplay: flex; align-items: center el-avatar :size24 :srcitem.avatar / span stylemargin-left: 6px{{ item.value }}/span /div /template /el-mention /template script setup langts import { ref } from vue const value ref() const options ref([ { value: Fuphoenixes, avatar: https://avatars.githubusercontent.com/u/27912232 }, { value: kooriookami, avatar: https://avatars.githubusercontent.com/u/38392315 }, { value: Jeremy, avatar: https://avatars.githubusercontent.com/u/15975785 }, { value: btea, avatar: https://avatars.githubusercontent.com/u/24516654 }, ]) /script对应官方示例见 docs/examples/mention/label.vue。在 mention-dropdown.vue 中未提供该插槽时会回退为渲染item.label ?? item.valueslot namelabel :itemitem :indexindex span{{ item.label ?? item.value }}/span /slot远程加载选项异步 options当候选列表依赖接口请求时可监听search事件结合loading属性实现异步加载。search事件在触发前缀被命中时发出回调参数为(pattern, prefix)。template el-mention v-modelvalue :optionsoptions :loadingloading stylewidth: 320px placeholderPlease input searchhandleSearch / /template script setup langts import { onBeforeUnmount, ref } from vue import type { MentionOption } from element-plus const value ref() const loading ref(false) const options refMentionOption[]([]) let timer: ReturnTypetypeof setTimeout const handleSearch (pattern: string) { if (timer) clearTimeout(timer) loading.value true timer setTimeout(() { options.value [Fuphoenixes, kooriookami, Jeremy, btea].map( (item) ({ label: pattern item, value: pattern item, }) ) loading.value false }, 1500) } onBeforeUnmount(() { if (timer) clearTimeout(timer) }) /script对应官方示例见 docs/examples/mention/loading.vue。注意这里主动清空了上一次的定时器避免快速连续输入时旧请求覆盖新结果实际项目中通常配合 AbortController 或请求序号做竞态控制。源码层面的行为验证dropdownVisible的计算条件是面板可见且有筛选后的选项或处于 loading 状态因此在loading为true时即使options还是空的下拉面板也会展示加载态// packages/components/mention/src/mention.vue const dropdownVisible computed(() { return visible.value (!!filteredOptions.value.length || props.loading) })filteredOptions默认使用filterOption内置逻辑把pattern与option.label ?? option.value均转为小写后做includes包含匹配见 helper.ts 中的filterOption。如果希望完全交由服务端过滤可把filter-option设为false关闭本地过滤也可以传入自定义函数实现自己的匹配规则。此外下拉面板中还可以通过loading插槽自定义加载文案。自定义触发前缀多前缀 与prefix属性用于自定义触发字符默认是。它既可以传单个字符也可以传字符数组实现 提人、# 提标签这类多前缀场景。template el-mention v-modelvalue :optionsoptions :prefix[, #] stylewidth: 320px placeholderinput to mention people, # to mention tag searchhandleSearch / /template script setup langts import { ref } from vue import type { MentionOption } from element-plus const MOCK_DATA: Recordstring, string[] { : [Fuphoenixes, kooriookami, Jeremy, btea], #: [1.0, 2.0, 3.0], } const value ref() const options refMentionOption[]([]) const handleSearch (_: string, prefix: string) { options.value (MOCK_DATA[prefix] || []).map((value) ({ value })) } /script对应官方示例见 docs/examples/mention/prefix.vue。search事件的第二个参数prefix会告诉你当前命中的是哪个前缀从而按需加载不同的候选数据。约束提醒无论单字符还是数组每个前缀字符的长度都必须恰好为 1否则会被mention.ts中的validator校验拦截// packages/components/mention/src/mention.ts prefix: { type: definePropTypestring | string[]([, Array]), default: , validator: (val: string | string[]) { if (isString(val)) return val.length 1 return val.every((v) isString(v) v.length 1) }, },同理split分隔符的字符串长度也必须恰好为 1默认是空格 。整块删除whole 与 check-is-whole默认情况下按退格键只会删除光标前的一个字符。当whole为true时如果光标紧跟在某个已选中的提及之后按退格会一次性删除整个名字 块check-is-whole则用于自定义是否为完整提及的判断逻辑。template el-mention v-modelvalue1 whole :optionsoptions1 stylewidth: 320px placeholderPlease input / el-divider / el-mention v-modelvalue2 :optionsoptions2 :prefix[, #] whole :check-is-wholecheckIsWhole stylewidth: 320px placeholderinput to mention people, # to mention tag searchhandleSearch / /template script setup langts import { ref } from vue import type { MentionOption } from element-plus const MOCK_DATA: Recordstring, string[] { : [Fuphoenixes, kooriookami, Jeremy, btea], #: [1.0, 2.0, 3.0], } const value1 ref() const value2 ref() const options1 refMentionOption[]( MOCK_DATA[].map((value) ({ value })) ) const options2 refMentionOption[]([]) const handleSearch (_: string, prefix: string) { options2.value (MOCK_DATA[prefix] || []).map((value) ({ value })) } const checkIsWhole (pattern: string, prefix: string) { return (MOCK_DATA[prefix] || []).includes(pattern) } /script对应官方示例见 docs/examples/mention/whole.vue。源码中的删除逻辑mention.vue 的handleInputKeyDown做了三重判断一是whole开启且光标前存在mentionCtx二是是否视为整体成立——若提供了check-is-whole则调用它判断否则按该pattern是否能在options中匹配到对应value判断三是光标必须紧跟在分隔符之后splitIndex 1 selectionEnd。全部满足后preventDefault()阻止默认的单字符删除一次性移除从prefixIndex到splitIndex的整段文本派发whole-remove事件并把光标移到前缀起始位置。whole-remove事件自 2.10.4 版本起可用。与 el-form 表单集成Mention 基于el-input构建因此可以无缝放入el-form-item中参与校验、重置与提交。template el-form refruleFormRef stylemax-width: 600px :modelruleForm :rulesrules el-form-item labelname propname el-mention v-modelruleForm.name :optionsoptions / /el-form-item el-form-item labeldesc propdesc el-mention v-modelruleForm.desc typetextarea :optionsoptions / /el-form-item el-form-item el-button typeprimary clicksubmitForm(ruleFormRef)Submit/el-button el-button clickresetForm(ruleFormRef)Reset/el-button /el-form-item /el-form /template script langts setup import { reactive, ref } from vue import type { FormInstance, FormRules } from element-plus interface RuleForm { name: string desc: string } const ruleFormRef refFormInstance() const ruleForm reactiveRuleForm({ name: , desc: , }) const options ref([ { label: Fuphoenixes, value: Fuphoenixes }, { label: kooriookami, value: kooriookami }, { label: Jeremy, value: Jeremy }, { label: btea, value: btea }, ]) const rules reactiveFormRulesRuleForm({ name: [{ required: true, message: Please input name, trigger: blur }], desc: [{ required: true, message: Please input desc, trigger: blur }], }) const submitForm async (formEl: FormInstance | undefined) { if (!formEl) return await formEl.validate((valid, fields) { if (valid) { console.log(submit!) } else { console.log(error submit!, fields) } }) } const resetForm (formEl: FormInstance | undefined) { if (!formEl) return formEl.resetFields() } /script对应官方示例见 docs/examples/mention/form.vue。mention.vue中通过useFormDisabled读取el-form的禁用状态并在模板中把disabled透传给内部el-input与下拉面板因此当el-form被整体禁用时Mention 的输入与选择能力会一并禁用。完整 API 参考由于本组件基于el-input开发el-input原有的属性、事件、插槽均未改变此处不再重复请前往原组件文档查看。Attributes名称说明类型默认值options提及选项列表^[array]MentionOption[][]props ^(2.11.3)配置选项字段映射^[object]MentionOptionProps{value: value, label: label, disabled: disabled}prefix触发提及的前缀字符长度必须为 1^[string] | ^[array]string[]split分隔提及的字符长度必须为 1^[string] filter-option自定义过滤选项逻辑^[false] | ^[Function](pattern: string, option: MentionOption) boolean—placement设置弹出面板位置^[string]bottom \| topbottomshow-arrow下拉面板是否带箭头^[boolean]falseoffset下拉面板偏移量^[number]0whole按下退格键删除时是否将提及内容作为一个整体删除^[boolean]falsecheck-is-whole按下退格键删除时判断提及是否为整体^[Function](pattern: string, prefix: string) boolean—loading提及下拉面板是否处于加载状态^[boolean]falsemodel-value / v-model输入值^[string]—popper-class下拉面板自定义类名^[string] / ^[object]popper-style ^(2.11.5)下拉面板自定义样式^[string] / ^[object]—popper-optionspopper.js 参数^[object]参见 popper.js 文档—input props———关于placement源码中有一个细节当show-arrow为false时实际使用的位置是${placement}-start即bottom-start/top-start让面板与光标起始位置对齐只有开启箭头时才使用标准的bottom/top并配套[bottom, top]兜底位置// packages/components/mention/src/mention.vue const computedPlacement computedPlacement(() props.showArrow ? props.placement : ${props.placement}-start ) const computedFallbackPlacements computedPlacement[](() props.showArrow ? [bottom, top] : [bottom-start, top-start] )Events名称说明类型search触发前缀命中时触发^[Function](pattern: string, prefix: string) voidselect用户选中选项时触发^[Function](option: MentionOption, prefix: string) voidwhole-remove ^(2.10.4)整块提及被删除且whole为true或check-is-whole为true时触发^[Function](pattern: string, prefix: string) voidinput events——Slots名称说明类型label选项 label 内容^[object]{ item: MentionOption, index: number }loading选项加载中内容—header下拉面板顶部内容—footer下拉面板底部内容—input slots——Exposes名称说明类型inputel-input 组件实例^[object]RefInputInstance \| nulltooltipel-tooltip 组件实例^[object]RefTooltipInstance \| nulldropdownVisible ^(2.8.5)tooltip 显示状态^[object]ComputedRefboolean类型声明type MentionOption { value?: string label?: string disabled?: boolean [key: string]: any } type MentionOptionProps { value?: string label?: string disabled?: string [key: string]: string | undefined }可以看到MentionOption保留了索引签名[key: string]: any这正是 label 插槽中能够访问item.avatar等自定义字段的类型基础MentionOptionProps的索引签名则支持任意自定义字段名映射。键盘交互与无障碍设计从源码可以整理出完整的键盘操作约定↑ / ↓当下拉面板可见时在候选项中上移/下移navigateOptions会自动跳过禁用项并滚动到当前项见 mention-dropdown.vueEnter / NumpadEnter面板可见时选中当前高亮项不可见时对非 textarea 模式重新同步光标与面板状态Esc关闭下拉面板Backspace配合whole/check-is-whole实现整块删除。无障碍方面mention.vue为内部输入框动态注入了 ARIA 属性面板可见时设置rolecombobox、aria-expanded、aria-controls、aria-activedescendant与aria-autocompletenone面板本身使用rolelistbox选项使用roleoption并带aria-selected/aria-disabled整体遵循 WAI-ARIA Combobox 交互模式。示例的单元测试位于 packages/components/mention/tests/mention.test.tsx可结合测试用例进一步验证各交互分支的行为。总结ElMention以el-input为底座、el-tooltip为浮层容器在输入框和文本域中提供了完整、可键盘操作、支持异步与多前缀的提及能力。使用时只需记住几条核心线索用optionsprops准备候选数据用v-model绑定文本用prefix定制触发字符用split控制分隔符异步场景监听searchloading展示细节用label/header/footer/loading插槽定制表单场景直接放入el-form-item继承el-input的全部属性、事件与插槽需要整块删除时开启whole或提供check-is-whole自定义判断。若需进一步研究源码建议从 packages/components/mention/src/mention.vue 与 helper.ts 入手前者是完整的交互状态机后者包含提及上下文解析与光标定位两大核心算法。【免费下载链接】element-plus A Vue.js 3 UI Library made by Element team项目地址: https://gitcode.com/GitHub_Trending/el/element-plus创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考