Skip to content

系统架构

理解纯前端商业版时,最重要的不是记住目录名称,而是理解请求、状态、路由和页面之间如何协作。本章从运行时流程出发,说明项目的主要分层和关键边界。

总体分层

text
┌──────────────────────────────────────────────┐
│ 页面层  src/views                            │
│ 页面、搜索组件、弹窗、图表、业务交互         │
├──────────────────────────────────────────────┤
│ 复用层  src/components + src/hooks           │
│ ArtTable、ArtSearchBar、useTable、useAuth     │
├──────────────────────────────────────────────┤
│ 应用层  src/store + src/router + src/services │
│ 状态、路由与可选业务流程编排                   │
├──────────────────────────────────────────────┤
│ 数据层  src/api + src/mock + src/utils/http   │
│ 真实 HTTP、预置数据、统一响应与错误处理        │
├──────────────────────────────────────────────┤
│ 配置层  src/config + .env* + vite.config.ts   │
│ 品牌、布局、主题、代理、构建和菜单来源         │
└──────────────────────────────────────────────┘

页面层不应该知道 Token 如何附加,也不应该自行判断当前使用本地还是远程菜单。每层只处理自己的职责,后续替换后端接口时才能保持改动范围可控。

应用启动流程

入口位于 src/main.ts

text
createApp(App)
  -> initStore(app)
  -> initRouter(app)
  -> setupGlobDirectives(app)
  -> setupErrorHandle(app)
  -> setupElementPlusLocale()
  -> app.use(i18n)
  -> app.mount('#app')
  -> 异步加载站点公共配置

src/App.vue 在根组件中完成主题初始化、存储兼容检查、版本更新检查和页面标题同步。业务预置数据不需要在启动时集中加载,各页面会在进入时按需调用 src/mock

这种启动方式避免了“为了进入登录页就加载全部业务数据”,也让站点配置失败不会阻塞应用挂载。

登录与会话恢复

认证 HTTP 接口统一定义在 src/api/auth.ts。当前源码中的 src/services/auth.ts 只负责登录结果校验和会话恢复等流程语义,内部仍然调用 API 模块,不承担接口地址定义。

首次登录

text
登录页提交账号密码
  -> authService.signIn()
  -> POST /api/v1/auth/signin
  -> 校验 accessToken 非空
  -> userStore 保存 Token
  -> 进入目标页面
  -> 路由守卫请求 GET /api/v1/user/info
  -> 保存用户、角色和按钮权限
  -> 初始化菜单与动态路由

页面刷新

text
Pinia 恢复持久化 accessToken
  -> authService.restoreSession()
  -> 标记登录态可继续恢复
  -> GET /api/v1/user/info 校验 Token
  -> 使用最新用户信息重建菜单与动态路由

项目只持久化 Access Token,不长期持久化用户资料、角色和按钮权限。这样刷新页面时会重新获取服务端的最新权限,避免账号权限变更后浏览器仍长期使用旧数据。

当前没有 Refresh Token 流程

restoreSession() 只恢复本地 Access Token,不会刷新令牌。Token 过期时,请求层收到 401 后会清理登录态并返回登录页。如果客户后端需要双令牌协议,应在 src/api/auth.ts 中增加刷新接口,并在 src/utils/http 与用户状态中实现刷新和清理流程。

接口字段、401 清理和扩展边界见登录与会话

HTTP 请求链路

统一请求入口位于 src/utils/http/index.ts

text
src/api/*.ts
  -> request.get/post/put/patch/del
  -> Axios 请求拦截器
      -> 添加 Authorization: Bearer <accessToken>
      -> JSON 请求体序列化
  -> 后端响应 { code, msg, data }
  -> 响应拦截器
      -> code === 200 返回 data
      -> code === 401 统一登出
      -> 其他错误转换为 HttpError

默认超时时间为 15 秒,当前自动重试次数为 0。不要在页面中假设失败请求会自动重试,也不要在多个页面重复显示相同错误提示。

菜单与动态路由

菜单初始化由 src/router/guards/beforeEach.ts 触发,核心流程如下:

text
进入受保护页面
  -> 获取当前用户
  -> MenuProcessor.getMenuList()
      -> local: 前端路由按 roles 过滤
      -> remote: 请求 /api/v1/user/menus
  -> 校验菜单结构与路径
  -> 规范化嵌套路由路径和默认 redirect
  -> RouteRegistry.register()
  -> 菜单写入 menuStore
  -> 恢复原目标地址

远程菜单采用失败关闭策略:路由名称重复、组件路径不安全、外链不是 HTTP(S)、权限字段格式错误等情况都会拒绝注册整棵路由树,并进入错误页,而不是只注册一半菜单。

权限模型

当前用户接口返回两个关键字段:

ts
interface UserInfo {
  roles: string[]
  buttons: string[]
}
  • roles 用于本地菜单过滤、v-roleshasRole
  • buttons 用于 v-authhasAuth
  • 路由访问还会持续与当前菜单树校验,不能仅靠 Vue Router 已经匹配就放行。
  • 后端仍必须对每个数据接口和写操作执行真实鉴权。

完整配置见路由与权限

数据来源架构

业务页面显式选择数据来源:

ts
// 预置数据阶段
import { getDemoUserList } from '@/mock/system/organization'

// 接入后端后
import { fetchUserList } from '@/api/customer/user'

项目没有自动 Provider、Local Adapter 或“接口失败后退回 Mock”的机制。这样设计可以避免生产接口异常被前端预置数据掩盖。

src/mock 中的读取函数通常返回深拷贝,并通过分页和筛选工具模拟异步查询;写操作调用统一提示,明确不持久化。详细约束见本地数据模式

状态管理

Pinia 入口位于 src/store/index.ts,持久化键由 StorageKeyManager 统一管理。主要 Store 包括:

Store职责持久化重点
userToken、用户、语言、锁屏和搜索状态Token、语言及有限用户偏好
menu当前菜单树和首页路径运行时菜单为主
setting布局、主题、圆角和界面开关完整用户设置
worktab已打开标签和保活排除工作区体验
table表格列偏好用户表格设置
site-settings站点品牌与公共配置本地站点设置
notification通知展示状态当前前端会话能力

不要把 Pinia 持久化扩展成生产业务数据库。业务数据需要多用户一致性、审计或备份时,应由后端持久化。

各 Store 的职责、持久化键和账号隔离规则见状态管理

关键设计原则

  1. 认证接口最小化:首次运行只依赖登录、退出和当前用户。
  2. 页面渐进迁移:Mock 与真实 API 可以按页面并存。
  3. 失败不伪装成功:未接后端的写操作必须明确说明边界。
  4. 前端权限不越权:前端负责体验,后端负责安全。
  5. 接口目录统一:所有 HTTP 接口通过 src/api 暴露,页面再结合 Hook 和 Store 使用,避免 URL 与请求逻辑散落。

理解这些原则后,再阅读项目结构会更容易建立“需求到文件”的映射。

根据 MIT 许可证发布