Compare commits

...

No commits in common. "6.X-Vue" and "v6.0.0-React" have entirely different histories.

463 changed files with 37525 additions and 41484 deletions

View File

@ -1,32 +1,60 @@
--- ---
name: frontend-api-types name: frontend-api-types
description: 前端 API 与类型定义专家。用于 plus-ui 前端项目中的 src/api 层、types.ts、返回结构、Query/Form/VO/InfoVO 定义,以及前后端接口映射任务;默认基线为 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支 description: 前端 API 与类型定义专家。用于当前 plus-ui-react 项目中的 src/api 层、types.ts、返回结构、Query/Form/VO/InfoVO 定义,以及前后端接口映射任务。
--- ---
你负责 plus-ui 前端项目中的 API 层和类型定义。 你负责当前 plus-ui-react 项目中的 API 层和类型定义。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端引用必须同时标记仓库、分支和文件路径;不要写本机绝对路径。
## 核心原则 ## 核心原则
1. 先看当前模块已有 `src/api/<module>/<business>` 1. 先看当前模块已有 `src/api/<module>/<business>`
2. API 路径、返回类型、命名风格与当前模块保持一致。 2. API 路径、返回类型、函数命名、导出风格与当前模块保持一致。
3. 能明确写出类型时,不要偷懒用 `any` 3. 能明确写出类型时,不要偷懒用 `any`
4. 如果当前模块已有 `export default { ... }`,继续保持一致。 4. 如果当前模块已有特殊返回结构或聚合导出,继续保持一致。
5. 不创建页面,不改路由,除非用户明确要求。
6. 如果用户要求维护代码生成模板,优先同步当前仓库 `gen/api.ts.ftl`、`gen/types.ts.ftl`,必要时再看 boot4 后端 generator 字段定义。
## 重点关注 ## API 规则
- `Query` - 标准 import
- `VO` `import type { PageResult, R } from '@/api/types';`
- `Form` `import request from '@/api/request';`
- `InfoVO` `import type { XxxForm, XxxQuery, XxxVO } from './types';`
- `AxiosPromise<PageResult<T>>` - 分页列表:`GET /<module>/<business>/list`,返回 `R<PageResult<XxxVO>>`
- 详情接口与列表接口返回结构 - 树表列表:返回 `R<XxxVO[]>`
- 详情:`GET /<module>/<business>/{id}`,返回 `R<XxxVO>` 或业务 `InfoVO`
- 新增:`POST /<module>/<business>`,请求体用 `data`
- 修改:`PUT /<module>/<business>`,请求体用 `data`
- 删除:`DELETE /<module>/<business>/{id or ids}`。
- 状态切换:`PUT /<module>/<business>/changeStatus`,参数形态参考后端和相邻模块。
- query string 用 `params`,请求体用 `data`
- 不要从 `axios` 引入 `AxiosPromise`
## 类型规则
- 标准类型定义 `VO`、`Form`、`Query`。
- 必要时补 `InfoVO`、`TreeVO`、`ResetPwdForm`、`OptionVO` 等扩展类型。
- `Form` 通常继承 `BaseEntity`
- 非树表 `Query` 通常继承 `PageQuery`
- 树表 `Query` 通常不继承 `PageQuery`
- ID 字段通常使用 `string | number`
- 批量删除参数使用 `string | number | Array<string | number>`
- Java 数值类型映射为 `number`Boolean 映射为 `boolean`,日期和文本默认 `string`
- 日期范围查询保留 `params?: Record<string, unknown>` 或跟随相邻模块,不要无故删除。
- 列表对象、表单对象、查询对象职责分开;字段不一致时不要强行复用一个接口。
## gen 模板规则
- `gen/api.ts.ftl``gen/types.ts.ftl` 是当前 React 项目内置模板,优先于外部后端模板。
- 保持 Velocity 变量、宏和输出路径约定,不要破坏后端 generator 可替换变量。
- 输出类型应继续使用 `R`、`PageResult`、`PageQuery`、`BaseEntity` from `@/api/types``request` from `@/api/request`
- 不输出 Vue 版 `AxiosPromise`、`@/utils/request` 或 `src/views` 约定。
## 自检 ## 自检
- API 路径是否与后端一致 - API 路径是否与后端一致。
- 类型是否覆盖接口真实结构 - 类型是否覆盖接口真实结构。
- 是否不必要地把类型写宽了 - 是否不必要地把类型写宽了。
- 是否保留了当前模块的命名和导出风格。
- 是否同步维护了当前项目内置 `gen/*.ftl` 模板中对应 API/types 输出。
- 是否误用了 Vue 版 `AxiosPromise`、`@/utils/request` 或 `src/views` 约定。

View File

@ -1,22 +1,39 @@
--- ---
name: frontend-crud-coding name: frontend-crud-coding
description: 前端总入口。用于 plus-ui 前端项目中的标准 CRUD 页面、新增 API/types、复杂列表页增强、树筛选、导入导出、权限按钮与弹窗表单等任务,并根据任务类型选择合适的前端子 agent;默认基线为 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支 description: 前端总入口。用于当前 plus-ui-react 项目中的标准 React CRUD 页面、新增 API/types、复杂列表页增强、树筛选、导入导出、权限按钮、状态切换、弹窗表单和项目内置 gen/*.ftl React 代码生成模板维护任务,并根据任务类型选择合适的前端子 agent。
--- ---
你是 plus-ui 前端项目的总入口 agent。 你是当前 plus-ui-react 前端项目的总入口 agent。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端引用必须同时标记仓库、分支和文件路径;不要在 agent 文档中写本机绝对路径。
先判断任务类型,再按下面规则处理: 先判断任务类型,再按下面规则处理:
1. 如果是新增标准 CRUD 页面、补 `src/api`、`types.ts`、`index.vue`,优先使用 `frontend-crud-page.md` 1. 如果是新增标准 CRUD 页面、补 `src/api`、`types.ts`、`index.tsx`,优先使用 `frontend-crud-page.md`
2. 如果是修改已有列表页、增强导入导出、树筛选、更多菜单、状态切换,优先使用 `frontend-page-enhancement.md` 2. 如果是修改已有列表页、增强导入导出、树筛选、详情抽屉、更多操作、状态切换,优先使用 `frontend-page-enhancement.md`
3. 如果只改接口层和类型定义,优先使用 `frontend-api-types.md` 3. 如果只改接口层和类型定义,优先使用 `frontend-api-types.md`
4. 如果是维护代码生成模板,优先读取当前仓库 `gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`,再按 React 项目真实工具和页面规则修改模板。
通用要求: 通用要求:
- 先读当前目录下最近似页面和 API再动代码。 - 先读当前目录下最近似页面和 API再动代码。
- 冲突时优先相信当前项目真实页面,其次是公共组件和工具,再其次才是 plus-ui Gitee 仓库 `6.X-Vue` 分支的 `gen` 模板。 - 优先参考 `.codex/skills/frontend-crud-coding/SKILL.md``references/frontend.md`,保持 Claude agent 与 Codex skill 规则一致。
- 冲突时优先相信当前项目真实页面其次是公共组件、hooks、工具和请求封装再其次是当前仓库 `gen/*.ftl` React 模板,最后才是 Vue 参考项目和关联后端工程 generator 模板。
- 默认直接产出可落地代码,而不是只给抽象建议。 - 默认直接产出可落地代码,而不是只给抽象建议。
- 不要照搬 Vue 项目的 `src/views`、Element Plus、`AxiosPromise`、`v-hasPermi`、`ref/reactive` 写法。
当前项目关键约定:
- 页面目录是 `src/pages`
- 请求封装是 `@/api/request`
- API 外层类型是 `R<T>`,分页类型是 `PageResult<T>`,都来自 `@/api/types`
- 标准页面优先使用 `PageContainer`、`ProTable`、`ModalForm`、ProForm 组件。
- 权限通过 `useUserStore``hasPermi` 计算布尔值。
- 多选通过 `useTableSelection`,导出通过 `useTableExport`
- 行操作优先使用 `RowActions``@ant-design/icons`
- 日期范围、字典 options、树表展开、loading、搜索重置和确认弹窗优先使用 `useDateRangeQuery`、`dictOptions`、`useTreeTableExpand`、`useLoading`、`useSearchReset`、`confirmAction`/`confirmTitleSafe`。
- `gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl` 内容必须生成 React TSX 页面。
验证要求:
- 改 TS/TSX/API/types 后优先运行 `pnpm lint``pnpm exec tsc --noEmit`
- 改公共组件、hooks、构建配置或较大范围页面后运行 `pnpm build`
- 如果验证失败或无法运行,交付时说明命令和原因。

View File

@ -1,41 +1,73 @@
--- ---
name: frontend-crud-page name: frontend-crud-page
description: 前端标准 CRUD 页面专家。用于 plus-ui 前端项目中的新建列表页、弹窗表单页、标准 API/types/index.vue 骨架,以及 gen 模板到项目风格的落地任务;默认参考 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支 description: 前端标准 React CRUD 页面专家。用于当前 plus-ui-react 项目中的新建列表页、弹窗表单页、标准 API/types/index.tsx 骨架、项目内置 gen/*.ftl React 模板维护,以及后端 generator 字段到 React + ProTable 项目风格的落地任务
--- ---
你负责 plus-ui 前端项目中的标准 CRUD 页面实现。 你负责当前 plus-ui-react 项目中的标准 CRUD 页面实现。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端模板引用必须同时标记仓库、分支和路径,例如 `branch=6.X-Vue, path=gen/index.vue.ftl`;不要写本机绝对路径。
## 核心原则 ## 核心原则
1. 先看当前模块最近似页面。 1. 先看当前模块最近似页面。
2. 再参考 plus-ui Gitee 仓库 `6.X-Vue` 分支中的 `gen` 模板。 2. 再参考 `.codex/skills/frontend-crud-coding/references/frontend.md`
3. 默认同时维护: 3. 维护或新增生成能力时,优先参考当前仓库 `gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`。
4. 必要时参考关联后端工程 generator 模板确认接口、字段、权限和导出能力。
5. 默认同时维护:
`src/api/<module>/<business>/index.ts` `src/api/<module>/<business>/index.ts`
`src/api/<module>/<business>/types.ts` `src/api/<module>/<business>/types.ts`
`src/views/<module>/<business>/index.vue` `src/pages/<module>/<business>/index.tsx`
## 优先参考
- 标准单表:`src/pages/demo/demo/index.tsx`、`src/api/demo/demo/*`。
- 树表:`src/pages/demo/tree/index.tsx`、`src/pages/workflow/category/index.tsx`。
- 系统管理:`src/pages/system/user/index.tsx`、`system/role`、`system/post`、`system/config`。
- workflow`src/pages/workflow/*`、`src/api/workflow/*`。
## 页面规则 ## 页面规则
- 页面优先使用 `<script setup name="Xxx" lang="ts">` - 页面默认导出函数组件,命名如 `SystemClientPage`、`DemoDemoPage`、`WorkflowCategoryPage`。
- 标准结构通常包含: - 标准结构通常包含 `PageContainer`、`ProTable`、工具栏按钮、`ModalForm`。
搜索区、表格区、工具栏、分页、编辑弹窗
- 常见状态: - 常见状态:
`loading`、`showSearch`、`ids`、`single`、`multiple`、`total` `actionRef`、`form`、`modalOpen`、`modalTitle`、`ids`、`selectedOne`、权限布尔值。
- 查询与表单优先使用 `reactive<PageData<Form, Query>>({...})` - 表格列使用 `ProColumns<XxxVO>[]`
- 分页列表的 `request` 中使用 `toPageQuery(params)`、`updateExportParams(query)`、`listXxx(query)`、`toTableData(res)`。
- 多选使用 `useTableSelection<XxxVO>(row => row.id)`
- 导出使用 `useTableExport()``exportFile('/module/business/export', () => 'business_timestamp.xlsx')`
- 行操作使用 `RowActions`,行内删除确认放在 `confirm`
- 新增/编辑弹窗使用 `ModalForm<XxxForm>`,字段优先使用 ProForm 组件。
- 日期范围使用 `useDateRangeQuery`,字典 options 使用 `dictOptions`
- 状态切换等 Promise 式确认使用 `confirmAction``confirmTitleSafe`
- 异步 loading 优先使用 `useLoading`,带额外筛选状态的重置优先使用 `useSearchReset`
## API / types 规则 ## API / types 规则
- 请求统一通过 `@/utils/request` - 请求统一通过 `@/api/request`
- 同目录维护 `index.ts``types.ts` - 同目录维护 `index.ts``types.ts`
- 标准 CRUD 通常包含:列表、详情、新增、修改、删除 - 标准 CRUD 通常包含:列表、详情、新增、修改、删除。
- 列表接口通常返回 `AxiosPromise<PageResult<XxxVO>>` - 列表分页接口通常返回 `request<R<PageResult<XxxVO>>>`
- 树表列表接口通常返回 `request<R<XxxVO[]>>`
- 不要从 `axios` 引入 `AxiosPromise`
## 树表规则
- 判断树表后不要生成分页 `PageResult` 页面。
- `Query` 通常不继承 `PageQuery`
- 使用 `handleTree`、`pagination={false}`、`useTreeTableExpand`、`ProFormTreeSelect`。
- 树表展开/折叠优先使用 `useTreeTableExpand`,不要在页面或模板里重复手写收集 key 的状态逻辑。
- 新增子节点时从当前行带入 `parentId`
## gen 模板规则
- `gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl` 内容生成 React TSX。
- 保持 Velocity 变量、宏和后端 generator 可识别结构,不为了格式美化破坏模板语法。
- 模板应输出当前项目公共工具写法:`useTableSelection`、`useTableExport`、`useDateRangeQuery`、`dictOptions`、`useTreeTableExpand`、`confirmAction`、`toPageQuery`、`toTableData`、`handleTree`。
- 不把 Vue `src/views`、Element Plus、`AxiosPromise`、`v-hasPermi` 写入模板。
## 自检 ## 自检
- API 路径是否与后端一致 - API 路径是否与后端一致。
- `index.ts``types.ts` 是否同步补齐 - `index.ts``types.ts` 是否同步补齐。
- 页面是否只是模板裸输出,如果是要继续补强到当前项目风格 - 页面是否使用 React 项目的 ProTable/ModalForm 骨架。
- 权限标识是否与后端和相邻页面一致。
- 是否同步使用当前项目公共 hooks/工具,避免生成重复代码。
- 是否误用了 Vue/Element Plus/`src/views` 规则。

View File

@ -1,31 +1,58 @@
--- ---
name: frontend-page-enhancement name: frontend-page-enhancement
description: 复杂前端页面增强专家。用于修改 plus-ui 前端项目中已经存在的列表页、树筛选页、带导入导出和更多菜单的页面,强调增量修改和保留现有交互能力;默认基线为 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支 description: 复杂前端页面增强专家。用于修改当前 plus-ui-react 项目中已经存在的列表页、树筛选页、带导入导出、详情抽屉、更多操作和状态切换的页面,强调增量修改和保留现有交互能力。
--- ---
你负责 plus-ui 前端项目中已有页面的增强,不是重写页面。 你负责当前 plus-ui-react 项目中已有页面的增强,不是重写页面。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端引用必须同时标记仓库、分支和文件路径;不要写本机绝对路径。
## 核心原则 ## 核心原则
1. 优先阅读当前页面完整实现。 1. 优先阅读当前页面完整实现和相关子组件。
2. 增量修改,不重写整页。 2. 增量修改,不重写整页。
3. 保留已有树筛选、导入导出、列显隐、更多菜单、状态切换、路由跳转、SCSS 页面壳。 3. 保留已有树筛选、导入导出、列显隐、详情抽屉、更多操作、状态切换、路由跳转、权限控制和样式壳。
4. 不要把复杂页面退化成 generator 式基础列表页。 4. 不要把复杂页面退化成 demo/generator 式基础列表页。
5. 修改前先看 `.codex/skills/frontend-crud-coding/references/frontend.md` 中对应规则。
## 常见任务 ## 常见任务
- 调整工具栏和更多菜单 - 调整工具栏按钮和行操作。
- 增加筛选条件和日期范围 - 增加筛选条件和日期范围。
- 增加导入导出能力 - 增加导入、导出能力。
- 增加状态切换、快捷操作、确认弹窗 - 增加状态切换、快捷操作、确认弹窗。
- 补复杂页面的小型子功能 - 增加详情抽屉或增强现有抽屉。
- 补复杂页面的小型子功能。
- 接入新 API 并保持现有页面状态流。
## React 项目约定
- 表格页通常使用 `PageContainer` + `ProTable`
- 复杂系统页可包含 `TreePanel`、子组件弹窗、抽屉、授权路由跳转。
- 权限通过 `useUserStore` + `hasPermi` 计算,不使用 Vue 指令。
- 字典通过 `useDict` 加载,按现有页面映射为 Ant Design options。
- 字典 options 优先使用 `dictOptions` from `@/utils/dict`
- 导出通过 `useTableExport`,不要另写下载封装。
- 多选通过 `useTableSelection`,不要手写一套重复状态。
- 日期范围优先使用 `useDateRangeQuery`,底层保持 `addDateRange` 参数格式。
- 树表展开优先使用 `useTreeTableExpand`
- 异步 loading 优先使用 `useLoading`
- 额外筛选状态的重置优先使用 `useSearchReset`
- Promise 式确认框优先使用 `confirmAction``confirmTitleSafe`
- 状态切换失败时回滚或刷新,参考 `system/user`
## 增量修改规则
- 如果页面已有子组件,如 `UserFormModal`、`UserImportModal`、`UserDetailDrawer`,优先修改或复用子组件,不把所有逻辑堆回主页面。
- 如果页面已有权限布尔值,沿用同一命名风格补新权限。
- 如果页面已有导出参数缓存,继续通过 `updateExportParams` 更新。
- 如果页面已有树筛选状态,查询参数要合并该状态,不要覆盖掉。
- 如果页面已有特殊禁用规则、超级管理员保护或业务状态判断,必须保留。
- 如果增强的是标准 CRUD 能力或公共工具约定,要评估当前仓库 `gen/*.ftl` 是否也应同步更新。
## 自检 ## 自检
- 是否破坏了原页面结构和样式 - 是否破坏了原页面结构和样式。
- 是否误删了已有权限控制或交互能力 - 是否误删了已有权限控制或交互能力。
- 是否应该拆成子组件而不是继续堆主页面 - 是否保留了树筛选、导入导出、抽屉、路由跳转等复杂能力。
- 是否应该拆成子组件而不是继续堆主页面。
- 是否使用了当前项目已有工具,避免重复手写日期范围、字典 options、树展开、loading、确认框。
- 是否误用了 Vue/Element Plus/generator 的简化逻辑。

View File

@ -1,41 +1,31 @@
--- ---
name: frontend-crud-coding name: frontend-crud-coding
description: 在 plus-ui 前端项目中按真实 Vue 3 + TypeScript + Element Plus + oxlint/oxfmt 代码风格生成或修改页面、API、types、hooks 接入和样式壳。用于新增或修改标准 CRUD 列表页、树表页、系统管理页、监控页、workflow 页面、demo 页面,补齐与后端接口对应的 src/api、types 和 src/views 代码;默认参考 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支,触发后应先读取适用 references再阅读目标模块真实代码和项目内 gen 代码生成模板。 description: 在当前 plus-ui-react 前端项目中按真实 React + TypeScript + Umi Max + Ant Design ProComponents + oxlint/oxfmt 代码风格生成或修改页面、API、types、hooks 接入、样式和项目内置 gen/*.ftl 代码生成模板。用于新增或修改标准 CRUD 列表页、树表页、系统管理页、监控页、workflow 页面、demo 页面,补齐与 RuoYi-Vue-Plus boot4 后端接口对应的 src/api、types 和 src/pages 代码,或维护 gen/api.ts.ftl、gen/types.ts.ftl、gen/index.tsx.ftl、gen/index-tree.tsx.ftl触发后应先读取适用 references再阅读目标模块真实代码和项目内 gen 模板。
--- ---
# 前端编码规范 # 前端编码规范
先对齐当前前端项目里的真实实现,再参考项目内 `gen` 目录下的代码生成模板。不要只套通用 Vue 模板,也不要把 generator 模板原样复制进来而忽略当前项目已经演进出的 hooks、页面壳、类型入口和下载方式。 先对齐当前 React 项目里的真实实现,再参考项目内置 `gen/` 代码生成模板。Vue 版本 skill 和关联后端工程的代码生成器模板只用于理解字段、权限、任务分型和迁移意图。不要直接套 Vue 3、Element Plus、AxiosPromise 或 `src/views` 规则;要落成当前仓库的 `src/pages`、`src/api`、ProTable、ModalForm、权限、导出和公共工具方式。
## 项目基线
- 基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
- 默认分支:`6.X-Vue`
- 远端引用必须同时标记仓库、分支和文件路径,例如 `https://gitee.com/JavaLionLi/plus-ui/blob/6.X-Vue/gen/index.vue.ftl``branch=6.X-Vue, path=gen/index.vue.ftl`
- 不要在 skill、reference 或 agent 文档中写本机绝对路径;本地文件读取以运行时工作目录为准。
- 如果用户指定其他分支,先按用户分支读取同一仓库的对应文件,并在结果中说明使用的分支。
## 执行流程 ## 执行流程
1. 判断任务类型:新增标准 CRUD、树表、已有页面增强、复杂业务页、只补 API/types。 1. 判断任务类型:新增标准 CRUD、树表、已有页面增强、复杂业务页、只补 API/types。
2. 按“文档读取规则”读取必要 reference不一次性展开所有资料。 2. 按“文档读取规则”读取必要 reference不一次性展开所有资料。
3. 阅读目标目录下最近似的真实代码: 3. 阅读目标目录下最近似的真实代码:
- 标准单表优先看 `src/views/demo/demo/index.vue`、`src/api/demo/demo/*`。 - 标准单表优先看 `src/pages/demo/demo/index.tsx`、`src/api/demo/demo/*`。
- 树表优先看 `src/views/demo/tree/index.vue`、`src/views/workflow/category/index.vue`。 - 树表优先看 `src/pages/demo/tree/index.tsx`、`src/pages/workflow/category/index.tsx`。
- 系统复杂页优先看 `src/views/system/user/index.vue`、`system/role`、`system/post`、`system/config`。 - 系统复杂页优先看 `src/pages/system/user/index.tsx`、`system/role`、`system/post`、`system/config`。
- workflow 业务页优先看 `src/views/workflow/*` 同类页面。 - workflow 业务页优先看 `src/pages/workflow/*``src/api/workflow/*` 同类页面。
4. 新增标准页面前,对照项目内模板确认基础骨架: 4. 新增或维护代码生成能力时,优先阅读当前仓库 `gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`。这些是 React 版内置 FreeMarker 生成模板,页面模板必须输出当前项目 React/TSX 风格。
- API 模板:`gen/api.ts.ftl` 5. 需要从 Vue 版本迁移经验时,参考 Gitee 项目 `https://gitee.com/JavaLionLi/plus-ui``6.X-Vue` 分支,目录为 `.codex/skills/frontend-crud-coding``.claude/agents`,只吸收任务分型、增量修改和自检原则。
- types 模板:`gen/types.ts.ftl` 6. 新增标准页面前,可以对照 Gitee 项目 `https://gitee.com/dromara/RuoYi-Vue-Plus``6.X` 分支,目录为 `ruoyi-modules/ruoyi-gen/src/main/resources/fm`,确认字段、权限和导出能力,但输出和 `gen/` 模板必须保持 React 项目风格。
- 标准单表页模板:`gen/index.vue.ftl` 7. 新增代码时通常同步维护 `src/api/<module>/<business>/index.ts`、`types.ts`、`src/pages/<module>/<business>/index.tsx`;如果是生成模板优化,同步维护 `gen/` 对应 `.ftl`
- 树表页模板:`gen/index-tree.vue.ftl` 8. 增强已有页面时只做增量修改,保留原页面的树筛选、导入导出、列显隐、权限、字典、弹窗、抽屉和路由跳转能力。
5. 新增代码时通常同步维护 `src/api/<module>/<business>/index.ts`、`types.ts`、`src/views/<module>/<business>/index.vue`。 9. 修改完成后按影响范围运行验证:优先 `pnpm exec tsc --noEmit``pnpm lint`;大范围页面、公共组件、构建配置变更再跑 `pnpm build`
6. 增强已有页面时只做增量修改,保留原页面的树筛选、导入导出、列显隐、权限、字典、弹窗和路由跳转能力。
7. 修改完成后按影响范围运行验证:优先 `pnpm exec vue-tsc --noEmit`,改动页面或导入时再跑 `pnpm lint`,大范围变更再跑 `pnpm build`
## 文档读取规则 ## 文档读取规则
- 前端 API、types、页面、hooks、样式和验证规则先读 [references/frontend.md](references/frontend.md)。 - 前端 API、types、页面、hooks、样式、权限、导出和验证规则,先读 [references/frontend.md](references/frontend.md)。
- 不确定任务边界、需要标准用例或提问方式时,再读 [references/examples.md](references/examples.md)。 - 不确定任务边界、需要标准用例或提问方式时,再读 [references/examples.md](references/examples.md)。
- reference 只约束实现方式和自检范围;发生冲突时,以当前模块真实代码和实际调用点为准。 - reference 只约束实现方式和自检范围;发生冲突时,以当前模块真实代码和实际调用点为准。
@ -44,41 +34,46 @@ description: 在 plus-ui 前端项目中按真实 Vue 3 + TypeScript + Element P
发生冲突时按下面顺序决策: 发生冲突时按下面顺序决策:
1. 目标目录下最近似页面、API、types 的真实实现。 1. 目标目录下最近似页面、API、types 的真实实现。
2. 当前项目公共 hooks、组件、工具和样式约定。 2. 当前项目公共 hooks、组件、工具、样式和请求封装约定。
3. 项目内 `gen` 代码生成模板。 3. 当前项目内置 `gen/*.ftl` React 代码生成模板。
4. 通用 Vue 3 / Element Plus 习惯。 4. Gitee 项目 `plus-ui``6.X-Vue` 分支 `.codex` / `.claude` 中的任务分型和工作流。
5. Gitee 项目 `RuoYi-Vue-Plus``6.X` 分支 generator 模板。
6. 通用 React / Ant Design ProComponents 习惯。
也就是说: 也就是说:
- 同模块已有页面怎么写,优先怎么写。 - 同模块已有页面怎么写,优先怎么写。
- 没有现成页面时,使用项目内 `gen` 模板作为骨架,再改成当前项目风格 - 没有现成页面时,使用 demo 页面作为骨架,再按后端接口字段和权限补齐
- 复杂模块不能为了“标准 CRUD”退化成裸模板页。 - 复杂模块不能为了“标准 CRUD”退化成裸模板页。
- Vue 版本只作为迁移参考,不复制 Vue 组件、hooks、指令、样式类和类型导入。
## 仓库通用规则 ## 仓库通用规则
- 遵循 [`.editorconfig`](../../../.editorconfig)UTF-8、LF、2 空格缩进Markdown 例外。 - 遵循 `.editorconfig` 和仓库现状UTF-8、2 空格缩进、TypeScript、TSX。
- 当前仓库没有 `.prettierrc`,格式脚本是 `pnpm run fmt` 调用 `oxfmt .`lint 脚本是 `pnpm lint` 调用 `oxlint src` - 包管理使用 `pnpm`;格式脚本是 `pnpm run fmt`lint 脚本是 `pnpm lint`
- 页面优先使用 `<script setup name="Xxx" lang="ts">` - 技术栈是 React 19 + TypeScript + Umi Max + Ant Design 6 + Ant Design ProComponents + ahooks + TanStack Query + Zustand。
- API 返回类型优先从 `@/utils/api-types` 引入 `AxiosPromise`,分页结果从 `@/api/types` 引入 `PageResult` - 页面放在 `src/pages`,不是 Vue 项目的 `src/views`
- 请求统一通过 `@/utils/request`,导出下载使用 `import { download as requestDownload } from '@/utils/request';` - 请求统一通过 `src/api/request.ts`API 返回类型使用 `Promise<R<T>>`,不要从 `axios` 或 Vue 项目引入 `AxiosPromise`
- 标准列表页优先复用 `useLoading`、`useSearchToggle`、`useSearchReset`、`useTableSelection`、`useFormDialog`、`useDateRangeQuery`。 - 分页结果使用 `PageResult<T>` from `@/api/types`,接口外层使用 `R<T>` from `@/api/types`
- 页面壳优先使用 `p-2 app-container <module>-<business>-page`、`search-panel`、`toolbar-shell`、`data-table`、`right-toolbar`、`pagination`。 - 标准列表页优先复用 `ProTable`、`ModalForm`、`RowActions`、`useTableSelection`、`useTableExport`、`toPageQuery`、`toTableData`。
- 新页面不要无故引入另一套状态管理、请求封装、样式体系或权限写法。 - 查询、字典、树表、loading、重置和确认弹窗优先复用 `useDateRangeQuery`、`dictOptions`、`useTreeTableExpand`、`useLoading`、`useSearchReset`、`confirmAction`/`confirmTitleSafe`。
- 权限用 `const userInfo = useUserStore(state => state.userInfo)``hasPermi(userInfo, ['module:business:action'])` 计算布尔值。
- 新页面不要无故引入另一套状态管理、请求封装、表格封装、样式体系或权限写法。
## 目录映射规则 ## 目录映射规则
通常按下面关系组织代码: 通常按下面关系组织代码:
- 后端 `/system/user/*` 对应 `src/api/system/user/*``src/views/system/user/*` - 后端 `/system/user/*` 对应 `src/api/system/user/*``src/pages/system/user/*`
- 后端 `/monitor/xxx/*` 对应 `src/api/monitor/xxx/*``src/views/monitor/xxx/*` - 后端 `/monitor/xxx/*` 对应 `src/api/monitor/xxx/*``src/pages/monitor/xxx/*`
- 后端 `/workflow/xxx/*` 对应 `src/api/workflow/xxx/*``src/views/workflow/xxx/*` - 后端 `/workflow/xxx/*` 对应 `src/api/workflow/xxx/*``src/pages/workflow/xxx/*`
- 后端 `/demo/xxx/*` 对应 `src/api/demo/xxx/*``src/views/demo/xxx/*` - 后端 `/demo/xxx/*` 对应 `src/api/demo/xxx/*``src/pages/demo/xxx/*`
标准新增通常至少包含: 标准新增通常至少包含:
- `src/api/<module>/<business>/index.ts` - `src/api/<module>/<business>/index.ts`
- `src/api/<module>/<business>/types.ts` - `src/api/<module>/<business>/types.ts`
- `src/views/<module>/<business>/index.vue` - `src/pages/<module>/<business>/index.tsx`
按业务复杂度,可能继续补: 按业务复杂度,可能继续补:
@ -87,17 +82,17 @@ description: 在 plus-ui 前端项目中按真实 Vue 3 + TypeScript + Element P
- 树筛选面板 - 树筛选面板
- 列显隐配置 - 列显隐配置
- 分配/授权子页面 - 分配/授权子页面
- 自定义 SCSS 样式 - 自定义 Less 样式
## 任务分型 ## 任务分型
### 1. 标准单表 CRUD ### 1. 标准单表 CRUD
`gen/index.vue.ftl`、`gen/api.ts.ftl`、`gen/types.ts.ftl` 和 `src/views/demo/demo/index.vue` 为主要起点,补齐列表、搜索、分页、新增、编辑、删除、导出、权限、类型和验证。 `src/pages/demo/demo/index.tsx` 和 `src/api/demo/demo/*` 为主要起点,补齐列表、搜索、分页、新增、编辑、删除、导出、权限、类型和验证。
### 2. 树表 CRUD ### 2. 树表 CRUD
`src/views/demo/tree/index.vue`、`src/views/workflow/category/index.vue` 为主要起点。列表接口通常返回数组而不是 `PageResult`,页面使用 `handleTree`、`useTreeTableExpand``Query` 通常不继承 `PageQuery` `src/pages/demo/tree/index.tsx`、`src/pages/workflow/category/index.tsx` 为主要起点。列表接口通常返回数组,不使用分页 `PageResult`;页面使用 `handleTree`、`useTreeTableExpand`、`ProFormTreeSelect`
### 3. 强业务页面 ### 3. 强业务页面
@ -105,12 +100,23 @@ description: 在 plus-ui 前端项目中按真实 Vue 3 + TypeScript + Element P
### 4. 工作流页面 ### 4. 工作流页面
workflow 目录优先参考 `src/views/workflow/*`。流程定义、流程实例、任务列表、请假申请等页面通常有业务按钮、弹窗和路由跳转,不要硬套 system 模块。 workflow 目录优先参考 `src/pages/workflow/*`。流程定义、流程实例、任务列表、请假申请等页面通常有业务按钮、弹窗和路由跳转,不要硬套 system 模块。
### 5. 只补 API 和 types ### 5. 只补 API 和 types
只维护 `src/api/<module>/<business>/index.ts``types.ts`,但仍要与后端路由、返回结构、当前模块导入方式和类型入口一致。 只维护 `src/api/<module>/<business>/index.ts``types.ts`,但仍要与后端路由、返回结构、当前模块导入方式和类型入口一致。
### 6. 维护 React 生成模板
模板只放在当前仓库 `gen/` 下,除非用户明确要求写入 boot4 后端工程。维护时保持 FreeMarker 变量和文件名不变:
- `gen/api.ts.ftl`
- `gen/types.ts.ftl`
- `gen/index.tsx.ftl`
- `gen/index-tree.tsx.ftl`
模板内容必须生成当前 React 项目代码,并优先使用已有公共工具:`useDateRangeQuery`、`dictOptions`、`useTreeTableExpand`、`useTableSelection`、`useTableExport`、`confirmAction`、`toPageQuery`、`toTableData`、`handleTree`、`formatDateTimeFields`、`toDayjsFields`。不要把 Vue 模板、Element Plus 组件或 `src/views` 路径写入这些模板。
## 输出要求 ## 输出要求
使用本 skill 时,默认期望产出应满足: 使用本 skill 时,默认期望产出应满足:
@ -119,21 +125,26 @@ workflow 目录优先参考 `src/views/workflow/*`。流程定义、流程实例
- API 路径、函数名、权限标识与后端接口保持一致。 - API 路径、函数名、权限标识与后端接口保持一致。
- 标准页查询、重置、分页、弹窗、提交、删除、导出流程闭环完整。 - 标准页查询、重置、分页、弹窗、提交、删除、导出流程闭环完整。
- 复杂页面保留原有交互能力和业务约束。 - 复杂页面保留原有交互能力和业务约束。
- 代码体现当前项目 hooks、页面壳和下载方式而不是 `gen` 模板裸输出。 - 代码体现当前项目 hooks、页面壳和下载方式而不是 Vue 版本或 generator 裸输出。
- 生成模板变更要同步反映当前项目公共 hooks/工具,避免生成的新页面重复手写日期范围、字典 options、树展开、loading、搜索重置和确认框逻辑。
- 交付前说明运行过的验证命令;如果无法验证,说明原因。 - 交付前说明运行过的验证命令;如果无法验证,说明原因。
## 快速检查清单 ## 快速检查清单
- `AxiosPromise` 是否来自 `@/utils/api-types`。 - API 是否从 `@/api/request` 引入 `request`。
- `PageResult` 是否来自 `@/api/types` - `R`、`PageResult`、`PageQuery`、`BaseEntity` 是否来自 `@/api/types`
- API `params``data` 是否与后端方法一致。 - API `params``data` 是否与后端方法一致。
- 日期范围是否通过 `useDateRangeQuery` 或附近页面现有方式处理。 - 分页表格 request 是否通过 `toPageQuery(params)``toTableData(res)`
- 列表 loading 是否通过 `useLoading` 或原页面方式维护。 - 导出是否通过 `useTableExport``exportFile('/module/business/export', () => 'name_timestamp.xlsx')`
- 弹窗是否通过 `useFormDialog` 或原页面方式维护。 - 多选是否通过 `useTableSelection<T>(row => row.id)`
- 多选状态是否通过 `useTableSelection` 或原页面方式维护。 - 行操作是否优先使用 `RowActions` 和 Ant Design 图标。
- 权限指令是否保持同文件一致,默认使用当前项目主流 `v-hasPermi` - 权限是否通过 `useUserStore` + `hasPermi` 计算,不要写 Vue 指令。
- 导出是否使用 `requestDownload('<module>/<business>/export', { ...queryParams.value }, '<name>_<time>.xlsx')` - 日期范围是否通过 `useDateRangeQuery``addDateRange` 相关工具处理。
- 页面壳是否保留 `search-panel`、`table-panel`、`toolbar-shell`、`data-table`、`right-toolbar`、`pagination`。 - 字典 options 是否通过 `dictOptions`
- 树表是否使用 `handleTree`、`pagination={false}`、`useTreeTableExpand`、`ProFormTreeSelect`。
- loading 是否优先使用 `useLoading`,搜索重置是否优先使用 `useSearchReset`
- 需要 Promise 式确认时是否使用 `confirmAction``confirmTitleSafe`
- 代码生成模板是否仍输出 React TSX 项目风格。
## 推荐提问方式 ## 推荐提问方式
@ -147,5 +158,5 @@ workflow 目录优先参考 `src/views/workflow/*`。流程定义、流程实例
例如: 例如:
- 使用 `$frontend-crud-coding``/system/client` 补一套标准 CRUD 页面,参考 `gen` 模板、`demo/demo` 和 `system/client` - 使用 `$frontend-crud-coding``/system/client` 补一套 React 标准 CRUD 页面,参考 `demo/demo`、现有 `system/client` 和 boot4 generator 模板
- 使用 `$frontend-crud-coding` 修改 `workflow/category` 列表页,增加导出按钮和状态筛选,保持当前 workflow 风格。 - 使用 `$frontend-crud-coding` 修改 `workflow/category` 列表页,增加导出按钮和状态筛选,保持当前 workflow 树表风格。

View File

@ -1,7 +1,7 @@
interface: interface:
display_name: "前端编码" display_name: "前端编码"
short_description: "按 plus-ui 6.X-Vue 真实代码和 gen 模板编写前端 CRUD" short_description: "按 plus-ui-react 真实代码和 gen 模板编写 React CRUD"
default_prompt: "使用 $frontend-crud-coding 先读取适用 reference再按 plus-ui Gitee 仓库 6.X-Vue 分支的真实页面、hooks、API/types 约定和项目内 gen 模板实现前端修改。" default_prompt: "使用 $frontend-crud-coding 先读取适用 reference再按 plus-ui-react 真实页面、项目内置 gen 模板、API/types、ProTable、ModalForm、权限、导出和公共工具约定实现前端修改。"
policy: policy:
allow_implicit_invocation: true allow_implicit_invocation: true

View File

@ -5,18 +5,19 @@
### 用户提问示例 ### 用户提问示例
```text ```text
使用 $frontend-crud-coding 为 system/client 补一套前端 CRUD 页面。 使用 $frontend-crud-coding 为 system/client 补一套 React 前端 CRUD 页面。
后端接口已经有 /system/client/list、/system/client/{id}、POST /system/client、PUT /system/client、DELETE /system/client/{ids}。 后端接口已经有 /system/client/list、/system/client/{id}、POST /system/client、PUT /system/client、DELETE /system/client/{ids}。
请参考 plus-ui Gitee 仓库 6.X-Vue 分支的项目内 gen 模板、src/views/demo/demo/index.vue 和现有 system/client 风格实现。 请参考 src/pages/demo/demo/index.tsx 和现有 system/client 风格实现。
``` ```
### 期望执行方式 ### 期望执行方式
- 先看 `src/api/system/client/*``src/views/system/client/index.vue` 是否已存在。 - 先看 `src/api/system/client/*``src/pages/system/client/index.tsx` 是否已存在。
- 再看 `src/views/demo/demo/index.vue` 的标准 hooks 版 CRUD 骨架。 - 再看 `src/pages/demo/demo/index.tsx` 的标准 React CRUD 骨架。
- 对照项目内 `gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.vue.ftl`。 - 新增标准生成能力时优先对照当前仓库 `gen/index.tsx.ftl`、`gen/api.ts.ftl`、`gen/types.ts.ftl`boot4 generator 只用于核对字段、权限和接口。
- 生成或修改 `api/index.ts`、`types.ts`、`views/.../index.vue`。 - 生成或修改 `api/index.ts`、`types.ts`、`pages/.../index.tsx`。
- 使用 `AxiosPromise` from `@/utils/api-types`、`PageResult` from `@/api/types`、`useLoading`、`useFormDialog`、`useSearchReset`、`useTableSelection`。 - 使用 `request<R<PageResult<T>>>`、`ProTable`、`ModalForm`、`RowActions`、`useTableSelection`、`useTableExport`。
- 日期范围、字典 options、确认框、loading 等优先使用 `useDateRangeQuery`、`dictOptions`、`confirmAction`、`useLoading` 等项目工具。
## 案例 2新增树表页面 ## 案例 2新增树表页面
@ -24,17 +25,33 @@
```text ```text
使用 $frontend-crud-coding 为 demo/tree2 新增树表 CRUD接口返回数组字段包含 id、parentId、name、orderNum。 使用 $frontend-crud-coding 为 demo/tree2 新增树表 CRUD接口返回数组字段包含 id、parentId、name、orderNum。
参考 src/views/demo/tree/index.vue 和 workflow/category。 参考 src/pages/demo/tree/index.tsx 和 workflow/category。
``` ```
### 期望执行方式 ### 期望执行方式
- 优先判断这是树表,不生成分页 `PageResult` 页面。 - 判断这是树表,不生成分页 `PageResult` 页面。
- API 列表返回 `AxiosPromise<Tree2VO[]>`。 - API 列表返回 `R<Tree2VO[]>`。
- `Query` 不继承 `PageQuery` - `Query` 不继承 `PageQuery`
- 页面使用 `handleTree`、`row-key`、`tree-props`、`useTreeTableExpand`、`el-tree-select`。 - 页面使用 `handleTree`、`pagination={false}`、`useTreeTableExpand`、`ProFormTreeSelect`。
- 新增子节点时从当前行带入 `parentId` - 新增子节点时从当前行带入 `parentId`
## 案例 2.1:优化 React 代码生成模板
### 用户提问示例
```text
使用 $frontend-crud-coding 优化当前项目 gen 目录下的 React 代码生成模板,让生成页面继续使用新加的工具。
```
### 期望执行方式
- 读取 `gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`。
- 保持 FreeMarker 变量、宏和文件名不变。
- 普通表模板继续使用 `useTableSelection`、`useTableExport`、`useDateRangeQuery`、`dictOptions`、`confirmAction`。
- 树表模板继续使用 `handleTree`、`filterTree`、`useTreeTableExpand`、`dictOptions`。
- 输出仍是 React TSX 项目代码,不写 Vue `src/views`、Element Plus 或 `AxiosPromise`
## 案例 3修改已有复杂列表页 ## 案例 3修改已有复杂列表页
### 用户提问示例 ### 用户提问示例
@ -42,15 +59,15 @@
```text ```text
使用 $frontend-crud-coding 修改 system/user 页面: 使用 $frontend-crud-coding 修改 system/user 页面:
1. 新增一个创建时间快捷筛选 1. 新增一个创建时间快捷筛选
2. 导出按钮保留在更多菜单 2. 导出按钮保留在工具栏
3. 保持现有树筛选、导入、列显隐、详情抽屉和角色分配不变 3. 保持现有树筛选、导入、详情抽屉和角色分配不变
``` ```
### 期望执行方式 ### 期望执行方式
- 判断这是“已有复杂页面增强”,不是重新生成 CRUD。 - 判断这是“已有复杂页面增强”,不是重新生成 CRUD。
- 优先阅读 `src/views/system/user/index.vue` - 优先阅读 `src/pages/system/user/index.tsx` 和相关子组件
- 保留 `TreePanel`、导入弹窗、`right-toolbar` 列显隐、`UserViewDrawer`、角色分配路由、权限控制。 - 保留 `TreePanel`、导入弹窗、`UserDetailDrawer`、角色分配路由、权限控制。
- 只增量修改搜索和查询参数处理。 - 只增量修改搜索和查询参数处理。
## 案例 4修改 workflow 页面 ## 案例 4修改 workflow 页面
@ -63,8 +80,8 @@
### 期望执行方式 ### 期望执行方式
- 优先看 `src/views/workflow/category/index.vue` 和 `src/api/workflow/category/*` - 优先看 `src/pages/workflow/category/index.tsx` 和 `src/api/workflow/category/*`
- 判断是否需要后端新增导出接口;前端导出路径保持 `workflow/category/export`。 - 判断是否需要后端新增导出接口;前端导出路径保持 `/workflow/category/export`。
- 不迁移 system/user 的用户专属逻辑。 - 不迁移 system/user 的用户专属逻辑。
- 保留树表、`useTreeTableExpand`、`handleTree` 和分类弹窗逻辑。 - 保留树表、`useTreeTableExpand`、`handleTree` 和分类弹窗逻辑。
@ -79,8 +96,8 @@
### 期望执行方式 ### 期望执行方式
- 只维护 `src/api/monitor/cache/index.ts``src/api/monitor/cache/types.ts` - 只维护 `src/api/monitor/cache/index.ts``src/api/monitor/cache/types.ts`
- 仍然检查同目录 monitor API 的 `export function` / `export const` 风格。 - 仍然检查同目录 monitor API 的命名、返回结构和导出风格。
- 返回类型使用 `AxiosPromise` from `@/utils/api-types`。 - 返回类型使用 `R<T>` 和 `PageResult<T>` from `@/api/types`。
- 不创建页面,不改路由。 - 不创建页面,不改路由。
## 案例 6接入后端新增状态切换接口 ## 案例 6接入后端新增状态切换接口
@ -88,26 +105,26 @@
### 用户提问示例 ### 用户提问示例
```text ```text
使用 $frontend-crud-coding 给 system/client 页面接入 PUT /system/client/changeStatus状态字段 status参考 gen 模板 使用 $frontend-crud-coding 给 system/client 页面接入 PUT /system/client/changeStatus状态字段 status参考 system/user
``` ```
### 期望执行方式 ### 期望执行方式
- API 增加 `changeClientStatus(id, status)` - API 增加 `changeClientStatus(id, status)`
- types 确认 `status` 类型是 string、number 还是 boolean。 - types 确认 `status` 类型是 string、number 还是 boolean。
- 表格列用 `el-switch`active/inactive 值跟后端字段类型一致。 - 表格列用 Ant Design `Switch`checked 值跟后端字段类型一致。
- 切换失败时回滚原状态。 - 切换失败时回滚原状态或刷新列表
- 权限使用 `system:client:edit` 或后端实际权限。 - 权限使用 `system:client:edit` 或后端实际权限。
## 推荐的高质量任务描述 ## 推荐的高质量任务描述
```text ```text
使用 $frontend-crud-coding 在当前前端项目中`/system/notice` 列表页增强 使用 $frontend-crud-coding 在当前 React 前端项目中增 `/system/notice` 列表页:
1. 保留现有页面 1. 保留现有页面
2. 新增状态筛选和导出 2. 新增状态筛选和导出
3. API 路径沿用后端接口 3. API 路径沿用后端接口
4. 参考 system/config 的工具栏与导出交互 4. 参考 system/config 的工具栏与导出交互
5. 参考 plus-ui Gitee 仓库 6.X-Vue 分支的 gen 模板补齐缺失 types 5. 参考当前项目 gen 模板和 boot4 generator 字段补齐缺失 types
``` ```
## 不推荐的任务描述 ## 不推荐的任务描述

View File

@ -2,41 +2,36 @@
## 优先参考的代码来源 ## 优先参考的代码来源
- 默认远端基线:`https://gitee.com/JavaLionLi/plus-ui`,分支 `6.X-Vue`。引用远端文件时必须同时写明 `branch=6.X-Vue` 和仓库内路径。
- 当前目标目录下最近似页面、API、types。 - 当前目标目录下最近似页面、API、types。
- 标准单表:`src/views/demo/demo/index.vue`、`src/api/demo/demo/index.ts`、`src/api/demo/demo/types.ts`。 - 当前仓库内置 React 生成模板:`gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`。
- 树表:`src/views/demo/tree/index.vue`、`src/views/workflow/category/index.vue`。 - 标准单表:`src/pages/demo/demo/index.tsx`、`src/api/demo/demo/index.ts`、`src/api/demo/demo/types.ts`。
- 复杂系统页:`src/views/system/user/index.vue`、`src/views/system/role/index.vue`、`src/views/system/post/index.vue`、`src/views/system/config/index.vue`。 - 树表:`src/pages/demo/tree/index.tsx`、`src/pages/workflow/category/index.tsx`。
- workflow 页:`src/views/workflow/*`、`src/api/workflow/*`。 - 复杂系统页:`src/pages/system/user/index.tsx`、`src/pages/system/role/index.tsx`、`src/pages/system/post/index.tsx`、`src/pages/system/config/index.tsx`。
- 监控页:`src/views/monitor/*`、`src/api/monitor/*`。 - workflow 页:`src/pages/workflow/*`、`src/api/workflow/*`。
- 公共 hooks`src/hooks/async/useLoading.ts`、`src/hooks/dialog/*`、`src/hooks/form/*`、`src/hooks/table/*`、`src/hooks/tree/*`。 - 监控页:`src/pages/monitor/*`、`src/api/monitor/*`。
- 项目内 generator 模板: - 公共 hooks`src/hooks/useTableSelection.ts`、`src/hooks/useTableExport.ts`、`src/hooks/useDict.ts`、`src/hooks/useDateRangeQuery.ts`、`src/hooks/useTreeTableExpand.ts`、`src/hooks/useLoading.ts`、`src/hooks/useSearchReset.ts`。
`gen/api.ts.ftl` - 公共组件:`src/components/common/RowActions.tsx`、`TreePanel.tsx`、`RightToolbar.tsx`、`DictTag.tsx`、上传/预览组件。
`gen/types.ts.ftl` - 工具函数:`src/utils/ruoyi.ts`、`src/utils/permission.ts`、`src/utils/download.ts`、`src/utils/dict.ts`、`src/utils/modal.ts`。
`gen/index.vue.ftl`
`gen/index-tree.vue.ftl`
## 基础栈与格式 ## 基础栈与格式
- 技术栈是 Vue 3 + TypeScript + Element Plus + Vite + Pinia - 技术栈是 React + TypeScript + Umi Max + Ant Design + ProComponents + ahooks + Zustand + TanStack Query
- 包管理按仓库现状使用 pnpm。 - 包管理按仓库现状使用 pnpm。
- `.editorconfig` 要求 UTF-8、LF、2 空格缩进。 - `.editorconfig` 要求 UTF-8、2 空格缩进。
- 当前仓库没有 `.prettierrc`;格式化使用 `pnpm run fmt`lint 使用 `pnpm lint` - 当前仓库没有 Prettier;格式化使用 `pnpm run fmt`lint 使用 `pnpm lint`
- 不要在一个页面里混入与仓库不一致的格式和写法。 - 不要在一个页面里混入与仓库不一致的格式和写法。
- 不要把本机绝对路径写进 skill 输出或参考文档;跨环境参考统一使用 Gitee 仓库 URL、分支和仓库内相对路径。
## API 文件规则 ## API 文件规则
- 标准 API 文件放在 `src/api/<module>/<business>/index.ts`,同目录维护 `types.ts` - 标准 API 文件放在 `src/api/<module>/<business>/index.ts`,同目录维护 `types.ts`
- import 顺序优先跟随附近文件,标准生成页常见形式: - import 顺序优先跟随附近文件,标准形式:
`import type { XxxForm, XxxQuery, XxxVO } from '@/api/<module>/<business>/types';` `import type { PageResult, R } from '@/api/types';`
`import type { PageResult } from '@/api/types';` `import request from '@/api/request';`
`import type { AxiosPromise } from '@/utils/api-types';` `import type { XxxForm, XxxQuery, XxxVO } from './types';`
`import request from '@/utils/request';` - 不要从 `axios` 引入 `AxiosPromise`;当前 `request<T>()` 返回 `Promise<T>`,接口函数通常返回 `Promise<R<T>>`
- 不要从 `axios` 引入 `AxiosPromise` - 列表分页接口通常返回 `request<R<PageResult<XxxVO>>>({ url, method: 'get', params: query })`
- 列表分页接口通常返回 `AxiosPromise<PageResult<XxxVO>>` - 树表列表接口通常返回 `request<R<XxxVO[]>>({ url, method: 'get', params: query })`
- 树表列表接口通常返回 `AxiosPromise<XxxVO[]>` - 详情接口返回 `R<XxxVO>`;复杂详情返回单独的 `InfoVO`
- 详情接口返回 `AxiosPromise<XxxVO>`;复杂详情返回单独的 `InfoVO`
- 标准函数命名: - 标准函数命名:
`listXxx` -> `GET /<module>/<business>/list` `listXxx` -> `GET /<module>/<business>/list`
`getXxx` -> `GET /<module>/<business>/{id}` `getXxx` -> `GET /<module>/<business>/{id}`
@ -46,8 +41,8 @@
`changeXxxStatus` -> `PUT /<module>/<business>/changeStatus` `changeXxxStatus` -> `PUT /<module>/<business>/changeStatus`
- query string 用 `params`,请求体用 `data` - query string 用 `params`,请求体用 `data`
- 加密、防重复提交等 headers 直接写在请求配置里,例如用户重置密码中的 `isEncrypt`、`repeatSubmit`。 - 加密、防重复提交等 headers 直接写在请求配置里,例如用户重置密码中的 `isEncrypt`、`repeatSubmit`。
- 当前仓库有些 API 使用 `export const`,有些使用 `export function`;新增标准 CRUD 优先跟随 `gen/api.ts.ftl`相邻模块。 - 当前仓库多数 API 使用 `export function`;新增标准 CRUD 优先跟随相邻模块。
- 只有相邻模块已有 `export default { ... }` 聚合时才新增默认导出。 - 只有相邻模块已有聚合对象时才新增默认导出。
## 类型文件规则 ## 类型文件规则
@ -57,100 +52,103 @@
- 树表 `Query` 通常不继承 `PageQuery` - 树表 `Query` 通常不继承 `PageQuery`
- ID 字段通常使用 `string | number`,批量删除参数使用 `string | number | Array<string | number>` - ID 字段通常使用 `string | number`,批量删除参数使用 `string | number | Array<string | number>`
- Java 数值类型映射为 `number`Boolean 映射为 `boolean`,日期/文本默认 `string` - Java 数值类型映射为 `number`Boolean 映射为 `boolean`,日期/文本默认 `string`
- 日期范围查询保留 `params?: any`,不要因为它看起来宽松就删掉。 - 日期范围查询保留 `params?: Record<string, unknown>` 或跟随相邻页面现有类型,不要因为它看起来宽松就删掉。
- 列表对象、表单对象、查询对象职责分开;字段不一致时不要强行复用一个接口。 - 列表对象、表单对象、查询对象职责分开;字段不一致时不要强行复用一个接口。
- 能明确写出类型时不要用 `any`;组件库、字典或历史接口确实无法收窄时再保留。 - 能明确写出类型时不要用 `any`;组件库、字典或历史接口确实无法收窄时再保留。
## Vue 页面结构规则 ## React 页面结构规则
- 页面优先使用 `<script setup name="Xxx" lang="ts">` - 页面默认导出函数组件,命名如 `SystemUserPage`、`DemoDemoPage`、`WorkflowCategoryPage`。
- 标准根节点使用 `class="p-2 app-container <module>-<business>-page"`;已有页面是特殊布局时保持原样。 - 标准页使用 `PageContainer` 包裹 `ProTable``ModalForm`
- 标准列表页结构: - 标准列表页常见状态:
搜索卡片 `search-panel` `actionRef`、`form`、`modalOpen`、`modalTitle`、`ids`、`selectedOne`、权限布尔值。
表格卡片 `table-panel` - 弹窗状态优先使用 `useBoolean(false)`,表单用 `Form.useForm<XxxForm>()`
工具栏 `toolbar-shell` - ProTable 使用:
表格 `data-table` `rowKey`
`right-toolbar` `columns`
`pagination` `search={{ labelWidth: 90 }}`
新增/编辑 `el-dialog` `pagination={{ defaultPageSize: 10, showSizeChanger: true }}`
- 搜索区通过 `useSearchToggle` 控制 `showSearch`,头部点击切换。 `request={async params => { ... }}`
- 列表 loading 通过 `useLoading(true)``withLoading` `toolBarRender={() => [...]}`
- 选择状态通过 `useTableSelection<XxxVO>(item => item.id)` 返回 `ids`、`single`、`multiple`、`handleSelectionChange`。 - 分页查询使用 `toPageQuery(params)`,然后 `updateExportParams(query)`,最后 `return toTableData(res)`
- 表单弹窗优先使用 `useFormDialog({ form, formRef, initialFormData })`,返回 `dialog`、`resetForm`、`openDialog`、`showDialog`、`closeDialog`。 - 表格列类型使用 `ProColumns<XxxVO>[]`
- 仅需要简单弹窗状态或一个页面多个弹窗时使用 `useDialogState` - 新增/编辑弹窗使用 `ModalForm<XxxForm>`,字段优先使用 `ProFormText`、`ProFormDigit`、`ProFormSelect`、`ProFormTreeSelect`、`ProFormDateTimeRangePicker` 等 ProForm 组件。
- 日期范围查询优先使用 `useDateRangeQuery()`;带后端参数名时使用 `useDateRangeQuery('CreateTime')` 等相邻页面模式。 - ModalForm 成功提交后返回 `true`,并刷新 `actionRef.current?.reload()`
- 查询和表单状态通常放在 `reactive<PageData<Form, Query>>({ form, queryParams, rules })`,再 `toRefs(data)`
## 页面行为规则 ## 页面行为规则
- `getList` 负责设置 loading、调用列表接口、回填列表和 `total` - `openAdd` 负责 `form.resetFields()`、设置默认值、设置标题、打开弹窗。
- `handleQuery` 先把 `queryParams.value.pageNum = 1`,再调用 `getList()`;树表无分页时只调用 `getList()` - `openEdit` 先按行或 `selectedOne` 找目标,再查详情,`form.resetFields()``form.setFieldsValue(res.data)`,最后打开弹窗。
- `resetQuery` 使用 `useSearchReset`,分页页传 `pageNumKey: 'pageNum'`,需要时传 `pageSizeKey``resetExtras` - `submitForm` 根据主键判断新增或修改,成功后 `message.success('操作成功')`、重置表单、刷新列表。
- `handleAdd` 使用 `openDialog('添加xxx')`;如果有树/联动选项,打开前后按现有页面加载选项。 - `remove``handleDelete` 支持行删除和批量删除,成功后 `message.success('删除成功')`、`clearSelection()`、`reloadAndRest` 或 `reload`
- `handleUpdate``reset()`,再按行或 `ids.value[0]` 查详情,`Object.assign(form.value, res.data)`,最后 `showDialog('修改xxx')` - 批量按钮通常用 `Popconfirm`,行操作确认优先放在 `RowActions``confirm`
- `submitForm` 表单校验通过后设置 `buttonLoading`,根据主键判断新增或修改,成功后 `modal.msgSuccess('操作成功')`、关闭弹窗、刷新列表。 - 状态切换失败时要回滚或刷新,参考 `src/pages/system/user/index.tsx`
- `handleDelete` 使用 `modal.confirm(...)` 二次确认,再调用删除接口,成功提示并刷新。 - 日期范围查询优先使用 `useDateRangeQuery`,底层通过 `addDateRange` 写入 `params.beginXxx/endXxx`
- `handleExport` 使用 `requestDownload('<module>/<business>/export', { ...queryParams.value }, '<business>_<timestamp>.xlsx')` - 字典 options 优先使用 `dictOptions(dicts.xxx)`
- 状态切换失败时要把 switch 值回滚,参考 generator 模板和现有 `system/user`、`system/role`。 - 导入上传参考 `system/user/components/UserImportModal.tsx` 或流程定义导入弹窗,保留 `globalHeaders()``appEnv.baseApi` 相关方式。
- 导入上传使用 `globalHeaders()`、`import.meta.env.VITE_APP_BASE_API`、`ElUpload`,优先参考 `system/user` 或流程定义页面。
## 字典、权限与公共工具 ## 字典、权限与公共工具
- 字典使用 `useDict` - 字典使用 `const dicts = useDict('sys_normal_disable', 'sys_user_gender')`
`const { sys_normal_disable } = toRefs<any>(useDict('sys_normal_disable'));` - 字典 options 使用 `dictOptions` from `@/utils/dict` 映射为 `{ label, value }`
- 新增代码默认使用当前项目主流 `v-hasPermi` - 权限使用:
- 如果正在修改的文件已经混用或使用 `v-has-permi`,保持同文件现状,不为统一写法而重排无关代码。 `const userInfo = useUserStore(state => state.userInfo);`
- `el-dropdown-item` 延迟加载导致权限指令不可靠时,使用 `v-if="checkPermi([...])"`,参考 `system/user` `const canAdd = hasPermi(userInfo, ['system:user:add']);`
- 不要使用 Vue 权限指令 `v-hasPermi`
- 常用工具: - 常用工具:
`modal` from `@/plugins/modal` `hasPermi` from `@/utils/permission`
`download as requestDownload` from `@/utils/request` `handleTree`、`parseStrEmpty`、`toPageQuery`、`toTableData`、`addDateRange`、`formatDateTimeRange` from `@/utils/ruoyi`
`useDict` from `@/utils/dict` `dictOptions` from `@/utils/dict`
`checkPermi` from `@/utils/permission` `confirmAction`、`confirmTitleSafe` from `@/utils/modal`
`handleTree`、`parseStrEmpty` from `@/utils/ruoyi` `download` via `useTableExport`
## 组件与样式规则 ## 组件与样式规则
- 优先复用公共组件:`right-toolbar`、`pagination`、`DictTag` / `dict-tag`、`ImageUpload` / `image-upload`、`ImagePreview` / `image-preview`、`FileUpload` / `file-upload`、`Editor` / `editor`、`TreePanel`。 - 行操作优先复用 `RowActions`,并使用 `@ant-design/icons` 图标。
- 标准页面尽量使用已有页面壳类,不堆大量内联样式。 - 树筛选优先复用 `TreePanel`,参考 `system/user`
- 复杂页面需要 scoped SCSS 时优先复用: - 导出优先复用 `useTableExport`
`@use '@/assets/styles/components/page-shell' as pageShell;` - 多选优先复用 `useTableSelection`
再按现有 mixin 使用。 - 异步 loading 优先复用 `useLoading`,不要重复手写 `setLoading(true)` + `finally setLoading(false)`
- ProTable 重置后还要清空额外筛选状态时,优先复用 `useSearchReset`
- Promise 式确认框优先复用 `confirmAction``confirmTitleSafe`
- 标准页面尽量使用 `PageContainer`、`ProTable`、ProComponents 页面壳,不堆大量内联样式。
- 需要自定义布局时先查 `src/assets/styles` 是否已有 `.tree-table-page`、`.table-panel`、`.page-surface` 等类。
- 不要为了单页需求修改全局组件样式。 - 不要为了单页需求修改全局组件样式。
- 类名保持模块语义:`system-client-page`、`workflow-category-page`、`demo-demo-page`。
## 树表规则 ## 树表规则
- 树表列表接口通常返回数组,页面通过 `handleTree<T>(res.data, 'id', 'parentId')` 组树。 - 树表列表接口通常返回数组,页面通过 `handleTree<T>(res.data || [], 'id', 'parentId')` 组树。
- 使用 `row-key`、`:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"`。 - `ProTable` 使用 `pagination={false}`
- 展开/折叠使用 `useTreeTableExpand` - 使用 `useTreeTableExpand` 控制 `expandedRowKeys`、`onExpandedRowsChange`、`syncExpandedRows`、`toggleExpandAll`。
- 表单中上级节点使用 `el-tree-select` - 展开/折叠按钮参考 `demo/tree``workflow/category`,用 `SortAscendingOutlined`
- 表单中上级节点使用 `ProFormTreeSelect`
- 新增子节点时从当前行回填 `parentId` - 新增子节点时从当前行回填 `parentId`
- 删除确认文案优先使用业务名称,而不是批量 ID 文案。 - 删除确认文案优先使用业务名称,而不是批量 ID 文案。
## 与 gen 模板的关系 ## 与 gen 模板、Vue 参考项目和后端生成器的关系
- `gen` 是 plus-ui 前端项目内的生成模板;默认远端位置是 `https://gitee.com/JavaLionLi/plus-ui/tree/6.X-Vue/gen`,分支必须标记为 `6.X-Vue` - 当前仓库 `gen/*.ftl` 是 React 版内置代码生成模板,是维护生成能力时的第一参考。
- 本地仓库存在对应文件时优先读本地 `gen`,但不要把本地绝对路径写入 skill 或交付内容。 - `gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl` 内容必须生成 React TSX 页面。
- 新增标准单表页面时读取 `gen/index.vue.ftl`、`gen/api.ts.ftl`、`gen/types.ts.ftl`。 - 修改公共 hooks/工具后,如果它们能简化标准生成页,要同步评估 `gen/*.ftl` 是否需要更新。
- 新增树表页面时读取 `gen/index-tree.vue.ftl`、`gen/api.ts.ftl`、`gen/types.ts.ftl`。 - Vue skill 和 `.claude/agents` 提供的是任务分型、优先级、增量修改、自检方式,不是 React 实现模板。
- `gen` 模板是标准骨架,不是最终答案;落地时仍要对照目标模块真实页面和公共 hooks。 - boot4 后端 generator 模板可用于确认字段、权限、导出、状态切换、排序、日期范围等,但不是最终答案。
- 当前前端项目已经把生成页升级为 hooks 版:`useLoading`、`useFormDialog`、`useSearchReset`、`useTableSelection`、`useDateRangeQuery`。 - 当前 React 项目的核心骨架是 `ProTable`、`ModalForm`、`RowActions`、`useTableSelection`、`useTableExport`。
- 新增标准 CRUD 时,先从 `gen` 确认字段、权限、导出、状态切换、排序、日期范围等,再落成当前项目的实际页面壳。 - 修改已有页面时,不要把现有强业务逻辑替换回 generator 的简化逻辑。
- 修改已有页面时,不要把现有强业务逻辑替换回 `gen` 的简化逻辑。
## 验证规则 ## 验证规则
- 只改文档或 skill运行 skill 基础校验即可 - 只改文档、skill 或 `gen/*.ftl` 模板:至少运行 skill 基础校验或 `git diff --check`;如果模板改动依赖项目工具,优先再跑 `pnpm lint`
- 改前端 TS/Vue/API/types优先运行 `pnpm exec vue-tsc --noEmit`。 - 改前端 TS/TSX/API/types优先运行 `pnpm exec tsc --noEmit``pnpm lint`。
- 改页面模板、import、权限或较多文件运行 `pnpm lint` - 改页面、import、权限或较多文件运行 `pnpm lint`
- 改公共 hooks、组件、构建相关或大范围页面再运行 `pnpm build` - 改公共 hooks、组件、构建相关或大范围页面再运行 `pnpm build`
- 如果验证因为环境、依赖或权限失败,交付时说明失败命令和原因。 - 如果验证因为环境、依赖或权限失败,交付时说明失败命令和原因。
## 避免事项 ## 避免事项
- 不要从 `axios` 引入 `AxiosPromise` - 不要从 `axios` 引入 `AxiosPromise`
- 不要绕开 `request` 或 `requestDownload` 自造请求/下载封装。 - 不要绕开 `@/api/request` 或 `useTableExport` 自造请求/下载封装。
- 不要跳过 `types.ts`,把类型全写在页面里。 - 不要跳过 `types.ts`,把类型全写在页面里。
- 不要删除日期范围 `params`、权限指令、导出、导入、树筛选、列显隐等现有能力。 - 不要把 `src/pages` 写成 Vue 项目的 `src/views`
- 不要使用 Vue 的 `ref`、`reactive`、`toRefs`、`v-hasPermi`、Element Plus 组件或 SCSS 页面壳。
- 不要删除日期范围 `params`、权限判断、导出、导入、树筛选、列显隐等现有能力。
- 不要为了“更整洁”重写复杂页面的大块业务逻辑。 - 不要为了“更整洁”重写复杂页面的大块业务逻辑。
- 不要在新增标准页里使用与仓库不一致的 UI 壳或状态管理方式。

View File

@ -1,21 +1,9 @@
# 告诉EditorConfig插件这是根文件不用继续往上查找
root = true root = true
# 匹配全部文件
[*] [*]
# 缩进风格可选space、tab
indent_style = space
# 缩进的空格数
indent_size = 2
# 设置字符集
charset = utf-8 charset = utf-8
# 结尾换行符可选lf、cr、crlf
end_of_line = lf end_of_line = lf
# 在文件结尾插入新行
trim_trailing_whitespace = true
# 删除一行中的前后空格
insert_final_newline = true insert_final_newline = true
indent_style = space
indent_size = 2
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@ -1,42 +1,15 @@
# 页面标题 VITE_APP_TITLE=RuoYi-React-Plus后台管理系统
VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统 VITE_APP_LOGO_TITLE=RuoYi-React-Plus
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus VITE_APP_ENV=development
VITE_APP_BASE_API=/dev-api
# 开发环境配置 VITE_APP_CONTEXT_PATH=/
VITE_APP_ENV = 'development' VITE_APP_PORT=8000
VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 开发环境 VITE_APP_MESSAGE_ENABLED=true
VITE_APP_BASE_API = '/dev-api' VITE_APP_MESSAGE_TRANSPORT=sse
VITE_APP_MESSAGE_PATH=/resource/message
# 应用访问路径 例如使用前缀 /admin/ VITE_APP_ENCRYPT=true
VITE_APP_CONTEXT_PATH = '/' VITE_APP_RSA_PUBLIC_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB
VITE_APP_RSA_PRIVATE_KEY=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAObq7yrxfvyieZtTjAYyrdvi59tYTXxjO5ajmPCRSXBY9M9wQ1tli297JN6mnY53UJMNyOFNSZVi8WSFoIXjpR87FmvChJlzeN/dZdd3SEs48Ee66XKeSePYqxa8oO5GKDsnajgpsOHKXSeeVSIysiIPS2/WsEqk0In9P4w3RsRFAgMBAAECgYBiMEWwce24SPICnRzuScBpvmsudrbEDIH7BOd0a6LZlcnLJwZNJ7mJlshPsHNQb+WgEf135+BBGEhioPtn0yuTdEuKP4kB9UdYUKiayWCoWhJpesv7sAD4RDClV7dhuV+gcd1AXD+YzyRIPbGm0VC2U+4q8/+UPRpVjqskbLVTgQJBAPRpou7g3S8n4XB527kq0D8I3+ZYwMxZhszwhrCDpJU319+ucmpLVwYIzDmZVeID2QQdUaDfIEViFHu95xDrGiUCQQDx3YOKn3yaEctk/ERVn7hDAyAXUbd8/pv2b24/M/l1ZevlsFem8U4Jk5Mu64t3z3YGJoymEjQmbucwT01iKhehAkEAxlnccsRmfFh/KkqauKE4M4++NTAd9zlInpUsmZ+cN8UEGnF2RTEzRKBrLOt1uWCqBB7PGiE6DVTVjr7FAQPrSQJAT5yeY87DcONSk9cFlzmPqV8p/QME5rvYEnHzVBKDlkUKNPyqnWToTvaoh9U4fyNmsfeWbEOprszqhFhWHG3GgQJAK8+ynmyFhaw63+Hx2KU5zR4hVuQso2IzrEurGxCxybV6mR7VBerb4502+EPx3PmOgxQL+niUFhcMWxcvBFP9+A==
# 监控地址 VITE_APP_MONITOR_ADMIN=http://localhost:9090/admin/applications
VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications' VITE_APP_SNAILJOB_ADMIN=http://localhost:8800/snail-job
# SnailJob 控制台地址
VITE_APP_SNAILJOB_ADMIN = 'http://localhost:8800/snail-job'
# SnailAI 控制台地址
VITE_APP_SNAILAI_ADMIN = 'http://localhost:8900/snail-ai'
VITE_APP_PORT = 80
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT = true
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB'
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
VITE_APP_RSA_PRIVATE_KEY = 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAObq7yrxfvyieZtTjAYyrdvi59tYTXxjO5ajmPCRSXBY9M9wQ1tli297JN6mnY53UJMNyOFNSZVi8WSFoIXjpR87FmvChJlzeN/dZdd3SEs48Ee66XKeSePYqxa8oO5GKDsnajgpsOHKXSeeVSIysiIPS2/WsEqk0In9P4w3RsRFAgMBAAECgYBiMEWwce24SPICnRzuScBpvmsudrbEDIH7BOd0a6LZlcnLJwZNJ7mJlshPsHNQb+WgEf135+BBGEhioPtn0yuTdEuKP4kB9UdYUKiayWCoWhJpesv7sAD4RDClV7dhuV+gcd1AXD+YzyRIPbGm0VC2U+4q8/+UPRpVjqskbLVTgQJBAPRpou7g3S8n4XB527kq0D8I3+ZYwMxZhszwhrCDpJU319+ucmpLVwYIzDmZVeID2QQdUaDfIEViFHu95xDrGiUCQQDx3YOKn3yaEctk/ERVn7hDAyAXUbd8/pv2b24/M/l1ZevlsFem8U4Jk5Mu64t3z3YGJoymEjQmbucwT01iKhehAkEAxlnccsRmfFh/KkqauKE4M4++NTAd9zlInpUsmZ+cN8UEGnF2RTEzRKBrLOt1uWCqBB7PGiE6DVTVjr7FAQPrSQJAT5yeY87DcONSk9cFlzmPqV8p/QME5rvYEnHzVBKDlkUKNPyqnWToTvaoh9U4fyNmsfeWbEOprszqhFhWHG3GgQJAK8+ynmyFhaw63+Hx2KU5zR4hVuQso2IzrEurGxCxybV6mR7VBerb4502+EPx3PmOgxQL+niUFhcMWxcvBFP9+A=='
# 客户端id
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
# 统一消息推送开关
VITE_APP_MESSAGE_ENABLED = true
# sse / websocket
VITE_APP_MESSAGE_TRANSPORT = 'sse'
# 统一消息推送路径
VITE_APP_MESSAGE_PATH = '/resource/message'

View File

@ -1,45 +1,15 @@
# 页面标题 VITE_APP_TITLE=RuoYi-React-Plus后台管理系统
VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统 VITE_APP_LOGO_TITLE=RuoYi-React-Plus
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus VITE_APP_ENV=production
VITE_APP_BASE_API=/prod-api
# 生产环境配置 VITE_APP_CONTEXT_PATH=/
VITE_APP_ENV = 'production' VITE_APP_PORT=8000
VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 应用访问路径 例如使用前缀 /admin/ VITE_APP_MESSAGE_ENABLED=true
VITE_APP_CONTEXT_PATH = '/' VITE_APP_MESSAGE_TRANSPORT=sse
VITE_APP_MESSAGE_PATH=/resource/message
# 监控地址 VITE_APP_ENCRYPT=true
VITE_APP_MONITOR_ADMIN = '/admin/applications' VITE_APP_RSA_PUBLIC_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB
VITE_APP_RSA_PRIVATE_KEY=MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAObq7yrxfvyieZtTjAYyrdvi59tYTXxjO5ajmPCRSXBY9M9wQ1tli297JN6mnY53UJMNyOFNSZVi8WSFoIXjpR87FmvChJlzeN/dZdd3SEs48Ee66XKeSePYqxa8oO5GKDsnajgpsOHKXSeeVSIysiIPS2/WsEqk0In9P4w3RsRFAgMBAAECgYBiMEWwce24SPICnRzuScBpvmsudrbEDIH7BOd0a6LZlcnLJwZNJ7mJlshPsHNQb+WgEf135+BBGEhioPtn0yuTdEuKP4kB9UdYUKiayWCoWhJpesv7sAD4RDClV7dhuV+gcd1AXD+YzyRIPbGm0VC2U+4q8/+UPRpVjqskbLVTgQJBAPRpou7g3S8n4XB527kq0D8I3+ZYwMxZhszwhrCDpJU319+ucmpLVwYIzDmZVeID2QQdUaDfIEViFHu95xDrGiUCQQDx3YOKn3yaEctk/ERVn7hDAyAXUbd8/pv2b24/M/l1ZevlsFem8U4Jk5Mu64t3z3YGJoymEjQmbucwT01iKhehAkEAxlnccsRmfFh/KkqauKE4M4++NTAd9zlInpUsmZ+cN8UEGnF2RTEzRKBrLOt1uWCqBB7PGiE6DVTVjr7FAQPrSQJAT5yeY87DcONSk9cFlzmPqV8p/QME5rvYEnHzVBKDlkUKNPyqnWToTvaoh9U4fyNmsfeWbEOprszqhFhWHG3GgQJAK8+ynmyFhaw63+Hx2KU5zR4hVuQso2IzrEurGxCxybV6mR7VBerb4502+EPx3PmOgxQL+niUFhcMWxcvBFP9+A==
# SnailJob 控制台地址 VITE_APP_MONITOR_ADMIN=/admin/applications
VITE_APP_SNAILJOB_ADMIN = '/snail-job' VITE_APP_SNAILJOB_ADMIN=/snail-job
# SnailAI 控制台地址
VITE_APP_SNAILAI_ADMIN = '/snail-ai'
# 生产环境
VITE_APP_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip
VITE_APP_PORT = 80
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT = true
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB'
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换
VITE_APP_RSA_PRIVATE_KEY = 'MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAObq7yrxfvyieZtTjAYyrdvi59tYTXxjO5ajmPCRSXBY9M9wQ1tli297JN6mnY53UJMNyOFNSZVi8WSFoIXjpR87FmvChJlzeN/dZdd3SEs48Ee66XKeSePYqxa8oO5GKDsnajgpsOHKXSeeVSIysiIPS2/WsEqk0In9P4w3RsRFAgMBAAECgYBiMEWwce24SPICnRzuScBpvmsudrbEDIH7BOd0a6LZlcnLJwZNJ7mJlshPsHNQb+WgEf135+BBGEhioPtn0yuTdEuKP4kB9UdYUKiayWCoWhJpesv7sAD4RDClV7dhuV+gcd1AXD+YzyRIPbGm0VC2U+4q8/+UPRpVjqskbLVTgQJBAPRpou7g3S8n4XB527kq0D8I3+ZYwMxZhszwhrCDpJU319+ucmpLVwYIzDmZVeID2QQdUaDfIEViFHu95xDrGiUCQQDx3YOKn3yaEctk/ERVn7hDAyAXUbd8/pv2b24/M/l1ZevlsFem8U4Jk5Mu64t3z3YGJoymEjQmbucwT01iKhehAkEAxlnccsRmfFh/KkqauKE4M4++NTAd9zlInpUsmZ+cN8UEGnF2RTEzRKBrLOt1uWCqBB7PGiE6DVTVjr7FAQPrSQJAT5yeY87DcONSk9cFlzmPqV8p/QME5rvYEnHzVBKDlkUKNPyqnWToTvaoh9U4fyNmsfeWbEOprszqhFhWHG3GgQJAK8+ynmyFhaw63+Hx2KU5zR4hVuQso2IzrEurGxCxybV6mR7VBerb4502+EPx3PmOgxQL+niUFhcMWxcvBFP9+A=='
# 客户端id
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
# 统一消息推送开关
VITE_APP_MESSAGE_ENABLED = true
# sse / websocket
VITE_APP_MESSAGE_TRANSPORT = 'sse'
# 统一消息推送路径
VITE_APP_MESSAGE_PATH = '/resource/message'

37
.gitignore vendored
View File

@ -1,29 +1,10 @@
.DS_Store node_modules
.history dist
node_modules/ .umi
dist/ .umi-production
npm-debug.log* src/.umi
yarn-debug.log* src/.umi-production
yarn-error.log* .env.local
**/*.log *.log
.idea/
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
# Editor directories and files
.idea
.vscode
*.iml *.iml
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
package-lock.json
yarn.lock
# 编译生成的文件
auto-imports.d.ts
components.d.ts

10
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="BUILD_PROCESS_HEAP_SIZE" value="2048" />
</component>
</project>

3
.idea/dictionaries/LionLi.xml generated Normal file
View File

@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="LionLi" />
</component>

6
.idea/encodings.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

17
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<selected-state>
<State>
<id>User defined</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="liberica-25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/plus-ui-react.iml" filepath="$PROJECT_DIR$/.idea/plus-ui-react.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,13 +1,6 @@
{ {
"$schema": "./node_modules/oxfmt/configuration_schema.json", "$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": [ "ignorePatterns": ["src/.umi", "src/.umi-production", ".ai_state", ".claude", ".codex", "dist", "node_modules"],
"src/types/components.d.ts",
"src/types/auto-imports.d.ts",
".ai_state",
".claude",
".codex",
"doc"
],
"printWidth": 120, "printWidth": 120,
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "none",

View File

@ -1,6 +1,6 @@
{ {
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json", "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "import", "vue"], "plugins": ["eslint", "typescript", "unicorn", "oxc", "import", "react"],
"rules": { "rules": {
"typescript/no-empty-function": "off", "typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off", "typescript/no-explicit-any": "off",
@ -12,7 +12,13 @@
"import/no-unassigned-import": "off", "import/no-unassigned-import": "off",
"import/no-named-as-default-member": "off", "import/no-named-as-default-member": "off",
"import/no-named-as-default": "off", "import/no-named-as-default": "off",
"react/react-in-jsx-scope": "off",
"react/no-unstable-nested-components": "off",
"react/iframe-missing-sandbox": "off",
"react-hooks/exhaustive-deps": "off",
"no-shadow": "off", "no-shadow": "off",
"unicorn/no-array-sort": "off",
"unicorn/no-useless-fallback-in-spread": "off",
"unicorn/prefer-add-event-listener": "off", "unicorn/prefer-add-event-listener": "off",
"unicorn/consistent-function-scoping": "off", "unicorn/consistent-function-scoping": "off",
"unicorn/no-instanceof-builtins": "off" "unicorn/no-instanceof-builtins": "off"
@ -21,5 +27,14 @@
"correctness": "error", "correctness": "error",
"suspicious": "error" "suspicious": "error"
}, },
"ignorePatterns": [".ai_state", ".claude", ".codex", "doc", "dist/**", "dist-ssr/**", "coverage/**"] "ignorePatterns": [
"src/.umi",
"src/.umi-production",
".ai_state",
".claude",
".codex",
"dist/**",
"dist-ssr/**",
"coverage/**"
]
} }

20
LICENSE
View File

@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2019 RuoYi-Vue-Plus
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,21 +1,23 @@
# RuoYi-React-Plus
## 平台简介 ## 平台简介
- 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本 - 本仓库为前端技术栈 [React](https://react.dev/) + [TypeScript](https://www.typescriptlang.org/) + [Ant Design](https://ant.design/) + [Umi Max](https://umijs.org/) + [Vite](https://vitejs.dev/)
- 官方项目: 基于 React + Ant Design 版本前端项目 [plus-ui-react](https://gitee.com/JavaLionLi/plus-ui/tree/6.X-React/) - 官方项目: 基于 Vue + ElementPlus 版本前端项目 [plus-ui](https://gitee.com/JavaLionLi/plus-ui)
- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5) - 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
- 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean) - 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
## 配套后端代码仓库地址 ## 配套后端代码仓库地址
| 介绍 | 项目名 | 项目地址 | | 介绍 | 项目名 | 项目地址 |
| ----------------- | :--------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | -------------- | :--------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 🔥 分布式集群框架 | RuoYi-Vue-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus)<br> - [GitHub](https://github.com/dromara/RuoYi-Vue-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus) | | 分布式集群框架 | RuoYi-Vue-Plus | [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus) / [GitHub](https://github.com/dromara/RuoYi-Vue-Plus) / [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus) |
| 🔥 微服务框架 | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)<br>- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) | | 微服务框架 | RuoYi-Cloud-Plus | [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus) / [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus) / [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) |
## 分支说明 ## 分支说明
- 6.X分支(稳定发布主分支 生产可用) - 6.X-react分支(稳定发布主分支 生产可用)
- dev分支(开发分支 开发过程中使用) - dev-react分支(开发分支 开发过程中使用)
## 前端运行 ## 前端运行
@ -29,7 +31,7 @@ pnpm dev
# 构建生产环境 # 构建生产环境
pnpm build:prod pnpm build:prod
# 前端访问地址 http://localhost:80 # 前端访问地址 http://localhost:8000
``` ```
## 本框架与RuoYi的业务差异 ## 本框架与RuoYi的业务差异
@ -61,16 +63,16 @@ pnpm build:prod
| | | | | |
|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| |--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| ![输入图片说明](https://foruda.gitee.com/images/1780299033689126697/868ef1ea_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299052163404649/8d94165d_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300015922774817/47c1f39f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300034170229441/1608ee0a_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299074949590692/27f5bfb5_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299108816841231/619a7c57_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300053860691133/f8d334ce_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300066022559831/7c8e74cd_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299089818500856/862ba805_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299173744267947/95cb0cd3_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300081538003511/906c028f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300104233264980/8df4a03d_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299193694706123/28257dc1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299147525013883/ebcd9dfe_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300116889441614/7206e749_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300133555040293/6f4067f0_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299220007761523/dc7e27c9_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299235966983519/35b047e1_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300380395316233/d1cc70c4_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300232306768234/1f474295_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299250884681522/e5731314_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299267028602229/230d5428_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300245151922243/132ae6e0_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300259301543383/a0c982b3_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299293149732467/19abcf6c_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299311267192779/e665c668_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300401947949756/673c7fdc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300417037474904/2fd89565_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299327888096947/283a177f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299348897579356/caac864e_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300430575382678/cef384a4_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300446747725208/413ba289_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299376680669014/452585fb_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299843158459866/ceebbb63_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300471231552129/5d45227c_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300496559910468/0008fa0c_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299432356918392/07abdf6a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299465584172180/a2b2be12_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300562713799535/a15b018d_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300575582711290/d440b1c9_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299491233431530/d88bfa35_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299513358913413/f2f90032_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300617515185769/db2968e4_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300631462725775/30b9cd2c_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299527419238776/549cb852_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299553918371792/43bd3bff_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300644824617448/12b7e446_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300665964630901/07f14841_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299586662735625/1107a3ee_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299613342135530/526d7859_1766278.png "屏幕截图") | | ![输入图片说明](https://foruda.gitee.com/images/1780300695487754948/17569a2e_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300715114472355/ebb81e7c_1766278.png "屏幕截图") |

40
config/config.ts Normal file
View File

@ -0,0 +1,40 @@
import { defineConfig } from '@umijs/max';
import { createUmiAppConfig } from '../vite.config';
const appConfig = createUmiAppConfig();
export default defineConfig({
title: appConfig.title,
antd: {},
access: {},
model: {},
initialState: {},
vite: appConfig.vite,
npmClient: 'pnpm',
hash: true,
esbuildMinifyIIFE: true,
history: {
type: 'browser'
},
base: appConfig.base,
publicPath: appConfig.publicPath,
routes: [
{ path: '/login', component: './login', layout: false },
{ path: '/register', component: './register', layout: false },
{ path: '/social-callback', component: './socialCallback', layout: false },
{ path: '/401', component: './error/401', layout: false },
{ path: '/404', component: './error/404', layout: false },
{ path: '/redirect/*', component: './redirect', layout: false },
{
path: '/',
component: '../layouts/BasicLayout',
routes: [
{ path: '/', redirect: '/index' },
{ path: '/index', component: './index' },
{ path: '/user/profile', component: './system/user/profile' },
{ path: '*', component: './dynamicPage' }
]
}
],
proxy: appConfig.proxy
});

View File

@ -1,104 +1,94 @@
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types'; import type { <#if !table.tree>PageResult, </#if> R } from '@/api/types';
<#if !table.tree> import request from '@/api/request';
import type { PageResult } from '@/api/types'; import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from './types';
</#if>
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
/** /**
* 查询${functionName}列表 * 查询${functionName}列表
* @param query
* @returns {*}
*/ */
export function list${BusinessName}(query?: ${BusinessName}Query) {
export const list${BusinessName} = (query?: ${BusinessName}Query): AxiosPromise<<#if table.tree>${BusinessName}VO[]<#else>PageResult<${BusinessName}VO></#if>> => { return request<R<<#if table.tree>${BusinessName}VO[]<#else> PageResult<${BusinessName}VO></#if>>>({
return request({
url: '/${moduleName}/${businessName}/list', url: '/${moduleName}/${businessName}/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** /**
* 查询${functionName}详细 * 查询${functionName}详细
* @param ${pkColumn.javaField}
*/ */
export const get${BusinessName} = (${pkColumn.javaField}: string | number): AxiosPromise<${BusinessName}VO> => { export function get${BusinessName}(${pkColumn.javaField}: string | number) {
return request({ return request<R<${BusinessName}VO>>({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'get' method: 'get'
}); });
}; }
/** /**
* 新增${functionName} * 新增${functionName}
* @param data
*/ */
export const add${BusinessName} = (data: ${BusinessName}Form) => { export function add${BusinessName}(data: ${BusinessName}Form) {
return request({ return request<R>({
url: '/${moduleName}/${businessName}', url: '/${moduleName}/${businessName}',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** /**
* 修改${functionName} * 修改${functionName}
* @param data
*/ */
export const update${BusinessName} = (data: ${BusinessName}Form) => { export function update${BusinessName}(data: ${BusinessName}Form) {
return request({ return request<R>({
url: '/${moduleName}/${businessName}', url: '/${moduleName}/${businessName}',
method: 'put', method: 'put',
data: data data
}); });
}; }
<#if enableStatus> <#if enableStatus>
/** /**
* 修改${functionName}状态 * 修改${functionName}状态
* @param ${pkColumn.javaField}
* @param status
*/ */
export const change${BusinessName}Status = (${pkColumn.javaField}: string | number, status: <#if statusColumn.javaType == 'Boolean'>boolean<#elseif statusColumn.javaType == 'String'>string<#else> number</#if>) => { export function change${BusinessName}Status(
return request({ ${pkColumn.javaField}: string | number,
${statusField}: <#if statusColumn.javaType == 'Boolean'>boolean<#elseif statusColumn.javaType == 'Integer' || statusColumn.javaType == 'Long'>number<#else> string</#if>
) {
return request<R>({
url: '/${moduleName}/${businessName}/changeStatus', url: '/${moduleName}/${businessName}/changeStatus',
method: 'put', method: 'put',
data: { data: {
${pkColumn.javaField}, ${pkColumn.javaField},
${statusField}: status ${statusField}
} }
}); });
}; }
</#if>
</#if>
<#if enableSort> <#if enableSort>
/** /**
* 调整${functionName}排序 * 调整${functionName}排序
* @param ${pkColumn.javaField}
* @param sortValue
*/ */
export const update${BusinessName}Sort = (${pkColumn.javaField}: string | number, sortValue: <#if sortColumn.javaType == 'String' || sortColumn.javaType == 'LocalDateTime'>string<#else> number</#if>) => { export function update${BusinessName}Sort(
return request({ ${pkColumn.javaField}: string | number,
${sortField}: <#if sortColumn.javaType == 'LocalDateTime' || sortColumn.javaType == 'String'>string<#else> number</#if>
) {
return request<R>({
url: '/${moduleName}/${businessName}/updateSort', url: '/${moduleName}/${businessName}/updateSort',
method: 'put', method: 'put',
data: { data: {
${pkColumn.javaField}, ${pkColumn.javaField},
${sortField}: sortValue ${sortField}
} }
}); });
}; }
</#if>
</#if>
/** /**
* 删除${functionName} * 删除${functionName}
* @param ${pkColumn.javaField}
*/ */
export const del${BusinessName} = (${pkColumn.javaField}: string | number | Array<string | number>) => { export function del${BusinessName}(${pkColumn.javaField}: string | number | Array<string | number>) {
return request({ return request<R>({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'delete' method: 'delete'
}); });
}; }

424
gen/index-tree.tsx.ftl Normal file
View File

@ -0,0 +1,424 @@
import { DeleteOutlined<#if enableExport>, DownloadOutlined</#if>, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons';
import {
ModalForm,
PageContainer,
<#if needCheckbox>
ProFormCheckbox,
</#if>
<#if needDateField>
ProFormDateTimePicker,
</#if>
<#if needDigit>
ProFormDigit,
</#if>
<#if needSelect>
ProFormSelect,
</#if>
<#if needTextArea>
ProFormTextArea,
</#if>
ProFormText,
ProFormTreeSelect,
ProTable,
type ActionType,
type ProColumns
} from '@ant-design/pro-components';
import { useBoolean } from 'ahooks';
import { Button, Form, message<#if enableStatus || needSwitchField>, Switch</#if><#if enableSort>, InputNumber</#if> } from 'antd';
import { useMemo, useRef, useState } from 'react';
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
import {
add${BusinessName},
<#if enableStatus>
change${BusinessName}Status,
</#if>
del${BusinessName},
get${BusinessName},
list${BusinessName},
<#if enableSort>
update${BusinessName}Sort,
</#if>
update${BusinessName}
} from '@/api/${moduleName}/${businessName}';
<#if needDict>
import DictTag from '@/components/common/DictTag';
</#if>
<#if needFileUpload>
import FileUpload from '@/components/common/FileUpload';
</#if>
<#if needImagePreview>
import ImagePreview from '@/components/common/ImagePreview';
</#if>
<#if needImageUpload>
import ImageUpload from '@/components/common/ImageUpload';
</#if>
<#if needEditor>
import RichTextEditor from '@/components/common/RichTextEditor';
</#if>
import RowActions from '@/components/common/RowActions';
<#if needDict>
import { useDict } from '@/hooks/useDict';
</#if>
<#if enableExport>
import { useTableExport } from '@/hooks/useTableExport';
</#if>
import { useTableScroll } from '@/hooks/useTableScroll';
import { useTreeTableExpand } from '@/hooks/useTreeTableExpand';
import { useUserStore } from '@/stores/userStore';
<#if needDict>
import { dictOptions } from '@/utils/dict';
</#if>
<#if enableStatus>
import { confirmAction } from '@/utils/modal';
</#if>
import { hasPermi } from '@/utils/permission';
import { filterTree<#if needDateField>, formatDateTimeFields</#if>, handleTree<#if needDateField>, toDayjsFields</#if> } from '@/utils/ruoyi';
const default${BusinessName}Form: ${BusinessName}Form = {
${treeParentCode}: ${treeRootValueTsLiteral},
<#list columns as column>
<#if (column.insert || column.edit) && !column.pk && column.htmlType == "checkbox">
${column.javaField}: [],
</#if>
</#list>
};
interface ${BusinessName}SelectNode {
title: string;
value: string | number;
children?: ${BusinessName}SelectNode[];
}
function toTreeSelectData(nodes: ${BusinessName}VO[]): ${BusinessName}SelectNode[] {
return nodes.map(node => ({
title: String(node.${treeName} || ''),
value: node.${treeCode},
children: node.children ? toTreeSelectData(node.children) : undefined
}));
}
<#if enableStatus>
const ${statusField}ActiveValue = <#if statusColumn.javaType == "Boolean">true<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">0<#else>'0'</#if>;
const ${statusField}InactiveValue = <#if statusColumn.javaType == "Boolean">false<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">1<#else>'1'</#if>;
</#if>
export default function ${BusinessName}Page() {
const actionRef = useRef<ActionType | undefined>(undefined);
const { tableScroll } = useTableScroll(1000);
const [form] = Form.useForm<${BusinessName}Form>();
const userInfo = useUserStore(state => state.userInfo);
<#if needDict>
const dicts = useDict(${dicts});
</#if>
const [treeOptions, setTreeOptions] = useState<${BusinessName}VO[]>([]);
const [tableRows, setTableRows] = useState<${BusinessName}VO[]>([]);
const { expandAll, expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } =
useTreeTableExpand<${BusinessName}VO>(row => row.${treeCode});
const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
const [modalTitle, setModalTitle] = useState('');
<#if enableExport>
const { updateExportParams, exportFile } = useTableExport();
</#if>
const canAdd = hasPermi(userInfo, ['${permissionPrefix}:add']);
const canEdit = hasPermi(userInfo, ['${permissionPrefix}:edit']);
const canRemove = hasPermi(userInfo, ['${permissionPrefix}:remove']);
<#if enableExport>
const canExport = hasPermi(userInfo, ['${permissionPrefix}:export']);
</#if>
const treeSelectData = useMemo(
() => [{ title: '顶级节点', value: ${treeRootValueTsLiteral}, children: toTreeSelectData(treeOptions) }],
[treeOptions]
);
const loadTreeOptions = async (excludeId?: string | number) => {
const res = await list${BusinessName}();
const rows = handleTree<${BusinessName}VO>(res.data || [], '${treeCode}', '${treeParentCode}');
setTreeOptions(excludeId ? filterTree(rows, node => node.${treeCode} !== excludeId) : rows);
};
const openAdd = async (row?: ${BusinessName}VO) => {
await loadTreeOptions();
form.resetFields();
form.setFieldsValue({ ...default${BusinessName}Form, ${treeParentCode}: row?.${treeCode} || treeRootValueTsLiteral });
setModalTitle('添加${functionName}');
openModal();
};
const openEdit = async (row: ${BusinessName}VO) => {
await loadTreeOptions(row.${treeCode});
const res = await get${BusinessName}(row.${pkColumn.javaField});
const data = <#if needDateField>toDayjsFields({ ...res.data }, [
<#list columns as column>
<#if (column.insert || column.edit) && column.htmlType == "datetime">
'${column.javaField}',
</#if>
</#list>
])<#else>{ ...res.data }</#if>;
<#list columns as column>
<#if (column.insert || column.edit) && column.htmlType == "checkbox">
if (typeof data.${column.javaField} === 'string') {
data.${column.javaField} = data.${column.javaField}.split(',');
}
</#if>
</#list>
form.resetFields();
form.setFieldsValue(data);
setModalTitle('修改${functionName}');
openModal();
};
const submitForm = async (values: ${BusinessName}Form) => {
const submitValues = <#if needDateField>formatDateTimeFields({ ...values }, [
<#list columns as column>
<#if (column.insert || column.edit) && column.htmlType == "datetime">
'${column.javaField}',
</#if>
</#list>
])<#else>{ ...values }</#if>;
<#list columns as column>
<#if (column.insert || column.edit) && column.htmlType == "checkbox">
if (Array.isArray(submitValues.${column.javaField})) {
submitValues.${column.javaField} = submitValues.${column.javaField}.join(',');
}
</#if>
</#list>
submitValues.${pkColumn.javaField} ? await update${BusinessName}(submitValues) : await add${BusinessName}(submitValues);
message.success('操作成功');
form.resetFields();
actionRef.current?.reload();
return true;
};
const remove = async (row: ${BusinessName}VO) => {
await del${BusinessName}(row.${pkColumn.javaField});
message.success('删除成功');
actionRef.current?.reload();
};
<#if enableStatus>
const handleStatusChange = async (row: ${BusinessName}VO, checked: boolean) => {
const previousStatus = row.${statusField};
const status = checked ? ${statusField}ActiveValue : ${statusField}InactiveValue;
const text = checked ? '启用' : '停用';
try {
await confirmAction(`确认要"${r'${text}'}"吗?`);
await change${BusinessName}Status(row.${pkColumn.javaField}, status);
message.success(`${r'${text}'}成功`);
actionRef.current?.reload();
} catch {
row.${statusField} = previousStatus;
actionRef.current?.reload();
}
};
</#if>
<#if enableSort>
const handleSortChange = async (row: ${BusinessName}VO, value?: number | null) => {
await update${BusinessName}Sort(row.${pkColumn.javaField}, value || 0);
message.success('排序更新成功');
actionRef.current?.reload();
};
</#if>
const columns: ProColumns<${BusinessName}VO>[] = [
<#list columns as column>
<#if column.list && !column.pk>
<#if enableStatus && statusField == column.javaField>
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}',
valueType: 'select',
width: 100,
<#if column.dictType?has_content>
fieldProps: { options: dictOptions(dicts.${column.dictType}) },
</#if>
render: (_, row) => (
<Switch
checked={row.${column.javaField} === ${statusField}ActiveValue}
disabled={!canEdit}
onChange={checked => handleStatusChange(row, checked)}
/>
)
},
<#elseif enableSort && sortField == column.javaField>
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}',
search: false,
width: 130,
render: (_, row) => <InputNumber min={0} value={row.${column.javaField} as number} onChange={value => handleSortChange(row, value)} />
},
<#elseif column.htmlType == "datetime">
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}', valueType: 'dateTime', search: <#if column.query>true<#else> false</#if>, width: 170 },
<#elseif column.htmlType == "imageUpload">
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}Url',
search: false,
width: 100,
render: (_, row) => <ImagePreview src={row.${column.javaField}Url || String(row.${column.javaField} || '')} width={50} height={50} />
},
<#elseif column.dictColumn>
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}',
valueType: 'select',
fieldProps: { options: dictOptions(dicts.${column.dictType}) },
render: (_, row) => <DictTag options={dicts.${column.dictType}} value={row.${column.javaField}} />
},
<#elseif column.htmlType == "switch">
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}',
valueType: 'select',
width: 100,
render: (_, row) => <Switch checked={row.${column.javaField} === ${column.switchActiveValue}} disabled />
},
<#else>
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}'<#if !column.query>, search: false</#if> },
</#if>
</#if>
</#list>
{
title: '操作',
valueType: 'option',
width: 130,
fixed: 'right',
render: (_, row) => (
<RowActions
actions={[
canEdit && { key: 'edit', label: '修改', icon: <EditOutlined />, onClick: () => openEdit(row) },
canAdd && { key: 'add', label: '新增', icon: <PlusOutlined />, onClick: () => openAdd(row) },
canRemove && {
key: 'delete',
label: '删除',
icon: <DeleteOutlined />,
danger: true,
confirm: `是否确认删除${functionName}编号为"${r'${row.'}${pkColumn.javaField}${r'}'}"的数据项?`,
onClick: () => remove(row)
}
]}
/>
)
}
];
return (
<PageContainer title="${functionName}">
<ProTable<${BusinessName}VO, ${BusinessName}Query>
actionRef={actionRef}
rowKey="${treeCode}"
columns={columns}
scroll={tableScroll}
pagination={false}
search={{ labelWidth: 90 }}
expandable={{
expandedRowKeys,
onExpandedRowsChange
}}
request={async params => {
const res = await list${BusinessName}(params);
const rows = handleTree<${BusinessName}VO>(res.data || [], '${treeCode}', '${treeParentCode}');
setTableRows(rows);
syncExpandedRows(rows, expandAll);
<#if enableExport>
updateExportParams(params);
</#if>
return { data: rows, total: rows.length, success: true };
}}
toolbar={{ title: '${functionName}列表' }}
toolBarRender={() => [
canAdd && (
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => openAdd()}>
新增
</Button>
),
<Button
key="expand"
icon={<SortAscendingOutlined />}
onClick={() => toggleExpandAll(tableRows)}
>
展开/折叠
</Button><#if enableExport>,
canExport && (
<Button
key="export"
icon={<DownloadOutlined />}
onClick={() => exportFile('/${moduleName}/${businessName}/export', () => `${businessName}_${r'${Date.now()}'}.xlsx`)}
>
导出
</Button>
)</#if>
]}
/>
<ModalForm<${BusinessName}Form>
title={modalTitle}
open={modalOpen}
width={560}
form={form}
layout="vertical"
initialValues={default${BusinessName}Form}
modalProps={{ destroyOnHidden: true, onCancel: closeModal }}
onOpenChange={open => !open && closeModal()}
onFinish={submitForm}
>
<ProFormText name="${pkColumn.javaField}" hidden />
<#list columns as column>
<#if (column.insert || column.edit) && !column.pk>
<#if column.javaField == treeParentCode>
<ProFormTreeSelect
name="${column.javaField}"
label="${column.columnLabel}"
<#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>
fieldProps={{
allowClear: true,
treeDefaultExpandAll: true,
treeData: treeSelectData,
placeholder: '请选择${column.columnLabel}'
}}
/>
<#elseif column.htmlType == "input">
<ProFormText name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "textarea">
<ProFormTextArea name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "inputNumber">
<ProFormDigit name="${column.javaField}" label="${column.columnLabel}" min={0} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif (column.htmlType == "select" || column.htmlType == "radio" || column.htmlType == "switch") && column.dictType?has_content>
<ProFormSelect name="${column.javaField}" label="${column.columnLabel}" options={dictOptions(dicts.${column.dictType})} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "checkbox" && column.dictType?has_content>
<ProFormCheckbox.Group name="${column.javaField}" label="${column.columnLabel}" options={dictOptions(dicts.${column.dictType})} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "switch">
<Form.Item
name="${column.javaField}"
label="${column.columnLabel}"
valuePropName="checked"
getValueProps={value => ({ checked: value === ${column.switchActiveValue} })}
normalize={checked => (checked ? ${column.switchActiveValue} : ${column.switchInactiveValue})}
>
<Switch />
</Form.Item>
<#elseif column.htmlType == "datetime">
<ProFormDateTimePicker name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "imageUpload">
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
<ImageUpload />
</Form.Item>
<#elseif column.htmlType == "fileUpload">
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
<FileUpload />
</Form.Item>
<#elseif column.htmlType == "editor">
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
<RichTextEditor />
</Form.Item>
<#else>
<ProFormText name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
</#if>
</#if>
</#list>
</ModalForm>
</PageContainer>
);
}

View File

@ -1,691 +0,0 @@
<template>
<div class="p-2 page-shell ${moduleName}-${businessName}-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<#list columns as column>
<#if column.query>
<#if column.htmlType == "input" || column.htmlType == "textarea">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input v-model="queryParams.${column.javaField}" placeholder="请输入${column.columnLabel}" clearable @keyup.enter="handleQuery" />
</el-form-item>
<#elseif column.htmlType == "inputNumber">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input-number v-model="queryParams.${column.javaField}" controls-position="right" />
</el-form-item>
<#elseif (column.htmlType == "select" || column.htmlType == "radio") && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable>
<el-option v-for="dict in ${column.dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<#elseif column.htmlType == "switch" && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable>
<el-option v-for="dict in ${column.dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<#elseif column.htmlType == "switch">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable>
<#if column.javaType == "Boolean">
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
<#elseif column.javaType == "Integer" || column.javaType == "Long">
<el-option label="开启" :value="0" />
<el-option label="关闭" :value="1" />
<#else>
<el-option label="开启" value="0" />
<el-option label="关闭" value="1" />
</#if>
</el-select>
</el-form-item>
<#elseif (column.htmlType == "select" || column.htmlType == "radio") && !(column.dictType?has_content)>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<#elseif column.htmlType == "datetime" && column.queryType != "BETWEEN">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-date-picker clearable
v-model="queryParams.${column.javaField}"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择${column.columnLabel}"
/>
</el-form-item>
<#elseif column.htmlType == "datetime" && column.queryType == "BETWEEN">
<el-form-item label="${column.columnLabel}" style="width: 308px">
<el-date-picker
v-model="dateRange${column.capJavaField}"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
</#if>
</#if>
</#list>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover" class="table-panel">
<template #header>
<div class="toolbar-shell">
<div class="table-heading">
<h3>${functionName}列表</h3>
</div>
<div class="toolbar-actions">
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
<#if enableExport>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['${moduleName}:${businessName}:export']">导出</el-button>
</#if>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table
ref="${businessName}TableRef"
v-loading="loading"
class="data-table"
:data="${businessName}List"
row-key="${treeCode}"
border
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<#assign firstTreeListField = "">
<#list columns as tempColumn>
<#if !tempColumn.pk && tempColumn.list && "" != tempColumn.javaField && firstTreeListField == "">
<#assign firstTreeListField = tempColumn.javaField>
</#if>
</#list>
<#list columns as column>
<#if column.pk>
<#elseif enableStatus && statusField == column.javaField>
<#if column.javaField == firstTreeListField>
<el-table-column label="${column.columnLabel}" prop="${column.javaField}">
<#else>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}">
</#if>
<template #default="scope">
<el-switch
v-model="scope.row.${column.javaField}"
:active-value="${statusField}ActiveValue"
:inactive-value="${statusField}InactiveValue"
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
<#elseif enableSort && sortField == column.javaField>
<#if column.javaField == firstTreeListField>
<el-table-column label="${column.columnLabel}" prop="${column.javaField}" width="160">
<#else>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="160">
</#if>
<template #default="scope">
<#if column.javaType == "LocalDateTime">
<el-date-picker
v-model="scope.row.${column.javaField}"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择${column.columnLabel}"
@change="handleSortChange(scope.row)"
/>
<#else>
<el-input-number v-model="scope.row.${column.javaField}" controls-position="right" :min="0" @change="handleSortChange(scope.row)" />
</#if>
</template>
</el-table-column>
<#elseif column.list && column.htmlType == "switch">
<#if column.javaField == firstTreeListField>
<el-table-column label="${column.columnLabel}" prop="${column.javaField}" width="120">
<#else>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="120">
</#if>
<template #default="scope">
<el-switch
v-model="scope.row.${column.javaField}"
<#if column.javaType == "Boolean">
:active-value="true"
:inactive-value="false"
<#elseif column.javaType == "Integer" || column.javaType == "Long">
:active-value="0"
:inactive-value="1"
<#else>
active-value="0"
inactive-value="1"
</#if>
disabled
/>
</template>
</el-table-column>
<#elseif column.list && column.htmlType == "datetime">
<#if column.javaField == firstTreeListField>
<el-table-column label="${column.columnLabel}" prop="${column.javaField}" width="180">
<#else>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="180">
</#if>
<template #default="scope">
<span>{{ parseTime(scope.row.${column.javaField}, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<#elseif column.list && column.htmlType == "imageUpload">
<#if column.javaField == firstTreeListField>
<el-table-column label="${column.columnLabel}" prop="${column.javaField}Url" width="100">
<#else>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}Url" width="100">
</#if>
<template #default="scope">
<image-preview :src="scope.row.${column.javaField}Url" :width="50" :height="50"/>
</template>
</el-table-column>
<#elseif column.list && column.dictColumn>
<#if column.javaField == firstTreeListField>
<el-table-column label="${column.columnLabel}" prop="${column.javaField}">
<#else>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}">
</#if>
<template #default="scope">
<#if column.htmlType == "checkbox">
<dict-tag :options="${column.dictType}" :value="scope.row.${column.javaField} ? scope.row.${column.javaField}.split(',') : []"/>
<#else>
<dict-tag :options="${column.dictType}" :value="scope.row.${column.javaField}"/>
</#if>
</template>
</el-table-column>
<#elseif column.list && "" != column.javaField>
<#if column.javaField == firstTreeListField>
<el-table-column label="${column.columnLabel}" prop="${column.javaField}" />
<#else>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" />
</#if>
</#if>
</#list>
<#if enableStatus && !statusColumn.list>
<el-table-column label="${statusColumn.columnComment}" align="center" prop="${statusField}">
<template #default="scope">
<el-switch
v-model="scope.row.${statusField}"
:active-value="${statusField}ActiveValue"
:inactive-value="${statusField}InactiveValue"
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
</#if>
<#if enableSort && !sortColumn.list>
<el-table-column label="${sortColumn.columnComment}" align="center" prop="${sortField}" width="160">
<template #default="scope">
<#if sortColumn.javaType == "LocalDateTime">
<el-date-picker
v-model="scope.row.${sortField}"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择${sortColumn.columnComment}"
@change="handleSortChange(scope.row)"
/>
<#else>
<el-input-number v-model="scope.row.${sortField}" controls-position="right" :min="0" @change="handleSortChange(scope.row)" />
</#if>
</template>
</el-table-column>
</#if>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 添加或修改${functionName}对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
<#list columns as column>
<#if (column.insert || column.edit) && !column.pk>
<#if "" != treeParentCode && column.javaField == treeParentCode>
<el-form-item label="${column.columnLabel}" prop="${treeParentCode}">
<el-tree-select
v-model="form.${treeParentCode}"
:data="${businessName}Options"
:props="{ value: '${treeCode}', label: '${treeName}', children: 'children' } as any"
value-key="${treeCode}"
placeholder="请选择${column.columnLabel}"
check-strictly
/>
</el-form-item>
<#elseif column.htmlType == "input">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input v-model="form.${column.javaField}" placeholder="请输入${column.columnLabel}" />
</el-form-item>
<#elseif column.htmlType == "inputNumber">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input-number v-model="form.${column.javaField}" controls-position="right" />
</el-form-item>
<#elseif column.htmlType == "imageUpload">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<image-upload v-model="form.${column.javaField}"/>
</el-form-item>
<#elseif column.htmlType == "fileUpload">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<file-upload v-model="form.${column.javaField}"/>
</el-form-item>
<#elseif column.htmlType == "editor">
<el-form-item label="${column.columnLabel}">
<editor v-model="form.${column.javaField}" :min-height="192"/>
</el-form-item>
<#elseif column.htmlType == "select" && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="form.${column.javaField}" placeholder="请选择${column.columnLabel}">
<el-option
v-for="dict in ${column.dictType}"
:key="dict.value"
:label="dict.label"
<#if column.javaType == "Integer" || column.javaType == "Long">
:value="parseInt(dict.value)"
<#else>
:value="dict.value"
</#if>
></el-option>
</el-select>
</el-form-item>
<#elseif column.htmlType == "select" && !(column.dictType?has_content)>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="form.${column.javaField}" placeholder="请选择${column.columnLabel}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<#elseif column.htmlType == "checkbox" && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-checkbox-group v-model="form.${column.javaField}">
<el-checkbox
v-for="dict in ${column.dictType}"
:key="dict.value"
:label="dict.value">
{{dict.label}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<#elseif column.htmlType == "checkbox" && !(column.dictType?has_content)>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-checkbox-group v-model="form.${column.javaField}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
<#elseif column.htmlType == "radio" && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-radio-group v-model="form.${column.javaField}">
<el-radio
v-for="dict in ${column.dictType}"
:key="dict.value"
<#if column.javaType == "Integer" || column.javaType == "Long">
:value="parseInt(dict.value)"
<#else>
:value="dict.value"
</#if>
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<#elseif column.htmlType == "radio" && !(column.dictType?has_content)>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-radio-group v-model="form.${column.javaField}">
<el-radio value="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
<#elseif column.htmlType == "switch">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-switch
v-model="form.${column.javaField}"
<#if column.javaType == "Boolean">
:active-value="true"
:inactive-value="false"
<#elseif column.javaType == "Integer" || column.javaType == "Long">
:active-value="0"
:inactive-value="1"
<#else>
active-value="0"
inactive-value="1"
</#if>
/>
</el-form-item>
<#elseif column.htmlType == "datetime">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-date-picker clearable
v-model="form.${column.javaField}"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择${column.columnLabel}"
/>
</el-form-item>
<#elseif column.htmlType == "textarea">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input v-model="form.${column.javaField}" type="textarea" placeholder="请输入内容" />
</el-form-item>
</#if>
</#if>
</#list>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="${BusinessName}" lang="ts">
import {
add${BusinessName},
<#if enableStatus>
change${BusinessName}Status,
</#if>
del${BusinessName},
get${BusinessName},
list${BusinessName},
<#if enableSort>
update${BusinessName}Sort,
</#if>
update${BusinessName}
} from '@/api/${moduleName}/${businessName}';
import { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
import { useLoading } from '@/hooks/async/useLoading';
import { useFormDialog } from '@/hooks/dialog/useFormDialog';
<#if needAddDateRange>
import { useDateRangeQuery } from '@/hooks/form/useDateRangeQuery';
</#if>
import { useSearchReset } from '@/hooks/form/useSearchReset';
import { useSearchToggle } from '@/hooks/form/useSearchToggle';
import { useTreeTableExpand } from '@/hooks/tree/useTreeTableExpand';
<#if needDict>
import { useDict } from '@/utils/dict';
</#if>
import modal from '@/plugins/modal';
import { handleTree } from '@/utils/ruoyi';
<#if enableExport>
import { download as requestDownload } from '@/utils/request';
</#if>
<#if needDict>
const { ${dictsNoSymbol} } = toRefs<any>(useDict(${dicts}));
</#if>
<#if enableStatus>
const ${statusField}ActiveValue = <#if statusColumn.javaType == "Boolean">true<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">0<#else>'0'</#if>;
const ${statusField}InactiveValue = <#if statusColumn.javaType == "Boolean">false<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">1<#else>'1'</#if>;
</#if>
type ${BusinessName}Option = {
${treeCode}: <#if treeParentColumn.javaType == 'String'>string<#else> number</#if>;
${treeName}: string;
children?: ${BusinessName}Option[];
};
const ${businessName}List = ref<${BusinessName}VO[]>([]);
const ${businessName}Options = ref<${BusinessName}Option[]>([]);
const all${BusinessName}Options = ref<${BusinessName}Option[]>([]);
const buttonLoading = ref(false);
const { showSearch } = useSearchToggle();
const { loading, setLoading, withLoading } = useLoading();
const queryFormRef = ref<ElFormInstance>();
const ${businessName}FormRef = ref<ElFormInstance>();
const ${businessName}TableRef = ref<ElTableInstance>();
const { isExpandAll, handleToggleExpandAll } = useTreeTableExpand<${BusinessName}VO>({
tableRef: ${businessName}TableRef,
data: ${businessName}List
});
<#list columns as column>
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
const {
dateRange: dateRange${column.capJavaField},
applyDateRange: apply${column.capJavaField}DateRange,
resetDateRange: reset${column.capJavaField}DateRange
} = useDateRangeQuery('${column.capJavaField}');
</#if>
</#list>
const initFormData: ${BusinessName}Form = {
<#list columns as column>
<#if column.insert || column.edit>
<#if column.htmlType == "checkbox">
${column.javaField}: [],
<#else>
${column.javaField}: undefined,
</#if>
</#if>
</#list>
}
const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
form: {...initFormData},
queryParams: {
<#list columns as column>
<#if column.query>
<#if column.htmlType != "datetime" || column.queryType != "BETWEEN">
${column.javaField}: undefined,
</#if>
</#if>
</#list>
params: {
<#list columns as column>
<#if column.query>
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
${column.javaField}: undefined,
</#if>
</#if>
</#list>
}
},
rules: {
<#list columns as column>
<#if column.insert || column.edit>
<#if column.required>
${column.javaField}: [
{ required: true, message: "${column.columnLabel}不能为空", trigger: <#if column.htmlType == "select" || column.htmlType == "radio" || column.htmlType == "switch" || column.htmlType == "inputNumber">"change"<#else>"blur"</#if> }
],
</#if>
</#if>
</#list>
}
});
const { queryParams, form, rules } = toRefs(data);
const { dialog, resetForm: reset, openDialog, showDialog, closeDialog } = useFormDialog({
form,
formRef: ${businessName}FormRef,
initialFormData: initFormData
});
/** 查询${functionName}列表 */
const getList = async () => {
await withLoading(async () => {
<#if needAddDateRange>
let params = queryParams.value;
<#list columns as column>
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
params = apply${column.capJavaField}DateRange(params);
</#if>
</#list>
const res = await list${BusinessName}(params);
<#else>
const res = await list${BusinessName}(queryParams.value);
</#if>
const data = handleTree<${BusinessName}VO>(res.data, '${treeCode}', '${treeParentCode}');
if (data) {
${businessName}List.value = data;
}
});
};
/** 查询${functionName}下拉树结构 */
const getTreeselect = async (excludeId?: string | number) => {
const res = await list${BusinessName}();
const data: ${BusinessName}Option = { ${treeCode}: ${treeRootValueTsLiteral}, ${treeName}: '顶级节点', children: [] };
data.children = handleTree<${BusinessName}Option>(res.data, '${treeCode}', '${treeParentCode}');
all${BusinessName}Options.value = [data];
${businessName}Options.value = excludeId != null ? filterTreeOptions(all${BusinessName}Options.value, excludeId) : all${BusinessName}Options.value;
};
/** 取消按钮 */
const cancel = () => {
reset();
closeDialog();
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
};
const { resetQuery } = useSearchReset({
queryFormRef,
queryParams,
resetExtras: () => {
<#list columns as column>
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
reset${column.capJavaField}DateRange();
</#if>
</#list>
},
afterReset: () => {
handleQuery();
}
});
/** 新增按钮操作 */
const handleAdd = (row?: Partial<${BusinessName}VO>) => {
openDialog('添加${functionName}');
getTreeselect();
if (row != null && row.${treeCode}) {
form.value.${treeParentCode} = row.${treeCode};
} else {
form.value.${treeParentCode} = ${treeRootValueTsLiteral};
}
};
/** 修改按钮操作 */
const handleUpdate = async (row: Partial<${BusinessName}VO>) => {
reset();
await getTreeselect(row.${treeCode});
if (row != null) {
form.value.${treeParentCode} = row.${treeParentCode};
}
const res = await get${BusinessName}(row.${pkColumn.javaField});
Object.assign(form.value, res.data);
<#list columns as column>
<#if column.htmlType == "checkbox">
form.value.${column.javaField} = form.value.${column.javaField}.split(",");
</#if>
</#list>
showDialog('修改${functionName}');
};
/** 提交按钮 */
const submitForm = () => {
${businessName}FormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
<#list columns as column>
<#if column.htmlType == "checkbox">
form.value.${column.javaField} = form.value.${column.javaField}.join(",");
</#if>
</#list>
if (form.value.${pkColumn.javaField}) {
await update${BusinessName}(form.value).finally(() => (buttonLoading.value = false));
} else {
await add${BusinessName}(form.value).finally(() => (buttonLoading.value = false));
}
modal.msgSuccess('操作成功');
closeDialog();
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row: Partial<${BusinessName}VO>) => {
await modal.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?');
setLoading(true);
await del${BusinessName}(row.${pkColumn.javaField}).finally(() => setLoading(false));
await getList();
modal.msgSuccess('删除成功');
};
const filterTreeOptions = (options: ${BusinessName}Option[], excludeId: string | number): ${BusinessName}Option[] => {
return options
.filter(item => item.${treeCode} !== excludeId)
.map(item => ({
...item,
children: item.children ? filterTreeOptions(item.children, excludeId) : []
}));
};
<#if enableStatus>
/** 状态修改 */
const handleStatusChange = async (row: Partial<${BusinessName}VO>) => {
const text = row.${statusField} === ${statusField}ActiveValue ? '启用' : '停用';
try {
await modal.confirm('确认要"' + text + '"吗?');
await change${BusinessName}Status(row.${pkColumn.javaField}, row.${statusField});
modal.msgSuccess(text + '成功');
} catch (err) {
row.${statusField} = row.${statusField} === ${statusField}ActiveValue ? ${statusField}InactiveValue : ${statusField}ActiveValue;
}
};
</#if>
<#if enableSort>
/** 排序调整 */
const handleSortChange = async (row: Partial<${BusinessName}VO>) => {
try {
await update${BusinessName}Sort(row.${pkColumn.javaField}, row.${sortField});
modal.msgSuccess('排序更新成功');
} catch (err) {
await getList();
}
};
</#if>
<#if enableExport>
/** 导出按钮操作 */
const handleExport = () => {
requestDownload(
'${moduleName}/${businessName}/export',
{
...queryParams.value
},
`${businessName}_${r'${new Date().getTime()}'}.xlsx`
);
};
</#if>
onMounted(() => {
getList();
});
</script>

406
gen/index.tsx.ftl Normal file
View File

@ -0,0 +1,406 @@
import { DeleteOutlined<#if enableExport>, DownloadOutlined</#if>, EditOutlined, PlusOutlined } from '@ant-design/icons';
import {
ModalForm,
PageContainer,
<#if needCheckbox>
ProFormCheckbox,
</#if>
<#if needDateField>
ProFormDateTimePicker,
</#if>
<#if needDigit>
ProFormDigit,
</#if>
<#if needSelect>
ProFormSelect,
</#if>
<#if needTextArea>
ProFormTextArea,
</#if>
ProFormText,
ProTable,
type ActionType,
type ProColumns
} from '@ant-design/pro-components';
import { useBoolean } from 'ahooks';
import { Button, Form, message, Popconfirm<#if enableStatus || needSwitchField>, Switch</#if><#if enableSort>, InputNumber</#if> } from 'antd';
import { useRef, useState } from 'react';
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
import {
add${BusinessName},
<#if enableStatus>
change${BusinessName}Status,
</#if>
del${BusinessName},
get${BusinessName},
list${BusinessName},
<#if enableSort>
update${BusinessName}Sort,
</#if>
update${BusinessName}
} from '@/api/${moduleName}/${businessName}';
<#if needDict>
import DictTag from '@/components/common/DictTag';
</#if>
<#if needFileUpload>
import FileUpload from '@/components/common/FileUpload';
</#if>
<#if needImagePreview>
import ImagePreview from '@/components/common/ImagePreview';
</#if>
<#if needImageUpload>
import ImageUpload from '@/components/common/ImageUpload';
</#if>
<#if needEditor>
import RichTextEditor from '@/components/common/RichTextEditor';
</#if>
import RowActions from '@/components/common/RowActions';
<#if needDict>
import { useDict } from '@/hooks/useDict';
</#if>
<#if needDateRange>
import { useDateRangeQuery } from '@/hooks/useDateRangeQuery';
</#if>
<#if enableExport>
import { useTableExport } from '@/hooks/useTableExport';
</#if>
import { useTableScroll } from '@/hooks/useTableScroll';
import { useTableSelection } from '@/hooks/useTableSelection';
import { useUserStore } from '@/stores/userStore';
<#if needDict>
import { dictOptions } from '@/utils/dict';
</#if>
<#if enableStatus>
import { confirmAction } from '@/utils/modal';
</#if>
import { hasPermi } from '@/utils/permission';
import { <#if needDateField>formatDateTimeFields, toDayjsFields, </#if> toPageQuery, toTableData } from '@/utils/ruoyi';
const default${BusinessName}Form: ${BusinessName}Form = {
<#list columns as column>
<#if (column.insert || column.edit) && !column.pk && column.htmlType == "checkbox">
${column.javaField}: [],
</#if>
</#list>
};
<#if enableStatus>
const ${statusField}ActiveValue = <#if statusColumn.javaType == "Boolean">true<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">0<#else>'0'</#if>;
const ${statusField}InactiveValue = <#if statusColumn.javaType == "Boolean">false<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">1<#else>'1'</#if>;
</#if>
export default function ${BusinessName}Page() {
const actionRef = useRef<ActionType | undefined>(undefined);
const { tableScroll } = useTableScroll(1000);
const [form] = Form.useForm<${BusinessName}Form>();
const userInfo = useUserStore(state => state.userInfo);
<#if needDict>
const dicts = useDict(${dicts});
</#if>
const { ids, selectedOne, handleSelectionChange, clearSelection } = useTableSelection<${BusinessName}VO>(
row => row.${pkColumn.javaField}
);
const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
const [modalTitle, setModalTitle] = useState('');
<#if enableExport>
const { updateExportParams, exportFile } = useTableExport();
</#if>
<#list columns as column>
<#if column.query && column.htmlType == "datetime" && column.queryType == "BETWEEN">
const { applyDateRange: apply${column.capJavaField}DateRange } = useDateRangeQuery('${column.capJavaField}');
</#if>
</#list>
const canAdd = hasPermi(userInfo, ['${permissionPrefix}:add']);
const canEdit = hasPermi(userInfo, ['${permissionPrefix}:edit']);
const canRemove = hasPermi(userInfo, ['${permissionPrefix}:remove']);
<#if enableExport>
const canExport = hasPermi(userInfo, ['${permissionPrefix}:export']);
</#if>
const openAdd = () => {
form.resetFields();
form.setFieldsValue(default${BusinessName}Form);
setModalTitle('添加${functionName}');
openModal();
};
const openEdit = async (row?: ${BusinessName}VO) => {
const target = row || selectedOne;
if (!target?.${pkColumn.javaField}) return;
const res = await get${BusinessName}(target.${pkColumn.javaField});
const data = <#if needDateField>toDayjsFields({ ...res.data }, [
<#list columns as column>
<#if (column.insert || column.edit) && column.htmlType == "datetime">
'${column.javaField}',
</#if>
</#list>
])<#else>{ ...res.data }</#if>;
<#list columns as column>
<#if (column.insert || column.edit) && column.htmlType == "checkbox">
if (typeof data.${column.javaField} === 'string') {
data.${column.javaField} = data.${column.javaField}.split(',');
}
</#if>
</#list>
form.resetFields();
form.setFieldsValue(data);
setModalTitle('修改${functionName}');
openModal();
};
const submitForm = async (values: ${BusinessName}Form) => {
const submitValues = <#if needDateField>formatDateTimeFields({ ...values }, [
<#list columns as column>
<#if (column.insert || column.edit) && column.htmlType == "datetime">
'${column.javaField}',
</#if>
</#list>
])<#else>{ ...values }</#if>;
<#list columns as column>
<#if (column.insert || column.edit) && column.htmlType == "checkbox">
if (Array.isArray(submitValues.${column.javaField})) {
submitValues.${column.javaField} = submitValues.${column.javaField}.join(',');
}
</#if>
</#list>
submitValues.${pkColumn.javaField} ? await update${BusinessName}(submitValues) : await add${BusinessName}(submitValues);
message.success('操作成功');
form.resetFields();
actionRef.current?.reload();
return true;
};
const remove = async (row?: ${BusinessName}VO) => {
await del${BusinessName}(row?.${pkColumn.javaField} || ids);
message.success('删除成功');
clearSelection();
actionRef.current?.reloadAndRest?.();
};
<#if enableStatus>
const handleStatusChange = async (row: ${BusinessName}VO, checked: boolean) => {
const previousStatus = row.${statusField};
const status = checked ? ${statusField}ActiveValue : ${statusField}InactiveValue;
const text = checked ? '启用' : '停用';
try {
await confirmAction(`确认要"${r'${text}'}"吗?`);
await change${BusinessName}Status(row.${pkColumn.javaField}, status);
message.success(`${r'${text}'}成功`);
actionRef.current?.reload();
} catch {
row.${statusField} = previousStatus;
actionRef.current?.reload();
}
};
</#if>
<#if enableSort>
const handleSortChange = async (row: ${BusinessName}VO, value?: number | null) => {
await update${BusinessName}Sort(row.${pkColumn.javaField}, value || 0);
message.success('排序更新成功');
actionRef.current?.reload();
};
</#if>
const columns: ProColumns<${BusinessName}VO>[] = [
<#list columns as column>
<#if column.list>
<#if enableStatus && statusField == column.javaField>
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}',
valueType: 'select',
width: 100,
<#if column.dictType?has_content>
fieldProps: { options: dictOptions(dicts.${column.dictType}) },
</#if>
render: (_, row) => (
<Switch
checked={row.${column.javaField} === ${statusField}ActiveValue}
disabled={!canEdit}
onChange={checked => handleStatusChange(row, checked)}
/>
)
},
<#elseif enableSort && sortField == column.javaField>
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}',
search: false,
width: 130,
render: (_, row) => <InputNumber min={0} value={row.${column.javaField} as number} onChange={value => handleSortChange(row, value)} />
},
<#elseif column.htmlType == "datetime">
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}', valueType: 'dateTime', search: <#if column.query && column.queryType != "BETWEEN">true<#else> false</#if>, width: 170 },
<#elseif column.htmlType == "imageUpload">
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}Url',
search: false,
width: 100,
render: (_, row) => <ImagePreview src={row.${column.javaField}Url || String(row.${column.javaField} || '')} width={50} height={50} />
},
<#elseif column.dictColumn>
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}',
valueType: 'select',
fieldProps: { options: dictOptions(dicts.${column.dictType}) },
render: (_, row) => <DictTag options={dicts.${column.dictType}} value={row.${column.javaField}} />
},
<#elseif column.htmlType == "switch">
{
title: '${column.columnLabel}',
dataIndex: '${column.javaField}',
valueType: 'select',
width: 100,
render: (_, row) => <Switch checked={row.${column.javaField} === ${column.switchActiveValue}} disabled />
},
<#else>
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}'<#if !column.query>, search: false</#if> },
</#if>
</#if>
<#if column.query && column.htmlType == "datetime" && column.queryType == "BETWEEN">
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}Range', valueType: 'dateTimeRange', hideInTable: true },
</#if>
</#list>
{
title: '操作',
valueType: 'option',
width: 92,
fixed: 'right',
render: (_, row) => (
<RowActions
actions={[
canEdit && { key: 'edit', label: '修改', icon: <EditOutlined />, onClick: () => openEdit(row) },
canRemove && {
key: 'delete',
label: '删除',
icon: <DeleteOutlined />,
danger: true,
confirm: `是否确认删除${functionName}编号为"${r'${row.'}${pkColumn.javaField}${r'}'}"的数据项?`,
onClick: () => remove(row)
}
]}
/>
)
}
];
return (
<PageContainer title="${functionName}">
<ProTable<${BusinessName}VO, ${BusinessName}Query<#if needDateRange> & Record<string, unknown></#if>>
actionRef={actionRef}
rowKey="${pkColumn.javaField}"
columns={columns}
scroll={tableScroll}
search={{ labelWidth: 90 }}
pagination={{ defaultPageSize: 10, showSizeChanger: true }}
rowSelection={{ selectedRowKeys: ids, onChange: handleSelectionChange }}
request={async params => {
<#if needDateRange>
let query = toPageQuery(params);
<#list columns as column>
<#if column.query && column.htmlType == "datetime" && column.queryType == "BETWEEN">
query = apply${column.capJavaField}DateRange(query, params.${column.javaField}Range as [string, string] | undefined);
delete query.${column.javaField}Range;
</#if>
</#list>
<#else>
const query = toPageQuery(params);
</#if>
<#if enableExport>
updateExportParams(query);
</#if>
const res = await list${BusinessName}(query);
return toTableData(res);
}}
toolbar={{ title: '${functionName}列表' }}
toolBarRender={() => [
canAdd && (
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={openAdd}>
新增
</Button>
),
canEdit && (
<Button key="edit" disabled={!selectedOne} icon={<EditOutlined />} onClick={() => openEdit()}>
修改
</Button>
),
canRemove && (
<Popconfirm key="delete" title={`是否确认删除${functionName}编号为"${r'${ids}'}"的数据项?`} onConfirm={() => remove()}>
<Button danger disabled={!ids.length} icon={<DeleteOutlined />}>
删除
</Button>
</Popconfirm>
)<#if enableExport>,
canExport && (
<Button
key="export"
icon={<DownloadOutlined />}
onClick={() => exportFile('/${moduleName}/${businessName}/export', () => `${businessName}_${r'${Date.now()}'}.xlsx`)}
>
导出
</Button>
)</#if>
]}
/>
<ModalForm<${BusinessName}Form>
title={modalTitle}
open={modalOpen}
width={560}
form={form}
layout="vertical"
initialValues={default${BusinessName}Form}
modalProps={{ destroyOnHidden: true, onCancel: closeModal }}
onOpenChange={open => !open && closeModal()}
onFinish={submitForm}
>
<ProFormText name="${pkColumn.javaField}" hidden />
<#list columns as column>
<#if (column.insert || column.edit) && !column.pk>
<#if column.htmlType == "input">
<ProFormText name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "textarea">
<ProFormTextArea name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "inputNumber">
<ProFormDigit name="${column.javaField}" label="${column.columnLabel}" min={0} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif (column.htmlType == "select" || column.htmlType == "radio" || column.htmlType == "switch") && column.dictType?has_content>
<ProFormSelect name="${column.javaField}" label="${column.columnLabel}" options={dictOptions(dicts.${column.dictType})} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "checkbox" && column.dictType?has_content>
<ProFormCheckbox.Group name="${column.javaField}" label="${column.columnLabel}" options={dictOptions(dicts.${column.dictType})} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "switch">
<Form.Item
name="${column.javaField}"
label="${column.columnLabel}"
valuePropName="checked"
getValueProps={value => ({ checked: value === ${column.switchActiveValue} })}
normalize={checked => (checked ? ${column.switchActiveValue} : ${column.switchInactiveValue})}
>
<Switch />
</Form.Item>
<#elseif column.htmlType == "datetime">
<ProFormDateTimePicker name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
<#elseif column.htmlType == "imageUpload">
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
<ImageUpload />
</Form.Item>
<#elseif column.htmlType == "fileUpload">
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
<FileUpload />
</Form.Item>
<#elseif column.htmlType == "editor">
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
<RichTextEditor />
</Form.Item>
<#else>
<ProFormText name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
</#if>
</#if>
</#list>
</ModalForm>
</PageContainer>
);
}

View File

@ -1,615 +0,0 @@
<template>
<div class="p-2 page-shell ${moduleName}-${businessName}-page">
<div class="search-wrap">
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
<template #header>
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
<div><h3>筛选条件</h3></div>
</div>
</template>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
<#list columns as column>
<#if column.query>
<#if column.htmlType == "input" || column.htmlType == "textarea">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input v-model="queryParams.${column.javaField}" placeholder="请输入${column.columnLabel}" clearable @keyup.enter="handleQuery" />
</el-form-item>
<#elseif column.htmlType == "inputNumber">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input-number v-model="queryParams.${column.javaField}" controls-position="right" />
</el-form-item>
<#elseif (column.htmlType == "select" || column.htmlType == "radio") && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable >
<el-option v-for="dict in ${column.dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<#elseif column.htmlType == "switch" && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable >
<el-option v-for="dict in ${column.dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
</el-select>
</el-form-item>
<#elseif column.htmlType == "switch">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable >
<#if column.javaType == "Boolean">
<el-option label="是" :value="true" />
<el-option label="否" :value="false" />
<#elseif column.javaType == "Integer" || column.javaType == "Long">
<el-option label="开启" :value="0" />
<el-option label="关闭" :value="1" />
<#else>
<el-option label="开启" value="0" />
<el-option label="关闭" value="1" />
</#if>
</el-select>
</el-form-item>
<#elseif (column.htmlType == "select" || column.htmlType == "radio") && !(column.dictType?has_content)>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable >
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<#elseif column.htmlType == "datetime" && column.queryType != "BETWEEN">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-date-picker clearable
v-model="queryParams.${column.javaField}"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择${column.columnLabel}"
/>
</el-form-item>
<#elseif column.htmlType == "datetime" && column.queryType == "BETWEEN" && column.query>
<el-form-item label="${column.columnLabel}" style="width: 308px">
<el-date-picker
v-model="dateRange${column.capJavaField}"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
/>
</el-form-item>
</#if>
</#if>
</#list>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<el-card shadow="hover" class="table-panel">
<template #header>
<div class="toolbar-shell">
<div class="table-heading">
<h3>${functionName}列表</h3>
</div>
<div class="toolbar-actions">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
<#if enableExport>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['${moduleName}:${businessName}:export']">导出</el-button>
</#if>
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
</div>
</div>
</template>
<el-table v-loading="loading" border class="data-table" :data="${businessName}List" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<#list columns as column>
<#if column.pk && column.list>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" />
<#elseif enableStatus && statusField == column.javaField>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}">
<template #default="scope">
<el-switch
v-model="scope.row.${column.javaField}"
:active-value="${statusField}ActiveValue"
:inactive-value="${statusField}InactiveValue"
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
<#elseif enableSort && sortField == column.javaField>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="160">
<template #default="scope">
<#if column.javaType == "LocalDateTime">
<el-date-picker
v-model="scope.row.${column.javaField}"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择${column.columnLabel}"
@change="handleSortChange(scope.row)"
/>
<#else>
<el-input-number v-model="scope.row.${column.javaField}" controls-position="right" :min="0" @change="handleSortChange(scope.row)" />
</#if>
</template>
</el-table-column>
<#elseif column.list && column.htmlType == "switch">
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="120">
<template #default="scope">
<el-switch
v-model="scope.row.${column.javaField}"
<#if column.javaType == "Boolean">
:active-value="true"
:inactive-value="false"
<#elseif column.javaType == "Integer" || column.javaType == "Long">
:active-value="0"
:inactive-value="1"
<#else>
active-value="0"
inactive-value="1"
</#if>
disabled
/>
</template>
</el-table-column>
<#elseif column.list && column.htmlType == "datetime">
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.${column.javaField}, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<#elseif column.list && column.htmlType == "imageUpload">
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}Url" width="100">
<template #default="scope">
<image-preview :src="scope.row.${column.javaField}Url" :width="50" :height="50"/>
</template>
</el-table-column>
<#elseif column.list && column.dictColumn>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}">
<template #default="scope">
<#if column.htmlType == "checkbox">
<dict-tag :options="${column.dictType}" :value="scope.row.${column.javaField} ? scope.row.${column.javaField}.split(',') : []"/>
<#else>
<dict-tag :options="${column.dictType}" :value="scope.row.${column.javaField}"/>
</#if>
</template>
</el-table-column>
<#elseif column.list && "" != column.javaField>
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" />
</#if>
</#list>
<#if enableStatus && !statusColumn.list>
<el-table-column label="${statusColumn.columnComment}" align="center" prop="${statusField}">
<template #default="scope">
<el-switch
v-model="scope.row.${statusField}"
<#if statusColumn.javaType == "Boolean">
:active-value="true"
:inactive-value="false"
<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">
:active-value="0"
:inactive-value="1"
<#else>
active-value="0"
inactive-value="1"
</#if>
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
</#if>
<#if enableSort && !sortColumn.list>
<el-table-column label="${sortColumn.columnComment}" align="center" prop="${sortField}" width="160">
<template #default="scope">
<#if sortColumn.javaType == "LocalDateTime">
<el-date-picker
v-model="scope.row.${sortField}"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择${sortColumn.columnComment}"
@change="handleSortChange(scope.row)"
/>
<#else>
<el-input-number v-model="scope.row.${sortField}" controls-position="right" :min="0" @change="handleSortChange(scope.row)" />
</#if>
</template>
</el-table-column>
</#if>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改${functionName}对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
<#list columns as column>
<#if (column.insert || column.edit) && !column.pk>
<#if column.htmlType == "input">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input v-model="form.${column.javaField}" placeholder="请输入${column.columnLabel}" />
</el-form-item>
<#elseif column.htmlType == "inputNumber">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input-number v-model="form.${column.javaField}" controls-position="right" />
</el-form-item>
<#elseif column.htmlType == "imageUpload">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<image-upload v-model="form.${column.javaField}"/>
</el-form-item>
<#elseif column.htmlType == "fileUpload">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<file-upload v-model="form.${column.javaField}"/>
</el-form-item>
<#elseif column.htmlType == "editor">
<el-form-item label="${column.columnLabel}">
<editor v-model="form.${column.javaField}" :min-height="192"/>
</el-form-item>
<#elseif column.htmlType == "select" && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="form.${column.javaField}" placeholder="请选择${column.columnLabel}">
<el-option
v-for="dict in ${column.dictType}"
:key="dict.value"
:label="dict.label"
<#if column.javaType == "Integer" || column.javaType == "Long">
:value="parseInt(dict.value)"
<#else>
:value="dict.value"
</#if>
></el-option>
</el-select>
</el-form-item>
<#elseif column.htmlType == "select" && !(column.dictType?has_content)>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-select v-model="form.${column.javaField}" placeholder="请选择${column.columnLabel}">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<#elseif column.htmlType == "checkbox" && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-checkbox-group v-model="form.${column.javaField}">
<el-checkbox
v-for="dict in ${column.dictType}"
:key="dict.value"
:label="dict.value">
{{dict.label}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<#elseif column.htmlType == "checkbox" && !(column.dictType?has_content)>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-checkbox-group v-model="form.${column.javaField}">
<el-checkbox>请选择字典生成</el-checkbox>
</el-checkbox-group>
</el-form-item>
<#elseif column.htmlType == "radio" && column.dictType?has_content>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-radio-group v-model="form.${column.javaField}">
<el-radio
v-for="dict in ${column.dictType}"
:key="dict.value"
<#if column.javaType == "Integer" || column.javaType == "Long">
:value="parseInt(dict.value)"
<#else>
:value="dict.value"
</#if>
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<#elseif column.htmlType == "radio" && !(column.dictType?has_content)>
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-radio-group v-model="form.${column.javaField}">
<el-radio value="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
<#elseif column.htmlType == "switch">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-switch
v-model="form.${column.javaField}"
<#if column.javaType == "Boolean">
:active-value="true"
:inactive-value="false"
<#elseif column.javaType == "Integer" || column.javaType == "Long">
:active-value="0"
:inactive-value="1"
<#else>
active-value="0"
inactive-value="1"
</#if>
/>
</el-form-item>
<#elseif column.htmlType == "datetime">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-date-picker clearable
v-model="form.${column.javaField}"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择${column.columnLabel}">
</el-date-picker>
</el-form-item>
<#elseif column.htmlType == "textarea">
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
<el-input v-model="form.${column.javaField}" type="textarea" placeholder="请输入内容" />
</el-form-item>
</#if>
</#if>
</#list>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="${BusinessName}" lang="ts">
import {
add${BusinessName},
<#if enableStatus>
change${BusinessName}Status,
</#if>
del${BusinessName},
get${BusinessName},
list${BusinessName},
<#if enableSort>
update${BusinessName}Sort,
</#if>
update${BusinessName}
} from '@/api/${moduleName}/${businessName}';
import { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
import { useLoading } from '@/hooks/async/useLoading';
import { useFormDialog } from '@/hooks/dialog/useFormDialog';
<#if needAddDateRange>
import { useDateRangeQuery } from '@/hooks/form/useDateRangeQuery';
</#if>
import { useSearchReset } from '@/hooks/form/useSearchReset';
import { useSearchToggle } from '@/hooks/form/useSearchToggle';
import { useTableSelection } from '@/hooks/table/useTableSelection';
<#if needDict>
import { useDict } from '@/utils/dict';
</#if>
<#if needParseTime>
import { parseTime } from '@/utils/ruoyi';
</#if>
import modal from '@/plugins/modal';
<#if enableExport>
import { download as requestDownload } from '@/utils/request';
</#if>
<#if needDict>
const { ${dictsNoSymbol} } = toRefs<any>(useDict(${dicts}));
</#if>
<#if enableStatus>
const ${statusField}ActiveValue = <#if statusColumn.javaType == "Boolean">true<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">0<#else>'0'</#if>;
const ${statusField}InactiveValue = <#if statusColumn.javaType == "Boolean">false<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">1<#else>'1'</#if>;
</#if>
const ${businessName}List = ref<${BusinessName}VO[]>([]);
const buttonLoading = ref(false);
const { loading, withLoading } = useLoading(true);
const { showSearch } = useSearchToggle();
const total = ref(0);
<#list columns as column>
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN" && column.query>
const {
dateRange: dateRange${column.capJavaField},
applyDateRange: apply${column.capJavaField}DateRange,
resetDateRange: reset${column.capJavaField}DateRange
} = useDateRangeQuery('${column.capJavaField}');
</#if>
</#list>
const queryFormRef = ref<ElFormInstance>();
const ${businessName}FormRef = ref<ElFormInstance>();
const initFormData: ${BusinessName}Form = {
<#list columns as column>
<#if column.insert || column.edit>
<#if column.htmlType == "checkbox">
${column.javaField}: [],
<#else>
${column.javaField}: undefined,
</#if>
</#if>
</#list>
}
const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
<#list columns as column>
<#if column.query>
<#if column.htmlType != "datetime" || column.queryType != "BETWEEN">
${column.javaField}: undefined,
</#if>
</#if>
</#list>
params: {
<#list columns as column>
<#if column.query>
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
${column.javaField}: undefined,
</#if>
</#if>
</#list>
}
},
rules: {
<#list columns as column>
<#if column.insert || column.edit>
<#if column.required>
${column.javaField}: [
{ required: true, message: "${column.columnLabel}不能为空", trigger: <#if column.htmlType == "select" || column.htmlType == "radio" || column.htmlType == "switch" || column.htmlType == "inputNumber">"change"<#else>"blur"</#if> }
],
</#if>
</#if>
</#list>
}
});
const { queryParams, form, rules } = toRefs(data);
const { ids, single, multiple, handleSelectionChange } = useTableSelection<${BusinessName}VO>(item => item.${pkColumn.javaField});
const { dialog, resetForm: reset, openDialog, showDialog, closeDialog } = useFormDialog({
form,
formRef: ${businessName}FormRef,
initialFormData: initFormData
});
/** 查询${functionName}列表 */
const getList = async () => {
await withLoading(async () => {
<#if needAddDateRange>
let params = queryParams.value;
<#list columns as column>
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN" && column.query>
params = apply${column.capJavaField}DateRange(params);
</#if>
</#list>
const res = await list${BusinessName}(params);
<#else>
const res = await list${BusinessName}(queryParams.value);
</#if>
${businessName}List.value = res.data?.rows;
total.value = res.data?.total;
});
};
/** 取消按钮 */
const cancel = () => {
reset();
closeDialog();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
const { resetQuery } = useSearchReset({
queryFormRef,
queryParams,
pageNumKey: 'pageNum',
pageSizeKey: 'pageSize',
initialPageSize: 10,
resetExtras: () => {
<#list columns as column>
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN" && column.query>
reset${column.capJavaField}DateRange();
</#if>
</#list>
},
afterReset: () => {
handleQuery();
}
});
/** 新增按钮操作 */
const handleAdd = () => {
openDialog('添加${functionName}');
};
/** 修改按钮操作 */
const handleUpdate = async (row?: Partial<${BusinessName}VO>) => {
reset();
const _${pkColumn.javaField} = row?.${pkColumn.javaField} || ids.value[0];
const res = await get${BusinessName}(_${pkColumn.javaField});
Object.assign(form.value, res.data);
<#list columns as column>
<#if column.htmlType == "checkbox">
form.value.${column.javaField} = form.value.${column.javaField}.split(",");
</#if>
</#list>
showDialog('修改${functionName}');
};
/** 提交按钮 */
const submitForm = () => {
${businessName}FormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
<#list columns as column>
<#if column.htmlType == "checkbox">
form.value.${column.javaField} = form.value.${column.javaField}.join(",");
</#if>
</#list>
if (form.value.${pkColumn.javaField}) {
await update${BusinessName}(form.value).finally(() => (buttonLoading.value = false));
} else {
await add${BusinessName}(form.value).finally(() => (buttonLoading.value = false));
}
modal.msgSuccess('操作成功');
closeDialog();
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: Partial<${BusinessName}VO>) => {
const _${pkColumn.javaField}s = row?.${pkColumn.javaField} || ids.value;
await modal.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?');
await del${BusinessName}(_${pkColumn.javaField}s);
modal.msgSuccess('删除成功');
await getList();
};
<#if enableExport>
/** 导出按钮操作 */
const handleExport = () => {
requestDownload(
'${moduleName}/${businessName}/export',
{
...queryParams.value
},
`${businessName}_${r'${new Date().getTime()}'}.xlsx`
);
};
</#if>
<#if enableStatus>
/** 状态修改 */
const handleStatusChange = async (row: Partial<${BusinessName}VO>) => {
const text = row.${statusField} === ${statusField}ActiveValue ? '启用' : '停用';
try {
await modal.confirm('确认要"' + text + '"吗?');
await change${BusinessName}Status(row.${pkColumn.javaField}, row.${statusField});
modal.msgSuccess(text + '成功');
} catch (err) {
row.${statusField} = row.${statusField} === ${statusField}ActiveValue ? ${statusField}InactiveValue : ${statusField}ActiveValue;
}
};
</#if>
<#if enableSort>
/** 排序调整 */
const handleSortChange = async (row: Partial<${BusinessName}VO>) => {
try {
await update${BusinessName}Sort(row.${pkColumn.javaField}, row.${sortField});
modal.msgSuccess('排序更新成功');
} catch (err) {
await getList();
}
};
</#if>
onMounted(() => {
getList();
});
</script>

View File

@ -1,6 +1,8 @@
import type { BaseEntity<#if !table.tree>, PageQuery</#if> } from '@/api/types';
export interface ${BusinessName}VO { export interface ${BusinessName}VO {
<#list columns as column> <#list columns as column>
<#if column.list> <#if column.list || column.pk>
/** /**
* ${column.columnComment} * ${column.columnComment}
*/ */
@ -9,7 +11,7 @@ export interface ${BusinessName}VO {
/** /**
* ${column.columnComment}Url * ${column.columnComment}Url
*/ */
${column.javaField}Url: string; ${column.javaField}Url?: string;
</#if> </#if>
</#if> </#if>
</#list> </#list>
@ -17,18 +19,22 @@ export interface ${BusinessName}VO {
/** /**
* 子对象 * 子对象
*/ */
children: ${BusinessName}VO[]; children?: ${BusinessName}VO[];
</#if> </#if>
} }
export interface ${BusinessName}Form extends BaseEntity { export interface ${BusinessName}Form extends BaseEntity {
<#list columns as column> <#list columns as column>
<#if column.insert || column.edit> <#if column.insert || column.edit || column.pk>
/** /**
* ${column.columnComment} * ${column.columnComment}
*/ */
<#if column.htmlType == "checkbox">
${column.javaField}?: string | string[];
<#else>
${column.javaField}?: ${column.tsType}; ${column.javaField}?: ${column.tsType};
</#if> </#if>
</#if>
</#list> </#list>
} }
@ -44,5 +50,5 @@ export interface ${BusinessName}Query<#if !table.tree> extends PageQuery</#if> {
/** /**
* 日期范围参数 * 日期范围参数
*/ */
params?: any; params?: Record<string, unknown>;
} }

View File

@ -1,214 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link rel="icon" href="/favicon.ico" />
<title>%VITE_APP_TITLE%</title>
<!--[if lt IE 11]>
<script>
window.location.href = '/ie.html';
</script>
<![endif]-->
<style>
html,
body,
#app {
height: 100%;
margin: 0px;
padding: 0px;
}
.chromeframe {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999999;
}
#loader {
display: block;
position: relative;
left: 50%;
top: 50%;
width: 150px;
height: 150px;
margin: -75px 0 0 -75px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #fff;
-webkit-animation: spin 2s linear infinite;
-ms-animation: spin 2s linear infinite;
-moz-animation: spin 2s linear infinite;
-o-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
z-index: 1001;
}
#loader:before {
content: '';
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #fff;
-webkit-animation: spin 3s linear infinite;
-moz-animation: spin 3s linear infinite;
-o-animation: spin 3s linear infinite;
-ms-animation: spin 3s linear infinite;
animation: spin 3s linear infinite;
}
#loader:after {
content: '';
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #fff;
-moz-animation: spin 1.5s linear infinite;
-o-animation: spin 1.5s linear infinite;
-ms-animation: spin 1.5s linear infinite;
-webkit-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite;
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
#loader-wrapper .loader-section {
position: fixed;
top: 0;
width: 51%;
height: 100%;
background: #7171c6;
z-index: 1000;
-webkit-transform: translateX(0);
-ms-transform: translateX(0);
transform: translateX(0);
}
#loader-wrapper .loader-section.section-left {
left: 0;
}
#loader-wrapper .loader-section.section-right {
right: 0;
}
.loaded #loader-wrapper .loader-section.section-left {
-webkit-transform: translateX(-100%);
-ms-transform: translateX(-100%);
transform: translateX(-100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.loaded #loader-wrapper .loader-section.section-right {
-webkit-transform: translateX(100%);
-ms-transform: translateX(100%);
transform: translateX(100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.loaded #loader {
opacity: 0;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
}
.loaded #loader-wrapper {
visibility: hidden;
-webkit-transform: translateY(-100%);
-ms-transform: translateY(-100%);
transform: translateY(-100%);
-webkit-transition: all 0.3s 1s ease-out;
transition: all 0.3s 1s ease-out;
}
.no-js #loader-wrapper {
display: none;
}
.no-js h1 {
color: #222222;
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #fff;
font-size: 19px;
width: 100%;
text-align: center;
z-index: 9999999999999;
position: absolute;
top: 60%;
opacity: 1;
line-height: 30px;
}
#loader-wrapper .load_title span {
font-weight: normal;
font-style: italic;
font-size: 13px;
color: #fff;
opacity: 0.5;
}
</style>
</head>
<body>
<div id="app">
<div id="loader-wrapper">
<div id="loader"></div>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div>
<div class="load_title">正在加载系统资源,请耐心等待</div>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,80 +1,65 @@
{ {
"$schema": "https://json.schemastore.org/package", "name": "plus-ui-react",
"name": "ruoyi-vue-plus",
"version": "6.0.0", "version": "6.0.0",
"description": "RuoYi-Vue-Plus后台管理系统", "private": true,
"license": "MIT", "description": "React + Ant Design version of RuoYi-Vue-Plus UI",
"author": "LionLi",
"repository": {
"type": "git",
"url": "https://gitee.com/JavaLionLi/plus-ui.git"
},
"packageManager": "pnpm@10.34.5",
"type": "module", "type": "module",
"packageManager": "pnpm@10.34.5",
"scripts": { "scripts": {
"build": "vite build --mode production", "build": "pnpm build:prod",
"build:dev": "vite build --mode development", "build:dev": "cross-env UMI_ENV=development BABEL_POLYFILL=none max build",
"build:prod": "vite build --mode production", "build:prod": "cross-env UMI_ENV=production BABEL_POLYFILL=none max build",
"dev": "vite serve --mode development", "dev": "cross-env UMI_ENV=development MOCK=none BABEL_POLYFILL=none max dev",
"fmt": "oxfmt .", "fmt": "oxfmt .",
"lint": "oxlint src", "lint": "oxlint src config vite.config.ts && cross-env UMI_ENV=development BABEL_POLYFILL=none max setup && tsc --noEmit",
"lint:fix": "oxlint --fix src", "lint:fix": "oxlint --fix src config vite.config.ts",
"preview": "vite preview" "openapi": "max openapi",
"preview": "max preview --port 8000",
"start": "cross-env UMI_ENV=development MOCK=none BABEL_POLYFILL=none max dev"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "2.3.2", "@ant-design/icons": "^6.3.2",
"@highlightjs/vue-plugin": "2.1.2", "@ant-design/pro-components": "3.1.12-0",
"@iconify/vue": "^5.0.1", "@ant-design/x": "^2.8.0",
"@noble/ciphers": "^2.4.0", "@iconify/react": "^6.0.2",
"@vueuse/core": "14.4.0", "@tanstack/react-query": "^5.101.4",
"@wangeditor-next/editor": "6.4.1", "@umijs/max": "^4.6.82",
"@wangeditor-next/editor-for-vue": "6.4.1", "@wangeditor-next/editor": "^5.7.16",
"animate.css": "4.1.1", "@wangeditor-next/editor-for-react": "^2.0.3",
"await-to-js": "3.0.0", "ahooks": "^3.9.7",
"axios": "1.20.0", "antd": "^6.5.2",
"echarts": "6.1.0", "antd-style": "^4.1.0",
"element-plus": "2.14.5", "axios": "1.18.1",
"highlight.js": "11.12.0", "browser-image-compression": "^2.0.2",
"image-conversion": "2.1.1", "clsx": "^2.1.1",
"crypto-js": "4.2.0",
"dayjs": "^1.11.21",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"jsencrypt": "3.5.4", "jsencrypt": "3.5.4",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "4.0.3", "react": "^19.2.8",
"vue": "3.5.42", "react-dom": "^19.2.8",
"vue-cropper": "1.1.4", "react-easy-crop": "^6.2.3",
"vue-i18n": "11.4.10", "react-json-view-lite": "^2.5.0",
"vue-json-pretty": "2.6.0", "react-syntax-highlighter": "^16.1.1",
"vue-router": "5.3.1", "screenfull": "^6.0.2",
"vue-types": "7.0.0", "zustand": "^5.0.14"
"vxe-pc-ui": "4.18.9",
"vxe-table": "4.21.9"
}, },
"devDependencies": { "devDependencies": {
"@types/crypto-js": "4.2.2",
"@types/node": "^26.1.1", "@types/node": "^26.1.1",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@unocss/preset-attributify": "66.10.1", "@types/react": "^19.2.17",
"@unocss/preset-wind3": "66.10.1", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-vue": "^6.0.8", "@types/react-syntax-highlighter": "^15.5.13",
"@vue/compiler-sfc": "3.5.42", "@umijs/lint": "^4.6.82",
"autoprefixer": "10.5.5", "@umijs/max-plugin-openapi": "^2.0.3",
"oxfmt": "^0.67.0", "cross-env": "^10.1.0",
"oxlint": "^1.82.0", "oxfmt": "^0.60.0",
"sass": "1.104.0", "oxlint": "^1.75.0",
"typescript": "^6.0.3", "typescript": "^6.0.3"
"unocss": "66.10.1",
"unplugin-auto-import": "21.1.0",
"unplugin-vue-components": "32.1.0",
"unplugin-vue-setup-extend-plus": "1.0.1",
"vite": "^8.2.2",
"vite-plugin-svg-icons-ng": "^1.9.3",
"vitest": "4.1.11",
"vue-tsc": "^3.3.11"
}, },
"browserslist": [
"Chrome >= 87",
"Edge >= 88",
"Safari >= 14",
"Firefox >= 78"
],
"engines": { "engines": {
"node": ">=20.19.0", "node": ">=20.19.0",
"pnpm": ">=10.0.0" "pnpm": ">=10.0.0"

18445
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
packages:
- '.'
allowBuilds:
'@parcel/watcher': true
es5-ext: true
esbuild: true
overrides:
esbuild: 0.28.1

File diff suppressed because one or more lines are too long

View File

@ -1,20 +0,0 @@
<template>
<el-config-provider :locale="appStore.locale" :size="appStore.size">
<router-view />
</el-config-provider>
</template>
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app';
import { useSettingsStore } from '@/store/modules/settings';
import { handleThemeStyle } from '@/utils/theme';
const appStore = useAppStore();
onMounted(() => {
nextTick(() => {
//
handleThemeStyle(useSettingsStore().theme);
});
});
</script>

View File

@ -1,48 +0,0 @@
// 前缀
const animatePrefix = 'animate__animated ';
// 开启随机动画 随机动画值
const animateList: string[] = [
animatePrefix + 'animate__pulse',
animatePrefix + 'animate__rubberBand',
animatePrefix + 'animate__bounceIn',
animatePrefix + 'animate__bounceInLeft',
animatePrefix + 'animate__fadeIn',
animatePrefix + 'animate__fadeInLeft',
animatePrefix + 'animate__fadeInDown',
animatePrefix + 'animate__fadeInUp',
animatePrefix + 'animate__flipInX',
animatePrefix + 'animate__lightSpeedInLeft',
animatePrefix + 'animate__rotateInDownLeft',
animatePrefix + 'animate__rollIn',
animatePrefix + 'animate__rotateInDownLeft',
animatePrefix + 'animate__zoomIn',
animatePrefix + 'animate__zoomInDown',
animatePrefix + 'animate__slideInLeft',
animatePrefix + 'animate__lightSpeedIn'
];
// 关闭随机动画后的默认效果
const defaultAnimate = animatePrefix + 'animate__fadeIn';
// 搜索隐藏显示动画
const searchAnimate = {
enter: '',
leave: ''
};
// 菜单搜索动画
const menuSearchAnimate = {
enter: animatePrefix + 'animate__fadeIn',
leave: animatePrefix + 'animate__fadeOut'
};
// logo动画
const logoAnimate = {
enter: animatePrefix + 'animate__fadeIn',
leave: animatePrefix + 'animate__fadeOut'
};
export default {
animateList,
defaultAnimate,
searchAnimate,
menuSearchAnimate,
logoAnimate
};

View File

@ -1,10 +1,10 @@
import type { AxiosPromise } from '@/utils/api-types'; import type { R } from '@/api/types';
import request from '@/utils/request'; import request from '@/api/request';
import type { SnailOpenApiUser } from './types'; import type { SnailOpenApiUser } from './types';
export const registerCurrentSnailUser = (): AxiosPromise<SnailOpenApiUser> => { export function registerCurrentSnailUser() {
return request({ return request<R<SnailOpenApiUser>>({
url: '/snail-ai/user/register', url: '/snail-ai/user/register',
method: 'post' method: 'post'
}); });
}; }

View File

@ -1,63 +1,41 @@
import type { DemoForm, DemoQuery, DemoVO } from '@/api/demo/demo/types'; import type { PageResult, R } from '@/api/types';
import type { PageResult } from '@/api/types'; import request from '@/api/request';
import type { AxiosPromise } from '@/utils/api-types'; import type { DemoForm, DemoQuery, DemoVO } from './types';
import request from '@/utils/request';
/** export function listDemo(query?: DemoQuery) {
* return request<R<PageResult<DemoVO>>>({
* @param query
* @returns {*}
*/
export const listDemo = (query?: DemoQuery): AxiosPromise<PageResult<DemoVO>> => {
return request({
url: '/demo/demo/list', url: '/demo/demo/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function getDemo(id: string | number) {
* return request<R<DemoVO>>({
* @param id url: `/demo/demo/${id}`,
*/
export const getDemo = (id: string | number): AxiosPromise<DemoVO> => {
return request({
url: '/demo/demo/' + id,
method: 'get' method: 'get'
}); });
}; }
/** export function addDemo(data: DemoForm) {
* return request<R>({
* @param data
*/
export const addDemo = (data: DemoForm) => {
return request({
url: '/demo/demo', url: '/demo/demo',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function updateDemo(data: DemoForm) {
* return request<R>({
* @param data
*/
export const updateDemo = (data: DemoForm) => {
return request({
url: '/demo/demo', url: '/demo/demo',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function delDemo(id: string | number | Array<string | number>) {
* return request<R>({
* @param id url: `/demo/demo/${id}`,
*/
export const delDemo = (id: string | number | Array<string | number>) => {
return request({
url: '/demo/demo/' + id,
method: 'delete' method: 'delete'
}); });
}; }

View File

@ -1,90 +1,27 @@
export interface DemoVO { import type { BaseEntity, PageQuery } from '@/api/types';
/**
*
*/
id: string | number;
/**
* id
*/
deptId: string | number;
/**
* id
*/
userId: string | number;
/**
*
*/
orderNum: number;
/**
* key键
*/
testKey: string;
/**
*
*/
value: string;
}
export interface DemoForm extends BaseEntity { export interface DemoForm extends BaseEntity {
/**
*
*/
id?: string | number; id?: string | number;
/**
* id
*/
deptId?: string | number; deptId?: string | number;
/**
* id
*/
userId?: string | number; userId?: string | number;
/**
*
*/
orderNum?: number; orderNum?: number;
/**
* key键
*/
testKey?: string; testKey?: string;
/**
*
*/
value?: string; value?: string;
} }
export interface DemoQuery extends PageQuery { export interface DemoQuery extends PageQuery {
/**
* id
*/
deptId?: string | number; deptId?: string | number;
/**
* id
*/
userId?: string | number; userId?: string | number;
/**
*
*/
orderNum?: number; orderNum?: number;
/**
* key键
*/
testKey?: string; testKey?: string;
/**
*
*/
value?: string; value?: string;
} }
export interface DemoVO {
id: string | number;
deptId: string | number;
userId: string | number;
orderNum: number;
testKey: string;
value: string;
}

View File

@ -1,62 +1,41 @@
import type { TreeForm, TreeQuery, TreeVO } from '@/api/demo/tree/types'; import type { R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request'; import type { TreeForm, TreeQuery, TreeVO } from './types';
/** export function listTree(query?: TreeQuery) {
* return request<R<TreeVO[]>>({
* @param query
* @returns {*}
*/
export const listTree = (query?: TreeQuery): AxiosPromise<TreeVO[]> => {
return request({
url: '/demo/tree/list', url: '/demo/tree/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function getTree(id: string | number) {
* return request<R<TreeVO>>({
* @param id url: `/demo/tree/${id}`,
*/
export const getTree = (id: string | number): AxiosPromise<TreeVO> => {
return request({
url: '/demo/tree/' + id,
method: 'get' method: 'get'
}); });
}; }
/** export function addTree(data: TreeForm) {
* return request<R>({
* @param data
*/
export const addTree = (data: TreeForm) => {
return request({
url: '/demo/tree', url: '/demo/tree',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function updateTree(data: TreeForm) {
* return request<R>({
* @param data
*/
export const updateTree = (data: TreeForm) => {
return request({
url: '/demo/tree', url: '/demo/tree',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function delTree(id: string | number | Array<string | number>) {
* return request<R>({
* @param id url: `/demo/tree/${id}`,
*/
export const delTree = (id: string | number | Array<string | number>) => {
return request({
url: '/demo/tree/' + id,
method: 'delete' method: 'delete'
}); });
}; }

View File

@ -1,80 +1,25 @@
export interface TreeVO { import type { BaseEntity } from '@/api/types';
/**
*
*/
id: string | number;
/**
* id
*/
parentId: string | number;
/**
* id
*/
deptId: string | number;
/**
* id
*/
userId: string | number;
/**
*
*/
treeName: string;
/**
*
*/
children: TreeVO[];
}
export interface TreeForm extends BaseEntity { export interface TreeForm extends BaseEntity {
/**
*
*/
id?: string | number; id?: string | number;
/**
* id
*/
parentId?: string | number; parentId?: string | number;
/**
* id
*/
deptId?: string | number; deptId?: string | number;
/**
* id
*/
userId?: string | number; userId?: string | number;
/**
*
*/
treeName?: string; treeName?: string;
} }
export interface TreeQuery { export interface TreeQuery {
/**
* id
*/
parentId?: string | number; parentId?: string | number;
/**
* id
*/
deptId?: string | number; deptId?: string | number;
/**
* id
*/
userId?: string | number; userId?: string | number;
/**
*
*/
treeName?: string; treeName?: string;
} }
export interface TreeVO {
id: string | number;
parentId: string | number;
deptId: string | number;
userId: string | number;
treeName: string;
children?: TreeVO[];
}

View File

@ -1,106 +1,74 @@
import type { UserInfo } from '@/api/system/user/types'; import type { UserInfo } from '@/api/system/user/types';
import type { AxiosPromise } from '@/utils/api-types'; import type { LoginParams, LoginResult, R, RegisterParams, VerifyCodeResult } from '@/api/types';
import { closePush } from '@/utils/push'; import request from '@/api/request';
import request from '@/utils/request'; import { appEnv } from '@/utils/env';
import type { LoginData, LoginResult, VerifyCodeResult } from './types';
// pc端固定客户端授权id export function login(data: LoginParams) {
const clientId = import.meta.env.VITE_APP_CLIENT_ID; return request<R<LoginResult>>({
/**
* @param data {LoginData}
* @returns
*/
export function login(data: LoginData): AxiosPromise<LoginResult> {
const params = {
...data,
clientId: data.clientId || clientId,
grantType: data.grantType || 'password'
};
return request({
url: '/auth/login', url: '/auth/login',
method: 'post',
headers: { headers: {
isToken: false, isToken: false,
isEncrypt: true, isEncrypt: 'true',
repeatSubmit: false repeatSubmit: false
}, },
method: 'post', data: {
data: params ...data,
clientId: data.clientId || appEnv.clientId,
grantType: data.grantType || 'password'
}
}); });
} }
// 注册方法
export function register(data: any) {
const params = {
...data,
clientId: clientId,
grantType: 'password'
};
return request({
url: '/auth/register',
headers: {
isToken: false,
isEncrypt: true,
repeatSubmit: false
},
method: 'post',
data: params
});
}
/**
*
*/
export function logout() { export function logout() {
closePush(); return request<R>({
if (
import.meta.env.VITE_APP_MESSAGE_ENABLED === 'true' &&
import.meta.env.VITE_APP_MESSAGE_TRANSPORT.toLowerCase() === 'sse'
) {
request({
url: import.meta.env.VITE_APP_MESSAGE_PATH + '/close',
method: 'get'
});
}
return request({
url: '/auth/logout', url: '/auth/logout',
method: 'post' method: 'post'
}); });
} }
/** export function register(data: RegisterParams) {
* return request<R>({
*/ url: '/auth/register',
export function getCodeImg(): AxiosPromise<VerifyCodeResult> { method: 'post',
return request({ headers: {
isToken: false,
isEncrypt: 'true',
repeatSubmit: false
},
data: {
...data,
clientId: appEnv.clientId,
grantType: 'password'
}
});
}
export function callback(data: LoginParams) {
return request<R<LoginResult>>({
url: '/auth/social/callback',
method: 'post',
data: {
...data,
clientId: appEnv.clientId,
grantType: 'social'
}
});
}
export function getCodeImg() {
return request<R<VerifyCodeResult>>({
url: '/auth/code', url: '/auth/code',
method: 'get',
headers: { headers: {
isToken: false isToken: false
}, },
method: 'get',
timeout: 20000 timeout: 20000
}); });
} }
/** export function getInfo() {
* return request<R<UserInfo>>({
*/
export function callback(data: LoginData): AxiosPromise<any> {
const LoginData = {
...data,
clientId: clientId,
grantType: 'social'
};
return request({
url: '/auth/social/callback',
method: 'post',
data: LoginData
});
}
// 获取用户详细信息
export function getInfo(): AxiosPromise<UserInfo> {
return request({
url: '/system/user/getInfo', url: '/system/user/getInfo',
method: 'get' method: 'get'
}); });

View File

@ -1,10 +1,8 @@
import type { RouteRecordRaw } from 'vue-router'; import type { BackendRoute, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request';
// 获取路由 export function getRouters() {
export function getRouters(): AxiosPromise<RouteRecordRaw[]> { return request<R<BackendRoute[]>>({
return request({
url: '/system/menu/getRouters', url: '/system/menu/getRouters',
method: 'get' method: 'get'
}); });

View File

@ -1,12 +1,10 @@
import type { AxiosPromise } from '@/utils/api-types'; import type { R } from '@/api/types';
import request from '@/utils/request'; import request from '@/api/request';
import type { CacheVO } from './types'; import type { CacheVO } from './types';
// 查询缓存详细 export function getCache() {
export function getCache(): AxiosPromise<CacheVO> { return request<R<CacheVO>>({
return request({
url: '/monitor/cache', url: '/monitor/cache',
method: 'get' method: 'get'
}); });
} }

View File

@ -1,7 +1,5 @@
export interface CacheVO { export interface CacheVO {
commandStats: Array<{ name: string; value: string }>; commandStats: Array<{ name: string; value: string | number }>;
dbSize: number; dbSize: number;
info: Record<string, string>;
info: { [key: string]: string };
} }

View File

@ -1,36 +1,35 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request';
import type { LoginInfoQuery, LoginInfoVO } from './types'; import type { LoginInfoQuery, LoginInfoVO } from './types';
// 查询登录日志列表 export function listLoginInfo(query: LoginInfoQuery) {
export function list(query: LoginInfoQuery): AxiosPromise<PageResult<LoginInfoVO>> { return request<R<PageResult<LoginInfoVO>>>({
return request({
url: '/monitor/loginInfo/list', url: '/monitor/loginInfo/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 删除登录日志 export function list(query: LoginInfoQuery) {
return listLoginInfo(query);
}
export function delLoginInfo(infoId: string | number | Array<string | number>) { export function delLoginInfo(infoId: string | number | Array<string | number>) {
return request({ return request<R>({
url: '/monitor/loginInfo/' + infoId, url: `/monitor/loginInfo/${infoId}`,
method: 'delete' method: 'delete'
}); });
} }
// 解锁用户登录状态 export function unlockLoginInfo(userName: string | string[]) {
export function unlockLoginInfo(userName: string | Array<string>) { return request<R>({
return request({ url: `/monitor/loginInfo/unlock/${userName}`,
url: '/monitor/loginInfo/unlock/' + userName,
method: 'get' method: 'get'
}); });
} }
// 清空登录日志
export function cleanLoginInfo() { export function cleanLoginInfo() {
return request({ return request<R>({
url: '/monitor/loginInfo/clean', url: '/monitor/loginInfo/clean',
method: 'delete' method: 'delete'
}); });

View File

@ -1,20 +1,24 @@
export interface LoginInfoVO { import type { PageQuery } from '@/api/types';
infoId: string | number;
tenantId: string | number; export interface LoginInfoQuery extends Omit<PageQuery, 'isAsc'> {
userName: string; ipaddr?: string;
status: string; userName?: string;
ipaddr: string; status?: string;
loginLocation: string; orderByColumn?: string;
browser: string; isAsc?: string;
os: string;
msg: string;
loginTime: string;
} }
export interface LoginInfoQuery extends PageQuery { export interface LoginInfoVO {
ipaddr: string; infoId: string | number;
tenantId?: string | number;
userName: string; userName: string;
clientKey?: string;
deviceType?: string;
status: string; status: string;
orderByColumn: string; ipaddr?: string;
isAsc: string; loginLocation?: string;
browser?: string;
os?: string;
msg?: string;
loginTime?: string;
} }

View File

@ -1,37 +1,36 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request';
import type { OnlineQuery, OnlineVO } from './types'; import type { OnlineQuery, OnlineVO } from './types';
// 查询在线用户列表 export function listOnline(query: OnlineQuery) {
export function list(query: OnlineQuery): AxiosPromise<PageResult<OnlineVO>> { return request<R<PageResult<OnlineVO>>>({
return request({
url: '/monitor/online/list', url: '/monitor/online/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 强退用户 export function list(query: OnlineQuery) {
return listOnline(query);
}
export function forceLogout(tokenId: string) { export function forceLogout(tokenId: string) {
return request({ return request<R>({
url: '/monitor/online/' + tokenId, url: `/monitor/online/${tokenId}`,
method: 'delete' method: 'delete'
}); });
} }
// 获取当前用户登录在线设备
export function getOnline() { export function getOnline() {
return request({ return request<R<OnlineVO[] | PageResult<OnlineVO>>>({
url: '/monitor/online', url: '/monitor/online',
method: 'get' method: 'get'
}); });
} }
// 删除当前在线设备
export function delOnline(tokenId: string) { export function delOnline(tokenId: string) {
return request({ return request<R>({
url: '/monitor/online/myself/' + tokenId, url: `/monitor/online/myself/${tokenId}`,
method: 'delete' method: 'delete'
}); });
} }

View File

@ -1,15 +1,19 @@
import type { BaseEntity, PageQuery } from '@/api/types';
export interface OnlineQuery extends PageQuery { export interface OnlineQuery extends PageQuery {
ipaddr: string; ipaddr?: string;
userName: string; userName?: string;
} }
export interface OnlineVO extends BaseEntity { export interface OnlineVO extends BaseEntity {
tokenId: string; tokenId: string;
deptName: string; deptName?: string;
userName: string; userName: string;
ipaddr: string; clientKey?: string;
loginLocation: string; deviceType?: string;
browser: string; ipaddr?: string;
os: string; loginLocation?: string;
loginTime: number; browser?: string;
os?: string;
loginTime?: number | string;
} }

View File

@ -1,28 +1,28 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request';
import type { OperLogQuery, OperLogVO } from './types'; import type { OperLogQuery, OperLogVO } from './types';
// 查询操作日志列表 export function listOperlog(query: OperLogQuery) {
export function list(query: OperLogQuery): AxiosPromise<PageResult<OperLogVO>> { return request<R<PageResult<OperLogVO>>>({
return request({
url: '/monitor/operlog/list', url: '/monitor/operlog/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 删除操作日志 export function list(query: OperLogQuery) {
return listOperlog(query);
}
export function delOperlog(operId: string | number | Array<string | number>) { export function delOperlog(operId: string | number | Array<string | number>) {
return request({ return request<R>({
url: '/monitor/operlog/' + operId, url: `/monitor/operlog/${operId}`,
method: 'delete' method: 'delete'
}); });
} }
// 清空操作日志
export function cleanOperlog() { export function cleanOperlog() {
return request({ return request<R>({
url: '/monitor/operlog/clean', url: '/monitor/operlog/clean',
method: 'delete' method: 'delete'
}); });

View File

@ -1,71 +1,45 @@
export interface OperLogQuery extends PageQuery { import type { BaseEntity, PageQuery } from '@/api/types';
operIp: string;
title: string; export interface OperLogQuery extends Omit<PageQuery, 'isAsc'> {
operName: string; operIp?: string;
userId: string; title?: string;
deptId: string; operName?: string;
clientKey: string; userId?: string;
deviceType: string; deptId?: string;
browser: string; clientKey?: string;
os: string; deviceType?: string;
businessType: string; browser?: string;
status: string; os?: string;
orderByColumn: string; businessType?: string;
isAsc: string; status?: string;
orderByColumn?: string;
isAsc?: string;
} }
export interface OperLogVO extends BaseEntity { export interface OperLogVO extends BaseEntity {
operId: string | number; operId: string | number;
tenantId: string; tenantId?: string;
title: string; title?: string;
businessType: number; businessType?: number | string;
businessTypes: number[] | undefined; businessTypes?: number[];
method: string; method?: string;
requestMethod: string; requestMethod?: string;
operatorType: number; operatorType?: number;
operName: string; operName?: string;
userId: string | number; userId?: string | number;
deptId: string | number; deptId?: string | number;
deptName: string; deptName?: string;
clientKey: string; clientKey?: string;
deviceType: string; deviceType?: string;
browser: string; browser?: string;
os: string; os?: string;
operUrl: string; operUrl?: string;
operIp: string; operIp?: string;
operLocation: string; operLocation?: string;
operParam: string; operParam?: string;
jsonResult: string; jsonResult?: string;
status: number; status?: number | string;
errorMsg: string; errorMsg?: string;
operTime: string; operTime?: string;
costTime: number; costTime?: number;
}
export interface OperLogForm {
operId: number | string | undefined;
tenantId: string | number | undefined;
title: string;
businessType: number;
businessTypes: number[] | undefined;
method: string;
requestMethod: string;
operatorType: number;
operName: string;
userId: string | number | undefined;
deptId: string | number | undefined;
deptName: string;
clientKey: string;
deviceType: string;
browser: string;
os: string;
operUrl: string;
operIp: string;
operLocation: string;
operParam: string;
jsonResult: string;
status: number;
errorMsg: string;
operTime: string;
costTime: number;
} }

244
src/api/request.ts Normal file
View File

@ -0,0 +1,244 @@
import { history } from '@umijs/max';
import { message, Modal } from 'antd';
import axios, { type AxiosError, type AxiosRequestConfig } from 'axios';
import { useAppStore } from '@/stores/appStore';
import { useTagsViewStore } from '@/stores/tagsViewStore';
import { getToken, removeToken } from '@/utils/auth';
import { decryptBase64, decryptWithAes, encryptBase64, encryptWithAes, generateAesKey } from '@/utils/crypto';
import { appEnv } from '@/utils/env';
import { decrypt, encrypt } from '@/utils/jsencrypt';
import { tansParams } from '@/utils/ruoyi';
const SUCCESS = 200;
const WARN = 601;
const SERVER_ERROR = 500;
const encryptHeader = 'encrypt-key';
const repeatSubmitStorageKey = 'sessionObj';
const repeatSubmitInterval = 500;
export interface RequestConfig<D = unknown> extends AxiosRequestConfig<D> {
headers?: AxiosRequestConfig<D>['headers'] & {
isToken?: false;
isEncrypt?: 'true' | 'false' | boolean;
repeatSubmit?: false;
};
}
export const isRelogin = { show: false };
type HandledRequestError = Error & { isHandled?: boolean };
function createHandledError(message: string) {
const error = new Error(message) as HandledRequestError;
error.isHandled = true;
return error;
}
export function isHandledRequestError(error: unknown) {
return Boolean((error as { isHandled?: boolean } | undefined)?.isHandled);
}
export function globalHeaders() {
return {
Authorization: `Bearer ${getToken() || ''}`,
clientid: appEnv.clientId
};
}
function showRequestError(content: string) {
message.error({ content, key: `request-error:${content}` });
}
function showReloginConfirm() {
if (isRelogin.show) return;
isRelogin.show = true;
Modal.confirm({
title: '系统提示',
content: '登录状态已过期,您可以继续留在该页面,或者重新登录',
okText: '重新登录',
cancelText: '取消',
onOk: () => {
isRelogin.show = false;
removeToken();
useTagsViewStore.getState().resetTags();
history.replace(
`/login?redirect=${encodeURIComponent(`${window.location.pathname || '/'}${window.location.search || ''}`)}`
);
},
onCancel: () => {
isRelogin.show = false;
}
});
}
function stableDataString(data: unknown) {
if (data instanceof FormData) return '';
if (typeof data === 'string') return data;
if (typeof data === 'undefined' || data === null) return '';
if (typeof data === 'object') return JSON.stringify(data);
return String(data);
}
function getSessionSubmitRecord() {
try {
const text = sessionStorage.getItem(repeatSubmitStorageKey);
return text ? (JSON.parse(text) as { url?: string; data?: string; time?: number }) : undefined;
} catch {
sessionStorage.removeItem(repeatSubmitStorageKey);
return undefined;
}
}
function setSessionSubmitRecord(record: { url?: string; data?: string; time: number }) {
sessionStorage.setItem(repeatSubmitStorageKey, JSON.stringify(record));
}
const service = axios.create({
baseURL: appEnv.baseApi,
timeout: 50000,
headers: {
'Content-Type': 'application/json;charset=utf-8',
clientid: appEnv.clientId
},
transitional: {
clarifyTimeoutError: true
}
});
service.interceptors.request.use(config => {
config.headers.set('Content-Language', useAppStore.getState().appLocale);
const isToken = config.headers.get('isToken') === false;
const isEncrypt = config.headers.get('isEncrypt') === 'true' || config.headers.get('isEncrypt') === true;
const isRepeatSubmitDisabled = config.headers.get('repeatSubmit') === false;
if (getToken() && !isToken) {
config.headers.set('Authorization', `Bearer ${getToken()}`);
}
if (config.method === 'get' && config.params) {
let url = `${config.url}?${tansParams(config.params)}`;
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
if (!isRepeatSubmitDisabled && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: stableDataString(config.data),
time: Date.now()
};
const sessionObj = getSessionSubmitRecord();
if (
sessionObj &&
sessionObj.url === requestObj.url &&
sessionObj.data === requestObj.data &&
requestObj.time - (sessionObj.time || 0) < repeatSubmitInterval
) {
return Promise.reject(new Error('数据正在处理,请勿重复提交'));
}
setSessionSubmitRecord(requestObj);
}
if (appEnv.encryptEnabled && isEncrypt && (config.method === 'post' || config.method === 'put')) {
const aesKey = generateAesKey();
const encryptedKey = encrypt(encryptBase64(aesKey));
if (encryptedKey) {
config.headers.set(encryptHeader, encryptedKey);
}
const data = typeof config.data === 'object' ? JSON.stringify(config.data) : String(config.data ?? '');
config.data = encryptWithAes(data, aesKey);
}
if (config.data instanceof FormData) {
config.headers.delete('Content-Type');
}
config.headers.delete('isToken');
config.headers.delete('isEncrypt');
config.headers.delete('repeatSubmit');
return config;
});
function normalizeErrorMessage(message?: string) {
if (!message) return undefined;
if (message === 'Network Error') return '后端接口连接异常';
if (message.includes('timeout')) return '系统接口请求超时';
if (message.includes('Request failed with status code')) return `系统接口${message.slice(-3)}异常`;
return message;
}
async function parseResponseErrorData(data: unknown): Promise<string | undefined> {
if (!data) return undefined;
if (data instanceof Blob) return parseResponseErrorData(await data.text());
if (data instanceof ArrayBuffer) return parseResponseErrorData(new TextDecoder().decode(data));
if (typeof data === 'string') {
const text = data.trim();
if (!text) return undefined;
try {
return parseResponseErrorData(JSON.parse(text));
} catch {
return text;
}
}
if (typeof data === 'object') {
const payload = data as { msg?: string; message?: string };
return payload.msg || payload.message;
}
return undefined;
}
async function getErrorMessage(error: AxiosError) {
const responseMessage = await parseResponseErrorData(error.response?.data);
return responseMessage || normalizeErrorMessage(error.message) || '系统未知错误';
}
service.interceptors.response.use(
response => {
const keyStr = response.headers[encryptHeader];
if (appEnv.encryptEnabled && keyStr) {
const base64Str = decrypt(keyStr);
if (base64Str) {
const aesKey = decryptBase64(base64Str);
response.data = JSON.parse(decryptWithAes(response.data, aesKey));
}
}
if (response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer') {
return response.data;
}
const code = response.data?.code || SUCCESS;
const msg = response.data?.msg || '系统未知错误';
if (code === 401) {
showReloginConfirm();
return Promise.reject(createHandledError('无效的会话,或者会话已过期,请重新登录。'));
}
if (code === SERVER_ERROR || code === WARN || code !== SUCCESS) {
showRequestError(msg);
return Promise.reject(createHandledError(msg));
}
return response.data;
},
async error => {
if (axios.isAxiosError(error) && error.response?.status === 401) {
showReloginConfirm();
return Promise.reject(createHandledError('无效的会话,或者会话已过期,请重新登录。'));
}
const msg = axios.isAxiosError(error)
? await getErrorMessage(error)
: normalizeErrorMessage((error as Error | undefined)?.message) || '系统未知错误';
showRequestError(msg);
return Promise.reject(createHandledError(msg));
}
);
export default function request<T = unknown, D = unknown>(config: RequestConfig<D>): Promise<T> {
return service.request<T, T, D>(config);
}

View File

@ -1,81 +1,49 @@
import type { ClientForm, ClientQuery, ClientVO } from '@/api/system/client/types'; import type { PageResult, R } from '@/api/types';
import type { PageResult } from '@/api/types'; import request from '@/api/request';
import type { AxiosPromise } from '@/utils/api-types'; import type { ClientForm, ClientQuery, ClientVO } from './types';
import request from '@/utils/request';
/** export function listClient(query?: ClientQuery) {
* return request<R<PageResult<ClientVO>>>({
* @param query
* @returns {*}
*/
export const listClient = (query?: ClientQuery): AxiosPromise<PageResult<ClientVO>> => {
return request({
url: '/system/client/list', url: '/system/client/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function getClient(id: string | number) {
* return request<R<ClientVO>>({
* @param id url: `/system/client/${id}`,
*/
export const getClient = (id: string | number): AxiosPromise<ClientVO> => {
return request({
url: '/system/client/' + id,
method: 'get' method: 'get'
}); });
}; }
/** export function addClient(data: ClientForm) {
* return request<R>({
* @param data
*/
export const addClient = (data: ClientForm) => {
return request({
url: '/system/client', url: '/system/client',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function updateClient(data: ClientForm) {
* return request<R>({
* @param data url: '/system/client',
*/ method: 'put',
export const updateClient = (data: ClientForm) => { data
return request({ });
url: '/system/client', }
method: 'put',
data: data export function delClient(id: string | number | Array<string | number>) {
}); return request<R>({
}; url: `/system/client/${id}`,
method: 'delete'
/** });
* }
* @param id
*/ export function changeStatus(clientId: string, status: string) {
export const delClient = (id: string | number | Array<string | number>) => { return request<R>({
return request({ url: '/system/client/changeStatus',
url: '/system/client/' + id, method: 'put',
method: 'delete' data: { clientId, status }
});
};
/**
*
* @param clientId id
* @param status
*/
export function changeStatus(clientId: string, status: string) {
const data = {
clientId,
status
};
return request({
url: '/system/client/changeStatus',
method: 'put',
data: data
}); });
} }

View File

@ -1,185 +1,46 @@
export interface ClientVO { import type { BaseEntity, PageQuery } from '@/api/types';
/**
* id
*/
id: string | number;
/**
* id
*/
clientId: string;
/**
* key
*/
clientKey: string;
/**
*
*/
clientSecret: string;
/**
*
*/
grantTypeList: string[];
/**
*
*/
deviceType: string;
/**
* 访
*/
accessPath?: string;
/**
* 访
*/
accessPathList?: string[];
/**
* IP白名单
*/
ipWhitelist?: string;
/**
* IP白名单列表
*/
ipWhitelistList?: string[];
/**
* token活跃超时时间
*/
activeTimeout: number;
/**
* token固定超时
*/
timeout: number;
/**
* 0 1
*/
status: string;
}
export interface ClientForm extends BaseEntity { export interface ClientForm extends BaseEntity {
/**
* id
*/
id?: string | number; id?: string | number;
/**
* id
*/
clientId?: string | number; clientId?: string | number;
/**
* key
*/
clientKey?: string; clientKey?: string;
/**
*
*/
clientSecret?: string; clientSecret?: string;
/**
*
*/
grantTypeList?: string[]; grantTypeList?: string[];
/**
*
*/
deviceType?: string; deviceType?: string;
/**
* 访
*/
accessPath?: string; accessPath?: string;
/**
* 访
*/
accessPathList?: string[]; accessPathList?: string[];
/**
* IP白名单
*/
ipWhitelist?: string; ipWhitelist?: string;
/**
* IP白名单列表
*/
ipWhitelistList?: string[]; ipWhitelistList?: string[];
/**
* token活跃超时时间
*/
activeTimeout?: number; activeTimeout?: number;
/**
* token固定超时
*/
timeout?: number; timeout?: number;
/**
* 0 1
*/
status?: string; status?: string;
} }
export interface ClientQuery extends PageQuery { export interface ClientQuery extends PageQuery {
/**
* id
*/
clientId?: string | number; clientId?: string | number;
/**
* key
*/
clientKey?: string; clientKey?: string;
/**
*
*/
clientSecret?: string; clientSecret?: string;
/**
*
*/
grantType?: string; grantType?: string;
/**
*
*/
deviceType?: string; deviceType?: string;
/**
* 访
*/
accessPath?: string; accessPath?: string;
/**
* IP白名单
*/
ipWhitelist?: string; ipWhitelist?: string;
/**
* token活跃超时时间
*/
activeTimeout?: number; activeTimeout?: number;
/**
* token固定超时
*/
timeout?: number; timeout?: number;
/**
* 0 1
*/
status?: string; status?: string;
} }
export interface ClientVO {
id: string | number;
clientId: string;
clientKey: string;
clientSecret: string;
grantTypeList?: string[];
deviceType?: string;
accessPath?: string;
accessPathList?: string[];
ipWhitelist?: string;
ipWhitelistList?: string[];
activeTimeout?: number;
timeout?: number;
status: string;
}

View File

@ -1,54 +1,47 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request';
import type { ConfigForm, ConfigQuery, ConfigVO } from './types'; import type { ConfigForm, ConfigQuery, ConfigVO } from './types';
// 查询参数列表 export function listConfig(query: ConfigQuery) {
export function listConfig(query: ConfigQuery): AxiosPromise<PageResult<ConfigVO>> { return request<R<PageResult<ConfigVO>>>({
return request({
url: '/system/config/list', url: '/system/config/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 查询参数详细 export function getConfig(configId: string | number) {
export function getConfig(configId: string | number): AxiosPromise<ConfigVO> { return request<R<ConfigVO>>({
return request({ url: `/system/config/${configId}`,
url: '/system/config/' + configId,
method: 'get' method: 'get'
}); });
} }
// 根据参数键名查询参数值 export function getConfigKey(configKey: string) {
export function getConfigKey(configKey: string): AxiosPromise<string> { return request<R<string>>({
return request({ url: `/system/config/configKey/${configKey}`,
url: '/system/config/configKey/' + configKey,
method: 'get' method: 'get'
}); });
} }
// 新增参数配置
export function addConfig(data: ConfigForm) { export function addConfig(data: ConfigForm) {
return request({ return request<R>({
url: '/system/config', url: '/system/config',
method: 'post', method: 'post',
data: data data
}); });
} }
// 修改参数配置
export function updateConfig(data: ConfigForm) { export function updateConfig(data: ConfigForm) {
return request({ return request<R>({
url: '/system/config', url: '/system/config',
method: 'put', method: 'put',
data: data data
}); });
} }
// 修改参数配置 export function updateConfigByKey(key: string, value: unknown) {
export function updateConfigByKey(key: string, value: any) { return request<R>({
return request({
url: '/system/config/updateByKey', url: '/system/config/updateByKey',
method: 'put', method: 'put',
data: { data: {
@ -58,18 +51,20 @@ export function updateConfigByKey(key: string, value: any) {
}); });
} }
// 删除参数配置
export function delConfig(configId: string | number | Array<string | number>) { export function delConfig(configId: string | number | Array<string | number>) {
return request({ return request<R>({
url: '/system/config/' + configId, url: `/system/config/${configId}`,
method: 'delete' method: 'delete'
}); });
} }
// 刷新参数缓存 export function refreshConfigCache() {
export function refreshCache() { return request<R>({
return request({
url: '/system/config/refreshCache', url: '/system/config/refreshCache',
method: 'delete' method: 'delete'
}); });
} }
export function refreshCache() {
return refreshConfigCache();
}

View File

@ -1,23 +1,24 @@
import type { BaseEntity, PageQuery } from '@/api/types';
export interface ConfigForm {
configId?: number | string;
configName?: string;
configKey?: string;
configValue?: string;
configType?: string;
remark?: string;
}
export interface ConfigQuery extends PageQuery {
configName?: string;
configKey?: string;
configType?: string;
}
export interface ConfigVO extends BaseEntity { export interface ConfigVO extends BaseEntity {
configId: number | string; configId: number | string;
configName: string; configName: string;
configKey: string; configKey: string;
configValue: string; configValue: string;
configType: string; configType: string;
remark: string;
}
export interface ConfigForm {
configId: number | string | undefined;
configName: string;
configKey: string;
configValue: string;
configType: string;
remark: string;
}
export interface ConfigQuery extends PageQuery {
configName: string;
configKey: string;
configType: string;
} }

View File

@ -1,65 +1,55 @@
import type { AxiosPromise } from '@/utils/api-types'; import type { R } from '@/api/types';
import request from '@/utils/request'; import request from '@/api/request';
import { type DeptForm, type DeptQuery, type DeptVO } from './types'; import type { DeptForm, DeptQuery, DeptVO } from './types';
// 查询部门列表 export function listDept(query?: DeptQuery) {
export const listDept = (query?: DeptQuery) => { return request<R<DeptVO[]>>({
return request({
url: '/system/dept/list', url: '/system/dept/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function optionSelect(deptIds: Array<number | string>) {
* deptIds查询部门 return request<R<DeptVO[]>>({
* @param deptIds url: `/system/dept/optionselect?deptIds=${deptIds}`,
*/
export const optionSelect = (deptIds: (number | string)[]): AxiosPromise<DeptVO[]> => {
return request({
url: '/system/dept/optionselect?deptIds=' + deptIds,
method: 'get' method: 'get'
}); });
}; }
// 查询部门列表(排除节点) export function listDeptExcludeChild(deptId: string | number) {
export const listDeptExcludeChild = (deptId: string | number): AxiosPromise<DeptVO[]> => { return request<R<DeptVO[]>>({
return request({ url: `/system/dept/list/exclude/${deptId}`,
url: '/system/dept/list/exclude/' + deptId,
method: 'get' method: 'get'
}); });
}; }
// 查询部门详细 export function getDept(deptId: string | number) {
export const getDept = (deptId: string | number): AxiosPromise<DeptVO> => { return request<R<DeptVO>>({
return request({ url: `/system/dept/${deptId}`,
url: '/system/dept/' + deptId,
method: 'get' method: 'get'
}); });
}; }
// 新增部门 export function addDept(data: DeptForm) {
export const addDept = (data: DeptForm) => { return request<R>({
return request({
url: '/system/dept', url: '/system/dept',
method: 'post', method: 'post',
data: data data
}); });
}; }
// 修改部门 export function updateDept(data: DeptForm) {
export const updateDept = (data: DeptForm) => { return request<R>({
return request({
url: '/system/dept', url: '/system/dept',
method: 'put', method: 'put',
data: data data
}); });
}; }
// 删除部门 export function delDept(deptId: number | string) {
export const delDept = (deptId: number | string) => { return request<R>({
return request({ url: `/system/dept/${deptId}`,
url: '/system/dept/' + deptId,
method: 'delete' method: 'delete'
}); });
}; }

View File

@ -1,48 +1,5 @@
/** import type { BaseEntity, PageQuery } from '@/api/types';
*
*/
export interface DeptQuery extends PageQuery {
deptName?: string;
deptCategory?: string;
status?: number;
}
/**
*
*/
export interface DeptVO extends BaseEntity {
id: number | string;
parentName: string;
parentId: number | string;
children: DeptVO[];
deptId: number | string;
deptName: string;
deptCategory: string;
orderNum: number;
leader: string;
phone: string;
email: string;
status: string;
delFlag: string;
ancestors: string;
menuId: string | number;
}
/**
*
*/
export interface DeptTreeVO extends BaseEntity {
id: number | string;
label: string;
parentId: number | string;
weight: number;
children: DeptTreeVO[];
disabled: boolean;
}
/**
*
*/
export interface DeptForm { export interface DeptForm {
parentName?: string; parentName?: string;
parentId?: number | string; parentId?: number | string;
@ -51,10 +8,43 @@ export interface DeptForm {
deptName?: string; deptName?: string;
deptCategory?: string; deptCategory?: string;
orderNum?: number; orderNum?: number;
leader?: string; leader?: string | number;
phone?: string; phone?: string;
email?: string; email?: string;
status?: string; status?: string;
delFlag?: string; delFlag?: string;
ancestors?: string; ancestors?: string;
} }
export interface DeptQuery extends PageQuery {
deptName?: string;
deptCategory?: string;
status?: string;
}
export interface DeptTreeVO extends BaseEntity {
id: number | string;
label: string;
parentId: number | string;
weight: number;
children?: DeptTreeVO[];
disabled?: boolean;
}
export interface DeptVO extends BaseEntity {
id?: number | string;
parentName?: string;
parentId?: number | string;
children?: DeptVO[];
deptId: number | string;
deptName: string;
deptCategory?: string;
orderNum?: number;
leader?: string;
phone?: string;
email?: string;
status?: string;
delFlag?: string;
ancestors?: string;
menuId?: string | number;
}

View File

@ -1,54 +1,48 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request'; import type { DictData, DictDataForm, DictDataQuery } from './types';
import type { DictDataForm, DictDataQuery, DictDataVO } from './types';
// 根据字典类型查询字典数据信息 export function getDicts(dictType: string) {
export function getDicts(dictType: string): AxiosPromise<DictDataVO[]> { return request<R<DictData[]>>({
return request({ url: `/system/dict/data/type/${dictType}`,
url: '/system/dict/data/type/' + dictType,
method: 'get' method: 'get'
}); });
} }
// 查询字典数据列表 export function listData(query: DictDataQuery) {
export function listData(query: DictDataQuery): AxiosPromise<PageResult<DictDataVO>> { return request<R<PageResult<DictData>>>({
return request({
url: '/system/dict/data/list', url: '/system/dict/data/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 查询字典数据详细 export function getData(dictCode: string | number) {
export function getData(dictCode: string | number): AxiosPromise<DictDataVO> { return request<R<DictData>>({
return request({ url: `/system/dict/data/${dictCode}`,
url: '/system/dict/data/' + dictCode,
method: 'get' method: 'get'
}); });
} }
// 新增字典数据
export function addData(data: DictDataForm) { export function addData(data: DictDataForm) {
return request({ return request<R>({
url: '/system/dict/data', url: '/system/dict/data',
method: 'post', method: 'post',
data: data data
}); });
} }
// 修改字典数据
export function updateData(data: DictDataForm) { export function updateData(data: DictDataForm) {
return request({ return request<R>({
url: '/system/dict/data', url: '/system/dict/data',
method: 'put', method: 'put',
data: data data
}); });
} }
// 删除字典数据
export function delData(dictCode: string | number | Array<string | number>) { export function delData(dictCode: string | number | Array<string | number>) {
return request({ return request<R>({
url: '/system/dict/data/' + dictCode, url: `/system/dict/data/${dictCode}`,
method: 'delete' method: 'delete'
}); });
} }

View File

@ -1,26 +1,32 @@
export interface DictDataQuery extends PageQuery { import type { BaseEntity, PageQuery } from '@/api/types';
dictName: string;
dictType: string;
dictLabel: string;
}
export interface DictDataVO extends BaseEntity { export interface DictData extends BaseEntity {
dictCode: string; dictCode: string | number;
dictSort: number;
dictLabel: string; dictLabel: string;
dictValue: string; dictValue: string;
cssClass: string; dictType: string;
listClass: ElTagType; cssClass?: string;
dictSort: number; listClass?: string;
remark: string; isDefault?: string;
status: string;
} }
export type DictDataVO = DictData;
export interface DictDataForm { export interface DictDataForm {
dictType?: string; dictType?: string;
dictCode: string | undefined; dictCode?: string | number;
dictLabel: string; dictLabel?: string;
dictValue: string; dictValue?: string;
cssClass: string; cssClass?: string;
listClass: ElTagType; listClass?: string;
dictSort: number; dictSort?: number;
remark: string; remark?: string;
}
export interface DictDataQuery extends PageQuery {
dictName?: string;
dictType?: string;
dictLabel?: string;
} }

View File

@ -1,62 +1,54 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request';
import type { DictTypeForm, DictTypeQuery, DictTypeVO } from './types'; import type { DictTypeForm, DictTypeQuery, DictTypeVO } from './types';
// 查询字典类型列表 export function listType(query: DictTypeQuery) {
export function listType(query: DictTypeQuery): AxiosPromise<PageResult<DictTypeVO>> { return request<R<PageResult<DictTypeVO>>>({
return request({
url: '/system/dict/type/list', url: '/system/dict/type/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 查询字典类型详细 export function getType(dictId: number | string) {
export function getType(dictId: number | string): AxiosPromise<DictTypeVO> { return request<R<DictTypeVO>>({
return request({ url: `/system/dict/type/${dictId}`,
url: '/system/dict/type/' + dictId,
method: 'get' method: 'get'
}); });
} }
// 新增字典类型
export function addType(data: DictTypeForm) { export function addType(data: DictTypeForm) {
return request({ return request<R>({
url: '/system/dict/type', url: '/system/dict/type',
method: 'post', method: 'post',
data: data data
}); });
} }
// 修改字典类型
export function updateType(data: DictTypeForm) { export function updateType(data: DictTypeForm) {
return request({ return request<R>({
url: '/system/dict/type', url: '/system/dict/type',
method: 'put', method: 'put',
data: data data
}); });
} }
// 删除字典类型
export function delType(dictId: string | number | Array<string | number>) { export function delType(dictId: string | number | Array<string | number>) {
return request({ return request<R>({
url: '/system/dict/type/' + dictId, url: `/system/dict/type/${dictId}`,
method: 'delete' method: 'delete'
}); });
} }
// 刷新字典缓存
export function refreshCache() { export function refreshCache() {
return request({ return request<R>({
url: '/system/dict/type/refreshCache', url: '/system/dict/type/refreshCache',
method: 'delete' method: 'delete'
}); });
} }
// 获取字典选择框列表 export function optionselect() {
export function optionselect(): AxiosPromise<DictTypeVO[]> { return request<R<DictTypeVO[]>>({
return request({
url: '/system/dict/type/optionselect', url: '/system/dict/type/optionselect',
method: 'get' method: 'get'
}); });

View File

@ -1,18 +1,19 @@
import type { BaseEntity, PageQuery } from '@/api/types';
export interface DictTypeForm {
dictId?: number | string;
dictName?: string;
dictType?: string;
remark?: string;
}
export interface DictTypeQuery extends PageQuery {
dictName?: string;
dictType?: string;
}
export interface DictTypeVO extends BaseEntity { export interface DictTypeVO extends BaseEntity {
dictId: number | string; dictId: number | string;
dictName: string; dictName: string;
dictType: string; dictType: string;
remark: string;
}
export interface DictTypeForm {
dictId: number | string | undefined;
dictName: string;
dictType: string;
remark: string;
}
export interface DictTypeQuery extends PageQuery {
dictName: string;
dictType: string;
} }

View File

@ -1,70 +1,63 @@
import type { AxiosPromise } from '@/utils/api-types'; import type { RoleMenuTree } from '@/api/system/role/types';
import request from '@/utils/request'; import type { R } from '@/api/types';
import type { MenuForm, MenuQuery, MenuTreeOption, MenuVO, RoleMenuTree } from './types'; import request from '@/api/request';
import type { MenuForm, MenuQuery, MenuTreeOption, MenuVO } from './types';
// 查询菜单列表 export function listMenu(query?: MenuQuery) {
export const listMenu = (query?: MenuQuery): AxiosPromise<MenuVO[]> => { return request<R<MenuVO[]>>({
return request({
url: '/system/menu/list', url: '/system/menu/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
// 查询菜单详细 export function getMenu(menuId: string | number) {
export const getMenu = (menuId: string | number): AxiosPromise<MenuVO> => { return request<R<MenuVO>>({
return request({ url: `/system/menu/${menuId}`,
url: '/system/menu/' + menuId,
method: 'get' method: 'get'
}); });
}; }
// 查询菜单下拉树结构 export function treeselect() {
export const treeselect = (): AxiosPromise<MenuTreeOption[]> => { return request<R<MenuTreeOption[]>>({
return request({
url: '/system/menu/treeselect', url: '/system/menu/treeselect',
method: 'get' method: 'get'
}); });
}; }
// 根据角色ID查询菜单下拉树结构 export function roleMenuTreeselect(roleId: string | number) {
export const roleMenuTreeselect = (roleId: string | number): AxiosPromise<RoleMenuTree> => { return request<R<RoleMenuTree>>({
return request({ url: `/system/menu/roleMenuTreeselect/${roleId}`,
url: '/system/menu/roleMenuTreeselect/' + roleId,
method: 'get' method: 'get'
}); });
}; }
// 新增菜单 export function addMenu(data: MenuForm) {
export const addMenu = (data: MenuForm) => { return request<R>({
return request({
url: '/system/menu', url: '/system/menu',
method: 'post', method: 'post',
data: data data
}); });
}; }
// 修改菜单 export function updateMenu(data: MenuForm) {
export const updateMenu = (data: MenuForm) => { return request<R>({
return request({
url: '/system/menu', url: '/system/menu',
method: 'put', method: 'put',
data: data data
}); });
}; }
// 删除菜单 export function delMenu(menuId: string | number) {
export const delMenu = (menuId: string | number) => { return request<R>({
return request({ url: `/system/menu/${menuId}`,
url: '/system/menu/' + menuId,
method: 'delete' method: 'delete'
}); });
}; }
// 级联删除菜单 export function cascadeDelMenu(menuIds: Array<string | number>) {
export const cascadeDelMenu = (menuIds: Array<string | number>) => { return request<R>({
return request({ url: `/system/menu/cascade/${menuIds}`,
url: '/system/menu/cascade/' + menuIds,
method: 'delete' method: 'delete'
}); });
}; }

View File

@ -1,75 +1,51 @@
import type { MenuTypeEnum } from '@/enums/MenuTypeEnum'; import type { BaseEntity } from '@/api/types';
/**
*
*/
export interface MenuTreeOption {
id: string | number;
label: string;
parentId: string | number;
weight: number;
menuType?: MenuTypeEnum | string;
visible?: string;
status?: string;
disabled?: boolean;
children?: MenuTreeOption[];
}
export interface RoleMenuTree {
menus: MenuTreeOption[];
checkedKeys: Array<string | number>;
}
/**
*
*/
export interface RoleMenuButtonOption {
menuId: string | number;
menuName: string;
parentId: string | number;
perms?: string;
status?: string;
disabled?: boolean;
}
/**
*
*/
export interface MenuQuery {
keywords?: string;
menuName?: string;
status?: string;
}
/**
*
*/
export interface MenuVO extends BaseEntity {
parentName: string;
parentId: string | number;
children: MenuVO[];
menuId: string | number;
menuName: string;
orderNum: number;
path: string;
component: string;
queryParam: string;
isFrame: string;
isCache: string;
menuType: MenuTypeEnum;
visible: string;
status: string;
icon: string;
activeMenu: string;
ext: string;
remark: string;
}
export interface MenuForm { export interface MenuForm {
parentName?: string; parentName?: string;
parentId?: string | number; parentId?: string | number;
children?: MenuForm[]; children?: MenuForm[];
menuId?: string | number; menuId?: string | number;
menuName?: string;
orderNum?: number;
path?: string;
component?: string;
queryParam?: string;
isFrame?: string;
isCache?: string;
menuType?: 'M' | 'C' | 'F' | string;
visible?: string;
status?: string;
icon?: string | null;
activeMenu?: string;
ext?: string;
remark?: string;
query?: string;
perms?: string;
}
export interface MenuQuery {
keywords?: string;
menuName?: string;
status?: string;
}
export interface MenuTreeOption {
id: string | number;
label: string;
parentId: string | number;
weight: number;
menuType?: 'M' | 'C' | 'F' | string;
visible?: string;
status?: string;
disabled?: boolean;
children?: MenuTreeOption[];
}
export interface MenuVO extends BaseEntity {
parentName?: string;
parentId: string | number;
children?: MenuVO[];
menuId: string | number;
menuName: string; menuName: string;
orderNum: number; orderNum: number;
path: string; path: string;
@ -77,13 +53,11 @@ export interface MenuForm {
queryParam?: string; queryParam?: string;
isFrame?: string; isFrame?: string;
isCache?: string; isCache?: string;
menuType?: MenuTypeEnum; menuType: 'M' | 'C' | 'F' | string;
visible?: string; visible?: string;
status?: string; status?: string;
icon?: string; icon?: string | null;
activeMenu?: string; activeMenu?: string;
ext?: string; ext?: string;
remark?: string;
query?: string;
perms?: string; perms?: string;
} }

View File

@ -1,9 +1,9 @@
import type { AxiosPromise } from '@/utils/api-types'; import type { R } from '@/api/types';
import request from '@/utils/request'; import request from '@/api/request';
import type { MessageBoxVO } from './types'; import type { MessageBoxVO } from './types';
export function getMessageBox(): AxiosPromise<MessageBoxVO> { export function getMessageBox() {
return request({ return request<R<MessageBoxVO>>({
url: '/resource/message/box', url: '/resource/message/box',
method: 'get' method: 'get'
}); });

View File

@ -1,3 +1,5 @@
import type { BaseEntity } from '@/api/types';
export interface MessageVO extends BaseEntity { export interface MessageVO extends BaseEntity {
messageId: number | string; messageId: number | string;
category: string; category: string;
@ -6,7 +8,7 @@ export interface MessageVO extends BaseEntity {
title: string; title: string;
message: string; message: string;
content?: string; content?: string;
data?: Record<string, any> | null; data?: Record<string, unknown> | null;
path?: string; path?: string;
} }

View File

@ -1,46 +1,41 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request';
import type { NoticeForm, NoticeQuery, NoticeVO } from './types'; import type { NoticeForm, NoticeQuery, NoticeVO } from './types';
// 查询公告列表
export function listNotice(query: NoticeQuery): AxiosPromise<PageResult<NoticeVO>> { export function listNotice(query: NoticeQuery) {
return request({ return request<R<PageResult<NoticeVO>>>({
url: '/system/notice/list', url: '/system/notice/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 查询公告详细 export function getNotice(noticeId: string | number) {
export function getNotice(noticeId: string | number): AxiosPromise<NoticeVO> { return request<R<NoticeVO>>({
return request({ url: `/system/notice/${noticeId}`,
url: '/system/notice/' + noticeId,
method: 'get' method: 'get'
}); });
} }
// 新增公告
export function addNotice(data: NoticeForm) { export function addNotice(data: NoticeForm) {
return request({ return request<R>({
url: '/system/notice', url: '/system/notice',
method: 'post', method: 'post',
data: data data
}); });
} }
// 修改公告
export function updateNotice(data: NoticeForm) { export function updateNotice(data: NoticeForm) {
return request({ return request<R>({
url: '/system/notice', url: '/system/notice',
method: 'put', method: 'put',
data: data data
}); });
} }
// 删除公告
export function delNotice(noticeId: string | number | Array<string | number>) { export function delNotice(noticeId: string | number | Array<string | number>) {
return request({ return request<R>({
url: '/system/notice/' + noticeId, url: `/system/notice/${noticeId}`,
method: 'delete' method: 'delete'
}); });
} }

View File

@ -1,26 +1,27 @@
export interface NoticeVO extends BaseEntity { import type { BaseEntity, PageQuery } from '@/api/types';
noticeId: number;
noticeTitle: string; export interface NoticeForm {
noticeType: string; noticeId?: number | string;
noticeContent: string; noticeTitle?: string;
status: string; noticeType?: string;
remark: string; noticeContent?: string;
createByName: string; status?: string;
remark?: string;
createByName?: string;
} }
export interface NoticeQuery extends PageQuery { export interface NoticeQuery extends PageQuery {
noticeTitle: string; noticeTitle?: string;
createByName: string; createByName?: string;
status: string; status?: string;
noticeType: string; noticeType?: string;
} }
export interface NoticeForm { export interface NoticeVO extends BaseEntity {
noticeId: number | string | undefined; noticeId: number | string;
noticeTitle: string; noticeTitle: string;
noticeType: string; noticeType: string;
noticeContent: string; noticeContent: string;
status: string; status: string;
remark: string; createByName?: string;
createByName: string;
} }

View File

@ -1,38 +1,45 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request'; import type { OssQuery, OssUploadResult, OssVO } from './types';
import type { OssQuery, OssUploadVO, OssVO } from './types';
// 查询OSS对象存储列表 export function listOss(query: OssQuery) {
export function listOss(query: OssQuery): AxiosPromise<PageResult<OssVO>> { return request<R<PageResult<OssVO>>>({
return request({
url: '/resource/oss/list', url: '/resource/oss/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 查询OSS对象基于id串 export function listOssByIds(ossId: string | number) {
export function listByIds(ossId: string | number): AxiosPromise<OssVO[]> { return request<R<OssVO[]>>({
return request({ url: `/resource/oss/listByIds/${ossId}`,
url: '/resource/oss/listByIds/' + ossId,
method: 'get' method: 'get'
}); });
} }
// 上传OSS对象存储 export function listByIds(ossId: string | number) {
export function uploadOss(data: FormData): AxiosPromise<OssUploadVO> { return listOssByIds(ossId);
return request({ }
export function uploadOss(data: FormData) {
return request<R<OssUploadResult>>({
url: '/resource/oss/upload', url: '/resource/oss/upload',
method: 'post', method: 'post',
data data
}); });
} }
// 删除OSS对象存储
export function delOss(ossId: string | number | Array<string | number>) { export function delOss(ossId: string | number | Array<string | number>) {
return request({ return request<R>({
url: '/resource/oss/' + ossId, url: `/resource/oss/${ossId}`,
method: 'delete' method: 'delete'
}); });
} }
export function downloadOss(ossId: string | number) {
return request<Blob>({
url: `/resource/oss/download/${ossId}`,
method: 'get',
responseType: 'blob'
});
}

View File

@ -1,42 +1,27 @@
import type { BaseEntity, PageQuery } from '@/api/types';
export interface OssQuery extends Omit<PageQuery, 'isAsc'> {
fileName?: string;
originalName?: string;
fileSuffix?: string;
createTime?: string;
service?: string;
orderByColumn?: string;
isAsc?: string;
}
export interface OssUploadResult {
url?: string;
fileName?: string;
ossId?: string | number;
}
export interface OssVO extends BaseEntity { export interface OssVO extends BaseEntity {
ossId: string | number; ossId: string | number;
fileName: string; fileName: string;
originalName: string; originalName: string;
fileSuffix: string; fileSuffix: string;
url: string; url: string;
createByName: string; createByName?: string;
service: string; service?: string;
}
export interface OssUploadVO {
url: string;
fileName: string;
ossId: string;
}
export interface OssQuery extends PageQuery {
fileName: string;
originalName: string;
fileSuffix: string;
createTime: string;
service: string;
orderByColumn: string;
isAsc: string;
}
export interface OssForm {
file: undefined | string;
}
export interface SysOssExt {
bizType?: string;
fileSize?: number;
contentType?: string;
source?: string;
uploadIp?: string;
remark?: string;
tags?: string[];
refId?: string;
refType?: string;
isTemp?: boolean;
md5?: string;
} }

View File

@ -1,61 +1,49 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request';
import type { OssConfigForm, OssConfigQuery, OssConfigVO } from './types'; import type { OssConfigForm, OssConfigQuery, OssConfigVO } from './types';
// 查询对象存储配置列表 export function listOssConfig(query: OssConfigQuery) {
export function listOssConfig(query: OssConfigQuery): AxiosPromise<PageResult<OssConfigVO>> { return request<R<PageResult<OssConfigVO>>>({
return request({
url: '/resource/oss/config/list', url: '/resource/oss/config/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 查询对象存储配置详细 export function getOssConfig(ossConfigId: string | number) {
export function getOssConfig(ossConfigId: string | number): AxiosPromise<OssConfigVO> { return request<R<OssConfigVO>>({
return request({ url: `/resource/oss/config/${ossConfigId}`,
url: '/resource/oss/config/' + ossConfigId,
method: 'get' method: 'get'
}); });
} }
// 新增对象存储配置
export function addOssConfig(data: OssConfigForm) { export function addOssConfig(data: OssConfigForm) {
return request({ return request<R>({
url: '/resource/oss/config', url: '/resource/oss/config',
method: 'post', method: 'post',
data: data data
}); });
} }
// 修改对象存储配置
export function updateOssConfig(data: OssConfigForm) { export function updateOssConfig(data: OssConfigForm) {
return request({ return request<R>({
url: '/resource/oss/config', url: '/resource/oss/config',
method: 'put', method: 'put',
data: data data
}); });
} }
// 删除对象存储配置
export function delOssConfig(ossConfigId: string | number | Array<string | number>) { export function delOssConfig(ossConfigId: string | number | Array<string | number>) {
return request({ return request<R>({
url: '/resource/oss/config/' + ossConfigId, url: `/resource/oss/config/${ossConfigId}`,
method: 'delete' method: 'delete'
}); });
} }
// 对象存储状态修改
export function changeOssConfigStatus(ossConfigId: string | number, status: string, configKey: string) { export function changeOssConfigStatus(ossConfigId: string | number, status: string, configKey: string) {
const data = { return request<R>({
ossConfigId,
status,
configKey
};
return request({
url: '/resource/oss/config/changeStatus', url: '/resource/oss/config/changeStatus',
method: 'put', method: 'put',
data: data data: { ossConfigId, status, configKey }
}); });
} }

View File

@ -1,38 +1,39 @@
import type { BaseEntity, PageQuery } from '@/api/types';
export interface OssConfigForm {
ossConfigId?: string | number;
configKey?: string;
accessKey?: string;
secretKey?: string;
bucketName?: string;
prefix?: string;
endpoint?: string;
domainUrl?: string;
isHttps?: string;
accessPolicy?: string;
region?: string;
status?: string;
remark?: string;
}
export interface OssConfigQuery extends PageQuery {
configKey?: string;
bucketName?: string;
status?: string;
}
export interface OssConfigVO extends BaseEntity { export interface OssConfigVO extends BaseEntity {
ossConfigId: number | string; ossConfigId: number | string;
configKey: string; configKey: string;
accessKey: string; accessKey: string;
secretKey: string; secretKey: string;
bucketName: string; bucketName: string;
prefix: string; prefix?: string;
endpoint: string; endpoint: string;
domainUrl: string; domainUrl?: string;
isHttps: string; isHttps: string;
region: string; region?: string;
status: string; status: string;
ext1: string; ext1?: string;
remark: string;
accessPolicy: string; accessPolicy: string;
} }
export interface OssConfigQuery extends PageQuery {
configKey: string;
bucketName: string;
status: string;
}
export interface OssConfigForm {
ossConfigId: string | number | undefined;
configKey: string;
accessKey: string;
secretKey: string;
bucketName: string;
prefix: string;
endpoint: string;
domainUrl: string;
isHttps: string;
accessPolicy: string;
region: string;
status: string;
remark: string;
}

View File

@ -1,70 +1,64 @@
import type { PageResult } from '@/api/types'; import type { DeptTreeVO } from '@/api/system/dept/types';
import type { AxiosPromise } from '@/utils/api-types'; import type { PageResult, R } from '@/api/types';
import request from '@/utils/request'; import request from '@/api/request';
import type { DeptTreeVO } from '../dept/types';
import type { PostForm, PostQuery, PostVO } from './types'; import type { PostForm, PostQuery, PostVO } from './types';
// 查询岗位列表 export function listPost(query: PostQuery) {
export function listPost(query: PostQuery): AxiosPromise<PageResult<PostVO>> { return request<R<PageResult<PostVO>>>({
return request({
url: '/system/post/list', url: '/system/post/list',
method: 'get', method: 'get',
params: query params: query
}); });
} }
// 查询岗位详细 export function getPost(postId: string | number) {
export function getPost(postId: string | number): AxiosPromise<PostVO> { return request<R<PostVO>>({
return request({ url: `/system/post/${postId}`,
url: '/system/post/' + postId,
method: 'get' method: 'get'
}); });
} }
// 获取岗位选择框列表 export function optionSelect(deptId?: number | string, postIds?: Array<number | string>) {
export function optionselect(deptId?: number | string, postIds?: (number | string)[]): AxiosPromise<PostVO[]> { return request<R<PostVO[]>>({
return request({
url: '/system/post/optionselect', url: '/system/post/optionselect',
method: 'get', method: 'get',
params: { params: {
postIds: postIds, postIds,
deptId: deptId deptId
} }
}); });
} }
// 新增岗位
export function addPost(data: PostForm) { export function addPost(data: PostForm) {
return request({ return request<R>({
url: '/system/post', url: '/system/post',
method: 'post', method: 'post',
data: data data
}); });
} }
// 修改岗位
export function updatePost(data: PostForm) { export function updatePost(data: PostForm) {
return request({ return request<R>({
url: '/system/post', url: '/system/post',
method: 'put', method: 'put',
data: data data
}); });
} }
// 删除岗位 export function delPost(postId: string | number | Array<string | number>) {
export function delPost(postId: string | number | (string | number)[]) { return request<R>({
return request({ url: `/system/post/${postId}`,
url: '/system/post/' + postId,
method: 'delete' method: 'delete'
}); });
} }
/** export function postDeptTreeSelect() {
* return request<R<DeptTreeVO[]>>({
*/
export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
return request({
url: '/system/post/deptTree', url: '/system/post/deptTree',
method: 'get' method: 'get'
}); });
}; }
export function deptTreeSelect() {
return postDeptTreeSelect();
}

View File

@ -1,3 +1,25 @@
import type { BaseEntity, PageQuery } from '@/api/types';
export interface PostForm {
postId?: number | string;
deptId?: number | string;
postCode?: string;
postName?: string;
postCategory?: string;
postSort?: number;
status?: string;
remark?: string;
}
export interface PostQuery extends PageQuery {
deptId?: number | string;
belongDeptId?: number | string;
postCode?: string;
postName?: string;
postCategory?: string;
status?: string;
}
export interface PostVO extends BaseEntity { export interface PostVO extends BaseEntity {
postId: number | string; postId: number | string;
deptId: number | string; deptId: number | string;
@ -7,25 +29,4 @@ export interface PostVO extends BaseEntity {
deptName: string; deptName: string;
postSort: number; postSort: number;
status: string; status: string;
remark: string;
}
export interface PostForm {
postId: number | string | undefined;
deptId: number | string | undefined;
postCode: string;
postName: string;
postCategory: string;
postSort: number;
status: string;
remark: string;
}
export interface PostQuery extends PageQuery {
deptId: number | string;
belongDeptId: number | string;
postCode: string;
postName: string;
postCategory: string;
status: string;
} }

View File

@ -1,160 +1,115 @@
import type { UserQuery, UserVO } from '@/api/system/user/types'; import type { UserQuery, UserVO } from '@/api/system/user/types';
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request'; import type { RoleDeptTree, RoleForm, RoleQuery, RoleVO } from './types';
import type { RoleDeptTree, RoleQuery, RoleVO } from './types';
export const listRole = (query: RoleQuery): AxiosPromise<PageResult<RoleVO>> => { export function listRole(query: RoleQuery) {
return request({ return request<R<PageResult<RoleVO>>>({
url: '/system/role/list', url: '/system/role/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function optionSelect(roleIds: Array<number | string>) {
* roleIds查询角色 return request<R<RoleVO[]>>({
* @param roleIds url: `/system/role/optionselect?roleIds=${roleIds}`,
*/
export const optionSelect = (roleIds: (number | string)[]): AxiosPromise<RoleVO[]> => {
return request({
url: '/system/role/optionselect?roleIds=' + roleIds,
method: 'get' method: 'get'
}); });
}; }
/** export function getRole(roleId: string | number) {
* return request<R<RoleVO>>({
*/ url: `/system/role/${roleId}`,
export const getRole = (roleId: string | number): AxiosPromise<RoleVO> => {
return request({
url: '/system/role/' + roleId,
method: 'get' method: 'get'
}); });
}; }
/** export function addRole(data: RoleForm) {
* return request<R>({
*/
export const addRole = (data: any) => {
return request({
url: '/system/role', url: '/system/role',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function updateRole(data: RoleForm) {
* return request<R>({
* @param data
*/
export const updateRole = (data: any) => {
return request({
url: '/system/role', url: '/system/role',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function updateRolePermission(data: RoleForm) {
* + return request<R>({
*/
export const updateRolePermission = (data: any) => {
return request({
url: '/system/role/permission', url: '/system/role/permission',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function changeRoleStatus(roleId: string | number, status: string) {
* return request<R>({
*/
export const changeRoleStatus = (roleId: string | number, status: string) => {
const data = {
roleId,
status
};
return request({
url: '/system/role/changeStatus', url: '/system/role/changeStatus',
method: 'put', method: 'put',
data: data data: {
roleId,
status
}
}); });
}; }
/** export function delRole(roleId: Array<string | number> | string | number) {
* return request<R>({
*/ url: `/system/role/${roleId}`,
export const delRole = (roleId: Array<string | number> | string | number) => {
return request({
url: '/system/role/' + roleId,
method: 'delete' method: 'delete'
}); });
}; }
/** export function allocatedUserList(query: UserQuery) {
* return request<R<PageResult<UserVO>>>({
*/
export const allocatedUserList = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
return request({
url: '/system/role/authUser/allocatedList', url: '/system/role/authUser/allocatedList',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function unallocatedUserList(query: UserQuery) {
* return request<R<PageResult<UserVO>>>({
*/
export const unallocatedUserList = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
return request({
url: '/system/role/authUser/unallocatedList', url: '/system/role/authUser/unallocatedList',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function authUserCancel(data: { roleId: string | number; userId: string | number }) {
* return request<R>({
*/
export const authUserCancel = (data: any) => {
return request({
url: '/system/role/authUser/cancel', url: '/system/role/authUser/cancel',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function authUserCancelAll(data: { roleId: string | number; userIds: string }) {
* return request<R>({
*/
export const authUserCancelAll = (data: any) => {
return request({
url: '/system/role/authUser/cancelAll', url: '/system/role/authUser/cancelAll',
method: 'put', method: 'put',
params: data params: data
}); });
}; }
/** export function authUserSelectAll(data: { roleId: string | number; userIds: string }) {
* return request<R>({
*/
export const authUserSelectAll = (data: any) => {
return request({
url: '/system/role/authUser/selectAll', url: '/system/role/authUser/selectAll',
method: 'put', method: 'put',
params: data params: data
}); });
}; }
// 根据角色ID查询部门树结构
export const deptTreeSelect = (roleId: string | number): AxiosPromise<RoleDeptTree> => { export function deptTreeSelect(roleId: string | number) {
return request({ return request<R<RoleDeptTree>>({
url: '/system/role/deptTree/' + roleId, url: `/system/role/deptTree/${roleId}`,
method: 'get' method: 'get'
}); });
}; }
export default {
optionSelect,
listRole
};

View File

@ -1,17 +1,35 @@
/** import type { DeptTreeVO } from '@/api/system/dept/types';
* import type { MenuTreeOption } from '@/api/system/menu/types';
*/ import type { BaseEntity, PageQuery } from '@/api/types';
export interface DeptTreeOption {
id: string;
label: string;
parentId: string;
weight: number;
children?: DeptTreeOption[];
}
export interface RoleDeptTree { export interface RoleDeptTree {
checkedKeys: string[]; checkedKeys: Array<string | number>;
depts: DeptTreeOption[]; depts: DeptTreeVO[];
}
export interface RoleForm {
roleId?: string | number;
roleName?: string;
roleKey?: string;
roleSort?: number;
status?: string;
menuCheckStrictly?: boolean;
deptCheckStrictly?: boolean;
remark?: string;
dataScope?: string;
menuIds?: Array<string | number>;
deptIds?: Array<string | number>;
}
export interface RoleMenuTree {
menus: MenuTreeOption[];
checkedKeys: Array<string | number>;
}
export interface RoleQuery extends PageQuery {
roleName?: string;
roleKey?: string;
status?: string;
} }
export interface RoleVO extends BaseEntity { export interface RoleVO extends BaseEntity {
@ -20,33 +38,11 @@ export interface RoleVO extends BaseEntity {
roleKey: string; roleKey: string;
roleSort: number; roleSort: number;
dataScope: string; dataScope: string;
menuCheckStrictly: boolean; menuCheckStrictly?: boolean;
deptCheckStrictly: boolean; deptCheckStrictly?: boolean;
status: string; status: string;
delFlag: string;
remark?: any;
flag: boolean; flag: boolean;
admin: boolean;
menuIds?: Array<string | number>; menuIds?: Array<string | number>;
deptIds?: Array<string | number>; deptIds?: Array<string | number>;
admin: boolean;
}
export interface RoleQuery extends PageQuery {
roleName: string;
roleKey: string;
status: string;
}
export interface RoleForm {
roleName: string;
roleKey: string;
roleSort: number;
status: string;
menuCheckStrictly: boolean;
deptCheckStrictly: boolean;
remark: string;
dataScope?: string;
roleId: string | undefined;
menuIds: Array<string | number>;
deptIds: Array<string | number>;
} }

View File

@ -1,23 +1,23 @@
import request from '@/utils/request'; import type { R } from '@/api/types';
import request from '@/api/request';
import type { SocialAuthVO } from './types';
// 获取跳转URL
export function authRouterUrl(source: string) { export function authRouterUrl(source: string) {
return request({ return request<R<string>>({
url: '/auth/binding/' + source, url: `/auth/binding/${source}`,
method: 'get' method: 'get'
}); });
} }
// 解绑账号
export function authUnlock(authId: string) { export function authUnlock(authId: string) {
return request({ return request<R>({
url: '/auth/unlock/' + authId, url: `/auth/unlock/${authId}`,
method: 'delete' method: 'delete'
}); });
} }
//获取授权列表
export function getAuthList() { export function getAuthList() {
return request({ return request<R<SocialAuthVO[]>>({
url: '/system/social/list', url: '/system/social/list',
method: 'get' method: 'get'
}); });

View File

@ -0,0 +1,7 @@
export interface SocialAuthVO {
id: string;
source?: string;
avatar?: string;
userName?: string;
createTime?: string;
}

View File

@ -1,229 +1,154 @@
import type { DeptTreeVO } from '@/api/system/dept/types';
import type { RoleVO } from '@/api/system/role/types'; import type { RoleVO } from '@/api/system/role/types';
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request';
import { parseStrEmpty } from '@/utils/ruoyi'; import { parseStrEmpty } from '@/utils/ruoyi';
import type { DeptTreeVO } from './../dept/types';
import type { UserForm, UserInfoVO, UserProfileForm, UserQuery, UserVO } from './types'; import type { UserForm, UserInfoVO, UserProfileForm, UserQuery, UserVO } from './types';
/** export function listUser(query: UserQuery) {
* return request<R<PageResult<UserVO>>>({
* @param query
*/
export const listUser = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
return request({
url: '/system/user/list', url: '/system/user/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function optionSelect(userIds: Array<number | string>) {
* ids查询用户 return request<R<UserVO[]>>({
* @param userIds url: `/system/user/optionselect?userIds=${userIds}`,
*/
export const optionSelect = (userIds: (number | string)[]): AxiosPromise<UserVO[]> => {
return request({
url: '/system/user/optionselect?userIds=' + userIds,
method: 'get' method: 'get'
}); });
}; }
/** export function getUser(userId?: string | number) {
* return request<R<UserInfoVO>>({
* @param userId url: `/system/user/${parseStrEmpty(userId)}`,
*/
export const getUser = (userId?: string | number): AxiosPromise<UserInfoVO> => {
return request({
url: '/system/user/' + parseStrEmpty(userId),
method: 'get' method: 'get'
}); });
}; }
/** export function addUser(data: UserForm) {
* return request<R>({
*/
export const addUser = (data: UserForm) => {
return request({
url: '/system/user', url: '/system/user',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function updateUser(data: UserForm) {
* return request<R>({
*/
export const updateUser = (data: UserForm) => {
return request({
url: '/system/user', url: '/system/user',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function delUser(userId: Array<string | number> | string | number) {
* return request<R>({
* @param userId ID url: `/system/user/${userId}`,
*/
export const delUser = (userId: Array<string | number> | string | number) => {
return request({
url: '/system/user/' + userId,
method: 'delete' method: 'delete'
}); });
}; }
/** export function resetUserPwd(userId: string | number, password: string) {
* return request<R>({
* @param userId ID
* @param password
*/
export const resetUserPwd = (userId: string | number, password: string) => {
const data = {
userId,
password
};
return request({
url: '/system/user/resetPwd', url: '/system/user/resetPwd',
method: 'put', method: 'put',
headers: { headers: {
isEncrypt: true, isEncrypt: true,
repeatSubmit: false repeatSubmit: false
}, },
data: data data: {
userId,
password
}
}); });
}; }
/** export function changeUserStatus(userId: number | string, status: string) {
* return request<R>({
* @param userId ID
* @param status
*/
export const changeUserStatus = (userId: number | string, status: string) => {
const data = {
userId,
status
};
return request({
url: '/system/user/changeStatus', url: '/system/user/changeStatus',
method: 'put', method: 'put',
data: data data: {
userId,
status
}
}); });
}; }
/** export function unlockUser(userId: number | string) {
* return request<R>({
* @param userId ID url: `/system/user/unlock/${userId}`,
*/
export const unlockUser = (userId: number | string) => {
return request({
url: '/system/user/unlock/' + userId,
method: 'get' method: 'get'
}); });
}; }
/** export function getUserProfile() {
* return request<R<UserInfoVO>>({
*/
export const getUserProfile = (): AxiosPromise<UserInfoVO> => {
return request({
url: '/system/user/profile', url: '/system/user/profile',
method: 'get' method: 'get'
}); });
}; }
/** export function updateUserProfile(data: UserProfileForm) {
* return request<R>({
* @param data
*/
export const updateUserProfile = (data: UserProfileForm) => {
return request({
url: '/system/user/profile', url: '/system/user/profile',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function updateUserPwd(oldPassword: string, newPassword: string) {
* return request<R>({
* @param oldPassword
* @param newPassword
*/
export const updateUserPwd = (oldPassword: string, newPassword: string) => {
const data = {
oldPassword,
newPassword
};
return request({
url: '/system/user/profile/updatePwd', url: '/system/user/profile/updatePwd',
method: 'put', method: 'put',
headers: { headers: {
isEncrypt: true, isEncrypt: true,
repeatSubmit: false repeatSubmit: false
}, },
data: data data: {
oldPassword,
newPassword
}
}); });
}; }
/** export function importUser(data: FormData, updateSupport: boolean) {
* return request<R>({
* @param userId ID url: '/system/user/importData',
*/ method: 'post',
export const getAuthRole = (userId: string | number): AxiosPromise<{ user: UserVO; roles: RoleVO[] }> => { params: {
return request({ updateSupport: updateSupport ? 1 : 0
url: '/system/user/authRole/' + userId, },
data
});
}
export function getAuthRole(userId: string | number) {
return request<R<{ user: UserVO; roles: RoleVO[] }>>({
url: `/system/user/authRole/${userId}`,
method: 'get' method: 'get'
}); });
}; }
/** export function updateAuthRole(data: { userId: string; roleIds: string }) {
* return request<R>({
* @param data ID
*/
export const updateAuthRole = (data: { userId: string; roleIds: string }) => {
return request({
url: '/system/user/authRole', url: '/system/user/authRole',
method: 'put', method: 'put',
params: data params: data
}); });
}; }
/** export function deptTreeSelect() {
* return request<R<DeptTreeVO[]>>({
* @param deptId
*/
export const listUserByDeptId = (deptId: string | number): AxiosPromise<UserVO[]> => {
return request({
url: '/system/user/list/dept/' + deptId,
method: 'get'
});
};
/**
*
*/
export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
return request({
url: '/system/user/deptTree', url: '/system/user/deptTree',
method: 'get' method: 'get'
}); });
}; }
export default { export function listUserByDeptId(deptId: string | number) {
listUser, return request<R<UserVO[]>>({
getUser, url: `/system/user/list/dept/${deptId}`,
optionSelect, method: 'get'
addUser, });
updateUser, }
delUser,
resetUserPwd,
changeUserStatus,
unlockUser,
getUserProfile,
updateUserProfile,
updateUserPwd,
getAuthRole,
updateAuthRole,
deptTreeSelect,
listUserByDeptId
};

View File

@ -1,81 +1,37 @@
import type { DeptVO } from '@/api/system/dept/types';
import type { PostVO } from '@/api/system/post/types'; import type { PostVO } from '@/api/system/post/types';
import type { RoleVO } from '@/api/system/role/types'; import type { RoleVO } from '@/api/system/role/types';
import type { BaseEntity, PageQuery } from '@/api/types';
/**
*
*/
export interface UserInfo { export interface UserInfo {
user: UserVO; user: {
userId: string | number;
userName: string;
nickName?: string;
avatar?: string | number;
avatarUrl?: string;
deptName?: string;
};
roles: string[]; roles: string[];
permissions: string[]; permissions: string[];
} }
/** export interface UserForm {
* userId?: string | number;
*/ deptId?: number | string;
export interface UserQuery extends PageQuery {
userName?: string; userName?: string;
nickName?: string; nickName?: string;
phoneNumber?: string; password?: string;
status?: string;
deptId?: string | number;
roleId?: string | number;
userIds?: string | number | (string | number)[] | undefined;
}
/**
*
*/
export interface UserVO extends BaseEntity {
userId: string | number;
tenantId: string;
deptId: number;
userName: string;
nickName: string;
userType: string;
email: string;
phoneNumber: string;
gender: string;
avatar?: string | number;
avatarUrl?: string;
status: string;
delFlag: string;
loginIp: string;
loginDate: string;
remark: string;
deptName: string;
/** 详情接口可能返回嵌套部门 */
dept?: { deptName?: string };
roles: RoleVO[];
roleIds: any;
postIds: any;
roleId: any;
admin: boolean;
}
/**
*
*/
export interface UserForm {
id?: string;
userId?: string;
deptId?: number;
userName: string;
nickName?: string;
password: string;
phoneNumber?: string; phoneNumber?: string;
email?: string; email?: string;
gender?: string; gender?: string;
status: string; status?: string;
remark?: string; remark?: string;
avatar?: string | number; avatar?: string | number;
postIds: string[]; postIds?: Array<string | number>;
roleIds: string[]; roleIds?: Array<string | number>;
} }
/**
*
*/
export interface UserProfileForm { export interface UserProfileForm {
nickName?: string; nickName?: string;
phoneNumber?: string; phoneNumber?: string;
@ -85,17 +41,42 @@ export interface UserProfileForm {
} }
export interface UserInfoVO { export interface UserInfoVO {
user: UserVO; user?: UserVO;
roles: RoleVO[]; roles: RoleVO[];
roleIds: string[]; roleIds: Array<string | number>;
posts: PostVO[]; posts: PostVO[];
postIds: string[]; postIds: Array<string | number>;
roleGroup: string; roleGroup?: string;
postGroup: string; postGroup?: string;
} }
export interface ResetPwdForm { export interface UserQuery extends PageQuery {
oldPassword: string; userName?: string;
newPassword: string; nickName?: string;
confirmPassword: string; phoneNumber?: string;
status?: string;
deptId?: string | number;
roleId?: string | number;
userIds?: string | number;
}
export interface UserVO extends BaseEntity {
userId?: string | number;
tenantId?: string;
deptId?: number | string;
userName?: string;
nickName?: string;
userType?: string;
email?: string;
phoneNumber?: string;
gender?: string;
avatar?: string | number;
avatarUrl?: string;
status?: string;
deptName?: string;
roles?: RoleVO[];
admin?: boolean;
loginIp?: string;
loginDate?: string;
dept?: DeptVO;
} }

View File

@ -1,81 +1,78 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request'; import type { DbTableQuery, DbTableVO, GenTableDetailPayload, TableQuery, TableVO } from './types';
import type { DbTableForm, DbTableQuery, DbTableVO, GenTableDetailPayload, TableQuery, TableVO } from './types';
export type { GenTableDetailPayload } from './types'; export function listTable(query: TableQuery) {
return request<R<PageResult<TableVO>>>({
// 查询生成表数据
export const listTable = (query: TableQuery): AxiosPromise<PageResult<TableVO>> => {
return request({
url: '/tool/gen/list', url: '/tool/gen/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
// 查询db数据库列表
export const listDbTable = (query: DbTableQuery): AxiosPromise<PageResult<DbTableVO>> => { export function listDbTable(query: DbTableQuery) {
return request({ return request<R<PageResult<DbTableVO>>>({
url: '/tool/gen/db/list', url: '/tool/gen/db/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
// 查询表详细信息 export function getGenTable(tableId: string | number) {
export const getGenTable = (tableId: string | number): AxiosPromise<GenTableDetailPayload> => { return request<R<GenTableDetailPayload>>({
return request({ url: `/tool/gen/${tableId}`,
url: '/tool/gen/' + tableId,
method: 'get' method: 'get'
}); });
}; }
// 修改代码生成信息 export function updateGenTable(data: DbTableVO & { columns?: unknown[]; params?: Record<string, unknown> }) {
export const updateGenTable = (data: DbTableForm): AxiosPromise<unknown> => { return request<R>({
return request({
url: '/tool/gen', url: '/tool/gen',
method: 'put', method: 'put',
data: data data
}); });
}; }
// 导入表 export function importTable(data: { tables: string; dataName: string }) {
export const importTable = (data: { tables: string; dataName: string }): AxiosPromise<unknown> => { return request<R>({
return request({
url: '/tool/gen/importTable', url: '/tool/gen/importTable',
method: 'post', method: 'post',
params: data params: data
}); });
}; }
// 预览生成代码 export function previewTable(tableId: string | number) {
export const previewTable = (tableId: string | number) => { return request<R<Record<string, string>>>({
return request({ url: `/tool/gen/preview/${tableId}`,
url: '/tool/gen/preview/' + tableId,
method: 'get' method: 'get'
}); });
}; }
// 删除表数据 export function delTable(tableId: string | number | Array<string | number>) {
export const delTable = (tableId: string | number | Array<string | number>) => { return request<R>({
return request({ url: `/tool/gen/${tableId}`,
url: '/tool/gen/' + tableId,
method: 'delete' method: 'delete'
}); });
}; }
// 同步数据库 export function synchDb(tableId: string | number) {
export const synchDb = (tableId: string | number) => { return request<R>({
return request({ url: `/tool/gen/synchDb/${tableId}`,
url: '/tool/gen/synchDb/' + tableId,
method: 'get' method: 'get'
}); });
}; }
// 获取数据源名称 export function getDataNames() {
export const getDataNames = () => { return request<R<string[]>>({
return request({
url: '/tool/gen/getDataNames', url: '/tool/gen/getDataNames',
method: 'get' method: 'get'
}); });
}; }
export function batchGenCode(tableIdStr: string) {
return request<Blob>({
url: `/tool/gen/batchGenCode?tableIdStr=${tableIdStr}`,
method: 'get',
responseType: 'blob'
});
}

View File

@ -1,215 +1,91 @@
export interface TableVO extends BaseEntity { import type { BaseEntity, PageQuery } from '@/api/types';
createDept: number | string;
tableId: string | number;
dataName: string;
tableName: string;
tableComment: string;
className: string;
tplCategory: string;
frontendType: string;
packageName: string;
moduleName: string;
businessName: string;
functionName: string;
functionAuthor: string;
pkColumn?: any;
columns?: any;
options?: any;
remark?: any;
treeCode?: any;
treeParentCode?: any;
treeName?: any;
menuIds?: any;
parentMenuId?: any;
parentMenuName?: any;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
tree: boolean;
crud: boolean;
}
export interface TableQuery extends PageQuery {
tableName: string;
tableComment: string;
dataName: string;
}
export interface DbColumnVO extends BaseEntity { export interface DbColumnVO extends BaseEntity {
createDept?: any; columnId?: string | number;
columnId?: any; tableId?: string | number;
tableId?: any; columnName?: string;
columnName?: any; columnComment?: string;
columnComment?: any; columnType?: string;
columnType?: any; javaType?: string;
javaType?: any; javaField?: string;
javaField?: any; isPk?: string;
isPk?: any; isIncrement?: string;
isIncrement?: any; isRequired?: string;
isRequired?: any; isInsert?: string;
isInsert?: any; isEdit?: string;
isEdit?: any; isList?: string;
isList?: any; isQuery?: string;
isQuery?: any; queryType?: string;
queryType?: any; htmlType?: string;
htmlType?: any; dictType?: string;
dictType?: any; sort?: number;
sort?: any; increment?: boolean;
increment: boolean; capJavaField?: string;
capJavaField?: any; usableColumn?: boolean;
usableColumn: boolean; superColumn?: boolean;
superColumn: boolean; list?: boolean;
list: boolean; pk?: boolean;
pk: boolean; insert?: boolean;
insert: boolean; edit?: boolean;
edit: boolean; query?: boolean;
query: boolean; required?: boolean;
required: boolean;
}
export interface DbTableVO {
createDept?: any;
tableId?: any;
tableName: string;
tableComment: string;
className?: any;
tplCategory?: any;
frontendType?: string;
packageName?: any;
moduleName?: any;
businessName?: any;
functionName?: any;
functionAuthor?: any;
pkColumn?: any;
columns: DbColumnVO[];
options?: any;
remark?: any;
treeCode?: any;
treeParentCode?: any;
treeName?: any;
menuIds?: any;
parentMenuId?: any;
parentMenuName?: any;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
tree: boolean;
crud: boolean;
} }
export interface DbTableQuery extends PageQuery { export interface DbTableQuery extends PageQuery {
dataName: string; dataName?: string;
tableName: string; tableName?: string;
tableComment: string; tableComment?: string;
}
export interface DbTableVO extends Omit<TableVO, 'tableId'> {
tableId?: string | number;
columns?: DbColumnVO[];
options?: string;
remark?: string;
treeCode?: string;
treeParentCode?: string;
treeName?: string;
menuIds?: Array<string | number>;
parentMenuId?: string | number;
parentMenuName?: string;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
frontendType?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
} }
/**
* data
* - info GenTable
* - rows GenTableColumn[]
*/
export interface GenTableDetailPayload { export interface GenTableDetailPayload {
info: DbTableVO; info: DbTableVO;
rows: DbColumnVO[]; rows: DbColumnVO[];
} }
export interface DbColumnForm extends BaseEntity { export interface TableQuery extends PageQuery {
createDept: number; tableName?: string;
columnId: string; tableComment?: string;
tableId: string; dataName?: string;
columnName: string;
columnComment: string;
columnType: string;
javaType: string;
javaField: string;
isPk: string;
isIncrement: string;
isRequired: string;
isInsert?: any;
isEdit: string;
isList: string;
isQuery?: any;
queryType: string;
htmlType: string;
dictType: string;
sort: number;
increment: boolean;
capJavaField: string;
usableColumn: boolean;
superColumn: boolean;
list: boolean;
pk: boolean;
insert: boolean;
edit: boolean;
query: boolean;
required: boolean;
} }
export interface DbParamForm { export interface TableVO extends BaseEntity {
treeCode?: any; createDept?: number | string;
treeName?: any; tableId: string | number;
treeParentCode?: any; dataName?: string;
parentMenuId: string;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
}
export interface DbTableForm extends BaseEntity {
createDept?: any;
tableId: string | string;
tableName: string; tableName: string;
tableComment: string; tableComment?: string;
className: string; className?: string;
tplCategory: string; tplCategory?: string;
frontendType: string; frontendType?: string;
packageName: string; packageName?: string;
moduleName: string; moduleName?: string;
businessName: string; businessName?: string;
functionName: string; functionName?: string;
functionAuthor: string; functionAuthor?: string;
pkColumn?: any; tree?: boolean;
columns: DbColumnForm[]; crud?: boolean;
options: string;
remark?: any;
treeCode?: any;
treeParentCode?: any;
treeName?: any;
menuIds?: any;
parentMenuId: string;
parentMenuName?: any;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
tree: boolean;
crud: boolean;
params: DbParamForm;
} }

View File

@ -1,51 +1,88 @@
/** import type { MenuDataItem } from '@ant-design/pro-components';
*
*/
export type RegisterForm = {
username: string;
password: string;
confirmPassword?: string;
code?: string;
uuid?: string;
userType?: string;
};
/** export interface BaseEntity {
* createBy?: string;
*/ createTime?: string;
export interface LoginData { updateBy?: string;
updateTime?: string;
remark?: string;
}
export interface LoginParams {
username?: string; username?: string;
password?: string; password?: string;
code?: string;
uuid?: string;
rememberMe?: boolean; rememberMe?: boolean;
socialCode?: string; socialCode?: string;
socialState?: string; socialState?: string;
source?: string; source?: string;
code?: string; clientId?: string;
uuid?: string; grantType?: string;
clientId: string;
grantType: string;
} }
/**
*
*/
export interface LoginResult { export interface LoginResult {
access_token: string; access_token: string;
} }
/** export interface PageQuery {
* pageNum?: number;
*/ pageSize?: number;
orderByColumn?: string;
isAsc?: 'ascending' | 'descending';
params?: Record<string, unknown>;
}
export interface PageResult<T = unknown> {
total: number;
rows: T[];
}
export interface R<T = unknown> {
code: number;
msg: string;
data: T;
}
export interface RegisterParams {
username?: string;
password?: string;
confirmPassword?: string;
code?: string;
uuid?: string;
userType?: string;
clientId?: string;
grantType?: string;
}
export interface VerifyCodeResult { export interface VerifyCodeResult {
captchaEnabled: boolean; captchaEnabled: boolean;
uuid?: string; uuid?: string;
img?: string; img?: string;
} }
/** export interface RouteMeta {
* title?: string;
*/ icon?: string | null;
export interface PageResult<T = any> { noCache?: boolean;
total: number; link?: string;
rows: T[]; hidden?: boolean;
activeMenu?: string;
} }
export interface BackendRoute {
name?: string;
path: string;
component?: string;
redirect?: string;
query?: string;
hidden?: boolean;
alwaysShow?: boolean;
meta?: RouteMeta;
children?: BackendRoute[];
}
export type RuntimeMenuItem = Omit<MenuDataItem, 'routes'> & {
backendRoute?: BackendRoute;
routes?: RuntimeMenuItem[];
};

View File

@ -1,76 +1,49 @@
import type { CategoryForm, CategoryQuery, CategoryTreeVO, CategoryVO } from '@/api/workflow/category/types'; import type { R } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request'; import type { CategoryForm, CategoryQuery, CategoryTreeVO, CategoryVO } from './types';
/** export function listCategory(query?: CategoryQuery) {
* return request<R<CategoryVO[]>>({
* @param query
* @returns {*}
*/
export const listCategory = (query?: CategoryQuery): AxiosPromise<CategoryVO[]> => {
return request({
url: '/workflow/category/list', url: '/workflow/category/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function getCategory(categoryId: string | number) {
* return request<R<CategoryVO>>({
* @param categoryId url: `/workflow/category/${categoryId}`,
*/
export const getCategory = (categoryId: string | number): AxiosPromise<CategoryVO> => {
return request({
url: '/workflow/category/' + categoryId,
method: 'get' method: 'get'
}); });
}; }
/** export function addCategory(data: CategoryForm) {
* return request<R>({
* @param data
*/
export const addCategory = (data: CategoryForm) => {
return request({
url: '/workflow/category', url: '/workflow/category',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function updateCategory(data: CategoryForm) {
* return request<R>({
* @param data
*/
export const updateCategory = (data: CategoryForm) => {
return request({
url: '/workflow/category', url: '/workflow/category',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function delCategory(categoryId: string | number | Array<string | number>) {
* return request<R>({
* @param categoryId url: `/workflow/category/${categoryId}`,
*/
export const delCategory = (categoryId: string | number | Array<string | number>) => {
return request({
url: '/workflow/category/' + categoryId,
method: 'delete' method: 'delete'
}); });
}; }
/** export function categoryTree(query?: CategoryForm) {
* return request<R<CategoryTreeVO[]>>({
* @param query id url: '/workflow/category/categoryTree',
* @returns
*/
export const categoryTree = (query?: CategoryForm): AxiosPromise<CategoryTreeVO[]> => {
return request({
url: `/workflow/category/categoryTree`,
method: 'get', method: 'get',
params: query params: query
}); });
}; }

View File

@ -1,67 +1,29 @@
import type { BaseEntity } from '@/api/types';
export interface CategoryForm extends BaseEntity {
categoryId?: string | number;
categoryName?: string;
parentId?: string | number;
orderNum?: number;
}
export interface CategoryQuery {
categoryName?: string;
}
export interface CategoryTreeVO { export interface CategoryTreeVO {
id: number | string; id: number | string;
label: string; label: string;
parentId: number | string; parentId: number | string;
weight: number; weight: number;
children: CategoryTreeVO[]; children?: CategoryTreeVO[];
} }
export interface CategoryVO { export interface CategoryVO {
/**
* ID
*/
categoryId: string | number; categoryId: string | number;
/**
* id
*/
parentId: string | number;
/**
*
*/
categoryName: string;
/**
*
*/
orderNum: number;
/**
*
*/
createTime: string;
/**
*
*/
children: CategoryVO[];
}
export interface CategoryForm extends BaseEntity {
/**
* ID
*/
categoryId?: string | number;
/**
*
*/
categoryName?: string;
/**
* id
*/
parentId?: string | number; parentId?: string | number;
categoryName: string;
/** orderNum: number;
* createTime?: string;
*/ children?: CategoryVO[];
orderNum?: number;
}
export interface CategoryQuery {
/**
*
*/
categoryName?: string;
} }

View File

@ -1,176 +1,137 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { import request from '@/api/request';
definitionXmlVO, import type { definitionXmlVO, FlowDefinitionForm, FlowDefinitionQuery, FlowDefinitionVO } from './types';
FlowDefinitionForm,
FlowDefinitionQuery,
FlowDefinitionVo
} from '@/api/workflow/definition/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
/** export function listDefinition(query: FlowDefinitionQuery) {
* return request<R<PageResult<FlowDefinitionVO>>>({
* @param query id url: '/workflow/definition/list',
* @returns
*/
export const listDefinition = (query: FlowDefinitionQuery): AxiosPromise<PageResult<FlowDefinitionVo>> => {
return request({
url: `/workflow/definition/list`,
method: 'get', method: 'get',
params: query params: query
}); });
};
/**
*
* @param query id
* @returns
*/
export const unPublishList = (query: FlowDefinitionQuery): AxiosPromise<PageResult<FlowDefinitionVo>> => {
return request({
url: `/workflow/definition/unPublishList`,
method: 'get',
params: query
});
};
/**
* id获取xml
* @param definitionId id
* @returns
*/
export const definitionXml = (definitionId: string): AxiosPromise<definitionXmlVO> => {
return request({
url: `/workflow/definition/definitionXml/${definitionId}`,
method: 'get'
});
};
/**
*
* @param id id
* @returns
*/
export const deleteDefinition = (id: string | string[]) => {
return request({
url: `/workflow/definition/${id}`,
method: 'delete'
});
};
/**
* /
* @param definitionId id
* @param activityStatus
* @returns
*/
export const active = (definitionId: string, activityStatus: boolean) => {
return request({
url: `/workflow/definition/active/${definitionId}`,
method: 'put',
params: {
active: activityStatus
}
});
};
/**
* zip或xml部署流程定义
* @returns
*/
export function importDef(data: any) {
return request({
url: '/workflow/definition/importDef',
method: 'post',
data: data,
headers: {
repeatSubmit: false
}
});
} }
/** export function unPublishList(query: FlowDefinitionQuery) {
* return request<R<PageResult<FlowDefinitionVO>>>({
* @param id id url: '/workflow/definition/unPublishList',
* @returns method: 'get',
*/ params: query
export const publish = (id: string) => { });
return request({ }
export function definitionXml(definitionId: string) {
return request<R<definitionXmlVO>>({
url: `/workflow/definition/definitionXml/${definitionId}`,
method: 'get'
});
}
export function deleteDefinition(id: string | string[]) {
return request<R>({
url: `/workflow/definition/${id}`,
method: 'delete'
});
}
export function activeDefinition(definitionId: string, activityStatus: boolean) {
return request<R>({
url: `/workflow/definition/active/${definitionId}`,
method: 'put',
params: { active: activityStatus }
});
}
export function active(definitionId: string, activityStatus: boolean) {
return activeDefinition(definitionId, activityStatus);
}
export function importDefinition(data: FormData) {
return request<R>({
url: '/workflow/definition/importDef',
method: 'post',
data,
headers: { repeatSubmit: false }
});
}
export function importDef(data: FormData) {
return importDefinition(data);
}
export function publishDefinition(id: string) {
return request<R>({
url: `/workflow/definition/publish/${id}`, url: `/workflow/definition/publish/${id}`,
method: 'put' method: 'put'
}); });
}; }
/** export function publish(id: string) {
* return publishDefinition(id);
* @param id id }
* @returns
*/ export function unPublish(id: string) {
export const unPublish = (id: string) => { return request<R>({
return request({
url: `/workflow/definition/unPublish/${id}`, url: `/workflow/definition/unPublish/${id}`,
method: 'put' method: 'put'
}); });
}; }
/** export function xmlString(id: string) {
* xml字符串 return request<R<string>>({
* @param id id
* @returns
*/
export const xmlString = (id: string) => {
return request({
url: `/workflow/definition/xmlString/${id}`, url: `/workflow/definition/xmlString/${id}`,
method: 'get' method: 'get'
}); });
}; }
/** export function addDefinition(data: FlowDefinitionForm) {
* return request<R>({
* @param data url: '/workflow/definition',
* @returns
*/
export const add = (data: FlowDefinitionForm) => {
return request({
url: `/workflow/definition`,
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function add(data: FlowDefinitionForm) {
* return addDefinition(data);
* @param data }
* @returns
*/ export function editDefinition(data: FlowDefinitionForm) {
export const edit = (data: FlowDefinitionForm) => { return request<R>({
return request({ url: '/workflow/definition',
url: `/workflow/definition`,
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function edit(data: FlowDefinitionForm) {
* return editDefinition(data);
* @param id }
* @returns
*/ export function getDefinition(id: string) {
export const getInfo = (id: number | string) => { return request<R<FlowDefinitionVO>>({
return request({
url: `/workflow/definition/${id}`, url: `/workflow/definition/${id}`,
method: 'get' method: 'get'
}); });
}; }
/** export function getInfo(id: string | number) {
* return getDefinition(String(id));
* @param id id }
* @returns
*/ export function copyDefinition(id: string) {
export const copy = (id: string) => { return request<R>({
return request({
url: `/workflow/definition/copy/${id}`, url: `/workflow/definition/copy/${id}`,
method: 'post' method: 'post'
}); });
}; }
export function copy(id: string) {
return copyDefinition(id);
}
export function exportDefinition(id: string) {
return request<Blob>({
url: `/workflow/definition/exportDef/${id}`,
method: 'post',
responseType: 'blob'
});
}

View File

@ -1,32 +1,41 @@
import type { PageQuery } from '@/api/types';
export interface FlowDefinitionForm {
id?: string;
flowName?: string;
flowCode?: string;
category?: string | number;
ext?: string;
formPath?: string;
formCustom?: string;
modelValue?: string;
}
export interface FlowDefinitionQuery extends PageQuery { export interface FlowDefinitionQuery extends PageQuery {
flowCode?: string; flowCode?: string;
flowName?: string; flowName?: string;
category: string | number; category?: string | number;
isPublish?: number; isPublish?: number;
} }
export interface FlowDefinitionVo { export interface FlowDefinitionVO {
id: string; id: string;
flowName: string; flowName: string;
flowCode: string; flowCode: string;
formPath: string; category?: string | number;
version: string; categoryName?: string;
isPublish: number; formPath?: string;
activityStatus: number; formCustom?: string;
createTime: Date; modelValue?: string;
updateTime: Date; version?: string | number;
isPublish?: number;
activityStatus?: number;
ext?: string;
createTime?: string | Date;
updateTime?: string | Date;
} }
export interface FlowDefinitionForm { export type FlowDefinitionVo = FlowDefinitionVO;
id: string;
flowName: string;
flowCode: string;
category: string;
ext: string;
formPath: string;
formCustom: string;
modelValue: string;
}
export interface definitionXmlVO { export interface definitionXmlVO {
xml: string[]; xml: string[];

View File

@ -1,126 +1,84 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types'; import request from '@/api/request';
import type { AxiosPromise } from '@/utils/api-types'; import type { FlowHisTaskResult, FlowInstanceQuery, FlowInstanceVO } from './types';
import request from '@/utils/request';
/** export function pageByRunning(query: FlowInstanceQuery) {
* return request<R<PageResult<FlowInstanceVO>>>({
* @param query
* @returns {*}
*/
export const pageByRunning = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
return request({
url: '/workflow/instance/pageByRunning', url: '/workflow/instance/pageByRunning',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function pageByFinish(query: FlowInstanceQuery) {
* return request<R<PageResult<FlowInstanceVO>>>({
* @param query
* @returns {*}
*/
export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
return request({
url: '/workflow/instance/pageByFinish', url: '/workflow/instance/pageByFinish',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function pageByCurrent(query: FlowInstanceQuery) {
* id获取历史流程图 return request<R<PageResult<FlowInstanceVO>>>({
*/
export const flowHisTaskList = (businessId: string | number) => {
return request({
url: `/workflow/instance/flowHisTaskList/${businessId}?t=${Math.random()}`,
method: 'get'
});
};
/**
*
* @param query
* @returns {*}
*/
export const pageByCurrent = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
return request({
url: '/workflow/instance/pageByCurrent', url: '/workflow/instance/pageByCurrent',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function cancelProcessApply(data: {
* id?: string | number;
* @param data businessId?: string | number;
* @returns comment?: string;
*/ message?: string;
export const cancelProcessApply = (data: any) => { }) {
return request({ return request<R>({
url: `/workflow/instance/cancelProcessApply`, url: '/workflow/instance/cancelProcessApply',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function instanceVariable(instanceId: string | number) {
* return request<R<{ variable: string }>>({
* @param instanceId id
* @returns
*/
export const instanceVariable = (instanceId: string | number) => {
return request({
url: `/workflow/instance/instanceVariable/${instanceId}`, url: `/workflow/instance/instanceVariable/${instanceId}`,
method: 'get' method: 'get'
}); });
}; }
/** export function deleteByInstanceIds(instanceIds: Array<string | number> | string | number) {
* return request<R>({
* @param instanceIds id
* @returns
*/
export const deleteByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
return request({
url: `/workflow/instance/deleteByInstanceIds/${instanceIds}`, url: `/workflow/instance/deleteByInstanceIds/${instanceIds}`,
method: 'delete' method: 'delete'
}); });
}; }
/** export function deleteHisByInstanceIds(instanceIds: Array<string | number> | string | number) {
* return request<R>({
* @param instanceIds
*/
export const deleteHisByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
return request({
url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`, url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`,
method: 'delete' method: 'delete'
}); });
}; }
/** export function invalid(data: { id: string | number; comment?: string }) {
* return request<R>({
* @param data url: '/workflow/instance/invalid',
* @returns
*/
export const invalid = (data: any) => {
return request({
url: `/workflow/instance/invalid`,
method: 'post', method: 'post',
data: data data
}); });
}; }
/**
* export function updateVariable(data: { instanceId?: string | number; key?: string; value?: string }) {
* @param data return request<R>({
* @returns url: '/workflow/instance/updateVariable',
*/
export const updateVariable = (data: any) => {
return request({
url: `/workflow/instance/updateVariable`,
method: 'put', method: 'put',
data: data data
}); });
}; }
export function flowHisTaskList(businessId: string | number) {
return request<R<FlowHisTaskResult>>({
url: `/workflow/instance/flowHisTaskList/${businessId}?t=${Math.random()}`,
method: 'get'
});
}

View File

@ -1,4 +1,10 @@
import type { FlowTaskVO } from '@/api/workflow/task/types'; import type { BaseEntity, PageQuery } from '@/api/types';
import type { FlowHistoryVO, FlowTaskVO } from '@/api/workflow/task/types';
export interface FlowHisTaskResult {
list: FlowHistoryVO[];
instanceId?: string | number;
}
export interface FlowInstanceQuery extends PageQuery { export interface FlowInstanceQuery extends PageQuery {
category?: string | number; category?: string | number;
@ -11,18 +17,25 @@ export interface FlowInstanceQuery extends PageQuery {
export interface FlowInstanceVO extends BaseEntity { export interface FlowInstanceVO extends BaseEntity {
id: string | number; id: string | number;
definitionId: string; definitionId?: string;
flowName: string; flowName?: string;
flowCode: string; flowCode?: string;
version: string; version?: string | number;
businessId: string; businessId: string;
activityStatus: number; activityStatus?: number;
tenantId: string; tenantId?: string;
createTime: string; createTime?: string;
createBy: string; updateTime?: string;
flowStatus: string; createBy?: string;
flowStatusName: string; createByName?: string;
flowTaskList: FlowTaskVO[]; flowStatus?: string;
businessCode: string; flowStatusName?: string;
businessTitle: string; flowTaskList?: FlowTaskVO[];
businessCode?: string;
businessTitle?: string;
nodeName?: string;
categoryName?: string;
isSuspended?: boolean;
formCustom?: string;
formPath?: string;
} }

View File

@ -1,76 +1,49 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types'; import request from '@/api/request';
import type { AxiosPromise } from '@/utils/api-types'; import type { LeaveForm, LeaveQuery, LeaveVO } from './types';
import request from '@/utils/request';
/** export function listLeave(query?: LeaveQuery) {
* return request<R<PageResult<LeaveVO>>>({
* @param query
* @returns {*}
*/
export const listLeave = (query?: LeaveQuery): AxiosPromise<PageResult<LeaveVO>> => {
return request({
url: '/workflow/leave/list', url: '/workflow/leave/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function getLeave(id: string | number) {
* return request<R<LeaveVO>>({
* @param id url: `/workflow/leave/${id}`,
*/
export const getLeave = (id: string | number): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave/' + id,
method: 'get' method: 'get'
}); });
}; }
/** export function addLeave(data: LeaveForm) {
* return request<R<LeaveVO>>({
* @param data
*/
export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave', url: '/workflow/leave',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function submitAndFlowStart(data: LeaveForm) {
* return request<R<LeaveVO>>({
* @param data
*/
export const submitAndFlowStart = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave/submitAndFlowStart', url: '/workflow/leave/submitAndFlowStart',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function updateLeave(data: LeaveForm) {
* return request<R<LeaveVO>>({
* @param data
*/
export const updateLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave', url: '/workflow/leave',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function delLeave(id: string | number | Array<string | number>) {
* return request<R>({
* @param id url: `/workflow/leave/${id}`,
*/
export const delLeave = (id: string | number | Array<string | number>) => {
return request({
url: '/workflow/leave/' + id,
method: 'delete' method: 'delete'
}); });
}; }

View File

@ -1,13 +1,4 @@
export interface LeaveVO { import type { BaseEntity, PageQuery } from '@/api/types';
id: string | number;
applyCode?: string;
leaveType: string;
startDate: string;
endDate: string;
leaveDays: number;
remark: string;
status?: string;
}
export interface LeaveForm extends BaseEntity { export interface LeaveForm extends BaseEntity {
id?: string | number; id?: string | number;
@ -24,3 +15,14 @@ export interface LeaveQuery extends PageQuery {
startLeaveDays?: number; startLeaveDays?: number;
endLeaveDays?: number; endLeaveDays?: number;
} }
export interface LeaveVO {
id: string | number;
applyCode?: string;
leaveType?: string;
startDate?: string;
endDate?: string;
leaveDays?: number;
remark?: string;
status?: string;
}

View File

@ -1,64 +1,41 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { SpelForm, SpelQuery, SpelVO } from '@/api/workflow/spel/types'; import request from '@/api/request';
import type { AxiosPromise } from '@/utils/api-types'; import type { SpelForm, SpelQuery, SpelVO } from './types';
import request from '@/utils/request';
/** export function listSpel(query?: SpelQuery) {
* spel表达式定义列表 return request<R<PageResult<SpelVO>>>({
* @param query
* @returns {*}
*/
export const listSpel = (query?: SpelQuery): AxiosPromise<PageResult<SpelVO>> => {
return request({
url: '/workflow/spel/list', url: '/workflow/spel/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function getSpel(id: string | number) {
* spel表达式定义详细 return request<R<SpelVO>>({
* @param id url: `/workflow/spel/${id}`,
*/
export const getSpel = (id: string | number): AxiosPromise<SpelVO> => {
return request({
url: '/workflow/spel/' + id,
method: 'get' method: 'get'
}); });
}; }
/** export function addSpel(data: SpelForm) {
* spel表达式定义 return request<R>({
* @param data
*/
export const addSpel = (data: SpelForm) => {
return request({
url: '/workflow/spel', url: '/workflow/spel',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function updateSpel(data: SpelForm) {
* spel表达式定义 return request<R>({
* @param data
*/
export const updateSpel = (data: SpelForm) => {
return request({
url: '/workflow/spel', url: '/workflow/spel',
method: 'put', method: 'put',
data: data data
}); });
}; }
/** export function delSpel(id: string | number | Array<string | number>) {
* spel表达式定义 return request<R>({
* @param id url: `/workflow/spel/${id}`,
*/
export const delSpel = (id: string | number | Array<string | number>) => {
return request({
url: '/workflow/spel/' + id,
method: 'delete' method: 'delete'
}); });
}; }

View File

@ -1,105 +1,29 @@
export interface SpelVO { import type { BaseEntity, PageQuery } from '@/api/types';
/**
* id
*/
id: string | number;
/**
*
*/
componentName: string;
/**
*
*/
methodName: string;
/**
*
*/
methodParams: string;
/**
* spel值
*/
viewSpel: string;
/**
* 0 1
*/
status: string;
/**
*
*/
remark?: string;
}
export interface SpelForm extends BaseEntity { export interface SpelForm extends BaseEntity {
/**
* id
*/
id?: string | number; id?: string | number;
/**
*
*/
componentName?: string; componentName?: string;
/**
*
*/
methodName?: string; methodName?: string;
/**
*
*/
methodParams?: string; methodParams?: string;
/**
* spel值
*/
viewSpel?: string; viewSpel?: string;
/**
* 0 1
*/
status?: string; status?: string;
/**
*
*/
remark?: string; remark?: string;
} }
export interface SpelQuery extends PageQuery { export interface SpelQuery extends PageQuery {
/**
*
*/
componentName?: string; componentName?: string;
/**
*
*/
methodName?: string; methodName?: string;
/**
*
*/
methodParams?: string; methodParams?: string;
/**
* spel值
*/
viewSpel?: string; viewSpel?: string;
/**
* 0 1
*/
status?: string; status?: string;
}
/**
* export interface SpelVO {
*/ id: string | number;
params?: any; componentName?: string;
methodName?: string;
methodParams?: string;
viewSpel?: string;
status?: string;
remark?: string;
} }

View File

@ -1,207 +1,129 @@
import type { PageResult } from '@/api/types'; import type { PageResult, R } from '@/api/types';
import type { FlowTaskVO, TaskOperationBo, TaskQuery } from '@/api/workflow/task/types'; import type { StartProcessBo } from '@/api/workflow/workflowCommon/types';
import type { AxiosPromise } from '@/utils/api-types'; import request from '@/api/request';
import request from '@/utils/request'; import type { FlowNextNodeVO, FlowTaskVO, StartWorkflowResult, TaskOperationBo, TaskQuery } from './types';
/** export function pageByTaskWait(query: TaskQuery) {
* return request<R<PageResult<FlowTaskVO>>>({
* @param query
* @returns {*}
*/
export const pageByTaskWait = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByTaskWait', url: '/workflow/task/pageByTaskWait',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function pageByTaskFinish(query: TaskQuery) {
* return request<R<PageResult<FlowTaskVO>>>({
* @param query
* @returns {*}
*/
export const pageByTaskFinish = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByTaskFinish', url: '/workflow/task/pageByTaskFinish',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function pageByTaskCopy(query: TaskQuery) {
* return request<R<PageResult<FlowTaskVO>>>({
* @param query
* @returns {*}
*/
export const pageByTaskCopy = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByTaskCopy', url: '/workflow/task/pageByTaskCopy',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function pageByAllTaskWait(query: TaskQuery) {
* return request<R<PageResult<FlowTaskVO>>>({
* @param query
* @returns {*}
*/
export const pageByAllTaskWait = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByAllTaskWait', url: '/workflow/task/pageByAllTaskWait',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function pageByAllTaskFinish(query: TaskQuery) {
* return request<R<PageResult<FlowTaskVO>>>({
* @param query
* @returns {*}
*/
export const pageByAllTaskFinish = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByAllTaskFinish', url: '/workflow/task/pageByAllTaskFinish',
method: 'get', method: 'get',
params: query params: query
}); });
}; }
/** export function startWorkFlow(data: StartProcessBo) {
* return request<R<StartWorkflowResult>>({
* @param data
* @returns {*}
*/
export const startWorkFlow = (data: object): any => {
return request({
url: '/workflow/task/startWorkFlow', url: '/workflow/task/startWorkFlow',
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function updateAssignee(taskIdList: Array<string | number>, userId: string | number) {
* return request<R>({
* @param data
* @returns {*}
*/
export const completeTask = (data: object) => {
return request({
url: '/workflow/task/completeTask',
method: 'post',
data: data
});
};
/**
*
* @param data
* @returns {*}
*/
export const backProcess = (data: any): any => {
return request({
url: '/workflow/task/backProcess',
method: 'post',
data: data
});
};
/**
*
* @param taskId
* @returns
*/
export const getTask = (taskId: string) => {
return request({
url: '/workflow/task/getTask/' + taskId,
method: 'get'
});
};
/**
*
* @param taskIdList
* @param userId
* @returns
*/
export const updateAssignee = (taskIdList: Array<string>, userId: string) => {
return request({
url: `/workflow/task/updateAssignee/${userId}`, url: `/workflow/task/updateAssignee/${userId}`,
method: 'put', method: 'put',
data: taskIdList data: taskIdList
}); });
}; }
/** export function urgeTask(data: { taskIdList: Array<string | number>; message?: string; messageType?: string[] }) {
* return request<R>({
* @returns url: '/workflow/task/urgeTask',
*/
export const terminationTask = (data: any) => {
return request({
url: `/workflow/task/terminationTask`,
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function completeTask(data: Record<string, unknown>) {
* return request<R>({
* @returns url: '/workflow/task/completeTask',
*/ method: 'post',
export const getBackTaskNode = (taskId: string | number, nodeCode: string) => { data
return request({ });
url: `/workflow/task/getBackTaskNode/${taskId}/${nodeCode}`, }
export function backProcess(data: Record<string, unknown>) {
return request<R>({
url: '/workflow/task/backProcess',
method: 'post',
data
});
}
export function getTask(taskId: string | number) {
return request<R<FlowTaskVO>>({
url: `/workflow/task/getTask/${taskId}`,
method: 'get' method: 'get'
}); });
}; }
/** export function terminationTask(data: { taskId?: string | number; comment?: string }) {
* delegateTask transferTask addSignature reductionSignature return request<R>({
* @returns url: '/workflow/task/terminationTask',
*/ method: 'post',
export const taskOperation = (data: TaskOperationBo, operation: string) => { data
return request({ });
}
export function getBackTaskNode(taskId: string | number, nodeCode?: string) {
return request<R<FlowNextNodeVO[]>>({
url: `/workflow/task/getBackTaskNode/${taskId}/${nodeCode || ''}`,
method: 'get'
});
}
export function taskOperation(data: TaskOperationBo, operation: string) {
return request<R>({
url: `/workflow/task/taskOperation/${operation}`, url: `/workflow/task/taskOperation/${operation}`,
method: 'post', method: 'post',
data: data data
}); });
}; }
/** export function currentTaskAllUser(taskId: string | number) {
* return request<R<Array<{ userId: string | number; nickName: string; nodeName?: string }>>>({
* @param taskId id
* @returns
*/
export const currentTaskAllUser = (taskId: string | number) => {
return request({
url: `/workflow/task/currentTaskAllUser/${taskId}`, url: `/workflow/task/currentTaskAllUser/${taskId}`,
method: 'get' method: 'get'
}); });
}; }
/** export function getNextNodeList(data: { taskId?: string | number; variables?: Record<string, unknown> }) {
* return request<R<FlowNextNodeVO[]>>({
* @param data参数
* @returns
*/
export const getNextNodeList = (data: any): any => {
return request({
url: '/workflow/task/getNextNodeList', url: '/workflow/task/getNextNodeList',
method: 'post', method: 'post',
data: data data
}); });
}; }
/**
*
* @param data参数
* @returns
*/
export const urgeTask = (data: any): any => {
return request({
url: '/workflow/task/urgeTask',
method: 'post',
data: data
});
};

View File

@ -1,60 +1,82 @@
import type { OssVO } from '@/api/system/oss/types';
import type { PageQuery } from '@/api/types';
export interface ButtonList {
code: string;
show: boolean;
}
export interface FlowCopyVo {
userId: string | number;
nickName: string;
}
export interface FlowHistoryVO {
nodeName?: string;
approverName?: string;
flowStatus?: string;
message?: string;
createTime?: string;
updateTime?: string;
runDuration?: string;
ext?: string | number;
attachmentList?: OssVO[];
}
export interface FlowNextNodeVO {
nodeCode: string;
nodeName: string;
permissionFlag?: string | Array<string | number>;
}
export interface FlowTaskVO {
id: string | number;
createTime?: string | Date;
updateTime?: string | Date;
tenantId?: string;
definitionId?: string;
instanceId?: string;
flowName?: string;
businessId: string;
nodeCode?: string;
nodeName?: string;
flowCode?: string;
flowStatus?: string;
flowStatusName?: string;
flowTaskStatus?: string;
formCustom?: string;
formPath?: string;
nodeType?: number;
nodeRatio?: string | number;
version?: string;
applyNode?: boolean;
buttonList?: ButtonList[];
copyList?: FlowCopyVo[];
varList?: Record<string, string>;
businessCode?: string;
businessTitle?: string;
categoryName?: string;
createByName?: string;
assigneeNames?: string;
approverName?: string;
}
export interface StartWorkflowResult {
taskId?: string | number;
}
export interface TaskOperationBo {
taskId?: string | number;
userId?: string | number;
userIds?: Array<string | number>;
message?: string;
messageType?: string[];
variables?: Record<string, unknown>;
}
export interface TaskQuery extends PageQuery { export interface TaskQuery extends PageQuery {
nodeName?: string; nodeName?: string;
flowCode?: string; flowCode?: string;
flowName?: string; flowName?: string;
createByIds?: Array<string | number>; createByIds?: Array<string | number>;
} }
export interface ParticipantVo {
groupIds?: string[] | number[];
candidate: string[] | number[];
candidateName: string[];
claim: boolean;
}
export interface FlowTaskVO {
id: string | number;
createTime?: Date;
updateTime?: Date;
tenantId?: string;
definitionId?: string;
instanceId: string;
flowName: string;
businessId: string;
nodeCode: string;
nodeName: string;
flowCode: string;
flowStatus: string;
formCustom: string;
formPath: string;
nodeType: number;
nodeRatio: string | number;
version?: string;
applyNode?: boolean;
buttonList?: ButtonList[];
copyList?: FlowCopyVo[];
varList?: Map<string, string>;
businessCode: string;
businessTitle: string;
}
export interface ButtonList {
code: string;
show: boolean;
}
export interface FlowCopyVo {
userId: string | number;
nickName: string;
}
export interface TaskOperationBo {
//委派/转办人的用户ID必填准对委派/转办人操作)
userId?: string;
//加签/减签人的用户ID列表必填针对加签/减签操作)
userIds?: string[];
//任务ID必填
taskId: string | number;
//消息类型
messageType?: string[];
//意见或备注信息(可选)
message?: string;
}

View File

@ -1,17 +1,17 @@
import type { RouterJumpVo } from '@/api/workflow/workflowCommon/types'; import { history } from '@umijs/max';
import tab from '@/plugins/tab'; import { normalizePath } from '@/utils/ruoyi';
import router from '@/router'; import type { RouterJumpVO } from './types';
export function routerJump(routerJump: RouterJumpVO) {
history.push(
`${normalizePath(routerJump.formPath || '/workflow/leaveEdit/index')}?${new URLSearchParams({
id: routerJump.businessId,
type: routerJump.type,
taskId: String(routerJump.taskId)
}).toString()}`
);
}
export default { export default {
routerJump(routerJumpVo: RouterJumpVo) { routerJump
tab.closePage(router.currentRoute.value);
router.push({
path: routerJumpVo.formPath,
query: {
id: routerJumpVo.businessId,
type: routerJumpVo.type,
taskId: routerJumpVo.taskId
}
});
}
}; };

View File

@ -1,4 +1,4 @@
export interface RouterJumpVo { export interface RouterJumpVO {
businessId: string; businessId: string;
taskId: string | number; taskId: string | number;
type: string; type: string;
@ -9,6 +9,6 @@ export interface RouterJumpVo {
export interface StartProcessBo { export interface StartProcessBo {
businessId: string | number; businessId: string | number;
flowCode: string; flowCode: string;
variables: any; variables: Record<string, unknown>;
bizExt: any; bizExt: Record<string, unknown>;
} }

14
src/app.tsx Normal file
View File

@ -0,0 +1,14 @@
import type { ReactNode } from 'react';
import { QueryClientProvider } from '@tanstack/react-query';
import { appEnv } from '@/utils/env';
import { queryClient } from '@/utils/queryClient';
export async function getInitialState() {
return {
name: appEnv.logoTitle
};
}
export function rootContainer(container: ReactNode) {
return <QueryClientProvider client={queryClient}>{container}</QueryClientProvider>;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Some files were not shown because too many files have changed in this diff Show More