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

20 KiB
Raw Blame History

CraftLabs 前端框架设计 v1.0

基于设计体系 2026-05-19-design-system.md 提炼的前端工程框架规范
目标:统一架构、约束风格、加速开发、降低交接成本
日期:2026-05-19


目录

  1. 框架概述
  2. 技术栈
  3. 工程结构
  4. 核心模块
  5. 布局系统
  6. 组件体系
  7. 页面模板
  8. 数据流
  9. 开发规范
  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                 # ★ 全局主题 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
  │
  └─ 通过 → 正常渲染

新增页面只需

  1. children 数组中加一条路由
  2. 设置 meta.roles 控制可见角色
  3. 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 Plus90% 场景不需要封装
  • 抽取时机:同一模式在 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:快速上手

创建一个新页面(以「审计日志」为例)

  1. 创建 src/views/AuditLogView.vue,使用标准列表页模板
  2. api/platform.js 添加 listAuditEvents() 函数
  3. router/index.js 添加路由,设置 meta.roles
  4. MainLayout.vuemenuItems 数组添加菜单项
  5. npm run dev 验证

修订记录:2026-05-19 初版,基于设计体系 v1.0