20260913231607
This commit is contained in:
1 parent
f3ad727f1c
commit
c31329d387
179 files changed
+10846
-2684
No files matched your search
@@ -0,0 +1,265 @@
|
||||
---
|
||||
name: 组件库动效体系落地
|
||||
overview: 按动效规范为 fms-vue 组件库补齐动画体系:9 个 motion token 落进 tokens.css,浮层组件(popover/modal/drawer)补离场状态机,纯 CSS 过渡组件批量补课,修复 Spin 组件不旋转的缺陷并收敛业务页手写 keyframes,全站适配 prefers-reduced-motion。
|
||||
todos:
|
||||
- id: motion-tokens
|
||||
content: 用 [subagent:code-explorer] 盘点组件样式入口,并把 9 个动效 token 与 reduced-motion 归零块写入 tokens.css
|
||||
status: completed
|
||||
- id: popover-motion
|
||||
content: 给 popover 补进出场过渡,覆盖 dropdown / tooltip / select 并确认 select 是否继承
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: modal-drawer-motion
|
||||
content: modal 与 drawer 分层进出场,Drawer 用组件级变量承载 240/200ms
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: button-input-motion
|
||||
content: button 补颜色过渡与按下缩放,input 补聚焦过渡
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: form-control-motion
|
||||
content: switch、checkbox、radio、segmented、tabs 补控件微交互过渡
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: collapse-motion
|
||||
content: FormGroup 与树节点用 grid 1fr 到 0fr 实现折叠过渡
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: feedback-motion
|
||||
content: message 与 notification 用 TransitionGroup 补进出场,scrollbar 补淡出
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: spin-motion
|
||||
content: 修复 Spin 旋转并收敛业务页面两处手写 keyframes
|
||||
status: completed
|
||||
dependencies:
|
||||
- motion-tokens
|
||||
- id: motion-verify
|
||||
content: 用 [skill:ui-ux-pro-max] 核对动效与无障碍,跑 oxlint 与编译确认无裸写时长
|
||||
status: completed
|
||||
dependencies:
|
||||
- popover-motion
|
||||
- modal-drawer-motion
|
||||
- button-input-motion
|
||||
- form-control-motion
|
||||
- collapse-motion
|
||||
- feedback-motion
|
||||
- spin-motion
|
||||
---
|
||||
|
||||
## 产品概述
|
||||
|
||||
为 `fms-vue` 内置组件库(`src/components/ui`,26 个组件)建立统一的动效体系:此前组件库里已有的动画代码被移除过,现按用户给定的动效规范全部恢复并补齐。目标是在高频后台操作场景下,让状态变化"可被感知但不拖慢操作"。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- **动效契约**:9 个动效 token(3 档时长、3 档缓动、2 档位移幅度)写入 `src/theme/tokens.css`,沿用 `--fms-` 命名空间
|
||||
- **浮层进出场**:popover、modal、drawer、下拉菜单、tooltip、select 具备进场与退场动画(遮罩与内容分层、进场错峰、退场同时走)
|
||||
- **控件微交互**:按钮 hover 变色 + 按下缩放、输入框聚焦、开关滑块位移、分段控制器与标签页指示器滑动、勾选态变化
|
||||
- **折叠展开**:表单分组与树节点用网格行高过渡实现展开收起
|
||||
- **反馈提示**:message / notification 进出场、滚动条淡出
|
||||
- **加载指示器**:修复 Spin 组件不旋转的缺陷,并将业务页面中各自手写的旋转动画收敛回组件
|
||||
- **无障碍**:系统开启"减少动态效果"时时长归零(保留状态变化、去掉过渡),浮层动画期间不移动焦点
|
||||
|
||||
## 视觉与体验效果
|
||||
|
||||
按钮按下时有极轻微的收缩反馈;浮层从遮罩淡入到内容浮起有清晰的先后层次,关闭时干脆利落;开关拨动、指示器切换跟随手指;折叠区域平滑展开。整体节奏偏快(100~200ms),不出现回弹、位移、页面转场等干扰性效果。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 框架与样式:Vue 3(`script setup`)+ SCSS(沿用 `@use './index.scss'` 拆分约定)
|
||||
- 变量体系:CSS 自定义属性,复用现有 `--fms-` 命名空间与 `src/theme/tokens.css` 单源
|
||||
- 过渡机制:CSS `transition` + Vue 内置 Transition / TransitionGroup 组件
|
||||
- 校验:`pnpm exec oxlint` + Vite 编译检查(dev server 端口 5082)
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 分层策略
|
||||
|
||||
1. **契约层**:9 个 token 落到 `tokens.css` 的 `:root`,并追加 `prefers-reduced-motion` 归零块。所有组件只引用变量、不写裸时长,保证"改一处、全站一致"。
|
||||
2. **行为层**:浮层组件用 Vue 内置 Transition 承载进出场 —— 这是本次工作量最大、也最容易出 bug 的部分,先做 popover(dropdown / tooltip / select 的共同基座)。选择:全局开关只做系统级自动适配,不新建 ConfigProvider、不引入手动开关状态。
|
||||
3. **表现层**:纯 CSS 补控件微交互,无卸载时序风险,可并行推进。
|
||||
|
||||
### 关键决策与理由
|
||||
|
||||
**决策 1:浮层离场用 Vue 内置 Transition,不手写 `isLeaving` 状态类 + `setTimeout`**
|
||||
规范给出的 `.is-leaving` 写法是框架无关方案;本项目是 Vue 3,用内置过渡组件更省代码。更关键的理由是:**reduced-motion 下 token 归零会让 `transition-duration` 变成 `0ms`,此时 `transitionend` 事件不会触发** —— 若卸载靠该事件驱动,浮层会卡在页面上关不掉。Vue 的 Transition 会读取计算样式,时长为 0 时直接结束过渡,天然不会卡死。代价是过渡类名变成 `.fms-xxx-enter-active` 形态,但时长与缓动参数完全按规范取值。
|
||||
|
||||
**决策 2:Drawer 的 240ms / 200ms 用组件级 CSS 变量承载,不裸写数字**
|
||||
规范建议这两个值写死在组件里,但那样会逃出 reduced-motion 的变量归零覆盖,无障碍要求落空。改为组件级变量(默认值保持 240/200 的视觉意图),并在组件级补一条 reduced-motion 覆盖,兼顾"不进全局 token"与"可被关闭"。
|
||||
|
||||
**决策 3:只动 opacity / transform / color 系**
|
||||
所有新增过渡严格限定这三类属性,走合成层,不触发布局与重绘。表格行、数字滚动、页面转场、`transition: all` 一律不做。
|
||||
|
||||
**决策 4:折叠展开用网格 `1fr → 0fr`**
|
||||
`height: auto` 无法过渡。FormGroup 当前已是 `v-show` + grid 结构,直接可用;树节点同步采用同一手法,避免 JS 测高。
|
||||
|
||||
**决策 5:补齐规范对照表遗漏的场景**
|
||||
Switch 滑块、Segmented 滑块、Scrollbar 淡出、Spin 循环旋转(为"禁止无意义图标旋转"开显式豁免)、Tooltip 用更快的档位(高频鼠标扫过场景,避免拖残影);同时明确位移幅度取值用途:下拉 / 提示用 4px、Modal 用 8px、Drawer 用整屏位移。
|
||||
|
||||
**决策 6:按钮 hover 采用规范原值(base 档同时管进出)**
|
||||
此前工具栏 ghost 按钮的"背景色闪烁"真因是性能浮层压住按钮命中区(已单独修复),与过渡时长无关。此处不设"离开即时"之类的例外规则,保持全站一致。
|
||||
|
||||
### 性能与可靠性
|
||||
|
||||
- 只改合成层属性,无布局抖动;无新增 DOM 节点(Transition 不额外渲染)
|
||||
- 每个组件改动互相独立,浮层与纯 CSS 组分批推进,避免一次性全量改动难以定位问题
|
||||
- 退场时序统一由 Transition 管理,不引入自定义定时器,杜绝"浮层关不掉 / 提前卸载闪烁"两类经典 bug
|
||||
|
||||
### 验证方式
|
||||
|
||||
用户明确要求不自动启动浏览器实测。本次以静态检查为主:`oxlint` + Vite 编译 + 逐文件核对"无裸写时长数字、无禁用属性、reduced-motion 覆盖完整"。
|
||||
|
||||
## 实现注意
|
||||
|
||||
- 严格沿用存量风格:SCSS 独立文件用 `@use './index.scss'` 引入;注释用中文说明"为什么这样做",与现有代码一致
|
||||
- 除 Transition 包裹层外不改动组件 DOM 结构与既有 props / emits,保证向后兼容
|
||||
- popover 是 dropdown / tooltip / select 的基座,优先改造收益最大;select 是否完全复用 popover 需在实施时确认,若为独立实现在其内部补同样处理
|
||||
- 控件类动画不影响键盘操作与焦点顺序;浮层禁止在动画期间抢焦点
|
||||
- 不为动画而动画:无状态变化的组件(栅格、分割器等拖拽即时类)不强行加过渡
|
||||
|
||||
## 架构设计
|
||||
|
||||
本次改动是"在既有组件库上加一层动效契约",不引入新的架构模式:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A["tokens.css<br/>9 个动效 token + reduced-motion 归零"] --> B["浮层组件<br/>popover / modal / drawer"]
|
||||
A --> C["表单控件<br/>button / input / switch / checkbox / radio"]
|
||||
A --> D["导航与容器<br/>tabs / segmented / form-group / tree / scrollbar"]
|
||||
A --> E["反馈与加载<br/>message / notification / spin"]
|
||||
B --> B1["dropdown / tooltip / select<br/>经 popover 继承"]
|
||||
F["业务页面手写旋转动画"] --> E
|
||||
```
|
||||
|
||||
- **契约层**:`src/theme/tokens.css` 提供唯一动效参数来源
|
||||
- **行为层**:浮层组件内置过渡状态,dropdown / tooltip / select 通过基座继承
|
||||
- **表现层**:各组件 SCSS 引用 token,声明自己用哪几档
|
||||
|
||||
## 目录结构
|
||||
|
||||
本次改动为既有项目补动效,涉及文件如下(未列出的文件不动):
|
||||
|
||||
```
|
||||
fms-vue/
|
||||
├── src/
|
||||
│ ├── theme/
|
||||
│ │ └── tokens.css # [MODIFY] 追加 9 个动效 token(3 档时长 / 3 档缓动 / 2 档位移)到 :root,
|
||||
│ │ # 并追加 @media (prefers-reduced-motion: reduce) 块把三档时长归零。
|
||||
│ │ # 深色 .dark 块不需要覆盖(动效参数与色彩无关)。
|
||||
│ ├── components/ui/
|
||||
│ │ ├── popover/
|
||||
│ │ │ ├── popover.vue # [MODIFY] 浮层基座:把 v-if 内容包进 Transition,补进退场。
|
||||
│ │ │ │ # 进场 base + ease-out,退场 base + ease-in;定位逻辑不动。
|
||||
│ │ │ └── index.scss # [MODIFY] 补 enter/leave 过渡类(opacity + 4px 位移)
|
||||
│ │ ├── modal/
|
||||
│ │ │ ├── modal.vue # [MODIFY] 遮罩与内容分层过渡,进场遮罩先铺、内容延迟 20ms 落
|
||||
│ │ │ └── index.scss # [MODIFY] 进场 slow + ease-out(scale 0.96→1),退场 base + ease-in;
|
||||
│ │ │ # 遮罩进场 base + ease-out、退场 base + ease-in
|
||||
│ │ ├── drawer/
|
||||
│ │ │ ├── drawer.vue # [MODIFY] 补进场/退场过渡,复用与 modal 相同的分层写法
|
||||
│ │ │ └── index.scss # [MODIFY] 用组件级变量承载 240ms 进 / 200ms 出(不裸写),
|
||||
│ │ │ # 并在组件级加 reduced-motion 覆盖
|
||||
│ │ ├── select/
|
||||
│ │ │ ├── select.vue # [MODIFY] 确认是否复用 popover;若独立实现则补下拉进出场
|
||||
│ │ │ └── index.scss # [MODIFY] 同上,补选项面板过渡类
|
||||
│ │ ├── dropdown/index.scss # [MODIFY] 下拉项 hover 底色过渡(菜单容器过渡由 popover 提供)
|
||||
│ │ ├── tooltip/index.scss # [MODIFY] 用更快档位(100ms),高频鼠标扫过场景避免拖残影
|
||||
│ │ ├── button/index.scss # [MODIFY] 补 background-color / border-color / box-shadow(base + standard)
|
||||
│ │ │ # 与 transform(fast + standard),补 :active 的 scale 0.97
|
||||
│ │ ├── input/
|
||||
│ │ │ ├── input.vue # [MODIFY] 核对聚焦/校验态切换所依赖的 class 是否齐备
|
||||
│ │ │ └── index.scss # [MODIFY] 补边框色与阴影的聚焦过渡(base + standard)
|
||||
│ │ ├── switch/index.scss # [MODIFY] 滑块 translateX 过渡(fast + standard),轨道颜色同步
|
||||
│ │ ├── checkbox/index.scss # [MODIFY] 勾选态颜色/描边过渡
|
||||
│ │ ├── radio/index.scss # [MODIFY] 选中态过渡,与 checkbox 同参数
|
||||
│ │ ├── segmented/index.scss # [MODIFY] 滑块位移过渡(base + standard),与 tabs 指示器同类
|
||||
│ │ ├── tabs/
|
||||
│ │ │ ├── tabs.vue # [MODIFY] 若已有滑动指示器元素,补其位移态;无则仅补 pane 淡入
|
||||
│ │ │ └── index.scss # [MODIFY] 指示器位移过渡(base + standard)
|
||||
│ │ ├── form/
|
||||
│ │ │ ├── FormGroup.vue # [MODIFY] 折叠容器改为 grid 1fr↔0fr 过渡(分组图标同步旋转)
|
||||
│ │ │ └── index.scss # [MODIFY] 折叠过渡类(base + standard),禁止动 height
|
||||
│ │ ├── tree/
|
||||
│ │ │ ├── tree-node.vue # [MODIFY] 子节点容器套用 grid 1fr↔0fr 手法
|
||||
│ │ │ └── index.scss # [MODIFY] 展开收起过渡 + 展开箭头旋转(fast + standard)
|
||||
│ │ ├── message/
|
||||
│ │ │ ├── message-container.vue # [MODIFY] 列表渲染改为 TransitionGroup,使项进出有过渡
|
||||
│ │ │ └── index.scss # [MODIFY] 进场 base + ease-out、退场 base + ease-in
|
||||
│ │ ├── notification/
|
||||
│ │ │ ├── notification-container.vue # [MODIFY] 同 message 的 TransitionGroup 处理
|
||||
│ │ │ └── index.scss # [MODIFY] 同 message 参数
|
||||
│ │ ├── scrollbar/scrollbar.vue # [MODIFY] leave 模式下滚动条淡出(opacity 过渡,样式为组件内联)
|
||||
│ │ ├── spin/index.scss # [MODIFY] 补 @keyframes 旋转动画到 .spin-indicator,
|
||||
│ │ │ # 时长用组件级变量承载以便 reduced-motion 归零
|
||||
│ │ ├── tag/index.scss # [MODIFY] 关闭/删除类交互的轻量过渡(若存在)
|
||||
│ │ └── pagination/index.scss # [MODIFY] 页码切换态过渡(若存在)
|
||||
│ └── views/module/
|
||||
│ ├── module-management/index.vue # [MODIFY] 移除手写 .mm-config-state__spinner 与 @keyframes mm-config-spin,
|
||||
│ │ # 改用 Spin 组件,复用统一旋转参数
|
||||
│ └── menu-management/index.vue # [MODIFY] 同上,移除 @keyframes mm-spin
|
||||
```
|
||||
|
||||
## 关键代码结构
|
||||
|
||||
动效契约(`src/theme/tokens.css`)是全部组件依赖的唯一来源,必须精确定义:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 时长:跟手操作 / 同层小元素 / 占满屏幕 三档封顶 */
|
||||
--fms-duration-fast: 100ms;
|
||||
--fms-duration-base: 150ms;
|
||||
--fms-duration-slow: 200ms;
|
||||
|
||||
/* 缓动:进场用 out、退场用 in、纯颜色变化用 standard */
|
||||
--fms-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--fms-ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||
--fms-ease-in: cubic-bezier(0.4, 0, 1, 1);
|
||||
|
||||
/* 位移幅度:下拉 / 提示用 sm,Modal 用 md */
|
||||
--fms-offset-sm: 4px;
|
||||
--fms-offset-md: 8px;
|
||||
}
|
||||
|
||||
/* 无障碍:时长归零保留状态变化,去掉过渡过程 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
:root {
|
||||
--fms-duration-fast: 0ms;
|
||||
--fms-duration-base: 0ms;
|
||||
--fms-duration-slow: 0ms;
|
||||
}
|
||||
}
|
||||
|
||||
/* 组件级变量示例(Drawer):不进全局 token,但同样可被上面的覆盖命中 */
|
||||
.fms-drawer {
|
||||
--fms-drawer-duration-in: 240ms;
|
||||
--fms-drawer-duration-out: 200ms;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.fms-drawer {
|
||||
--fms-drawer-duration-in: 0ms;
|
||||
--fms-drawer-duration-out: 0ms;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Agent Extensions
|
||||
|
||||
### SubAgent
|
||||
|
||||
- **code-explorer**
|
||||
- Purpose: 在动工前一次性盘点 `src/components/ui` 下 26 个组件的样式入口、可动画属性与浮层显隐结构,确认"哪些组件走 Transition、哪些纯 CSS 补课、select 是否复用 popover",避免逐文件试探。
|
||||
- Expected outcome: 输出一份带路径的改动清单与每类组件的处理方式,作为后续实现的任务依据。
|
||||
|
||||
### Skill
|
||||
|
||||
- **ui-ux-pro-max**
|
||||
- Purpose: 核对本次动效参数与无障碍设计的合理性(时长是否落在后台高频场景的舒适区间、缓动方向是否正确、reduced-motion 与焦点处理是否到位),并检查是否有遗漏的交互态。
|
||||
- Expected outcome: 一份针对动效参数与无障碍的核对结论,确认无系统性偏差后再收尾。
|
||||
Reference in new issue
Block a user