# 4.4 前端分层架构
:::tip 概述
前端采用 Vue 3 + TypeScript 组件化架构,分为 View(视图)→ API(接口)→ Store(状态)→ HTTP(请求)四层。辅以 Router(路由)、Directives(指令)、Hooks(组合式函数)、Utils(工具)等支撑模块。
:::
## 架构全景
```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) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 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.vue 登录页
├── 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 | `data` |
| 导入 | `{module}Import` | POST | FormData |
| 导出 | `{module}Export` | GET | `params` |
**完整示例:**
```typescript
// src/api/system/user.ts
import { http } from '@/utils/http/axios';
// 分页查询
export function getUserPage(params?) {
return http.request({ url: '/user/page', method: 'GET', params });
}
// 详情
export function getUserDetail(id) {
return http.request({ url: '/user/detail/' + id, method: 'GET' });
}
// 新增
export function userAdd(data: any) {
return http.request({ url: '/user/add', method: 'POST', data });
}
// 修改
export function userUpdate(data: any) {
return http.request({ url: '/user/update', method: 'PUT', data });
}
// 删除
export function userDelete(id) {
return http.request({ url: '/user/delete/' + id, method: 'DELETE' });
}
// 批量删除
export function userBatchDelete(data: any) {
return http.request({ url: '/user/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 → 获取 access_token
→ Storage.set(ACCESS_TOKEN, token)
→ this.setToken(token)
获取用户信息:
userStore.GetInfo()
→ GET /index/getUserInfo
→ this.setPermissions(res.permissions)
→ this.setUserInfo(res)
登出:
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` 指令始终放行。
:::
## 支撑模块
### Hooks(组合式函数)
| Hook | 文件 | 说明 |
|------|------|------|
| `usePermission` | `hooks/web/usePermission.ts` | 权限判断(hasPermission、hasEveryPermission、hasSomePermission) |
| `usePage` | `hooks/web/usePage.ts` | 分页参数管理 |
| `useECharts` | `hooks/web/useECharts.ts` | ECharts 图表挂载与销毁 |
| `useForm` | `components/Form/src/hooks/useForm.ts` | 表单注册(BasicForm 配合 `@register` 使用) |
### Directives(自定义指令)
| 指令 | 说明 |
|------|------|
| `v-perm` | 单权限控制 |
| `v-perms` | 多权限控制(满足任一) |
### Utils(工具函数)
| 工具 | 文件 | 说明 |
|------|------|------|
| HTTP | `utils/http/axios/` | Axios 封装 |
| Storage | `utils/Storage.ts` | localStorage 封装 |
| useLockFn | `utils/useLockFn.ts` | 防重复提交(请求级加锁) |
## 数据流向
### 完整请求链路
```text
用户操作(点击搜索按钮)
│
▼
View: fetchData()
│ 调用 API 函数
▼
API: getExamplePage(params)
│ 调用 http.request()
▼
HTTP: 请求拦截器添加 Token
│ 发送 GET /example/page?pageNo=1&pageSize=20
▼
后端: Controller → Logic → Model → Database
│
▼
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 列表 → 控制元素显隐
```
## 前后端对应关系
| 后端 | 前端 | 说明 |
|------|------|------|
| `app/controller/UserController.php` | `src/api/system/user.ts` | API 函数映射 |
| `app/logic/UserLogic.php` | `src/views/system/user/` | 业务页面 |
| `app/model/User.php` | — | 前端不直接操作模型 |
| `app/validate/UserValidate.php` | — | 前端校验由表单组件处理 |
| `app/validate/*Validate.php` 的场景 | 表单 `rules` | 前端表单校验规则 |
| `route/app.php` | `src/api/**/*.ts` | 路由 ↔ API 函数 |
| 后端字典 `DictService` | `src/enums/` + 字典 Store | 枚举常量 |
| 后端 `#[Permission]` 注解 | `v-perm` 指令 | 权限码对应 |
## 层间通信规范
| 调用方向 | 是否允许 | 说明 |
|----------|---------|------|
| 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 |