- 前端
- UI组件
- AI 应用
【免费下载链接】MateChat
前端智能化场景解决方案UI库,轻松构建你的AI应用,我们将持续完善更新,欢迎你的使用与建议。 官网地址:https://matechat.gitcode.com
List 是 MateChat 前端智能化场景解决方案 UI 库中用于"展示一组内容或选项"的轻量级组件,非常适合历史对话列表、下拉候选、标签式展示等 AI 应用常见交互。本文将以官方文档(demo.md、api.md)为骨架,结合仓库源码剖析其参数背后的实现原理,帮助你在实际项目中快速落地选中类、展示类与下拉类列表场景。
组件定位与按需引入
从仓库 packages/components/index.ts 可以看出,MateChat 组件库以Mc前缀统一命名,List 组件对应导出名McList,既可以通过默认install全量注册,也可以按需单独引入:
import { McList } from '@matechat/core';按需引入后,组件通过自身的install方法完成注册,相关实现见 packages/components/List/index.ts:
McList.install = (app: App) => { app.component('McList', McList); };同时该文件会export * from './list-types',将ListItemData、ListDirection、ListVariant等类型一并导出,方便你在 TypeScript 场景下做类型标注。
在组件内部,模板结构非常简洁(见 List.vue):外层容器负责滚动与样式,内层通过v-for渲染每个选项;所有交互逻辑(点击选中、键盘预选、懒加载滚动判断)都收敛在 use-list.ts 的useList组合式函数中,样式类由useListRender计算生成。整个组件只依赖 Vue 3 的组合式 API,无额外运行时依赖。
基本用法:选中类场景的垂直列表
List 最典型的应用是"选中类"场景,例如历史对话列表、Agent 切换下拉等。选项数据由data传入,其中:
disabled字段用于禁用某个选项;active字段用于指定选中项;- 默认交互为悬浮高亮,点击时选中高亮。
<template> <McList :data="options" @select="onSelect" class="my-list"></McList> </template> <script setup> import { defineComponent, ref } from 'vue'; const options = ref( new Array(6).fill(0).map((item, i) => ({ label: `Option ${i + 1}`, value: i + 1, disabled: i === 3, active: i === 1, })), ); const onSelect = (e) => { console.log('list selected', e); }; </script> <style scoped lang="scss"> .my-list { width: 280px; } </style>点击选项后,select事件会抛出用户传入的完整选项数据(ListItemData),方便回调中直接读取label、value或自定义字段。
从源码看点击处理的核心逻辑在 use-list.ts 的onItemClick中:
const onItemClick = (item: ListItemData) => { if (item.disabled) { return; } if (props.selectable) { for (let i = 0; i < props.data.length; i++) { props.data[i].active = props.data[i].value === item.value; } } emits('select', { ...item }); };需要注意三个实现细节:
- 禁用项(
disabled: true)直接拦截,不会触发select事件; - 选中高亮是单选语义——点击某一项后,组件会遍历整个
data数组,将active置为value匹配的那一项,其余项清空; - 事件抛出的
{ ...item }是数据的浅拷贝,避免外部回调意外改动源数据对象。
样式层面,list.scss 中定义了.mc-list-item-active高亮态、.mc-list-item-disabled禁用态(置灰、cursor: not-allowed),并统一应用了$devui-border-radius圆角与过渡动画。
水平排列与展示形态
除垂直列表外,List 还支持水平排列,适用于"展示类"场景(如标签、能力选项等)。核心参数有三个:
direction:排列方向,'vertical'(默认)或'horizontal';variant:选项展示形态,取值'transparent' | 'filled' | 'bordered' | 'none';autoWrap:水平排列时是否自动换行,默认true;selectable:设为false可禁用点击时的选中高亮。
<template> <div style="display: flex; margin-bottom: 10px; align-items: center;"> <div style="margin-right: 10px;">展示形态</div> <d-radio-group v-model="selectedVariant" direction="row"> <d-radio v-for="item in variantList" :key="item.value" :value="item.value">{{ item.label }}</d-radio> </d-radio-group> </div> <div style="display: flex; margin-bottom: 10px; align-items: center;"> <div style="margin-right: 10px;">自动换行</div> <d-switch v-model="autoWrap" size="sm"></d-switch> </div> <McList :data="options" :selectable="false" :autoWrap="autoWrap" direction="horizontal" :variant="selectedVariant"></McList> </template> <script setup> import { defineComponent, ref } from 'vue'; const selectedVariant = ref('bordered'); const autoWrap = ref(true); const variantList = ref([ { label: '填充', value: 'filled' }, { label: '边框', value: 'bordered' }, ]); const options = ref(new Array(10).fill(0).map((item, i) => ({ label: `Option ${i + 1}`, value: i + 1 }))); </script>各variant形态在 list.scss 中均有对应样式:
filled:使用$devui-gray-form-control-bg作为背景色(填充态);bordered:加1px solid $devui-dividing-line边框;transparent(默认):无背景色,仅靠悬浮与选中高亮区分;none:组件完全不做样式干预,交由用户自定义。
水平排列时,容器会切换为mc-list-horizontal类,通过display: flex; flex-wrap: wrap; gap: 12px实现换行与间距;当autoWrap为false时追加mc-list-nowrap类(flex-wrap: nowrap),且每个选项flex: none保持自身宽度。对应的动态类名生成逻辑见 use-list.ts 中的useListRender:
const listClasses = computed(() => ({ 'mc-list': true, 'mc-list-horizontal': props.direction === ListDirection.Horizontal, 'mc-list-nowrap': props.direction === ListDirection.Horizontal && !props.autoWrap, }));值得注意的是,direction、autoWrap的默认值定义在 list-types.ts 中:direction默认'vertical'、autoWrap默认true,与文档描述一致。
自定义选项内容:插槽与完全自定义
List 提供item插槽,作用域参数为{ item }(即当前的ListItemData),支持两种自定义粒度:
部分自定义:通过#item插槽改写选项内容,悬浮高亮、圆角、禁用态等仍由组件控制(默认渲染分支仍带mc-list-item等样式类)。
完全自定义:将variant设为'none',此时组件直接渲染插槽内容,不包裹任何样式类,悬浮高亮、圆角等全部由用户自行实现。
<template> <h4>部分自定义</h4> <p>通过item插槽自定义选项的内容,悬浮高亮和圆角等样式仍由组件控制。</p> <McList :data="options"> <template #item="{ item }"><i :class="item.icon" style="margin-right:4px;"></i>{{ item.label }}</template> </McList> <h4>完全自定义</h4> <p>通过item插槽自定义选项的内容,variant参数设置为none,悬浮高亮和圆角等样式也由用户自行控制。</p> <McList :data="options" variant="none"> <template #item="{ item }"> <div class="custom-list-item"><i :class="item.icon" style="margin-right:4px;"></i>{{ item.label }}</div> </template> </McList> </template> <script setup> import { defineComponent, ref } from 'vue'; const options = ref([ { label: `Option 1`, value: 1, icon: 'icon-information' }, { label: `Option 2`, value: 2, icon: 'icon-inform' }, { label: `Option 3`, value: 3, icon: 'icon-infrastructure' }, ]); </script> <style scoped lang="scss"> .custom-list-item { line-height: 20px; padding: 8px; cursor: pointer; &:hover { background-color: var(--devui-list-item-active-bg); } } </style>插槽机制在 List.vue 模板中清晰可见:当variant === ListVariant.None时直接渲染裸插槽;否则渲染带样式的div.mc-list-item,插槽作为内部内容(未提供插槽时默认输出item.label)。
这里还体现了ListItemData的灵活性——[key: string]: any允许携带任意自定义字段(如上例中的icon),插槽和select事件回调都能拿到这些附加数据,非常适合 AI 场景下给选项挂图标、类型标签等元信息。
快捷键操作:打造连贯的下拉选择体验
enableShortKey用于开启键盘快捷键,适合下拉列表场景下的连贯性操作。开启后:
- 按上 / 下方向键在选项中"预选中"(pre-select,视觉上等同悬浮高亮);
- 按回车键完成选择,触发
select事件; - 通过
inputEl指定监听按键的元素,默认在document上监听。
典型用法是与d-dropdown+d-input组合成可键盘操作的下拉框:
<template> <d-dropdown :visible="isOpen" trigger="manually" :position="['top-start']" @toggle="(e) => (isOpen = e)"> <d-input ref="inputEl" v-model="inputValue" placeholder="点击后,可通过快捷键选中选项,并将选中项回显到这里" clearable @click="() => (isOpen = true)" ></d-input> <template #menu> <McList :data="options" enableShortKey :inputEl="inputEl" style="width:300px" @select="onSelect"></McList> </template> </d-dropdown> </template> <script setup> import { defineComponent, ref } from 'vue'; const inputValue = ref(''); const inputEl = ref(); const isOpen = ref(false); const options = ref(new Array(4).fill(0).map((item, i) => ({ label: `Option ${i + 1}`, value: i + 1 }))); const onSelect = (e) => { inputValue.value = e.label; isOpen.value = false; }; </script>底层实现位于 use-list.ts 的onKeyDown:
const onKeyDown = (e: KeyboardEvent) => { if (e.code === ArrowUp) { preSelectIndex.value = preSelectIndex.value === 0 ? props.data.length - 1 : preSelectIndex.value - 1; } if (e.code === ArrowDown) { preSelectIndex.value = preSelectIndex.value === props.data.length - 1 ? 0 : preSelectIndex.value + 1; } if (e.code === Enter) { if (props.selectable) { onItemClick(props.data[preSelectIndex.value]); } } };有三个值得留意的实现细节:
- 循环索引:在首项按上键会跳到末尾,在末项按下键会跳回首项,保证键盘操作不会被"卡死";
- 按回车时实际复用了
onItemClick,因此同样遵守disabled拦截与selectable开关; - 预选中的选项会挂上
mc-list-item-pre-selection类,在 list.scss 中与:hover共享同一套高亮样式。
inputEl的解析逻辑(initListenDom部分)也很智能:如果传入元素本身是TEXTAREA或INPUT标签(常量InputTagNames,见 const.ts),直接监听该元素;否则会在元素内查找textarea或input作为监听目标,找不到则回退到document。这保证了传入 DevUId-input这类组件实例时也能正确工作——注意 demo 中通过ref="inputEl"拿到的是组件实例,而use-list.ts中props.inputEl.$el ?? props.inputEl会先尝试解包出真实 DOM 元素。
这一特性在仓库中已有真实落地:Mention 组件的演示 playground/src/mention-demo.vue 中,McList就以enableShortKey :inputEl="inputRef"的形式嵌套在McInput的#mention插槽内,配合触发字符实现 @ 提及候选,快捷键选中后回填到输入框;docs/playground/playground.md 中 Agent 切换下拉也直接使用McList承载agentList数据并通过select事件更新选中项。这两处都印证了"List + 输入框 + 快捷键"的组合是 Mention 类功能的标准搭建方式。
懒加载:滚动到底自动加载更多
enableLazyLoad开启懒加载后,列表滚动到接近底部时会触发loadMore事件,适合历史消息等大数据量场景的分页加载。
组件默认设置列表最大高度为 300px(max-height: 300px; overflow: auto,见 list.scss),你可以直接在组件标签上用style或class覆盖,例如设为height: 400px。
<template> <div v-loading="loading" :backdrop="true" position-type="relative" :view="{ top: '50%', left: '50%' }" :z-index="100"> <McList :data="options" enableLazyLoad @loadMore="onLoadMore" style="height:400px"></McList> </div> </template> <script setup> import { defineComponent, ref } from 'vue'; const options = ref(new Array(10).fill(0).map((item, i) => ({ label: `Option ${i + 1}`, value: i + 1 }))); const loading = ref(false); const total = 30; const onLoadMore = () => { if (loading.value || options.value.length >= total) { return; } loading.value = true; setTimeout(() => { const newData = new Array(10).fill(0).map((item, i) => ({ label: `Option ${options.value.length + i}`, value: options.value.length + i, })); options.value.push(...newData); loading.value = false; }, 1500); }; </script>触发的判断逻辑在onListScroll中:
const onListScroll = (e: Event) => { if (!props.enableLazyLoad || props.direction !== ListDirection.Vertical) { return; } const targetEl = e.target as HTMLElement; const scrollHeight = targetEl.scrollHeight; const clientHeight = targetEl.clientHeight; const scrollTop = targetEl.scrollTop; if (scrollHeight - clientHeight - scrollTop < LazyLoadThreshold) { emits('loadMore', e); } };这里有两个关键约束,对应文档中loadMore事件"仅在启用懒加载且排列方式为垂直时触发"的说明:
- 未开启
enableLazyLoad直接返回; - 水平排列(
direction !== 'vertical')时不触发懒加载,因为水平列表的滚动维度与高度阈值判断不匹配。
触发阈值为常量LazyLoadThreshold = 50(px),定义在 const.ts,即"剩余可滚动距离小于 50px"时提前触发loadMore,为异步请求留出缓冲。需要注意的是事件触发时组件本身不会自动追加数据,追加逻辑(含防重入的loading判断与总量上限判断)需要你在回调中自行实现,如上例所示。
参数、事件与插槽速查表
以下 API 定义完整继承自官方文档 api.md,并在 list-types.ts 中有对应 props 声明(默认值与文档一致)。
参数(Props)
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| data | ListItemData[] | [] | 需要显示的数据 |
| direction | ListDirection | 'vertical' | 选项排列方式,垂直或者水平 |
| autoWrap | boolean | true | 是否自动换行,仅在水平排列时生效 |
| variant | ListVariant | 'transparent' | 选项的形态,默认无背景色,悬浮和选中高亮 |
| selectable | boolean | true | 是否可选中,可选中时,点击后选项高亮 |
| enableLazyLoad | boolean | false | 是否支持懒加载 |
| enableShortKey | boolean | false | 是否支持快捷键选择 |
| inputEl | object | -- | 当支持快捷键选择时,监听键盘事件的元素,默认为document |
事件(Events)
| 事件名 | 类型 | 说明 |
|---|---|---|
| select | (e: ListItemData) => void | 点击选项时触发的事件,参数为用户传入的选项数据 |
| loadMore | (e: Event) => void | 滚动到底时触发的事件,仅在启用懒加载且排列方式为垂直时触发 |
插槽(Slots)
| 插槽名 | 返回值 | 说明 |
|---|---|---|
| item | { item: ListItemData } | 自定义选项内容 |
类型定义
三个核心类型定义同样直接来自官方文档,并与 list-types.ts 中的源码声明一一对应,可在业务代码中直接 import 使用。
ListItemData
interface ListItemData { label: string; // 显示的内容 value: string | number; // 对应的值 disabled?: boolean; // 是否禁用 active?: boolean; // 是否选中 [key: string]: any; // 其他自定义字段 }ListDirection
enum ListDirection { Horizontal = 'horizontal', Vertical = 'vertical', }ListVariant
enum ListVariant { Transparent = 'transparent', // 无背景色,悬浮和选中高亮 Filled = 'filled', // 带背景色 Bordered = 'bordered', // 带边框 None = 'none', //无任何样式,完全由用户自定义 }组件扩展:Angular 同构实现
值得一提的是,MateChat 还在 packages/components-ng 中提供了 Angular 版本的mc-list组件(见 list.component.ts),其direction、autoWrap、variant、enableLazyLoad、enableShortKey、data、inputEl、selectable输入项与 Vue 版完全对齐,select/loadMore输出事件语义一致,并将核心逻辑下沉到ListFoundation。两个框架版本共享同一套ListDirection/ListVariant/ListItemData类型与LazyLoadThreshold常量,保证了跨框架行为的一致性——如果你在 Angular 工程中集成,可参考同样的参数体系快速迁移。
小结
MateChat 的McList用极小的 API 面覆盖了列表场景的大多数诉求:垂直选中(disabled/active+select)、水平展示(direction/variant/autoWrap)、插槽自定义、键盘连贯操作(enableShortKey/inputEl)以及滚动懒加载(enableLazyLoad/loadMore)。配合源码阅读,你可以清楚看到点击拦截、单选高亮、快捷键循环索引、50px 懒加载阈值等关键行为的精确实现,从而在历史对话列表、Agent 选择、Mention 提及候选等 AI 应用场景中把它用得更稳、更透。
- 前端
- UI组件
- AI 应用
【免费下载链接】MateChat
前端智能化场景解决方案UI库,轻松构建你的AI应用,我们将持续完善更新,欢迎你的使用与建议。 官网地址:https://matechat.gitcode.com
相关推荐
MateChat @matechat/ng 中 mc-list 列表组件全解析:参数、快捷键、懒加载与源码实现
MateChat @matechat/ng 中 mc list 列表组件全解析:参数、快捷键、懒加载与源码实现 MateChat 的 Angular 组件库 @
前端UI组件AI 应用MateChat Angular 版 mc-list 列表组件:参数、事件与懒加载、快捷键的完整实现解析
MateChat Angular 版 mc list 列表组件:参数、事件与懒加载、快捷键的完整实现解析 本篇基于 MateChat 的 Angular 组件库
前端UI组件AI 应用Material Web 组件实战:使用 `md-list` 与 `md-list-item` 构建连续垂直列表
Material Web 组件实战:使用 md list 与 md list item 构建连续垂直列表 本指南以 Material Web(Material
UI组件前端设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考