日期: 2025-02-10
影响范围: 主应用apps/main、共享包packages/ui、所有子应用
一、现状诊断
1.1 样式文件分布(主应用)
当前主应用存在 3 个互不关联的样式目录,没有统一入口:
apps/main/src/
├── assets/
│ ├── main.css ← Vue 脚手架残留 + Tailwind 入口
│ ├── base.css ← Vue 脚手架残留 CSS Reset + 变量
│ └── styles/
│ └── micro-app.css ← 微前端"隔离"样式(!important 硬覆盖)
├── styles/
│ ├── index.scss ← 样式入口(⚠️ 从未被任何文件引用!)
│ ├── var.css ← 布局 + 主题 CSS 变量
│ ├── dark.css ← 暗色主题变量
│ ├── resizeStyle.scss ← 632 行巨型文件(EP 覆盖 + 布局 + 业务混杂)
│ ├── dialog.scss ← 弹窗尺寸
│ ├── theme.scss ← 空文件(全注释)
│ ├── variables.scss ← 仅 2 个 SCSS 变量
│ └── global.module.scss ← 导出命名空间给 JS
1.2 main.ts 样式导入链
main.ts
├── assets/main.css → @import base.css + @import tailwindcss
├── assets/styles/micro-app.css
├── element-plus 组件样式 ×4(message-box / message / notification / loading)
└── @cmclink/ui/styles → variables + element-override + base + utilities + animations + tailwindcss
关键发现:styles/index.scss(包含 var.css、resizeStyle.scss、dialog.scss)在整个项目中没有被任何文件 import。这意味着 632 行的 Element Plus 覆盖样式、布局变量、暗色主题等可能完全没有生效,或者曾经生效但在某次重构中被遗漏。
1.3 核心问题清单
| # | 问题 | 严重程度 | 说明 |
|---|---|---|---|
| 1 | Tailwind CSS 重复引入 | 🔴 高 | assets/main.css 和 @cmclink/ui/styles 各引入一次,产生重复样式 |
| 2 | CSS Reset 重复执行 | 🔴 高 | assets/base.css 和 @cmclink/ui/base.scss 各做一次全局 reset |
| 3 | 主色定义冲突 | 🔴 高 | var.css→#005aae、@cmclink/ui→#004889、resizeStyle.scss→硬编码 #005aae,三处不一致 |
| 4 | styles/index.scss 未被引用 | 🔴 高 | 632 行 EP 覆盖 + 布局变量 + 暗色主题可能完全未生效 |
| 5 | body 样式双重定义 | 🟡 中 | base.css 和 @cmclink/ui/base.scss 都设置了 body 样式 |
| 6 | 微前端样式隔离 = !important 战争 | 🔴 高 | micro-app.css 用 !important 硬覆盖,没有利用 wujie CSS 沙箱 |
| 7 | 脚手架残留代码 | 🟡 中 | base.css 的 --vt-c-* 变量、main.css 的 .green 类 |
| 8 | 废文件堆积 | 🟡 中 | theme.scss(空)、variables.scss(2 行)、global.module.scss(价值存疑) |
| 9 | !important 泛滥 | 🟡 中 | resizeStyle.scss 和 micro-app.css 大量使用 |
| 10 | 样式职责不清 | 🔴 高 | EP 覆盖、页面布局、业务样式、主题变量全混在一个文件 |
1.4 @cmclink/ui 样式包分析
packages/ui/src/styles/ 已经有一套相对完整的设计体系:
- variables.scss — 完整的设计令牌(颜色、字体、间距、圆角、阴影等)+ CSS 变量映射
- mixins.scss — 响应式断点、文本截断、布局、按钮变体等混合器
- base.scss — 全局 reset、标题、段落、表格、滚动条、打印、暗色、无障碍
- element-override.scss — Element Plus 样式覆盖
- utilities.scss — CMC 专用工具类(
cmc-*前缀) - animations.scss — 动画库(1250 行,含大量与 Tailwind 重复的工具类)
问题:@cmclink/ui 的 animations.scss 中有大量 .translate-*、.scale-*、.rotate-*、.duration-* 等类名,与 Tailwind CSS 完全重复。
二、目标架构设计
2.1 设计原则
- 单一真相源(Single Source of Truth):设计令牌只在
@cmclink/ui中定义一次 - 分层隔离:全局样式、主题变量、EP 覆盖、布局样式、业务样式严格分层
- 微前端天然隔离:依赖 wujie 的 CSS 沙箱(WebComponent + iframe),而非
!important - Tailwind 唯一入口:整个主应用只在一个地方引入 Tailwind CSS
- 可维护性:每个文件职责单一,命名语义化,新人 5 分钟能理解结构
2.2 目标目录结构
apps/main/src/
├── styles/ ← 主应用样式唯一目录
│ ├── index.scss ← ⭐ 唯一入口文件(main.ts 只 import 这一个)
│ ├── tailwind.css ← Tailwind CSS 唯一引入点
│ │
│ ├── tokens/ ← 主应用级设计令牌(覆盖/扩展 @cmclink/ui)
│ │ ├── _variables.scss ← SCSS 变量(供 SCSS 文件内部使用)
│ │ └── _css-variables.scss ← CSS 自定义属性(布局变量、主应用专属变量)
│ │
│ ├── themes/ ← 主题系统
│ │ ├── _light.scss ← 亮色主题变量
│ │ └── _dark.scss ← 暗色主题变量
│ │
│ ├── overrides/ ← Element Plus 样式覆盖(主应用级)
│ │ ├── _button.scss ← 按钮覆盖
│ │ ├── _table.scss ← 表格覆盖
│ │ ├── _form.scss ← 表单覆盖(filterBox 等)
│ │ ├── _dialog.scss ← 弹窗覆盖
│ │ ├── _message-box.scss ← 消息框覆盖
│ │ ├── _select.scss ← 下拉框覆盖
│ │ ├── _tag.scss ← 标签覆盖
│ │ ├── _tabs.scss ← 标签页覆盖
│ │ └── _index.scss ← EP 覆盖汇总入口
│ │
│ ├── layout/ ← 布局样式
│ │ ├── _app-view.scss ← AppViewContent / AppViewScroll
│ │ ├── _login.scss ← 登录页样式
│ │ └── _index.scss ← 布局汇总入口
│ │
│ ├── vendors/ ← 第三方库样式适配
│ │ └── _nprogress.scss ← NProgress 主题色适配
│ │
│ └── legacy/ ← 遗留业务样式(逐步迁移到组件 scoped 中)
│ ├── _si-detail.scss ← 出口单证详情
│ ├── _marketing.scss ← 营销模块
│ └── _index.scss ← 遗留样式汇总入口
│
├── assets/ ← 仅保留静态资源
│ └── images/ ← 图片资源
│ ├── avatar.gif
│ ├── cmc-logo.png
│ └── ...
2.3 入口文件设计
styles/index.scss(唯一入口):
// =============================================================================
// CMCLink 主应用样式入口
// 加载顺序严格按照优先级排列,请勿随意调整
// =============================================================================
// 1️⃣ Tailwind CSS(最先加载,作为基础原子类层)
@use './tailwind.css';
// 2️⃣ 主应用级设计令牌(覆盖/扩展 @cmclink/ui 的变量)
@use './tokens/css-variables';
// 3️⃣ 主题系统
@use './themes/light';
@use './themes/dark';
// 4️⃣ Element Plus 样式覆盖
@use './overrides/index';
// 5️⃣ 布局样式
@use './layout/index';
// 6️⃣ 第三方库适配
@use './vendors/nprogress';
// 7️⃣ 遗留业务样式(逐步清理)
@use './legacy/index';
main.ts(重构后):
// 样式:唯一入口
import './styles/index.scss'
// Element Plus 反馈组件样式(非按需导入的全局组件)
import 'element-plus/es/components/message-box/style/css'
import 'element-plus/es/components/message/style/css'
import 'element-plus/es/components/notification/style/css'
import 'element-plus/es/components/loading/style/css'
// 组件库样式(@cmclink/ui 提供设计令牌 + 基础样式 + EP 覆盖)
import '@cmclink/ui/styles'
// 其余保持不变...
注意:
@cmclink/ui/styles中的 Tailwind CSS 引入需要移除,改为只在主应用的styles/tailwind.css中引入一次。
2.4 样式加载顺序
┌─────────────────────────────────────────────────────────┐
│ 1. Tailwind CSS(原子类基础层) │
├─────────────────────────────────────────────────────────┤
│ 2. @cmclink/ui/styles │
│ ├── variables.scss → 设计令牌 + CSS 变量映射 │
│ ├── base.scss → 全局 reset + 排版 │
│ ├── element-override.scss → 组件库级 EP 覆盖 │
│ ├── utilities.scss → CMC 工具类 │
│ └── animations.scss → 动画(需清理与 Tailwind 重复部分)│
├─────────────────────────────────────────────────────────┤
│ 3. Element Plus 反馈组件样式(message/notification 等) │
├─────────────────────────────────────────────────────────┤
│ 4. 主应用 styles/index.scss │
│ ├── tokens/ → 主应用级变量(布局尺寸、主题色扩展) │
│ ├── themes/ → 亮色/暗色主题 │
│ ├── overrides/ → 主应用级 EP 覆盖 │
│ ├── layout/ → 页面布局 │
│ ├── vendors/ → 第三方库适配 │
│ └── legacy/ → 遗留业务样式 │
├─────────────────────────────────────────────────────────┤
│ 5. 组件 <style scoped>(组件级样式,天然隔离) │
└─────────────────────────────────────────────────────────┘
三、微前端样式隔离策略
3.1 wujie CSS 沙箱机制
wujie 使用 WebComponent(shadowDOM)+ iframe 双重沙箱:
- JS 沙箱:子应用 JS 运行在 iframe 中,天然隔离
- CSS 沙箱:子应用 DOM 渲染在 WebComponent 的 shadowDOM 中,样式天然隔离
这意味着:
- ✅ 子应用的样式不会泄漏到主应用
- ✅ 主应用的样式不会侵入子应用(shadowDOM 边界)
- ⚠️ 但是:Element Plus 的弹窗(Dialog/Drawer/MessageBox)默认挂载到
document.body,会逃逸出 shadowDOM
3.2 弹窗逃逸问题的正确解决方案
当前做法(错误):在主应用 micro-app.css 中用 !important 覆盖子应用弹窗样式。
正确做法:在子应用中配置 Element Plus 的 teleported 和 append-to 属性,让弹窗挂载到子应用自身的 DOM 容器内,而非 document.body。
方案 A:子应用全局配置(推荐)
在子应用的入口文件中配置 Element Plus 的全局属性:
// 子应用 main.ts
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
const app = createApp(App)
app.use(ElementPlus, {
// 弹窗不使用 teleport,保持在子应用 DOM 树内
// 这样弹窗样式就在 shadowDOM 内,天然隔离
})
同时在子应用的弹窗组件中统一设置:
<!-- 子应用中使用 Dialog -->
<el-dialog :teleported="false">
<!-- 内容 -->
</el-dialog>
<!-- 或者指定挂载到子应用容器 -->
<el-dialog append-to=".child-app-root">
<!-- 内容 -->
</el-dialog>
方案 B:wujie 插件拦截(自动化)
通过 wujie 的插件系统,自动为子应用的弹窗组件注入配置:
// plugins/wujie.ts 中增加插件配置
import { setupApp } from 'wujie'
setupApp({
name: 'child-app',
// ... 其他配置
plugins: [
{
// CSS loader:可以在子应用 CSS 加载时做处理
cssLoader: (code: string) => code,
// JS loader:可以在子应用 JS 加载时做处理
jsLoader: (code: string) => code,
}
]
})
方案 C:共享样式包(兜底方案)
如果确实需要主应用和子应用共享某些样式(如统一的弹窗规范),应该通过 @cmclink/ui 包来分发,而非在主应用中硬写覆盖:
packages/ui/src/styles/
├── shared/ ← 可被子应用独立引入的共享样式
│ ├── dialog-standard.scss ← 弹窗标准样式
│ └── form-standard.scss ← 表单标准样式
子应用按需引入:
// 子应用 main.ts
import '@cmclink/ui/styles/shared/dialog-standard.scss'
3.3 样式隔离总结
| 场景 | 隔离机制 | 是否需要额外处理 |
|---|---|---|
| 子应用普通样式 | wujie shadowDOM 天然隔离 | ❌ 无需处理 |
| 子应用弹窗/抽屉 | 配置 :teleported="false" | ✅ 子应用侧配置 |
| 主应用与子应用共享样式 | 通过 @cmclink/ui 包分发 | ✅ 包级别管理 |
| 主应用全局样式 | 不会侵入子应用(shadowDOM) | ❌ 无需处理 |
3.4 删除 micro-app.css
重构完成后,assets/styles/micro-app.css 应该被完全删除。其中的内容处理方式:
| 原内容 | 处理方式 |
|---|---|
.micro-app .el-dialog 覆盖 | 子应用配置 :teleported="false" 后不再需要 |
.micro-app-test.marketing | 测试代码,直接删除 |
.test-paragraph / .test-box | 测试代码,直接删除 |
四、主色统一方案
4.1 现状:三处主色定义
| 位置 | 主色值 | 说明 |
|---|---|---|
styles/var.css | #005aae | 主应用布局变量 |
@cmclink/ui/variables.scss | #004889 | 组件库设计令牌 |
resizeStyle.scss | #005aae(硬编码) | 登录页等处直接写死 |
4.2 统一方案
以 @cmclink/ui 的设计令牌为唯一真相源。
-
确认最终主色值(与设计团队对齐):
- 如果设计稿是
#004889→ 主应用的var.css需要同步修改 - 如果设计稿是
#005aae→@cmclink/ui的variables.scss需要同步修改
- 如果设计稿是
-
主应用中所有硬编码的颜色值,改为引用 CSS 变量:
// ❌ 错误 background-color: #005aae; // ✅ 正确 background-color: var(--el-color-primary); -
主应用的
tokens/_css-variables.scss只定义布局相关的变量,颜色变量全部继承@cmclink/ui。
五、文件迁移映射
5.1 需要删除的文件
| 文件 | 原因 |
|---|---|
assets/main.css | 脚手架残留,Tailwind 迁移到 styles/tailwind.css |
assets/base.css | 脚手架残留,与 @cmclink/ui/base.scss 重复 |
assets/styles/micro-app.css | !important 硬覆盖,改用 wujie 沙箱 |
styles/theme.scss | 空文件 |
styles/variables.scss | 仅 2 行,合并到 tokens/_variables.scss |
styles/global.module.scss | 价值存疑,如需保留可合并 |
5.2 需要拆分的文件
styles/var.css(154 行)→ 拆分为:
| 目标文件 | 内容 |
|---|---|
tokens/_css-variables.scss | 布局变量(--left-menu-*、--top-header-*、--tags-view-*、--app-content-* 等) |
| 删除 | 颜色变量(--el-color-primary 等),改为继承 @cmclink/ui |
styles/resizeStyle.scss(632 行)→ 拆分为:
| 目标文件 | 内容 | 行数(约) |
|---|---|---|
overrides/_button.scss | 按钮样式覆盖(L1-20) | ~20 |
overrides/_tag.scss | 标签样式(L29-33) | ~5 |
overrides/_select.scss | 下拉框样式(L36-38) | ~3 |
overrides/_table.scss | 表格样式(L41-80, L374-417) | ~80 |
overrides/_message-box.scss | 消息框样式(L83-98, L399-406) | ~20 |
overrides/_form.scss | filterBox 表单样式(L484-618) | ~135 |
layout/_login.scss | 登录页样式(L100-117) | ~18 |
layout/_app-view.scss | AppViewContent / AppViewScroll / tabPage(L119-306) | ~190 |
legacy/_si-detail.scss | 出口单证详情(index.scss L50-105) | ~55 |
legacy/_marketing.scss | 营销模块(L308-373, L418-438) | ~70 |
| 删除 | .container-selected-row(箱管选中行)→ 迁移到对应组件 scoped | ~8 |
styles/index.scss(126 行)→ 拆分为:
| 目标文件 | 内容 |
|---|---|
vendors/_nprogress.scss | NProgress 主题色适配(L22-38) |
overrides/_form.scss | append-click-input(L39-49) |
legacy/_si-detail.scss | si-detail-dialog / si-detail-tabs(L50-105) |
| 删除 | .reset-margin、.el-popup-parent--hidden、.el-scrollbar__bar → 评估是否仍需要 |
styles/dark.css(85 行)→ 迁移到:
| 目标文件 | 说明 |
|---|---|
themes/_dark.scss | 完整保留暗色主题变量,清理注释掉的废代码 |
styles/dialog.scss(17 行)→ 迁移到:
| 目标文件 | 说明 |
|---|---|
overrides/_dialog.scss | 弹窗尺寸规范 |
5.3 @cmclink/ui 需要调整的部分
| 文件 | 调整内容 |
|---|---|
styles/index.scss | 移除 @use 'tailwindcss',Tailwind 只在应用层引入 |
styles/animations.scss | 清理与 Tailwind 重复的工具类(.translate-*、.scale-*、.rotate-*、.duration-* 等约 800 行) |
styles/utilities.scss | 保留 cmc-* 前缀的工具类,删除与 Tailwind 重复的部分 |
六、实施步骤
Phase 1:基础清理(低风险,可立即执行)
预计耗时:1-2 小时
-
创建新目录结构
- 创建
styles/tokens/、styles/themes/、styles/overrides/、styles/layout/、styles/vendors/、styles/legacy/
- 创建
-
创建
styles/tailwind.css@import "tailwindcss"; -
删除废文件
styles/theme.scss(空文件)
-
统一主色
- 与设计团队确认最终主色值
- 更新所有硬编码颜色为 CSS 变量引用
Phase 2:文件拆分迁移(中风险,需逐步验证)
预计耗时:3-4 小时
- 拆分
resizeStyle.scss(632 行 → 8 个文件) - 拆分
var.css(布局变量 →tokens/_css-variables.scss) - 迁移
dark.css→themes/_dark.scss - 迁移
dialog.scss→overrides/_dialog.scss - 拆分
index.scss(NProgress → vendors,业务 → legacy) - 创建新的
styles/index.scss入口文件
Phase 3:入口重构(高风险,需完整回归测试)
预计耗时:1-2 小时
-
修改
main.ts- 移除
import './assets/main.css' - 移除
import './assets/styles/micro-app.css' - 添加
import './styles/index.scss' - 调整
@cmclink/ui/styles的导入顺序
- 移除
-
删除旧文件
assets/main.cssassets/base.cssassets/styles/micro-app.cssassets/styles/目录
-
完整回归测试
- 登录页样式
- 主布局(侧边栏、顶部导航、标签页)
- 表格页面(筛选框、表格、分页)
- 弹窗/抽屉
- 暗色主题切换
- 子应用加载和样式隔离
Phase 4:微前端隔离优化(需子应用配合)
预计耗时:2-3 小时(每个子应用)
-
子应用配置弹窗不逃逸
- 全局配置
:teleported="false"或append-to - 在
@cmclink/micro-bootstrap中提供统一配置能力
- 全局配置
-
删除
micro-app.css -
验证子应用样式隔离
- 主应用样式不侵入子应用
- 子应用样式不泄漏到主应用
- 弹窗/抽屉样式正确
Phase 5:@cmclink/ui 优化(独立进行)
预计耗时:2-3 小时
- 移除 Tailwind CSS 引入(从
@cmclink/ui/styles/index.scss) - 清理
animations.scss中与 Tailwind 重复的工具类(约 800 行) - 审查
utilities.scss中与 Tailwind 重复的部分
七、风险评估与回退方案
7.1 风险点
| 风险 | 概率 | 影响 | 缓解措施 |
|---|---|---|---|
styles/index.scss 未被引用但样式实际生效 | 低 | 高 | 先在浏览器 DevTools 确认哪些样式实际生效 |
| 拆分后样式加载顺序变化导致覆盖失效 | 中 | 中 | 严格按照原有顺序组织 @use |
子应用弹窗 :teleported="false" 导致层级问题 | 中 | 中 | 逐个子应用测试,必要时用 z-index 调整 |
删除 base.css 后某些页面样式异常 | 低 | 中 | @cmclink/ui/base.scss 已覆盖所有 reset |
7.2 回退方案
每个 Phase 独立提交 Git,如果出现问题可以精确回退到任意阶段:
feat(styles): phase-1 基础清理和目录结构
feat(styles): phase-2 文件拆分迁移
feat(styles): phase-3 入口重构
feat(styles): phase-4 微前端隔离优化
feat(styles): phase-5 @cmclink/ui 优化
八、验证清单
8.1 样式正确性
- 登录页样式正常(背景、表单、按钮)
- 主布局样式正常(侧边栏展开/收起、顶部导航、面包屑)
- 标签页样式正常(激活态、hover、关闭按钮)
- 表格页面样式正常(筛选框、表头、行、分页)
- 弹窗/抽屉样式正常(大/中/小尺寸)
- 表单样式正常(输入框、下拉框、日期选择器)
- 按钮样式正常(主要、链接、危险、禁用)
- NProgress 进度条颜色正确
- 暗色主题切换正常
8.2 微前端隔离
- 子应用加载后样式正常
- 主应用样式未侵入子应用
- 子应用样式未泄漏到主应用
- 子应用弹窗样式正确(不逃逸到主应用 body)
- 多个子应用切换时样式无残留
8.3 构建产物
- 构建无报错
- CSS 产物体积不增长(预期减少 30%+)
- 无重复的 Tailwind CSS 输出
- 无重复的 CSS Reset
8.4 开发体验
- HMR 样式热更新正常
- 新增样式时知道该放在哪个文件
- SCSS 变量和 CSS 变量可正常引用
九、预期收益
| 维度 | 现状 | 重构后 |
|---|---|---|
| CSS 产物体积 | Tailwind ×2 + Reset ×2 + 大量重复 | 减少约 30-40% |
| 样式文件数 | 散落 3 个目录,8+ 个文件 | 1 个目录,清晰分层 |
| 入口文件 | main.ts 导入 2 个样式文件 + 隐式依赖 | main.ts 导入 1 个入口 |
| 新人上手 | 不知道样式该写在哪 | 5 分钟理解结构 |
| 微前端隔离 | !important 硬覆盖 | wujie 沙箱天然隔离 |
| 主色一致性 | 3 处定义,2 个不同值 | 1 处定义,全局统一 |
| !important 使用 | 泛滥 | 仅在必要的 EP 覆盖中使用 |