# 4.4 前端分层架构 :::tip 概述 前端采用 Vue 3 + TypeScript 组件化架构,分为 View(视图)→ API(接口)→ Store(状态)→ HTTP(请求)四层。辅以 Router(路由)、Directives(指令)、Hooks(组合式函数)、Utils(工具)等支撑模块。前端为本仓库 `ui/` 目录内的 AntdVue 管理后台。 ::: ## 架构全景 ```text ┌─────────────────────────────────────────────────────────────────────┐ │ 支撑模块 │ │ │ │ Router(路由) Directives(指令) Hooks(组合式函数) │ │ src/router/ src/directives/ src/hooks/ │ │ │ │ Utils(工具) Enums(枚举) Styles(样式) │ │ src/utils/ src/enums/ src/styles/ │ └──────────────────────────────┬──────────────────────────────────────┘ │ 被主干层引用 ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 主干链路 │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ View(视图层) │ │ │ │ 页面渲染、用户交互、调用 API 和 Store │ │ │ │ 文件:src/views/**/*.vue │ │ │ └──────────────────────────┬──────────────────────────────────┘ │ │ │ 调用 API 函数 │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ API(接口层) │ │ │ │ 封装后端 API 调用,定义请求参数和响应类型 │ │ │ │ 文件:src/api/**/*.ts │ │ │ └──────────────────────────┬──────────────────────────────────┘ │ │ │ 调用 http.request() │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ HTTP(请求层) │ │ │ │ Axios 封装:拦截器、Token 携带、错误处理 │ │ │ │ 文件:src/utils/http/axios/ │ │ │ └──────────────────────────┬──────────────────────────────────┘ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 后端 API(RESTful JSON,{code, ok, msg, data}) │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Store(状态层) │ │ │ │ 全局状态管理:Token、用户信息、权限列表、动态路由 │ │ │ │ 文件:src/store/modules/*.ts │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ ``` ## 各层职责详解 ### View 层(视图) **文件**:`src/views/**/*.vue` **职责**:页面渲染、用户交互、调用 API 获取数据 ```text src/views/ ├── system/ 系统管理 │ ├── user/ 用户管理 │ │ ├── index.vue 列表页 │ │ └── edit.vue 编辑弹窗 │ ├── role/ 角色管理 │ └── menu/ 菜单管理 ├── content/ 内容管理 │ ├── article/ 文章管理 │ └── category/ 分类管理 ├── login/ 登录页 ├── dashboard/ 仪表盘 └── ... ``` **典型页面结构:** ```vue ``` ### API 层(接口) **文件**:`src/api/**/*.ts` **职责**:封装后端 API 调用,一个后端模块对应一个 API 文件 **命名规范:** | 操作 | 函数名 | HTTP 方法 | 参数传递 | |------|--------|----------|----------| | 分页查询 | `get{Module}Page` | GET | `params` | | 全量列表 | `get{Module}List` | GET | `params` | | 详情 | `get{Module}Detail` | GET | URL 参数 | | 新增 | `{module}Add` | POST | `data` | | 修改 | `{module}Update` | PUT | `data` | | 删除 | `{module}Delete` | DELETE | URL 参数 | | 批量删除 | `{module}BatchDelete` | **DELETE**(带 body) | `data`(`{ids:[]}`) | | 导入 | `{module}Import` | POST | FormData | | 导出 | `{module}Export` | GET | `params` | :::warning 批量删除方法为 DELETE 本项目 `batchDelete` 路由为 **DELETE**(请求体仍传 `ids` 数组——DELETE 方法携带 body),见 [3.7 路由注册](/zh/guide/route.html)。 ::: **完整示例:** ```typescript // src/api/tool/example.ts import { http } from '@/utils/http/axios'; // 分页查询 export function getExamplePage(params?) { return http.request({ url: '/example/page', method: 'GET', params }); } // 详情 export function getExampleDetail(id) { return http.request({ url: '/example/detail/' + id, method: 'GET' }); } // 新增 export function exampleAdd(data: any) { return http.request({ url: '/example/add', method: 'POST', data }); } // 修改 export function exampleUpdate(data: any) { return http.request({ url: '/example/update', method: 'PUT', data }); } // 删除 export function exampleDelete(id) { return http.request({ url: '/example/delete/' + id, method: 'DELETE' }); } // 批量删除(DELETE + 请求体 ids 数组) export function exampleBatchDelete(data: any) { return http.request({ url: '/example/batchDelete', method: 'DELETE', data }); } ``` ### HTTP 层(请求) **文件**:`src/utils/http/axios/` **职责**:Axios 封装,自动处理 Token、响应码、错误提示 ```text src/utils/http/axios/ ├── index.ts Axios 实例配置和拦截器 ├── Axios.ts VAxios 类封装 ├── axiosTransform.ts 数据转换接口 ├── checkStatus.ts HTTP 状态码处理 ├── helper.ts 辅助函数 └── types.ts 类型定义 ``` **核心拦截器:** ```text 请求拦截器: ┌──────────────────────────────────────────┐ │ 1. 从 Store 获取 Token │ │ 2. 自动添加 Authorization: Bearer │ └──────────────────────────────────────────┘ 响应拦截器: ┌──────────────────────────────────────────┐ │ code === 0 → 成功,返回 data │ │ code === 401 → Token 过期,跳转登录页 │ │ code === 403 → 权限不足,显示错误提示 │ │ 其它 code → 业务失败,显示 msg │ └──────────────────────────────────────────┘ ``` ### Store 层(状态) **文件**:`src/store/modules/*.ts` **职责**:全局状态管理(Pinia) | Store | 文件 | 管理的状态 | |-------|------|-----------| | userStore | `user.ts` | Token、用户信息、权限列表、登录/登出 | | asyncRouteStore | `asyncRoute.ts` | 动态路由、keep-alive 缓存列表 | | projectSettingStore | `projectSetting.ts` | 项目配置(导航/顶栏主题、布局等) | | tabsViewStore | `tabsView.ts` | 标签页导航状态 | | lockscreenStore | `lockscreen.ts` | 锁屏状态 | **userStore 核心流程:** ```text 登录: userStore.login(userInfo) → POST /login(含 key/code 验证码参数) → 获取 access_token → Storage.set(ACCESS_TOKEN, token) 获取用户信息: userStore.GetInfo() → GET /index/getUser → setPermissions(res.permissions) → setUserInfo(res) 获取菜单(动态路由): → GET /index/getMenus 登出: userStore.logout() → POST /logout → 清除 Token、用户信息、权限列表 → 跳转登录页 ``` ## 路由与动态菜单 ### 路由架构 ```text 静态路由(固定,src/router/) ├── /login 登录页 ├── / 根路径,重定向到首页 ├── /redirect/:path 重定向路由 └── /:path(.*)* 404 错误页 动态路由(从后端菜单生成) ├── /dashboard 仪表盘 ├── /system/user 用户管理 ├── /system/role 角色管理 ├── /content/article 文章管理 └── ... ``` ### 动态路由生成流程 ```text 1. 用户登录 → 获取 Token → 存入 Store 2. 路由守卫(router.beforeEach)检测到 Token 3. 调用 GET /index/getMenus 获取菜单树(后端按用户权限过滤) 4. generator-routers.ts 将菜单树转换为路由表 5. router.addRoute() 动态注入路由 6. 渲染侧边栏菜单 ``` ### 布局系统 ```text ┌─────────────────────────────────────────────────────────────┐ │ 顶部导航栏 │ │ [Logo] [面包屑] [搜索] [通知] [头像] │ ├──────────┬──────────────────────────────────────────────────┤ │ │ │ │ 侧边栏 │ 内容区域 │ │ 菜单 │ │ │ │ ┌──────────────────────────────────────────┐ │ │ ──────── │ │ 标签页导航 │ │ │ 系统管理 │ ├──────────────────────────────────────────┤ │ │ ├ 用户 │ │ │ │ │ ├ 角色 │ │ 页面内容(keep-alive 缓存) │ │ │ └ 菜单 │ │ │ │ │ │ └──────────────────────────────────────────┘ │ └──────────┴──────────────────────────────────────────────────┘ ``` ## 权限控制 ### 按钮级权限指令 ```vue 新增 操作 ``` 无权限时元素直接从 DOM 移除,而非仅隐藏。 :::tip 超级管理员 用户权限列表包含 `*:*:*` 时,表示超级管理员,拥有所有权限,`v-perm`/`v-perms` 指令始终放行。 ::: :::warning 前端权限不是安全防线 `v-perm` 只是体验层(隐藏按钮);真正的安全校验在后端 `#[Permission]` 注解 + `auth.jwt` 中间件(见 [3.8 权限节点配置](/zh/guide/permission.html))。 ::: ## 支撑模块 ### Hooks(组合式函数) | Hook | 文件 | 说明 | |------|------|------| | `usePermission` | `hooks/web/usePermission.ts` | 权限判断(hasPermission、hasSomePermission) | | `useTable` | `hooks/web/useTable.ts` | 表格通用逻辑(分页、搜索、刷新) | | `useForm` | `hooks/web/useForm.ts` | 表单通用逻辑(提交、重置、校验) | ### Directives(自定义指令) | 指令 | 说明 | |------|------| | `v-perm` | 单权限控制 | | `v-perms` | 多权限控制(满足任一) | ### Utils(工具函数) | 工具 | 文件 | 说明 | |------|------|------| | HTTP | `utils/http/axios/` | Axios 封装 | | Storage | `utils/Storage.ts` | localStorage 封装 | | Dict | `utils/dict.ts` | 字典工具(从 Store 获取字典值) | ## 数据流向 ### 完整请求链路 ```text 用户操作(点击搜索按钮) │ ▼ View: fetchData() │ 调用 API 函数 ▼ API: getExamplePage(params) │ 调用 http.request() ▼ HTTP: 请求拦截器添加 Token │ 发送 GET /example/page?pageNo=1&pageSize=20 ▼ 后端: Controller → Service → Model → Database │(响应 {code, ok, msg, data},snake→camel 已在后端完成) ▼ HTTP: 响应拦截器判断 code │ code === 0 → 返回 data ▼ API: 返回 data │ ▼ View: tableData.value = res.records; total.value = res.total │ ▼ Vue 响应式 → 表格自动刷新 ``` ### Store 数据流 ```text 登录成功 │ ▼ userStore.setToken(token) → 响应式更新 userStore.setUserInfo(info) → 响应式更新 userStore.setPermissions(perm) → 响应式更新 │ ▼ 其他组件通过 userStore.username / userStore.permissions 读取 │ ▼ v-perm 指令读取 permissions 列表 → 控制元素显隐 ``` ## 前后端对应关系 | 后端 | 前端 | 说明 | |------|------|------| | `Http/Controllers/Api/UserController.php` | `src/api/system/user.ts` | API 函数映射 | | `Services/UserService.php` | `src/views/system/user/` | 业务页面 | | `Http/Requests/UserRequest.php` 的 rules | 表单 `rules` | 前端表单校验规则(体验层) | | `Models/User.php` | — | 前端不直接操作模型 | | `routes/api.php` | `src/api/**/*.ts` | 路由 ↔ API 函数 | | 字典数据(`dict` 表) | `src/enums/` + 字典 Store | 枚举常量 | | `#[Permission]` 注解 | `v-perm` 指令 | 权限码对应 | | 分页 `records/total/current/size/pages` | a-pagination 绑定 | 固定分页结构 | ## 层间通信规范 | 调用方向 | 是否允许 | 说明 | |----------|---------|------| | View → API | ✅ | 标准调用 | | View → Store | ✅ | 读取/修改全局状态 | | View → Hooks | ✅ | 复用逻辑 | | API → HTTP | ✅ | 调用 http.request() | | API → Store | ❌ | API 层不应操作状态 | | Store → API | ✅ | Store actions 中调用 API | | HTTP → Store | ⚠️ | 仅在拦截器中读取 Token | | Hooks → API | ✅ | Hooks 中可调用 API |