mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
20 KiB
20 KiB
CraftLabs 前端框架设计 v1.0
基于设计体系
2026-05-19-design-system.md提炼的前端工程框架规范
目标:统一架构、约束风格、加速开发、降低交接成本
日期:2026-05-19
目录
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 # ★ 全局主题 Token(CSS 变量)
│ │
│ ├── 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 变量实现换肤。
/* 核心 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)
路由表结构:
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
│
└─ 通过 → 正常渲染
新增页面只需:
- 在
children数组中加一条路由 - 设置
meta.roles控制可见角色 component: () => import(...)懒加载
4.3 状态管理 (stores/auth.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
},
},
})
使用方式:
import { useAuthStore } from "../stores/auth"
const auth = useAuthStore()
// 模板中
auth.hasAnyRole(["SYS_ADMIN", "DEVELOPER"]) // 角色判断
auth.displayName // 用户名
4.4 API 层 (api/platform.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)
// 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 约定
/* ✅ 推荐: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 行):
<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 开发环境
cd web/delivery-platform-ui
npm install
npm run dev # → http://localhost:5173
# /api → proxy → http://127.0.0.1:8080
10.2 生产构建
# 指定后端地址
VITE_API_BASE=https://api.craftlabs.cn npm run build
# 产物:dist/
# 部署:Nginx 静态托管 + 反代 /api 到后端
10.3 CI/CD
# .github/workflows/ci-platform.yml (前端部分)
- name: Build Frontend
run: |
cd web/delivery-platform-ui
npm ci
npm run build
10.4 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:快速上手
创建一个新页面(以「审计日志」为例):
- 创建
src/views/AuditLogView.vue,使用标准列表页模板 - 在
api/platform.js添加listAuditEvents()函数 - 在
router/index.js添加路由,设置meta.roles - 在
MainLayout.vue的menuItems数组添加菜单项 npm run dev验证
修订记录:2026-05-19 初版,基于设计体系 v1.0