0d84b07f68
CI / docker (push) Successful in 1m58s
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。 Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。 Co-authored-by: Cursor <cursoragent@cursor.com>
2014 lines
56 KiB
Markdown
2014 lines
56 KiB
Markdown
# UI 设计规范
|
||
|
||
> 适用范围:基于 Vue 3 + Element Plus 2.x 的后台管理系统 Web 页面
|
||
> **本文档为唯一设计规范**:色彩、间距、布局、组件样式、全局 CSS 均定义于本文件(§6 分模块说明 + §8.9 完整 CSS)。项目实施时从本文档复制代码即可,不依赖其他设计资产文件。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [页面整体风格](#1-页面整体风格)
|
||
2. [布局标准](#2-布局标准)
|
||
3. [字体标准](#3-字体标准)
|
||
4. [颜色规范](#4-颜色规范)
|
||
5. [图标使用规范](#5-图标使用规范)
|
||
6. [组件与页面标准样式](#6-组件与页面标准样式)
|
||
7. [交互原则](#7-交互原则)
|
||
8. [附录:复刻指南](#8-附录复刻指南)
|
||
|
||
---
|
||
|
||
## 1. 页面整体风格
|
||
|
||
### 1.1 设计定位
|
||
|
||
本规范定义一套**浅色、克制、数据导向**的后台界面风格。视觉语言强调可读性与操作效率,避免装饰性元素喧宾夺主。
|
||
|
||
| 维度 | 规范 |
|
||
|------|------|
|
||
| 整体调性 | 专业、冷静、工具感;类似现代 SaaS 控制台 |
|
||
| 色彩策略 | 大面积中性灰白底 + 青绿品牌色点缀 |
|
||
| 层次表达 | 靠**字号阶梯**与**灰度阶梯**区分,不靠重阴影 |
|
||
| 容器形态 | 白底细边框卡片(`1px` + `8px` 圆角),无浮起阴影 |
|
||
| 信息密度 | 列表/表格偏紧凑(`size="small"`),表单区留白充分 |
|
||
| 技术底座 | Element Plus 组件 + 全局 CSS 变量覆盖 |
|
||
|
||
### 1.2 核心设计原则
|
||
|
||
| 原则 | 说明 |
|
||
|------|------|
|
||
| **浅色优先** | 页面背景 `#f4f4f5`,内容区白卡片;禁止深色全屏侧栏 |
|
||
| **克制用色** | 主色青绿仅用于品牌、主按钮、导航激活态、关键正向状态 |
|
||
| **细边框卡片** | 白底 + `1px` 浅灰边框 + `8px` 圆角,禁止大面积 box-shadow |
|
||
| **8px 间距节奏** | 所有间距使用间距 Token(4px 仅用于极紧凑场景) |
|
||
| **层次靠字号与灰度** | 标题 `#18181b`,正文 `#3f3f46`,辅助 `#71717a` |
|
||
| **组件复用优先** | 使用 `PageHeader`、`AppCard`、`StatCard` 等标准壳层,禁止页面内自建容器体系 |
|
||
| **国际化友好** | 文案走 i18n;布局预留中英文长度差异(按钮组 `flex-wrap`) |
|
||
|
||
### 1.3 视觉示意
|
||
|
||
```
|
||
┌──────────────┬────────────────────────────────────┐
|
||
│ Sidebar │ Topbar(52px,白底,底部分割线) │
|
||
│ 240px ├────────────────────────────────────┤
|
||
│ 半透明浅灰 │ Main Content(padding 24px) │
|
||
│ 品牌区+导航 │ .page(max-width 1400px) │
|
||
│ │ PageHeader → StatGrid → AppCard │
|
||
└──────────────┴────────────────────────────────────┘
|
||
```
|
||
|
||
登录页为独立全屏居中布局,无侧栏与顶栏。
|
||
|
||
---
|
||
|
||
## 2. 布局标准
|
||
|
||
### 2.1 栅格系统
|
||
|
||
#### PC 端(≥1025px)
|
||
|
||
| 项目 | 规范 |
|
||
|------|------|
|
||
| 基准栅格 | **12 列**逻辑栅格(内容区 `max-width: 1400px` 内划分) |
|
||
| 列间距(gutter) | `16px`(`--space-4`) |
|
||
| 页面水平边距 | 主内容区内边距 `24px`(`--space-6`) |
|
||
| 典型列分配 | 统计卡 4×3 列;双栏内容 `1.4fr : 1fr` 或 `1fr : 1fr`;三栏等分 `1fr × 3` |
|
||
|
||
常用网格类:
|
||
|
||
| 类名 | PC 列数 | 说明 |
|
||
|------|---------|------|
|
||
| `.stat-grid` | 4 列 | 概览指标卡 |
|
||
| `.content-grid--2` | 2 列等宽 | 并列卡片 |
|
||
| `.content-grid--2-1` | 2 列(约 7:5) | 主内容 + 侧信息 |
|
||
| `.content-grid--3` | 3 列等宽 | 三列面板(按需扩展) |
|
||
|
||
#### 平板(641px – 1024px)
|
||
|
||
| 项目 | 规范 |
|
||
|------|------|
|
||
| 有效栅格 | **8 列**逻辑栅格 |
|
||
| `.stat-grid` | 2 列 |
|
||
| `.content-grid--*` | 全部折叠为 **1 列** |
|
||
| 主内容 padding | 保持 `24px` |
|
||
|
||
#### 移动端(≤640px)
|
||
|
||
| 项目 | 规范 |
|
||
|------|------|
|
||
| 有效栅格 | **4 列**逻辑栅格 |
|
||
| `.stat-grid` | 1 列 |
|
||
| `.page-header` | 纵向堆叠(标题在上,操作在下) |
|
||
| 主内容 padding | `16px`(`--space-4`) |
|
||
| 底部 Tab(若启用) | 固定底部,高度 `56px`,图标 `20px` + 文字 `10px` |
|
||
|
||
> 默认以 PC 为主、移动端通过响应式折叠适配;若启用底部 Tab 导航,须使用本节尺寸,不得自定义高度。
|
||
|
||
### 2.2 间距体系(8px 基准)
|
||
|
||
**硬性规则:页面级、组件级外边距与内边距必须使用间距 Token,禁止出现 5px、7px、10px、15px 等非体系数值。**
|
||
|
||
| Token | 值 | 是否 8 的倍数 | 典型用途 |
|
||
|-------|-----|---------------|----------|
|
||
| `--space-1` | 4px | ½×8,仅紧凑场景 | 标题与副标题间距、行内 code 水平 padding |
|
||
| `--space-2` | 8px | ✓ | 按钮组 gap、导航项内边距、表格单元格垂直 padding |
|
||
| `--space-3` | 12px | — | 列表项间距、卡片底栏提示 |
|
||
| `--space-4` | 16px | ✓ | 卡片间距、工具栏下边距、移动端页面 padding |
|
||
| `--space-5` | 20px | — | 卡片水平内边距、统计卡内边距 |
|
||
| `--space-6` | 24px | ✓ | 页头下边距、主内容 padding、区块分隔 |
|
||
| `--space-8` | 32px | ✓ | 登录卡片内边距、大区块分隔 |
|
||
|
||
**垂直节奏(页面级)**
|
||
|
||
- `.page-header` 下方:`24px`
|
||
- `.stat-grid`、`.content-grid` 下方:`24px`
|
||
- 相邻 `.app-card` 间距:`16px`;页面最后一个卡片无底边距
|
||
|
||
**已知例外(保留,勿改)**
|
||
|
||
| 位置 | 值 | 原因 |
|
||
|------|-----|------|
|
||
| `.form-hint` 的 `margin-top` | `6px` | 紧贴表单项控件底部,视觉上等同 8px 体系 |
|
||
|
||
`--primary-hover` 已定义但未写独立 CSS 规则,主按钮 hover 由 Element Plus 通过 `--el-color-primary-dark-2` 自动处理。
|
||
|
||
### 2.3 布局组件尺寸
|
||
|
||
| 组件 | 尺寸 | 其他规则 |
|
||
|------|------|----------|
|
||
| **侧栏** `.sidebar` | 宽 `240px`(`15rem`) | 右边框 `1px`;背景半透明 + `backdrop-filter: blur(8px)` |
|
||
| **侧栏品牌区** `.sidebar-brand` | 高随内容;padding `20px 16px` | 品牌图标 `32×32px` |
|
||
| **导航项** `.nav-item` | 高随内容;padding `8px 12px`;margin 左右 `8px` | 圆角 `8px`;图标 `16px` |
|
||
| **导航分组标签** `.nav-group-label` | padding `16px 16px 8px` | 字号 `12px`,全大写 |
|
||
| **顶栏** `.topbar` | 高 **`52px`** | 左右 padding `24px`;白底 + 底边框 |
|
||
| **主内容** `.main-content` | flex 填满剩余高度 | padding `24px`(移动端 `16px`);纵向滚动 |
|
||
| **页面容器** `.page` | max-width **`1400px`** | 无水平居中强制(随主内容左对齐) |
|
||
| **底部 Tab**(预留) | 高 **`56px`** | 图标 `20px`,标签 `10px`,最多 5 项 |
|
||
| **登录卡片** `.login-card` | max-width 外层 `400px`;内边距 `32px` | 品牌图标 `48×48px`,圆角 `12px` |
|
||
|
||
### 2.4 断点
|
||
|
||
| 断点 | 媒体查询 | 主要变化 |
|
||
|------|----------|----------|
|
||
| 平板 | `max-width: 1024px` | 统计 2 列;内容网格单列 |
|
||
| 手机 | `max-width: 640px` | 统计 1 列;页头纵向;主内容 16px padding |
|
||
|
||
### 2.5 布局结构(ASCII)
|
||
|
||
```
|
||
PC (≥1025px)
|
||
┌─ sidebar 240px ─┬──────────── main-area ────────────────────┐
|
||
│ [brand 32px] │ topbar 52px │
|
||
│ [nav × N] ├───────────────────────────────────────────┤
|
||
│ │ padding 24px │
|
||
│ │ ┌─ page max 1400px ─────────────────┐ │
|
||
│ │ │ page-header │ │
|
||
│ │ │ stat-grid (4 col, gap 16) │ │
|
||
│ │ │ app-card │ │
|
||
│ │ └────────────────────────────────────┘ │
|
||
└─────────────────┴───────────────────────────────────────────┘
|
||
|
||
Mobile (≤640px)
|
||
┌──────────────── main-area 100% ────────────────┐
|
||
│ topbar 52px │
|
||
├────────────────────────────────────────────────┤
|
||
│ padding 16px │
|
||
│ page-header (column) │
|
||
│ stat-grid (1 col) │
|
||
│ app-card │
|
||
├────────────────────────────────────────────────┤
|
||
│ [optional bottom-tab 56px] │
|
||
└────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 字体标准
|
||
|
||
### 3.1 字体族
|
||
|
||
| 场景 | 字体栈 |
|
||
|------|--------|
|
||
| **中文界面** | `"PingFang SC"`, `"Microsoft YaHei"`, `"Noto Sans SC"`, `"Helvetica Neue"`, `Arial`, `sans-serif` |
|
||
| **英文界面** | `ui-sans-serif`, `system-ui`, `-apple-system`, `"Segoe UI"`, `Roboto`, `"Helvetica Neue"`, `Arial`, `sans-serif` |
|
||
| **合并栈(推荐默认)** | `ui-sans-serif`, `system-ui`, `-apple-system`, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif` |
|
||
| **等宽(代码/文件名)** | `ui-monospace`, `SFMono-Regular`, `Menlo`, `Monaco`, `Consolas`, monospace` |
|
||
|
||
> 中英文共用同一 sans 栈:西文优先 system 字体,CJK 由系统自动 fallback 至苹方/微软雅黑。禁止为中文单独引入 Web Font,保持加载性能。
|
||
|
||
### 3.2 字号阶梯
|
||
|
||
根元素 `font-size: 14px`(`1rem = 14px`)。所有字号使用 `rem` 相对根字号。
|
||
|
||
| 层级 | 类名 / 场景 | 字号 | 字重 | 行高 | 颜色 |
|
||
|------|-------------|------|------|------|------|
|
||
| 页面标题 | `.page-title` | `1.5rem`(21px) | 600 | 默认 | `--foreground` |
|
||
| 登录品牌标题 | `.page-title`(登录页) | 同上 | 600 | 默认 | `--foreground` |
|
||
| 卡片标题 | `.app-card__title` | `0.9375rem`(13px) | 600 | 默认 | `--foreground` |
|
||
| 品牌名 | `.brand-name` | `1.125rem`(15.75px) | 600 | 默认 | `--foreground` |
|
||
| 统计数值 | `.stat-card__value` | `1.75rem`(24.5px) | 600 | 默认 | `--foreground` |
|
||
| 正文 / 导航 / 顶栏 | `.nav-item`, `.topbar-title`, 表格正文 | `0.875rem`(12.25px) | 400–500 | 1.4(表格) | `--el-text-color-regular` 或 `--muted-foreground` |
|
||
| 描述 / 标签说明 | `.page-desc`, `.stat-card__label` | `0.875rem` | 400 | 默认 | `--muted-foreground` |
|
||
| 表头 / 辅助 | 表头 `th`, `.form-hint`, `.nav-group-label` | `0.75rem`(10.5px) | 600(表头)/ 400 | 默认 | `--muted-foreground` |
|
||
| 极小说明 | `.form-hint`, `code.inline-code` | `0.75rem` | 400 | 默认 | `--muted-foreground` |
|
||
| 等宽数据 | `.kv-block code`, `.meta-list` | `0.8125rem` | 400 | 默认 | 继承 |
|
||
|
||
### 3.3 字重与样式
|
||
|
||
| 样式 | 使用场景 | 禁止场景 |
|
||
|------|----------|----------|
|
||
| **600(Semibold)** | 页面标题、卡片标题、表头、导航激活态、分组标题 `.section-title` | 大段正文 |
|
||
| **500(Medium)** | 导航默认态、顶栏次要文字 | — |
|
||
| **400(Regular)** | 正文、描述、表单标签 | — |
|
||
| **加粗 `<strong>`** | 行内强调少量关键词 | 整段文字 |
|
||
| **斜体 `<em>`** | 极少使用;仅引用或术语首次出现 | 按钮、导航、表头 |
|
||
| **`letter-spacing: -0.02em`** | 仅 `.page-title` | 其他文字 |
|
||
| **`text-transform: uppercase`** | 仅 `.nav-group-label` | 其他文字 |
|
||
|
||
### 3.4 Element Plus 表单标签
|
||
|
||
| 场景 | `label-width` |
|
||
|------|---------------|
|
||
| 简单 CRUD 对话框 | `100px` |
|
||
| 字段名较长 | `110px` |
|
||
| 系统设置长表单 | `120px` |
|
||
| 嵌套子表单 | `90px` |
|
||
| 登录页 | `size="large"`(EP 默认大号控件) |
|
||
|
||
---
|
||
|
||
## 4. 颜色规范
|
||
|
||
### 4.1 色彩角色一览
|
||
|
||
```
|
||
品牌层:primary 系列(操作引导)
|
||
中性层:background / card / muted / border(结构)
|
||
语义层:success / warning / danger / info(状态)
|
||
文本层:foreground / regular / muted-foreground(可读性)
|
||
```
|
||
|
||
### 4.2 完整色板
|
||
|
||
#### 品牌色
|
||
|
||
| Token | 色值 | 用途 |
|
||
|-------|------|------|
|
||
| `--primary` | `#0f7a62` | 主按钮、品牌图标底、导航激活文字、链接强调 |
|
||
| `--primary-hover` | `#0d6b56` | 主按钮 hover |
|
||
| `--primary-foreground` | `#fafafa` | 主色背景上的文字/图标 |
|
||
| `--el-color-primary-light-3` | `#4da08c` | EP 组件浅色变体 |
|
||
| `--el-color-primary-light-5` | `#76b8a8` | — |
|
||
| `--el-color-primary-light-7` | `#a0d0c4` | — |
|
||
| `--el-color-primary-light-8` | `#b8dcd2` | — |
|
||
| `--el-color-primary-light-9` | `#d0e8e1` | 浅色背景提示 |
|
||
| `--el-color-primary-dark-2` | `#0c624e` | 主色按下/深色态 |
|
||
|
||
#### 中性色
|
||
|
||
| Token | 色值 | 用途 |
|
||
|-------|------|------|
|
||
| `--background` | `#f4f4f5` | 页面底色、登录页背景 |
|
||
| `--foreground` | `#18181b` | 主标题、强强调正文 |
|
||
| `--card` | `#ffffff` | 卡片、顶栏、弹窗内容区背景 |
|
||
| `--card-foreground` | `#18181b` | 卡片上主文字 |
|
||
| `--muted` | `#f4f4f5` | 表头背景、内嵌块、行内 code 底 |
|
||
| `--muted-foreground` | `#71717a` | 描述、表头文字、顶栏标题、占位性文字 |
|
||
| `--border` | `#e4e4e7` | 卡片边框、分割线、顶栏底边 |
|
||
| `--input` | `#e4e4e7` | 输入框边框(与 border 一致) |
|
||
|
||
#### 侧栏专用
|
||
|
||
| Token | 色值 | 用途 |
|
||
|-------|------|------|
|
||
| `--sidebar` | `rgba(250, 250, 250, 0.85)` | 侧栏背景(半透明) |
|
||
| `--sidebar-foreground` | `#18181b` | 侧栏主文字 |
|
||
| `--sidebar-accent` | `#f4f4f5` | 导航 hover / active 背景 |
|
||
| `--sidebar-border` | `#e4e4e7` | 侧栏右边框 |
|
||
|
||
#### 语义色
|
||
|
||
| Token / 类名 | 色值 | 用途 |
|
||
|--------------|------|------|
|
||
| `--success` | `#047857` | 成功状态(与 EP success 同步) |
|
||
| `--warning` | `#b45309` | 警告状态 |
|
||
| `--danger` / `--destructive` | `#dc2626` | 危险、错误、删除 |
|
||
| `--info` | `#a1a1aa` | 中性、禁用、分类标签 |
|
||
| `--el-color-success` | `#047857` | `el-tag type="success"` |
|
||
| `--el-color-warning` | `#b45309` | `el-tag type="warning"` |
|
||
| `--el-color-danger` | `#dc2626` | `el-tag type="danger"` |
|
||
| `--el-color-info` | `#a1a1aa` | `el-tag type="info"` |
|
||
| `.status-good` | `#047857` | 数值/状态优良 |
|
||
| `.status-warn` | `#b45309` | 数值/状态偏高 |
|
||
| `.status-bad` | `#dc2626` | 数值/状态很差 |
|
||
| `.status-muted` | `--muted-foreground` | 无数据/超时/不可用 |
|
||
| `.meta-list__ok` | `var(--success)` | 列表项就绪 |
|
||
| `.meta-list__warn` | `var(--warning)` | 列表项缺失/警告 |
|
||
|
||
#### 文本色(Element Plus 对齐)
|
||
|
||
| Token | 色值 | 用途 |
|
||
|-------|------|------|
|
||
| `--el-text-color-primary` | `#18181b` | EP 主要文字 |
|
||
| `--el-text-color-regular` | `#3f3f46` | 表格正文 |
|
||
| `--el-text-color-secondary` | `#71717a` | EP 次要文字 |
|
||
|
||
### 4.3 颜色使用规范
|
||
|
||
| 规则 | 说明 |
|
||
|------|------|
|
||
| **禁止硬编码色值** | 新样式必须使用 CSS 变量 |
|
||
| **主色克制** | 单屏主色面积不超过 15%;除主按钮与激活导航外不大面积铺主色 |
|
||
| **状态色统一** | 成功/失败/禁用一律用 `el-tag` 语义 type,不自创色块 |
|
||
| **边框优先于阴影** | 容器分隔用 `1px solid var(--border)`,不用阴影 |
|
||
| **危险操作** | 破坏性操作用 `type="danger"` + `link`(表格行内)或二次确认弹框 |
|
||
| **图表** | 遵循 [§6.16](#616-图表规范echarts) 配色与 grid 配置 |
|
||
|
||
### 4.4 圆角
|
||
|
||
| Token | 值 | 用途 |
|
||
|-------|-----|------|
|
||
| `--radius` | `0.5rem`(8px) | 卡片、按钮、输入框、导航项、内嵌块 |
|
||
| 登录品牌图标 | `12px` | 仅 `.login-brand-icon`,略大以强化品牌 |
|
||
|
||
---
|
||
|
||
## 5. 图标使用规范
|
||
|
||
### 5.1 图标体系
|
||
|
||
| 类型 | 来源 | 说明 |
|
||
|------|------|------|
|
||
| **品牌图标** | 项目自有 Logo(自行实现) | 产品标识;仅用于侧栏 `.brand-icon`、登录页 `.login-brand-icon`;**各项目图形不同,本文档不提供参考实现** |
|
||
| **功能图标** | `@element-plus/icons-vue` | 导航、操作提示;通过 `<el-icon>` 包裹 |
|
||
| **状态** | 优先 `el-tag` 文字 | 不以图标替代明确状态文案 |
|
||
|
||
### 5.2 尺寸规范
|
||
|
||
| 场景 | 尺寸 |
|
||
|------|------|
|
||
| 侧栏品牌区 Logo | `20px` |
|
||
| 登录页品牌 Logo | `24px` |
|
||
| 侧栏导航图标 | `16px`(`<el-icon :size="16">`) |
|
||
| 按钮内图标(若使用) | `14px` |
|
||
| 底部 Tab(预留) | `20px` |
|
||
|
||
### 5.3 常见场景图标推荐
|
||
|
||
图标库统一使用 `@element-plus/icons-vue`(Outlined 线型)。下表为常见导航/功能场景的推荐映射,各项目可按业务替换,但须保持风格一致。
|
||
|
||
| 场景 | 推荐图标 | 含义 |
|
||
|------|----------|------|
|
||
| 首页 / 仪表盘 | `Odometer`、`HomeFilled` | 总览、监控 |
|
||
| 列表 / 规则 / 配置项 | `List`、`Menu` | 条目化管理 |
|
||
| 连接 / 网络 | `Connection`、`Share` | 链路、拓扑 |
|
||
| 外部关联 / 链接 | `Link` | 外部数据源 |
|
||
| 切换 / 路由 / 策略 | `Switch` | 分流、调度 |
|
||
| 日志 / 文档 | `Document`、`Tickets` | 记录、流水 |
|
||
| 用户 / 权限 | `User`、`Lock` | 身份、安全 |
|
||
| 系统设置 | `Setting` | 全局配置 |
|
||
| 新建 | `Plus` | 创建资源 |
|
||
| 编辑 | `Edit` | 修改 |
|
||
| 删除 | `Delete` | 移除(配合 danger 按钮,不单用图标) |
|
||
| 刷新 | `Refresh` | 重新加载 |
|
||
| 导入 / 导出 | `Upload`、`Download` | 数据交换 |
|
||
|
||
### 5.4 使用原则
|
||
|
||
| 原则 | 说明 |
|
||
|------|------|
|
||
| **导航必带图标** | 侧栏每项左侧固定 `16px` 图标,增强扫读性 |
|
||
| **颜色跟随文字** | 图标 `currentColor`,不单独设色 |
|
||
| **禁止混用风格** | 统一 Outlined 线型图标(Element Plus Icons) |
|
||
| **操作按钮** | 主操作优先文字按钮;图标-only 按钮需 `aria-label` |
|
||
| **品牌图标不外借** | Logo 仅用于品牌区,不用于普通操作按钮 |
|
||
|
||
---
|
||
|
||
## 6. 组件与页面标准样式
|
||
|
||
> 本节 6.1–6.14 为**分模块说明**(便于查阅);**合并后的完整全局 CSS 见 [§8.9](#89-完整全局样式一键复制)**。两处内容须保持一致。
|
||
|
||
### 6.0 技术栈与引入
|
||
|
||
| 项目 | 要求 |
|
||
|------|------|
|
||
| Vue | `^3.4` |
|
||
| Element Plus | `^2.4`(2.x 均可,须使用 CSS 变量主题) |
|
||
| 图标 | `@element-plus/icons-vue` |
|
||
| 图表(可选) | `echarts` `^5` |
|
||
|
||
**`main.ts` 引入顺序(顺序不可颠倒):**
|
||
|
||
```ts
|
||
import { createApp } from 'vue'
|
||
import ElementPlus from 'element-plus'
|
||
import 'element-plus/dist/index.css' // ① 先 Element Plus 基础样式
|
||
import './styles/global.css' // ② 再将本文档 §8.9 复制为全局样式文件后引入
|
||
|
||
const app = createApp(App)
|
||
app.use(ElementPlus) // ③ 全量注册(或按需,但变量须生效)
|
||
app.mount('#app')
|
||
```
|
||
|
||
**依赖说明:**
|
||
|
||
- 全局样式**仅来源于本文档 §8.9**,文件名与存放路径由项目自定(如 `global.css`、`app.css`)。
|
||
- 按钮、输入框、弹框、Tabs 等**无需逐组件写 CSS**,通过 `:root` 中的 `--el-*` 变量与品牌色对齐。
|
||
- 表格、卡片容器、布局壳层使用本文档自定义 class,须与 §8.1 壳层 DOM 结构配合。
|
||
|
||
### 6.1 设计 Token 与基础重置
|
||
|
||
```css
|
||
:root {
|
||
--background: #f4f4f5;
|
||
--foreground: #18181b;
|
||
--card: #ffffff;
|
||
--card-foreground: #18181b;
|
||
--primary: #0f7a62;
|
||
--primary-hover: #0d6b56;
|
||
--primary-foreground: #fafafa;
|
||
--muted: #f4f4f5;
|
||
--muted-foreground: #71717a;
|
||
--border: #e4e4e7;
|
||
--input: #e4e4e7;
|
||
--destructive: #dc2626;
|
||
--success: #047857;
|
||
--warning: #b45309;
|
||
--danger: #dc2626;
|
||
--info: #a1a1aa;
|
||
--sidebar: rgba(250, 250, 250, 0.85);
|
||
--sidebar-foreground: #18181b;
|
||
--sidebar-accent: #f4f4f5;
|
||
--sidebar-border: #e4e4e7;
|
||
--sidebar-width: 15rem;
|
||
--radius: 0.5rem;
|
||
--space-1: 4px;
|
||
--space-2: 8px;
|
||
--space-3: 12px;
|
||
--space-4: 16px;
|
||
--space-5: 20px;
|
||
--space-6: 24px;
|
||
--space-8: 32px;
|
||
|
||
--el-color-primary: #0f7a62;
|
||
--el-color-primary-light-3: #4da08c;
|
||
--el-color-primary-light-5: #76b8a8;
|
||
--el-color-primary-light-7: #a0d0c4;
|
||
--el-color-primary-light-8: #b8dcd2;
|
||
--el-color-primary-light-9: #d0e8e1;
|
||
--el-color-primary-dark-2: #0c624e;
|
||
--el-color-success: #047857;
|
||
--el-color-warning: #b45309;
|
||
--el-color-danger: #dc2626;
|
||
--el-color-info: #a1a1aa;
|
||
--el-border-radius-base: 0.5rem;
|
||
--el-border-color: #e4e4e7;
|
||
--el-bg-color-page: #f4f4f5;
|
||
--el-text-color-primary: #18181b;
|
||
--el-text-color-regular: #3f3f46;
|
||
--el-text-color-secondary: #71717a;
|
||
--el-font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
html, body, #app {
|
||
margin: 0;
|
||
padding: 0;
|
||
height: 100%;
|
||
font-size: 14px;
|
||
font-family: var(--el-font-family);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
}
|
||
```
|
||
|
||
### 6.2 布局壳层(侧栏 / 顶栏 / 主内容)
|
||
|
||
```css
|
||
.layout {
|
||
display: flex;
|
||
height: 100vh;
|
||
}
|
||
|
||
.sidebar {
|
||
width: var(--sidebar-width);
|
||
flex-shrink: 0;
|
||
background: var(--sidebar);
|
||
border-right: 1px solid var(--sidebar-border);
|
||
backdrop-filter: blur(8px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.sidebar-brand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-3);
|
||
padding: var(--space-5) var(--space-4);
|
||
}
|
||
|
||
.brand-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: var(--radius);
|
||
background: var(--primary);
|
||
color: var(--primary-foreground);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.brand-name {
|
||
font-size: 1.125rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.nav-group-label {
|
||
font-size: 0.75rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
color: var(--muted-foreground);
|
||
padding: var(--space-4) var(--space-4) var(--space-2);
|
||
}
|
||
|
||
.nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
padding: var(--space-2) var(--space-3);
|
||
margin: 0 var(--space-2);
|
||
border-radius: var(--radius);
|
||
color: var(--muted-foreground);
|
||
text-decoration: none;
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.nav-item:hover { background: var(--sidebar-accent); }
|
||
|
||
.nav-item.active {
|
||
background: var(--sidebar-accent);
|
||
color: var(--primary);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.main-area {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
}
|
||
|
||
.topbar {
|
||
height: 52px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 var(--space-6);
|
||
background: var(--card);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.topbar-title {
|
||
font-size: 0.875rem;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.main-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: var(--space-6);
|
||
}
|
||
```
|
||
|
||
### 6.3 页面头与网格
|
||
|
||
```css
|
||
.page {
|
||
max-width: 1400px;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
margin-bottom: var(--space-6);
|
||
gap: var(--space-4);
|
||
}
|
||
|
||
.page-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
flex-shrink: 0;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 1.5rem;
|
||
font-weight: 600;
|
||
letter-spacing: -0.02em;
|
||
margin: 0 0 var(--space-1);
|
||
}
|
||
|
||
.page-desc {
|
||
font-size: 0.875rem;
|
||
color: var(--muted-foreground);
|
||
margin: 0;
|
||
}
|
||
|
||
.stat-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: var(--space-4);
|
||
margin-bottom: var(--space-6);
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: var(--space-5);
|
||
}
|
||
|
||
.stat-card__label {
|
||
font-size: 0.875rem;
|
||
color: var(--muted-foreground);
|
||
margin-bottom: var(--space-2);
|
||
}
|
||
|
||
.stat-card__value {
|
||
font-size: 1.75rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.content-grid {
|
||
display: grid;
|
||
gap: var(--space-4);
|
||
margin-bottom: var(--space-6);
|
||
}
|
||
|
||
.content-grid--2-1 { grid-template-columns: 1.4fr 1fr; }
|
||
.content-grid--2 { grid-template-columns: 1fr 1fr; }
|
||
.content-grid--3 { grid-template-columns: repeat(3, 1fr); }
|
||
```
|
||
|
||
### 6.4 卡片与工具栏
|
||
|
||
```css
|
||
.app-card {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
.page > .app-card:last-child,
|
||
.page > .content-grid:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.app-card__header {
|
||
padding: var(--space-4) var(--space-5);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.app-card__title {
|
||
font-size: 0.9375rem;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
|
||
.app-card__body--padded { padding: var(--space-5); }
|
||
|
||
.card-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
padding: var(--space-4) var(--space-5) 0;
|
||
margin-bottom: var(--space-4);
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.card-toolbar--desc {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.card-pager {
|
||
padding: var(--space-4) var(--space-5);
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.card-footer-hint {
|
||
padding: 0 var(--space-5) var(--space-3);
|
||
margin: 0;
|
||
}
|
||
|
||
.card-footer-hint + .card-pager {
|
||
padding-top: var(--space-3);
|
||
}
|
||
|
||
.card-inset-block {
|
||
margin: 0 var(--space-5) var(--space-4);
|
||
padding: var(--space-3);
|
||
background: var(--muted);
|
||
border-radius: var(--radius);
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.tabs-bar {
|
||
padding: 0 var(--space-5);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.tabs-bar .el-tabs__header { margin-bottom: 0; }
|
||
|
||
.page-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
margin-bottom: var(--space-4);
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.inline-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.card-header-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: var(--space-4);
|
||
}
|
||
```
|
||
|
||
### 6.5 表单、间距工具类
|
||
|
||
```css
|
||
.form-hint {
|
||
margin-top: 6px;
|
||
font-size: 0.75rem;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.form-hint--flush { margin-top: 0; }
|
||
|
||
.u-mt-3 { margin-top: var(--space-3); }
|
||
.u-mt-4 { margin-top: var(--space-4); }
|
||
.u-mb-3 { margin-bottom: var(--space-3); }
|
||
.u-mb-4 { margin-bottom: var(--space-4); }
|
||
.u-mb-6 { margin-bottom: var(--space-6); }
|
||
.text-center { text-align: center; }
|
||
.w-full { width: 100%; }
|
||
|
||
code.inline-code {
|
||
font-size: 0.75rem;
|
||
padding: 0 var(--space-1);
|
||
background: var(--muted);
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
.section-title:not(:first-child) { margin-top: var(--space-6); }
|
||
|
||
.el-form .el-select { width: 100%; }
|
||
```
|
||
|
||
### 6.6 登录页
|
||
|
||
```css
|
||
.login-page {
|
||
min-height: 100vh;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--background);
|
||
padding: var(--space-6);
|
||
}
|
||
|
||
.login-wrap { max-width: 400px; width: 100%; text-align: center; }
|
||
|
||
.login-brand-icon {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 12px;
|
||
background: var(--primary);
|
||
color: var(--primary-foreground);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
.login-card {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: var(--space-8);
|
||
margin-top: var(--space-6);
|
||
text-align: left;
|
||
}
|
||
```
|
||
|
||
### 6.7 表格
|
||
|
||
```css
|
||
.app-card .el-table {
|
||
--el-table-header-bg-color: var(--muted);
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.app-card .el-table--small .el-table__cell {
|
||
padding: var(--space-2) var(--space-3);
|
||
}
|
||
|
||
.app-card .el-table--small th.el-table__cell {
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.app-card .el-table--small td.el-table__cell {
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
|
||
.app-card .el-table--small .el-table__row {
|
||
height: 40px;
|
||
}
|
||
|
||
.app-card .el-table .el-button.is-link {
|
||
padding: 0;
|
||
height: auto;
|
||
}
|
||
|
||
.app-card .el-table .cell {
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.table-scroll--sm { max-height: 320px; }
|
||
.table-scroll--md { max-height: 360px; }
|
||
.table-scroll--lg { max-height: 480px; }
|
||
|
||
.card-pager .el-pagination {
|
||
font-size: 0.875rem;
|
||
}
|
||
```
|
||
|
||
**表格列宽约定**
|
||
|
||
| 列类型 | 宽度 |
|
||
|--------|------|
|
||
| 时间 | `168px` |
|
||
| 状态 / 布尔 | `80–100px` |
|
||
| 操作 | `120–160px`,`fixed="right"` |
|
||
| 主文本 | `min-width`,不设固定宽 |
|
||
|
||
### 6.8 工具栏控件宽度
|
||
|
||
```css
|
||
.toolbar-select { width: 120px; }
|
||
.toolbar-select--country { width: 160px; }
|
||
.toolbar-select--wide { width: 280px; }
|
||
.toolbar-input { width: 200px; }
|
||
.toolbar-input--wide { width: 260px; }
|
||
.toolbar-input--grow { flex: 1; min-width: 200px; }
|
||
.table-cell-select { width: 100%; min-width: 200px; }
|
||
|
||
.card-toolbar .toolbar-input--grow {
|
||
flex: 1;
|
||
min-width: 200px;
|
||
}
|
||
```
|
||
|
||
### 6.9 按钮规范(Element Plus 约定)
|
||
|
||
| 场景 | 写法 |
|
||
|------|------|
|
||
| 页面主操作 | `<el-button type="primary">` |
|
||
| 页头次要操作 | 默认按钮(无 type) |
|
||
| 表格行内操作 | `<el-button size="small" link>` |
|
||
| 表格行内删除 | `<el-button size="small" type="danger" link>` |
|
||
| 顶栏退出 | `<el-button text>` |
|
||
| 登录提交 | `<el-button type="primary" class="w-full" size="large">` |
|
||
| 加载中 | `:loading="true"`,禁止重复点击 |
|
||
|
||
### 6.10 弹框与抽屉
|
||
|
||
| 类型 | 宽度 | 属性 |
|
||
|------|------|------|
|
||
| 简单 CRUD | `480–520px` | `destroy-on-close` |
|
||
| 导入/中等表单 | `560–640px` | 同上 |
|
||
| 复杂列表弹窗 | `720px` | 同上 |
|
||
| 二级编辑 | 抽屉 `50%` | — |
|
||
|
||
**底部按钮顺序**:取消(默认)在左,确认(`type="primary"`)在右。
|
||
|
||
### 6.11 标签(Tag)
|
||
|
||
统一 `size="small"`:
|
||
|
||
| 语义 | type |
|
||
|------|------|
|
||
| 启用 / 成功 / 存活 | `success` |
|
||
| 禁用 / 中性 | `info` |
|
||
| 失败 / 错误 | `danger` |
|
||
| 警告 / 未就绪 | `warning` |
|
||
| 可选项高亮 | `primary` |
|
||
|
||
### 6.12 状态色与辅助块
|
||
|
||
```css
|
||
.status-good { color: #047857; }
|
||
.status-warn { color: #b45309; }
|
||
.status-bad { color: #dc2626; }
|
||
.status-muted { color: var(--muted-foreground); }
|
||
|
||
.kv-block p {
|
||
margin: 0 0 var(--space-2);
|
||
}
|
||
|
||
.kv-block p:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.kv-block .label {
|
||
display: inline-block;
|
||
width: 5rem;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.kv-block code {
|
||
font-size: 0.8125rem;
|
||
}
|
||
|
||
.cell-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-2);
|
||
width: 100%;
|
||
}
|
||
|
||
.meta-list {
|
||
margin: 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
font-size: 0.8125rem;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.meta-list li {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: var(--space-3);
|
||
padding: var(--space-1) 0;
|
||
}
|
||
|
||
.meta-list__mono {
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||
}
|
||
|
||
.meta-list__ok { color: var(--el-color-success); }
|
||
.meta-list__warn { color: var(--el-color-warning); }
|
||
|
||
.chart-box {
|
||
height: 280px;
|
||
}
|
||
```
|
||
|
||
### 6.13 滚动条与响应式
|
||
|
||
```css
|
||
.custom-scrollbar {
|
||
scrollbar-width: thin;
|
||
scrollbar-color: #e4e4e7 transparent;
|
||
}
|
||
|
||
.custom-scrollbar::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
|
||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||
background: #e4e4e7;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.custom-scrollbar::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
@media (max-width: 1024px) {
|
||
.stat-grid { grid-template-columns: repeat(2, 1fr); }
|
||
.content-grid--2-1,
|
||
.content-grid--2,
|
||
.content-grid--3 { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.stat-grid { grid-template-columns: 1fr; }
|
||
.main-content { padding: var(--space-4); }
|
||
.page-header { flex-direction: column; }
|
||
}
|
||
```
|
||
|
||
### 6.14 全局工具类速查
|
||
|
||
| 类名 | 用途 |
|
||
|------|------|
|
||
| `.card-toolbar` | 卡片内筛选行 |
|
||
| `.card-pager` | 卡片内分页(右对齐) |
|
||
| `.card-header-row` | 卡片头标题 + 右侧操作 |
|
||
| `.form-hint` | 表单项下方说明 |
|
||
| `.section-title` | 长表单分组标题 |
|
||
| `.page-toolbar` | 页内独立工具条 |
|
||
| `.tabs-bar` | 卡片内 Tab 容器 |
|
||
| `.toolbar-select` / `.toolbar-input` | 工具栏控件宽度 |
|
||
| `.table-scroll--sm/md/lg` | 表格最大高度 |
|
||
| `.w-full` | 宽度 100% |
|
||
| `.u-mb-4` / `.u-mt-3` 等 | 标准外边距 |
|
||
| `.status-good` / `.status-warn` / `.status-bad` | 行内数值/状态语义色 |
|
||
| `.kv-block` | 键值对结果展示(常配合 `.card-inset-block`) |
|
||
| `.cell-stack` | 表格单元格内纵向堆叠 |
|
||
| `.meta-list` | 元数据清单列表 |
|
||
| `.custom-scrollbar` | 细滚动条(Firefox + WebKit) |
|
||
|
||
### 6.15 Element Plus 适配说明
|
||
|
||
以下组件**依靠 EP 默认样式 + `:root` 变量**即可,禁止重复覆盖(除非本节另有说明):
|
||
|
||
| 组件 | 使用约定 | 自定义 CSS |
|
||
|------|----------|------------|
|
||
| `el-button` | 主操作 `type="primary"`;行内 `size="small" link` | 无(颜色来自 `--el-color-primary*`) |
|
||
| `el-input` / `el-select` | 表单内 select 宽度 `100%` | 仅 `.el-form .el-select { width: 100% }` |
|
||
| `el-dialog` | `destroy-on-close`;宽度见 §6.10 | 无 |
|
||
| `el-drawer` | 二级编辑 `size="50%"` | 无 |
|
||
| `el-tabs` | 放在 `.tabs-bar` 内 | 仅 `.tabs-bar .el-tabs__header { margin-bottom: 0 }`;激活色来自 `--el-color-primary` |
|
||
| `el-pagination` | 放在 `.card-pager` 内 | 仅 `font-size: 0.875rem` |
|
||
| `el-tag` | 统一 `size="small"` | 语义色来自 `--el-color-success` 等 |
|
||
| `el-message` / `el-message-box` | 操作反馈 | 无 |
|
||
| `el-form` | `label-width` 见 §3.4 | 无 |
|
||
|
||
**Tabs 推荐结构:**
|
||
|
||
```vue
|
||
<AppCard title="列表标题">
|
||
<div class="tabs-bar">
|
||
<el-tabs v-model="activeTab">
|
||
<el-tab-pane label="Tab A" name="a" />
|
||
<el-tab-pane label="Tab B" name="b" />
|
||
</el-tabs>
|
||
</div>
|
||
<!-- 表格或内容 -->
|
||
</AppCard>
|
||
```
|
||
|
||
**Dialog 推荐结构:**
|
||
|
||
```vue
|
||
<el-dialog v-model="visible" title="标题" width="520px" destroy-on-close>
|
||
<el-form label-width="100px">…</el-form>
|
||
<template #footer>
|
||
<el-button @click="visible = false">取消</el-button>
|
||
<el-button type="primary" :loading="saving" @click="save">确认</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
```
|
||
|
||
### 6.16 图表规范(ECharts)
|
||
|
||
含折线图的统计页使用 `.chart-box` 容器(高度 `280px`)。图表配置须与品牌色一致:
|
||
|
||
| 配置项 | 值 |
|
||
|--------|-----|
|
||
| 容器 class | `chart-box`(`height: 280px`) |
|
||
| 主系列色 | `#0f7a62`(`var(--primary)`) |
|
||
| 次系列色 | `#71717a`(`var(--muted-foreground)`) |
|
||
| 第三系列及以后 | `#4da08c`、`#76b8a8`、`#a0d0c4`(primary-light 阶梯) |
|
||
| `grid` | `{ left: 40, right: 16, top: 16, bottom: 24 }` |
|
||
| 坐标轴字号 | `12`(与 `0.857rem` 接近) |
|
||
| 坐标轴颜色 | `#71717a` |
|
||
| 分割线 | `#e4e4e7` |
|
||
| 折线 | `smooth: true`;线宽 `2` |
|
||
| tooltip | `trigger: 'axis'` |
|
||
| 图例 | 顶部或底部,字号 `12` |
|
||
|
||
```ts
|
||
chart.setOption({
|
||
grid: { left: 40, right: 16, top: 16, bottom: 24 },
|
||
xAxis: {
|
||
type: 'category',
|
||
data: labels,
|
||
axisLabel: { color: '#71717a', fontSize: 12 },
|
||
axisLine: { lineStyle: { color: '#e4e4e7' } },
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
axisLabel: { color: '#71717a', fontSize: 12 },
|
||
splitLine: { lineStyle: { color: '#e4e4e7' } },
|
||
},
|
||
series: [
|
||
{ name: '主指标', type: 'line', smooth: true, data: a, color: '#0f7a62' },
|
||
{ name: '次指标', type: 'line', smooth: true, data: b, color: '#71717a' },
|
||
],
|
||
tooltip: { trigger: 'axis' },
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 交互原则
|
||
|
||
### 7.1 反馈与状态
|
||
|
||
| 原则 | 规范 |
|
||
|------|------|
|
||
| **操作反馈** | 成功/失败使用消息提示(如 `ElMessage`);破坏性操作须二次确认(如 `ElMessageBox.confirm`) |
|
||
| **加载态** | 列表 `v-loading`;按钮 `:loading`;全页加载用卡片级 loading |
|
||
| **空状态** | 表格无数据依赖 EP 默认 empty;需文案时走 i18n |
|
||
| **禁用态** | 批量任务进行中禁用与之互斥的行内操作 |
|
||
| **乐观更新** | 不做乐观更新;等 API 成功后再刷新列表 |
|
||
|
||
### 7.2 导航与层级
|
||
|
||
| 原则 | 规范 |
|
||
|------|------|
|
||
| **路由即导航** | 侧栏链接与当前路由同步,激活态高亮 |
|
||
| **顶栏标题** | 显示当前一级导航名称,非重复页面 H1 |
|
||
| **页内标题** | `PageHeader` 的 `title` 为页面 H1,描述可选 |
|
||
| **退出登录** | 顶栏右侧 `text` 按钮,清除会话后跳转登录页 |
|
||
|
||
### 7.3 表单与对话框
|
||
|
||
| 原则 | 规范 |
|
||
|------|------|
|
||
| **关闭销毁** | 对话框统一 `destroy-on-close`,避免脏数据残留 |
|
||
| **提交防重** | 提交按钮 `loading` 期间禁止重复提交 |
|
||
| **取消不保存** | 取消仅关闭弹层,不触发 API |
|
||
| **密码字段** | `type="password"` + `show-password` |
|
||
| **字段说明** | 用 `.form-hint`,不用 placeholder 替代说明 |
|
||
|
||
### 7.4 列表与表格
|
||
|
||
| 原则 | 规范 |
|
||
|------|------|
|
||
| **主操作位置** | 新建/导入放 `PageHeader#actions`;筛选放 `.card-toolbar` |
|
||
| **行内操作** | 链接式小按钮,删除放最右 |
|
||
| **分页** | 放 `.card-pager`,右对齐 |
|
||
| **刷新** | 提供显式「刷新」按钮;除监控大屏等实时场景外,不依赖隐式自动刷新 |
|
||
| **固定列** | 操作列 `fixed="right"` |
|
||
|
||
### 7.5 动效与过渡
|
||
|
||
| 原则 | 规范 |
|
||
|------|------|
|
||
| **克制动效** | 仅使用 EP 默认过渡(对话框、下拉、Message) |
|
||
| **禁止花哨动画** | 不加自定义 page transition、弹跳、视差 |
|
||
| **侧栏** | 桌面端固定展开,不用折叠动画(移动端待扩展) |
|
||
|
||
### 7.6 无障碍与可用性
|
||
|
||
| 原则 | 规范 |
|
||
|------|------|
|
||
| **对比度** | 正文与背景对比度 ≥ 4.5:1 |
|
||
| **焦点** | 依赖 EP 默认 focus ring,不自行移除 |
|
||
| **点击目标** | 按钮最小高度遵循 EP(默认 32px,large 40px) |
|
||
| **错误信息** | 接口错误映射为用户可读的本地化文案(如限流、权限不足) |
|
||
|
||
### 7.7 国际化
|
||
|
||
| 原则 | 规范 |
|
||
|------|------|
|
||
| **文案** | 所有可见文字走国际化方案(如 i18n),禁止硬编码用户可见字符串 |
|
||
| **时间** | 显示格式 `YYYY-MM-DD HH:mm:ss`(ISO 替换 `T` 并截断) |
|
||
| **布局弹性** | 按钮组、工具栏 `flex-wrap: wrap` 适配长文案 |
|
||
|
||
---
|
||
|
||
## 8. 附录:复刻指南
|
||
|
||
按以下顺序实施,可在新项目中复刻本文档定义的全部视觉(Vue 3 + Element Plus 2.x)。
|
||
|
||
### 8.1 壳层布局完整模板
|
||
|
||
**DOM 层级(class 名称不可改):**
|
||
|
||
```
|
||
.layout
|
||
├── .sidebar.custom-scrollbar
|
||
│ ├── .sidebar-brand
|
||
│ │ ├── .brand-icon → 项目自有 Logo(20px)
|
||
│ │ └── .brand-name
|
||
│ ├── .nav-group-label(可选分组)
|
||
│ └── a.nav-item[.active] × N(含 el-icon 16px)
|
||
└── .main-area
|
||
├── header.topbar
|
||
│ ├── .topbar-title
|
||
│ └── 顶栏操作(如 el-button text)
|
||
└── main.main-content.custom-scrollbar
|
||
└── router-view → 各页面 .page
|
||
```
|
||
|
||
```vue
|
||
<!-- layouts/MainLayout.vue — 文件名自定 -->
|
||
<script setup lang="ts">
|
||
import { computed } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { Odometer, List, Setting } from '@element-plus/icons-vue'
|
||
|
||
const appName = '应用名称' // 替换为实际产品名
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
|
||
const navItems = [
|
||
{ path: '/', label: '概览', icon: Odometer },
|
||
{ path: '/items', label: '列表', icon: List },
|
||
{ path: '/settings', label: '设置', icon: Setting },
|
||
]
|
||
|
||
const pageTitle = computed(() => {
|
||
return navItems.find((n) => n.path === route.path)?.label ?? '后台'
|
||
})
|
||
|
||
function logout() {
|
||
router.push('/login')
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="layout">
|
||
<aside class="sidebar custom-scrollbar">
|
||
<div class="sidebar-brand">
|
||
<div class="brand-icon">
|
||
<!-- 项目自有 Logo,显示尺寸 20px -->
|
||
</div>
|
||
<span class="brand-name">{{ appName }}</span>
|
||
</div>
|
||
<div class="nav-group-label">管理</div>
|
||
<router-link
|
||
v-for="item in navItems"
|
||
:key="item.path"
|
||
:to="item.path"
|
||
class="nav-item"
|
||
:class="{ active: route.path === item.path }"
|
||
>
|
||
<el-icon :size="16"><component :is="item.icon" /></el-icon>
|
||
{{ item.label }}
|
||
</router-link>
|
||
</aside>
|
||
<div class="main-area">
|
||
<header class="topbar">
|
||
<span class="topbar-title">{{ pageTitle }}</span>
|
||
<el-button text @click="logout">退出</el-button>
|
||
</header>
|
||
<main class="main-content custom-scrollbar">
|
||
<router-view />
|
||
</main>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
### 8.2 登录页完整模板
|
||
|
||
```vue
|
||
<!-- views/Login.vue — 文件名自定 -->
|
||
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
|
||
const appName = '应用名称'
|
||
const router = useRouter()
|
||
const password = ref('')
|
||
const loading = ref(false)
|
||
|
||
async function submit() {
|
||
loading.value = true
|
||
try {
|
||
// await auth.login(password.value)
|
||
router.push('/')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="login-page">
|
||
<div class="login-wrap">
|
||
<div class="login-brand-icon">
|
||
<!-- 项目自有 Logo,显示尺寸 24px -->
|
||
</div>
|
||
<h1 class="page-title">{{ appName }}</h1>
|
||
<p class="page-desc">一句话产品说明</p>
|
||
<div class="login-card">
|
||
<h3 class="app-card__title u-mb-6">登录</h3>
|
||
<el-form size="large" @submit.prevent="submit">
|
||
<el-form-item label="密码">
|
||
<el-input v-model="password" type="password" show-password placeholder="请输入密码" />
|
||
</el-form-item>
|
||
<el-button type="primary" native-type="submit" :loading="loading" class="w-full">
|
||
登录
|
||
</el-button>
|
||
</el-form>
|
||
<p class="form-hint u-mt-4 text-center">登录说明文字</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
### 8.3 公共组件完整源码
|
||
|
||
```vue
|
||
<!-- components/PageHeader.vue -->
|
||
<script setup lang="ts">
|
||
defineProps<{ title: string; description?: string }>()
|
||
</script>
|
||
|
||
<template>
|
||
<div class="page-header">
|
||
<div>
|
||
<h1 class="page-title">{{ title }}</h1>
|
||
<p v-if="description" class="page-desc">{{ description }}</p>
|
||
</div>
|
||
<div v-if="$slots.actions" class="page-actions">
|
||
<slot name="actions" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
```vue
|
||
<!-- components/AppCard.vue -->
|
||
<script setup lang="ts">
|
||
defineProps<{ title?: string; padded?: boolean }>()
|
||
</script>
|
||
|
||
<template>
|
||
<div class="app-card">
|
||
<div v-if="title || $slots.header" class="app-card__header">
|
||
<slot name="header">
|
||
<h3 class="app-card__title">{{ title }}</h3>
|
||
</slot>
|
||
</div>
|
||
<div :class="padded ? 'app-card__body--padded' : ''">
|
||
<slot />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
```vue
|
||
<!-- components/StatCard.vue -->
|
||
<script setup lang="ts">
|
||
defineProps<{ label: string; value: string | number }>()
|
||
</script>
|
||
|
||
<template>
|
||
<div class="stat-card">
|
||
<div class="stat-card__label">{{ label }}</div>
|
||
<div class="stat-card__value">{{ value }}</div>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
品牌 Logo 由各项目自行提供,放入 `.brand-icon`(20px)与 `.login-brand-icon`(24px)容器即可;须使用 `currentColor` 或适配主色底上的 `--primary-foreground`。
|
||
|
||
### 8.4 标准页面模板
|
||
|
||
```vue
|
||
<template>
|
||
<div class="page">
|
||
<PageHeader title="页面标题" description="一句话说明">
|
||
<template #actions>
|
||
<el-button type="primary">主操作</el-button>
|
||
</template>
|
||
</PageHeader>
|
||
|
||
<div class="stat-grid">
|
||
<StatCard label="指标 A" value="128" />
|
||
<StatCard label="指标 B" value="64" />
|
||
</div>
|
||
|
||
<AppCard>
|
||
<div class="card-toolbar">
|
||
<el-input class="toolbar-input" placeholder="搜索" clearable />
|
||
<el-button type="primary">查询</el-button>
|
||
</div>
|
||
<el-table :data="items" v-loading="loading" size="small">
|
||
<el-table-column prop="name" label="名称" min-width="160" />
|
||
<el-table-column label="操作" width="120" fixed="right">
|
||
<template #default="{ row }">
|
||
<el-button size="small" type="danger" link @click="remove(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="card-pager">
|
||
<el-pagination layout="prev, pager, next" :total="100" />
|
||
</div>
|
||
</AppCard>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
### 8.5 带 Tab 的列表页
|
||
|
||
```vue
|
||
<AppCard title="数据列表">
|
||
<div class="tabs-bar">
|
||
<el-tabs v-model="activeTab">
|
||
<el-tab-pane label="实时" name="live" />
|
||
<el-tab-pane label="历史" name="history" />
|
||
</el-tabs>
|
||
</div>
|
||
<div v-if="activeTab === 'live'" class="card-toolbar">…</div>
|
||
<el-table size="small" class="table-scroll--md">…</el-table>
|
||
<div class="card-pager">…</div>
|
||
</AppCard>
|
||
```
|
||
|
||
### 8.6 新页面 Checklist
|
||
|
||
1. 将 §8.9 复制为项目全局样式文件,按 §6.0 顺序在 `main.ts` 中引入
|
||
2. 复制 §8.1 壳层布局、§8.3 公共组件(文件名自定)
|
||
3. 页面根节点 `<div class="page">`
|
||
4. `PageHeader`(标题 + 描述 + actions)
|
||
5. 主内容 `AppCard`;表单页加 `padded`
|
||
6. 列表筛选用 `.card-toolbar`,分页用 `.card-pager`
|
||
7. 间距与颜色仅使用 CSS 变量与全局类
|
||
8. 在主布局中注册导航项与图标(§5.3)
|
||
|
||
### 8.7 反模式
|
||
|
||
| 不要 | 应该 |
|
||
|------|------|
|
||
| 深色侧栏 | 浅色半透明侧栏 |
|
||
| 页面内裸 `el-card` 作主容器 | `AppCard` |
|
||
| 无标题直接上表格 | 先 `PageHeader` |
|
||
| 硬编码色值 | CSS 变量 |
|
||
| 重阴影 | 细边框 |
|
||
| 每页自定义 `.toolbar` / `.pager` | `.card-toolbar` / `.card-pager` |
|
||
| 表单卡片不加 `padded` | `AppCard padded` |
|
||
| `style="margin-top: 12px"` | `.u-mt-3` 等工具类 |
|
||
| 非 8px 倍数的随意间距 | 间距 Token |
|
||
| 在 EP 样式之前引入全局样式 | 先 `element-plus/dist/index.css`,再引入 §8.9 全局样式 |
|
||
|
||
### 8.8 推荐目录结构(示例)
|
||
|
||
```
|
||
styles/global.css ← 由本文档 §8.9 复制生成,路径与文件名自定
|
||
components/PageHeader.vue
|
||
components/StatCard.vue
|
||
components/AppCard.vue
|
||
layouts/MainLayout.vue ← 见 §8.1,文件名自定
|
||
views/Login.vue ← 见 §8.2,文件名自定
|
||
```
|
||
|
||
样式变更时,须同时更新本文档 **§6.1–6.14** 与 **§8.9** 两处 CSS,保持内容一致。
|
||
|
||
### 8.9 完整全局样式(一键复制)
|
||
|
||
以下为**设计规范定义的完整全局 CSS**,直接复制到项目全局样式文件即可(§6.1–6.14 为分模块对照版,内容须与本节一致)。
|
||
|
||
```css
|
||
:root {
|
||
--background: #f4f4f5;
|
||
--foreground: #18181b;
|
||
--card: #ffffff;
|
||
--card-foreground: #18181b;
|
||
--primary: #0f7a62;
|
||
--primary-hover: #0d6b56;
|
||
--primary-foreground: #fafafa;
|
||
--muted: #f4f4f5;
|
||
--muted-foreground: #71717a;
|
||
--border: #e4e4e7;
|
||
--input: #e4e4e7;
|
||
--destructive: #dc2626;
|
||
--success: #047857;
|
||
--warning: #b45309;
|
||
--danger: #dc2626;
|
||
--info: #a1a1aa;
|
||
--sidebar: rgba(250, 250, 250, 0.85);
|
||
--sidebar-foreground: #18181b;
|
||
--sidebar-accent: #f4f4f5;
|
||
--sidebar-border: #e4e4e7;
|
||
--sidebar-width: 15rem;
|
||
--radius: 0.5rem;
|
||
--space-1: 4px;
|
||
--space-2: 8px;
|
||
--space-3: 12px;
|
||
--space-4: 16px;
|
||
--space-5: 20px;
|
||
--space-6: 24px;
|
||
--space-8: 32px;
|
||
|
||
--el-color-primary: #0f7a62;
|
||
--el-color-primary-light-3: #4da08c;
|
||
--el-color-primary-light-5: #76b8a8;
|
||
--el-color-primary-light-7: #a0d0c4;
|
||
--el-color-primary-light-8: #b8dcd2;
|
||
--el-color-primary-light-9: #d0e8e1;
|
||
--el-color-primary-dark-2: #0c624e;
|
||
--el-color-success: #047857;
|
||
--el-color-warning: #b45309;
|
||
--el-color-danger: #dc2626;
|
||
--el-color-info: #a1a1aa;
|
||
--el-border-radius-base: 0.5rem;
|
||
--el-border-color: #e4e4e7;
|
||
--el-bg-color-page: #f4f4f5;
|
||
--el-text-color-primary: #18181b;
|
||
--el-text-color-regular: #3f3f46;
|
||
--el-text-color-secondary: #71717a;
|
||
--el-font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
html, body, #app {
|
||
margin: 0;
|
||
padding: 0;
|
||
height: 100%;
|
||
font-size: 14px;
|
||
font-family: var(--el-font-family);
|
||
background: var(--background);
|
||
color: var(--foreground);
|
||
}
|
||
|
||
.layout {
|
||
display: flex;
|
||
height: 100vh;
|
||
}
|
||
|
||
.sidebar {
|
||
width: var(--sidebar-width);
|
||
flex-shrink: 0;
|
||
background: var(--sidebar);
|
||
border-right: 1px solid var(--sidebar-border);
|
||
backdrop-filter: blur(8px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.sidebar-brand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-3);
|
||
padding: var(--space-5) var(--space-4);
|
||
}
|
||
|
||
.brand-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: var(--radius);
|
||
background: var(--primary);
|
||
color: var(--primary-foreground);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.brand-name {
|
||
font-size: 1.125rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.nav-group-label {
|
||
font-size: 0.75rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
color: var(--muted-foreground);
|
||
padding: var(--space-4) var(--space-4) var(--space-2);
|
||
}
|
||
|
||
.nav-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
padding: var(--space-2) var(--space-3);
|
||
margin: 0 var(--space-2);
|
||
border-radius: var(--radius);
|
||
color: var(--muted-foreground);
|
||
text-decoration: none;
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.nav-item:hover { background: var(--sidebar-accent); }
|
||
|
||
.nav-item.active {
|
||
background: var(--sidebar-accent);
|
||
color: var(--primary);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.main-area {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
}
|
||
|
||
.topbar {
|
||
height: 52px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 var(--space-6);
|
||
background: var(--card);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.topbar-title {
|
||
font-size: 0.875rem;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.main-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: var(--space-6);
|
||
}
|
||
|
||
.page {
|
||
max-width: 1400px;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
margin-bottom: var(--space-6);
|
||
gap: var(--space-4);
|
||
}
|
||
|
||
.page-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
flex-shrink: 0;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 1.5rem;
|
||
font-weight: 600;
|
||
letter-spacing: -0.02em;
|
||
margin: 0 0 var(--space-1);
|
||
}
|
||
|
||
.page-desc {
|
||
font-size: 0.875rem;
|
||
color: var(--muted-foreground);
|
||
margin: 0;
|
||
}
|
||
|
||
.stat-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: var(--space-4);
|
||
margin-bottom: var(--space-6);
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: var(--space-5);
|
||
}
|
||
|
||
.stat-card__label {
|
||
font-size: 0.875rem;
|
||
color: var(--muted-foreground);
|
||
margin-bottom: var(--space-2);
|
||
}
|
||
|
||
.stat-card__value {
|
||
font-size: 1.75rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.app-card {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
.page > .app-card:last-child,
|
||
.page > .content-grid:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.app-card__header {
|
||
padding: var(--space-4) var(--space-5);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.app-card__title {
|
||
font-size: 0.9375rem;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
|
||
.app-card__body--padded { padding: var(--space-5); }
|
||
|
||
.content-grid {
|
||
display: grid;
|
||
gap: var(--space-4);
|
||
margin-bottom: var(--space-6);
|
||
}
|
||
|
||
.content-grid--2-1 { grid-template-columns: 1.4fr 1fr; }
|
||
.content-grid--2 { grid-template-columns: 1fr 1fr; }
|
||
.content-grid--3 { grid-template-columns: repeat(3, 1fr); }
|
||
|
||
.card-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
padding: var(--space-4) var(--space-5) 0;
|
||
margin-bottom: var(--space-4);
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.card-toolbar--desc {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.card-pager {
|
||
padding: var(--space-4) var(--space-5);
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.card-footer-hint {
|
||
padding: 0 var(--space-5) var(--space-3);
|
||
margin: 0;
|
||
}
|
||
|
||
.card-footer-hint + .card-pager {
|
||
padding-top: var(--space-3);
|
||
}
|
||
|
||
.card-inset-block {
|
||
margin: 0 var(--space-5) var(--space-4);
|
||
padding: var(--space-3);
|
||
background: var(--muted);
|
||
border-radius: var(--radius);
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.tabs-bar {
|
||
padding: 0 var(--space-5);
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.tabs-bar .el-tabs__header { margin-bottom: 0; }
|
||
|
||
.page-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
margin-bottom: var(--space-4);
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.inline-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-2);
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.card-header-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: var(--space-4);
|
||
}
|
||
|
||
.form-hint {
|
||
margin-top: 6px;
|
||
font-size: 0.75rem;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.form-hint--flush { margin-top: 0; }
|
||
|
||
.u-mt-3 { margin-top: var(--space-3); }
|
||
.u-mt-4 { margin-top: var(--space-4); }
|
||
.u-mb-3 { margin-bottom: var(--space-3); }
|
||
.u-mb-4 { margin-bottom: var(--space-4); }
|
||
.u-mb-6 { margin-bottom: var(--space-6); }
|
||
.text-center { text-align: center; }
|
||
.w-full { width: 100%; }
|
||
|
||
code.inline-code {
|
||
font-size: 0.75rem;
|
||
padding: 0 var(--space-1);
|
||
background: var(--muted);
|
||
border-radius: var(--radius);
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
.section-title:not(:first-child) { margin-top: var(--space-6); }
|
||
|
||
.login-page {
|
||
min-height: 100vh;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--background);
|
||
padding: var(--space-6);
|
||
}
|
||
|
||
.login-wrap { max-width: 400px; width: 100%; text-align: center; }
|
||
|
||
.login-brand-icon {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 12px;
|
||
background: var(--primary);
|
||
color: var(--primary-foreground);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
.login-card {
|
||
background: var(--card);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: var(--space-8);
|
||
margin-top: var(--space-6);
|
||
text-align: left;
|
||
}
|
||
|
||
.status-good { color: #047857; }
|
||
.status-warn { color: #b45309; }
|
||
.status-bad { color: #dc2626; }
|
||
.status-muted { color: var(--muted-foreground); }
|
||
|
||
.toolbar-select { width: 120px; }
|
||
.toolbar-select--country { width: 160px; }
|
||
.toolbar-select--wide { width: 280px; }
|
||
.toolbar-input { width: 200px; }
|
||
.toolbar-input--wide { width: 260px; }
|
||
.toolbar-input--grow { flex: 1; min-width: 200px; }
|
||
.table-cell-select { width: 100%; min-width: 200px; }
|
||
|
||
.el-form .el-select { width: 100%; }
|
||
|
||
.app-card .el-table {
|
||
--el-table-header-bg-color: var(--muted);
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.app-card .el-table--small .el-table__cell {
|
||
padding: var(--space-2) var(--space-3);
|
||
}
|
||
|
||
.app-card .el-table--small th.el-table__cell {
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.app-card .el-table--small td.el-table__cell {
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
|
||
.app-card .el-table--small .el-table__row {
|
||
height: 40px;
|
||
}
|
||
|
||
.app-card .el-table .el-button.is-link {
|
||
padding: 0;
|
||
height: auto;
|
||
}
|
||
|
||
.app-card .el-table .cell {
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.table-scroll--sm { max-height: 320px; }
|
||
.table-scroll--md { max-height: 360px; }
|
||
.table-scroll--lg { max-height: 480px; }
|
||
|
||
.card-toolbar .toolbar-input--grow {
|
||
flex: 1;
|
||
min-width: 200px;
|
||
}
|
||
|
||
.card-pager .el-pagination {
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.kv-block p {
|
||
margin: 0 0 var(--space-2);
|
||
}
|
||
|
||
.kv-block p:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.kv-block .label {
|
||
display: inline-block;
|
||
width: 5rem;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.kv-block code {
|
||
font-size: 0.8125rem;
|
||
}
|
||
|
||
.cell-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--space-2);
|
||
width: 100%;
|
||
}
|
||
|
||
.meta-list {
|
||
margin: 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
font-size: 0.8125rem;
|
||
color: var(--muted-foreground);
|
||
}
|
||
|
||
.meta-list li {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: var(--space-3);
|
||
padding: var(--space-1) 0;
|
||
}
|
||
|
||
.meta-list__mono {
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||
}
|
||
|
||
.meta-list__ok { color: var(--el-color-success); }
|
||
.meta-list__warn { color: var(--el-color-warning); }
|
||
|
||
.chart-box {
|
||
height: 280px;
|
||
}
|
||
|
||
.custom-scrollbar {
|
||
scrollbar-width: thin;
|
||
scrollbar-color: #e4e4e7 transparent;
|
||
}
|
||
|
||
.custom-scrollbar::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
|
||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||
background: #e4e4e7;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.custom-scrollbar::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
@media (max-width: 1024px) {
|
||
.stat-grid { grid-template-columns: repeat(2, 1fr); }
|
||
.content-grid--2-1,
|
||
.content-grid--2,
|
||
.content-grid--3 { grid-template-columns: 1fr; }
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.stat-grid { grid-template-columns: 1fr; }
|
||
.main-content { padding: var(--space-4); }
|
||
.page-header { flex-direction: column; }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
*浅色后台管理系统 UI 设计规范*
|