Files
workspace/code/fms/.codebuddy/plans/fms-vue-tabs_bc485ecf.md
T
2026-08-16 22:01:32 +08:00

154 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: fms-vue-tabs
overview: 在 fms-vue 组件库中新增 Tabs 标签页组件:line 线型 + top/left/right 三种标签方向,同时支持 items 数组与 TabPane 插槽双数据源,含键盘方向键导航、懒渲染缓存、禁用态与完整 ARIA,并在演示站 App.vue 中新增展示。
design:
architecture:
framework: vue
styleKeywords:
- 克制简洁
- 清晰分层
- 主色指示条
fontSystem:
fontFamily: PingFang SC
heading:
size: 14px
weight: 600
subheading:
size: 14px
weight: 500
body:
size: 14px
weight: 400
colorSystem:
primary:
- "#0f172b"
background:
- "#ffffff"
- "#f5f6f8"
text:
- "#1f2329"
- "#86909c"
- "#c9cdd4"
functional:
- "#e5e6eb"
todos:
- id: create-tab-pane-and-normalize
content: 参考 [skill:antdv-next] 核对 API 语义,创建 tab-pane.vue 并实现 tabs.vue 双数据源归一与激活管理
status: completed
- id: implement-tabs-interaction
content: 在 tabs.vue 完成模板:tablist/方向键导航/懒渲染缓存/ARIA 关联
status: completed
dependencies:
- create-tab-pane-and-normalize
- id: write-tabs-styles
content: 用 [skill:ui-ux-pro-max] 校验后编写 index.scss:top/left/right 布局、指示条与各状态样式
status: completed
dependencies:
- create-tab-pane-and-normalize
- id: add-demo-and-check
content: App.vue 新增 Tabs 导航项与演示 section,做静态检查并汇报
status: completed
dependencies:
- implement-tabs-interaction
- write-tabs-styles
---
## 产品概述
为 fms-vue 自研 UI 组件库新增 Tabs 标签页组件,遵循 `fms-vue/开发规范.md` 约束并借鉴 `参考组件库/antdv-next` 的 Tabs 设计语义。包含 Tabs(根)与 TabPane(子声明)两个组件,用于在同一区域内分组展示内容、切换视图。
## 核心功能
- **双数据源**:支持 `items` 数组(`[{ key, label, disabled?, content? }]`)与 TabPane 插槽两种声明方式,与 Select 的 options/插槽模式一致,二选一使用。
- **line 线型指示**:激活标签底部(top)或侧边(left/right)显示主色指示条,标签栏有浅色分隔线。
- **侧边/垂直方向**:`placement` 支持 `top`(默认,横排在上)、`left`、`right`(纵向排列在左/右,内容区自适应剩余宽度)。
- **禁用态**:支持单个 TabPane 禁用与 Tabs 整组禁用,反映到原生属性与视觉(降透明 + not-allowed)。
- **键盘与无障碍**:tablist/tab/tabpanel 语义,方向键循环切换激活(top 用左右键,left/right 用上下键),roving tabindex、aria-selected/aria-controls/aria-labelledby,`:focus-visible` 焦点样式。
- **懒渲染 + 缓存**:非激活面板不渲染,首次激活后缓存(切换不销毁),符合性能优先规范。
- **自定义标签**:`#label` 插槽支持自定义标题内容(TabPane 与 items 两种模式均可用)。
## 技术栈
- Vue 3.5 `<script setup>` + Sass(项目现有技术栈,不引入新依赖)
- 样式复用 `src/theme/tokens.css` 的 `--fms-*` token,图标按需从 `@lucide/vue` 导入(本组件无需图标)
- 参照 `radio-group/radio` 的协作组件写法与 `select` 的"数组 + 插槽"双数据源模式
## 实现思路
Tabs 根组件通过 `slots.default()` 遍历插槽 vnode(展平 Fragment、过滤注释),识别 `type.name === 'TabPane'` 的子组件并提取其 props 与子插槽(default 内容、label 自定义标题)——此机制参考 antdv-next 的 `useLegacyItems`。归一化后的 panes 统一管理激活态、渲染与键盘导航;items 数组模式 normalize 为同构数据结构,两条路径输出一致的渲染结果。
### 关键技术决策
- **vnode 解析而非 provide/inject**:TabPane 是纯数据声明组件(自身渲染空),激活状态必须由根组件统一管理,根组件负责渲染 tab 头列表与内容区;这与 antd 的 TabPane 语义一致,优于 Radio 的 provide/inject 协作模式(radio 每个子项自己渲染视觉)。
- **指示条不加 JS 测量**:line 模式指示条用激活 tab 自身的 `border-bottom`/`border-left`(2px 主色)实现,标签栏底部/侧边用 1px `--fms-border`。不做滑动跟随动画,避免布局读取与高频重排,符合"过渡限低成本属性、不主动构建"的规范。
- **懒渲染 + 缓存**:`renderedKeys` Set 记录已激活 pane,模板 `v-if`(首次激活才渲染)+ `v-show`(激活显示)组合,首屏只渲染激活项且切换不重建内容。
- **激活回退**:`modelValue` 未传或指向不存在/禁用的 pane 时,回退到第一个可用 pane,且不写回 modelValue(保持受控性)。
### 实现注意
- 键盘仅遍历 enabled panes 循环移动,禁用项跳过;方向键按 `placement` 映射(top: Left/Right,left/right: Up/Down,Home/End 定位首尾)。
- 切换时先更新内部激活态再 emit `update:modelValue` 与 `change`;点击触发 `tabClick(key, event)`。
- id 用 `useId()` 生成,tab 与 tabpanel 通过 `aria-controls`/`aria-labelledby` 关联。
- 卸载无需清理(无全局监听器、无定时器)。
## 架构设计
```
Tabs(根组件)
├─ 数据源归一(items 数组 / TabPane 插槽 vnode 解析)→ mergedPanes
├─ 激活管理(modelValue 回退、select、emit)
├─ 渲染:nav 列表(role=tablist)+ 内容区(role=tabpanel,v-if/v-show 懒渲染)
├─ 键盘导航(方向键循环 + roving tabindex)
└─ 布局:placement top(纵向堆叠)/ left|right(flex 行)
```
## 目录结构
```
fms-vue/src/components/ui/tabs/
├── tabs.vue # [NEW] Tabs 根组件。props: modelValue/placement/disabled/items;
│ # emits: update:modelValue/change/tabClick;实现 vnode 解析、激活管理、
│ # 懒渲染缓存、键盘导航与 ARIA 完整模板。
├── tab-pane.vue # [NEW] TabPane 数据声明组件。props: tabKey/label/disabled;
│ # 插槽: default(面板内容)/ label(自定义标题);
│ # defineOptions({ name: 'TabPane' }) 供根组件识别;自身渲染空节点。
└── index.scss # [NEW] 样式:top/left/right 三种布局、line 指示条、hover/disabled/focus 态、
# prefers-reduced-motion;全部复用 --fms-* token,tabs.vue 内 @use 引入。
fms-vue/src/App.vue # [MODIFY] 通用分组新增导航项 { key: 'tabs', label: 'Tabs 标签页' },
# 新增演示 section(基础用法/items 数组/侧边方向/禁用/自定义标签/懒渲染)。
```
## 关键数据结构(伪类型)
```js
// 归一化后的 pane(items 数组与 TabPane 插槽统一输出)
{ key: String, label: String|VNode, disabled: Boolean,
content: () => VNode[], // 内容渲染函数(懒渲染时按需调用)
labelVNode: () => VNode[] // 自定义标题渲染函数,无则 undefined
}
```
## 设计风格
与现有组件库一致的克制现代风格:干净的分层、克制的交互反馈、清晰的状态表达。
## 视觉要点
- **布局**:top 模式标签横排在上、内容在下,标签栏底部 1px `--fms-border` 分隔;left/right 模式为 flex 行,标签纵向排列于左/右,内容区 `flex: 1 1 auto` 自适应。垂直方向标签栏加 `--fms-border` 侧边分隔,激活标签文字主色并配 `--fms-secondary-hover` 浅背景增强选中识别。
- **指示条(line)**:top 模式激活标签自身 2px 主色下边框,left/right 为 2px 主色侧边框,无滑动动画,稳定且零布局读取。
- **状态反馈**:hover 文字变主色;禁用用 `--fms-disabled-text` + `not-allowed` 光标并降透明;选中不单靠颜色,同时用指示条与文字粗细双重表达。
- **间距**:标签 padding 约 0 16px、高度对齐 `--fms-control-height`(32px),标签间距紧凑;内容区 padding 16px 0 与标签栏对齐。
- **焦点**:`:focus-visible` 主色 outline 2px + 2px offset。
- **动画**:仅 hover 文字颜色过渡(低成本属性),`prefers-reduced-motion` 下关闭。
## Agent 扩展
### Skill
- **antdv-next**
- 用途:核对 antdv-next Tabs/TabPane 的 API 语义(tabKey 解析顺序、键盘交互、ARIA 细节),确保借鉴准确。
- 预期产出:tabs.vue 的 props/事件/键盘映射与参考库一致,无遗漏交互。
- **ui-ux-pro-max**
- 用途:辅助校验 Tabs 视觉细节(间距、指示条、禁用/焦点状态表达),与组件库整体风格对齐。
- 预期产出:index.scss 的视觉规格(间距、指示条、状态色)符合现代组件库实践。