签入版本
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
# 9.13 API 模块编写规范
|
||||
|
||||
:::tip 概述
|
||||
api 模块按域组织(src/api/{域}/{模块}.ts),每接口一个导出函数,内部 http.request 调用。url 对齐后端 gmeta path(无 /api 前缀、无 /v1)。本节给出规范与真实示例。
|
||||
:::
|
||||
|
||||
## 文件组织
|
||||
|
||||
```text
|
||||
src/api/{域}/{模块}.ts 域与后端模块域一致(system/data/content/monitor/tool)
|
||||
src/api/common/*.ts 跨域公共接口(city/index/menu/user)
|
||||
命名:驼峰(fileTemplate.ts、onlineUser.ts、cacheMonitor.ts)
|
||||
函数命名:get{Mod}Page / get{Mod}Detail / {mod}Add / {mod}Update /
|
||||
{mod}Delete / {mod}BatchDelete / {mod}Import / {mod}Export
|
||||
```
|
||||
|
||||
## 真实示例(摘自 src/api/tool/example.ts)
|
||||
|
||||
```ts
|
||||
import { http } from '@/utils/http/axios';
|
||||
|
||||
/**
|
||||
* 案例演示模块接口
|
||||
*
|
||||
* 提供案例演示的增删改查及导入导出接口调用。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 获取案例演示分页列表
|
||||
*
|
||||
* @param params 查询参数,包含 pageNo、pageSize 及业务筛选条件
|
||||
* @returns 分页结果,含 records、total、size、current、pages
|
||||
*/
|
||||
export function getExamplePage(params?: Record<string, any>) {
|
||||
return http.request({
|
||||
url: '/example/page',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 获取案例演示详情
|
||||
*
|
||||
* @param id 案例演示ID
|
||||
* @returns 案例演示详情数据
|
||||
*/
|
||||
export function getExampleDetail(id) {
|
||||
return http.request({
|
||||
url: '/example/detail/' + id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加案例演示
|
||||
*
|
||||
* @param data 案例演示数据
|
||||
* @returns 新记录的 ID
|
||||
*/
|
||||
export function exampleAdd(data: any) {
|
||||
return http.request({
|
||||
url: '/example/add',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## url 对齐规则
|
||||
|
||||
```text
|
||||
前端 api url 后端 gmeta path 说明
|
||||
'/example/page' ↔ /api/example/page baseURL 已含 /api
|
||||
'/example/detail/' + id ↔ /api/example/detail/:id 路由参数拼接
|
||||
'/config/item/page' ↔ /api/config/item/page 子资源前缀对齐
|
||||
|
||||
禁止:写 '/api/example/page'(重复);写 '/v1/...'(无版本段)
|
||||
```
|
||||
|
||||
## 请求方法与参数位置
|
||||
|
||||
| 方法 | 参数位置 | 说明 |
|
||||
|------|---------|------|
|
||||
| GET | params | 查询参数(分页/筛选/路由参数) |
|
||||
| POST/PUT | data | JSON body(AddReq/UpdateReq 结构) |
|
||||
| DELETE | url 路径参数 或 data | /delete/:id 走路径;/batchDelete 走 data {ids:[]} |
|
||||
|
||||
## 导入导出函数
|
||||
|
||||
```ts
|
||||
// 导入:multipart(组件内组装 FormData,api 层可直接透传)
|
||||
export function exampleImport(data: any) {
|
||||
return http.request({
|
||||
url: '/example/import',
|
||||
method: 'POST',
|
||||
data, // FormData(Content-Type 自动 multipart)
|
||||
});
|
||||
}
|
||||
|
||||
// 导出:GET + 筛选参数(导出=所见)
|
||||
export function getExampleExport(params?: Record<string, any>) {
|
||||
return http.request({
|
||||
url: '/example/export',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 编写规约
|
||||
|
||||
```text
|
||||
1. 一接口一函数,JSDoc 注释(参数/返回/用途)——生成器模板同款风格
|
||||
2. url 从后端 gmeta path 抄写(去掉 /api 前缀),改动后端 path 同步前端
|
||||
3. method 与 gmeta method 一致(大小写不敏感但团队统一大写 GET/POST/PUT/DELETE)
|
||||
4. 不在 api 层做数据加工(transform 解包已完成;加工放页面/组合式函数)
|
||||
5. 复杂请求(上传/下载/超时定制)用 RequestOptions 第三参数定制
|
||||
6. 新增函数后对照 /api.json 核对路径与方法
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
# 9.2 目录结构详解
|
||||
|
||||
:::tip 概述
|
||||
前端目录按"共享层平铺、页面层按域分目录"组织:api/views 按域(system/data/content/monitor/tool)对应后端模块域;components/store/utils/hooks 全局共享。本节逐层说明。
|
||||
:::
|
||||
|
||||
## src 目录树
|
||||
|
||||
```text
|
||||
ui/src/
|
||||
├── App.vue
|
||||
├── main.ts # 入口:挂载 Pinia/Router/ElementPlus 等
|
||||
├── api/ # 接口模块(按域分目录)
|
||||
│ ├── common/ # city/index/menu/user(跨域公共接口)
|
||||
│ ├── content/ # article/category/link
|
||||
│ ├── dashboard/ # console/message
|
||||
│ ├── data/ # city/config/dictionary/notice/param
|
||||
│ ├── file/ # fileTemplate
|
||||
│ ├── monitor/ # cacheMonitor/job/onlineUser/serverMonitor…
|
||||
│ ├── region/ # region(区划)
|
||||
│ ├── setting/ # profile/web
|
||||
│ ├── system/ # dept/dictionary/level/loginLog/menu/operLog/position/role…
|
||||
│ └── tool/ # example/generator
|
||||
├── components/ # 公共组件(25 类)
|
||||
│ ├── Application Authority CheckToken ChinaArea CountTo Cropper Editor
|
||||
│ ├── Excel Form Lockscreen Modal Page Password Qrcode Region Render
|
||||
│ ├── Select Table TableSelect Upload Websocket icon importFile
|
||||
│ ├── numberInput pagination priceInput …
|
||||
├── directives/ # 自定义指令
|
||||
│ ├── permission.ts # 权限指令(按钮级显隐)
|
||||
│ ├── clickOutside.ts / scrollBar.ts
|
||||
├── enums/ # 枚举常量
|
||||
│ ├── httpEnum.ts # RequestEnum/ResultEnum/ContentTypeEnum
|
||||
│ ├── pageEnum.ts cacheEnum.ts permissionsEnum.ts roleEnum.ts …
|
||||
├── hooks/ # 组合式函数
|
||||
│ ├── core/ event/ setting/ web/
|
||||
│ ├── use-async.ts useBattery.ts useDomWidth.ts useOnline.ts useTime.ts
|
||||
├── layout/ # 布局系统(侧栏/顶栏/标签页/面包屑)
|
||||
├── router/
|
||||
│ ├── constant.ts # 常量路由(login/redirect/403/404…)
|
||||
│ ├── generator-routers.ts # 动态菜单路由生成(消费 getMenus)
|
||||
│ ├── router-guards.ts # 守卫(token 校验/动态路由注入/进度条)
|
||||
│ ├── base.ts router-icons.ts menus/ types.ts index.ts
|
||||
├── settings/ # 全局设置
|
||||
│ ├── projectSetting.ts # 项目设置(主题/水印/标签页等)
|
||||
│ ├── designSetting.ts componentSetting.ts animateSetting.ts
|
||||
├── store/ # Pinia
|
||||
│ ├── modules/
|
||||
│ │ ├── user.ts # 登录/令牌/身份信息
|
||||
│ │ ├── asyncRoute.ts # 动态路由
|
||||
│ │ ├── tabsView.ts # 标签页
|
||||
│ │ ├── designSetting.ts projectSetting.ts lockscreen.ts ossConfig.ts
|
||||
│ ├── mutation-types.ts # ACCESS_TOKEN 等存储键常量
|
||||
│ ├── index.ts types.ts plugins/
|
||||
├── styles/ # 全局样式与主题变量(Element Plus 变量覆盖)
|
||||
├── utils/
|
||||
│ ├── http/axios/ # HTTP 封装(VAxios/transform/checkStatus…)
|
||||
│ ├── auth.ts Storage.ts # 路径工具 / 本地存储封装
|
||||
│ ├── downloadFile.ts dateUtil.ts validate.ts propTypes.ts …
|
||||
│ ├── file/ helper/ is/ lib/
|
||||
├── views/ # 页面(按域分目录)
|
||||
│ ├── system/ data/ content/ monitor/ tool/ file/ setting/
|
||||
│ ├── dashboard/ login/ exception/ iframe/ redirect/ about/
|
||||
└── plugins/ assets/
|
||||
```
|
||||
|
||||
## 命名约定
|
||||
|
||||
```text
|
||||
api 文件:驼峰(fileTemplate.ts、onlineUser.ts)
|
||||
views 目录/文件:域/驼峰模块(content/article/index.vue)
|
||||
配置文件:camelCase(columns.ts、querySchemas.ts)
|
||||
组件:PascalCase(.vue)
|
||||
后端对照:前端驼峰 ↔ 后端 snake(fileTemplate ↔ file_template)——
|
||||
联调时注意两边命名形态不同
|
||||
```
|
||||
|
||||
## 页面标准五件套
|
||||
|
||||
```text
|
||||
views/{域}/{模块}/
|
||||
├── index.vue # 列表页(分页表格 + 筛选 + 增删改入口)
|
||||
├── edit.vue # 新增/编辑弹窗
|
||||
├── detail.vue # 详情
|
||||
├── columns.ts # 表格列配置
|
||||
└── querySchemas.ts # 筛选表单配置
|
||||
|
||||
(生成器树形模块用 ui2 模板:index/edit/detail 三件,无 columns/querySchemas)
|
||||
```
|
||||
|
||||
## 分目录的理由
|
||||
|
||||
| 层 | 组织方式 | 理由 |
|
||||
|----|---------|------|
|
||||
| api/views | 按域分目录 | 与后端模块域、生成器 genDomainMap 一致,心智对齐 |
|
||||
| components/hooks/utils | 全局平铺 | 跨域共享,避免"通用组件放哪个域"的争议 |
|
||||
| enums/settings/styles | 全局 | 全局配置面 |
|
||||
| store/modules | 按状态域一文件 | user/asyncRoute 等状态域清晰 |
|
||||
@@ -0,0 +1,78 @@
|
||||
# 9.9 数据展示组件
|
||||
|
||||
:::tip 概述
|
||||
components/ 提供 25 类公共组件,覆盖数据展示(Table/Page/CountTo/Qrcode/Render)、容器(Modal)、辅助(icon/Websocket/Lockscreen)等场景。本节介绍高频展示组件。
|
||||
:::
|
||||
|
||||
## 高频展示组件
|
||||
|
||||
| 组件 | 职责 | 典型用法 |
|
||||
|------|------|---------|
|
||||
| Table | 表格封装(columns 配置驱动) | 列表页主表格,对接 columns.ts |
|
||||
| pagination | 分页组件 | 配合 Table,切换触发重新请求 |
|
||||
| Page | 页面容器(标题/操作区/搜索区) | 列表页外框 |
|
||||
| Render | 函数式渲染(h 函数封装) | 复杂列渲染 |
|
||||
| CountTo | 数字滚动动画 | 仪表盘统计卡片 |
|
||||
| Qrcode | 二维码生成 | 扫码场景 |
|
||||
| icon | 图标组件 | 菜单/操作图标 |
|
||||
| Modal | 弹窗封装 | 编辑/详情弹窗 |
|
||||
| TableSelect | 表格选择器 | 单选/多选弹窗选择数据 |
|
||||
| Websocket | WebSocket 封装 | 实时消息(前端遗留能力) |
|
||||
|
||||
## Table 与 columns.ts 的配合(真实模式)
|
||||
|
||||
```ts
|
||||
// columns.ts 定义列(真实模式摘自 example)
|
||||
export const columns = [
|
||||
{ type: 'selection' }, // 多选框列
|
||||
{ label: 'ID', prop: 'id', fixed: 'left', width: 50 },
|
||||
{ label: '案例名称', prop: 'name', minWidth: 120 }, // 普通文本
|
||||
{
|
||||
label: '案例图片', prop: 'avatar', minWidth: 120,
|
||||
// 图片渲染:以方形头像形式展示
|
||||
render(record) {
|
||||
return h(ElAvatar, { size: 48, src: record.row.avatar, shape: 'square', fit: 'fill' });
|
||||
},
|
||||
},
|
||||
// 枚举列建议渲染 {field}Text(statusText)
|
||||
];
|
||||
```
|
||||
|
||||
渲染规则约定:图片列 ElAvatar、选项列 ElTag、链接列 a 标签、其余普通文本。
|
||||
|
||||
## 展示 {field}Text(枚举列)
|
||||
|
||||
```text
|
||||
后端基座已填充 statusText 等字典文本 → columns 直接 prop: 'statusText'
|
||||
或 render 用 ElTag 展示 text + 语义色
|
||||
唯一真相在字典管理页(前端不硬编码枚举文案)
|
||||
```
|
||||
|
||||
## Page 容器布局(列表页标准形态)
|
||||
|
||||
```text
|
||||
<Page>
|
||||
搜索区(querySchemas 驱动的 Form)
|
||||
操作区(新增/导出等按钮,v-permission 控制)
|
||||
表格区(Table + pagination)
|
||||
</Page>
|
||||
|
||||
弹窗层:edit.vue(新增/编辑)、detail.vue(详情)
|
||||
```
|
||||
|
||||
## 与其他层的对接
|
||||
|
||||
```text
|
||||
Table data ← http 解包后的 data.records(PageData 声明)
|
||||
pagination ← data.total / data.size / data.current
|
||||
columns ← views/{域}/{模块}/columns.ts
|
||||
弹窗表单 ← edit.vue(字段对齐 Req 结构体)
|
||||
```
|
||||
|
||||
## 开发规约
|
||||
|
||||
+ 列表列用 columns.ts 声明式配置(勿在模板里手写 el-table-column)
|
||||
+ 复杂列用 render 函数(h + Element Plus 组件)
|
||||
+ 图片列 src 用后端 GetFileUrl 已补域名的字段(勿手拼)
|
||||
+ 枚举列展示 {field}Text(勿硬编码映射)
|
||||
+ 操作列按钮逐个加 v-permission(对齐 gmeta permission)
|
||||
@@ -0,0 +1,82 @@
|
||||
# 9.3 环境与构建配置
|
||||
|
||||
:::tip 概述
|
||||
前端构建基于 Vite 多环境模式:dev/test/uat/prod 四套构建命令 + 环境变量文件。开发环境经 Vite proxy 代理 /api 到后端;生产构建产物交 Nginx 托管。
|
||||
:::
|
||||
|
||||
## 构建命令(package.json)
|
||||
|
||||
```sh
|
||||
pnpm dev # 开发服务器(vite)
|
||||
pnpm build # production 模式构建 + postBuild 后处理
|
||||
pnpm build:prod # --mode production
|
||||
pnpm build:uat # --mode uat
|
||||
pnpm build:test # --mode test
|
||||
pnpm build:dev # --mode dev
|
||||
pnpm preview # 构建 + vite preview 本地预览
|
||||
pnpm type:check # vue-tsc --noEmit --skipLibCheck
|
||||
```
|
||||
|
||||
构建产物经 `build/script/postBuild.ts` 后处理输出 `dist/`。
|
||||
|
||||
## Vite 配置要点(vite.config.ts)
|
||||
|
||||
```text
|
||||
关键配置:
|
||||
* Vite 端口与 host
|
||||
* proxy:/api → http://127.0.0.1:8000(开发代理,同源化)
|
||||
* 别名 @ → src
|
||||
* unplugin-auto-import / 组件自动注册等工程插件
|
||||
```
|
||||
|
||||
开发代理让前端"同源"访问后端——dev 环境无 CORS 问题;生产建议 Nginx 同域反代 /api。
|
||||
|
||||
## 环境变量
|
||||
|
||||
```text
|
||||
.env.development / .env.test / .env.uat / .env.production
|
||||
|
||||
常用键(以项目实际文件为准):
|
||||
VITE_PORT 开发端口(默认 8001)
|
||||
VITE_PUBLIC_PATH 部署公网路径(子路径部署时配置)
|
||||
VITE_GLOB_API_URL API 地址(baseURL,一般留空,由前缀拼接)
|
||||
VITE_GLOB_API_URL_PREFIX API 版本前缀(/api,与后端公开路径规则配套)
|
||||
VITE_GLOB_UPLOAD_URL 上传接口地址
|
||||
VITE_GLOB_IMG_URL 图片服务地址(对应后端 file.domainUrl)
|
||||
```
|
||||
|
||||
代码经 `useGlobSetting()`(hooks/setting)读取环境变量,统一收敛不散落。
|
||||
|
||||
## baseURL 与代理的配合
|
||||
|
||||
```text
|
||||
开发:axios baseURL = /api → Vite proxy 转发 :8000
|
||||
生产:axios baseURL = /api → Nginx location /api/ 反代后端
|
||||
|
||||
注意:接口 url 在 api 模块里不写 /api 前缀(如 '/user/page'),
|
||||
baseURL 已含 /api;后端 gmeta path 是 /api/user/page——两层拼接对齐。
|
||||
无 /v1 版本段:不要拼 /v1。
|
||||
```
|
||||
|
||||
## lint 与质量工具
|
||||
|
||||
| 工具 | 命令 | 作用 |
|
||||
|------|------|------|
|
||||
| ESLint | pnpm lint:eslint | TS/Vue 代码检查修复 |
|
||||
| Prettier | pnpm lint:prettier | 格式化 |
|
||||
| Stylelint | pnpm lint:stylelint | 样式检查 |
|
||||
| commitlint | (husky 提交钩子) | 提交信息规范 |
|
||||
| vue-tsc | pnpm type:check | 类型检查 |
|
||||
|
||||
## 生产部署形态
|
||||
|
||||
```text
|
||||
Nginx:
|
||||
location / → 静态资源 dist/
|
||||
location /api/ → 反代后端 :8000
|
||||
location /upload/ → 反代后端(或直挂文件服务/CDN)
|
||||
try_files $uri $uri/ /index.html (SPA 路由回退)
|
||||
|
||||
子路径部署:VITE_PUBLIC_PATH=/admin/ + Nginx location /admin/
|
||||
详见 11.5 前端部署
|
||||
```
|
||||
@@ -0,0 +1,78 @@
|
||||
# 9.11 Excel 导入导出组件
|
||||
|
||||
:::tip 概述
|
||||
前端 Excel 能力由 components/Excel 与 importFile 组件 + xlsx 库提供:导入走文件上传接口拿统计结果,导出走导出接口拿文件 URL 再下载。前后端协作协议见 [7.4 数据导入导出](../tools/excel)。
|
||||
:::
|
||||
|
||||
## 组件与工具
|
||||
|
||||
| 模块 | 职责 |
|
||||
|------|------|
|
||||
| components/Excel | Excel 处理组件(导入解析/导出生成的前端封装) |
|
||||
| components/importFile | 导入文件入口组件(选文件→上传→结果展示) |
|
||||
| utils/downloadFile | 文件下载工具(导出 URL 触发浏览器下载) |
|
||||
| xlsx(依赖) | SheetJS 解析/生成 |
|
||||
|
||||
## 导入协作流
|
||||
|
||||
```text
|
||||
1. 页面"导入"按钮(v-permission="sys:xxx:import")→ importFile 组件
|
||||
2. 选择 xls/xlsx → POST /api/{module}/import(multipart,字段 file)
|
||||
3. 后端 excel.ReadSheet → 逐行校验写入
|
||||
4. 返回统计:{total, success, failed, reasons[]}
|
||||
5. 组件展示结果(成功 N 条 / 失败 M 条 + 逐行原因列表)
|
||||
6. 关闭后刷新列表
|
||||
```
|
||||
|
||||
真实响应示例(example 导入):
|
||||
|
||||
```json
|
||||
{ "total": 100, "success": 98, "failed": 2,
|
||||
"reasons": ["第5行:案例名称为空", "第17行:写入失败 Duplicate entry..."] }
|
||||
```
|
||||
|
||||
## 导出协作流
|
||||
|
||||
```text
|
||||
1. 页面"导出"按钮(v-permission="sys:xxx:export")
|
||||
2. 携带当前列表筛选参数 GET /api/{module}/export
|
||||
(导出=所见:与列表同套筛选,含数据权限过滤)
|
||||
3. 后端 ChunkQuery 分批 + excel.WriteSheet → 返回文件 URL
|
||||
4. downloadFile 触发下载
|
||||
```
|
||||
|
||||
## 导入模板约定
|
||||
|
||||
```text
|
||||
表头 = 表单字段名(导出文件即导入模板——"导出→改数据→导入"闭环)
|
||||
单元格全字符串解析(TrimSpace);空行跳过
|
||||
列序与后端 cellAt(row, N) 索引对应——改列序需同步后端解析
|
||||
```
|
||||
|
||||
## 大文件注意
|
||||
|
||||
| 场景 | 说明 |
|
||||
|------|------|
|
||||
| 导出大结果集 | 后端 ChunkQuery 500/页 防 OOM;生成仍为单文件(Excel 行数上限 104 万内) |
|
||||
| 导入大文件 | 逐行独立成败;超大批量建议分批导入(控制单请求耗时) |
|
||||
| 导出文件有效期 | 导出文件落 /upload/temp/——受 tempExpireDays 清理,及时下载 |
|
||||
|
||||
## 新页面接入步骤
|
||||
|
||||
```text
|
||||
1. 后端:Req(ImportReq 带 File 字段 / ExportReq 带筛选字段)+ logic 实现
|
||||
(参照 example 的 Import/Export 真实代码)
|
||||
2. 权限节点:import/export(生成器默认自动建)
|
||||
3. 前端:importFile 组件 + 导出按钮(downloadFile)
|
||||
4. 列表页操作区挂两个按钮(v-permission 对齐)
|
||||
```
|
||||
|
||||
## 排障
|
||||
|
||||
| 症状 | 原因 |
|
||||
|------|------|
|
||||
| 导入全部失败 | 表头/列序与后端解析不匹配;对照导出模板 |
|
||||
| 导入部分失败 | 看 reasons 逐行原因(唯一冲突/必填缺失/字典值非法) |
|
||||
| 导出内容与列表不符 | ExportReq 未带筛选字段或未走 applyFilters(对照 7.4 规范) |
|
||||
| 导出 404 | 文件已被 temp 清理;重新导出立即下载 |
|
||||
| 导出超时 | 结果集过大——确认 ChunkQuery 已接;拆分筛选条件分批导 |
|
||||
@@ -0,0 +1,81 @@
|
||||
# 9.10 数据录入组件
|
||||
|
||||
:::tip 概述
|
||||
数据录入组件覆盖表单场景:Form(querySchemas 驱动)、Select(下拉)、ChinaArea/Region(级联区划)、Cropper(裁剪)、Password(密码强度)、numberInput/priceInput(数字/价格)等。
|
||||
:::
|
||||
|
||||
## 录入组件清单
|
||||
|
||||
| 组件 | 职责 | 对接 |
|
||||
|------|------|------|
|
||||
| Form | 表单封装(FormSchema 配置驱动) | querySchemas.ts / edit.vue |
|
||||
| Select | 下拉封装 | 字典驱动 options |
|
||||
| ChinaArea | 中国省市区级联 | 对接 /api/area 接口(级联懒加载) |
|
||||
| Region | 行政区划组件 | 同上 |
|
||||
| Cropper / vue-cropper | 图片裁剪 | 头像上传(裁剪→上传→转正) |
|
||||
| Password | 密码输入(强度显示) | 登录/改密/重置密码 |
|
||||
| numberInput | 数字输入 | 金额外的数字字段 |
|
||||
| priceInput | 价格输入 | 金额字段(精度处理) |
|
||||
| TableSelect | 表格选择器 | 关联数据选择(如选用户/选分类) |
|
||||
| Authority | 权限包装 | 权限控制容器 |
|
||||
| CheckToken | 令牌检查 | 登录态相关 |
|
||||
|
||||
## FormSchema 驱动(querySchemas 真实模式)
|
||||
|
||||
```ts
|
||||
export const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name', // 字段名 ↔ PageReq json 字段(驼峰)
|
||||
component: 'Input', // 字符串 → 后端 LikeFields
|
||||
label: '案例名称',
|
||||
componentProps: { placeholder: '请输入案例名称', clearable: true },
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
component: 'Select', // 选择 → 后端 EqFields
|
||||
label: '案例类型',
|
||||
componentProps: {
|
||||
placeholder: '请选择案例类型',
|
||||
clearable: true,
|
||||
options: [ /* 字典驱动时改为 getDictItemList 结果 */ ],
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
组件类型与后端筛选的对应(三件套):Input → LikeFields;Select/InputNumber → EqFields。
|
||||
|
||||
## 区划级联(ChinaArea/Region)
|
||||
|
||||
```text
|
||||
数据源:GET /api/area/getParent(省份)→ /api/city/getCityList/:parentCode(级联懒加载)
|
||||
大表纪律:级联按 parent_code 拉单级子集,勿全量拉取
|
||||
存储:user 表四级编码列(province/city/district/street_code)
|
||||
提交:名称串或编码数组(后端 ResolveCityCodes/ResolveCityNames 互转)
|
||||
```
|
||||
|
||||
## 图片裁剪(头像场景)
|
||||
|
||||
```text
|
||||
选择图片 → Cropper 裁剪 → 裁剪结果 blob → POST /api/upload/uploadFile(type=image)
|
||||
→ 返回 temp URL → 表单 avatar 字段 → 保存资料时后端转正
|
||||
```
|
||||
|
||||
## edit.vue 表单模式
|
||||
|
||||
```text
|
||||
新增/编辑共用 edit.vue:
|
||||
新增:空表单 → exampleAdd(data)
|
||||
编辑:Detail 回显 → 全量表单 → exampleUpdate(data)(全量 PUT 语义)
|
||||
|
||||
字段对齐:表单 model ↔ AddReq/UpdateReq 结构体(驼峰 json)
|
||||
校验:required 对齐后端 v 标签(前端提示 + 后端 gvalid 双层)
|
||||
```
|
||||
|
||||
## 开发规约
|
||||
|
||||
+ 筛选表单用 querySchemas 配置(勿手写搜索区)
|
||||
+ 下拉选项优先字典驱动(getDictItemList);静态 options 仅示例/特殊场景
|
||||
+ 编辑表单全量提交(基座 Update 是全量表单语义;零值字段也要带)
|
||||
+ 图片/文件字段持 URL(上传组件产出),后端 FileFields 转正
|
||||
+ 敏感字段(密码)用 Password 组件;提交前确认后端 bcrypt 路径
|
||||
@@ -0,0 +1,88 @@
|
||||
# 9.4 HTTP 请求封装
|
||||
|
||||
:::tip 概述
|
||||
HTTP 封装在 utils/http/axios:VAxios 类 + transform 链式处理。统一完成信封解包(code===0 返回 data)、错误消息提示、401 跳登录、请求取消管理。业务只需 `http.request({url, method, params/data})`。
|
||||
:::
|
||||
|
||||
## 封装架构
|
||||
|
||||
```text
|
||||
utils/http/axios/
|
||||
├── Axios.ts VAxios 类(拦截器安装/请求实例管理)
|
||||
├── axiosTransform.ts transform 抽象基类(钩子:请求前/响应后/错误处理)
|
||||
├── index.ts 实例配置(transform 实现 + 拦截器装配)
|
||||
├── checkStatus.ts HTTP 层状态码处理(401/403/404/500…)
|
||||
├── axiosCancel.ts 请求取消管理(重复请求取消)
|
||||
├── helper.ts 日期格式化/时间戳拼接
|
||||
└── types.ts RequestOptions / Result 类型
|
||||
```
|
||||
|
||||
## 响应解包(transformRequestData,真实逻辑)
|
||||
|
||||
```text
|
||||
1. isReturnNativeResponse → 返回原生响应头场景
|
||||
2. isTransformResponse=false → 直接返回 res.data(需自处理 code 时)
|
||||
3. 解构 {code, data, msg}:
|
||||
hasSuccess = data && code === ResultEnum.SUCCESS(0)
|
||||
成功:按 isShowSuccessMessage/successMessageText 提示 → 返回 data
|
||||
失败:errMsg 含 \n → ElMessageBox.confirm(HTML 换行)
|
||||
否则 → ElMessage.error(errMsg)
|
||||
4. 401 → 跳登录页(配合 token 清理)
|
||||
```
|
||||
|
||||
## 使用方式(api 模块真实模式)
|
||||
|
||||
```ts
|
||||
import { http } from '@/utils/http/axios';
|
||||
|
||||
// GET(params)
|
||||
export function getExamplePage(params?: Record<string, any>) {
|
||||
return http.request({
|
||||
url: '/example/page',
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// POST(data)
|
||||
export function exampleAdd(data: any) {
|
||||
return http.request({
|
||||
url: '/example/add',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
请求选项(RequestOptions)可按请求定制:isShowMessage / isShowErrorMessage / isTransformResponse 等。
|
||||
|
||||
## 拦截器链
|
||||
|
||||
```text
|
||||
请求拦截:
|
||||
* token 注入(ACCESS_TOKEN 从 storage 读)
|
||||
* 请求取消管理(axiosCancel,重复请求策略)
|
||||
* 时间戳拼接(防缓存,可选)
|
||||
|
||||
响应拦截:
|
||||
* checkStatus:HTTP 层状态码统一处理
|
||||
* transformRequestData:信封解包与消息
|
||||
* 401 → 清 token → 跳登录(redirect 记录来源)
|
||||
```
|
||||
|
||||
## 与后端信封的对应
|
||||
|
||||
| 后端 code | 前端行为 |
|
||||
|-----------|---------|
|
||||
| 0 | 返回 data(解包完成,业务拿到纯数据) |
|
||||
| 1 | ElMessage.error(msg)(ResultEnum.ERROR) |
|
||||
| 401 | 跳登录页 |
|
||||
| 403/429 等 | msg 弹窗(细分码降级为前端统一处理) |
|
||||
|
||||
## 开发规约
|
||||
|
||||
+ api 模块只写 url(不含 /api 前缀——baseURL 已含)
|
||||
+ 默认走 transform 解包;需要看原始 code 时 `isTransformResponse: false`
|
||||
+ 写操作成功提示用 `isShowSuccessMessage`(列表查询不弹成功)
|
||||
+ 错误文案含多行时自动转 MessageBox(后端多行错误场景)
|
||||
+ 排障:请求 404 → 查 url 是否重复拼了 /api 或 /v1;401 循环 → 查 refresh 逻辑与令牌存储
|
||||
@@ -0,0 +1,67 @@
|
||||
# 9.17 枚举与全局设置
|
||||
|
||||
:::tip 概述
|
||||
本项目前端不引入 i18n(当前仅简体中文)。与"全局常量与设置"对应的体系是:enums/ 枚举常量 + settings/ 全局设置 + store/mutation-types 存储键。本节说明三者的组织与使用。
|
||||
:::
|
||||
|
||||
## enums/ 枚举常量
|
||||
|
||||
| 文件 | 内容 |
|
||||
|------|------|
|
||||
| httpEnum.ts | RequestEnum(GET/POST…)、ResultEnum(SUCCESS=0/ERROR=1…)、ContentTypeEnum |
|
||||
| pageEnum.ts | PageEnum(BASE_LOGIN 等页面路径常量) |
|
||||
| cacheEnum.ts | 缓存键常量 |
|
||||
| permissionsEnum.ts | 权限相关常量(如通配权限 `*:*:*`) |
|
||||
| roleEnum.ts | 角色常量(ROLE_SUPER_ADMIN 等) |
|
||||
| avatarEnum.ts / breakpointEnum.ts | 头像/断点枚举 |
|
||||
|
||||
```ts
|
||||
// 使用模式
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
if (code === ResultEnum.SUCCESS) { ... }
|
||||
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types';
|
||||
```
|
||||
|
||||
前后端枚举对照:后端 consts(biz_err/log_type 等)定义真值,前端 enums 定义消费侧镜像——后端改枚举值须同步前端(破坏性变更流程)。
|
||||
|
||||
## settings/ 全局设置
|
||||
|
||||
| 文件 | 内容 | 消费方 |
|
||||
|------|------|--------|
|
||||
| projectSetting.ts | 布局模式/标签页/面包屑/水印等项目级设置 | layout |
|
||||
| designSetting.ts | 主题色/暗黑/灰度/宽度等设计设置 | styles/主题 |
|
||||
| componentSetting.ts | 分页 sizes 等组件默认 | components |
|
||||
| animateSetting.ts | 页面动画 | layout/路由切换 |
|
||||
|
||||
设置经 Pinia store 持久化(用户偏好跨会话),部分可在系统设置页在线调整。
|
||||
|
||||
## store/mutation-types(存储键)
|
||||
|
||||
```text
|
||||
ACCESS_TOKEN 令牌存储键
|
||||
FIRST_ROUTE 首个路由标记
|
||||
统一在 mutation-types.ts 定义——禁止散落魔法字符串
|
||||
```
|
||||
|
||||
## 为什么不用 i18n
|
||||
|
||||
```text
|
||||
当前版本仅服务简体中文场景:
|
||||
* 后端 msg/字典文案均为中文
|
||||
* 引入 vue-i18n 会带来文案键管理成本(25 表 + 字典 + 错误码全量翻译)
|
||||
|
||||
国际化演进方向(如需要):
|
||||
1) 字典层天然支持多语言(dict_item 加 lang 维度或平行字典)
|
||||
2) 前端引入 vue-i18n,UI 文案抽 key
|
||||
3) 后端错误 msg 语义码化(前端按码翻译)
|
||||
——属于大版本演进,当前文档不展开
|
||||
```
|
||||
|
||||
## 使用规约
|
||||
|
||||
+ 常量优先放 enums/(跨页复用);仅页内用的常量放页内
|
||||
+ 后端枚举(log_type 41 值等)前端按值映射显示(映射表放 enums 或页内)
|
||||
+ 存储键统一 mutation-types(防拼写漂移)
|
||||
+ 设置项修改经 store action(勿直接改 settings 常量对象——它是默认值不是状态)
|
||||
+ 万能权限 `*:*:*` 用 permissionsEnum 常量,勿硬编码字符串散落
|
||||
@@ -0,0 +1,63 @@
|
||||
# 9.15 布局系统
|
||||
|
||||
:::tip 概述
|
||||
布局系统(src/layout/)提供管理后台标准骨架:侧栏菜单、顶栏(面包屑/用户区)、标签页(tabsView)、内容区(keep-alive 页面缓存)、锁屏等,全部受 projectSetting 在线配置驱动。
|
||||
:::
|
||||
|
||||
## 布局骨架
|
||||
|
||||
```text
|
||||
┌───────┬──────────────────────────────────────────┐
|
||||
│ │ 顶栏:折叠按钮 / 面包屑 / 全屏 / 用户头像 │
|
||||
│ 侧栏 ├──────────────────────────────────────────┤
|
||||
│ 菜单 │ 标签页(tabsView):多页签导航 + 缓存 │
|
||||
│ ├──────────────────────────────────────────┤
|
||||
│ │ │
|
||||
│ │ 内容区 <router-view> + keep-alive │
|
||||
│ │ │
|
||||
└───────┴──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 组成
|
||||
|
||||
| 部分 | 说明 |
|
||||
|------|------|
|
||||
| 侧栏 | 菜单数据来自 asyncRoute(getMenus 生成);支持折叠;图标 router-icons 映射 |
|
||||
| 顶栏 | 面包屑(路由层级)、主题/全屏工具、用户下拉(个人中心/退出) |
|
||||
| tabsView | 多标签页导航;visitedTabs 记录;keep-alive name 对应缓存 |
|
||||
| 内容区 | router-view;cachedTabs 内页面状态保留(列表筛选/页码不丢) |
|
||||
| 锁屏 | lockscreen store(离开工位锁定) |
|
||||
|
||||
## 布局模式(projectSetting)
|
||||
|
||||
```text
|
||||
侧栏布局(默认)/ 顶部导航布局等模式切换
|
||||
侧栏主题(深/浅)、固定头/侧栏、标签页开关、面包屑开关
|
||||
响应式:窄屏侧栏自动折叠
|
||||
```
|
||||
|
||||
## 与路由/菜单的协作
|
||||
|
||||
```text
|
||||
asyncRoute.generateRoutes(getMenus)
|
||||
→ 侧栏渲染菜单树(hide=1 不渲染,sort 排序)
|
||||
→ 动态路由 addRoute
|
||||
→ tabsView 记录访问页签
|
||||
→ keep-alive 按 name 缓存页面状态
|
||||
```
|
||||
|
||||
## keep-alive 与页面缓存
|
||||
|
||||
```text
|
||||
缓存条件:路由 meta.keepAlive + tabsView cachedTabs 包含
|
||||
页面 component name 需与路由 name 一致(缓存键)
|
||||
清除缓存:tabsView 右键"关闭其他/全部"或 delCachedTab
|
||||
列表页需要保留筛选/页码时开启 keep-alive
|
||||
```
|
||||
|
||||
## 定制建议
|
||||
|
||||
+ 品牌定制:logo(assets)、主题色(9.14 样式与主题定制)、系统名称(配置)
|
||||
+ 新布局组件放 layout/ 下,勿塞进页面
|
||||
+ 布局级权限:菜单驱动(未授权菜单不出现)——无需布局层额外判断
|
||||
+ iframe 页面:views/iframe(内嵌外部系统场景,路由 meta 指定 URL)
|
||||
@@ -0,0 +1,75 @@
|
||||
# 9.8 登录流程
|
||||
|
||||
:::tip 概述
|
||||
登录页支持多表单形态(账号/手机/Qrcode),核心链路为:验证码获取 → 登录请求 → access_token 存储 → 跳转首页。前端读 `data.access_token`(下划线例外),守卫与 http 拦截器全程携带。
|
||||
:::
|
||||
|
||||
## 登录页结构(views/login/)
|
||||
|
||||
```text
|
||||
views/login/
|
||||
├── index.vue 登录页容器(多表单切换)
|
||||
├── LoginForm.vue 账号密码表单(默认)
|
||||
├── PhoneForm.vue 手机号表单形态
|
||||
├── QrcodeForm.vue 扫码表单形态
|
||||
└── RegisterForm.vue 注册表单形态(后端 /api/register 路由未注册——当前不可用)
|
||||
```
|
||||
|
||||
## 核心登录链路(账号表单)
|
||||
|
||||
```text
|
||||
1. 页面加载 → GET /api/captcha → {key, captcha(base64 图片)}
|
||||
2. 用户填账号/密码/验证码 → Login action
|
||||
POST /api/login {username, password, code, key}
|
||||
(开发环境验证码可填 bypassCode 618618 免校验)
|
||||
3. 成功响应:
|
||||
{data: {access_token, refresh_token, expires_in, token_type}}
|
||||
前端读 data.access_token → storage.set(ACCESS_TOKEN) + store
|
||||
4. 跳转首页(或 redirect 参数记录的来源路径)
|
||||
5. 后续请求:http 拦截器从 storage 读 token 注入 Authorization 头
|
||||
```
|
||||
|
||||
::: warning 字段名
|
||||
登录响应是全系统唯一下划线命名:`data.access_token`(不是 accessToken)。读错字段是登录后仍跳登录页的最常见原因。
|
||||
:::
|
||||
|
||||
## token 生命周期(前端侧)
|
||||
|
||||
```text
|
||||
写入:登录成功 / URL ?token= 直达(守卫识别写入)
|
||||
读取:守卫校验 / http 拦截器注入
|
||||
清除:登出(LogOut)/ 401 未认证
|
||||
刷新:access 过期(401)→ refresh_token 走 /api/oauth2/token 轮换
|
||||
(对接实现见 8.6 Token 使用说明)
|
||||
```
|
||||
|
||||
## 多表单形态说明
|
||||
|
||||
| 形态 | 说明 |
|
||||
|------|------|
|
||||
| LoginForm | 默认账号密码 + 验证码 |
|
||||
| PhoneForm | 手机号形态(界面演示;后端登录以账号/租户编码为准) |
|
||||
| QrcodeForm | 扫码形态(界面演示;对接需扫码通道) |
|
||||
| RegisterForm | 注册形态(后端 /api/register 白名单放行但路由未注册——启用需补后端实现) |
|
||||
|
||||
生产启用形态可在登录页配置裁剪。
|
||||
|
||||
## 租户内登录
|
||||
|
||||
```text
|
||||
多租户同名场景:登录请求携带 tenantCode(租户编码)
|
||||
后端先按 code 定位租户再按 username 过滤
|
||||
不带:全局查找,同名时优先平台租户,仍歧义提示携带租户编码
|
||||
登录表单如需租户输入框:加 tenantCode 字段随请求提交即可
|
||||
```
|
||||
|
||||
## 排障
|
||||
|
||||
| 症状 | 原因 |
|
||||
|------|------|
|
||||
| 登录成功又跳回登录页 | 读错 access_token 字段;或 storage 写入失败(隐私模式) |
|
||||
| 验证码不显示 | 后端未启动 / /api/captcha 限流(认证入口 10/分钟) |
|
||||
| 登录报"验证码错误" | 每次 key 配套唯一验证码(一次性);用新 key 或 bypassCode |
|
||||
| 登录 429 | 触发 authLimit——等 1 分钟窗口过期 |
|
||||
| 登录报"演示环境" | 不应出现——登录在公开白名单放行;检查 demo 配置 |
|
||||
| 跳转丢 redirect | 守卫 redirect 参数未透传;检查白名单与 query 处理 |
|
||||
@@ -0,0 +1,81 @@
|
||||
# 9.12 新增页面开发步骤
|
||||
|
||||
:::tip 概述
|
||||
新增前端页面的标准路径:api 模块 → columns.ts → querySchemas.ts → index.vue → edit.vue/detail.vue → 菜单权限。用生成器可一次产出五件套,本节给出手动与生成两条路径。
|
||||
:::
|
||||
|
||||
## 标准步骤(手动开发)
|
||||
|
||||
```text
|
||||
1. api 接口模块:src/api/{域}/{模块}.ts
|
||||
http.request({url: '/{prefix}/page', method: 'GET', params})
|
||||
增删改查+导入导出逐个函数(url 对齐后端 gmeta path,无 /api 前缀)
|
||||
|
||||
2. 表格列:src/views/{域}/{模块}/columns.ts
|
||||
selection 列 + ID + 业务字段 + 枚举列(展示 {field}Text)+ 操作列
|
||||
图片/选项/链接用 render 函数
|
||||
|
||||
3. 筛选表单:src/views/{域}/{模块}/querySchemas.ts
|
||||
每个输入 ↔ PageReq 同名驼峰字段 ↔ spec 声明(三件套对齐)
|
||||
Input → LikeFields;Select/InputNumber → EqFields
|
||||
|
||||
4. 列表页:index.vue
|
||||
Page 容器 = 搜索区(schemas)+ 操作区(按钮 v-permission)+ Table + pagination
|
||||
加载:getXxxPage({pageNo, pageSize, ...筛选值})
|
||||
|
||||
5. 编辑弹窗:edit.vue
|
||||
新增/编辑共用;字段对齐 AddReq/UpdateReq(全量提交语义)
|
||||
文件字段用 Upload 组件(持 URL)
|
||||
|
||||
6. 详情:detail.vue(需要时)
|
||||
|
||||
7. 菜单与权限:
|
||||
生成器自动创建(菜单 + 权限节点);
|
||||
手动:菜单管理建菜单(path /{domain}/{moduleCamel}, component=path+"/index")
|
||||
+ 权限节点(sys:{module}:{action})→ 角色勾选
|
||||
|
||||
8. 联调:登录 → 访问菜单 → 增删改查 → 对照 /api.json 核对接口
|
||||
```
|
||||
|
||||
## 生成器路径(推荐)
|
||||
|
||||
```sh
|
||||
# 后端+前端一次产出(含菜单/权限/字典/路由注册副作用)
|
||||
go run ./cmd/generator -tables "goframe_example|案例演示表"
|
||||
go build ./... && 重启
|
||||
```
|
||||
|
||||
产出前端文件(ui 普通模板):
|
||||
|
||||
```text
|
||||
src/api/{域}/{moduleCamel}.ts
|
||||
src/views/{域}/{moduleCamel}/
|
||||
index.vue edit.vue detail.vue columns.ts querySchemas.ts
|
||||
|
||||
树形模块用 ui2 模板:index.vue edit.vue detail.vue(无 columns/querySchemas)
|
||||
```
|
||||
|
||||
生成后调整:querySchemas options 改字典驱动、复杂列 render、扩展按钮与接口。
|
||||
|
||||
## 三件套对齐自检(联调前)
|
||||
|
||||
```text
|
||||
□ api url 与 gmeta path 一致(无 /v1;baseURL 补 /api)
|
||||
□ querySchemas field ↔ PageReq json ↔ LikeFields/EqFields 声明
|
||||
□ columns prop ↔ Item/DetailRes json 字段
|
||||
□ 枚举列展示 {field}Text
|
||||
□ 按钮 v-permission ↔ gmeta permission ↔ 角色已勾选
|
||||
□ edit 表单字段 ↔ AddReq/UpdateReq(全量提交)
|
||||
□ 文件字段 URL 已由后端 GetFileUrl 补域名(直接绑定展示)
|
||||
```
|
||||
|
||||
## 常见联调问题
|
||||
|
||||
| 症状 | 排查 |
|
||||
|------|------|
|
||||
| 列表 404 | url 重复拼 /api 或误加 /v1 |
|
||||
| 筛选无效果 | 三件套有一环没对齐(field 名/Req 字段/spec 声明) |
|
||||
| 新页面 403 | 权限节点未建/未勾选/身份缓存旧 |
|
||||
| 新页面 404(路由) | 菜单 component 路径与实际文件不符 |
|
||||
| 编辑保存字段丢失 | 表单未全量提交(基座 Update 全量语义) |
|
||||
| 枚举显示数字 | 后端 SerializeMaps 未声明或前端 prop 用了 status 而非 statusText |
|
||||
@@ -0,0 +1,83 @@
|
||||
# 9.1 前端工程概述
|
||||
|
||||
:::tip 概述
|
||||
前端为 Vue3 + TypeScript + Vite + Element Plus + Pinia 的标准工程化项目(ui/ 目录),采用 Composition API,核心机制为:动态菜单路由、指令级权限、三件套页面规范(api/columns/querySchemas)、统一封装的 axios。
|
||||
:::
|
||||
|
||||
## 技术栈
|
||||
|
||||
| 技术 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| Vue | ^3.4.27 | 核心框架(Composition API) |
|
||||
| TypeScript | ^5.x | 类型系统 |
|
||||
| Vite | ^5.x | 构建(多环境模式 dev/test/uat/prod) |
|
||||
| Element Plus | ^2.7.4 | UI 组件库 |
|
||||
| Pinia | ^2.1.7 | 状态管理 |
|
||||
| Vue Router | ^4.3.2 | 路由(常量路由 + 动态菜单路由) |
|
||||
| axios | ^0.27.2 | HTTP(封装于 utils/http/axios,VAxios 类) |
|
||||
| echarts | ^5.5.0 | 数据可视化(仪表盘) |
|
||||
| tinymce / vue-quill | 5.10.3 / beta.8 | 富文本编辑器 |
|
||||
| xlsx | ^0.18.5 | Excel 导入导出 |
|
||||
| cropperjs / vue-cropper | — | 图片裁剪 |
|
||||
| dayjs / lodash-es | — | 工具 |
|
||||
|
||||
## 工程结构(src 下九层)
|
||||
|
||||
```text
|
||||
api/ 接口模块(按域分目录,url 对齐 gmeta path)
|
||||
components/ 公共组件(25 类)
|
||||
directives/ 自定义指令(permission 权限等)
|
||||
enums/ 枚举常量(http/cache/page/permissions/role…)
|
||||
hooks/ 组合式函数(core/event/setting/web…)
|
||||
layout/ 布局系统
|
||||
router/ 路由(constant 常量 + generator-routers 动态 + 守卫)
|
||||
settings/ 全局设置(项目/设计/组件/动画)
|
||||
store/ Pinia(modules 7 个)
|
||||
styles/ 全局样式与主题变量
|
||||
utils/ 工具库(http 封装/auth/storage/downloadFile…)
|
||||
views/ 页面(按域分目录:system/data/content/monitor/tool…)
|
||||
```
|
||||
|
||||
## 核心机制一览
|
||||
|
||||
| 机制 | 说明 | 详见 |
|
||||
|------|------|------|
|
||||
| HTTP 封装 | VAxios 类 + transform 链;统一信封解包、401 处理、消息提示 | 9.4 |
|
||||
| 动态菜单路由 | 登录后拉 /api/index/getMenus 生成路由(generator-routers) | 9.5 |
|
||||
| 状态管理 | Pinia modules:user/asyncRoute/tabsView/designSetting… | 9.6 |
|
||||
| 权限控制 | directives/permission 按 permissions 控制按钮 | 9.7 |
|
||||
| 登录流程 | 多形态(账号/手机/扫码/Qrcode 表单)+ token 存储 | 9.8 |
|
||||
| 三件套规范 | api + columns.ts + querySchemas.ts 页面开发模式 | 9.12 |
|
||||
|
||||
## 与后端的契约
|
||||
|
||||
```text
|
||||
baseURL = /api(Vite 开发代理指向 :8000)
|
||||
接口 url 无 /v1 版本段(/user/page 而非 /v1/user/page)
|
||||
响应信封 {code, ok, msg, data}:0 成功解包 / 1 弹 msg / 401 跳登录
|
||||
登录读 data.access_token(下划线例外)
|
||||
筛选三件套对齐:querySchemas ↔ PageReq ↔ spec 声明
|
||||
```
|
||||
|
||||
## 常用命令
|
||||
|
||||
```sh
|
||||
pnpm dev # 开发(Vite 代理 /api → :8000)
|
||||
pnpm build # 生产构建
|
||||
pnpm type:check # vue-tsc 类型检查
|
||||
pnpm lint:eslint # ESLint 修复
|
||||
pnpm preview # 构建并预览
|
||||
```
|
||||
|
||||
## 页面与后端模块对应
|
||||
|
||||
| 前端目录 | 后端模块 |
|
||||
|---------|---------|
|
||||
| views/system/{user,role,menu,dept,level,position,tenant} | system 域七模块 |
|
||||
| views/system/logger/{loginLog,operLog} | login_log / operation_log |
|
||||
| views/data/{city,config,dict,notice,param} | data 域五模块 |
|
||||
| views/content/{article,category,link} | content 域三模块 |
|
||||
| views/monitor/{online,server,cache,job} | online_user/server_monitor/cache_monitor/job |
|
||||
| views/tool/{generator,example} | generator / example |
|
||||
| views/file/fileTemplate | file_template |
|
||||
| views/setting/{profile,configWeb} | index 个人中心 / config web 接口 |
|
||||
@@ -0,0 +1,68 @@
|
||||
# 9.7 权限控制
|
||||
|
||||
:::tip 概述
|
||||
前端权限是按钮级 UX 控制:directives/permission.ts 按 getUser 返回的 permissions 数组控制元素显隐;超管通配权限 `*:*:*` 直接放行。后端接口级校验是强制层(403),前端指令只是体验层。
|
||||
:::
|
||||
|
||||
## 权限数据流
|
||||
|
||||
```text
|
||||
登录 → GetInfo(/api/index/getUser)
|
||||
→ userInfo.permissions(后端 LoadIdentity 的 Perms)
|
||||
超管 = ["*:*:*"](通配)
|
||||
→ store 缓存 → 指令消费
|
||||
```
|
||||
|
||||
后端 Perms 来源:role_menu → menu(type=1 且 status=0 且 hide=0)的 permission 去重集合(详见 [5.4 权限管理(RBAC)](../core/rbac))。
|
||||
|
||||
## 指令用法(directives/permission.ts)
|
||||
|
||||
```text
|
||||
按钮级控制(语义示意):
|
||||
<el-button v-permission="'sys:user:add'">新增</el-button>
|
||||
|
||||
指令逻辑:
|
||||
permissions 含 "*:*:*" → 放行(超管)
|
||||
permissions 含传入权限码 → 放行
|
||||
否则 → 移除元素(v-if 等效)
|
||||
```
|
||||
|
||||
## 前后端权限对照(联调要点)
|
||||
|
||||
```text
|
||||
后端 gmeta permission:"sys:user:add"
|
||||
↔ 前端指令 v-permission="'sys:user:add'"
|
||||
↔ 菜单权限节点 permission=sys:user:add
|
||||
↔ 角色已勾选该节点
|
||||
|
||||
四处必须一致——不一致的表现:
|
||||
前端可见按钮但后端 403 → 节点存在但角色未勾选(或身份缓存旧)
|
||||
前端按钮消失但后端有权限 → permissions 缓存旧 / 指令码拼错
|
||||
```
|
||||
|
||||
## 双重校验的定位
|
||||
|
||||
| 层 | 位置 | 性质 |
|
||||
|----|------|------|
|
||||
| 前端指令 | v-permission | UX 控制(防误操作、界面简洁)——**可被绕过** |
|
||||
| 后端校验 | Bind 包装层 HasPerm | 强制安全(403 拒绝)——唯一真值 |
|
||||
|
||||
::: warning 定位认知
|
||||
前端权限不是安全边界。绕过前端直接调接口的请求由后端 403 拦截。新增接口**必须**声明 gmeta permission(或评审确认留空=登录即可)。
|
||||
:::
|
||||
|
||||
## 菜单与按钮的分层
|
||||
|
||||
```text
|
||||
菜单(type=0):控制"页面能否访问"(路由生成层面)
|
||||
按钮(type=1 权限节点):控制"页面内操作能否看到"(指令层面)
|
||||
getUser.permissions 下发权限码集合;菜单树不含按钮节点
|
||||
```
|
||||
|
||||
## 开发规约
|
||||
|
||||
+ 页面按钮逐个对齐 gmeta permission 码(复制粘贴防拼错)
|
||||
+ 新增权限码流程:gmeta 声明 → 菜单建节点 → 角色勾选 → 指令引用
|
||||
+ 不要发明前端私有权限码(必须与后端节点一致才有意义)
|
||||
+ 超管测试:admin 账号应看到全部按钮(`*:*:*` 放行)
|
||||
+ 权限变更后重新登录验证(身份缓存 300s;前端 GetInfo 在登录/刷新时拉取)
|
||||
@@ -0,0 +1,66 @@
|
||||
# 9.5 路由与动态菜单
|
||||
|
||||
:::tip 概述
|
||||
路由分两层:constant.ts 常量路由(login/redirect/403/404,登录前可访问)与动态菜单路由(登录后由 /api/index/getMenus 返回的菜单树经 generator-routers 生成)。守卫统一处理 token 校验与动态注入。
|
||||
:::
|
||||
|
||||
## 两层路由
|
||||
|
||||
```text
|
||||
常量路由(router/constant.ts):
|
||||
login / redirect / 403 / 404 / iframe 等——无需权限,始终注册
|
||||
|
||||
动态菜单路由(router/generator-routers.ts):
|
||||
登录 → userStore.GetInfo() → asyncRouteStore.generateRoutes(userInfo)
|
||||
→ 消费 getMenus 菜单树 → 生成路由记录(component 按 path 动态 import)
|
||||
→ addRoute 注入
|
||||
未授权菜单不出现;超管返回全量菜单
|
||||
```
|
||||
|
||||
## 守卫流程(router-guards.ts,真实逻辑)
|
||||
|
||||
```text
|
||||
beforeEach:
|
||||
1. URL 带 ?token= → 直接写入 ACCESS_TOKEN(免登直达场景)
|
||||
2. NProgress 进度条启动
|
||||
3. 白名单(login)且无 token → 放行
|
||||
4. 无 token 且非 ignoreAuth → 跳登录(redirect 记录来源路径)
|
||||
5. 已是动态路由已添加 → 放行
|
||||
6. 首次:setDynamicAddedRoute(true) → GetInfo() → generateRoutes
|
||||
→ 动态路由注入 → next(to fullPath) 重进
|
||||
```
|
||||
|
||||
## 菜单 → 路由 → 页面的映射
|
||||
|
||||
```text
|
||||
后端 menu 表(type=0 菜单):
|
||||
path = "/{domain}/{moduleCamel}" 如 /content/article
|
||||
component = path + "/index" 如 /content/article/index
|
||||
icon / name / sort / hide
|
||||
|
||||
前端 generator-routers:
|
||||
path 匹配 → component 动态 import(views/{path}.vue)
|
||||
hide=1 的菜单不渲染
|
||||
sort 控制侧栏顺序
|
||||
```
|
||||
|
||||
生成器创建菜单(createMenu 副作用)保证 path/component 与前端文件路径一致——这是"生成即可用"的前提。
|
||||
|
||||
## 路由图标与菜单辅助
|
||||
|
||||
```text
|
||||
router-icons.ts 图标解析(menu.icon → 组件映射)
|
||||
router/menus/ 菜单数据结构辅助
|
||||
base.ts ErrorPageRoute / AboutPageRoute / SettingPageRoute 等基础路由
|
||||
findFirstRoutePath 首个可达路由定位(登录后跳转首页)
|
||||
```
|
||||
|
||||
## 排障
|
||||
|
||||
| 症状 | 原因 |
|
||||
|------|------|
|
||||
| 登录后 404 | 动态路由 component 路径与实际文件不符(菜单 component 值错) |
|
||||
| 菜单空白 | getMenus 返回空(无角色/未勾选菜单);超管应全量 |
|
||||
| 刷新页面 404 | 动态路由未重新注入——守卫应重跑 generateRoutes;检查 isDynamicAddedRoute 状态 |
|
||||
| 直接访问子路由被踢登录 | 无 token;守卫 redirect 记录来源 |
|
||||
| 新模块菜单点了 404 | 生成器菜单 component 与前端产出路径不一致;对照 path/domain |
|
||||
@@ -0,0 +1,81 @@
|
||||
# 9.6 状态管理(Pinia)
|
||||
|
||||
:::tip 概述
|
||||
状态管理用 Pinia,modules 七个状态域:user(登录身份)、asyncRoute(动态路由)、tabsView(标签页)、designSetting/projectSetting(界面设置)、lockscreen(锁屏)、ossConfig(存储配置)。本节说明各域职责与关键状态。
|
||||
:::
|
||||
|
||||
## Store 模块清单(store/modules/)
|
||||
|
||||
| 模块 | 职责 | 关键状态/动作 |
|
||||
|------|------|-------------|
|
||||
| user.ts | 登录与身份 | token(ACCESS_TOKEN)、用户信息、Login/GetInfo/LogOut |
|
||||
| asyncRoute.ts | 动态路由 | 菜单树、generateRoutes、isDynamicAddedRoute |
|
||||
| tabsView.ts | 标签页 | 多标签缓存、visitedTabs、delCachedTab |
|
||||
| designSetting.ts | 设计设置 | 主题色、水印、灰度/色弱模式 |
|
||||
| projectSetting.ts | 项目设置 | 布局模式、标签页开关、面包屑等 |
|
||||
| lockscreen.ts | 锁屏 | 锁定状态、解锁 |
|
||||
| ossConfig.ts | 存储配置 | OSS 相关配置状态 |
|
||||
|
||||
辅助文件:`mutation-types.ts`(ACCESS_TOKEN/FIRST_ROUTE 等存储键常量)、`index.ts`(createPinia)、`types.ts`、`plugins/`(持久化插件)。
|
||||
|
||||
## user 模块(核心)
|
||||
|
||||
```text
|
||||
状态:
|
||||
token 从 storage.get(ACCESS_TOKEN) 恢复(刷新不丢)
|
||||
userInfo getUser 接口数据(头像/名称/permissions…)
|
||||
|
||||
动作:
|
||||
Login 登录 → data.access_token 存 storage + store
|
||||
GetInfo 调 /api/index/getUser → 权限/角色注入
|
||||
LogOut 调 /api/logout → 清 token/存储 → 跳登录
|
||||
|
||||
关键:登录响应读 data.access_token(下划线字段)
|
||||
```
|
||||
|
||||
## asyncRoute 模块(动态路由)
|
||||
|
||||
```text
|
||||
generateRoutes(userInfo):
|
||||
调 /api/index/getMenus → 菜单树
|
||||
→ generator-routers 转换为路由记录(component 动态 import)
|
||||
→ 常量路由 + 动态路由合并 → 返回
|
||||
|
||||
isDynamicAddedRoute:守卫判断"是否已注入动态路由"的标志
|
||||
```
|
||||
|
||||
## 令牌存储(mutation-types + Storage)
|
||||
|
||||
```text
|
||||
ACCESS_TOKEN = "ACCESS_TOKEN" storage 封装(utils/Storage.ts)
|
||||
登录写入 → 守卫读取 → http 拦截器注入 Authorization
|
||||
登出/401 清除
|
||||
```
|
||||
|
||||
## 使用模式
|
||||
|
||||
```ts
|
||||
// 组件内获取状态
|
||||
const userStore = useUserStore();
|
||||
const avatar = userStore.userInfo?.avatar;
|
||||
|
||||
// 跨组件动作
|
||||
const { logOut } = useUserStoreWidthOut(); // store 外引用(store/modules 导出)
|
||||
```
|
||||
|
||||
## 持久化策略
|
||||
|
||||
```text
|
||||
token:localStorage 持久化(刷新不丢)
|
||||
用户信息/菜单:内存态(刷新重新 GetInfo + generateRoutes)
|
||||
界面设置:持久化(designSetting/projectSetting 经插件)
|
||||
```
|
||||
|
||||
## 排障
|
||||
|
||||
| 症状 | 原因 |
|
||||
|------|------|
|
||||
| 刷新后回登录页 | ACCESS_TOKEN 未持久化或被清;查 storage 与守卫 |
|
||||
| 权限按钮不更新 | GetInfo 缓存旧 permissions;重新登录或刷新用户信息 |
|
||||
| 标签页缓存失效 | tabsView cachedTabs 与 keep-alive name 不匹配 |
|
||||
| store 类型报错 | types.ts 与模块状态不一致;跑 type:check |
|
||||
@@ -0,0 +1,77 @@
|
||||
# 9.14 样式与主题定制
|
||||
|
||||
:::tip 概述
|
||||
样式体系基于 Element Plus CSS 变量 + styles/ 全局覆盖:主题色、暗黑模式、水印、动画等经 designSetting/projectSetting 在线配置。定制优先走变量覆盖而非逐组件覆盖。
|
||||
:::
|
||||
|
||||
## 样式目录(src/styles/)
|
||||
|
||||
```text
|
||||
styles/
|
||||
├── index.less / index.scss 入口样式
|
||||
├── variables.* SCSS/Less 变量
|
||||
├── element/ Element Plus 变量覆盖
|
||||
├── public/ 公共样式
|
||||
└── ...
|
||||
```
|
||||
|
||||
main.ts 引入顺序:Element Plus 样式 → 变量覆盖 → 全局样式(覆盖生效)。
|
||||
|
||||
## 主题定制机制
|
||||
|
||||
```text
|
||||
Element Plus 暴露 CSS 变量(--el-color-primary 等):
|
||||
styles/ 覆盖变量根值 → 全组件生效
|
||||
|
||||
运行时主题切换(designSetting store):
|
||||
主题色在线更换 → 动态更新 CSS 变量 / 主题类名
|
||||
暗黑模式:html 挂 dark 类 → Element Plus dark 变量集
|
||||
灰度/色弱:filter 类名
|
||||
```
|
||||
|
||||
## designSetting / projectSetting(settings/)
|
||||
|
||||
| 设置 | 内容 |
|
||||
|------|------|
|
||||
| designSetting.ts | 主题色、水印开关与文案、灰度/色弱、布局宽度等 |
|
||||
| projectSetting.ts | 布局模式(侧栏/顶部)、标签页开关、面包屑、水印等 |
|
||||
| componentSetting.ts | 组件默认(分页 sizes 等) |
|
||||
| animateSetting.ts | 页面动画 |
|
||||
|
||||
在线配置经对应 store 持久化——用户偏好跨会话保留。
|
||||
|
||||
## 水印
|
||||
|
||||
```text
|
||||
designSetting 开关 + 文案 → 布局层渲染水印(canvas/css)
|
||||
用于截图防泄密/内部标识场景
|
||||
```
|
||||
|
||||
## 页面样式规约
|
||||
|
||||
```text
|
||||
1. 优先用 Element Plus 组件与 CSS 变量(勿直接写死颜色值)
|
||||
2. 页面 scoped 样式(<style scoped>);跨页公共样式进 styles/public
|
||||
3. 主题相关颜色用 var(--el-color-*)(随主题切换自动适配)
|
||||
4. 少用 deep 选择器覆盖(Element 升级易碎);确需覆盖集中在 element/ 目录
|
||||
5. 响应式:布局层已处理侧栏折叠;页面内部栅格用 el-row/el-col
|
||||
6. lint:stylelint 检查样式规范(pnpm lint:stylelint)
|
||||
```
|
||||
|
||||
## 定制品牌色流程
|
||||
|
||||
```text
|
||||
1. styles/element 覆盖 --el-color-primary 及其 light-3/5/7/8/9 派生
|
||||
2. 或 designSetting 默认主题色配置(运行时切换)
|
||||
3. 图表(echarts)色板同步调整(dashboard 组件色值)
|
||||
4. 图标/logo 替换(assets + 登录页/logo 配置)
|
||||
```
|
||||
|
||||
## 排障
|
||||
|
||||
| 症状 | 原因 |
|
||||
|------|------|
|
||||
| 变量覆盖不生效 | 引入顺序问题(覆盖必须在 Element 样式之后) |
|
||||
| 暗黑模式部分白块 | 第三方组件未适配 dark 变量;局部补 dark 样式 |
|
||||
| 样式冲突错乱 | scoped 丢失;或与 Element 内部类名冲突 |
|
||||
| 构建后样式差异 | less/scss 变量在构建期求值——运行时切换需 CSS 变量方案 |
|
||||
@@ -0,0 +1,74 @@
|
||||
# 9.16 工具函数
|
||||
|
||||
:::tip 概述
|
||||
utils/ 提供通用工具:HTTP 封装(9.4 已述)、Storage 本地存储、auth 路径工具、downloadFile 下载、dateUtil/formatTime 时间、validate 校验、is 类型判断、urlUtils 等。本节速查常用函数。
|
||||
:::
|
||||
|
||||
## utils/ 工具地图
|
||||
|
||||
| 文件/目录 | 职责 | 常用函数 |
|
||||
|-----------|------|---------|
|
||||
| http/axios | HTTP 封装 | http.request(详见 9.4) |
|
||||
| Storage.ts | 本地存储封装 | storage.get/set/remove(token 等) |
|
||||
| auth.ts | 路由/树工具 | getNormalPath、isExternal、findTreeByPath、objectToQuery |
|
||||
| downloadFile.ts | 文件下载 | 导出 URL 触发浏览器下载 |
|
||||
| dateUtil.ts / formatTime.ts | 日期处理 | dayjs 封装、格式化 |
|
||||
| validate.ts | 校验 | 表单校验辅助 |
|
||||
| urlUtils.ts | URL 工具 | setObjToUrlParams(参数拼接) |
|
||||
| is/ | 类型判断 | isString/isUrl/isEmpty… |
|
||||
| helper/ lib/ file/ | 辅助函数库 | 按需探索 |
|
||||
| propTypes.ts | prop 类型 | 组件 prop 校验 |
|
||||
| color.ts | 颜色 | 主题色处理 |
|
||||
| log.ts | 日志 | 调试日志 |
|
||||
| lodashChunk.ts | 分块 | 数组分块 |
|
||||
| useLockFn.ts | 防重 | 函数锁(防连点) |
|
||||
| wartermark.ts | 水印 | 水印绘制 |
|
||||
|
||||
## Storage(令牌存取核心)
|
||||
|
||||
```ts
|
||||
import { storage } from '@/utils/Storage';
|
||||
|
||||
storage.get(ACCESS_TOKEN); // 守卫/拦截器读取
|
||||
storage.set(ACCESS_TOKEN, v); // 登录写入
|
||||
storage.remove(ACCESS_TOKEN); // 登出清除
|
||||
|
||||
// ACCESS_TOKEN 键名常量在 store/mutation-types.ts
|
||||
```
|
||||
|
||||
## auth.ts 路径工具(守卫消费)
|
||||
|
||||
```text
|
||||
isExternal(path) 是否外链(https?:/mailto:/tel:)
|
||||
getNormalPath(path) 路径归一(去重复斜杠/尾斜杠)
|
||||
findTreeByPath 按 path 查菜单树节点(首个可达路由定位)
|
||||
objectToQuery 对象转 query 字符串
|
||||
```
|
||||
|
||||
## 下载与时间
|
||||
|
||||
```ts
|
||||
// 导出下载:后端返回文件 URL → 触发浏览器下载
|
||||
import { downloadFile } from '@/utils/downloadFile';
|
||||
downloadFile(url);
|
||||
|
||||
// 日期:dayjs 封装
|
||||
import { formatTime } from '@/utils/dateUtil';
|
||||
```
|
||||
|
||||
## hooks/ 配合(组合式复用)
|
||||
|
||||
```text
|
||||
hooks/core/ 核心(useMessage 等)
|
||||
hooks/setting/ useGlobSetting(读环境变量)/ useDesign(主题)
|
||||
hooks/web/ useBattery/useOnline/useTime 等浏览器能力
|
||||
hooks/event/ 事件
|
||||
```
|
||||
|
||||
## 使用规约
|
||||
|
||||
+ 工具函数无业务依赖(不 import api/store)——保持纯函数/低耦合
|
||||
+ 新增公共工具放 utils/,仅页面用的放页面目录或 hooks
|
||||
+ 日期统一 dayjs(勿混用 moment/原生拼串)
|
||||
+ 本地存储统一 storage 封装(不直接 localStorage——便于前缀/过期管理)
|
||||
+ 类型判断用 is/ 目录(勿手写 toString 判断)
|
||||
Reference in New Issue
Block a user