Files
craftlabs-authorization-sdk/docs/superpowers/specs/2026-05-19-frontend-framework-design.md

666 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CraftLabs 前端框架设计 v1.0
> 基于设计体系 `2026-05-19-design-system.md` 提炼的前端工程框架规范
> 目标:统一架构、约束风格、加速开发、降低交接成本
> 日期:2026-05-19
---
## 目录
1. [框架概述](#1-框架概述)
2. [技术栈](#2-技术栈)
3. [工程结构](#3-工程结构)
4. [核心模块](#4-核心模块)
5. [布局系统](#5-布局系统)
6. [组件体系](#6-组件体系)
7. [页面模板](#7-页面模板)
8. [数据流](#8-数据流)
9. [开发规范](#9-开发规范)
10. [构建与部署](#10-构建与部署)
---
## 1. 框架概述
### 1.1 定位
CraftLabs 前端框架是基于 Vue 3 + Element Plus 的企业级 B2B 授权运营平台前端方案。在 Element Plus 之上注入品牌设计 Token,提供开箱即用的布局、组件、页面模板和开发约束。
### 1.2 核心原则
| 原则 | 说明 |
|------|------|
| **约定优于配置** | 路由、鉴权、API 封装、错误处理均有默认行为,开发者只需关注业务 |
| **Token 驱动** | 所有视觉属性通过 CSS 变量控制,换肤只需改 `theme.css` |
| **组件隔离** | 每个 `.vue` 文件自包含模板+逻辑+样式,`<script setup>` 统一风格 |
| **渐进增强** | 在 Element Plus 基础上叠加品牌样式,不 fork 组件库 |
| **安全默认** | JWT 存储 localStorage、401 自动登出、路由级鉴权 |
### 1.3 层级架构
```
┌─────────────────────────────────────────────┐
│ Views (页面层) │
│ LicenseList CustomersView ContractsView │
├─────────────────────────────────────────────┤
│ Components (组件层) │
│ StatsCard SearchBar DataTable ... │
├─────────────────────────────────────────────┤
│ Layout System (布局系统) │
│ MainLayout (Header+Sidebar+Breadcrumb) │
├──────────────┬──────────────┬───────────────┤
│ Router │ Store │ API │
│ (鉴权/懒加载) │ (Pinia JWT) │ (axios封装) │
├──────────────┴──────────────┴───────────────┤
│ Element Plus + Vue 3 │
├─────────────────────────────────────────────┤
│ Theme System (Token层) │
│ theme.css · CSS Variables │
└─────────────────────────────────────────────┘
```
---
## 2. 技术栈
| 层 | 选型 | 版本 | 说明 |
|----|------|:--:|------|
| 框架 | Vue 3 | ^3.5 | Composition API + `<script setup>` |
| 构建 | Vite | ^6.0 | 极速 HMR + 开箱即用 |
| UI 库 | Element Plus | ^2.9 | 组件库本体,CSS 变量覆盖换肤 |
| 路由 | vue-router | ^4.5 | 懒加载 + meta 鉴权 |
| 状态 | Pinia | ^2.3 | 轻量状态管理 |
| HTTP | axios | ^1.7 | 拦截器 + JWT 注入 |
| 语言 | JavaScript (ESM) | — | 暂用 JS,必要时渐进引入 TS |
### 2.1 不引入的依赖
| 库 | 原因 |
|----|------|
| Tailwind CSS | Element Plus 已覆盖组件样式,避免双体系 |
| Vuex | Pinia 更轻量,官方推荐 |
| Lodash | 项目规模不需要工具函数库 |
| Moment.js | 日期格式化用原生 `Intl` 或字符串裁剪 |
---
## 3. 工程结构
```
web/delivery-platform-ui/
├── public/
│ ├── design-demo.html # 设计 Demo(独立 HTML,无依赖)
│ └── design-system.html # 设计体系可视化
├── src/
│ ├── main.js # 入口:挂载 Vue + Pinia + Router + 401拦截器
│ ├── App.vue # 根组件:纯 <router-view>
│ ├── theme.css # ★ 全局主题 TokenCSS 变量)
│ │
│ ├── layout/
│ │ └── MainLayout.vue # ★ 主布局:Header + Sidebar + Breadcrumb
│ │
│ ├── router/
│ │ └── index.js # ★ 路由表 + 鉴权守卫
│ │
│ ├── stores/
│ │ └── auth.js # ★ 认证状态:JWT + login/logout/roles
│ │
│ ├── api/
│ │ └── platform.js # ★ API 封装:按模块导出 axios 请求函数
│ │
│ ├── utils/
│ │ ├── apiErrorMessage.js # 统一错误消息提取
│ │ └── redactPayload.js # Callback payload 脱敏
│ │
│ └── views/ # ★ 页面组件(按模块命名)
│ ├── HomeView.vue # 工作台/Dashboard
│ ├── LoginView.vue # 登录页
│ ├── ForbiddenView.vue # 403
│ ├── NotFoundView.vue # 404
│ ├── CustomersView.vue # 客户管理
│ ├── ProjectsView.vue # 项目管理
│ ├── ContractsView.vue # 合同列表
│ ├── ContractWizardView.vue # 合同创建向导
│ ├── ContractDetailView.vue # 合同详情
│ ├── DeliveriesView.vue # 交付列表
│ ├── DeliveryBatchWizardView.vue
│ ├── DeliveryBatchDetailView.vue
│ ├── LicenseSnListView.vue # 许可SN列表
│ ├── LicenseSnWizardView.vue
│ ├── LicenseSnDetailView.vue
│ ├── LicenseList.vue # ★ 许可证管理(自研SDK核心页)
│ ├── CallbackInboxView.vue # Callback 收件箱
│ ├── CallbackInboxDetailView.vue
│ ├── IntegrationEnvironmentsView.vue
│ ├── IntegrationProductLinesView.vue
│ └── LayoutCompareView.vue # 设计审核工具(内部)
├── package.json
├── vite.config.js
├── Dockerfile
└── README.md
```
### 3.1 文件命名规范
| 类型 | 规范 | 示例 |
|------|------|------|
| 页面视图 | `PascalCaseView.vue` | `CustomersView.vue` |
| 布局组件 | `PascalCase.vue` | `MainLayout.vue` |
| 路由/Store/API | `kebab-case.js` | `platform.js` |
| 工具函数 | `camelCase.js` | `apiErrorMessage.js` |
| 样式文件 | `kebab-case.css` | `theme.css` |
---
## 4. 核心模块
### 4.1 主题系统 (`theme.css`)
所有视觉 Token 集中在一个 CSS 文件中,通过覆盖 Element Plus 变量实现换肤。
```css
/* 核心 3 行即可改变全站主色调 */
:root {
--el-color-primary: #2C3E6B;
--el-bg-color-page: #EAEFFA;
--el-border-radius-base: 6px;
}
/* 完整 Token 见 docs/superpowers/specs/2026-05-19-design-system.md */
```
**设计意图**
- 不修改 Element Plus 源码,只覆盖 CSS 变量
- 更换品牌色只需改 `--el-color-primary` 一个值
- 新页面自动继承主题,无需额外引入
### 4.2 路由系统 (`router/index.js`)
**路由表结构**
```js
const routes = [
// 公开路由(无鉴权)
{ path: "/login", component: LoginView },
// 受保护路由(需要登录)
{
path: "/",
component: MainLayout, // 统一布局壳
meta: { requiresAuth: true },
children: [
{
path: "licenses", // 子路由路径
name: "licenses", // 命名路由(用于编程跳转)
component: () => import("..."), // 懒加载
meta: { roles: ["SYS_ADMIN", "DEVELOPER"] } // 角色鉴权
},
// ... 更多子路由
]
},
// 兜底路由
{ path: "/403", component: ForbiddenView },
{ path: "/:pathMatch(.*)*", component: NotFoundView },
]
```
**鉴权守卫逻辑**
```
用户访问页面
├─ meta.requiresAuth && 无 token → 重定向 /login?redirect=原路径
├─ meta.roles && 用户角色不匹配 → 重定向 /403
└─ 通过 → 正常渲染
```
**新增页面只需**
1.`children` 数组中加一条路由
2. 设置 `meta.roles` 控制可见角色
3. `component: () => import(...)` 懒加载
### 4.3 状态管理 (`stores/auth.js`)
```js
export const useAuthStore = defineStore("auth", {
state: () => ({
token: localStorage.getItem("craftlabs_platform_token") || "",
displayName: "",
roles: [],
}),
actions: {
async login(username, password) {
const { data } = await axios.post("/api/v1/auth/login", { username, password })
this.token = data.token
this.roles = data.roles
localStorage.setItem("craftlabs_platform_token", this.token)
axios.defaults.headers.common.Authorization = `Bearer ${this.token}`
},
logout() {
this.token = ""
localStorage.removeItem("craftlabs_platform_token")
delete axios.defaults.headers.common.Authorization
},
},
})
```
**使用方式**
```js
import { useAuthStore } from "../stores/auth"
const auth = useAuthStore()
// 模板中
auth.hasAnyRole(["SYS_ADMIN", "DEVELOPER"]) // 角色判断
auth.displayName // 用户名
```
### 4.4 API 层 (`api/platform.js`)
```js
// 每个后端端点导出为一个具名函数
export function listCustomers(params) {
return axios.get("/api/v1/customers", { params })
}
export function createCustomer(body) {
return axios.post("/api/v1/customers", body)
}
export function updateCustomer(id, body) {
return axios.put(`/api/v1/customers/${id}`, body)
}
export function deleteCustomer(id) {
return axios.delete(`/api/v1/customers/${id}`)
}
```
**约定**
- 函数名 = HTTP 动词 + 资源名:`listXxx` / `createXxx` / `updateXxx` / `deleteXxx` / `getXxx`
- 状态变更用语义化函数名:`patchContractStatus()``addLine()`
- 所有函数返回 axios Promise,由调用方 `await` + try/catch
- 查询参数用 `{ params }` 传递(axios 自动序列化)
### 4.5 全局错误处理 (`main.js`)
```js
// 401 自动登出拦截器
axios.interceptors.response.use(
(r) => r,
(err) => {
if (err.response?.status === 401) {
const auth = useAuthStore()
auth.logout()
router.push({ name: "login", query: { redirect: router.currentRoute.value.fullPath } })
}
return Promise.reject(err)
}
)
```
---
## 5. 布局系统
### 5.1 MainLayout 结构
```
┌──────────────────────────────────────────────────────────┐
│ <header> 60px │
│ Logo │ 导航菜单 │ 全局搜索 │ 🔔 │ ⚙️ │ 👤用户名 │
├────────┬─────────────────────────────────────────────────┤
│<aside> │ <div class="breadcrumb"> 46px │
│ 232px │ 授权运营 › 当前页面 │
│ ├─────────────────────────────────────────────────┤
│ 菜单 │ │
│ 分组 │ <router-view /> │
│ │ (各页面通过 slot 注入到此区域) │
│ 11项 │ │
│ │ │
└────────┴─────────────────────────────────────────────────┘
```
### 5.2 布局尺寸契约
| 元素 | 尺寸 | CSS |
|------|:--:|------|
| Header | 60px | `height: 60px; flex-shrink: 0` |
| Sidebar | 232px | `width: 232px; flex-shrink: 0` |
| Breadcrumb | 46px | `height: 46px; flex-shrink: 0` |
| Content | 自适应 | `flex: 1; padding: 16px 20px` |
### 5.3 布局变体
| 变体 | 触发条件 | 说明 |
|------|---------|------|
| 标准布局 | 默认 | Header + Sidebar + Breadcrumb + 全宽 Content |
| Tree 布局 | `activeModule === 'licenses'` | 额外 280px Tree 面板在 Content 左侧 |
| 全屏布局 | 无 Header/Sidebar | 登录页、403/404 页 |
---
## 6. 组件体系
### 6.1 组件分类
```
组件层级:
Element Plus 基础组件 (el-button, el-table, el-dialog, ...)
├── 直接使用(无需封装)
│ el-button el-input el-select el-table el-pagination
│ el-tag el-badge el-tree el-dialog el-card
├── 组合组件(页面内组合)
│ 搜索栏 + 表格 + 分页 → 列表页模板
│ 统计卡片行 → Dashboard 模板
└── 业务组件(跨页面复用,按需抽取)
StatsCard SearchFilterBar StatusTag ConfirmDialog
```
### 6.2 组件开发原则
- **优先直接用 Element Plus**90% 场景不需要封装
- **抽取时机**:同一模式在 3+ 个页面出现时才抽取为独立组件
- **Props 优先于全局状态**:组件通过 props 接收数据,不直接读 store
- **Events 向上**:组件通过 `$emit` 通知父组件,不直接改父组件状态
### 6.3 CSS 约定
```css
/* ✅ 推荐:scoped + CSS 变量 */
<style scoped>
.my-page { background: var(--el-bg-color-page); }
.card { border-radius: var(--el-border-radius-base); }
.btn-primary { background: var(--el-color-primary); }
</style>
/* ❌ 避免:硬编码色值 */
<style scoped>
.card { background: #EAEFFA; } /* 应使用 CSS 变量 */
</style>
```
---
## 7. 页面模板
### 7.1 标准列表页
适用于:客户管理、合同管理、交付管理、许可SN
```
结构:
<统计卡片行> (可选)
<筛选栏>
[搜索框] [状态下拉] [查询按钮] [+ 新建按钮]
</筛选栏>
<数据表格>
<el-table> + <el-pagination>
</数据表格>
<新建/编辑 Dialog>
```
**样板代码(约 200 行)**
```vue
<script setup>
import { ref, onMounted } from "vue"
import { ElMessage, ElMessageBox } from "element-plus"
import { useAuthStore } from "../stores/auth"
import { listXxx, createXxx, updateXxx, deleteXxx } from "../api/platform"
import { apiErrorMessage } from "../utils/apiErrorMessage"
const auth = useAuthStore()
const loading = ref(false), saving = ref(false)
const rows = ref([]), total = ref(0), page = ref(1), pageSize = ref(10)
const keyword = ref(""), filterStatus = ref("")
const dialogVisible = ref(false), editingId = ref(null)
const formRef = ref(null)
const form = reactive({ name: "", ... })
onMounted(() => { auth.restoreAxiosAuth(); load() })
async function load() {
loading.value = true
try {
const { data } = await listXxx({ page: page.value-1, size: pageSize.value, keyword: keyword.value?.trim() })
rows.value = data.content ?? []
total.value = data.totalElements ?? 0
} catch(e) {
ElMessage.error(apiErrorMessage(e))
} finally { loading.value = false }
}
async function submit() {
await formRef.value.validate()
saving.value = true
try {
const payload = { name: form.name.trim(), ... }
editingId.value ? await updateXxx(editingId.value, payload) : await createXxx(payload)
dialogVisible.value = false
await load()
} catch(e) {
ElMessage.error(apiErrorMessage(e))
} finally { saving.value = false }
}
async function onDelete(row) {
await ElMessageBox.confirm(`确定删除「${row.name}」?`, "提示", { type: "warning" })
await deleteXxx(row.id)
await load()
}
</script>
```
### 7.2 Tree + 列表页
适用于:许可证管理
```
结构:
<div class="license-page"> ← display: flex
<Tree Panel 280px> ← 左侧固定宽度
<Main Panel> ← flex: 1
<统计卡片行>
<筛选栏 + 签发按钮>
<数据表格>
</Main Panel>
</div>
```
### 7.3 工作台 Dashboard
适用于:首页
```
结构:
<统计卡片行 4列>
<趋势图 + 待办列表 2列>
<快捷入口>
```
### 7.4 详情页
适用于:合同详情、交付详情、SN详情
```
结构:
<返回按钮 + 页面标题>
<详情卡片>
<el-descriptions> 或 自定义 KV 布局
</详情卡片>
<关联数据卡片> (可选:审计事件、Callback关联)
<操作按钮行>
```
---
## 8. 数据流
### 8.1 请求生命周期
```
用户操作(点击查询/新建/保存)
loading.value = true ← 显示加载态
await apiFunction(payload) ← axios 自动注入 JWT Header
├─ 200: data → 更新响应式状态
│ ElMessage.success("操作成功")
├─ 4xx: err → ElMessage.error(apiErrorMessage(e))
│ 401 → 自动登出
│ 403 → 提示无权限
└─ 5xx / Network Error → ElMessage.error("服务器错误")
loading.value = false ← 恢复交互
```
### 8.2 状态分类
| 状态类型 | 存储位置 | 示例 |
|---------|---------|------|
| 页面级临时状态 | 组件内 `ref()` / `reactive()` | 表格数据、表单数据、loading |
| 跨页面持久状态 | Pinia `auth.js` | token、用户名、角色 |
| UI 状态 | 组件内 | dialogVisible、activeTab |
| URL 状态 | `route.query` / `route.params` | 分页页码、详情页 ID |
---
## 9. 开发规范
### 9.1 新增页面检查清单
```
□ 文件命名:PascalCaseView.vue
□ 使用 <script setup> + Composition API
□ 所有 API 调用有 try/catch + apiErrorMessage
□ 列表有 v-loading / 提交按钮有 :loading
□ 路由 meta 包含 roles 鉴权
□ 表格表头使用 #F2F5FC 背景(继承 theme.css
□ 状态标签使用 .tag-active / .tag-revoked 等语义类
□ CTA 按钮使用 .btn-cta 带阴影
□ 色值使用 CSS 变量,不硬编码
□ 弹框使用 <el-dialog>(自动继承 8px 圆角)
```
### 9.2 禁止事项
| 禁止 | 原因 |
|------|------|
| 硬编码色值 (`#EAEFFA`) | 应使用 CSS 变量,便于换肤 |
| 直接操作 DOM (`document.querySelector`) | 使用 Vue 响应式 + ref |
| 在 `setup` 外定义响应式数据 | 非 `.vue` 文件需使用 `defineStore` |
| `v-if` + `v-for` 同时使用 | Vue 性能警告 |
| 忽略 try/catch | 未捕获异常导致白屏 |
| 提交 `console.log` | 使用 `ElMessage` 或移除 |
### 9.3 Git 提交规范
```
feat(web): 新增许可证管理页面
fix(web): 修复合同列表分页重置bug
refactor(web): 抽取 StatsCard 为独立组件
style(web): 统一表格表头样式
docs: 更新前端框架设计文档
```
---
## 10. 构建与部署
### 10.1 开发环境
```bash
cd web/delivery-platform-ui
npm install
npm run dev # → http://localhost:5173
# /api → proxy → http://127.0.0.1:8080
```
### 10.2 生产构建
```bash
# 指定后端地址
VITE_API_BASE=https://api.craftlabs.cn npm run build
# 产物:dist/
# 部署:Nginx 静态托管 + 反代 /api 到后端
```
### 10.3 CI/CD
```yaml
# .github/workflows/ci-platform.yml (前端部分)
- name: Build Frontend
run: |
cd web/delivery-platform-ui
npm ci
npm run build
```
### 10.4 Nginx 配置模板
```nginx
server {
listen 80;
server_name platform.craftlabs.cn;
root /var/www/delivery-platform-ui/dist;
index index.html;
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
try_files $uri $uri/ /index.html; # SPA fallback
}
}
```
---
## 附录 A:文件职责速查
| 文件 | 职责 | 谁需要修改 |
|------|------|-----------|
| `theme.css` | 全局色彩/圆角/阴影 Token | 设计师/UED |
| `MainLayout.vue` | 页面壳:Header/Sidebar/Breadcrumb | 架构师 |
| `router/index.js` | 路由表 + 鉴权守卫 | 全栈/前端 |
| `stores/auth.js` | JWT 认证状态 | 全栈/前端 |
| `api/platform.js` | 后端 API 封装 | 前端(新增接口时) |
| `utils/*.js` | 通用工具函数 | 前端 |
| `views/*View.vue` | 业务页面 | 前端 |
## 附录 B:快速上手
**创建一个新页面(以「审计日志」为例)**
1. 创建 `src/views/AuditLogView.vue`,使用标准列表页模板
2.`api/platform.js` 添加 `listAuditEvents()` 函数
3.`router/index.js` 添加路由,设置 `meta.roles`
4.`MainLayout.vue``menuItems` 数组添加菜单项
5. `npm run dev` 验证
---
*修订记录:2026-05-19 初版,基于设计体系 v1.0*