# 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 ``` **父组件脚本**: ```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` | 新增 | 重新分析前端框架,撰写全面开发规范文档 |