refactor: 完成全局CSS模块化治理
CSS quality / css-quality (push) Has been cancelled

This commit is contained in:
hectorzhao
2026-09-05 00:45:05 +08:00
parent 1a7a7245a6
commit 458ddd97df
31 changed files with 17265 additions and 9857 deletions
@@ -0,0 +1,457 @@
# `global.css` 分模块治理方案
## 1. 文档目的
本方案用于治理当前前端全局样式文件 `src/styles/global.css` 体积过大、页面样式归属不清、格式化容易产生大范围无关差异、跨页面覆盖难以追踪的问题。
本方案定义拆分策略、实施批次、验证方法和回退边界。2026-09-05 已按本文完成首次全量治理,实施结果和长期维护方式见 `css-modularization-result-20260905.md`;后续仍须遵守本文的所有权、门禁和验收规则。
本方案与以下规范共同生效:
- 根目录 `AGENTS.md`:定义整个仓库必须遵守的 CSS 所有权、禁止事项和验收底线。
- `docs/css-development-guidelines.md`:定义日常新增和修改 CSS 时的归属决策、命名、import、级联和审查规则。
- 本文档:只负责存量 `global.css` 的渐进拆分、验证和回退。
实施顺序必须是“强制规则 → 自动门禁 → 视觉基线 → 存量拆分”。不得先大规模移动 CSS,再补规则和门禁。
## 2. 当前事实基线
截至 2026-09-04,只读核验结果如下:
- `src/styles/global.css`:约 213,062 字节、9,846 行。
- 该文件最早创建于提交 `2f3c274``Initial CMPP frontend prototype`),创建时已经约 6,267 行、136KB,并非由一个很小的基础样式文件自然增长而来。
- 最近 30 天约有 30 个提交修改该文件,累计约增加 975 行、删除 274 行,是前端高频冲突文件。
- `src/main.tsx` 当前按顺序全局加载:
1. `tokens.css`
2. `reset.css`
3. `shell.css`
4. `global.css`
5. `admin.css`
6. `client.css`
7. `components.css`
- 2026-07-31 的提交 `ca4f591` 曾拆出 `reset.css``shell.css``admin.css``client.css`,但拆分没有持续完成。目前 `client.css` 仅 8 行,大量运营端、客户端和业务组件样式仍留在 `global.css`
- 当前已经存在少量页面级 CSS:短信通道、企业应用、安全检测、短信记录、短信任务进度、系统监控和客户端用户页面。这说明项目已具备页面就近管理样式的实现基础。
当前问题主要影响开发、审查和协作效率,不直接等同于浏览器运行性能问题。
## 3. 治理目标
### 3.1 主要目标
1.`global.css` 从业务样式总仓库降级为只减不增的临时兼容入口,最终清空并删除。
2. 每个页面或业务域的样式有唯一、可定位的归属文件。
3. 修改一个页面时,不再需要格式化、审查或合并近万行全局 CSS。
4. 保持现有页面视觉、交互、响应式和打印/导出效果不变。
5. 建立规则,防止新业务样式重新写回全局文件。
### 3.2 建议量化标准
- 第一阶段完成后:`global.css` 降至 6,000 行以内。
- 第二阶段完成后:`global.css` 降至 2,000 行以内。
- 最终阶段:`global.css` 清零并删除,或仅保留不超过 300 行、经过明确评审的全局兼容规则。
- 单个页面 CSS 建议不超过 800 行。
- 单个业务域 CSS 建议不超过 1,200 行;超过后继续按页面或组件拆分。
- 不允许业务页面通过无前缀标签选择器影响其他页面。
## 4. 不在本次治理中处理的内容
以下事项不能与 CSS 拆分同时进行:
- 页面重新设计、颜色调整、字号调整或布局优化。
- React 组件大规模重构。
- 后端 API、数据库、Redis、短信链路或权限逻辑修改。
- 将全部普通 CSS 一次性改造成 CSS Modules、Tailwind 或 CSS-in-JS。
- 删除看起来“可能无用”但没有运行时证据的选择器。
- 对整份历史 CSS 执行一次性 Prettier 重排并混入拆分提交。
拆分阶段的核心原则是“只改变样式存放位置,不改变最终计算样式”。
## 5. 目标目录结构
建议继续使用项目当前的普通 CSS 和页面前缀命名方式,不在第一轮引入新的样式技术栈。在通用门禁和所有硬编码路径完成适配前,保留 `global.css` 文件名,行为上将其视为 legacy 文件;不得为了目录整齐过早重命名。
```text
src/
├─ styles/
│ ├─ tokens.css # 设计变量,仅允许 CSS 自定义属性
│ ├─ reset.css # 标签重置和浏览器一致性
│ ├─ shell.css # AppShell、侧栏、顶栏、导航、页面容器
│ ├─ utilities.css # 少量明确、稳定的工具类
│ ├─ components/
│ │ ├─ buttons.css # 通用 UI Button
│ │ ├─ forms.css # Input、Select、Textarea、校验提示
│ │ ├─ modal.css # Modal、Drawer、Popover
│ │ ├─ table.css # 通用表格、分页、空状态
│ │ ├─ cards.css # 通用卡片与统计卡
│ │ └─ feedback.css # Tag、Alert、Toast、Loading
│ └─ global.css # 拆分过程中的临时剩余区,只减不增,治理后删除
├─ apps/
│ ├─ admin/
│ │ ├─ reporting/ # 报备资料、批次、字段映射、状态记录
│ │ ├─ enterprise-signatures/ # 企业签名、引流资料、退订/质量状态
│ │ ├─ channels/ # 通道、通道组、通道详情
│ │ ├─ sms-records/ # 短信记录与详情
│ │ ├─ sms-task-progress/ # 批次任务与进度
│ │ ├─ monitoring/ # 系统监控、服务监控、日志
│ │ ├─ risk/ # 风控、黑名单、敏感词、安全检测
│ │ └─ billing/ # 充值、对账、利润与账务页面
│ └─ client/
│ ├─ dashboard/
│ ├─ send/
│ ├─ signatures/
│ ├─ templates/
│ ├─ billing/
│ └─ users/
```
目录名称应优先复用现有页面目录,避免仅为样式拆分同步搬动 TSX 文件。
现有 `components.css``admin.css``client.css` 应先完成所有权清单,再决定迁入目录、拆分或保留;不得在旧文件仍承担相同职责时直接创建第二套重复公共样式。
## 6. 样式归属规则
日常开发以 `docs/css-development-guidelines.md` 的归属决策为准。本节用于拆分时判断存量选择器的目标位置。
| 样式类型 | 目标文件 | 规则 |
|---|---|---|
| 颜色、间距、字号、圆角、阴影变量 | `tokens.css` | 只定义变量,不放页面选择器 |
| `html``body`、标题、表单字体继承等浏览器重置 | `reset.css` | 不包含业务类名 |
| 侧栏、导航、页面框架、登录框架 | `shell.css` | 仅负责应用骨架,不负责页面业务卡片 |
| Button、Input、Modal、Tag、Pagination 等通用组件 | `styles/components/*.css` | 选择器必须与通用组件类名一致 |
| 只被一个页面使用的样式 | 页面同目录 CSS | 由该页面 TSX 直接 import |
| 同一业务域多个页面共享的样式 | 业务域目录中的共享 CSS | 必须带业务域根前缀 |
| 尚未完成归属确认的旧样式 | `global.css` | 行为上作为 legacy 文件,只允许迁出、删除和保持视觉等价所需的最小修正 |
禁止继续向 `global.css` 增加以下内容:
- 新页面样式。
- 新业务组件样式。
- 为解决单页问题而增加的无页面根节点限定覆盖。
- 新的 `!important`,除非有单独说明和回归用例。
## 7. 拆分前置工作
### 7.1 建立选择器清单
`global.css` 中每个顶层选择器记录:
- 选择器名称。
- 被哪些 TSX 文件引用。
- 是否存在重复定义。
- 是否出现在媒体查询中。
- 是否依赖前后定义顺序。
- 建议目标文件。
- 是否存在无法确认用途的情况。
不能仅凭类名猜测归属。必须通过 `rg`、构建产物和真实页面 DOM 共同确认。
### 7.2 建立视觉基线
至少保存以下尺寸下的真实页面截图和关键元素尺寸:
- 桌面:1600×1000。
- 常见笔记本:1366×768。
- 窄屏:390×844。
基线页面至少包括:
- 运营看板。
- 短信通道列表和报备详情。
- 报备字段库。
- 报备资料池、批次、通道报备明细和状态记录。
- 企业签名管理及查看资料弹窗。
- 短信记录和详情弹窗。
- 系统监控、安全检测和高风险操作弹窗。
- 客户端首页、发送短信、签名、模板、账务和用户页面。
- 运营端与客户端登录页。
使用真实 API 和测试环境数据进行最终验收;隔离截图数据只能用于布局比较,不能替代真实功能验收。
### 7.3 冻结无关格式化
拆分完成前:
- 禁止对 `global.css` 执行整文件 `prettier --write`
- 每个提交必须检查 `git diff --stat` 和 CSS diff;出现明显超出迁移范围的大量重排立即停止。
- 增加“本提交是否只移动目标选择器”的人工检查项。
- 不通过关闭全部格式检查规避问题;应对新文件执行格式检查,对历史遗留文件采用迁出即格式化策略。
### 7.4 建立治理基线
正式拆分前必须提交一份机器可读的基线,至少记录:
- `global.css` 的字节数、规则数、选择器数和声明数。
- 已知允许暂留的全局基础选择器。
- 已拆分页面的根类名和目标样式文件。
- 当前 CSS import 层级和入口顺序。
- 临时例外的原因、影响范围和清理条件。
基线比较必须使用 PostCSS AST;行数和字节数只作为辅助指标,不能把注释或换行变化误判为新增业务规则。
## 8. 分阶段实施方案
### 阶段 0:建立防扩散门禁
目标:在正式拆分前停止继续恶化。
1. 提交根目录 `AGENTS.md``docs/css-development-guidelines.md`,明确规则已开始生效。
2. 保留 `global.css` 文件名并增加“只减不增”文件头;先不重命名,避免破坏当前硬编码该路径的质量脚本。
3. 扩展增量格式脚本,使新增和修改的 CSS 进入 Prettier 检查。
4. 引入 Stylelint:新文件严格执行;`global.css` 初期使用兼容配置,不要求一次清理全部历史问题。
5. 新增基于 PostCSS AST 的通用静态检查,阻止向 `global.css` 增加页面/业务选择器、无说明 `!important` 和宽泛业务标签规则。
6. 建立增长基线和显式例外清单;无例外时,`global.css` 的规则、选择器、声明和字节数均不得无说明增长。
7. CI 使用目标分支 merge-base 或显式 `QUALITY_BASE_REF` 比较完整提交范围,不能只比较工作区相对 `HEAD` 的未提交差异。
8. 建立 CSS 选择器与页面引用清单,并登记已拆分页面的根类名和所有者文件。
9. 新页面必须使用同目录 CSS;共享样式必须先通过归属判断。
验收标准:规则文档可以被 Codex 和开发者直接执行;本地质量命令与 CI 都能拦截故意加入的违规样例;删除违规样例后全部通过;不改变任何计算样式,所有现有页面和构建结果保持一致。
### 阶段 1:基础层和应用骨架去重
目标:先迁移风险较低、归属明确的全局基础规则。
1. 对比 `global.css``reset.css``shell.css``components.css` 中重复的基础规则。
2. 将标签级重置统一到 `reset.css`
3. 将侧栏、顶栏、导航、页面容器和登录框架统一到 `shell.css`
4. 同名规则只在确认计算样式一致后删除旧定义。
风险重点:加载顺序和相同 specificity 下的后定义覆盖。
### 阶段 2:通用 UI 组件拆分
目标:将跨页面真正复用的组件从业务样式中分离。
建议依次拆分:
1. 按钮、图标按钮和操作按钮组。
2. Input、Select、Textarea、查询栏和错误提示。
3. Modal、Drawer、确认弹窗和弹窗 Footer。
4. Tag、Alert、Loading、Empty State。
5. Table、Pagination、统计卡和详情列表。
每迁出一类组件,需要搜索所有调用页面,验证 hover、focus、disabled、loading、danger 和窄屏状态。
### 阶段 3:报备工作台业务域
优先拆分近期最活跃、冲突概率最高的报备相关样式:
- `report-*`
- `channel-field-*`
- `channel-selected-*`
- `channel-export-*`
- `report-material-*`
- `report-batch-*`
- 报备资料查看、导入、导出格式选择弹窗
建议建立:
```text
src/apps/admin/reporting/
├─ report-workbench.css
├─ report-field-mapping.css
├─ report-materials.css
├─ report-batches.css
└─ report-records.css
```
本阶段必须特别验证:
- 字段池单卡片固定高度。
- 导出字段顺序和按钮宽度。
- 图片预览和历史字段展示。
- 状态记录大屏免横向滚动布局。
- 资料池、批次和明细页面的桌面/窄屏响应式。
### 阶段 4:企业签名和通道域
拆分:
- `signature-*`
- `enterprise-signature-*`
- `carrier-report-*`
- `channel-*` 中不属于报备字段弹窗的通道管理样式
- 通道组、通道状态、质量矩阵和签名三网状态样式
需要先区分“通道管理共享样式”和“报备工作台专用样式”,避免仅按 `channel-` 前缀批量移动。
### 阶段 5:短信、任务、监控和风险域
按现有页面目录继续迁移:
1. 短信发送、短信记录和发送详情。
2. 批次任务、任务进度和下游恢复/重投页面。
3. 系统日志、系统监控和服务指标。
4. 黑名单、敏感词、风险规则和安全检测。
5. 充值、对账、利润与账务页面。
已存在页面 CSS 的模块先做“从 global.css 迁出遗漏规则”,不重新创建第二份页面样式文件。
### 阶段 6:客户端域
`client.css` 从当前 8 行的占位文件调整为客户端共享层,并优先把页面专用规则放到具体客户端页面目录。
重点防止运营端和客户端使用相同短类名时互相覆盖。共享规则必须确认视觉语义一致,否则分别使用 `.admin-*``.client-*` 根前缀。
### 阶段 7:清理遗留文件
1. 对剩余选择器逐项确认引用。
2. 无引用选择器必须经过构建、DOM 检查和真实页面核验后按小批次删除。
3. 检查重复媒体查询、重复属性和无效覆盖。
4. 删除空的 `global.css` 及其入口引用。
5. 更新项目开发规范和测试进度。
## 9. 单批次迁移方法
每一批只处理一个页面或一个可独立验证的组件族:
1. 记录目标选择器及其原始行号。
2. 搜索 TSX 使用位置和其他 CSS 中的同名定义。
3. 将完整规则连同伪类、子选择器和媒体查询一起移动。
4. 在目标页面或业务域入口显式 import 新 CSS,并确保页面具有唯一根类名命名空间。
5. 保持选择器文本、属性顺序和 import 后的级联顺序不变。
6. 运行定向测试、TypeScript 和生产构建。
7. 在三个尺寸下比较迁移前后页面。
8. 检查浏览器控制台、网络请求、滚动和弹窗层级。
9. 独立提交,不夹带业务代码修改。
修改共享样式时必须列出全部已知消费者,并逐一检查关键状态;不能只验证提出本次修改的页面。
禁止使用正则一次性移动所有相同前缀,因为同一前缀可能跨多个页面或业务域。
## 10. 级联和加载顺序策略
CSS 拆分最大的风险不是文件移动本身,而是加载顺序变化。
建议最终固定顺序:
```text
tokens → reset → shell → shared components → business shared → page styles
```
注意事项:
- 相同 specificity 的规则依赖“后加载覆盖”,移动前必须记录原顺序。
- 页面级 CSS 由懒加载页面 import 时,要验证首次进入、刷新、跨页面跳转后的样式一致性。
- SPA 中已经加载的页面 CSS 通常不会在离开路由后卸载,因此页面样式必须位于唯一页面根类名下,不能把正确性建立在“当前页面最后加载”之上。
- 不依靠提高 specificity 或新增 `!important` 掩盖顺序错误。
- 公共组件样式不能反向依赖业务页面样式。
- 媒体查询应跟随其所属组件迁移,不集中到新的全局响应式文件。
## 11. 测试与验收要求
### 11.1 自动化检查
每批至少执行:
- 与页面匹配的 Vitest/Testing Library 用例。
- 前端全量测试。
- TypeScript 检查。
- Vite 生产构建。
- Bundle 预算检查。
- CSS/代码格式检查,仅覆盖新文件和本批实际修改文件。
- Stylelint;新文件执行严格规则,遗留文件执行过渡规则。
- `global.css` AST 增长和禁止选择器检查。
- 页面根类名、样式所有者和 import 关系检查。
- `git diff --check`
- 选择器引用检查和重复选择器报告。
门禁实现要求:
- 本地增量检查可覆盖 staged、unstaged 和 untracked CSS;提交前以 staged diff 为最终范围。
- CI 必须比较目标分支 merge-base 到当前提交,不能依赖默认 `HEAD` 导致已提交变化漏检。
- `global.css` 增长判断以 PostCSS AST 为主,行数和字节数为辅。
- 例外采用仓库内显式清单,至少包含选择器或规则、原因、影响范围和清理条件;不接受只有提交信息中的“临时处理”。
- 门禁脚本自身必须包含允许样例和拒绝样例测试。
### 11.2 浏览器验收
构建成功不能等同于拆分成功。必须检查:
- 真实页面是否加载对应 CSS 资源。
- 页面首次进入和路由切换后的样式是否一致。
- loading、空数据、失败、权限不足和历史数据状态。
- Modal、Drawer、下拉框、Sticky 表头和滚动容器。
- hover、focus、disabled、selected 和 danger 状态。
- 桌面端、笔记本和窄屏。
- 浏览器控制台无新增错误和样式资源 404。
- 页面没有新增横向溢出、遮挡或层级错误。
### 11.3 视觉差异判定
- 纯文件拆分批次的目标是关键页面像素级无变化。
- 允许字体抗锯齿等环境噪声,但布局尺寸、间距、颜色、折行、滚动和交互状态必须一致。
- 发现视觉变化时优先恢复原级联顺序,不允许顺手“优化得更好看”后继续合并。
## 12. Git 与协作策略
- 每个提交只拆一个页面或一个组件族。
- 提交名称示例:`refactor: extract report field mapping styles`
- 提交前检查 staged diff,只包含目标 CSS、新 import、测试和文档。
- 不在拆分提交中执行全仓库格式化。
- 不删除或覆盖其他会话的 staged、unstaged、untracked 文件。
- CSS 门禁例外必须与对应变更同批审查;不得用扩大例外范围的方式让无关规则通过。
- 主分支持续开发时,优先迁移冲突最频繁的业务域,并缩短分支存活时间。
- 每批可独立 `git revert`,不要求整体回退全部拆分阶段。
## 13. 风险与应对
| 风险 | 表现 | 应对 |
|---|---|---|
| CSS 加载顺序变化 | 颜色、间距或布局悄然变化 | 固定 import 顺序并比较 computed style |
| 选择器漏迁 | 某些弹窗或窄屏状态失去样式 | 伪类、子选择器、媒体查询按选择器族迁移 |
| 同名选择器跨页面复用 | 拆走后其他页面异常 | TSX/CSS 双向搜索,无法确认时暂留 legacy |
| 懒加载 CSS 顺序不稳定 | 跨页面跳转后样式变化 | 页面根节点命名空间隔离,验证刷新和跳转 |
| 整文件格式化污染 diff | 数千行无关修改 | 禁止格式化 legacy,迁出后只格式化新文件 |
| 误删历史样式 | 历史数据或低频页面显示异常 | 删除单独分批,真实历史数据页面复验 |
| 拆分期间业务需求冲突 | 合并困难、重复搬运 | 小提交、短周期、按高频业务域优先实施 |
## 14. 回退方案
每个迁移批次的回退单位是对应独立提交:
1. 浏览器验收失败时停止后续拆分。
2. 保留截图、控制台和具体选择器差异证据。
3. 回退当批提交,恢复原 import 和原文件位置。
4. 不通过追加高优先级覆盖掩盖错误。
5. 修正选择器归属或加载顺序后重新提交该批次。
CSS 拆分不涉及数据库迁移、服务配置或短信链路,因此不应触发数据库回滚、队列处理或短信重投。
## 15. 建议实施顺序与工作量
| 阶段 | 内容 | 预计工作量 |
|---|---|---:|
| 0 | AGENTS、日常规范、清单、视觉基线、防扩散门禁 | 2~3 人日 |
| 1 | Reset、Shell、基础层去重 | 12 人日 |
| 2 | 通用 UI 组件拆分 | 24 人日 |
| 3 | 报备工作台 | 2~3 人日 |
| 4 | 企业签名和通道 | 2~3 人日 |
| 5 | 短信、任务、监控、风险、账务 | 3~5 人日 |
| 6 | 客户端页面 | 2~3 人日 |
| 7 | 遗留清理、全量回归和规范固化 | 2~3 人日 |
整体建议按 2~4 周的渐进式治理安排实施,而不是一次性大提交。实际工期取决于测试环境登录态、历史数据覆盖和视觉回归工具可用性。
## 16. 完成标准
只有同时满足以下条件,才能认为治理完成:
1. `global.css` 已删除,或只保留经过批准且不超过 300 行的真正全局规则。
2. 所有业务样式均有清晰的页面或业务域归属。
3. 新业务样式写入遗留全局文件会被门禁阻止。
4. 根目录 `AGENTS.md` 和日常 CSS 规范已成为当前生效规则,自动门禁与其一致。
5. CSS 已纳入增量 Prettier、Stylelint、所有权和 AST 增长检查,本地及 CI 均能识别完整变更范围。
6. 前端全量测试、TypeScript、生产构建和包体积检查通过。
7. 核心运营端、客户端页面在三种尺寸下完成真实浏览器回归。
8. 浏览器控制台和网络请求无新增错误,CSS 资源无 404。
9. 报备状态记录的大屏布局、字段池固定高度、Sticky 表头和所有关键弹窗没有回归。
10. `docs/testing-progress.md` 和相关系统功能用例已同步更新。
## 17. 2026-09-05 实施状态
- `src/styles/global.css` 已删除,入口替换为 `src/styles/domains/index.css`;原1749条规则、2017个选择器、5321条声明和30个既有`!important`按原顶层节点顺序迁入14个业务域文件。
- 迁移前后Vite主CSS产物名称、字节内容和SHA-256完全一致,证明本次只改变源码所有权与文件边界,没有改变最终CSS级联结果。
- `tools/quality/css-governance-baseline.json`保存原始AST指标、完整选择器顺序、模块所有者、导入顺序和模块摘要;`verify-css-governance.mjs`验证旧文件不得重建、AST总量及顺序不变、既有`!important`不得增长、新CSS不得增加宽泛标签选择器、所有CSS必须存在明确import。
- CSS已进入增量Prettier和Stylelint。迁出的历史规则使用显式兼容清单保留现状;未登记的新CSS默认执行Stylelint标准规则,因此兼容配置不会自动扩散到新页面。
- 新增GitHub CSS质量工作流,以目标分支或父提交作为`QUALITY_BASE_REF`,避免只检查未提交工作区导致已提交违规变化漏检。
- 原方案建议逐批迁移;本次为满足一次性执行要求,采用单次原序抽取。由于14个模块共同组成一条完整且经产物哈希证明等价的级联链,保持为一个可整体回退的原子变更,避免中间提交出现规则重复或缺失。