Files
workspace/code/fms/.codebuddy/plans/select-tooltip-components_ad1fad70.md
2026-08-16 22:01:32 +08:00

9.2 KiB
Raw Permalink Blame History

name, overview, todos
name overview todos
select-tooltip-components 基于已完成的 Popover 基座派生两个组件:Tooltip(hover 文本提示薄封装)与 Select(基础单选下拉,options prop + 默认插槽双数据源,含 clearable、键盘导航、浮层宽度跟随),并在 App.vue 增补演示。
id content status
implement-tooltip 用 [skill:antdv-next] 查 Tooltip API 并实现 tooltip.vue 与 index.scss completed
id content status
implement-select 用 [skill:antdv-next] 查 Select API 并实现 select.vue、index.scss 及新增 token completed
id content status dependencies
integrate-app App.vue 接入 Select 与 Tooltip 的导航分类、演示 section 与示例状态 completed
implement-tooltip
implement-select
id content status dependencies
verify 用 [skill:playwright-cli] 验证两组件交互并执行 oxlint 与 oxfmt 检查 completed
integrate-app

产品概述

基于已完成的 Popover 浮层基座,新增两个基础组件并接入组件库演示页:

  • Select 下拉选择:基础单选版本,提供与 Input 一致的框体视觉、下拉选项列表、键盘导航、清除与禁用能力,浮层宽度跟随触发器。
  • Tooltip 文字提示:Popover 的 hover 薄封装,提供纯文本提示与富内容插槽两种用法。

核心功能

Select 单选下拉

  • 基础单选:选中值高亮显示,点击选项选中后自动收起浮层
  • 数据源双形态:options 数组 prop([{ label, value, disabled }])渲染标准选项;未传 options 时渲染默认插槽自定义内容
  • 触发器框体:显示选中项文案或 placeholder,右侧 ChevronDown 图标随开合旋转
  • clearable:有值且悬停/聚焦时显示清除按钮,点击清空
  • disabled:整组件禁用,不响应交互
  • 键盘导航:上下方向键移动高亮(跳过禁用项)、Enter 确认选中、Esc 关闭浮层
  • 浮层宽度跟随触发器宽度,超长选项自动换行,列表超高滚动

Tooltip 文字提示

  • hover 触发显示,移入浮层不关闭,移出延迟关闭
  • content prop 纯文本提示为主用法,#content 插槽承载富内容(插槽优先)
  • 支持 12 方位、箭头开关、进入/离开延迟、禁用态、受控/非受控

两个组件均沿用现有设计 token(卡片、边框、主色、圆角、字号),保持组件库视觉一致。

Tech Stack

  • 前端框架:Vue 3(<script setup> + Composition API)
  • 样式:SCSS + CSS 变量(沿用现有 design tokens)
  • 图标:@lucide/vue
  • 基座复用:现有 Popover 组件与 utils/position.js 定位纯函数,不改动

Implementation Approach

总体策略

不引入第三方依赖、不新增架构模式。Tooltip 与 Select 均作为 Popover 的派生组件,通过插槽透传复用 Popover 的定位、Teleport、Transition、click outside 与 Esc 关闭能力;各自只负责自身语义、交互和视觉。

  • Tooltip:固定 trigger="hover" 的薄封装,默认插槽映射为 Popover 的 #trigger,内容由 content prop 或 #content 插槽提供,转发 Popover 相关 props/emits。
  • Select:内部组合 Popover(trigger="click"、placement="bottom-start"、arrow=false、offset=4),自建触发器框体与下拉列表,管理选中值、键盘高亮与浮层宽度跟随。

关键技术决策

  1. 受控打开状态:Select 内部维护 openRef,通过 v-model:open 绑定 Popover 实现受控,并在 openChange 时重置键盘高亮索引。避免在 Select 内重复实现开合逻辑。
  2. 双数据源:options prop 存在时渲染标准选项列表(含选中/禁用/键盘逻辑);未传时渲染默认插槽(自定义内容逃生舱,由调用方控制关闭)。符合"基础单选、保持精简"的要求。
  3. 浮层宽度跟随:Popover 定位基于 trigger 尺寸,但浮层 Teleport 到 body 无法继承 CSS 宽度。Select 在打开时测量 .select-trigger 的 offsetWidth,以 min-width 内联样式注入 .select-dropdown,实现宽度跟随且允许内容自适应扩展。
  4. 键盘导航:下拉容器设 tabindex="-1",打开时 nextTick 聚焦以接收键盘事件;ArrowUp/ArrowDown 循环移动 activeIndex(跳过 disabled),Enter 选中,Esc 交由 Popover 的 document 监听统一关闭,避免双重处理。
  5. 清除按钮事件隔离:清除按钮 @click.stop 阻止冒泡,避免触发 Popover 的 toggle 开合。

性能与可靠性

  • 复用 Popover 的 rAF 合帧滚动跟随,Select/Tooltip 不新增全局监听器。
  • options 查找选中文案使用 computed 缓存;选项列表渲染无 N+1。
  • 键盘高亮仅更新 activeIndex 状态,选项使用 mouseenter 同步高亮,避免高频重排。

Implementation Notes

  • Popover 透传约定:Popover 的 open prop 默认 undefined 表示非受控;Tooltip/Select 的受控透传必须保持 undefined 语义,不能默认 false(否则破坏 Popover 非受控回退逻辑)。
  • Tooltip 语义:Popover 的 contentRole 已按 trigger === 'hover' 返回 'tooltip',Tooltip 固定 hover 即自动获得正确 ARIA role,无需额外处理。
  • Select 插槽渲染位置:<slot name="default" /> 必须放在 Popover #content 的 .select-dropdown 内部,且仅当 options 未提供时渲染;useSlots().default 用于判断插槽是否存在。
  • scoped 样式边界:Tooltip/Select 的浮层内容通过 slot 进入 Popover 的 Teleport 子树,自身 scoped 样式可作用于 slot 内容(Vue 会打上父级 scope id),但不要用 Tooltip 的 scss 覆盖 Popover 的 .popover-content(需 :deep 且易耦合),浮层基础样式统一由 Popover 提供。
  • 验证命令:用 pnpm exec oxlint <files> 与 pnpm exec oxfmt --check <files> 针对性检查源文件;全项目 pnpm lint 会误报 dist-verify 构建产物,pnpm build 会被批量删除安全拦截,均属环境限制,以 dev server 验证为准。
  • 清理:验证后关闭 dev server 与浏览器,删除临时截图/snapshot/日志文件。

Architecture Design

两个新组件与既有基座为单向依赖的派生关系,不修改 Popover 与定位模块:

graph TD
  Tooltip[Tooltip 薄封装] -->|trigger=hover| Popover[Popover 基座]
  Select[Select 单选下拉] -->|trigger=click bottom-start arrow=false| Popover
  Popover --> Position[utils/position.js 定位纯函数]
  Select --> Tokens[tokens.css 设计变量]
  Popover --> Tokens
  • Tooltip:仅做 props/emits 转发与插槽映射,无内部状态。
  • Select:内部状态含 openRef(开合)、activeIndex(键盘高亮)、dropdownMinWidth(宽度跟随);计算属性含 normalizedOptions(选项规范化)、selectedLabel(选中文案)、hasValue。

Directory Structure

fms-vue/src/
├── theme/
│   └── tokens.css                        # [MODIFY] 新增 --fms-select-dropdown-max-height(浅色定义,尺寸不随主题变化)
├── components/ui/
│   ├── tooltip/
│   │   ├── tooltip.vue                   # [NEW] Tooltip 组件:Popover hover 薄封装,content prop + #content 插槽 + 默认插槽触发器
│   │   └── index.scss                    # [NEW] Tooltip 微调样式:文本换行/行高,浮层基础样式复用 Popover
│   └── select/
│       ├── select.vue                    # [NEW] Select 组件:触发器框体 + 下拉选项列表 + 键盘导航 + clearable + 宽度跟随
│       └── index.scss                    # [NEW] Select 样式:触发器/箭头/清除按钮/下拉列表/选项选中/禁用/高亮态
└── App.vue                               # [MODIFY] 导入组件、导航分类接入(Select→数据录入,Tooltip→反馈)、新增演示 section 与示例状态/样式

Key Code Structures

Select 的核心接口契约(双数据源、单选、clearable、键盘导航依赖此定义):

// select.vue 核心 props / emits
defineProps({
  /** 双向绑定的选中值 */
  modelValue: { type: [String, Number], default: undefined },
  /** 选项数组,存在时渲染标准选项列表;未传则渲染默认插槽 */
  options: { type: Array, default: undefined }, // [{ label, value, disabled }]
  placeholder: { type: String, default: '请选择' },
  disabled: { type: Boolean, default: false },
  clearable: { type: Boolean, default: false },
})
defineEmits(['update:modelValue', 'change']) // change 参数为 (value, option)

Agent Extensions

Skill

  • antdv-next
  • 用途:查询 Antdv Next 中 Select 与 Tooltip 的标准 props/events/slots、键盘导航与浮层宽度跟随实现细节,作为 API 设计与交互参考
  • 预期结果:确认 Select/Tooltip 的关键接口命名与交互约定,确保派生组件 API 与成熟组件库对齐
  • playwright-cli
  • 用途:启动 vite dev server 后自动化验证 Select/Tooltip 的渲染效果与交互(打开/选中/清除/禁用/键盘导航/宽度跟随/受控开合)
  • 预期结果:通过截图与坐标/elementFromPoint 测量确认功能正确、无定位偏移,验证后清理临时文件