20260816220132

This commit is contained in:
oneao committed 2026-08-16 22:01:32 +08:00
1 parent b6f9a4c912
commit 72993ecdcd
179 files changed
+20050 -633

No files matched your search

@@ -0,0 +1,149 @@
---
name: select-tooltip-components
overview: 基于已完成的 Popover 基座派生两个组件:Tooltip(hover 文本提示薄封装)与 Select(基础单选下拉,options prop + 默认插槽双数据源,含 clearable、键盘导航、浮层宽度跟随),并在 App.vue 增补演示。
todos:
- id: implement-tooltip
content: 用 [skill:antdv-next] 查 Tooltip API 并实现 tooltip.vue 与 index.scss
status: completed
- id: implement-select
content: 用 [skill:antdv-next] 查 Select API 并实现 select.vue、index.scss 及新增 token
status: completed
- id: integrate-app
content: App.vue 接入 Select 与 Tooltip 的导航分类、演示 section 与示例状态
status: completed
dependencies:
- implement-tooltip
- implement-select
- id: verify
content: 用 [skill:playwright-cli] 验证两组件交互并执行 oxlint 与 oxfmt 检查
status: completed
dependencies:
- 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 与定位模块:
```mermaid
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、键盘导航依赖此定义):
```js
// 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 测量确认功能正确、无定位偏移,验证后清理临时文件