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

7.8 KiB
Raw Blame History

name, overview, design, todos
name overview design todos
spin-component 实现 Spin 加载组件(形态一:独立指示器;形态二:包裹内容+遮罩,且不破坏内部布局),配套 demo 与注册。
architecture styleKeywords fontSystem colorSystem
framework
vue
加载反馈
轻量遮罩
居中指示器
fontFamily heading subheading body
system-ui
size weight
14px 500
size weight
12px 400
size weight
12px 400
primary background text functional
#0f172b
transparent
#86909c
#0f172b
id content status
spin-tokens 用 [skill:antdv-next] 确认形态二结构,tokens.css 新增 --fms-spin-size completed
id content status dependencies
spin-style 创建 spin/index.scss(指示器旋转动画 + 形态二容器/轻量遮罩/绝对居中指示器 + reduced-motion) completed
spin-tokens
id content status dependencies
spin-component 创建 spin/spin.vue(spinning/delay/description + #indicator/#description/默认插槽 + delay 定时器清理 + ARIA) completed
spin-style
id content status dependencies
spin-demo 创建 demo/spin.vue 并注册到 App.vue「反馈」分类与 demoMap completed
spin-component
id content status dependencies
spin-verify 用 [skill:playwright-cli] 实测形态二内部布局零偏移 + oxlint 静态检查并汇报 completed
spin-demo

用户需求

实现 Spin 加载中组件,支持两种形态:

  • 形态一(无子元素):独立加载指示器,可带文案,如 <Spin spinning description="加载中" />
  • 形态二(有子元素):包裹内容并覆盖半透明遮罩,如 <Spin :spinning="loading"><表格/></Spin>

重点要求:形态二必须做好——不能因为添加 Spin 导致内部元素位置/尺寸错乱。遮罩和指示器应绝对定位脱离文档流,内容容器只加 position: relative,加载时内容降透明并禁用交互,布局保持不变。

核心功能

  • spinning(Boolean,默认 true):是否加载中
  • delay(Number,默认 0):延迟显示(毫秒),防短请求闪烁,卸载时清理定时器
  • description(String):加载文案,显示在指示器下方
  • 插槽:#indicator 自定义指示器(默认 LoaderCircle 旋转)、#description 自定义文案、默认插槽(传入则启用形态二)
  • 形态二:内容容器加载时 opacity 降低 + pointer-events: none 禁交互 + 轻量遮罩;指示器与文案绝对居中
  • ARIA:容器 aria-busy、指示器 role="status" + aria-label="加载中"
  • 不提供 size 枚举(遵循开发规范 7.1)

技术栈

  • Vue 3 <script setup> + scoped SCSS(@use './index.scss')
  • 设计 token:src/theme/tokens.css 新增 --fms-spin-size
  • 图标:@lucide/vue 按需导入 LoaderCircle(与 Select 加载态一致)
  • 动画:@keyframes spin 旋转 800ms linear infinite(对齐 Button 的 button-spin),prefers-reduced-motion 停动画

实现方案

形态二布局防错乱(核心)

参考 antdv-next Spin 结构,采用"内容容器 + 绝对定位覆盖层"方案,所有覆盖元素脱离文档流:

<div class="spin">                    ← 仅形态二;position: relative(不改变内部布局)
  <div class="spin-container">        ← 包裹 children,只加 position: relative
    <slot />                          ← 内部元素位置/尺寸完全不受影响
  </div>
  <div v-if="spinning" class="spin-mask" />      ← 绝对定位 inset:0,轻量半透明遮罩
  <div v-if="spinning" class="spin-section">     ← 绝对定位 50%/50% translate(-50%,-50%) 居中
    <LoaderCircle class="spin-indicator" />      ← 或 #indicator 插槽
    <div class="spin-description">加载中</div>   ← 或 #description 插槽
  </div>
</div>

关键决策:

  • 遮罩与指示器全部 position: absolute,不占文档流 → 内部元素位置不会偏移
  • .spin-container 不加任何尺寸/布局属性,只做 position: relative 承载遮罩层 → 内部布局零影响
  • 加载时容器 opacity: 0.5; user-select: none; pointer-events: none(对齐 antdv),遮罩 z-index 高于容器、低于指示器
  • 形态一(无默认插槽)渲染为 inline-flex 指示器区,不产生包裹层

Props 与状态

  • spinning 默认 true;delay 用 setTimeout 延迟内部显示,watch + onBeforeUnmount 清理定时器(规范 4.3)
  • 内部 active ref 为实际显示状态(delay 生效后),模板基于 active 渲染

样式与 token

  • --fms-spin-size: 20px 加入 tokens.css :root(尺寸不随主题)
  • 指示器颜色 var(--fms-primary);遮罩用 color-mix(in srgb, var(--fms-text) 6%, transparent) 派生轻量色(比 Modal 的 --fms-mask-bg 0.45 轻,不压暗内容可读性)
  • 文案用 --fms-text-secondary 12px

性能与可靠性

  • 无高频事件、无全局监听;动画仅 transform(低成本),reduced-motion 关闭
  • delay 定时器在卸载与 spinning 翻转时清理,避免内存泄漏
  • 遮罩层拦截点击(pointer-events 由容器 none、遮罩 auto 恢复),防止加载中误操作

目录结构

fms-vue/src/
├── theme/
│   └── tokens.css                      # [MODIFY] 新增 --fms-spin-size: 20px
├── components/ui/
│   ├── spin/
│   │   ├── spin.vue                    # [NEW] 主组件:spinning/delay/description + #indicator/#description/默认插槽;形态一/二分支;delay 定时器清理;ARIA
│   │   └── index.scss                  # [NEW] 指示器旋转动画、形态二容器/遮罩/居中指示器、reduced-motion
│   └── demo/
│       └── spin.vue                    # [NEW] 演示:基础指示器/带文案/包裹内容遮罩(重点验证内部布局不偏移)/delay 防闪烁/自定义 indicator
└── App.vue                             # [MODIFY] 「反馈」分类注册 Spin + demoMap

关键代码结构

// spin.vue props
defineProps({
  spinning: { type: Boolean, default: true },
  delay: { type: Number, default: 0 },
  description: { type: String, default: '' },
})
// 内部 active 状态:delay 后置 true;watch(spinning/delay) + onBeforeUnmount 清理 setTimeout
// 模板:hasDefaultSlot ? 形态二(container+mask+section 绝对定位) : 形态一(inline-flex section)

设计说明

Spin 为反馈类加载组件,视觉遵循项目现有加载态语言(Button/Select 的旋转 Loader 图标):

  • 指示器:LoaderCircle 图标 20px(--fms-spin-size),主色 --fms-primary,800ms 匀速旋转;文案 12px 次级灰 --fms-text-secondary,指示器与文案垂直排列、居中
  • 形态一:inline-flex 水平/垂直居中的指示器区,可独立嵌入按钮、卡片等任意容器
  • 形态二:内容保持原布局不变,加载时内容降透明(opacity 0.5)并禁用交互;轻量遮罩(文字色 6% 混合)覆盖内容区,指示器+文案绝对居中于遮罩之上,形成"内容不可操作、加载居中"的视觉反馈
  • 支持 prefers-reduced-motion:动画关闭,仅静态指示器

Agent Extensions

Skill

  • antdv-next
  • Purpose: 确认 Spin 形态二(包裹内容 + 遮罩)的 DOM 结构与交互细节(延迟显示、遮罩层级、指示器居中),确保方案与参考库一致
  • Expected outcome: 形态二实现不破坏内部布局的准确依据
  • playwright-cli
  • Purpose: 在 dev server(5082)上实测 Spin demo,重点验证形态二包裹内容后内部元素位置/尺寸与未包裹时一致
  • Expected outcome: 实测通过:指示器旋转、遮罩覆盖、内部布局零偏移、delay 生效、自定义 indicator 正常