# JeecgBoot Vue3 前端框架全局开发规范与最佳实践
> 分析日期:2026-06-03
> 框架版本:JeecgBoot 3.9.2(基于 Vben-Admin Vue3 深度定制)
> 分析范围:JeecgBoot 原框架(排除 `views/recruitment` 和 `views/spbb` 业务目录)
---
## 一、框架概述
### 1.1 技术栈
| 层级 | 技术 |
|-------|-----------------------------|
| 框架基础 | Vue3 + TypeScript + Vite |
| UI组件库 | Ant Design Vue 3.x |
| 状态管理 | Pinia |
| 路由 | Vue Router 4.x |
| 表格核心 | Vben BasicTable + VXE Table |
| HTTP | Axios(VAxios 封装) |
| 图标 | Ant Design Icons + Iconify |
| 国际化 | vue-i18n |
| 富文本 | Tinymce |
| 图表 | ECharts |
| 工具库 | lodash-es + dayjs + big.js |
### 1.2 目录结构(核心)
```
jeecgboot-vue3/src/
├── api/ # 全局公共 API(系统接口)
├── assets/ # 静态资源
├── components/ # 通用组件
│ ├── Form/ # BasicForm + 46种表单组件
│ ├── Table/ # BasicTable
│ ├── Modal/ # BasicModal / JModal
│ ├── Drawer/ # BasicDrawer
│ ├── jeecg/ # JeecgBoot 业务组件(JVxeTable/JPrompt/OnLine等)
│ └── registerGlobComp.ts # 全局组件注册
├── directives/ # 自定义指令(v-auth/v-loading等)
├── enums/ # 枚举定义
├── hooks/ # 自定义 Hooks
│ ├── system/ # useListPage/useMethods/useJvxeMethods
│ ├── web/ # usePermission/useMessage/useWebSocket/useECharts
│ ├── core/ # useAttrs/useContext/useLockFn/useRefs
│ ├── event/ # useBreakpoint/useEventListener/useScroll
│ ├── setting/ # useGlobSetting/useRootSetting/useMenuSetting
│ ├── jeecg/ # useAdaptiveWidth
│ └── component/ # useFormItem/usePageContext
├── layouts/ # 布局组件(default/iframe/page)
├── router/ # 路由配置 + 守卫
├── store/ # Pinia Store(8个模块)
├── utils/ # 工具类
│ ├── http/axios/ # HTTP 封装(VAxios + defHttp)
│ ├── auth/ # 认证工具
│ ├── cache/ # 缓存体系
│ ├── common/ # 通用工具(compUtils/renderUtils/vxeUtils)
│ └── dict/ # 字典工具
├── settings/ # 全局配置
└── views/ # 业务页面
├── system/ # 系统管理模块(17+子模块)
├── demo/ # 各种示例
└── super/online/ # Online 表单
```
---
## 二、页面文件结构规范
### 2.1 单表 CRUD 模块(标准4件套)
```
模块名/
index.vue # 列表主页(使用 useListPage + BasicTable)
xxx.data.ts # columns + searchFormSchema + formSchema
xxx.api.ts # 所有接口定义(enum Api + 导出函数)
XxxModal.vue # 新增/编辑弹窗(使用 BasicModal + BasicForm)
```
**典型代表**:`system/notice/`、`system/fillRule/`、`system/checkRule/`、`system/position/`、`system/examples/demo/`
### 2.2 主子表(一对多)模块
```
模块名/
index.vue # 主表列表+子表Tab区域
xxx.data.ts # 主表 + 所有子表的 columns/formSchema
xxx.api.ts # 主表 + 所有子表的 CRUD 接口
MainModal.vue # 主表编辑弹窗
SubTableList.vue # 子表列表组件(每个子表一个)
components/
SubModal.vue # 子表编辑弹窗(每个子表一个)
```
**典型代表**:`demo/jeecg/erplist/`
### 2.3 树形页面模块
```
模块名/
index.vue # 左树右表主页面
xxx.data.ts # 表格 columns + 表单 schema
xxx.api.ts # 树接口 + 列表接口 + CRUD接口
components/
XxxModal.vue # 编辑弹窗
LeftTree.vue # 左侧树组件(可选)
```
**典型代表**:`system/category/`(纯树形)、`system/depart/`(左树右表)
---
## 三、API 文件编写规范
### 3.1 最优写法
```typescript
import { defHttp } from '/@/utils/http/axios';
import { Modal } from 'ant-design-vue';
enum Api {
list = '/模块/实体/list',
save = '/模块/实体/add',
edit = '/模块/实体/edit',
get = '/模块/实体/queryById',
delete = '/模块/实体/delete',
deleteBatch = '/模块/实体/deleteBatch',
exportXls = '/模块/实体/exportXls',
importExcel = '/模块/实体/importExcel',
}
/** 导出 url(供导入导出组件使用) */
export const getExportUrl = Api.exportXls;
/** 导入 url */
export const getImportUrl = Api.importExcel;
/** 列表查询 */
export const list = (params) => defHttp.get({ url: Api.list, params });
/** 通过ID查询 */
export const getById = (params) => defHttp.get({ url: Api.get, params });
/** 保存或更新 */
export const saveOrUpdate = (params, isUpdate) => {
let url = isUpdate ? Api.edit : Api.save;
return defHttp.post({ url: url, params });
};
/** 单条删除 */
export const deleteOne = (params, handleSuccess) => {
return defHttp.delete({ url: Api.delete, params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
};
/** 批量删除 */
export const batchDelete = (params, handleSuccess) => {
Modal.confirm({
title: '确认删除',
content: '是否删除选中数据',
okText: '确认',
cancelText: '取消',
onOk: () => {
return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
handleSuccess();
});
},
});
};
```
### 3.2 API 编写要点
| 要点 | 规范 |
|----------|-------------------------------------------------------------------|
| URL定义 | 使用 `enum Api` 集中管理,不要散落 |
| GET请求 | `defHttp.get({ url, params })` |
| POST请求 | `defHttp.post({ url, params })` |
| DELETE请求 | `defHttp.delete({ url, params/data }, { joinParamsToUrl: true })` |
| 导出URL | 单独 `export const getExportUrl = Api.exportXls` |
| 导入URL | 单独 `export const getImportUrl = Api.importExcel` |
| 保存/更新合并 | `saveOrUpdate(params, isUpdate)` 通过判断URL区分新增/编辑 |
| 删除回调 | 第二个参数 `handleSuccess` 通常传入 `reload` |
---
## 四、Data 文件编写规范
### 4.1 columns 定义
```typescript
import { BasicColumn } from '/@/components/Table';
import { render } from '/@/utils/common/renderUtils';
export const columns: BasicColumn[] = [
{
title: '姓名',
dataIndex: 'name',
width: 170,
align: 'left',
resizable: true, // 允许拖拽调整列宽
sorter: { multiple: 1 }, // 支持多字段排序
},
{
title: '性别',
dataIndex: 'sex',
width: 120,
resizable: true,
sorter: { multiple: 2 },
customRender: ({ text }) => {
return render.renderDict(text, 'sex'); // 字典翻译渲染
},
},
{
title: '生日',
dataIndex: 'birthday',
width: 120,
resizable: true,
},
{
title: '订单类型', // 非字典的固定选项翻译
dataIndex: 'ctype',
width: 160,
customRender: ({ text }) => {
return text == '1' ? '国内订单' : text == '2' ? '国际订单' : '';
},
},
];
```
### 4.2 searchFormSchema 定义
```typescript
import { FormSchema } from '/@/components/Table';
export const searchFormSchema: FormSchema[] = [
{
field: 'name',
label: '姓名',
component: 'Input',
componentProps: { trim: true }, // 自动去首尾空格
colProps: { span: 6 },
},
{
field: 'birthday',
label: '生日',
component: 'RangePicker', // 日期范围查询
componentProps: { valueType: 'Date' },
colProps: { span: 8 },
},
{
field: 'age',
label: '年龄',
component: 'Input', // 区间查询通过 slot 自定义
slot: 'age',
colProps: { span: 8 },
},
{
field: 'sex',
label: '性别',
component: 'JDictSelectTag', // 字典下拉
componentProps: {
dictCode: 'sex',
placeholder: '请选择性别',
},
colProps: { span: 8 },
},
{
field: 'departId',
label: '部门',
component: 'JSelectDept', // 部门选择
colProps: { span: 6 },
},
{
field: 'status',
label: '状态',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'valid_status',
placeholder: '请选择状态',
},
colProps: { span: 6 },
},
];
```
### 4.3 formSchema 定义(弹窗表单)
```typescript
export const formSchema: FormSchema[] = [
{
field: 'id',
label: 'id',
component: 'Input',
show: false, // 隐藏字段(主键等)
},
{
field: 'name',
label: '名称',
component: 'Input',
required: true, // 必填校验
componentProps: { placeholder: '请输入名称' },
},
{
field: 'sex',
label: '性别',
component: 'JDictSelectTag',
defaultValue: '1', // 默认值
componentProps: {
type: 'radio', // 字典渲染为单选按钮组
dictCode: 'sex',
},
},
{
field: 'birthday',
label: '生日',
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD', // 日期格式化
placeholder: '请选择生日',
},
},
{
field: 'email',
label: '邮箱',
component: 'Input',
rules: [{ required: false, type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],
componentProps: { placeholder: '请输入邮箱' },
},
{
field: 'orderId', // 主子表外键字段
label: 'orderId',
component: 'Input',
show: false, // 隐藏
},
];
```
### 4.4 表单组件类型速查(componentMap 46种)
**Ant Design Vue 基础组件(22种)**:
| component值 | 说明 | 常用componentProps |
|-----------------|------|----------------------------------------|
| `Input` | 输入框 | `trim`, `placeholder` |
| `InputTextArea` | 多行文本 | `rows`, `placeholder` |
| `InputNumber` | 数字输入 | `min`, `max`, `step` |
| `InputPassword` | 密码框 | `placeholder` |
| `Select` | 下拉选择 | `options: [{label, value}]` |
| `TreeSelect` | 树选择 | `treeData`, `fieldNames` |
| `Switch` | 开关 | `checkedChildren`, `unCheckedChildren` |
| `RadioGroup` | 单选组 | `options: [{label, value}]` |
| `CheckboxGroup` | 复选组 | `options` |
| `Cascader` | 级联选择 | `options` |
| `DatePicker` | 日期选择 | `valueFormat: 'YYYY-MM-DD'` |
| `MonthPicker` | 月份选择 | `valueFormat` |
| `RangePicker` | 日期范围 | `valueType: 'Date'` |
| `TimePicker` | 时间选择 | `valueFormat` |
| `Slider` | 滑动条 | `min`, `max` |
| `Rate` | 评分 | `count` |
| `AutoComplete` | 自动完成 | `options` |
| `Divider` | 分隔线 | — |
**JeecgBoot 业务组件(32种,J开头为主)**:
| component值 | 说明 | 常用场景 |
|--------------------------------|----------------------|------------|
| `JDictSelectTag` | 字典下拉/单选/多选 | 所有字典字段 |
| `JInput` | 增强输入框(支持模糊/不等于等查询模式) | 查询条件 |
| `JImageUpload` | 图片上传 | 图片字段 |
| `JUpload` | 文件上传 | 附件字段 |
| `JEditor` | 富文本编辑器 | 富文本内容 |
| `JMarkdownEditor` | Markdown编辑器 | Markdown内容 |
| `JCodeEditor` | 代码编辑器 | 代码/SQL/JS |
| `JPopup` | Popup弹出选择 | 弹窗选择关联数据 |
| `JPopupDict` | 字典Popup弹框 | 表字典选择 |
| `JSelectDept` | 部门选择 | 部门字段 |
| `JSelectUser` / `UserSelect` | 用户选择 | 用户字段 |
| `JSelectRole` / `RoleSelect` | 角色选择 | 角色字段 |
| `JSelectPosition` | 岗位选择 | 岗位字段 |
| `JAreaLinkage` / `JAreaSelect` | 省市区联动 | 地区字段 |
| `JCategorySelect` | 分类选择 | 分类字典 |
| `JTreeDict` | 字典树 | 树形字典 |
| `JTreeSelect` | 自定义树选择 | 树形数据选择 |
| `JSwitch` | 自定义开关 | 开关字段 |
| `JSelectInput` | 下拉+输入组合 | 灵活输入 |
| `JSelectMultiple` | 多选下拉 | 多选字段 |
| `JSearchSelect` | 搜索选择 | 异步搜索选择 |
| `JEasyCron` | Cron表达式 | 定时任务配置 |
| `JCheckbox` | 自定义复选框 | 多选字段 |
| `JInputPop` | 弹出输入框 | 大文本输入 |
| `JLinkTableCard` | 关联记录卡片 | 关联表展示 |
| `JAddInput` | 动态添加输入框 | 多值输入 |
| `JRangeNumber` | 数字范围输入 | 区间查询 |
| `JInputSelect` | 输入选择 | 输入+下拉切换 |
| `RangeDate` | 日期范围 | 日期区间 |
| `RangeTime` | 时间范围 | 时间区间 |
| `ApiSelect` | API远程下拉 | 接口数据下拉 |
| `ApiTreeSelect` | API远程树选择 | 接口树数据 |
| `IconPicker` | 图标选择器 | 菜单图标 |
---
## 五、单表 CRUD 页面最优写法
### 5.1 列表页 index.vue(useListPage 模式 — 推荐)
```vue
```
### 5.2 弹窗 XxxModal.vue
```vue
```
### 5.3 useListPage Hook 详解
`useListPage` 是列表页开发的核心 Hook,封装了表格注册、导入导出、选择行、删除确认等通用逻辑。
**接口定义**:
```typescript
interface ListPageOptions {
designScope?: string; // 样式作用域
tableProps: TableProps; // 表格配置(必填)
pagination?: boolean; // 是否分页
exportConfig?: { // 导出配置
url: string | (() => string);
name?: string | (() => string);
params?: object | (() => object);
};
importConfig?: { // 导入配置
url: string | (() => string);
success?: (fileInfo?: any) => void;
};
}
```
**返回值**:
| 返回值 | 说明 |
|----------------------------|-----------------------------------------------------------------------------|
| `tableContext` | `[registerTable, methods, { rowSelection, selectedRowKeys, selectedRows }]` |
| `onExportXls()` | 导出 Excel(自动获取查询条件+选中行+列设置) |
| `onImportXls(file)` | 导入 Excel |
| `doRequest(api, options?)` | 通用请求(自动确认+刷新+清空选择) |
| `doDeleteRecord(api)` | 单条删除(无确认弹窗) |
| `prefixCls` | CSS 类名前缀 |
| `createMessage` | 消息提示 |
| `createConfirm` | 确认弹窗 |
**useListTable 默认配置**(自动应用,无需手动设置):
| 配置项 | 默认值 | 说明 |
|--------------------|------------------------|-----------|
| `rowKey` | `'id'` | 主键 |
| `useSearchForm` | `true` | 启用搜索表单 |
| `canResize` | `true` | 自适应高度 |
| `minHeight` | `300` | 最小高度 |
| `bordered` | `true` | 边框 |
| `striped` | `false` | 斑马纹 |
| `showTableSetting` | `true` | 表格设置 |
| `beforeFetch` | 默认排序 `createTime desc` | 请求前自动注入排序 |
---
## 六、主子表(一对多)页面最优写法
### 6.1 核心架构
- **主表列表** 使用 `useListPage` + `BasicTable`
- **子表** 使用 `a-tabs` + 独立的子表列表组件
- **主子表通信** 通过 `provide/inject` 传递主表选中 ID
- **主表单弹窗** 内含子表 Tab(新增/编辑时同时操作主表+子表)
### 6.2 主表列表页 index.vue
```vue
```
### 6.3 子表列表组件
```vue
新增
```
### 6.4 主子表 data.ts 规范
在同一个 data.ts 中定义主表和所有子表的配置:
```typescript
// ---- 主表 ----
export const columns: BasicColumn[] = [ /* 主表列 */ ];
export const searchFormSchema: FormSchema[] = [ /* 主表搜索 */ ];
export const formSchema: FormSchema[] = [ /* 主表表单 */ ];
// ---- 子表1 ----
export const sub1Columns: BasicColumn[] = [ /* 子表1列 */ ];
export const sub1FormSchema: FormSchema[] = [ /* 子表1表单 */ ];
// ---- 子表2 ----
export const sub2Columns: BasicColumn[] = [ /* 子表2列 */ ];
export const sub2FormSchema: FormSchema[] = [ /* 子表2表单 */ ];
```
### 6.5 主子表 api.ts 规范
在同一个 api.ts 中定义主表和所有子表的接口:
```typescript
enum Api {
// 主表
list = '/test/order/orderList',
save = '/test/order/add',
edit = '/test/order/edit',
deleteOne = '/test/order/delete',
deleteBatch = '/test/order/deleteBatch',
// 子表1
sub1List = '/test/order/listSub1ByMainId',
saveSub1 = '/test/order/addSub1',
editSub1 = '/test/order/editSub1',
deleteSub1 = '/test/order/deleteSub1',
// 子表2
sub2List = '/test/order/listSub2ByMainId',
saveSub2 = '/test/order/addSub2',
editSub2 = '/test/order/editSub2',
deleteSub2 = '/test/order/deleteSub2',
}
```
---
## 七、树形页面最优写法
### 7.1 纯树形列表(如分类字典)
关键差异:`isTreeTable: true` + 异步加载子节点
```vue
```
### 7.2 左树右表(如部门管理)
- 左侧:独立的树组件(`DepartLeftTree.vue`)
- 右侧:标准 BasicTable
- 通信:树选中 → 触发表格 `reload({ searchInfo: { departId: selectedKey } })`
---
## 八、BasicTable / BasicModal / BasicDrawer 使用规范
### 8.1 BasicTable 核心 API
**注册方式**:
```typescript
// 方式一:useListPage(推荐,自动处理导入导出)
const { tableContext } = useListPage({ tableProps: { ... } });
const [registerTable, { reload, setProps }, { rowSelection, selectedRowKeys }] = tableContext;
// 方式二:useTable(手动管理)
const [registerTable, { reload, setProps, getDataSource, updateTableDataRecord }] = useTable({
api: list,
columns,
formConfig: { schemas: searchFormSchema },
...
});
```
**模板使用**:
```vue
```
**常用方法**:
| 方法 | 说明 |
|-----------------------------------------|--------------|
| `reload(params?)` | 刷新表格(可传搜索参数) |
| `setProps(props)` | 动态修改表格属性 |
| `getDataSource()` | 获取当前数据源 |
| `updateTableDataRecord(rowKey, record)` | 更新指定行数据 |
| `findTableDataRecord(rowKey)` | 查找指定行 |
| `getColumns()` | 获取列配置 |
| `setColumns(columns)` | 设置列配置 |
| `setLoading(loading)` | 设置加载状态 |
| `getForm()` | 获取搜索表单实例 |
### 8.2 BasicModal 核心 API
**注册方式**:
```typescript
// 外部调用方
const [registerModal, { openModal, closeModal, setModalProps }] = useModal();
// Modal 内部
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
// data = openModal 传入的参数
await resetFields();
// 回填表单数据
await setFieldsValue({ ...data.record });
});
```
**常用方法**:
| 方法 | 说明 |
|----------------------------|-------------------------------------------|
| `openModal(visible, data)` | 打开弹窗并传参 |
| `closeModal()` | 关闭弹窗 |
| `setModalProps(props)` | 修改弹窗属性(confirmLoading, showOkBtn, title等) |
### 8.3 BasicDrawer 核心 API
**注册方式**:
```typescript
// 外部调用方
const [registerDrawer, { openDrawer, closeDrawer }] = useDrawer();
// Drawer 内部
const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
// data = openDrawer 传入的参数
});
```
---
## 九、Excel 导入导出规范
### 9.1 使用 useListPage 模式(推荐,最少代码)
```typescript
const { onExportXls, onImportXls } = useListPage({
tableProps: { ... },
exportConfig: {
name: '导出文件名',
url: getExportUrl,
params: { customParam: 'value' }, // 自定义导出参数(可选)
},
importConfig: {
url: getImportUrl,
success: () => { /* 导入成功回调 */ },
},
});
```
模板中直接调用:
```vue
导出
导入
```
### 9.2 使用 useMethods 模式(更多控制)
```typescript
import { useMethods } from '/@/hooks/system/useMethods';
const { handleExportXls, handleImportXls } = useMethods();
// 导出
handleExportXls('文件名', exportUrl, filterParams);
// 导入(配合 a-upload)
```
### 9.3 弹窗导入模式(JImportModal)
```vue
```
---
## 十、字典使用规范
### 10.1 字典翻译渲染(列表列)
```typescript
import { render } from '/@/utils/common/renderUtils';
// 方式一:普通字典翻译
{
title: '性别',
dataIndex: 'sex',
customRender: ({ text }) => render.renderDict(text, 'sex'),
}
// 方式二:表字典翻译(从其他表翻译)
{
title: '部门',
dataIndex: 'departId',
customRender: ({ text }) => render.renderDict(text, 'sys_depart,depart_name,id'),
}
```
### 10.2 字典下拉选择(表单/搜索)
```typescript
// 搜索条件中
{
field: 'sex',
label: '性别',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'sex',
placeholder: '请选择性别',
},
}
// 表单中 - 下拉模式
{
field: 'sex',
label: '性别',
component: 'JDictSelectTag',
componentProps: {
dictCode: 'sex',
},
}
// 表单中 - 单选按钮模式
{
field: 'sex',
label: '性别',
component: 'JDictSelectTag',
componentProps: {
type: 'radio',
dictCode: 'sex',
},
}
```
### 10.3 字典工具函数
```typescript
import { initDictOptions } from '/@/utils/dict';
// 在组件中获取字典选项
const dictOptions = await initDictOptions('sex');
// dictOptions = [{ value: '1', text: '男' }, { value: '2', text: '女' }]
```
---
## 十一、权限控制规范
### 11.1 功能权限(按钮级)
**方式一:v-auth 指令**(控制 DOM 显隐)
```vue
新增
编辑
删除
```
**方式二:usePermission Hook**(逻辑判断)
```typescript
import { usePermission } from '/@/hooks/web/usePermission';
const { hasPermission } = usePermission();
if (hasPermission('user:add')) {
// 有权限才执行的逻辑
}
```
**方式三:TableAction 权限控制**
```typescript
function getTableAction(record) {
return [
{
label: '编辑',
onClick: handleEdit.bind(null, record),
auth: 'user:edit', // 权限码
},
{
label: '删除',
popConfirm: { title: '确定删除?', confirm: handleDelete.bind(null, record) },
auth: 'user:delete',
},
];
}
```
### 11.2 数据权限
由后端 `@PermissionData` 注解 + `QueryGenerator` 自动处理,前端无需额外编码。
### 11.3 免认证配置
```typescript
// 方式一:路由元信息
{
path: '/public-page',
meta: { ignoreAuth: true },
}
// 方式二:将 URL 加入白名单(router/guard/permissionGuard.ts)
```
---
## 十二、HTTP 请求封装
### 12.1 defHttp 使用
```typescript
import { defHttp } from '/@/utils/http/axios';
// GET 请求
export const list = (params) => defHttp.get({ url: '/api/list', params });
// POST 请求
export const save = (params) => defHttp.post({ url: '/api/save', params });
// PUT 请求
export const update = (params) => defHttp.put({ url: '/api/update', params });
// DELETE 请求(参数拼接到URL)
export const remove = (params) => defHttp.delete({ url: '/api/delete', params }, { joinParamsToUrl: true });
// DELETE 请求(参数在body中)
export const batchRemove = (data) => defHttp.delete({ url: '/api/batchDelete', data });
// 文件上传
export const upload = (params) => defHttp.uploadFile({ url: '/api/upload', params });
```
### 12.2 自动注入的请求头
| Header | 说明 |
|------------------|--------------------|
| `X-Access-Token` | JWT Token(自动从缓存获取) |
| `X-Tenant-Id` | 租户ID(自动注入) |
| `X-Sign` | 请求签名(MD5) |
| `X-TIMESTAMP` | 请求时间戳 |
| `X-Version` | 版本号 |
| `X-Low-App-ID` | 低代码应用ID |
### 12.3 响应处理
- `code === 0` → 成功,返回 `result` 数据
- `code === 401` → Token 失效,自动跳转登录页
- 其他 → 错误提示
---
## 十三、常用 Hooks 速查
### 13.1 系统 Hooks
| Hook | 用途 | 核心导出 |
|------------------|-------------|-----------------------------------------------------------|
| `useListPage` | 列表页标准开发 | `tableContext`, `onExportXls`, `onImportXls`, `doRequest` |
| `useMethods` | 导入导出方法 | `handleExportXls`, `handleImportXls` |
| `useJvxeMethods` | JVxeTable方法 | 行编辑表格操作 |
### 13.2 Web Hooks
| Hook | 用途 | 核心导出 |
|-----------------|-----------|-------------------------------------------------------|
| `usePermission` | 权限判断 | `hasPermission(value)`, `isDisabledAuth` |
| `useMessage` | 消息弹窗 | `createMessage`, `createConfirm`, `createConfirmSync` |
| `useWebSocket` | WebSocket | `connectWebSocket(url)`, `onWebSocket(cb)` |
| `useECharts` | ECharts图表 | `setOptions`, `resize`, `getInstance` |
| `useTabs` | 多标签操作 | `refreshPage`, `closeAll`, `closeLeft` |
| `useDesign` | CSS命名空间 | `prefixCls` = `'jeecg'` |
| `usePage` | 页面跳转 | `useGo()`, `useRedo()` |
### 13.3 核心 Hooks
| Hook | 用途 |
|------------------------|------------------------|
| `useAttrs` | 透传属性 |
| `useContext` | 上下文 |
| `useLockFn` | 防重复提交 |
| `useRefs` | 模板 Refs 批量管理 |
| `onMountedOrActivated` | mounted/activated 统一入口 |
---
## 十四、常用工具函数速查
### 14.1 compUtils(最常用)
```typescript
import { getFileAccessHttpUrl, listToTree, filterObj, triggerWindowResizeEvent, getHeaders } from '/@/utils/common/compUtils';
```
| 函数 | 说明 |
|---------------------------------------|--------------------------|
| `getFileAccessHttpUrl(url)` | 文件 URL 拼接(自动加域名前缀) |
| `listToTree(data, pid, key)` | 扁平列表转树形结构 |
| `filterObj(obj)` | 过滤空属性对象 |
| `triggerWindowResizeEvent()` | 触发窗口 resize 事件 |
| `getHeaders()` | 获取上传请求头(含Token+TenantId) |
| `mapTableTotalSummary(data, columns)` | 表格合计行(big.js精度计算) |
| `simpleDebounce(fn, delay)` | 防抖 |
| `dateFormat(date, fmt)` | 日期格式化 |
| `bindMapFormSchema(schemas, span)` | 表单 span 响应式映射 |
### 14.2 renderUtils
```typescript
import { render } from '/@/utils/common/renderUtils';
render.renderDict(text, dictCode) // 字典翻译
render.renderTag(text, color) // 标签渲染
render.renderSwitch(text, colorMap) // 开关渲染
render.renderDate(text) // 日期格式化
```
### 14.3 dict 工具
```typescript
import { initDictOptions, getDictItemsByCode, ajaxGetDictItems } from '/@/utils/dict';
const options = await initDictOptions('sex'); // 获取字典选项(优先缓存)
```
### 14.4 auth 工具
```typescript
import { getToken, getTenantId, setAuthCache, getAuthCache, clearAuthCache } from '/@/utils/auth';
```
---
## 十五、自定义指令
| 指令 | 用途 | 示例 |
|-------------------|-----------|----------------------------------------|
| `v-auth` | 权限控制DOM显隐 | `v-auth="'user:add'"` |
| `v-loading` | 加载遮罩 | `v-loading="loading"` |
| `v-click-outside` | 点击外部触发 | `v-click-outside="handleClickOutside"` |
| `v-repeat-click` | 长按重复触发 | `v-repeat-click="handleRepeat"` |
| `v-ripple` | 水波纹效果 | `v-ripple` |
---
## 十六、全局配置文件
### 16.1 projectSetting.ts(核心配置)
| 配置项 | 默认值 | 说明 |
|-----------------------|-----------|-------------------------------------------|
| `permissionMode` | `'BACK'` | 权限模式(BACK=后端动态路由) |
| `permissionCacheType` | `'LOCAL'` | 权限缓存方式 |
| `themeMode` | `'LIGHT'` | 主题模式 |
| `showBreadCrumb` | `true` | 面包屑 |
| `openKeepAlive` | `true` | 页面缓存 |
| `headerSetting` | — | 头部配置(fixed/showDoc/showNotice/showSearch) |
| `menuSetting` | — | 菜单配置(type/mode/collapsed/accordion) |
| `multiTabsSetting` | — | 多标签配置(cache/show/canDrag) |
### 16.2 componentSetting.ts(组件默认配置)
| 配置项 | 默认值 |
|-------------------|----------------|
| `table.pageNo` | `'pageNo'` |
| `table.pageSize` | `'pageSize'` |
| `table.records` | `'records'` |
| `table.total` | `'total'` |
| `form.labelCol` | `{ span: 4 }` |
| `form.wrapperCol` | `{ span: 18 }` |
---
## 十七、Pinia Store 模块
| Store ID | 功能 | 核心状态 |
|--------------------|-------|-------------------------------|
| `app` | 应用全局 | 暗黑模式、页面加载、项目配置 |
| `app-user` | 用户 | token、用户信息、角色列表、**字典缓存**、租户ID |
| `app-permission` | 权限/路由 | permCodeList、authList、动态路由 |
| `app-multiple-tab` | 多标签页 | tab列表、KeepAlive列表 |
| `app-lock` | 锁屏 | 锁屏密码 |
| `app-locale` | 国际化 | 当前语言、路径标题映射 |
| `app-error-log` | 错误日志 | AJAX错误信息 |
| `defIndex` | 默认首页 | url和component |
---
## 十八、路由体系
### 18.1 路由模式
| 模式 | 说明 | 配置 |
|---------------|-----------------|-----------------------------------|
| **BACK**(默认) | 后端动态菜单 → 自动生成路由 | `permissionMode: 'BACK'` |
| ROLE | 后端角色 → 前端路由 | `permissionMode: 'ROLE'` |
| ROUTE_MAPPING | 前端静态路由 + 权限过滤 | `permissionMode: 'ROUTE_MAPPING'` |
### 18.2 路由守卫执行顺序
```
1. PageGuard — 状态检查
2. PageLoadingGuard — 页面加载进度条
3. HttpGuard — 清除pending请求
4. ScrollGuard — 滚动重置
5. MessageGuard — 消息清理
6. ProgressGuard — NProgress
7. PermissionGuard — 权限核心(白名单/登录判断/动态路由构建)
8. ParamMenuGuard — 参数菜单处理
9. StateGuard — 登录页状态清理
```
### 18.3 免登录路由
框架内置以下免登录路由(在 `routes/index.ts` 定义):
| 路由 | 说明 |
|---------------------|-----------------|
| `/login` | 登录页 |
| `/oauth2-app/login` | OAuth2免登(钉钉/企微) |
| `/tokenLogin` | Token静默登录 |
| `/ssoLogin` | 智慧人社SSO |
| `/file/share` | 文件分享 |
---
## 十九、JVxeTable(可编辑行表格)
用于需要行内编辑的场景(如主子表子表、批量录入等)。
### 19.1 基本使用
```vue
```
### 19.2 单元格类型(14种)
| 类型 | 说明 | 对应组件 |
|---------------------|-------|------------------------|
| `input` | 输入框 | JVxeInputCell |
| `inputNumber` | 数字输入 | JVxeInputCell |
| `select` | 下拉选择 | JVxeSelectCell |
| `selectSearch` | 搜索选择 | JVxeSelectCell |
| `selectMultiple` | 多选 | JVxeSelectCell |
| `date` / `datetime` | 日期 | JVxeDateCell |
| `time` | 时间 | JVxeTimeCell |
| `checkbox` | 复选框 | JVxeCheckboxCell |
| `radio` | 单选 | JVxeRadioCell |
| `textarea` | 多行文本 | JVxeTextareaCell |
| `upload` | 文件上传 | JVxeUploadCell |
| `treeSelect` | 树选择 | JVxeTreeSelectCell |
| `catTreeSelect` | 分类字典树 | JVxeCategorySelectCell |
| `progress` | 进度条 | JVxeProgressCell |
| `rowDragSort` | 拖拽排序 | JVxeDragSortCell |
| `slot` | 插槽自定义 | JVxeSlotCell |
### 19.3 数据校验(validateRules)
JVxeTable 完全支持对输入数据进行校验检查,包括非空、纯数字、纯字母、正则、自定义函数等。校验规则通过列配置中的 `validateRules`
数组定义。
#### 19.3.1 两级校验机制
| 层级 | 配置位置 | 说明 |
|---------|---------------------|-----------------------------------------|
| **组件级** | `editRules` prop | 全局 vxe-table 原生 `editRules`,可对所有列施加通用校验 |
| **列级** | `validateRules` 列属性 | 每列独立配置校验规则数组,优先级更高 |
两种校验规则通过 `lodash.merge` 合并后传入底层 vxe-table。
#### 19.3.2 validateRules 规则项字段
| 字段 | 类型 | 说明 |
|-------------|---------------------|------------------------------------------------------|
| `required` | `boolean` | 是否必填(非空校验) |
| `pattern` | `string` / `RegExp` | 正则表达式,支持快捷值 |
| `message` | `string` | 校验失败提示,支持 `${title}`、`${key}`、`${defaultValue}` 变量替换 |
| `validator` | `function` | 自定义异步校验函数(Promise 形式) |
| `handler` | `function` | 旧版回调式校验函数(兼容模式,内部转为 validator) |
| `unique` | `boolean` | 唯一性校验(同一列中值不能重复) |
#### 19.3.3 内置快捷校验值(fooPatterns)
组件内置了常用校验规则的快捷值,使用时直接传入 `pattern` 对应的快捷字符串即可,无需写正则。
定义位置:[useValidateRules.ts](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useValidateRules.ts#L82-L105)
| 快捷值 | 含义 | 对应正则 |
|---------|-----------|-------------------------------------------------------------|
| `*` | 非空 | 等同于 `required: true` |
| `n` | 纯数字(含小数) | `/^-?\d+(\.?\d+\|\d?)$/` |
| `s` | 纯字母 | `/^[A-Z\|a-z]+$/` |
| `z` | 整数 | `/^-?\d+$/` |
| `e` | 电子邮件 | `/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/` |
| `m` | 手机号码 | `/^1[3456789]\d{9}$/` |
| `p` | 邮政编码 | `/^\d{6}$/` |
| `url` | 网址 | URL 格式正则 |
| `money` | 金额 | `/^(([1-9][0-9]*)\|([0]\.\d{0,2}\|[1-9][0-9]*\.\d{0,5}))$/` |
| `n6-16` | 6到16位数字 | `/^\d{6,16}$/` |
| `*6-16` | 6到16位任意字符 | `/^.{6,16}$/` |
| `s6-18` | 6到18位字母 | `/^[a-z\|A-Z]{6,18}$/` |
| `only` | 唯一性校验 | 同 `unique: true`,遍历整表检查同列值是否重复 |
#### 19.3.4 使用示例
```typescript
import {JVxeTable, JVxeTypes} from '/@/components/jeecg/JVxeTable';
const columns = [
{
key: 'name',
title: '姓名',
type: JVxeTypes.input,
// 非空校验
validateRules: [{required: true, message: '${title}不能为空'}],
},
{
key: 'age',
title: '年龄',
type: JVxeTypes.inputNumber,
// 整数校验 + 非空
validateRules: [
{required: true, message: '${title}不能为空'},
{pattern: 'z', message: '${title}必须为整数'},
],
},
{
key: 'email',
title: '邮箱',
type: JVxeTypes.input,
// 非必填但格式校验
validateRules: [{pattern: 'e', message: '${title}格式不正确'}],
},
{
key: 'phone',
title: '手机号',
type: JVxeTypes.input,
// 手机号校验
validateRules: [{pattern: 'm', message: '${title}格式不正确'}],
},
{
key: 'code',
title: '编码',
type: JVxeTypes.input,
// 纯字母校验 + 唯一性校验
validateRules: [
{required: true, message: '${title}不能为空'},
{pattern: 's', message: '${title}只能包含字母'},
{unique: true, message: '${title}不能重复'},
],
},
{
key: 'amount',
title: '金额',
type: JVxeTypes.inputNumber,
// 金额格式校验
validateRules: [{pattern: 'money', message: '${title}格式不正确'}],
},
{
key: 'idCard',
title: '身份证号',
type: JVxeTypes.input,
// 自定义正则校验
validateRules: [{
pattern: '^\\d{6}(18|19|20)?\\d{2}(0[1-9]|1[012])(0[1-9]|[12]\\d|3[01])\\d{3}(\\d|[xX])$',
message: '${title}格式不正确',
}],
},
{
key: 'customField',
title: '自定义校验',
type: JVxeTypes.input,
// 自定义校验函数(handler 回调式,兼容旧版)
validateRules: [{
handler: (event, callback) => {
const {cellValue} = event;
if (cellValue && cellValue.length < 3) {
callback(false, '长度不能少于3位');
} else {
callback(true);
}
},
message: '${title}校验不通过',
}],
},
];
```
#### 19.3.5 非编辑组件的必填标记
对于 `checkbox`、`radio`、`upload`、`progress`、`departSelect`、`userSelect`、`image`、`file` 等非文本编辑组件,如果配置了
`validateRules` 且包含 `required`,会在列头的 `title` 前自动加 ` * ` 标记,提示用户该列为必填。
定义位置:[useColumns.ts](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts#L125-L149)
#### 19.3.6 手动触发校验
JVxeTable 对外暴露了两个校验方法,可在外部通过 ref 调用:
| 方法 | 说明 |
|----------------------------|--------------------------------|
| `validateTable(rows?)` | 校验当前表格,成功返回 `null`,失败返回 errMap |
| `fullValidateTable(rows?)` | 完整校验所有数据(无论是否修改过) |
```vue
```
定义位置:[useMethods.ts](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useMethods.ts#L580-L595)
### 19.4 插槽自定义字段输入(slotName + JVxeTypes.slot)
JVxeTable 支持通过插槽(slot)方式自定义某个字段的输入框内容,适用于需要自定义编辑组件(如下拉框、级联选择、日期范围等)且选项数据来自父组件的场景。
#### 19.4.1 插槽机制原理
JVxeTable 的插槽机制分两层:
**第一层:`JVxeTypes.slot` + `slotName`(推荐方式)**
这是 JVxeTable 内置的插槽支持机制,完全集成到行编辑系统中。
- 列配置中设置 `type: JVxeTypes.slot`,同时指定 `slotName`(如 `'myFieldSlot'`)
- JVxeTable 内部的 `JVxeSlotCell` 组件负责渲染该单元格
- `useColumns.ts` 中的 `handleSlots()`
方法([源码](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/hooks/useColumns.ts#L402-L408)
)会检查父组件是否传入了同名插槽,若有则将插槽函数注入渲染参数
**第二层:`$slots` 透传至底层 `vxe-grid`**
在 [JVxeTable.ts](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/JVxeTable.ts#L77-L81)
的 render 函数中,`this.$slots` 被完整透传给底层的 `vxe-grid` 组件。这意味着所有传递给 JVxeTable 的插槽也会被 vxe-grid
接收,可以使用 vxe-grid 原生的所有插槽能力。
#### 19.4.2 slot 插槽接收的参数
[JVxeSlotCell.ts](file:///d:/Code/Project/湛江人社/code/zjrs-jeecgBoot/jeecgboot-vue3/src/components/jeecg/JVxeTable/src/components/cells/JVxeSlotCell.ts#L10-L24)
中定义了插槽函数接收的参数对象:
| 参数 | 类型 | 说明 |
|----------------------|-------------------|------------------------------|
| `value` | `any` | 当前单元格的值 |
| `row` | `object` | 当前行数据 |
| `column` | `object` | 当前列配置信息 |
| `params` | `object` | vxe-table 原始渲染参数 |
| `$table` | `object` | vxe-table 实例,可调用内置方法 |
| `rowId` | `string` | 当前行唯一标识 |
| `index` / `rowIndex` | `number` | 当前行下标 |
| `columnIndex` | `number` | 当前列下标 |
| `scrolling` | `boolean` | 是否正在滚动 |
| `reloadEffect` | `boolean` | 是否开启了数据刷新特效 |
| `triggerChange` | `(value) => void` | **触发值变更**,用于将插槽组件的值更新到单元格数据中 |
#### 19.4.3 下拉框选项来自父组件的实现示例
**场景**:在 JVxeTable 中,某个字段需要使用下拉选择,下拉选项来自父组件(如通过 props 传入、API 加载、或父组件的响应式数据)。
**列配置(data 文件)**:
```typescript
import {JVxeTypes} from '/@/components/jeecg/JVxeTable';
export const studyAbroadColumns: JVxeColumn[] = [
{
title: '国家/地区',
key: 'country',
type: JVxeTypes.slot, // 使用 slot 类型
slotName: 'countrySlot', // 插槽名称,与模板中的 #countrySlot 对应
width: 160,
validateRules: [{required: true, message: '${title}不能为空'}],
},
{
title: '学校名称',
key: 'schoolName',
type: JVxeTypes.input,
width: 200,
},
{
title: '开始日期',
key: 'startDate',
type: JVxeTypes.date,
width: 140,
},
// ... 其他列
];
```
**父组件模板**:
```vue
triggerChange(val)"
/>
```
**父组件脚本**:
```typescript
```
#### 19.4.4 关键注意点
1. **`triggerChange` 是必须调用的**:插槽组件值变更时,必须调用 `triggerChange(newValue)` 将值同步回 JVxeTable
的数据源,否则表格数据不会更新。
2. **插槽名称要唯一**:每个 `JVxeTypes.slot` 列需要指定不同的 `slotName`,同一个 JVxeTable 内不能重名。
3. **slot 列支持校验**:`validateRules` 对 slot 列同样生效,非空校验会在列头显示 ` * ` 标记。
4. **`bordered: false`**:插槽内部使用 Ant Design 组件时,建议设置 `:bordered="false"` 以与表格单元格样式融合。
5. **插槽作用域来自父组件**:插槽模板在父组件中编写,可以直接访问父组件的所有响应式数据(props、ref、computed
等),因此下拉选项可以灵活地来自任何父组件数据源。
6. **与普通 select 类型对比**:
- `JVxeTypes.select`:适用于选项固定或通过 `dictCode` 从字典加载的场景
- `JVxeTypes.slot`:适用于选项来自父组件、需要完全自定义UI、或涉及复杂交互的场景
---
## 二十、WebSocket 实时推送
```typescript
import { useWebSocket } from '/@/hooks/web/useWebSocket';
// 建立连接
connectWebSocket('ws://localhost:8080/websocket');
// 添加监听
onWebSocket((data) => {
console.log('收到消息:', data);
});
// 移除监听
offWebSocket(callback);
```
特性:自动重连(10次/5s间隔)、心跳(55s)、全局单例。
---
## 二十一、关键源码索引
### 21.1 核心 Hooks
| 组件 | 文件 |
|---------------|--------------------------------------------------|
| useListPage | `jeecgboot-vue3/src/hooks/system/useListPage.ts` |
| useMethods | `jeecgboot-vue3/src/hooks/system/useMethods.ts` |
| usePermission | `jeecgboot-vue3/src/hooks/web/usePermission.ts` |
| useMessage | `jeecgboot-vue3/src/hooks/web/useMessage.ts` |
| useWebSocket | `jeecgboot-vue3/src/hooks/web/useWebSocket.ts` |
| useECharts | `jeecgboot-vue3/src/hooks/web/useECharts.ts` |
### 21.2 核心组件
| 组件 | 文件 |
|--------------|------------------------------------------------------------------|
| BasicTable | `jeecgboot-vue3/src/components/Table/src/BasicTable.vue` |
| BasicModal | `jeecgboot-vue3/src/components/Modal/src/BasicModal.vue` |
| BasicDrawer | `jeecgboot-vue3/src/components/Drawer/src/BasicDrawer.vue` |
| BasicForm | `jeecgboot-vue3/src/components/Form/src/BasicForm.vue` |
| JVxeTable | `jeecgboot-vue3/src/components/jeecg/JVxeTable/src/JVxeTable.ts` |
| componentMap | `jeecgboot-vue3/src/components/Form/src/componentMap.ts` |
### 21.3 工具类
| 工具 | 文件 |
|-------------|--------------------------------------------------|
| defHttp | `jeecgboot-vue3/src/utils/http/axios/index.ts` |
| compUtils | `jeecgboot-vue3/src/utils/common/compUtils.ts` |
| renderUtils | `jeecgboot-vue3/src/utils/common/renderUtils.ts` |
| dict | `jeecgboot-vue3/src/utils/dict/index.ts` |
| auth | `jeecgboot-vue3/src/utils/auth/index.ts` |
| cache | `jeecgboot-vue3/src/utils/cache/persistent.ts` |
### 21.4 示例页面
| 类型 | 文件 |
|-----------|------------------------------------------------------|
| 单表CRUD | `jeecgboot-vue3/src/views/system/examples/demo/` |
| 主子表ERP | `jeecgboot-vue3/src/views/demo/jeecg/erplist/` |
| 树形列表 | `jeecgboot-vue3/src/views/system/category/` |
| 树形左树右表 | `jeecgboot-vue3/src/views/system/depart/` |
| JVxeTable | `jeecgboot-vue3/src/views/demo/jeecg/JVxeTableDemo/` |
---
## 二十二、涉及的文件清单
| 文件 | 操作 | 原因 |
|--------------------------------------------|----|---------------------|
| `.docs/260603-JeecgBoot前端框架全局开发规范与最佳实践.md` | 新增 | 重新分析前端框架,撰写全面开发规范文档 |