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,154 @@
---
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 的视觉规格(间距、指示条、状态色)符合现代组件库实践。