339 lines
14 KiB
Markdown
339 lines
14 KiB
Markdown
# 前端页面设计风格一致性分析报告
|
||
|
||
> **项目**: 会议核销 SaaS 系统 Frontend
|
||
> **技术栈**: Vue 3 + Element Plus + Vite + TypeScript
|
||
> **分析范围**: `frontend/src/views/` 下的 31 个页面组件 + 20 个子组件
|
||
> **分析日期**: 2026-04-03
|
||
|
||
---
|
||
|
||
## 一、核心发现总览
|
||
|
||
```mermaid
|
||
pie title 风格不一致分布
|
||
"内联样式泛滥" : 35
|
||
"工具栏/搜索区布局混乱" : 20
|
||
"弹窗/抽屉尺寸不统一" : 15
|
||
"表格与按钮间距不一致" : 12
|
||
"CSS 管理缺失" : 10
|
||
"颜色/字号硬编码" : 8
|
||
```
|
||
|
||
> [!WARNING]
|
||
> 在 31 个页面组件中,**仅有 5 个** 使用了 `<style scoped>` 块,其余全部依赖内联 `style=""` 控制间距和布局。这严重影响后续维护和全局主题切换能力。
|
||
|
||
---
|
||
|
||
## 二、逐项分析
|
||
|
||
### 1. 页面容器结构 — `<el-card>` 使用方式不一致
|
||
|
||
所有 31 个功能页面都以 `<el-card>` 作为根容器,但细节差异如下:
|
||
|
||
| 类型 | 页面 | 使用方式 | 问题 |
|
||
|------|------|---------|------|
|
||
| ✅ 标准模式 | `UserPage`, `RolePage`, `MenuPage`, `PermissionPage` 等 25 个 | `<el-card>` (默认 shadow) | 一致 |
|
||
| ⚠️ 嵌套卡片 | `DataPermissionPage` | 外层 `<el-card>` + 内层 `<el-card shadow="never">` | 嵌套层级不统一 |
|
||
| ⚠️ 多卡片网格 | `OperationsDashboardPage` | 外层 `<el-card>` + 9 个内嵌 `<el-card>` | 独特网格布局,无复用 |
|
||
| ⚠️ 弹窗嵌套 | `MeetingAuditProgressDialog` | `<el-card shadow="never">` 在 dialog 内 | 扁平卡片仅此一处 |
|
||
| 🔴 登录页独立 | `PlatformLoginPage`, `TenantLoginPage` | `<el-card class="login-card">` | 独立设计,与后台页面完全脱离 |
|
||
|
||
> [!IMPORTANT]
|
||
> **建议**: 创建统一的 `<PageContainer>` 包裹组件,标准化 shadow 规则和 padding 规范。
|
||
|
||
---
|
||
|
||
### 2. 工具栏/搜索区域 — 布局方案碎片化
|
||
|
||
搜索栏和操作区的布局方式至少有 **4 种不同范式**:
|
||
|
||
````carousel
|
||
#### 方案 A: `<el-space wrap>` (7 个页面)
|
||
```
|
||
UserPage, RolePage, DataPermissionPage,
|
||
NotificationPolicyPage, TemplatePage,
|
||
PlatformDictionaryPage 等
|
||
```
|
||
使用 `<el-space wrap>` 包裹按钮组
|
||
|
||
---
|
||
|
||
#### 方案 B: `<el-form :inline="true">` (8 个页面)
|
||
```
|
||
ExpertPage, FinancePage, ObservabilityPage,
|
||
PlatformSessionPage, MeetingQueryToolbar 等
|
||
```
|
||
使用内联表单包裹搜索字段和按钮
|
||
|
||
---
|
||
|
||
#### 方案 C: 裸按钮 (3 个页面)
|
||
```
|
||
PermissionPage, OperationsDashboardPage,
|
||
AuditQueryToolbar
|
||
```
|
||
直接将 `<el-button>` 放在卡片内,无任何包裹容器
|
||
|
||
---
|
||
|
||
#### 方案 D: 自定义 header-row (1 个页面)
|
||
```
|
||
InAppNotificationPage
|
||
```
|
||
使用自定义 CSS class `.header-row` 实现 flex 布局
|
||
在 card header slot 中排列控件
|
||
````
|
||
|
||
> [!IMPORTANT]
|
||
> **建议**: 统一使用方案 A(`<el-space wrap>`)或方案 B(`<el-form :inline="true">`)二选一,并封装为 `<QueryToolbar>` 组件。
|
||
|
||
---
|
||
|
||
### 3. 表格与上方控件的间距 — 值不统一
|
||
|
||
当搜索栏与表格之间需要间距时,各页面的做法五花八门:
|
||
|
||
| 间距方式 | 页面 | 间距值 |
|
||
|---------|------|--------|
|
||
| `style="margin-top: 12px"` | `RolePage`, `PermissionPage`, `PlatformPermissionPage`, `DataPermissionPage` | 12px |
|
||
| `style="margin-top: 8px"` | `ObservabilityPage` (×3 处) | 8px |
|
||
| 无间距(紧贴搜索栏) | `UserPage`, `ExpertPage`, `MeetingPage`, `FinancePage` | 0 |
|
||
| `style="margin-bottom: 12px"` (反向) | `OperationsDashboardPage` | 12px (apply on button instead) |
|
||
| `<el-divider />` | `MeetingQueryToolbar`, `FinancePage`, `ObservabilityPage` | Element 默认 |
|
||
|
||
> [!NOTE]
|
||
> 同一个系统内出现 `0px`, `8px`, `12px` 以及 `<el-divider>` 四种不同间距方案来处理同一类场景。
|
||
|
||
---
|
||
|
||
### 4. 弹窗 / 抽屉尺寸 — 宽度规格混乱
|
||
|
||
```mermaid
|
||
graph LR
|
||
subgraph Drawer尺寸
|
||
D1["520px — RolePage"]
|
||
D2["560px — UserPage, ExpertPage ×2"]
|
||
D3["680px — DataPermissionPage, NotificationPolicyPage"]
|
||
D4["80% — ExpertPage (银行卡列表)"]
|
||
end
|
||
subgraph Dialog宽度
|
||
W1["500px — DataPermissionPage (分配角色)"]
|
||
W2["520px — ExpertPage (合并)"]
|
||
W3["560px — ObservabilityPage, ExpertPage (OCR)"]
|
||
W4["620px — ExpertPage (身份证OCR)"]
|
||
W5["680px — RolePage (权限绑定)"]
|
||
W6["700px — NotificationPolicyPage (触发发送)"]
|
||
W7["720px — UserPage (历史), NotificationPolicyPage (编辑)"]
|
||
W8["860px — UserPage (代理授权)"]
|
||
end
|
||
```
|
||
|
||
> **统计**: 抽屉有 4 种宽度规格,对话框有 8 种宽度规格,总计 **12 种不同尺寸**,严重缺乏统一的尺寸档位体系。
|
||
|
||
> [!IMPORTANT]
|
||
> **建议**: 定义 3 个标准 Drawer 尺寸 (`small: 480px`, `medium: 640px`, `large: 80%`)
|
||
> 定义 3 个标准 Dialog 尺寸 (`small: 520px`, `medium: 680px`, `large: 860px`)
|
||
|
||
---
|
||
|
||
### 5. 表单控件宽度 — 硬编码且无统一标准
|
||
|
||
在内联 `style` 中给 `<el-select>`, `<el-input>` 等设置的宽度值收集如下:
|
||
|
||
| 控件场景 | 出现的宽度值 |
|
||
|---------|------------|
|
||
| 搜索栏 Select | `90px`, `110px`, `140px`, `150px`, `160px`, `180px` |
|
||
| 表单 Select | `220px`, `240px`, `260px`, `280px` |
|
||
| 搜索栏 Input | `180px`, `190px`, `200px`, `260px` |
|
||
| 全宽 Select | `100%` |
|
||
| 弹窗内 Select | `100%`, `220px` |
|
||
|
||
> **统计**: 光 `<el-select>` 的宽度就出现了 **10 种** 不同的硬编码值。
|
||
|
||
---
|
||
|
||
### 6. CSS 管理 — `<style scoped>` 严重缺失
|
||
|
||
| 分类 | 页面数 | 比例 |
|
||
|------|--------|------|
|
||
| ✅ 使用 `<style scoped>` | 5 个 (`PlatformLoginPage`, `TenantLoginPage`, `NotificationPolicyPage`, `InAppNotificationPage`, `AppLayout`) | 16% |
|
||
| ✅ 子组件使用 `<style scoped>` | 6 个 (`MeetingListTable`, `MeetingMaterialDrawer`, `MeetingDocPreviewDialog`, `MeetingCreatePlatformExpertDialog`, `MeetingBindExpertDialog`, `MaterialPictureCardFileItem`) | — |
|
||
| 🔴 完全无 `<style>` 块 | 26 个页面 | **84%** |
|
||
|
||
> [!CAUTION]
|
||
> 84% 的页面组件没有任何 CSS 管理方案,全靠内联 `style=""` 支撑,极度不利于:
|
||
> - 全局主题切换 / 暗色模式适配
|
||
> - 设计 Token 标准化
|
||
> - 多人协作维护
|
||
|
||
---
|
||
|
||
### 7. 颜色与字体硬编码
|
||
|
||
在模板中直接内联的颜色和字号值:
|
||
|
||
| 硬编码值 | 出处 | 场景 |
|
||
|---------|------|------|
|
||
| `color: #606266` | `TenantLoginPage`, `AuditQueryToolbar`, `AppLayout` | 提示文字颜色 |
|
||
| `color: #909399` | `ExpertPage` ×4 | 辅助说明 |
|
||
| `font-weight: 600` | `RolePage`, `PlatformRolePage`, `FinancePage`, `ObservabilityPage`, `NotificationPolicyPage` | 小标题 |
|
||
| `font-size: 12px` | `NotificationPolicyPage` | 提示文字 |
|
||
| `font-size: 13px` | `AppLayout` | 登录信息 |
|
||
| `line-height: 1.8` / `1.9` | `ExpertPage`, `DataPermissionPage`, `NotificationPolicyPage` | 详情 |
|
||
| `background: #f8f8f8` | `NotificationPolicyPage`, `AppLayout` | 预览盒 / 主内容区 |
|
||
| `border: 1px solid #ebeef5` | `NotificationPolicyPage`, `AppLayout` | 边框 |
|
||
|
||
> [!NOTE]
|
||
> 这些颜色值实际上都对应 Element Plus 的设计变量(`--el-text-color-regular`, `--el-text-color-secondary` 等),应该改用 CSS 变量引用。
|
||
|
||
---
|
||
|
||
## 三、已有组件拆分的一致性
|
||
|
||
| 模块 | 是否拆分子组件 | 子组件数 | 评价 |
|
||
|------|--------------|---------|------|
|
||
| MeetingPage | ✅ 已拆分 | 17 个 | 拆分合理,但子组件之间也存在细微风格差异 |
|
||
| AuditPage | ✅ 已拆分 | 3 个 | 基本一致 |
|
||
| ProjectPage | ⚠️ 部分拆分 | 1 个 (ProjectEditDrawer) | 主页面和抽屉风格有差异 |
|
||
| 其余 28 个页面 | 🔴 未拆分 | 0 个 | 全部为单文件巨型组件 |
|
||
|
||
> 最大的单文件组件 `MeetingPage.vue` 达 **156KB / ~4000+ 行**,虽然已拆分子组件但主文件仍然庞大。
|
||
|
||
---
|
||
|
||
## 四、表单验证方式不一致
|
||
|
||
| 方式 | 页面 | 问题 |
|
||
|------|------|------|
|
||
| 手动 `if` 检查 | `ExpertPage` (submitExpert, submitCard) | 6 个连续 `if + ElMessage.warning` 全手工 |
|
||
| `ElMessageBox.prompt` | `UserPage`, `RolePage` | 用于输入框式验证 |
|
||
| 无验证 | `DataPermissionPage`, `OperationsDashboardPage` | 直接提交,无需验证 |
|
||
| ❌ 未使用 `el-form` 的 `rules` 和 `ref.validate()` | **全部页面** | 没有一个页面使用 Element Plus 原生的表单验证 |
|
||
|
||
---
|
||
|
||
## 五、TODO 清单(按优先级排列)
|
||
|
||
### P0 — 基础设施(先行完成,为后续改造铺路)
|
||
|
||
- [ ] **[P0-01]** 创建全局 CSS 变量文件 `src/styles/variables.css`
|
||
定义间距 Token (`--spacing-xs: 4px`, `--spacing-sm: 8px`, `--spacing-md: 12px`, `--spacing-lg: 16px`, `--spacing-xl: 24px`)
|
||
|
||
- [ ] **[P0-02]** 创建全局工具类文件 `src/styles/utilities.css`
|
||
定义 `.mt-sm`, `.mt-md`, `.mb-md`, `.gap-sm` 等常用间距类
|
||
|
||
- [ ] **[P0-03]** 定义弹窗/抽屉尺寸常量 `src/constants/ui.ts`
|
||
```ts
|
||
export const DRAWER_SIZE = { sm: '480px', md: '640px', lg: '80%' }
|
||
export const DIALOG_WIDTH = { sm: '520px', md: '680px', lg: '860px' }
|
||
```
|
||
|
||
- [ ] **[P0-04]** 定义表单控件标准宽度常量
|
||
```ts
|
||
export const INPUT_WIDTH = { xs: '100px', sm: '160px', md: '220px', lg: '280px', full: '100%' }
|
||
```
|
||
|
||
---
|
||
|
||
### P1 — 公共组件封装
|
||
|
||
- [ ] **[P1-01]** 封装 `<PageContainer>` 组件
|
||
统一 `<el-card>` + header slot + 标准 padding/shadow 行为
|
||
|
||
- [ ] **[P1-02]** 封装 `<QueryToolbar>` 组件
|
||
统一搜索栏布局、按钮排列方式、与下方表格的间距
|
||
|
||
- [ ] **[P1-03]** 封装 `<ActionButtons>` 组件
|
||
统一操作列按钮的间距和视觉风格
|
||
|
||
- [ ] **[P1-04]** 封装 `<SectionTitle>` 组件
|
||
替换 `<div style="font-weight: 600; margin-bottom: 8px">` 这类重复标题
|
||
|
||
---
|
||
|
||
### P2 — 各页面样式迁移(消灭内联 style)
|
||
|
||
- [ ] **[P2-01]** `UserPage.vue` — 移除 4 处内联 style,改用工具类;select/input 改用标准宽度
|
||
- [ ] **[P2-02]** `RolePage.vue` — 移除 5 处内联 style(margin-top, margin-right, margin-bottom)
|
||
- [ ] **[P2-03]** `ExpertPage.vue` — 移除 8 处内联 style(color, margin-left, width, border-radius)
|
||
- [ ] **[P2-04]** `DataPermissionPage.vue` — 统一嵌套 card shadow 规则、移除内联 margin/line-height
|
||
- [ ] **[P2-05]** `FinancePage.vue` — 移除 4 处内联 style(width, font-weight, margin)
|
||
- [ ] **[P2-06]** `ObservabilityPage.vue` — 移除 8 处内联 style(margin-top, width, font-weight)
|
||
- [ ] **[P2-07]** `NotificationPolicyPage.vue` — 移除 10+ 处内联 style,`.preview-box` 迁移到全局
|
||
- [ ] **[P2-08]** `PlatformSessionPage.vue` — 统一 4 个 select/input 的宽度规格
|
||
- [ ] **[P2-09]** `PlatformRolePage.vue` — 移除 4 处内联 style(margin-right, margin-bottom, font-weight)
|
||
- [ ] **[P2-10]** `PlatformMenuPage.vue` — 移除 3 处内联 style(margin-top, margin-right)
|
||
- [ ] **[P2-11]** `OperationsDashboardPage.vue` — 移除 1 处内联 style(margin-bottom)
|
||
- [ ] **[P2-12]** `PermissionPage.vue` / `PlatformPermissionPage.vue` — 统一 margin-top 值
|
||
- [ ] **[P2-13]** `TemplatePage.vue` — 移除 7 处内联 style(width, margin-top, margin-left)
|
||
- [ ] **[P2-14]** `TenantPage.vue` — 移除 3 处内联 style(object-fit, margin-bottom, color)
|
||
- [ ] **[P2-15]** `InAppNotificationPage.vue` — 移除 1 处内联 style(margin-bottom)
|
||
- [ ] **[P2-16]** `AuditQueryToolbar.vue` — 移除 3 处内联 style(margin-left, margin-bottom, color)
|
||
|
||
---
|
||
|
||
### P3 — 弹窗/抽屉标准化
|
||
|
||
- [ ] **[P3-01]** 全部 Drawer 改用 `DRAWER_SIZE` 常量(统一归档到 sm/md/lg)
|
||
- [ ] **[P3-02]** 全部 Dialog 改用 `DIALOG_WIDTH` 常量(统一归档到 sm/md/lg)
|
||
- [ ] **[P3-03]** 统一 Drawer 的 `destroy-on-close` 行为(目前部分有、部分无)
|
||
- [ ] **[P3-04]** 统一 footer 按钮排列方式(目前 [取消 + 确定] vs [关闭 + 保存] 命名不统一)
|
||
|
||
---
|
||
|
||
### P4 — 颜色与硬编码值替换
|
||
|
||
- [ ] **[P4-01]** `#606266` → `var(--el-text-color-regular)` (5 处)
|
||
- [ ] **[P4-02]** `#909399` → `var(--el-text-color-secondary)` (4 处)
|
||
- [ ] **[P4-03]** `#f8f8f8` → `var(--el-fill-color-lighter)` (2 处)
|
||
- [ ] **[P4-04]** `#ebeef5` → `var(--el-border-color-lighter)` (2 处)
|
||
- [ ] **[P4-05]** `#c0c4cc` → `var(--el-text-color-placeholder)` (AppLayout scrollbar)
|
||
|
||
---
|
||
|
||
### P5 — 表单验证标准化
|
||
|
||
- [ ] **[P5-01]** `ExpertPage.vue` — 用 `el-form` 的 `rules` + `formRef.validate()` 替换手动 if 校验
|
||
- [ ] **[P5-02]** `UserPage.vue` — 添加 `rules` 验证(姓名、手机号必填等)
|
||
- [ ] **[P5-03]** `NotificationPolicyPage.vue` — 添加表单 rules
|
||
- [ ] **[P5-04]** 所有有 required 标记的表单字段实际加上 `rules` 规则
|
||
|
||
---
|
||
|
||
### P6 — 长期优化
|
||
|
||
- [ ] **[P6-01]** `MeetingPage.vue` (156KB) 进一步拆分,将 dialog/drawer 独立为子组件
|
||
- [ ] **[P6-02]** 建立 ESLint 规则禁止新增内联 `style=""`
|
||
- [ ] **[P6-03]** 统一 `<el-form>` 的 `label-width` 值(当前 `70px / 90px / 100px / 110px` 四种)
|
||
- [ ] **[P6-04]** 抽取公共的 `statusFormatter` / `toZhStatus` 为 composable
|
||
- [ ] **[P6-05]** 考虑引入全局暗色模式支持
|
||
|
||
---
|
||
|
||
## 六、改造优先级路线图
|
||
|
||
```mermaid
|
||
gantt
|
||
title 前端风格统一改造路线图
|
||
dateFormat YYYY-MM-DD
|
||
section P0 基础设施
|
||
CSS 变量 & 工具类 :p0, 2026-04-07, 2d
|
||
尺寸/宽度常量 :after p0, 1d
|
||
section P1 公共组件
|
||
PageContainer :p1a, after p0, 2d
|
||
QueryToolbar :p1b, after p1a, 2d
|
||
SectionTitle & ActionButtons :after p1b, 1d
|
||
section P2-P4 样式迁移
|
||
高频页面 (User/Role/Expert) :p2, after p1b, 3d
|
||
中频页面 (Finance/Audit/Obs) :after p2, 3d
|
||
低频页面 (Platform/Template) :after p2, 3d
|
||
颜色硬编码替换 :after p2, 1d
|
||
section P5-P6 深度优化
|
||
表单验证标准化 :p5, after p2, 3d
|
||
MeetingPage 拆分 :after p5, 3d
|
||
ESLint 规则 & 暗色模式 :after p5, 2d
|
||
```
|
||
|
||
> [!TIP]
|
||
> 建议按 **P0 → P1 → P2(高频页面优先)→ P3 → P4 → P5 → P6** 的顺序执行,预计总工作量约 **15-20 人日**。
|