news 2026/10/14 1:59:26

MateChat List 列表组件实战指南:垂直选择、水平排列、快捷键与懒加载全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MateChat List 列表组件实战指南:垂直选择、水平排列、快捷键与懒加载全解析
  • 前端
  • UI组件
  • AI 应用

【免费下载链接】MateChat

前端智能化场景解决方案UI库,轻松构建你的AI应用,我们将持续完善更新,欢迎你的使用与建议。 官网地址:https://matechat.gitcode.com

项目地址:https://gitcode.com/DevCloudFE/MateChat
点击查看免费下载

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 }); };

需要注意三个实现细节:

  1. 禁用项(disabled: true)直接拦截,不会触发select事件;
  2. 选中高亮是单选语义——点击某一项后,组件会遍历整个data数组,将active置为value匹配的那一项,其余项清空;
  3. 事件抛出的{ ...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]); } } };

有三个值得留意的实现细节:

  1. 循环索引:在首项按上键会跳到末尾,在末项按下键会跳回首项,保证键盘操作不会被"卡死";
  2. 按回车时实际复用了onItemClick,因此同样遵守disabled拦截与selectable开关;
  3. 预选中的选项会挂上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事件"仅在启用懒加载且排列方式为垂直时触发"的说明:

  1. 未开启enableLazyLoad直接返回;
  2. 水平排列(direction !== 'vertical')时不触发懒加载,因为水平列表的滚动维度与高度阈值判断不匹配。

触发阈值为常量LazyLoadThreshold = 50(px),定义在 const.ts,即"剩余可滚动距离小于 50px"时提前触发loadMore,为异步请求留出缓冲。需要注意的是事件触发时组件本身不会自动追加数据,追加逻辑(含防重入的loading判断与总量上限判断)需要你在回调中自行实现,如上例所示。

参数、事件与插槽速查表

以下 API 定义完整继承自官方文档 api.md,并在 list-types.ts 中有对应 props 声明(默认值与文档一致)。

参数(Props)

参数名类型默认值说明
dataListItemData[][]需要显示的数据
directionListDirection'vertical'选项排列方式,垂直或者水平
autoWrapbooleantrue是否自动换行,仅在水平排列时生效
variantListVariant'transparent'选项的形态,默认无背景色,悬浮和选中高亮
selectablebooleantrue是否可选中,可选中时,点击后选项高亮
enableLazyLoadbooleanfalse是否支持懒加载
enableShortKeybooleanfalse是否支持快捷键选择
inputElobject--当支持快捷键选择时,监听键盘事件的元素,默认为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

项目地址:https://gitcode.com/DevCloudFE/MateChat
点击查看免费下载
上一篇:揭秘Beyond All Reason:开源RTS游戏的复兴与战略革新
下一篇:突破Android图像加载瓶颈:Coil与Jetpack Compose的无缝集成方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 1:58:41

Django购物商城系统实战:MTV架构、订单支付与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:58:37

SwiftUI Agent Skill:界面能力服务化与技能化封装实践

1. 从“SwiftUI-Agent-Skill”这个名字说起&#xff1a;它到底想解决什么问题第一次看到“SwiftUI-Agent-Skill”这个组合词&#xff0c;我脑子里蹦出来的不是某个具体框架&#xff0c;而是一种很典型的工程诉求&#xff1a;把 SwiftUI 的界面能力&#xff0c;和某种“代理式”…

作者头像 李华
网站建设 2026/10/14 1:58:27

Spring Boot单体电商系统设计:事务、库存与幂等性实战

简介&#xff1a;本资源是一份面向计算机专业本科生与研究生的MySQL网上购物系统课程设计文档&#xff0c;聚焦Spring Boot框架在B/S架构电商系统中的完整落地实践&#xff0c;帮助学习者掌握需求分析、模块设计、数据库建模&#xff08;含E-R图与表结构&#xff09;、多角色权…

作者头像 李华
网站建设 2026/10/14 1:57:44

Microsoft Edge 无法翻译/翻译失败的一种解决方法:修复 Edge

最近在使用 Microsoft Edge 浏览网页时遇到了一个问题&#xff1a; Edge 自带的网页翻译功能突然无法正常使用&#xff0c;一直提示“无法翻译”或者“翻译失败”。 浏览器本身可以正常使用&#xff0c;网页也能够正常打开&#xff0c;但翻译功能就是无法工作。 在尝试解决之…

作者头像 李华
网站建设 2026/10/14 1:57:18

Ghost误操作恢复全攻略:抢在二次写入前拯救数据

简介&#xff1a;当使用Ghost还原系统时误选目标分区&#xff0c;很可能导致整个硬盘的分区表损坏、数据“凭空消失”&#xff0c;这时掌握正确的恢复方法至关重要。这份图文教程以一位用户的真实误操作案例为切入点&#xff0c;面向遇到类似分区丢失问题的普通电脑用户与维护人…

作者头像 李华