e0a5f57793
Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as a monorepo subdirectory under desktop/. Source only — node_modules, dist, Cargo target and the upstream .git history are excluded. desktop/ keeps its own independent Cargo workspace (desktop/src-tauri with crates/*); it is not part of the backend boosapi crate, so a `cargo check` at the metapi root still compiles only the backend. Build the desktop app from desktop/src-tauri per desktop/README.md. Note: app icons are still placeholder assets pending the final BoosAPI logo. The updater signing keys live outside the repo and are not included.
522 lines
18 KiB
Markdown
522 lines
18 KiB
Markdown
# 样式架构方案
|
||
|
||
> 核心决策:**带默认样式 + CSS 变量全量可定制**,非 headless。
|
||
> shadcn 兼容:继承宿主变量,不覆盖;无宿主时回退到内置默认值。
|
||
|
||
---
|
||
|
||
## 设计原则
|
||
|
||
1. **开箱即用** — 引入即有完整合理的 Markdown 渲染样式
|
||
2. **变量换肤** — 用户只需覆盖 CSS 变量即可定制全部视觉表现
|
||
3. **零硬编码** — 组件内不允许出现硬编码的色值、间距等魔法数字
|
||
4. **Light/Dark 内建** — 两套主题开关切换,暗色模式完整覆盖所有组件
|
||
5. **不污染宿主** — 所有样式限定在 `.markstream-vue` 作用域内
|
||
6. **不覆盖宿主** — 不重定义 shadcn 同名变量,通过继承 + 回退兼容
|
||
|
||
---
|
||
|
||
## 变量不冲突策略
|
||
|
||
### 问题
|
||
|
||
如果我们在 `.markstream-vue` 上直接定义 `--background`,会覆盖宿主从 `:root` 继承下来的值:
|
||
|
||
```
|
||
宿主 :root { --background: 210 20% 98% } ← 用户自定义的暖灰
|
||
我们 .markstream-vue { --background: 0 0% 100% } ← 覆盖成了纯白 ❌
|
||
```
|
||
|
||
### 解决方案:Bridge Tokens
|
||
|
||
引入 `--ms-*` 前缀的桥接层,用 `var()` 继承宿主变量,未定义时回退到默认值:
|
||
|
||
```css
|
||
.markstream-vue {
|
||
--ms-background: var(--background, 0 0% 100%);
|
||
/* ↑ 宿主有则用宿主的 ↑ 没有则用回退值 */
|
||
}
|
||
```
|
||
|
||
**shadcn 宿主**:
|
||
|
||
```
|
||
:root { --background: 210 20% 98% } ← 宿主定义
|
||
└─ .markstream-vue 继承 --background = 210 20% 98%
|
||
└─ --ms-background: var(--background, 0 0% 100%)
|
||
= 210 20% 98% ✅ 宿主主题穿透生效
|
||
```
|
||
|
||
**独立使用(无 shadcn)**:
|
||
|
||
```
|
||
:root 未定义 --background
|
||
└─ .markstream-vue 未继承到 --background
|
||
└─ --ms-background: var(--background, 0 0% 100%)
|
||
= 0 0% 100% ✅ 回退值生效,开箱即用
|
||
```
|
||
|
||
---
|
||
|
||
## 分层架构
|
||
|
||
```
|
||
┌──────────────────────────────────────────────┐
|
||
│ Layer 4: Dark Theme │ 仅覆盖 Layer 1 bridge + ext 的回退值
|
||
│ .dark .markstream-vue { --ms-bg: var(...) } │
|
||
├──────────────────────────────────────────────┤
|
||
│ Layer 3: Component Styles │ 引用 Layer 2 变量,不允许硬编码
|
||
│ .heading { color: var(--blockquote-fg) } │
|
||
├──────────────────────────────────────────────┤
|
||
│ Layer 2: Semantic Tokens │ 组件级变量,引用 Layer 1
|
||
│ --code-bg: hsl(var(--ms-background)); │
|
||
├──────────────────────────────────────────────┤
|
||
│ Layer 1: Base Tokens │
|
||
│ ├─ Bridge (--ms-*): 继承 shadcn + 回退 │
|
||
│ └─ Extension (--*): 本项目独有 │
|
||
└──────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Layer 1: Base Tokens
|
||
|
||
### 1.1 Bridge Tokens(`--ms-*`,继承 shadcn 同名变量)
|
||
|
||
> 内部桥接用。用户定制时直接覆盖 shadcn 变量名(如 `--background`)或覆盖 semantic token 即可。
|
||
|
||
#### Light(默认)
|
||
|
||
```css
|
||
.markstream-vue {
|
||
|
||
/* ── 背景 / 前景 ── */
|
||
--ms-background: var(--background, 0 0% 100%);
|
||
--ms-foreground: var(--foreground, 222.2 84% 4.9%);
|
||
|
||
/* ── 弱化 ── */
|
||
--ms-muted: var(--muted, 210 40% 96.1%);
|
||
--ms-muted-foreground: var(--muted-foreground, 215.4 16.3% 46.9%);
|
||
|
||
/* ── 次要 ── */
|
||
--ms-secondary: var(--secondary, 210 40% 96.1%);
|
||
--ms-secondary-foreground: var(--secondary-foreground, 222.2 47.4% 11.2%);
|
||
|
||
/* ── 强调 ── */
|
||
--ms-accent: var(--accent, 210 40% 96.1%);
|
||
--ms-accent-foreground: var(--accent-foreground, 222.2 47.4% 11.2%);
|
||
|
||
/* ── 主要操作 ── */
|
||
--ms-primary: var(--primary, 222.2 47.4% 11.2%);
|
||
--ms-primary-foreground: var(--primary-foreground, 210 40% 98%);
|
||
|
||
/* ── 危险 ── */
|
||
--ms-destructive: var(--destructive, 0 84.2% 60.2%);
|
||
--ms-destructive-foreground: var(--destructive-foreground, 210 40% 98%);
|
||
|
||
/* ── 边框 / Focus ── */
|
||
--ms-border: var(--border, 214.3 31.8% 91.4%);
|
||
--ms-ring: var(--ring, 222.2 84% 4.9%);
|
||
|
||
/* ── 弹出层 ── */
|
||
--ms-popover: var(--popover, 0 0% 100%);
|
||
--ms-popover-foreground: var(--popover-foreground, 222.2 84% 4.9%);
|
||
|
||
/* ── 圆角 ── */
|
||
--ms-radius: var(--radius, 0.5rem);
|
||
}
|
||
```
|
||
|
||
#### Dark
|
||
|
||
```css
|
||
.dark .markstream-vue,
|
||
.markstream-vue.dark {
|
||
|
||
--ms-background: var(--background, 222.2 84% 4.9%);
|
||
--ms-foreground: var(--foreground, 210 40% 98%);
|
||
|
||
--ms-muted: var(--muted, 217.2 32.6% 17.5%);
|
||
--ms-muted-foreground: var(--muted-foreground, 215 20.2% 65.1%);
|
||
|
||
--ms-secondary: var(--secondary, 217.2 32.6% 17.5%);
|
||
--ms-secondary-foreground: var(--secondary-foreground, 210 40% 98%);
|
||
|
||
--ms-accent: var(--accent, 217.2 32.6% 17.5%);
|
||
--ms-accent-foreground: var(--accent-foreground, 210 40% 98%);
|
||
|
||
--ms-primary: var(--primary, 210 40% 98%);
|
||
--ms-primary-foreground: var(--primary-foreground, 222.2 47.4% 11.2%);
|
||
|
||
--ms-destructive: var(--destructive, 0 62.8% 30.6%);
|
||
--ms-destructive-foreground: var(--destructive-foreground, 210 40% 98%);
|
||
|
||
--ms-border: var(--border, 217.2 32.6% 17.5%);
|
||
--ms-ring: var(--ring, 212.7 26.8% 83.9%);
|
||
|
||
--ms-popover: var(--popover, 222.2 84% 4.9%);
|
||
--ms-popover-foreground: var(--popover-foreground, 210 40% 98%);
|
||
}
|
||
```
|
||
|
||
**工作原理**:
|
||
|
||
- shadcn 宿主有 `.dark { --background: X }` → `var(--background, fallback)` 取 X → 宿主暗色穿透
|
||
- 独立使用,无 `--background` → 取 fallback 中的暗色值 → 内置暗色生效
|
||
|
||
### 1.2 Extension Tokens(本项目独有,无冲突风险)
|
||
|
||
> 这些变量名 shadcn 不存在,直接定义即可。
|
||
|
||
#### Light
|
||
|
||
```css
|
||
.markstream-vue {
|
||
|
||
/* ── 语义色 ── */
|
||
--ms-info: 217 91% 60%; /* 信息/备注 (#448aff) */
|
||
--ms-info-foreground: 210 40% 98%;
|
||
|
||
--ms-success: 168 100% 37.5%; /* 成功/提示 (#00bfa5) */
|
||
--ms-success-foreground: 210 40% 98%;
|
||
|
||
--ms-warning: 34 100% 50%; /* 警告 (#ff9100) */
|
||
--ms-warning-foreground: 0 0% 100%;
|
||
|
||
/* ── Diff ── */
|
||
--ms-diff-added: 174 60% 51%; /* 新增 (#14b8a6) */
|
||
--ms-diff-removed: 350 100% 60%; /* 删除 (#ff3658) */
|
||
|
||
/* ── 其他 ── */
|
||
--ms-highlight: 54 100% 62%; /* 文本高亮 */
|
||
--ms-highlight-foreground: 0 0% 0%;
|
||
|
||
--ms-link: 212 100% 42%; /* 链接色 (#0366d6) */
|
||
|
||
/* ── 排版 ── */
|
||
--ms-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||
--ms-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
||
}
|
||
```
|
||
|
||
#### Dark
|
||
|
||
```css
|
||
.dark .markstream-vue,
|
||
.markstream-vue.dark {
|
||
|
||
--ms-diff-added: 174 72% 70%; /* 暗色更亮 (#5eead4) */
|
||
--ms-diff-removed: 0 92% 82%; /* 暗色更柔 (#fda4af) */
|
||
|
||
--ms-highlight: 54 80% 42%; /* 暗色降低亮度 */
|
||
--ms-highlight-foreground: 0 0% 100%;
|
||
|
||
--ms-link: 213 94% 68%; /* 暗色更亮链接 */
|
||
|
||
/* info / success / warning 在暗色下保持不变,仍然醒目 */
|
||
}
|
||
```
|
||
|
||
### 1.3 圆角推算规则
|
||
|
||
```
|
||
sm = calc(var(--ms-radius) - 2px) → 6px
|
||
base = var(--ms-radius) → 8px (0.5rem)
|
||
md = calc(var(--ms-radius) + 2px) → 10px
|
||
lg = calc(var(--ms-radius) + 4px) → 12px
|
||
full = 9999px
|
||
```
|
||
|
||
---
|
||
|
||
## Layer 2: Semantic Tokens
|
||
|
||
全部引用 Layer 1 的 `--ms-*` 变量。Dark 模式下 Layer 1 变化 → Layer 2 自动跟随,无需重复定义。
|
||
|
||
```css
|
||
.markstream-vue {
|
||
|
||
/* ── 行内代码 ── */
|
||
--inline-code-bg: hsl(var(--ms-secondary));
|
||
--inline-code-fg: hsl(var(--ms-foreground));
|
||
--inline-code-border: hsl(var(--ms-border) / 0.9);
|
||
|
||
/* ── 代码块 ── */
|
||
--code-bg: hsl(var(--ms-background));
|
||
--code-fg: hsl(var(--ms-foreground));
|
||
--code-border: hsl(var(--ms-border));
|
||
--code-header-bg: hsl(var(--ms-muted));
|
||
--code-selection-bg: hsl(var(--ms-accent) / 0.3);
|
||
--code-line-number: hsl(var(--ms-muted-foreground));
|
||
|
||
/* ── 代码块操作栏 ── */
|
||
--code-action-fg: hsl(var(--ms-muted-foreground));
|
||
--code-action-hover-bg: hsl(var(--ms-accent));
|
||
--code-action-hover-fg: hsl(var(--ms-accent-foreground));
|
||
--code-action-active-bg: hsl(var(--ms-primary));
|
||
--code-action-active-fg: hsl(var(--ms-primary-foreground));
|
||
|
||
/* ── Diff ── */
|
||
--diff-added-fg: hsl(var(--ms-diff-added));
|
||
--diff-removed-fg: hsl(var(--ms-diff-removed));
|
||
--diff-added-bg: hsl(var(--ms-diff-added) / 0.1);
|
||
--diff-added-inline-bg: hsl(var(--ms-diff-added) / 0.2);
|
||
--diff-removed-bg: hsl(var(--ms-diff-removed) / 0.1);
|
||
--diff-removed-inline-bg: hsl(var(--ms-diff-removed) / 0.2);
|
||
|
||
/* ── 引用块 ── */
|
||
--blockquote-border: hsl(var(--ms-border));
|
||
--blockquote-fg: hsl(var(--ms-foreground));
|
||
|
||
/* ── 提示框 (Admonition) ── */
|
||
--admonition-bg: hsl(var(--ms-muted));
|
||
--admonition-border: hsl(var(--ms-border));
|
||
--admonition-fg: hsl(var(--ms-foreground));
|
||
--admonition-muted: hsl(var(--ms-muted-foreground));
|
||
--admonition-header-bg: hsl(var(--ms-muted) / 0.5);
|
||
--admonition-note: hsl(var(--ms-info));
|
||
--admonition-tip: hsl(var(--ms-success));
|
||
--admonition-warning: hsl(var(--ms-warning));
|
||
--admonition-danger: hsl(var(--ms-destructive));
|
||
|
||
/* ── 表格 ── */
|
||
--table-border: hsl(var(--ms-border));
|
||
--table-header-bg: hsl(var(--ms-muted));
|
||
--table-header-fg: hsl(var(--ms-foreground));
|
||
--table-row-hover-bg: hsl(var(--ms-accent) / 0.5);
|
||
|
||
/* ── 链接 ── */
|
||
--link-color: hsl(var(--ms-link));
|
||
|
||
/* ── 列表 ── */
|
||
--list-marker: hsl(var(--ms-border));
|
||
--list-counter-marker: hsl(var(--ms-muted-foreground));
|
||
|
||
/* ── 分割线 ── */
|
||
--hr-border: hsl(var(--ms-border));
|
||
|
||
/* ── 高亮 ── */
|
||
--highlight-bg: hsl(var(--ms-highlight));
|
||
--highlight-fg: hsl(var(--ms-highlight-foreground));
|
||
|
||
/* ── 脚注 ── */
|
||
--footnote-border: hsl(var(--ms-border));
|
||
|
||
/* ── Tooltip ── */
|
||
--tooltip-bg: hsl(var(--ms-popover));
|
||
--tooltip-fg: hsl(var(--ms-popover-foreground));
|
||
--tooltip-border: hsl(var(--ms-border));
|
||
|
||
/* ── Modal ── */
|
||
--modal-overlay: hsl(0 0% 0% / 0.7);
|
||
--modal-bg: hsl(var(--ms-popover));
|
||
--modal-fg: hsl(var(--ms-popover-foreground));
|
||
|
||
/* ── 图表容器 (Mermaid / D2 / Infographic) ── */
|
||
--diagram-bg: hsl(var(--ms-muted));
|
||
--diagram-border: hsl(var(--ms-border));
|
||
--diagram-header-bg: hsl(var(--ms-muted));
|
||
|
||
/* ── 数学公式 ── */
|
||
--math-fg: hsl(var(--ms-foreground));
|
||
|
||
/* ── 加载态 ── */
|
||
--loading-spinner: hsl(var(--ms-muted-foreground));
|
||
--loading-shimmer: hsl(var(--ms-muted) / 0.5);
|
||
|
||
/* ── 图片 ── */
|
||
--image-placeholder-bg: hsl(var(--ms-muted));
|
||
|
||
/* ── Focus ring ── */
|
||
--focus-ring: hsl(var(--ms-ring));
|
||
}
|
||
```
|
||
|
||
**Dark 模式下无需重复定义 Layer 2** — 因为 Layer 1 的 `--ms-*` 值变了,这里自动跟随。
|
||
|
||
---
|
||
|
||
## Layer 3: Component Styles
|
||
|
||
组件中只引用 Layer 2 语义变量:
|
||
|
||
```css
|
||
/* ❌ 之前 */
|
||
.blockquote { border-left: 0.25rem solid #e2e8f0; }
|
||
.admonition-note { border-left-color: #448aff; }
|
||
.highlight-node { background-color: #ffff00; }
|
||
|
||
/* ✅ 之后 */
|
||
.blockquote { border-left: 0.25rem solid var(--blockquote-border); }
|
||
.admonition-note { border-left-color: var(--admonition-note); }
|
||
.highlight-node { background: var(--highlight-bg); }
|
||
```
|
||
|
||
---
|
||
|
||
## 暗色模式统一方案
|
||
|
||
**去掉组件级 `.is-dark` CSS**,统一为:
|
||
|
||
```
|
||
之前:isDark prop → 每个组件 .is-dark → 30+ 组件各自覆盖 CSS 变量
|
||
之后:.dark 类 → Layer 1 bridge 回退值切换 → Layer 2 自动跟随 → 全部组件适配
|
||
```
|
||
|
||
**保留 `isDark` prop** 仅用于 JS 逻辑(不用于 CSS):
|
||
- Monaco Editor 主题名切换
|
||
- Mermaid 主题名切换
|
||
- `prefers-color-scheme` 检测兜底
|
||
|
||
### CodeBlockNode `.is-dark` 例外
|
||
|
||
CodeBlockNode 保留 `.is-dark` CSS 块。此处 `.is-dark` 的语义是**"编辑器表面为暗色"**
|
||
(由 Monaco 主题检测 `resolvedSurfaceIsDark` 决定),与页面级 `.dark` 无关。
|
||
|
||
- 一个亮色页面上可以出现暗色主题的代码块
|
||
- `.is-dark` 块包含 ~15 个 diff 视图的阴影/透明度/渐变差异值
|
||
- 这些值已全部引用 `--ms-*` 语义令牌,只是 opacity 系数不同
|
||
|
||
### Admonition header-bg 例外
|
||
|
||
提示框标题背景需要在 dark 下使用更高透明度(6% → 12%),因此
|
||
`--admonition-*-header-bg` 在 `.dark` 区块中重新定义。这是 Layer 2 中
|
||
唯一需要 dark 覆盖的语义令牌。
|
||
|
||
---
|
||
|
||
## shadcn 兼容矩阵
|
||
|
||
| 变量来源 | 我们的处理 | 用户如何定制 |
|
||
|---|---|---|
|
||
| shadcn 同名(`--background` 等 14 个) | `--ms-*` 桥接继承 + 回退 | 在 `:root` 设置 shadcn 变量,自动穿透 |
|
||
| 本项目独有(`--ms-info` 等 8 个) | 直接定义在 `.markstream-vue` | 在 `.markstream-vue` 上覆盖 |
|
||
| 组件语义(`--code-bg` 等 ~35 个) | 引用 `--ms-*`,定义一次 | 在 `.markstream-vue` 上覆盖 |
|
||
|
||
### shadcn 未采用(本项目不需要)
|
||
|
||
| Token | 原因 |
|
||
|---|---|
|
||
| `--card` / `--card-foreground` | 无 card 组件 |
|
||
| `--input` | 无输入框 |
|
||
| `--sidebar-*` | 无侧边栏 |
|
||
| `--chart-*` | Mermaid 自带主题 |
|
||
|
||
### 本项目扩展(shadcn 没有)
|
||
|
||
| Token | 用途 |
|
||
|---|---|
|
||
| `--ms-info` / `--ms-success` / `--ms-warning` | Admonition 语义色 |
|
||
| `--ms-diff-added` / `--ms-diff-removed` | Diff 视图 |
|
||
| `--ms-highlight` / `--ms-highlight-foreground` | `==高亮==` 语法 |
|
||
| `--ms-link` | 链接色 |
|
||
|
||
---
|
||
|
||
## 当前控件清单
|
||
|
||
| 控件 | 出现组件 | 涉及 tokens |
|
||
|---|---|---|
|
||
| 操作按钮(复制/折叠/全屏/导出) | CodeBlock, Mermaid, D2, Infographic, Admonition | `--code-action-*`, `--focus-ring` |
|
||
| 字号调节按钮组 | CodeBlock, MarkdownCodeBlock | `--code-action-*` |
|
||
| 模式切换(Preview/Source) | CodeBlock, Mermaid, D2, Infographic | `--code-action-active-*` |
|
||
| 缩放控制 | Mermaid, Infographic | `--code-action-*` |
|
||
| Tooltip | 全局 | `--tooltip-*` |
|
||
| Modal | CodeBlock, Mermaid, Infographic | `--modal-*` |
|
||
| Checkbox(只读) | CheckboxNode | `--ms-foreground` |
|
||
| Spinner | Image, Table, MathBlock | `--loading-spinner` |
|
||
| Shimmer 骨架屏 | CodeBlock, Table | `--loading-shimmer` |
|
||
| 错误态 | ImageNode | `--ms-destructive` |
|
||
| 可点击引用 | FootnoteRef, ReferenceNode | `--link-color` |
|
||
|
||
---
|
||
|
||
## 用户定制示例
|
||
|
||
### 场景 1:shadcn 项目(零配置接入)
|
||
|
||
```js
|
||
// 只需引入样式,shadcn 变量自动穿透
|
||
import 'markstream-vue/dist/index.css'
|
||
```
|
||
|
||
宿主 `:root` 上的 `--background`、`--foreground` 等自动继承进 `.markstream-vue`。
|
||
|
||
如需补充 Markdown 扩展色:
|
||
|
||
```css
|
||
.markstream-vue {
|
||
--ms-info: 217 91% 60%;
|
||
--ms-success: 142 71% 45%;
|
||
--ms-link: 221 83% 53%;
|
||
}
|
||
```
|
||
|
||
### 场景 2:独立项目,换整体配色
|
||
|
||
```css
|
||
/* 方式 A:覆盖 bridge 识别的 shadcn 变量名 */
|
||
:root {
|
||
--background: 0 0% 98%;
|
||
--foreground: 240 10% 10%;
|
||
--border: 240 5% 85%;
|
||
}
|
||
|
||
/* 方式 B:直接覆盖 --ms-* bridge token */
|
||
.markstream-vue {
|
||
--ms-background: 0 0% 98%;
|
||
--ms-foreground: 240 10% 10%;
|
||
--ms-border: 240 5% 85%;
|
||
}
|
||
```
|
||
|
||
### 场景 3:只改代码块(覆盖 semantic token)
|
||
|
||
```css
|
||
.markstream-vue {
|
||
--code-bg: #1e1e2e;
|
||
--code-fg: #cdd6f4;
|
||
--code-border: transparent;
|
||
}
|
||
```
|
||
|
||
### 场景 4:只改提示框色彩
|
||
|
||
```css
|
||
.markstream-vue {
|
||
--ms-info: 221 83% 53%;
|
||
--ms-success: 142 71% 45%;
|
||
--ms-warning: 38 92% 50%;
|
||
--ms-destructive: 0 72% 51%;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 已知边界情况
|
||
|
||
**shadcn 宿主 + 仅对 markstream-vue 启用 dark**:
|
||
|
||
```html
|
||
<!-- 宿主全局 light,但 markstream-vue 想用 dark -->
|
||
<div class="markstream-vue dark">...</div>
|
||
```
|
||
|
||
此时宿主的 light `--background` 仍会继承进来,bridge 的 dark 回退值不会生效。
|
||
**解决**:shadcn 宿主应在文档级切换 `.dark`,或通过覆盖 semantic token 单独控制。
|
||
|
||
---
|
||
|
||
## 迁移策略
|
||
|
||
分步进行,每步可独立验证:
|
||
|
||
| 阶段 | 内容 | 影响范围 |
|
||
|---|---|---|
|
||
| 1 | 定义 Bridge Tokens + Extension Tokens(Layer 1) | 仅新增变量,不破坏现有 |
|
||
| 2 | 定义 Semantic Tokens(Layer 2),引用 `--ms-*` | 仅新增变量,不破坏现有 |
|
||
| 3 | 逐组件替换硬编码值为 Semantic Token 引用 | 逐组件改,可逐个验证 |
|
||
| 4 | 补全 Dark 的 Bridge Token 回退值 | 暗色模式完整 |
|
||
| 5 | 移除组件内 `.is-dark` CSS 覆盖 | 简化暗色逻辑 |
|
||
| 6 | 清理:删除废弃变量、统一动画变量 | 最终打磨 |
|