Compare commits

..
Author SHA1 Message Date
疯狂的狮子Li 9a3444782f 🦁🦁🦁发布 6.0.0-BETA 公测版本 大版本发布!!! 2026-06-22 10:41:32 +08:00
疯狂的狮子Li ef9d9482c4 refactor gen templates to freemarker 2026-06-18 15:32:30 +08:00
疯狂的狮子Li 117bb0ef8c update 修改 参数设置页面布局 2026-06-15 16:12:39 +08:00
疯狂的狮子Li 9bcb8a9f27 fix 修复 登录页后端403会出现多个弹窗问题 2026-06-11 13:17:19 +08:00
疯狂的狮子Li 565564a8a3 update 优化 增加icon空的情况 2026-06-08 18:47:20 +08:00
疯狂的狮子Li eafce61dac update: 升级前端主要依赖并替换头像裁剪组件
- 升级主要前端依赖
    - React / React DOM
    - Umi Max / Umi lint
    - Ant Design Icons / ProComponents
    - WangEditor
    - Axios
    - ECharts
    - TanStack Query
    - oxlint / oxfmt
    - 相关 TypeScript 类型包
- 替换头像裁剪实现 react-easy-crop
- 适配 Ant Design 6 废弃 API
2026-06-08 18:43:08 +08:00
疯狂的狮子Li d5e1b42ad1 fix: 优化前端安全与生命周期清理
- 登录页记住我仅保存用户名和 rememberMe,并清理历史明文密码缓存
- 修复下载 Blob 链接释放时机,提升浏览器兼容性
- 关闭消息推送时清理事件处理器并阻止重复重连
- 稳定流程审批组件 variables 默认值,避免无效重复加载
- 忽略 IDE 本地配置文件
- 删除未使用的 RightToolbar 组件
2026-06-08 17:58:52 +08:00
疯狂的狮子Li cf53df4f4a update 重构 头像上传改为先上传oss后更新用户数据 2026-06-05 16:48:57 +08:00
疯狂的狮子Li f9e9055a3f update 优化 代码生成 编辑页样式 2026-06-03 16:32:53 +08:00
疯狂的狮子Li 5b7df709f1 update 优化 代码生成 增加多前端模板选择 2026-06-03 16:15:05 +08:00
疯狂的狮子Li 74bfd8848d update 内置代码生成模板 优化skill 2026-06-03 15:24:03 +08:00
疯狂的狮子Li bbf508e123 update 内置代码生成模板 优化skill 2026-06-03 15:20:07 +08:00
疯狂的狮子Li 7eccb9b8c1 update 重构 封装工具 优化代码 2026-06-03 14:41:05 +08:00
疯狂的狮子Li b3e35ea84f update 优化 skill 内容 2026-06-03 12:14:45 +08:00
疯狂的狮子Li 889eb0e9ed fix 修复 ai聊天 第一句话不展示问题 2026-06-02 10:19:10 +08:00
疯狂的狮子Li 682b931931 fix 修复 选择图标时tooltip显示后滚动出现错位现象 2026-06-02 09:21:47 +08:00
疯狂的狮子Li e80b187810 update 优化 README.md 2026-06-01 16:02:28 +08:00
疯狂的狮子Li 329b39b87d fix 修复 字典懒加载问题 2026-06-01 15:58:52 +08:00
疯狂的狮子Li 97f75f7d6e update 优化 README.md 2026-06-01 13:05:01 +08:00
疯狂的狮子Li ea09d081c5 init 初始化 react + antd 版本前端 2026-06-01 12:01:08 +08:00
471 changed files with 37734 additions and 40741 deletions
+46 -18
View File
@@ -1,32 +1,60 @@
---
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 层和类型定义。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端引用必须同时标记仓库、分支和文件路径;不要写本机绝对路径。
你负责当前 plus-ui-react 项目中的 API 层和类型定义。
## 核心原则
1. 先看当前模块已有 `src/api/<module>/<business>`。
2. API 路径、返回类型、命名风格与当前模块保持一致。
2. API 路径、返回类型、函数命名、导出风格与当前模块保持一致。
3. 能明确写出类型时,不要偷懒用 `any`。
4. 如果当前模块已有 `export default { ... }`,继续保持一致。
4. 如果当前模块已有特殊返回结构或聚合导出,继续保持一致。
5. 不创建页面,不改路由,除非用户明确要求。
6. 如果用户要求维护代码生成模板,优先同步当前仓库 `gen/api.ts.vm`、`gen/types.ts.vm`,必要时再看 boot4 后端 generator 字段定义。
## 重点关注
## API 规则
- `Query`
- `VO`
- `Form`
- `InfoVO`
- `AxiosPromise<PageResult<T>>`
- 详情接口与列表接口返回结构
- 标准 import:
`import type { PageResult, R } from '@/api/types';`
`import request from '@/api/request';`
`import type { XxxForm, XxxQuery, XxxVO } from './types';`
- 分页列表:`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.vm` 和 `gen/types.ts.vm` 是当前 React 项目内置模板,优先于外部后端模板。
- 保持 Velocity 变量、宏和输出路径约定,不要破坏后端 generator 可替换变量。
- 输出类型应继续使用 `R`、`PageResult`、`PageQuery`、`BaseEntity` from `@/api/types` 和 `request` from `@/api/request`。
- 不输出 Vue 版 `AxiosPromise`、`@/utils/request` 或 `src/views` 约定。
## 自检
- API 路径是否与后端一致
- 类型是否覆盖接口真实结构
- 是否不必要地把类型写宽了
- API 路径是否与后端一致。
- 类型是否覆盖接口真实结构。
- 是否不必要地把类型写宽了。
- 是否保留了当前模块的命名和导出风格。
- 是否同步维护了当前项目内置 `gen/*.vm` 模板中对应 API/types 输出。
- 是否误用了 Vue 版 `AxiosPromise`、`@/utils/request` 或 `src/views` 约定。
+26 -9
View File
@@ -1,22 +1,39 @@
---
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/*.vm React 代码生成模板维护任务,并根据任务类型选择合适的前端子 agent。
---
你是 plus-ui 前端项目的总入口 agent。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端引用必须同时标记仓库、分支和文件路径;不要在 agent 文档中写本机绝对路径。
你是当前 plus-ui-react 前端项目的总入口 agent。
先判断任务类型,再按下面规则处理:
1. 如果是新增标准 CRUD 页面、补 `src/api`、`types.ts`、`index.vue`,优先使用 `frontend-crud-page.md`。
2. 如果是修改已有列表页、增强导入导出、树筛选、更多菜单、状态切换,优先使用 `frontend-page-enhancement.md`。
1. 如果是新增标准 CRUD 页面、补 `src/api`、`types.ts`、`index.tsx`,优先使用 `frontend-crud-page.md`。
2. 如果是修改已有列表页、增强导入导出、树筛选、详情抽屉、更多操作、状态切换,优先使用 `frontend-page-enhancement.md`。
3. 如果只改接口层和类型定义,优先使用 `frontend-api-types.md`。
4. 如果是维护代码生成模板,优先读取当前仓库 `gen/api.ts.vm`、`gen/types.ts.vm`、`gen/index.tsx.vm`、`gen/index-tree.tsx.vm`,再按 React 项目真实工具和页面规则修改模板。
通用要求:
- 先读当前目录下最近似页面和 API,再动代码。
- 冲突时优先相信当前项目真实页面,其次是公共组件和工具,再其次才是 plus-ui Gitee 仓库 `6.X-Vue` 分支的 `gen` 模板。
- 优先参考 `.codex/skills/frontend-crud-coding/SKILL.md` 和 `references/frontend.md`,保持 Claude agent 与 Codex skill 规则一致。
- 冲突时优先相信当前项目真实页面,其次是公共组件、hooks、工具和请求封装,再其次是当前仓库 `gen/*.vm` 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.vm`、`gen/index-tree.tsx.vm` 内容必须生成 React TSX 页面。
验证要求:
- 改 TS/TSX/API/types 后优先运行 `pnpm lint` 或 `pnpm exec tsc --noEmit`。
- 改公共组件、hooks、构建配置或较大范围页面后运行 `pnpm build`。
- 如果验证失败或无法运行,交付时说明命令和原因。
+53 -21
View File
@@ -1,41 +1,73 @@
---
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/*.vm React 模板维护,以及后端 generator 字段到 React + ProTable 项目风格的落地任务。
---
你负责 plus-ui 前端项目中的标准 CRUD 页面实现。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端模板引用必须同时标记仓库、分支和路径,例如 `branch=6.X-Vue, path=gen/index.vue.ftl`;不要写本机绝对路径。
你负责当前 plus-ui-react 项目中的标准 CRUD 页面实现。
## 核心原则
1. 先看当前模块最近似页面。
2. 再参考 plus-ui Gitee 仓库 `6.X-Vue` 分支中的 `gen` 模板。
3. 默认同时维护:
2. 再参考 `.codex/skills/frontend-crud-coding/references/frontend.md`。
3. 维护或新增生成能力时,优先参考当前仓库 `gen/api.ts.vm`、`gen/types.ts.vm`、`gen/index.tsx.vm`、`gen/index-tree.tsx.vm`。
4. 必要时参考关联后端工程 generator 模板确认接口、字段、权限和导出能力。
5. 默认同时维护:
`src/api/<module>/<business>/index.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`
- 查询与表单优先使用 `reactive<PageData<Form, Query>>({...})`
`actionRef`、`form`、`modalOpen`、`modalTitle`、`ids`、`selectedOne`、权限布尔值。
- 表格列使用 `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 规则
- 请求统一通过 `@/utils/request`
- 同目录维护 `index.ts` 与 `types.ts`
- 标准 CRUD 通常包含:列表、详情、新增、修改、删除
- 列表接口通常返回 `AxiosPromise<PageResult<XxxVO>>`
- 请求统一通过 `@/api/request`。
- 同目录维护 `index.ts` 与 `types.ts`。
- 标准 CRUD 通常包含:列表、详情、新增、修改、删除。
- 列表分页接口通常返回 `request<R<PageResult<XxxVO>>>`。
- 树表列表接口通常返回 `request<R<XxxVO[]>>`。
- 不要从 `axios` 引入 `AxiosPromise`。
## 树表规则
- 判断树表后不要生成分页 `PageResult` 页面。
- `Query` 通常不继承 `PageQuery`。
- 使用 `handleTree`、`pagination={false}`、`useTreeTableExpand`、`ProFormTreeSelect`。
- 树表展开/折叠优先使用 `useTreeTableExpand`,不要在页面或模板里重复手写收集 key 的状态逻辑。
- 新增子节点时从当前行带入 `parentId`。
## gen 模板规则
- `gen/index.tsx.vm`、`gen/index-tree.tsx.vm` 内容生成 React TSX。
- 保持 Velocity 变量、宏和后端 generator 可识别结构,不为了格式美化破坏模板语法。
- 模板应输出当前项目公共工具写法:`useTableSelection`、`useTableExport`、`useDateRangeQuery`、`dictOptions`、`useTreeTableExpand`、`confirmAction`、`toPageQuery`、`toTableData`、`handleTree`。
- 不把 Vue `src/views`、Element Plus、`AxiosPromise`、`v-hasPermi` 写入模板。
## 自检
- API 路径是否与后端一致
- `index.ts` 与 `types.ts` 是否同步补齐
- 页面是否只是模板裸输出,如果是要继续补强到当前项目风格
- API 路径是否与后端一致。
- `index.ts` 与 `types.ts` 是否同步补齐。
- 页面是否使用 React 项目的 ProTable/ModalForm 骨架。
- 权限标识是否与后端和相邻页面一致。
- 是否同步使用当前项目公共 hooks/工具,避免生成重复代码。
- 是否误用了 Vue/Element Plus/`src/views` 规则。
+44 -17
View File
@@ -1,31 +1,58 @@
---
name: frontend-page-enhancement
description: 复杂前端页面增强专家。用于修改 plus-ui 前端项目中已经存在的列表页、树筛选页、带导入导出和更多菜单的页面,强调增量修改和保留现有交互能力;默认基线为 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支。
description: 复杂前端页面增强专家。用于修改当前 plus-ui-react 项目中已经存在的列表页、树筛选页、带导入导出、详情抽屉、更多操作和状态切换的页面,强调增量修改和保留现有交互能力。
---
你负责 plus-ui 前端项目中已有页面的增强,不是重写页面。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端引用必须同时标记仓库、分支和文件路径;不要写本机绝对路径。
你负责当前 plus-ui-react 项目中已有页面的增强,不是重写页面。
## 核心原则
1. 优先阅读当前页面完整实现。
1. 优先阅读当前页面完整实现和相关子组件。
2. 增量修改,不重写整页。
3. 保留已有树筛选、导入导出、列显隐、更多菜单、状态切换、路由跳转、SCSS 页面壳。
4. 不要把复杂页面退化成 generator 式基础列表页。
3. 保留已有树筛选、导入导出、列显隐、详情抽屉、更多操作、状态切换、路由跳转、权限控制和样式壳。
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/*.vm` 是否也应同步更新。
## 自检
- 是否破坏了原页面结构和样式
- 是否误删了已有权限控制或交互能力
- 是否应该拆成子组件而不是继续堆主页面
- 是否破坏了原页面结构和样式。
- 是否误删了已有权限控制或交互能力。
- 是否保留了树筛选、导入导出、抽屉、路由跳转等复杂能力。
- 是否应该拆成子组件而不是继续堆主页面。
- 是否使用了当前项目已有工具,避免重复手写日期范围、字典 options、树展开、loading、确认框。
- 是否误用了 Vue/Element Plus/generator 的简化逻辑。
+67 -56
View File
@@ -1,41 +1,31 @@
---
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、页面壳、类型入口和下载方式。
## 项目基线
- 基线仓库:`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 文档中写本机绝对路径;本地文件读取以运行时工作目录为准。
- 如果用户指定其他分支,先按用户分支读取同一仓库的对应文件,并在结果中说明使用的分支。
先对齐当前 React 项目里的真实实现,再参考项目内置 `gen/` 代码生成模板。Vue 版本 skill 和关联后端工程的代码生成器模板只用于理解字段、权限、任务分型和迁移意图。不要直接套 Vue 3、Element Plus、AxiosPromise 或 `src/views` 规则;要落成当前仓库的 `src/pages`、`src/api`、ProTable、ModalForm、权限、导出和公共工具方式。
## 执行流程
1. 判断任务类型:新增标准 CRUD、树表、已有页面增强、复杂业务页、只补 API/types。
2. 按“文档读取规则”读取必要 reference,不一次性展开所有资料。
3. 阅读目标目录下最近似的真实代码:
- 标准单表优先看 `src/views/demo/demo/index.vue`、`src/api/demo/demo/*`。
- 树表优先看 `src/views/demo/tree/index.vue`、`src/views/workflow/category/index.vue`。
- 系统复杂页优先看 `src/views/system/user/index.vue`、`system/role`、`system/post`、`system/config`。
- workflow 业务页优先看 `src/views/workflow/*` 同类页面。
4. 新增标准页面前,对照项目内模板确认基础骨架:
- API 模板:`gen/api.ts.ftl`
- types 模板:`gen/types.ts.ftl`
- 标准单表页模板:`gen/index.vue.ftl`
- 树表页模板:`gen/index-tree.vue.ftl`
5. 新增代码时通常同步维护 `src/api/<module>/<business>/index.ts`、`types.ts`、`src/views/<module>/<business>/index.vue`。
6. 增强已有页面时只做增量修改,保留原页面的树筛选、导入导出、列显隐、权限、字典、弹窗和路由跳转能力。
7. 修改完成后按影响范围运行验证:优先 `pnpm exec vue-tsc --noEmit`,改动页面或导入时再跑 `pnpm lint`,大范围变更再跑 `pnpm build`。
- 标准单表优先看 `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/*` 同类页面。
4. 新增或维护代码生成能力时,优先阅读当前仓库 `gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`。这些是 React 版内置 FreeMarker 生成模板,页面模板必须输出当前项目 React/TSX 风格。
5. 需要从 Vue 版本迁移经验时,参考 `D:\git-sources\Plus相关\plus-ui-new\.codex\skills\frontend-crud-coding` 和 `.claude\agents`,只吸收任务分型、增量修改和自检原则。
6. 新增标准页面前,可以对照后端工程 `D:\git-sources\Plus相关\RuoYi-Vue-Plus-boot4\ruoyi-modules\ruoyi-gen\src\main\resources\fm` 确认字段、权限和导出能力,但输出和 `gen/` 模板必须保持 React 项目风格。
7. 新增代码时通常同步维护 `src/api/<module>/<business>/index.ts`、`types.ts`、`src/pages/<module>/<business>/index.tsx`;如果是生成模板优化,同步维护 `gen/` 对应 `.ftl`。
8. 增强已有页面时只做增量修改,保留原页面的树筛选、导入导出、列显隐、权限、字典、弹窗、抽屉和路由跳转能力。
9. 修改完成后按影响范围运行验证:优先 `pnpm exec 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)。
- reference 只约束实现方式和自检范围;发生冲突时,以当前模块真实代码和实际调用点为准。
@@ -44,41 +34,46 @@ description: 在 plus-ui 前端项目中按真实 Vue 3 + TypeScript + Element P
发生冲突时按下面顺序决策:
1. 目标目录下最近似页面、API、types 的真实实现。
2. 当前项目公共 hooks、组件、工具和样式约定。
3. 项目内 `gen` 代码生成模板。
4. 通用 Vue 3 / Element Plus 习惯。
2. 当前项目公共 hooks、组件、工具、样式和请求封装约定。
3. 当前项目内置 `gen/*.ftl` React 代码生成模板。
4. Vue 参考项目 `.codex` / `.claude` 中的任务分型和工作流。
5. 关联后端工程 generator 模板。
6. 通用 React / Ant Design ProComponents 习惯。
也就是说:
- 同模块已有页面怎么写,优先怎么写。
- 没有现成页面时,使用项目内 `gen` 模板作为骨架,再改成当前项目风格。
- 没有现成页面时,使用 demo 页面作为骨架,再按后端接口字段和权限补齐。
- 复杂模块不能为了“标准 CRUD”退化成裸模板页。
- Vue 版本只作为迁移参考,不复制 Vue 组件、hooks、指令、样式类和类型导入。
## 仓库通用规则
- 遵循 [`.editorconfig`](../../../.editorconfig):UTF-8、LF、2 空格缩进;Markdown 例外。
- 当前仓库没有 `.prettierrc`,格式脚本是 `pnpm run fmt` 调用 `oxfmt .`,lint 脚本是 `pnpm lint` 调用 `oxlint src`。
- 页面优先使用 `<script setup name="Xxx" lang="ts">`。
- API 返回类型优先从 `@/utils/api-types` 引入 `AxiosPromise`,分页结果从 `@/api/types` 引入 `PageResult`。
- 请求统一通过 `@/utils/request`,导出下载使用 `import { download as requestDownload } from '@/utils/request';`。
- 标准列表页优先复用 `useLoading`、`useSearchToggle`、`useSearchReset`、`useTableSelection`、`useFormDialog`、`useDateRangeQuery`。
- 页面壳优先使用 `p-2 app-container <module>-<business>-page`、`search-panel`、`toolbar-shell`、`data-table`、`right-toolbar`、`pagination`。
- 新页面不要无故引入另一套状态管理、请求封装、样式体系或权限写法。
- 遵循 `.editorconfig` 和仓库现状:UTF-8、2 空格缩进、TypeScript、TSX。
- 包管理使用 `pnpm`;格式脚本是 `pnpm run fmt`,lint 脚本是 `pnpm lint`。
- 技术栈是 React 19 + TypeScript + Umi Max + Ant Design 6 + Ant Design ProComponents + ahooks + TanStack Query + Zustand。
- 页面放在 `src/pages`,不是 Vue 项目的 `src/views`。
- 请求统一通过 `src/api/request.ts`,API 返回类型使用 `Promise<R<T>>`,不要从 `axios` 或 Vue 项目引入 `AxiosPromise`。
- 分页结果使用 `PageResult<T>` from `@/api/types`,接口外层使用 `R<T>` from `@/api/types`。
- 标准列表页优先复用 `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/*`
- 后端 `/monitor/xxx/*` 对应 `src/api/monitor/xxx/*` 与 `src/views/monitor/xxx/*`
- 后端 `/workflow/xxx/*` 对应 `src/api/workflow/xxx/*` 与 `src/views/workflow/xxx/*`
- 后端 `/demo/xxx/*` 对应 `src/api/demo/xxx/*` 与 `src/views/demo/xxx/*`
- 后端 `/system/user/*` 对应 `src/api/system/user/*` 与 `src/pages/system/user/*`
- 后端 `/monitor/xxx/*` 对应 `src/api/monitor/xxx/*` 与 `src/pages/monitor/xxx/*`
- 后端 `/workflow/xxx/*` 对应 `src/api/workflow/xxx/*` 与 `src/pages/workflow/xxx/*`
- 后端 `/demo/xxx/*` 对应 `src/api/demo/xxx/*` 与 `src/pages/demo/xxx/*`
标准新增通常至少包含:
- `src/api/<module>/<business>/index.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
以 `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
以 `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. 强业务页面
@@ -105,12 +100,23 @@ description: 在 plus-ui 前端项目中按真实 Vue 3 + TypeScript + Element P
### 4. 工作流页面
workflow 目录优先参考 `src/views/workflow/*`。流程定义、流程实例、任务列表、请假申请等页面通常有业务按钮、弹窗和路由跳转,不要硬套 system 模块。
workflow 目录优先参考 `src/pages/workflow/*`。流程定义、流程实例、任务列表、请假申请等页面通常有业务按钮、弹窗和路由跳转,不要硬套 system 模块。
### 5. 只补 API 和 types
只维护 `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 时,默认期望产出应满足:
@@ -119,21 +125,26 @@ workflow 目录优先参考 `src/views/workflow/*`。流程定义、流程实例
- API 路径、函数名、权限标识与后端接口保持一致。
- 标准页查询、重置、分页、弹窗、提交、删除、导出流程闭环完整。
- 复杂页面保留原有交互能力和业务约束。
- 代码体现当前项目 hooks、页面壳和下载方式,而不是 `gen` 模板裸输出。
- 代码体现当前项目 hooks、页面壳和下载方式,而不是 Vue 版本或 generator 裸输出。
- 生成模板变更要同步反映当前项目公共 hooks/工具,避免生成的新页面重复手写日期范围、字典 options、树展开、loading、搜索重置和确认框逻辑。
- 交付前说明运行过的验证命令;如果无法验证,说明原因。
## 快速检查清单
- `AxiosPromise` 是否来自 `@/utils/api-types`。
- `PageResult` 是否来自 `@/api/types`。
- API 是否从 `@/api/request` 引入 `request`。
- `R`、`PageResult`、`PageQuery`、`BaseEntity` 是否来自 `@/api/types`。
- API `params` 和 `data` 是否与后端方法一致。
- 日期范围是否通过 `useDateRangeQuery` 或附近页面现有方式处理。
- 列表 loading 是否通过 `useLoading` 或原页面方式维护。
- 弹窗是否通过 `useFormDialog` 或原页面方式维护。
- 多选状态是否通过 `useTableSelection` 或原页面方式维护。
- 权限指令是否保持同文件一致,默认使用当前项目主流 `v-hasPermi`。
- 导出是否使用 `requestDownload('<module>/<business>/export', { ...queryParams.value }, '<name>_<time>.xlsx')`。
- 页面壳是否保留 `search-panel`、`table-panel`、`toolbar-shell`、`data-table`、`right-toolbar`、`pagination`。
- 分页表格 request 是否通过 `toPageQuery(params)` 和 `toTableData(res)`。
- 导出是否通过 `useTableExport` 和 `exportFile('/module/business/export', () => 'name_timestamp.xlsx')`。
- 多选是否通过 `useTableSelection<T>(row => row.id)`。
- 行操作是否优先使用 `RowActions` 和 Ant Design 图标。
- 权限是否通过 `useUserStore` + `hasPermi` 计算,不要写 Vue 指令。
- 日期范围是否通过 `useDateRangeQuery` 或 `addDateRange` 相关工具处理。
- 字典 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` 修改 `workflow/category` 列表页,增加导出按钮和状态筛选,保持当前 workflow 风格。
- 使用 `$frontend-crud-coding` 为 `/system/client` 补一套 React 标准 CRUD 页面,参考 `demo/demo`、现有 `system/client` 和 boot4 generator 模板。
- 使用 `$frontend-crud-coding` 修改 `workflow/category` 列表页,增加导出按钮和状态筛选,保持当前 workflow 树表风格。
@@ -1,7 +1,7 @@
interface:
display_name: "前端编码"
short_description: "按 plus-ui 6.X-Vue 真实代码和 gen 模板编写前端 CRUD"
default_prompt: "使用 $frontend-crud-coding 先读取适用 reference,再按 plus-ui Gitee 仓库 6.X-Vue 分支的真实页面、hooks、API/types 约定和项目内 gen 模板实现前端修改。"
short_description: "按 plus-ui-react 真实代码和 gen 模板编写 React CRUD"
default_prompt: "使用 $frontend-crud-coding 先读取适用 reference,再按 plus-ui-react 真实页面、项目内置 gen 模板、API/types、ProTable、ModalForm、权限、导出和公共工具约定实现前端修改。"
policy:
allow_implicit_invocation: true
@@ -5,18 +5,19 @@
### 用户提问示例
```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}。
请参考 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/views/demo/demo/index.vue` 的标准 hooks 版 CRUD 骨架。
- 对照项目内 `gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.vue.ftl`。
- 生成或修改 `api/index.ts`、`types.ts`、`views/.../index.vue`。
- 使用 `AxiosPromise` from `@/utils/api-types`、`PageResult` from `@/api/types`、`useLoading`、`useFormDialog`、`useSearchReset`、`useTableSelection`。
- 先看 `src/api/system/client/*` 和 `src/pages/system/client/index.tsx` 是否已存在。
- 再看 `src/pages/demo/demo/index.tsx` 的标准 React CRUD 骨架。
- 新增标准生成能力时优先对照当前仓库 `gen/index.tsx.ftl`、`gen/api.ts.ftl`、`gen/types.ts.ftl`;boot4 generator 只用于核对字段、权限和接口。
- 生成或修改 `api/index.ts`、`types.ts`、`pages/.../index.tsx`。
- 使用 `request<R<PageResult<T>>>`、`ProTable`、`ModalForm`、`RowActions`、`useTableSelection`、`useTableExport`。
- 日期范围、字典 options、确认框、loading 等优先使用 `useDateRangeQuery`、`dictOptions`、`confirmAction`、`useLoading` 等项目工具。
## 案例 2:新增树表页面
@@ -24,17 +25,33 @@
```text
使用 $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` 页面。
- API 列表返回 `AxiosPromise<Tree2VO[]>`。
- 判断这是树表,不生成分页 `PageResult` 页面。
- API 列表返回 `R<Tree2VO[]>`。
- `Query` 不继承 `PageQuery`。
- 页面使用 `handleTree`、`row-key`、`tree-props`、`useTreeTableExpand`、`el-tree-select`。
- 页面使用 `handleTree`、`pagination={false}`、`useTreeTableExpand`、`ProFormTreeSelect`。
- 新增子节点时从当前行带入 `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:修改已有复杂列表页
### 用户提问示例
@@ -42,15 +59,15 @@
```text
使用 $frontend-crud-coding 修改 system/user 页面:
1. 新增一个创建时间快捷筛选
2. 导出按钮保留在更多菜单中
3. 保持现有树筛选、导入、列显隐、详情抽屉和角色分配不变
2. 导出按钮保留在工具栏中
3. 保持现有树筛选、导入、详情抽屉和角色分配不变
```
### 期望执行方式
- 判断这是“已有复杂页面增强”,不是重新生成 CRUD。
- 优先阅读 `src/views/system/user/index.vue`。
- 保留 `TreePanel`、导入弹窗、`right-toolbar` 列显隐、`UserViewDrawer`、角色分配路由、权限控制。
- 优先阅读 `src/pages/system/user/index.tsx` 和相关子组件。
- 保留 `TreePanel`、导入弹窗、`UserDetailDrawer`、角色分配路由、权限控制。
- 只增量修改搜索和查询参数处理。
## 案例 4:修改 workflow 页面
@@ -63,8 +80,8 @@
### 期望执行方式
- 优先看 `src/views/workflow/category/index.vue` 和 `src/api/workflow/category/*`。
- 判断是否需要后端新增导出接口;前端导出路径保持 `workflow/category/export`。
- 优先看 `src/pages/workflow/category/index.tsx` 和 `src/api/workflow/category/*`。
- 判断是否需要后端新增导出接口;前端导出路径保持 `/workflow/category/export`。
- 不迁移 system/user 的用户专属逻辑。
- 保留树表、`useTreeTableExpand`、`handleTree` 和分类弹窗逻辑。
@@ -79,8 +96,8 @@
### 期望执行方式
- 只维护 `src/api/monitor/cache/index.ts` 和 `src/api/monitor/cache/types.ts`。
- 仍然检查同目录 monitor API 的 `export function` / `export const` 风格。
- 返回类型使用 `AxiosPromise` from `@/utils/api-types`。
- 仍然检查同目录 monitor API 的命名、返回结构和导出风格。
- 返回类型使用 `R<T>` 和 `PageResult<T>` from `@/api/types`。
- 不创建页面,不改路由。
## 案例 6:接入后端新增状态切换接口
@@ -88,26 +105,26 @@
### 用户提问示例
```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)`。
- types 确认 `status` 类型是 string、number 还是 boolean。
- 表格列用 `el-switch`,active/inactive 值跟后端字段类型一致。
- 切换失败时回滚原状态。
- 表格列用 Ant Design `Switch`,checked 值跟后端字段类型一致。
- 切换失败时回滚原状态或刷新列表。
- 权限使用 `system:client:edit` 或后端实际权限。
## 推荐的高质量任务描述
```text
使用 $frontend-crud-coding 在当前前端项目中新增 `/system/notice` 列表页增强:
使用 $frontend-crud-coding 在当前 React 前端项目中增强 `/system/notice` 列表页:
1. 保留现有页面
2. 新增状态筛选和导出
3. API 路径沿用后端接口
4. 参考 system/config 的工具栏与导出交互
5. 参考 plus-ui Gitee 仓库 6.X-Vue 分支的 gen 模板补齐缺失 types
5. 参考当前项目 gen 模板和 boot4 generator 字段补齐缺失 types
```
## 不推荐的任务描述
@@ -2,41 +2,36 @@
## 优先参考的代码来源
- 默认远端基线:`https://gitee.com/JavaLionLi/plus-ui`,分支 `6.X-Vue`。引用远端文件时必须同时写明 `branch=6.X-Vue` 和仓库内路径。
- 当前目标目录下最近似页面、API、types。
- 标准单表:`src/views/demo/demo/index.vue`、`src/api/demo/demo/index.ts`、`src/api/demo/demo/types.ts`。
- 树表:`src/views/demo/tree/index.vue`、`src/views/workflow/category/index.vue`。
- 复杂系统页:`src/views/system/user/index.vue`、`src/views/system/role/index.vue`、`src/views/system/post/index.vue`、`src/views/system/config/index.vue`。
- workflow 页:`src/views/workflow/*`、`src/api/workflow/*`。
- 监控页:`src/views/monitor/*`、`src/api/monitor/*`。
- 公共 hooks:`src/hooks/async/useLoading.ts`、`src/hooks/dialog/*`、`src/hooks/form/*`、`src/hooks/table/*`、`src/hooks/tree/*`。
- 项目内 generator 模板:
`gen/api.ts.ftl`
`gen/types.ts.ftl`
`gen/index.vue.ftl`
`gen/index-tree.vue.ftl`
- 当前仓库内置 React 生成模板:`gen/api.ts.ftl`、`gen/types.ts.ftl`、`gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl`。
- 标准单表:`src/pages/demo/demo/index.tsx`、`src/api/demo/demo/index.ts`、`src/api/demo/demo/types.ts`。
- 树表:`src/pages/demo/tree/index.tsx`、`src/pages/workflow/category/index.tsx`。
- 复杂系统页:`src/pages/system/user/index.tsx`、`src/pages/system/role/index.tsx`、`src/pages/system/post/index.tsx`、`src/pages/system/config/index.tsx`。
- workflow 页:`src/pages/workflow/*`、`src/api/workflow/*`。
- 监控页:`src/pages/monitor/*`、`src/api/monitor/*`。
- 公共 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`。
- 公共组件:`src/components/common/RowActions.tsx`、`TreePanel.tsx`、`RightToolbar.tsx`、`DictTag.tsx`、上传/预览组件。
- 工具函数:`src/utils/ruoyi.ts`、`src/utils/permission.ts`、`src/utils/download.ts`、`src/utils/dict.ts`、`src/utils/modal.ts`。
## 基础栈与格式
- 技术栈是 Vue 3 + TypeScript + Element Plus + Vite + Pinia。
- 技术栈是 React + TypeScript + Umi Max + Ant Design + ProComponents + ahooks + Zustand + TanStack Query。
- 包管理按仓库现状使用 pnpm。
- `.editorconfig` 要求 UTF-8、LF、2 空格缩进。
- 当前仓库没有 `.prettierrc`;格式化使用 `pnpm run fmt`,lint 使用 `pnpm lint`。
- `.editorconfig` 要求 UTF-8、2 空格缩进。
- 当前仓库没有 Prettier;格式化使用 `pnpm run fmt`,lint 使用 `pnpm lint`。
- 不要在一个页面里混入与仓库不一致的格式和写法。
- 不要把本机绝对路径写进 skill 输出或参考文档;跨环境参考统一使用 Gitee 仓库 URL、分支和仓库内相对路径。
## API 文件规则
- 标准 API 文件放在 `src/api/<module>/<business>/index.ts`,同目录维护 `types.ts`。
- import 顺序优先跟随附近文件,标准生成页常见形式:
`import type { XxxForm, XxxQuery, XxxVO } from '@/api/<module>/<business>/types';`
`import type { PageResult } from '@/api/types';`
`import type { AxiosPromise } from '@/utils/api-types';`
`import request from '@/utils/request';`
- 不要从 `axios` 引入 `AxiosPromise`。
- 列表分页接口通常返回 `AxiosPromise<PageResult<XxxVO>>`。
- 树表列表接口通常返回 `AxiosPromise<XxxVO[]>`。
- 详情接口返回 `AxiosPromise<XxxVO>`;复杂详情返回单独的 `InfoVO`。
- import 顺序优先跟随附近文件,标准形式:
`import type { PageResult, R } from '@/api/types';`
`import request from '@/api/request';`
`import type { XxxForm, XxxQuery, XxxVO } from './types';`
- 不要从 `axios` 引入 `AxiosPromise`;当前 `request<T>()` 返回 `Promise<T>`,接口函数通常返回 `Promise<R<T>>`。
- 列表分页接口通常返回 `request<R<PageResult<XxxVO>>>({ url, method: 'get', params: query })`。
- 树表列表接口通常返回 `request<R<XxxVO[]>>({ url, method: 'get', params: query })`。
- 详情接口返回 `R<XxxVO>`;复杂详情返回单独的 `InfoVO`。
- 标准函数命名:
`listXxx` -> `GET /<module>/<business>/list`
`getXxx` -> `GET /<module>/<business>/{id}`
@@ -46,8 +41,8 @@
`changeXxxStatus` -> `PUT /<module>/<business>/changeStatus`
- query string 用 `params`,请求体用 `data`。
- 加密、防重复提交等 headers 直接写在请求配置里,例如用户重置密码中的 `isEncrypt`、`repeatSubmit`。
- 当前仓库有些 API 使用 `export const`,有些使用 `export function`;新增标准 CRUD 优先跟随 `gen/api.ts.ftl` 和相邻模块。
- 只有相邻模块已有 `export default { ... }` 聚合时才新增默认导出。
- 当前仓库多数 API 使用 `export function`;新增标准 CRUD 优先跟随相邻模块。
- 只有相邻模块已有聚合对象时才新增默认导出。
## 类型文件规则
@@ -57,100 +52,103 @@
- 树表 `Query` 通常不继承 `PageQuery`。
- ID 字段通常使用 `string | number`,批量删除参数使用 `string | number | Array<string | number>`。
- Java 数值类型映射为 `number`,Boolean 映射为 `boolean`,日期/文本默认 `string`。
- 日期范围查询保留 `params?: any`,不要因为它看起来宽松就删掉。
- 日期范围查询保留 `params?: Record<string, unknown>` 或跟随相邻页面现有类型,不要因为它看起来宽松就删掉。
- 列表对象、表单对象、查询对象职责分开;字段不一致时不要强行复用一个接口。
- 能明确写出类型时不要用 `any`;组件库、字典或历史接口确实无法收窄时再保留。
## Vue 页面结构规则
## React 页面结构规则
- 页面优先使用 `<script setup name="Xxx" lang="ts">`。
- 标准根节点使用 `class="p-2 app-container <module>-<business>-page"`;已有页面是特殊布局时保持原样。
- 标准列表页结构:
搜索卡片 `search-panel`
表格卡片 `table-panel`
工具栏 `toolbar-shell`
表格 `data-table`
`right-toolbar`
`pagination`
新增/编辑 `el-dialog`
- 搜索区通过 `useSearchToggle` 控制 `showSearch`,头部点击切换。
- 列表 loading 通过 `useLoading(true)` 和 `withLoading`。
- 选择状态通过 `useTableSelection<XxxVO>(item => item.id)` 返回 `ids`、`single`、`multiple`、`handleSelectionChange`。
- 表单弹窗优先使用 `useFormDialog({ form, formRef, initialFormData })`,返回 `dialog`、`resetForm`、`openDialog`、`showDialog`、`closeDialog`。
- 仅需要简单弹窗状态或一个页面多个弹窗时使用 `useDialogState`。
- 日期范围查询优先使用 `useDateRangeQuery()`;带后端参数名时使用 `useDateRangeQuery('CreateTime')` 等相邻页面模式。
- 查询和表单状态通常放在 `reactive<PageData<Form, Query>>({ form, queryParams, rules })`,再 `toRefs(data)`。
- 页面默认导出函数组件,命名如 `SystemUserPage`、`DemoDemoPage`、`WorkflowCategoryPage`。
- 标准页使用 `PageContainer` 包裹 `ProTable` 和 `ModalForm`。
- 标准列表页常见状态:
`actionRef`、`form`、`modalOpen`、`modalTitle`、`ids`、`selectedOne`、权限布尔值。
- 弹窗状态优先使用 `useBoolean(false)`,表单用 `Form.useForm<XxxForm>()`。
- ProTable 使用:
`rowKey`
`columns`
`search={{ labelWidth: 90 }}`
`pagination={{ defaultPageSize: 10, showSizeChanger: true }}`
`request={async params => { ... }}`
`toolBarRender={() => [...]}`
- 分页查询使用 `toPageQuery(params)`,然后 `updateExportParams(query)`,最后 `return toTableData(res)`。
- 表格列类型使用 `ProColumns<XxxVO>[]`。
- 新增/编辑弹窗使用 `ModalForm<XxxForm>`,字段优先使用 `ProFormText`、`ProFormDigit`、`ProFormSelect`、`ProFormTreeSelect`、`ProFormDateTimeRangePicker` 等 ProForm 组件。
- ModalForm 成功提交后返回 `true`,并刷新 `actionRef.current?.reload()`。
## 页面行为规则
- `getList` 负责设置 loading、调用列表接口、回填列表和 `total`。
- `handleQuery` 先把 `queryParams.value.pageNum = 1`,再调用 `getList()`;树表无分页时只调用 `getList()`。
- `resetQuery` 使用 `useSearchReset`,分页页传 `pageNumKey: 'pageNum'`,需要时传 `pageSizeKey` 和 `resetExtras`。
- `handleAdd` 使用 `openDialog('添加xxx')`;如果有树/联动选项,打开前后按现有页面加载选项。
- `handleUpdate` 先 `reset()`,再按行或 `ids.value[0]` 查详情,`Object.assign(form.value, res.data)`,最后 `showDialog('修改xxx')`。
- `submitForm` 表单校验通过后设置 `buttonLoading`,根据主键判断新增或修改,成功后 `modal.msgSuccess('操作成功')`、关闭弹窗、刷新列表。
- `handleDelete` 使用 `modal.confirm(...)` 二次确认,再调用删除接口,成功提示并刷新。
- `handleExport` 使用 `requestDownload('<module>/<business>/export', { ...queryParams.value }, '<business>_<timestamp>.xlsx')`。
- 状态切换失败时要把 switch 值回滚,参考 generator 模板和现有 `system/user`、`system/role`。
- 导入上传使用 `globalHeaders()`、`import.meta.env.VITE_APP_BASE_API`、`ElUpload`,优先参考 `system/user` 或流程定义页面。
- `openAdd` 负责 `form.resetFields()`、设置默认值、设置标题、打开弹窗。
- `openEdit` 先按行或 `selectedOne` 找目标,再查详情,`form.resetFields()`,`form.setFieldsValue(res.data)`,最后打开弹窗。
- `submitForm` 根据主键判断新增或修改,成功后 `message.success('操作成功')`、重置表单、刷新列表。
- `remove` 或 `handleDelete` 支持行删除和批量删除,成功后 `message.success('删除成功')`、`clearSelection()`、`reloadAndRest` 或 `reload`。
- 批量按钮通常用 `Popconfirm`,行操作确认优先放在 `RowActions` 的 `confirm`。
- 状态切换失败时要回滚或刷新,参考 `src/pages/system/user/index.tsx`。
- 日期范围查询优先使用 `useDateRangeQuery`,底层通过 `addDateRange` 写入 `params.beginXxx/endXxx`。
- 字典 options 优先使用 `dictOptions(dicts.xxx)`。
- 导入上传参考 `system/user/components/UserImportModal.tsx` 或流程定义导入弹窗,保留 `globalHeaders()` 和 `appEnv.baseApi` 相关方式。
## 字典、权限与公共工具
- 字典使用 `useDict`:
`const { sys_normal_disable } = toRefs<any>(useDict('sys_normal_disable'));`
- 新增代码默认使用当前项目主流 `v-hasPermi`。
- 如果正在修改的文件已经混用或使用 `v-has-permi`,保持同文件现状,不为统一写法而重排无关代码。
- `el-dropdown-item` 延迟加载导致权限指令不可靠时,使用 `v-if="checkPermi([...])"`,参考 `system/user`。
- 字典使用 `const dicts = useDict('sys_normal_disable', 'sys_user_gender')`。
- 字典 options 使用 `dictOptions` from `@/utils/dict` 映射为 `{ label, value }`。
- 权限使用:
`const userInfo = useUserStore(state => state.userInfo);`
`const canAdd = hasPermi(userInfo, ['system:user:add']);`
- 不要使用 Vue 权限指令 `v-hasPermi`。
- 常用工具:
`modal` from `@/plugins/modal`
`download as requestDownload` from `@/utils/request`
`useDict` from `@/utils/dict`
`checkPermi` from `@/utils/permission`
`handleTree`、`parseStrEmpty` from `@/utils/ruoyi`
`hasPermi` from `@/utils/permission`
`handleTree`、`parseStrEmpty`、`toPageQuery`、`toTableData`、`addDateRange`、`formatDateTimeRange` from `@/utils/ruoyi`
`dictOptions` from `@/utils/dict`
`confirmAction`、`confirmTitleSafe` from `@/utils/modal`
`download` via `useTableExport`
## 组件与样式规则
- 优先复用公共组件:`right-toolbar`、`pagination`、`DictTag` / `dict-tag`、`ImageUpload` / `image-upload`、`ImagePreview` / `image-preview`、`FileUpload` / `file-upload`、`Editor` / `editor`、`TreePanel`。
- 标准页面尽量使用已有页面壳类,不堆大量内联样式。
- 复杂页面需要 scoped SCSS 时优先复用:
`@use '@/assets/styles/components/page-shell' as pageShell;`
再按现有 mixin 使用。
- 行操作优先复用 `RowActions`,并使用 `@ant-design/icons` 图标。
- 树筛选优先复用 `TreePanel`,参考 `system/user`。
- 导出优先复用 `useTableExport`。
- 多选优先复用 `useTableSelection`。
- 异步 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')` 组树。
- 使用 `row-key`、`:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"`。
- 展开/折叠使用 `useTreeTableExpand`。
- 表单中上级节点使用 `el-tree-select`。
- 树表列表接口通常返回数组,页面通过 `handleTree<T>(res.data || [], 'id', 'parentId')` 组树。
- `ProTable` 使用 `pagination={false}`。
- 使用 `useTreeTableExpand` 控制 `expandedRowKeys`、`onExpandedRowsChange`、`syncExpandedRows`、`toggleExpandAll`。
- 展开/折叠按钮参考 `demo/tree` 和 `workflow/category`,用 `SortAscendingOutlined`。
- 表单中上级节点使用 `ProFormTreeSelect`。
- 新增子节点时从当前行回填 `parentId`。
- 删除确认文案优先使用业务名称,而不是批量 ID 文案。
## 与 gen 模板的关系
## 与 gen 模板、Vue 参考项目和后端生成器的关系
- `gen` 是 plus-ui 前端项目内的生成模板;默认远端位置是 `https://gitee.com/JavaLionLi/plus-ui/tree/6.X-Vue/gen`,分支必须标记为 `6.X-Vue`。
- 本地仓库存在对应文件时优先读本地 `gen`,但不要把本地绝对路径写入 skill 或交付内容。
- 新增标准单表页面时读取 `gen/index.vue.ftl`、`gen/api.ts.ftl`、`gen/types.ts.ftl`。
- 新增树表页面时读取 `gen/index-tree.vue.ftl`、`gen/api.ts.ftl`、`gen/types.ts.ftl`。
- `gen` 模板是标准骨架,不是最终答案;落地时仍要对照目标模块真实页面和公共 hooks。
- 当前前端项目已经把生成页升级为 hooks 版:`useLoading`、`useFormDialog`、`useSearchReset`、`useTableSelection`、`useDateRangeQuery`。
- 新增标准 CRUD 时,先从 `gen` 确认字段、权限、导出、状态切换、排序、日期范围等,再落成当前项目的实际页面壳。
- 修改已有页面时,不要把现有强业务逻辑替换回 `gen` 的简化逻辑。
- 当前仓库 `gen/*.ftl` 是 React 版内置代码生成模板,是维护生成能力时的第一参考。
- `gen/index.tsx.ftl`、`gen/index-tree.tsx.ftl` 内容必须生成 React TSX 页面。
- 修改公共 hooks/工具后,如果它们能简化标准生成页,要同步评估 `gen/*.ftl` 是否需要更新。
- Vue skill 和 `.claude/agents` 提供的是任务分型、优先级、增量修改、自检方式,不是 React 实现模板。
- boot4 后端 generator 模板可用于确认字段、权限、导出、状态切换、排序、日期范围等,但不是最终答案。
- 当前 React 项目的核心骨架是 `ProTable`、`ModalForm`、`RowActions`、`useTableSelection`、`useTableExport`。
- 修改已有页面时,不要把现有强业务逻辑替换回 generator 的简化逻辑。
## 验证规则
- 只改文档或 skill:运行 skill 基础校验即可。
- 改前端 TS/Vue/API/types:优先运行 `pnpm exec vue-tsc --noEmit`。
- 改页面模板、import、权限或较多文件:再运行 `pnpm lint`。
- 只改文档、skill 或 `gen/*.ftl` 模板:至少运行 skill 基础校验或 `git diff --check`;如果模板改动依赖项目工具,优先再跑 `pnpm lint`。
- 改前端 TS/TSX/API/types:优先运行 `pnpm exec tsc --noEmit` 或 `pnpm lint`。
- 改页面、import、权限或较多文件:运行 `pnpm lint`。
- 改公共 hooks、组件、构建相关或大范围页面:再运行 `pnpm build`。
- 如果验证因为环境、依赖或权限失败,交付时说明失败命令和原因。
## 避免事项
- 不要从 `axios` 引入 `AxiosPromise`。
- 不要绕开 `request` 或 `requestDownload` 自造请求/下载封装。
- 不要绕开 `@/api/request` 或 `useTableExport` 自造请求/下载封装。
- 不要跳过 `types.ts`,把类型全写在页面里。
- 不要删除日期范围 `params`、权限指令、导出、导入、树筛选、列显隐等现有能力。
- 不要把 `src/pages` 写成 Vue 项目的 `src/views`。
- 不要使用 Vue 的 `ref`、`reactive`、`toRefs`、`v-hasPermi`、Element Plus 组件或 SCSS 页面壳。
- 不要删除日期范围 `params`、权限判断、导出、导入、树筛选、列显隐等现有能力。
- 不要为了“更整洁”重写复杂页面的大块业务逻辑。
- 不要在新增标准页里使用与仓库不一致的 UI 壳或状态管理方式。
+2 -14
View File
@@ -1,21 +1,9 @@
# 告诉EditorConfig插件,这是根文件,不用继续往上查找
root = true
# 匹配全部文件
[*]
# 缩进风格,可选space、tab
indent_style = space
# 缩进的空格数
indent_size = 2
# 设置字符集
charset = utf-8
# 结尾换行符,可选lf、cr、crlf
end_of_line = lf
# 在文件结尾插入新行
trim_trailing_whitespace = true
# 删除一行中的前后空格
insert_final_newline = true
indent_style = space
indent_size = 2
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
+15 -45
View File
@@ -1,45 +1,15 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
# 后台请求地址
VITE_API_TARGET=http://localhost:8080
# 开发环境配置
VITE_APP_ENV = 'development'
# 开发环境
VITE_APP_BASE_API = '/dev-api'
# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/'
# 监控地址
VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications'
# 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'
VITE_APP_TITLE=RuoYi-React-Plus后台管理系统
VITE_APP_LOGO_TITLE=RuoYi-React-Plus
VITE_APP_ENV=development
VITE_APP_BASE_API=/dev-api
VITE_APP_CONTEXT_PATH=/
VITE_APP_PORT=8000
VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
VITE_APP_MESSAGE_ENABLED=true
VITE_APP_MESSAGE_TRANSPORT=sse
VITE_APP_MESSAGE_PATH=/resource/message
VITE_APP_ENCRYPT=true
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_SNAILJOB_ADMIN=http://localhost:8800/snail-job
+15 -45
View File
@@ -1,45 +1,15 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
# 生产环境配置
VITE_APP_ENV = 'production'
# 应用访问路径 例如使用前缀 /admin/
VITE_APP_CONTEXT_PATH = '/'
# 监控地址
VITE_APP_MONITOR_ADMIN = '/admin/applications'
# SnailJob 控制台地址
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'
VITE_APP_TITLE=RuoYi-React-Plus后台管理系统
VITE_APP_LOGO_TITLE=RuoYi-React-Plus
VITE_APP_ENV=production
VITE_APP_BASE_API=/prod-api
VITE_APP_CONTEXT_PATH=/
VITE_APP_PORT=8000
VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
VITE_APP_MESSAGE_ENABLED=true
VITE_APP_MESSAGE_TRANSPORT=sse
VITE_APP_MESSAGE_PATH=/resource/message
VITE_APP_ENCRYPT=true
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=/admin/applications
VITE_APP_SNAILJOB_ADMIN=/snail-job
+9 -28
View File
@@ -1,29 +1,10 @@
.DS_Store
.history
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
**/*.log
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
# Editor directories and files
.idea
.vscode
node_modules
dist
.umi
.umi-production
src/.umi
src/.umi-production
.env.local
*.log
.idea/
*.iml
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
package-lock.json
yarn.lock
# 编译生成的文件
auto-imports.d.ts
components.d.ts
+10
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
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
View File
@@ -0,0 +1,3 @@
<component name="ProjectDictionaryState">
<dictionary name="LionLi" />
</component>
+6
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
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
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>
Generated
+6
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>
+1 -8
View File
@@ -1,13 +1,6 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": [
"src/types/components.d.ts",
"src/types/auto-imports.d.ts",
".ai_state",
".claude",
".codex",
"doc"
],
"ignorePatterns": ["src/.umi", "src/.umi-production", ".ai_state", ".claude", ".codex", "dist", "node_modules"],
"printWidth": 120,
"singleQuote": true,
"trailingComma": "none",
+17 -2
View File
@@ -1,6 +1,6 @@
{
"$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": {
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
@@ -12,7 +12,13 @@
"import/no-unassigned-import": "off",
"import/no-named-as-default-member": "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",
"unicorn/no-array-sort": "off",
"unicorn/no-useless-fallback-in-spread": "off",
"unicorn/prefer-add-event-listener": "off",
"unicorn/consistent-function-scoping": "off",
"unicorn/no-instanceof-builtins": "off"
@@ -21,5 +27,14 @@
"correctness": "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
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.
+23 -21
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 + Ant Design 版本前端项目 [plus-ui-react](https://gitee.com/JavaLionLi/plus-ui/tree/6.X-react/)
- 本仓库为前端技术栈 [React](https://react.dev/) + [TypeScript](https://www.typescriptlang.org/) + [Ant Design](https://ant.design/) + [Umi Max](https://umijs.org/) + [Vite](https://vitejs.dev/)。
- 官方项目: 基于 Vue + ElementPlus 版本前端项目 [plus-ui](https://gitee.com/JavaLionLi/plus-ui)
- 成员项目: 基于 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)
## 配套后端代码仓库地址
| 介绍 | 项目名 | 项目地址 |
| ----------------- | :--------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 🔥 分布式集群框架 | 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-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-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) / [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus) / [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) |
## 分支说明
- 6.X分支(稳定发布主分支 生产可用)
- dev分支(开发分支 开发过程中使用)
- 6.X-react分支(稳定发布主分支 生产可用)
- dev-react分支(开发分支 开发过程中使用)
## 前端运行
@@ -29,7 +31,7 @@ pnpm dev
# 构建生产环境
pnpm build:prod
# 前端访问地址 http://localhost:80
# 前端访问地址 http://localhost:8000
```
## 本框架与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/1780299074949590692/27f5bfb5_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299108816841231/619a7c57_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/1780299193694706123/28257dc1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299147525013883/ebcd9dfe_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/1780299250884681522/e5731314_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299267028602229/230d5428_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/1780299327888096947/283a177f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299348897579356/caac864e_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/1780299432356918392/07abdf6a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299465584172180/a2b2be12_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/1780299527419238776/549cb852_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299553918371792/43bd3bff_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/1780300015922774817/47c1f39f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300034170229441/1608ee0a_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/1780300081538003511/906c028f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300104233264980/8df4a03d_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/1780300380395316233/d1cc70c4_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300232306768234/1f474295_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/1780300401947949756/673c7fdc_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300417037474904/2fd89565_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/1780300471231552129/5d45227c_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300496559910468/0008fa0c_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/1780300617515185769/db2968e4_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300631462725775/30b9cd2c_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/1780300695487754948/17569a2e_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780300715114472355/ebb81e7c_1766278.png "屏幕截图") |
+40
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
});
+38 -44
View File
@@ -1,104 +1,98 @@
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
<#if !table.tree>
import type { PageResult } from '@/api/types';
</#if>
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { <#if !table.tree>PageResult, </#if> R } from '@/api/types';
import request from '@/api/request';
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from './types';
/**
* 查询${functionName}列表
* @param query
* @returns {*}
*/
export const list${BusinessName} = (query?: ${BusinessName}Query): AxiosPromise<<#if table.tree>${BusinessName}VO[]<#else>PageResult<${BusinessName}VO></#if>> => {
return request({
export function list${BusinessName}(query?: ${BusinessName}Query) {
return request<R<<#if table.tree>${BusinessName}VO[]<#else> PageResult<${BusinessName}VO></#if>>>({
url: '/${moduleName}/${businessName}/list',
method: 'get',
params: query
});
};
}
/**
* 查询${functionName}详细
* @param ${pkColumn.javaField}
*/
export const get${BusinessName} = (${pkColumn.javaField}: string | number): AxiosPromise<${BusinessName}VO> => {
return request({
export function get${BusinessName}(${pkColumn.javaField}: string | number) {
return request<R<${BusinessName}VO>>({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'get'
});
};
}
/**
* 新增${functionName}
* @param data
*/
export const add${BusinessName} = (data: ${BusinessName}Form) => {
return request({
export function add${BusinessName}(data: ${BusinessName}Form) {
return request<R>({
url: '/${moduleName}/${businessName}',
method: 'post',
data: data
data
});
};
}
/**
* 修改${functionName}
* @param data
*/
export const update${BusinessName} = (data: ${BusinessName}Form) => {
return request({
export function update${BusinessName}(data: ${BusinessName}Form) {
return request<R>({
url: '/${moduleName}/${businessName}',
method: 'put',
data: data
data
});
};
}
<#if enableStatus>
/**
* 修改${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>) => {
return request({
export function change${BusinessName}Status(
${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',
method: 'put',
data: {
${pkColumn.javaField},
${statusField}: status
${statusField}
}
});
};
</#if>
}
</#if>
<#if enableSort>
/**
* 调整${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>) => {
return request({
export function update${BusinessName}Sort(
${pkColumn.javaField}: string | number,
${sortField}: <#if sortColumn.javaType == 'LocalDateTime' || sortColumn.javaType == 'String'>string<#else> number</#if>
) {
return request<R>({
url: '/${moduleName}/${businessName}/updateSort',
method: 'put',
data: {
${pkColumn.javaField},
${sortField}: sortValue
${sortField}
}
});
};
</#if>
}
</#if>
/**
* 删除${functionName}
* @param ${pkColumn.javaField}
*/
export const del${BusinessName} = (${pkColumn.javaField}: string | number | Array<string | number>) => {
return request({
export function del${BusinessName}(${pkColumn.javaField}: string | number | Array<string | number>) {
return request<R>({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'delete'
});
};
}
+427
View File
@@ -0,0 +1,427 @@
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 { 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 [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={{ x: 1000 }}
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>
<#assign field = column.javaField>
<#if field == 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>
);
}
-691
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>
+409
View File
@@ -0,0 +1,409 @@
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 { 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 [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={{ x: 1000 }}
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>
<#assign field = column.javaField>
<#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>
);
}
-615
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>
+14 -5
View File
@@ -1,6 +1,8 @@
import type { BaseEntity<#if !table.tree>, PageQuery</#if> } from '@/api/types';
export interface ${BusinessName}VO {
<#list columns as column>
<#if column.list>
<#if column.list || column.pk>
/**
* ${column.columnComment}
*/
@@ -9,7 +11,7 @@ export interface ${BusinessName}VO {
/**
* ${column.columnComment}Url
*/
${column.javaField}Url: string;
${column.javaField}Url?: string;
</#if>
</#if>
</#list>
@@ -17,18 +19,22 @@ export interface ${BusinessName}VO {
/**
* 子对象
*/
children: ${BusinessName}VO[];
children?: ${BusinessName}VO[];
</#if>
}
export interface ${BusinessName}Form extends BaseEntity {
<#list columns as column>
<#if column.insert || column.edit>
<#if column.insert || column.edit || column.pk>
/**
* ${column.columnComment}
*/
<#if column.htmlType == "checkbox">
${column.javaField}?: string | string[];
<#else>
${column.javaField}?: ${column.tsType};
</#if>
</#if>
</#list>
}
@@ -44,5 +50,8 @@ export interface ${BusinessName}Query<#if !table.tree> extends PageQuery</#if> {
/**
* 日期范围参数
*/
params?: any;
params?: Record<string, unknown>;
}
-218
View File
@@ -1,218 +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 src="/tac/load.min.js"></script>
<script src="/tac/css/tac.css"></script>
<script src="/tac/js/tac.min.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+48 -65
View File
@@ -1,83 +1,66 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "ruoyi-vue-plus",
"version": "6.0.0",
"description": "RuoYi-Vue-Plus后台管理系统",
"license": "MIT",
"author": "LionLi",
"repository": {
"type": "git",
"url": "https://gitee.com/JavaLionLi/plus-ui.git"
},
"packageManager": "pnpm@10.34.5",
"name": "plus-ui-react",
"version": "6.0.0-BETA",
"private": true,
"description": "React + Ant Design version of RuoYi-Vue-Plus UI",
"type": "module",
"scripts": {
"build": "vite build --mode production",
"build:dev": "vite build --mode development",
"build:prod": "vite build --mode production",
"dev": "vite serve --mode development",
"build": "pnpm build:prod",
"build:dev": "cross-env UMI_ENV=development BABEL_POLYFILL=none max build",
"build:prod": "cross-env UMI_ENV=production BABEL_POLYFILL=none max build",
"dev": "cross-env UMI_ENV=development MOCK=none BABEL_POLYFILL=none max dev",
"fmt": "oxfmt .",
"lint": "oxlint src",
"lint:fix": "oxlint --fix src",
"preview": "vite preview"
"lint": "oxlint src config vite.config.ts && cross-env UMI_ENV=development BABEL_POLYFILL=none max setup && tsc --noEmit",
"lint:fix": "oxlint --fix src config vite.config.ts",
"openapi": "max openapi",
"preview": "max preview --port 8000",
"start": "cross-env UMI_ENV=development MOCK=none BABEL_POLYFILL=none max dev"
},
"dependencies": {
"@element-plus/icons-vue": "2.3.2",
"@highlightjs/vue-plugin": "2.1.2",
"@iconify/vue": "^5.0.1",
"@vueuse/core": "14.3.0",
"@wangeditor-next/editor": "5.7.16",
"@wangeditor-next/editor-for-vue": "5.1.14",
"animate.css": "4.1.1",
"await-to-js": "3.0.0",
"axios": "1.18.1",
"@ant-design/icons": "^6.2.5",
"@ant-design/pro-components": "3.1.12-0",
"@ant-design/x": "^2.7.0",
"@iconify/react": "^6.0.2",
"@tanstack/react-query": "^5.101.0",
"@umijs/max": "^4.6.61",
"@wangeditor-next/editor": "^5.7.10",
"@wangeditor-next/editor-for-react": "^2.0.2",
"ahooks": "^3.9.7",
"antd": "^6.4.3",
"antd-style": "^4.1.0",
"axios": "1.17.0",
"browser-image-compression": "^2.0.2",
"clsx": "^2.1.1",
"crypto-js": "4.2.0",
"echarts": "6.1.0",
"element-plus": "2.14.3",
"highlight.js": "11.11.1",
"image-conversion": "2.1.1",
"dayjs": "^1.11.21",
"echarts": "^6.1.0",
"echarts-for-react": "^3.0.6",
"jsencrypt": "3.5.4",
"nprogress": "0.2.0",
"pinia": "4.0.2",
"vue": "3.5.40",
"vue-cropper": "1.1.4",
"vue-i18n": "11.4.8",
"vue-json-pretty": "2.6.0",
"vue-router": "5.2.0",
"vue-types": "7.0.0",
"vxe-pc-ui": "4.16.22",
"vxe-table": "4.20.8"
"react": "^19.2.7",
"react-dom": "^19.2.7",
"react-easy-crop": "^5.5.7",
"react-json-view-lite": "^2.5.0",
"react-syntax-highlighter": "^16.1.1",
"screenfull": "^6.0.2",
"zustand": "^5.0.14"
},
"devDependencies": {
"@types/crypto-js": "4.2.2",
"@types/node": "^26.1.1",
"@types/node": "^25.9.2",
"@types/nprogress": "0.2.3",
"@unocss/preset-attributify": "66.7.5",
"@unocss/preset-wind3": "66.7.5",
"@vitejs/plugin-vue": "^6.0.8",
"@vue/compiler-sfc": "3.5.40",
"autoprefixer": "10.5.4",
"oxfmt": "^0.60.0",
"oxlint": "^1.75.0",
"sass": "1.102.0",
"typescript": "^6.0.3",
"unocss": "66.7.5",
"unplugin-auto-import": "21.0.0",
"unplugin-vue-components": "32.1.0",
"unplugin-vue-setup-extend-plus": "1.0.1",
"vite": "^8.1.5",
"vite-plugin-svg-icons-ng": "^1.9.2",
"vitest": "4.1.10",
"vue-tsc": "^3.3.8"
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@types/react-syntax-highlighter": "^15.5.13",
"@umijs/lint": "^4.6.61",
"@umijs/max-plugin-openapi": "^2.0.3",
"cross-env": "^10.1.0",
"oxfmt": "^0.53.0",
"oxlint": "^1.68.0",
"typescript": "^6.0.3"
},
"browserslist": [
"Chrome >= 87",
"Edge >= 88",
"Safari >= 14",
"Firefox >= 78"
],
"engines": {
"node": ">=20.19.0",
"node": ">=20.0.0",
"pnpm": ">=10.0.0"
}
}
+15105 -2554
View File
File diff suppressed because it is too large Load Diff
-10
View File
@@ -1,10 +0,0 @@
packages:
- '.'
allowBuilds:
'@parcel/watcher': true
es5-ext: true
esbuild: true
overrides:
esbuild: 0.28.1
-258
View File
File diff suppressed because one or more lines are too long
-7
View File
@@ -1,7 +0,0 @@
#tianai-captcha-parent{box-shadow:0 0 11px 0 #999;width:318px;height:318px;overflow:hidden;position:relative;z-index:997;box-sizing:border-box;border-radius:5px;padding:8px}#tianai-captcha-parent #tianai-captcha-box{height:260px;width:100%;position:relative;overflow:hidden}#tianai-captcha-parent #tianai-captcha-box .loading{width:120px;height:20px;-webkit-mask:linear-gradient(90deg, #000 70%, rgba(0, 0, 0, 0) 0) 0/20%;background:linear-gradient(#f7b645 0 0) 0/0% no-repeat rgba(221,221,221,.4196078431);animation:cartoon 1s infinite steps(6);margin:120px auto}@keyframes cartoon{100%{background-size:120%}}#tianai-captcha-parent #tianai-captcha-box #tianai-captcha{transform-style:preserve-3d;will-change:transform;transition-duration:.45s;transform:translateX(-300px)}#tianai-captcha-parent #tianai-captcha-bg-img{background-color:#fff;background-position:top;background-size:cover;z-index:-1;width:100%;height:100%;top:0;left:0;position:absolute;border-radius:6px}#tianai-captcha-parent .slider-bottom{height:19px;width:100%}#tianai-captcha-parent .slider-bottom .close-btn{width:20px;height:20px;background-image:url(../../tac/images/icon.png);background-repeat:no-repeat;background-position:0 -14px;float:right;margin-right:2px;cursor:pointer}#tianai-captcha-parent .slider-bottom .refresh-btn{width:20px;height:20px;background-image:url(../../tac/images/icon.png);background-position:0 -167px;background-repeat:no-repeat;float:right;margin-right:10px;cursor:pointer}#tianai-captcha-parent .slider-bottom .logo{height:30px;float:left}#tianai-captcha-parent .slider-move-shadow{animation:myanimation 2s infinite;height:100%;width:5px;background-color:#fff;position:absolute;top:0;left:0;filter:opacity(0.5);box-shadow:1px 1px 1px #fff;border-radius:50%}#tianai-captcha-parent #tianai-captcha-slider-move-track-mask{border-width:1px;border-style:solid;border-color:#00f4ab;width:0;height:32px;background-color:#a9ffe5;opacity:.5;position:absolute;top:-1px;left:-1px;border-radius:5px}
#tianai-captcha{text-align:left;box-sizing:content-box;width:300px;height:260px;z-index:999}#tianai-captcha .slider-bottom .logo{height:30px}#tianai-captcha .slider-bottom{height:19px;width:100%}#tianai-captcha .content .tianai-captcha-tips{height:25px;width:100%;position:absolute;bottom:-25px;left:0;z-index:999;font-size:15px;line-height:25px;color:#fff;text-align:center;transition:bottom .3s ease-in-out}#tianai-captcha .content .tianai-captcha-tips.tianai-captcha-tips-error{background-color:#ff5d39}#tianai-captcha .content .tianai-captcha-tips.tianai-captcha-tips-success{background-color:#39c522}#tianai-captcha .content .tianai-captcha-tips.tianai-captcha-tips-on{bottom:0}#tianai-captcha .content #tianai-captcha-loading{z-index:9999;background-color:#f5f5f5;text-align:center;height:100%;overflow:hidden;position:relative;display:flex;justify-content:center;align-items:center}#tianai-captcha .content #tianai-captcha-loading img{display:block;width:45px;height:45px}#tianai-captcha #tianai-captcha-slider-bg-canvas{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:5px}#tianai-captcha #tianai-captcha-slider-bg-div{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:5px}#tianai-captcha #tianai-captcha-slider-bg-div .tianai-captcha-slider-bg-div-slice{position:absolute}@keyframes myanimation{from{left:0}to{left:289px}}
#tianai-captcha.tianai-captcha-slider{z-index:999;position:absolute;left:0;top:0;user-select:none}#tianai-captcha.tianai-captcha-slider .content{width:100%;height:180px;position:relative;overflow:hidden}#tianai-captcha.tianai-captcha-slider .bg-img-div{width:100%;height:100%;position:absolute;transform:translate(0px, 0px)}#tianai-captcha.tianai-captcha-slider .bg-img-div img{height:100%;width:100%;border-radius:5px}#tianai-captcha.tianai-captcha-slider .slider-img-div{height:100%;position:absolute;left:0;transform:translate(0px, 0px)}#tianai-captcha.tianai-captcha-slider .slider-img-div #tianai-captcha-slider-move-img{height:100%}#tianai-captcha.tianai-captcha-slider .slider-move{height:34px;width:100%;margin:11px 0;position:relative}#tianai-captcha.tianai-captcha-slider .slider-move-track{position:relative;height:32px;line-height:32px;text-align:center;background:#f5f5f5;color:#999;transition:0s;font-size:14px;box-sizing:content-box;border:1px solid #f5f5f5;border-radius:4px}#tianai-captcha.tianai-captcha-slider .refresh-btn,#tianai-captcha.tianai-captcha-slider .close-btn{display:inline-block}#tianai-captcha.tianai-captcha-slider .slider-move{line-height:38px;font-size:14px;text-align:center;white-space:nowrap;color:#88949d;-moz-user-select:none;-webkit-user-select:none;user-select:none;filter:opacity(0.8)}#tianai-captcha.tianai-captcha-slider .slider-move .slider-move-btn{transform:translate(0px, 0px);position:absolute;top:-6px;left:0;width:63px;height:45px;background-color:#fff;background-repeat:no-repeat;background-size:contain;border-radius:5px}#tianai-captcha.tianai-captcha-slider .slider-tip{margin-bottom:5px;font-weight:bold;font-size:15px;line-height:normal;color:#000}#tianai-captcha.tianai-captcha-slider .slider-move-btn:hover{cursor:pointer}
#tianai-captcha.tianai-captcha-rotate .rotate-img-div{height:100%;text-align:center}#tianai-captcha.tianai-captcha-rotate .rotate-img-div img{height:100%;transform:rotate(0deg);display:inline-block}
#tianai-captcha.tianai-captcha-concat .tianai-captcha-slider-concat-img-div{background-size:100% 180px;position:absolute;transform:translate(0px, 0px);z-index:1;width:100%}#tianai-captcha.tianai-captcha-concat .tianai-captcha-slider-concat-bg-img{width:100%;height:100%;position:absolute;transform:translate(0px, 0px);background-size:100% 180px}
#tianai-captcha.tianai-captcha-disable{z-index:999;position:absolute;left:0;top:0}#tianai-captcha.tianai-captcha-disable .content{width:100%;height:180px;position:relative;overflow:hidden}#tianai-captcha.tianai-captcha-disable .content .bg-img-div{background-image:url(../../tac/images/dun.jpeg);width:100%;height:100%;overflow:hidden}#tianai-captcha.tianai-captcha-disable .content .bg-img-div #content-span{color:#fff;overflow:hidden;margin-top:132px;display:block;text-align:center}
#tianai-captcha.tianai-captcha-word-click{box-sizing:border-box}#tianai-captcha.tianai-captcha-word-click .click-tip{position:relative;height:40px;width:100%}#tianai-captcha.tianai-captcha-word-click .click-tip .tip-img{height:35px;position:absolute;right:15px}#tianai-captcha.tianai-captcha-word-click .click-tip #tianai-captcha-click-track-font{font-size:18px;display:inline-block;height:40px;line-height:40px;position:absolute}#tianai-captcha.tianai-captcha-word-click .slider-bottom{position:relative;top:6px}#tianai-captcha.tianai-captcha-word-click .content #bg-img-click-mask{width:100%;height:100%;position:absolute;left:0;top:0}#tianai-captcha.tianai-captcha-word-click .content #bg-img-click-mask .click-span{position:absolute;left:0;top:0;border-radius:50px;background-color:#409eff;width:20px;height:20px;text-align:center;line-height:20px;color:#fff;border:2px solid #fff;box-sizing:content-box}#tianai-captcha.tianai-captcha-word-click .click-confirm-btn{width:100%;height:35px;border-radius:4px;background-image:linear-gradient(173deg, hsl(38.09, 91%, 57.89%) 0%, hsl(38.09, 89.38%, 71.74%) 100%);font-size:15px;text-align:center;box-sizing:border-box;line-height:35px;color:#fff;margin-top:3px}#tianai-captcha.tianai-captcha-word-click .click-confirm-btn:hover{cursor:pointer}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

-1
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
const Math=window.Math,head=document.getElementsByTagName("head")[0],TIMEOUT=1e4,TAC_LOADING_DIV='<div id="tac-loading" style="\n border: 1px solid #eee;\n /* background-color: #409EFF; */\n border-radius: 5px;\n width: 318px;\n height: 318px;\n line-height: 318px;\n color: #606266;\n text-align: center;\n position: relative;\n box-sizing: border-box;\n">璇风◢绛�...</div>';function showLoading(e){var t=document.querySelector(e);t&&(t.innerHTML=TAC_LOADING_DIV)}function hideLoading(e){let t=document.querySelector(e);t&&(t.innerHTML="")}function loadCaptchaScript(e,t,n,r,o){const i=e.scriptUrls,c=e.cssUrls,l=e.timeout||TIMEOUT;let s=i.length+c.length;function d(e,i){if(s--,e&&0===s){if(hideLoading(t.bindEl),!window.TAC)throw new Error("TAC鏈姞杞斤紝璇锋鏌ュ湴鍧€鏄惁姝g‘");r(new TAC(t,n))}else e||(hideLoading(t.bindEl),o(i))}setTimeout(()=>{0!==s&&showLoading(t.bindEl)},10),i.forEach(function(e){loadResource("string"==typeof e?{url:e}:e,d,"script",l)}),c.forEach(function(e){loadResource("string"==typeof e?{url:e}:e,d,"link",l)})}function loadResource(e,t,n="script",r){if(document.querySelector(`${n}[${"script"===n?"src":"href"}="${e.url}"]`))return void t(!0,e);let o=!1;const i=document.createElement(n);"link"===n?i.rel="stylesheet":i.async=!0,i["script"===n?"src":"href"]=e.url;let c;i.onload=i.onreadystatechange=(()=>{o||i.readyState&&"loaded"!==i.readyState&&"complete"!==i.readyState||function t(n){e.checkOnReady?c=setTimeout(()=>{e.checkOnReady()?n():t(n)},10):n()}(()=>{o=!0,setTimeout(()=>t(o,e),0)})}),i.onerror=(()=>{t(o=!1,e)}),head.appendChild(i),setTimeout(()=>{o||(c&&clearTimeout(c),i.onload=i.onerror=null,i.remove&&i.remove(),t(o,e))},r||TIMEOUT)}function loadTAC(e,t,n){return new Promise((r,o)=>{let i={..."string"==typeof e?{url:e}:e};i.url&&(i.url.endsWith("/")||(i.url+="/"),i.scriptUrls||(i.scriptUrls=[i.url+"js/tac.min.js"]),i.cssUrls||(i.cssUrls=[i.url+"css/tac.css"])),i.scriptUrls&&i.cssUrls?loadCaptchaScript(i,t,n,r,o):o("璇锋寜鐓ф枃妗i厤缃畉ac")})}setTimeout(()=>{let e=document.scripts,t=null;for(let n=0;n<e.length;n++)if(e[n].src.indexOf("load.js")>1||e[n].src.indexOf("load.min.js")>1){t=e[n].src.substring(e[n].src.indexOf("/"),e[n].src.lastIndexOf("/"));break}},100),window.loadCaptchaScript=loadCaptchaScript,window.loadTAC=loadTAC,window.initTAC=loadTAC;
-20
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>
-48
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
};
+146 -6
View File
@@ -1,10 +1,150 @@
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { SnailOpenApiUser } from './types';
import type { PageResult, R } from '@/api/types';
import request, { globalHeaders } from '@/api/request';
import { useAppStore } from '@/stores/appStore';
import { appEnv } from '@/utils/env';
import type {
AgentChatRequest,
AgentChatSyncResponse,
AgentItem,
ConversationMessage,
ConversationSummaryItem,
SnailOpenApiUser
} from './types';
export const registerCurrentSnailUser = (): AxiosPromise<SnailOpenApiUser> => {
return request({
export function fetchMyAgents() {
return request<R<AgentItem[] | PageResult<AgentItem>>>({
url: '/snail-ai/agents',
method: 'get'
});
}
export function fetchAgentDetail(id: number) {
return request<R<AgentItem>>({
url: `/snail-ai/agent/${id}`,
method: 'get'
});
}
export function fetchAgentConversations(
id: number,
params: { page?: number; size?: number; start?: string; end?: string }
) {
return request<R<PageResult<ConversationSummaryItem> | ConversationSummaryItem[]>>({
url: `/snail-ai/agent/${id}/conversations`,
method: 'get',
params
});
}
export function fetchConversationMessages(agentId: number, conversationId: string) {
return request<R<ConversationMessage[] | PageResult<ConversationMessage>>>({
url: `/snail-ai/agent/${agentId}/conversation/${conversationId}/messages`,
method: 'get'
});
}
export function createConversation(agentId: number, data: { title?: string }) {
return request<R<ConversationSummaryItem>>({
url: `/snail-ai/agent/${agentId}/conversation`,
method: 'post',
data
});
}
export function deleteConversation(agentId: number, conversationId: string) {
return request<R>({
url: `/snail-ai/agent/${agentId}/conversation/${conversationId}`,
method: 'delete'
});
}
export function registerCurrentSnailUser() {
return request<R<SnailOpenApiUser>>({
url: '/snail-ai/user/register',
method: 'post'
});
};
}
export function fetchChatMode() {
return request<R<{ mode?: 'stream' | 'sync' }>>({
url: '/snail-ai/chat/mode',
method: 'get'
});
}
export async function fetchAgentChat(
agentId: number,
data: AgentChatRequest,
options: {
onMessage: (chunk: string) => void;
onThinking?: (chunk: string) => void;
onDone: () => void;
onError: (error: Error) => void;
signal?: AbortSignal;
}
) {
const baseURL = appEnv.baseApi;
const language = useAppStore.getState().appLocale;
try {
const response = await fetch(`${baseURL}/snail-ai/agent/${agentId}/chat/stream`, {
method: 'POST',
headers: {
...globalHeaders(),
'Content-Language': language,
Accept: 'text/event-stream',
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(data),
signal: options.signal
});
if (!response.ok) {
const text = await response.text();
throw new Error(text || `HTTP ${response.status}`);
}
const reader = response.body?.getReader();
if (!reader) throw new Error('ReadableStream not supported');
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const eventBlocks = buffer.split(/\r?\n\r?\n/);
buffer = eventBlocks.pop() || '';
for (const block of eventBlocks) {
if (!block.trim()) continue;
let eventName = 'message';
let payload = '';
for (const line of block.split(/\r?\n/)) {
if (line.startsWith('event:')) eventName = line.slice(6).trim();
if (line.startsWith('data:')) payload += line.slice(5).trim();
}
if (!payload && eventName !== 'done') continue;
if (eventName === 'thinking') {
options.onThinking?.(payload);
} else if (eventName === 'done') {
options.onDone();
return;
} else if (eventName === 'error') {
throw new Error(payload || 'SSE stream error');
} else {
options.onMessage(payload);
}
}
}
options.onDone();
} catch (error) {
if ((error as Error).name !== 'AbortError') options.onError(error as Error);
}
}
export function fetchAgentChatSync(agentId: number, data: AgentChatRequest) {
return request<R<AgentChatSyncResponse>>({
url: `/snail-ai/agent/${agentId}/chat/sync`,
method: 'post',
data
});
}
+55
View File
@@ -1,3 +1,58 @@
export interface AgentItem {
id: number;
name: string;
description?: string;
avatar?: string;
greeting?: string;
status?: number;
presetQuestions?: string[];
}
export interface ConversationSummaryItem {
conversationId: string;
agentId?: number;
title: string;
lastMessageDt?: string;
createDt?: string;
updateDt?: string;
}
export interface ConversationSummaryList {
total: number;
rows: ConversationSummaryItem[];
}
export interface ConversationMessage {
role?: string;
content?: string;
thinking?: string;
message?: string;
text?: string;
messageType?: string;
senderType?: string;
}
export interface AgentChatRequest {
conversationId?: string;
content: string;
disabledMcpServerIds?: number[];
disabledSkillIds?: number[];
}
export interface AgentChatSyncResponse {
conversationId?: string;
content?: string;
traceId?: string;
durationMs?: number;
text?: string;
message?: string;
answer?: string;
reply?: string;
outputText?: string;
result?: string;
messages?: ConversationMessage[];
}
export interface SnailOpenApiUser {
openId: string;
nickname?: string;
+22 -44
View File
@@ -1,63 +1,41 @@
import type { DemoForm, DemoQuery, DemoVO } from '@/api/demo/demo/types';
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { DemoForm, DemoQuery, DemoVO } from './types';
/**
* 查询测试单列表
* @param query
* @returns {*}
*/
export const listDemo = (query?: DemoQuery): AxiosPromise<PageResult<DemoVO>> => {
return request({
export function listDemo(query?: DemoQuery) {
return request<R<PageResult<DemoVO>>>({
url: '/demo/demo/list',
method: 'get',
params: query
});
};
}
/**
* 查询测试单详细
* @param id
*/
export const getDemo = (id: string | number): AxiosPromise<DemoVO> => {
return request({
url: '/demo/demo/' + id,
export function getDemo(id: string | number) {
return request<R<DemoVO>>({
url: `/demo/demo/${id}`,
method: 'get'
});
};
}
/**
* 新增测试单
* @param data
*/
export const addDemo = (data: DemoForm) => {
return request({
export function addDemo(data: DemoForm) {
return request<R>({
url: '/demo/demo',
method: 'post',
data: data
data
});
};
}
/**
* 修改测试单
* @param data
*/
export const updateDemo = (data: DemoForm) => {
return request({
export function updateDemo(data: DemoForm) {
return request<R>({
url: '/demo/demo',
method: 'put',
data: data
data
});
};
}
/**
* 删除测试单
* @param id
*/
export const delDemo = (id: string | number | Array<string | number>) => {
return request({
url: '/demo/demo/' + id,
export function delDemo(id: string | number | Array<string | number>) {
return request<R>({
url: `/demo/demo/${id}`,
method: 'delete'
});
};
}
+10 -73
View File
@@ -1,90 +1,27 @@
export interface DemoVO {
/**
* 主键
*/
id: string | number;
/**
* 部门id
*/
deptId: string | number;
/**
* 用户id
*/
userId: string | number;
/**
* 排序号
*/
orderNum: number;
/**
* key键
*/
testKey: string;
/**
* 值
*/
value: string;
}
import type { BaseEntity, PageQuery } from '@/api/types';
export interface DemoForm extends BaseEntity {
/**
* 主键
*/
id?: string | number;
/**
* 部门id
*/
deptId?: string | number;
/**
* 用户id
*/
userId?: string | number;
/**
* 排序号
*/
orderNum?: number;
/**
* key键
*/
testKey?: string;
/**
* 值
*/
value?: string;
}
export interface DemoQuery extends PageQuery {
/**
* 部门id
*/
deptId?: string | number;
/**
* 用户id
*/
userId?: string | number;
/**
* 排序号
*/
orderNum?: number;
/**
* key键
*/
testKey?: string;
/**
* 值
*/
value?: string;
}
export interface DemoVO {
id: string | number;
deptId: string | number;
userId: string | number;
orderNum: number;
testKey: string;
value: string;
}
+22 -43
View File
@@ -1,62 +1,41 @@
import type { TreeForm, TreeQuery, TreeVO } from '@/api/demo/tree/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { R } from '@/api/types';
import request from '@/api/request';
import type { TreeForm, TreeQuery, TreeVO } from './types';
/**
* 查询测试树列表
* @param query
* @returns {*}
*/
export const listTree = (query?: TreeQuery): AxiosPromise<TreeVO[]> => {
return request({
export function listTree(query?: TreeQuery) {
return request<R<TreeVO[]>>({
url: '/demo/tree/list',
method: 'get',
params: query
});
};
}
/**
* 查询测试树详细
* @param id
*/
export const getTree = (id: string | number): AxiosPromise<TreeVO> => {
return request({
url: '/demo/tree/' + id,
export function getTree(id: string | number) {
return request<R<TreeVO>>({
url: `/demo/tree/${id}`,
method: 'get'
});
};
}
/**
* 新增测试树
* @param data
*/
export const addTree = (data: TreeForm) => {
return request({
export function addTree(data: TreeForm) {
return request<R>({
url: '/demo/tree',
method: 'post',
data: data
data
});
};
}
/**
* 修改测试树
* @param data
*/
export const updateTree = (data: TreeForm) => {
return request({
export function updateTree(data: TreeForm) {
return request<R>({
url: '/demo/tree',
method: 'put',
data: data
data
});
};
}
/**
* 删除测试树
* @param id
*/
export const delTree = (id: string | number | Array<string | number>) => {
return request({
url: '/demo/tree/' + id,
export function delTree(id: string | number | Array<string | number>) {
return request<R>({
url: `/demo/tree/${id}`,
method: 'delete'
});
};
}
+10 -65
View File
@@ -1,80 +1,25 @@
export interface TreeVO {
/**
* 主键
*/
id: string | number;
/**
* 父id
*/
parentId: string | number;
/**
* 部门id
*/
deptId: string | number;
/**
* 用户id
*/
userId: string | number;
/**
* 值
*/
treeName: string;
/**
* 子对象
*/
children: TreeVO[];
}
import type { BaseEntity } from '@/api/types';
export interface TreeForm extends BaseEntity {
/**
* 主键
*/
id?: string | number;
/**
* 父id
*/
parentId?: string | number;
/**
* 部门id
*/
deptId?: string | number;
/**
* 用户id
*/
userId?: string | number;
/**
* 值
*/
treeName?: string;
}
export interface TreeQuery {
/**
* 父id
*/
parentId?: string | number;
/**
* 部门id
*/
deptId?: string | number;
/**
* 用户id
*/
userId?: string | number;
/**
* 值
*/
treeName?: string;
}
export interface TreeVO {
id: string | number;
parentId: string | number;
deptId: string | number;
userId: string | number;
treeName: string;
children?: TreeVO[];
}
+47 -79
View File
@@ -1,106 +1,74 @@
import type { UserInfo } from '@/api/system/user/types';
import type { AxiosPromise } from '@/utils/api-types';
import { closePush } from '@/utils/push';
import request from '@/utils/request';
import type { LoginData, LoginResult, VerifyCodeResult } from './types';
import type { LoginParams, LoginResult, R, RegisterParams, VerifyCodeResult } from '@/api/types';
import request from '@/api/request';
import { appEnv } from '@/utils/env';
// pc端固定客户端授权id
const clientId = import.meta.env.VITE_APP_CLIENT_ID;
/**
* @param data {LoginData}
* @returns
*/
export function login(data: LoginData): AxiosPromise<LoginResult> {
const params = {
...data,
clientId: data.clientId || clientId,
grantType: data.grantType || 'password'
};
return request({
export function login(data: LoginParams) {
return request<R<LoginResult>>({
url: '/auth/login',
method: 'post',
headers: {
isToken: false,
isEncrypt: true,
isEncrypt: 'true',
repeatSubmit: false
},
method: 'post',
data: params
});
}
// 注册方法
export function register(data: any) {
const params = {
data: {
...data,
clientId: clientId,
grantType: 'password'
};
return request({
url: '/auth/register',
headers: {
isToken: false,
isEncrypt: true,
repeatSubmit: false
},
method: 'post',
data: params
clientId: data.clientId || appEnv.clientId,
grantType: data.grantType || 'password'
}
});
}
/**
* 注销
*/
export function logout() {
closePush();
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({
return request<R>({
url: '/auth/logout',
method: 'post'
});
}
/**
* 获取验证码
*/
export function getCodeImg(): AxiosPromise<VerifyCodeResult> {
return request({
url: '/resource/captcha/image',
export function register(data: RegisterParams) {
return request<R>({
url: '/auth/register',
method: 'post',
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',
method: 'get',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
});
}
/**
* 第三方登录
*/
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({
export function getInfo() {
return request<R<UserInfo>>({
url: '/system/user/getInfo',
method: 'get'
});
+4 -6
View File
@@ -1,10 +1,8 @@
import type { RouteRecordRaw } from 'vue-router';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { BackendRoute, R } from '@/api/types';
import request from '@/api/request';
// 获取路由
export function getRouters(): AxiosPromise<RouteRecordRaw[]> {
return request({
export function getRouters() {
return request<R<BackendRoute[]>>({
url: '/system/menu/getRouters',
method: 'get'
});
+4 -6
View File
@@ -1,12 +1,10 @@
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { R } from '@/api/types';
import request from '@/api/request';
import type { CacheVO } from './types';
// 查询缓存详细
export function getCache(): AxiosPromise<CacheVO> {
return request({
export function getCache() {
return request<R<CacheVO>>({
url: '/monitor/cache',
method: 'get'
});
}
+3 -5
View File
@@ -1,7 +1,5 @@
export interface CacheVO {
commandStats: Array<{ name: string; value: string }>;
export interface CacheVO {
commandStats: Array<{ name: string; value: string | number }>;
dbSize: number;
info: { [key: string]: string };
info: Record<string, string>;
}
-37
View File
@@ -1,37 +0,0 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { LoginInfoQuery, LoginInfoVO } from './types';
// 查询登录日志列表
export function list(query: LoginInfoQuery): AxiosPromise<PageResult<LoginInfoVO>> {
return request({
url: '/monitor/loginInfo/list',
method: 'get',
params: query
});
}
// 删除登录日志
export function delLoginInfo(infoId: string | number | Array<string | number>) {
return request({
url: '/monitor/loginInfo/' + infoId,
method: 'delete'
});
}
// 解锁用户登录状态
export function unlockLoginInfo(userName: string | Array<string>) {
return request({
url: '/monitor/loginInfo/unlock/' + userName,
method: 'get'
});
}
// 清空登录日志
export function cleanLoginInfo() {
return request({
url: '/monitor/loginInfo/clean',
method: 'delete'
});
}
-20
View File
@@ -1,20 +0,0 @@
export interface LoginInfoVO {
infoId: string | number;
tenantId: string | number;
userName: string;
status: string;
ipaddr: string;
loginLocation: string;
browser: string;
os: string;
msg: string;
loginTime: string;
}
export interface LoginInfoQuery extends PageQuery {
ipaddr: string;
userName: string;
status: string;
orderByColumn: string;
isAsc: string;
}
+36
View File
@@ -0,0 +1,36 @@
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { LoginInfoQuery, LoginInfoVO } from './types';
export function listLoginInfo(query: LoginInfoQuery) {
return request<R<PageResult<LoginInfoVO>>>({
url: '/monitor/loginInfo/list',
method: 'get',
params: query
});
}
export function list(query: LoginInfoQuery) {
return listLoginInfo(query);
}
export function delLoginInfo(infoId: string | number | Array<string | number>) {
return request<R>({
url: `/monitor/loginInfo/${infoId}`,
method: 'delete'
});
}
export function unlockLoginInfo(userName: string | string[]) {
return request<R>({
url: `/monitor/loginInfo/unlock/${userName}`,
method: 'get'
});
}
export function cleanLoginInfo() {
return request<R>({
url: '/monitor/loginInfo/clean',
method: 'delete'
});
}
+24
View File
@@ -0,0 +1,24 @@
import type { PageQuery } from '@/api/types';
export interface LoginInfoQuery extends Omit<PageQuery, 'isAsc'> {
ipaddr?: string;
userName?: string;
status?: string;
orderByColumn?: string;
isAsc?: string;
}
export interface LoginInfoVO {
infoId: string | number;
tenantId?: string | number;
userName: string;
clientKey?: string;
deviceType?: string;
status: string;
ipaddr?: string;
loginLocation?: string;
browser?: string;
os?: string;
msg?: string;
loginTime?: string;
}
+13 -14
View File
@@ -1,37 +1,36 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { OnlineQuery, OnlineVO } from './types';
// 查询在线用户列表
export function list(query: OnlineQuery): AxiosPromise<PageResult<OnlineVO>> {
return request({
export function listOnline(query: OnlineQuery) {
return request<R<PageResult<OnlineVO>>>({
url: '/monitor/online/list',
method: 'get',
params: query
});
}
// 强退用户
export function list(query: OnlineQuery) {
return listOnline(query);
}
export function forceLogout(tokenId: string) {
return request({
url: '/monitor/online/' + tokenId,
return request<R>({
url: `/monitor/online/${tokenId}`,
method: 'delete'
});
}
// 获取当前用户登录在线设备
export function getOnline() {
return request({
return request<R<OnlineVO[] | PageResult<OnlineVO>>>({
url: '/monitor/online',
method: 'get'
});
}
// 删除当前在线设备
export function delOnline(tokenId: string) {
return request({
url: '/monitor/online/myself/' + tokenId,
return request<R>({
url: `/monitor/online/myself/${tokenId}`,
method: 'delete'
});
}
+12 -8
View File
@@ -1,15 +1,19 @@
import type { BaseEntity, PageQuery } from '@/api/types';
export interface OnlineQuery extends PageQuery {
ipaddr: string;
userName: string;
ipaddr?: string;
userName?: string;
}
export interface OnlineVO extends BaseEntity {
tokenId: string;
deptName: string;
deptName?: string;
userName: string;
ipaddr: string;
loginLocation: string;
browser: string;
os: string;
loginTime: number;
clientKey?: string;
deviceType?: string;
ipaddr?: string;
loginLocation?: string;
browser?: string;
os?: string;
loginTime?: number | string;
}
+11 -11
View File
@@ -1,28 +1,28 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { OperLogQuery, OperLogVO } from './types';
// 查询操作日志列表
export function list(query: OperLogQuery): AxiosPromise<PageResult<OperLogVO>> {
return request({
export function listOperlog(query: OperLogQuery) {
return request<R<PageResult<OperLogVO>>>({
url: '/monitor/operlog/list',
method: 'get',
params: query
});
}
// 删除操作日志
export function list(query: OperLogQuery) {
return listOperlog(query);
}
export function delOperlog(operId: string | number | Array<string | number>) {
return request({
url: '/monitor/operlog/' + operId,
return request<R>({
url: `/monitor/operlog/${operId}`,
method: 'delete'
});
}
// 清空操作日志
export function cleanOperlog() {
return request({
return request<R>({
url: '/monitor/operlog/clean',
method: 'delete'
});
+40 -66
View File
@@ -1,71 +1,45 @@
export interface OperLogQuery extends PageQuery {
operIp: string;
title: string;
operName: string;
userId: string;
deptId: string;
clientKey: string;
deviceType: string;
browser: string;
os: string;
businessType: string;
status: string;
orderByColumn: string;
isAsc: string;
import type { BaseEntity, PageQuery } from '@/api/types';
export interface OperLogQuery extends Omit<PageQuery, 'isAsc'> {
operIp?: string;
title?: string;
operName?: string;
userId?: string;
deptId?: string;
clientKey?: string;
deviceType?: string;
browser?: string;
os?: string;
businessType?: string;
status?: string;
orderByColumn?: string;
isAsc?: string;
}
export interface OperLogVO extends BaseEntity {
operId: string | number;
tenantId: string;
title: string;
businessType: number;
businessTypes: number[] | undefined;
method: string;
requestMethod: string;
operatorType: number;
operName: string;
userId: string | number;
deptId: string | number;
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;
}
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;
tenantId?: string;
title?: string;
businessType?: number | string;
businessTypes?: number[];
method?: string;
requestMethod?: string;
operatorType?: number;
operName?: string;
userId?: string | number;
deptId?: string | number;
deptName?: string;
clientKey?: string;
deviceType?: string;
browser?: string;
os?: string;
operUrl?: string;
operIp?: string;
operLocation?: string;
operParam?: string;
jsonResult?: string;
status?: number | string;
errorMsg?: string;
operTime?: string;
costTime?: number;
}
+227
View File
@@ -0,0 +1,227 @@
import { history } from '@umijs/max';
import { 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 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) {
if (!isRelogin.show) {
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;
}
});
}
return Promise.reject(createHandledError('无效的会话,或者会话已过期,请重新登录。'));
}
if (code === SERVER_ERROR || code === WARN || code !== SUCCESS) {
return Promise.reject(new Error(msg));
}
return response.data;
},
async error => {
return Promise.reject(new Error(await getErrorMessage(error)));
}
);
export default function request<T = unknown, D = unknown>(config: RequestConfig<D>): Promise<T> {
return service.request<T, T, D>(config);
}
+24 -56
View File
@@ -1,81 +1,49 @@
import type { ClientForm, ClientQuery, ClientVO } from '@/api/system/client/types';
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { ClientForm, ClientQuery, ClientVO } from './types';
/**
* 查询客户端管理列表
* @param query
* @returns {*}
*/
export const listClient = (query?: ClientQuery): AxiosPromise<PageResult<ClientVO>> => {
return request({
export function listClient(query?: ClientQuery) {
return request<R<PageResult<ClientVO>>>({
url: '/system/client/list',
method: 'get',
params: query
});
};
}
/**
* 查询客户端管理详细
* @param id
*/
export const getClient = (id: string | number): AxiosPromise<ClientVO> => {
return request({
url: '/system/client/' + id,
export function getClient(id: string | number) {
return request<R<ClientVO>>({
url: `/system/client/${id}`,
method: 'get'
});
};
}
/**
* 新增客户端管理
* @param data
*/
export const addClient = (data: ClientForm) => {
return request({
export function addClient(data: ClientForm) {
return request<R>({
url: '/system/client',
method: 'post',
data: data
data
});
};
}
/**
* 修改客户端管理
* @param data
*/
export const updateClient = (data: ClientForm) => {
return request({
export function updateClient(data: ClientForm) {
return request<R>({
url: '/system/client',
method: 'put',
data: data
data
});
};
}
/**
* 删除客户端管理
* @param id
*/
export const delClient = (id: string | number | Array<string | number>) => {
return request({
url: '/system/client/' + id,
export function delClient(id: string | number | Array<string | number>) {
return request<R>({
url: `/system/client/${id}`,
method: 'delete'
});
};
}
/**
* 状态修改
* @param clientId 客户端id
* @param status 状态
*/
export function changeStatus(clientId: string, status: string) {
const data = {
clientId,
status
};
return request({
return request<R>({
url: '/system/client/changeStatus',
method: 'put',
data: data
data: { clientId, status }
});
}
+17 -156
View File
@@ -1,185 +1,46 @@
export interface ClientVO {
/**
* 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;
}
import type { BaseEntity, PageQuery } from '@/api/types';
export interface ClientForm extends BaseEntity {
/**
* id
*/
id?: string | number;
/**
* 客户端id
*/
clientId?: string | number;
/**
* 客户端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 ClientQuery extends PageQuery {
/**
* 客户端id
*/
clientId?: string | number;
/**
* 客户端key
*/
clientKey?: string;
/**
* 客户端秘钥
*/
clientSecret?: string;
/**
* 授权类型
*/
grantType?: string;
/**
* 设备类型
*/
deviceType?: string;
/**
* 允许访问路径
*/
accessPath?: string;
/**
* IP白名单
*/
ipWhitelist?: string;
/**
* token活跃超时时间
*/
activeTimeout?: number;
/**
* token固定超时
*/
timeout?: number;
/**
* 状态(0正常 1停用)
*/
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;
}
+24 -29
View File
@@ -1,54 +1,47 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { ConfigForm, ConfigQuery, ConfigVO } from './types';
// 查询参数列表
export function listConfig(query: ConfigQuery): AxiosPromise<PageResult<ConfigVO>> {
return request({
export function listConfig(query: ConfigQuery) {
return request<R<PageResult<ConfigVO>>>({
url: '/system/config/list',
method: 'get',
params: query
});
}
// 查询参数详细
export function getConfig(configId: string | number): AxiosPromise<ConfigVO> {
return request({
url: '/system/config/' + configId,
export function getConfig(configId: string | number) {
return request<R<ConfigVO>>({
url: `/system/config/${configId}`,
method: 'get'
});
}
// 根据参数键名查询参数值
export function getConfigKey(configKey: string): AxiosPromise<string> {
return request({
url: '/system/config/configKey/' + configKey,
export function getConfigKey(configKey: string) {
return request<R<string>>({
url: `/system/config/configKey/${configKey}`,
method: 'get'
});
}
// 新增参数配置
export function addConfig(data: ConfigForm) {
return request({
return request<R>({
url: '/system/config',
method: 'post',
data: data
data
});
}
// 修改参数配置
export function updateConfig(data: ConfigForm) {
return request({
return request<R>({
url: '/system/config',
method: 'put',
data: data
data
});
}
// 修改参数配置
export function updateConfigByKey(key: string, value: any) {
return request({
export function updateConfigByKey(key: string, value: unknown) {
return request<R>({
url: '/system/config/updateByKey',
method: 'put',
data: {
@@ -58,18 +51,20 @@ export function updateConfigByKey(key: string, value: any) {
});
}
// 删除参数配置
export function delConfig(configId: string | number | Array<string | number>) {
return request({
url: '/system/config/' + configId,
return request<R>({
url: `/system/config/${configId}`,
method: 'delete'
});
}
// 刷新参数缓存
export function refreshCache() {
return request({
export function refreshConfigCache() {
return request<R>({
url: '/system/config/refreshCache',
method: 'delete'
});
}
export function refreshCache() {
return refreshConfigCache();
}
+17 -16
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 {
configId: number | string;
configName: string;
configKey: string;
configValue: 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;
}
+30 -40
View File
@@ -1,65 +1,55 @@
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { type DeptForm, type DeptQuery, type DeptVO } from './types';
import type { R } from '@/api/types';
import request from '@/api/request';
import type { DeptForm, DeptQuery, DeptVO } from './types';
// 查询部门列表
export const listDept = (query?: DeptQuery) => {
return request({
export function listDept(query?: DeptQuery) {
return request<R<DeptVO[]>>({
url: '/system/dept/list',
method: 'get',
params: query
});
};
}
/**
* 通过deptIds查询部门
* @param deptIds
*/
export const optionSelect = (deptIds: (number | string)[]): AxiosPromise<DeptVO[]> => {
return request({
url: '/system/dept/optionselect?deptIds=' + deptIds,
export function optionSelect(deptIds: Array<number | string>) {
return request<R<DeptVO[]>>({
url: `/system/dept/optionselect?deptIds=${deptIds}`,
method: 'get'
});
};
}
// 查询部门列表(排除节点)
export const listDeptExcludeChild = (deptId: string | number): AxiosPromise<DeptVO[]> => {
return request({
url: '/system/dept/list/exclude/' + deptId,
export function listDeptExcludeChild(deptId: string | number) {
return request<R<DeptVO[]>>({
url: `/system/dept/list/exclude/${deptId}`,
method: 'get'
});
};
}
// 查询部门详细
export const getDept = (deptId: string | number): AxiosPromise<DeptVO> => {
return request({
url: '/system/dept/' + deptId,
export function getDept(deptId: string | number) {
return request<R<DeptVO>>({
url: `/system/dept/${deptId}`,
method: 'get'
});
};
}
// 新增部门
export const addDept = (data: DeptForm) => {
return request({
export function addDept(data: DeptForm) {
return request<R>({
url: '/system/dept',
method: 'post',
data: data
data
});
};
}
// 修改部门
export const updateDept = (data: DeptForm) => {
return request({
export function updateDept(data: DeptForm) {
return request<R>({
url: '/system/dept',
method: 'put',
data: data
data
});
};
}
// 删除部门
export const delDept = (deptId: number | string) => {
return request({
url: '/system/dept/' + deptId,
export function delDept(deptId: number | string) {
return request<R>({
url: `/system/dept/${deptId}`,
method: 'delete'
});
};
}
+35 -45
View File
@@ -1,48 +1,5 @@
/**
* 部门查询参数
*/
export interface DeptQuery extends PageQuery {
deptName?: string;
deptCategory?: string;
status?: number;
}
import type { BaseEntity, PageQuery } from '@/api/types';
/**
* 部门类型
*/
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 {
parentName?: string;
parentId?: number | string;
@@ -51,10 +8,43 @@ export interface DeptForm {
deptName?: string;
deptCategory?: string;
orderNum?: number;
leader?: string;
leader?: string | number;
phone?: string;
email?: string;
status?: string;
delFlag?: 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;
}
+18 -24
View File
@@ -1,54 +1,48 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { DictDataForm, DictDataQuery, DictDataVO } from './types';
// 根据字典类型查询字典数据信息
export function getDicts(dictType: string): AxiosPromise<DictDataVO[]> {
return request({
url: '/system/dict/data/type/' + dictType,
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { DictData, DictDataForm, DictDataQuery } from './types';
export function getDicts(dictType: string) {
return request<R<DictData[]>>({
url: `/system/dict/data/type/${dictType}`,
method: 'get'
});
}
// 查询字典数据列表
export function listData(query: DictDataQuery): AxiosPromise<PageResult<DictDataVO>> {
return request({
export function listData(query: DictDataQuery) {
return request<R<PageResult<DictData>>>({
url: '/system/dict/data/list',
method: 'get',
params: query
});
}
// 查询字典数据详细
export function getData(dictCode: string | number): AxiosPromise<DictDataVO> {
return request({
url: '/system/dict/data/' + dictCode,
export function getData(dictCode: string | number) {
return request<R<DictData>>({
url: `/system/dict/data/${dictCode}`,
method: 'get'
});
}
// 新增字典数据
export function addData(data: DictDataForm) {
return request({
return request<R>({
url: '/system/dict/data',
method: 'post',
data: data
data
});
}
// 修改字典数据
export function updateData(data: DictDataForm) {
return request({
return request<R>({
url: '/system/dict/data',
method: 'put',
data: data
data
});
}
// 删除字典数据
export function delData(dictCode: string | number | Array<string | number>) {
return request({
url: '/system/dict/data/' + dictCode,
return request<R>({
url: `/system/dict/data/${dictCode}`,
method: 'delete'
});
}
+24 -18
View File
@@ -1,26 +1,32 @@
export interface DictDataQuery extends PageQuery {
dictName: string;
dictType: string;
dictLabel: string;
}
import type { BaseEntity, PageQuery } from '@/api/types';
export interface DictDataVO extends BaseEntity {
dictCode: string;
export interface DictData extends BaseEntity {
dictCode: string | number;
dictSort: number;
dictLabel: string;
dictValue: string;
cssClass: string;
listClass: ElTagType;
dictSort: number;
remark: string;
dictType: string;
cssClass?: string;
listClass?: string;
isDefault?: string;
status: string;
}
export type DictDataVO = DictData;
export interface DictDataForm {
dictType?: string;
dictCode: string | undefined;
dictLabel: string;
dictValue: string;
cssClass: string;
listClass: ElTagType;
dictSort: number;
remark: string;
dictCode?: string | number;
dictLabel?: string;
dictValue?: string;
cssClass?: string;
listClass?: string;
dictSort?: number;
remark?: string;
}
export interface DictDataQuery extends PageQuery {
dictName?: string;
dictType?: string;
dictLabel?: string;
}
+16 -24
View File
@@ -1,62 +1,54 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { DictTypeForm, DictTypeQuery, DictTypeVO } from './types';
// 查询字典类型列表
export function listType(query: DictTypeQuery): AxiosPromise<PageResult<DictTypeVO>> {
return request({
export function listType(query: DictTypeQuery) {
return request<R<PageResult<DictTypeVO>>>({
url: '/system/dict/type/list',
method: 'get',
params: query
});
}
// 查询字典类型详细
export function getType(dictId: number | string): AxiosPromise<DictTypeVO> {
return request({
url: '/system/dict/type/' + dictId,
export function getType(dictId: number | string) {
return request<R<DictTypeVO>>({
url: `/system/dict/type/${dictId}`,
method: 'get'
});
}
// 新增字典类型
export function addType(data: DictTypeForm) {
return request({
return request<R>({
url: '/system/dict/type',
method: 'post',
data: data
data
});
}
// 修改字典类型
export function updateType(data: DictTypeForm) {
return request({
return request<R>({
url: '/system/dict/type',
method: 'put',
data: data
data
});
}
// 删除字典类型
export function delType(dictId: string | number | Array<string | number>) {
return request({
url: '/system/dict/type/' + dictId,
return request<R>({
url: `/system/dict/type/${dictId}`,
method: 'delete'
});
}
// 刷新字典缓存
export function refreshCache() {
return request({
return request<R>({
url: '/system/dict/type/refreshCache',
method: 'delete'
});
}
// 获取字典选择框列表
export function optionselect(): AxiosPromise<DictTypeVO[]> {
return request({
export function optionselect() {
return request<R<DictTypeVO[]>>({
url: '/system/dict/type/optionselect',
method: 'get'
});
+14 -13
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 {
dictId: number | string;
dictName: 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;
}
+34 -41
View File
@@ -1,70 +1,63 @@
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { MenuForm, MenuQuery, MenuTreeOption, MenuVO, RoleMenuTree } from './types';
import type { RoleMenuTree } from '@/api/system/role/types';
import type { R } from '@/api/types';
import request from '@/api/request';
import type { MenuForm, MenuQuery, MenuTreeOption, MenuVO } from './types';
// 查询菜单列表
export const listMenu = (query?: MenuQuery): AxiosPromise<MenuVO[]> => {
return request({
export function listMenu(query?: MenuQuery) {
return request<R<MenuVO[]>>({
url: '/system/menu/list',
method: 'get',
params: query
});
};
}
// 查询菜单详细
export const getMenu = (menuId: string | number): AxiosPromise<MenuVO> => {
return request({
url: '/system/menu/' + menuId,
export function getMenu(menuId: string | number) {
return request<R<MenuVO>>({
url: `/system/menu/${menuId}`,
method: 'get'
});
};
}
// 查询菜单下拉树结构
export const treeselect = (): AxiosPromise<MenuTreeOption[]> => {
return request({
export function treeselect() {
return request<R<MenuTreeOption[]>>({
url: '/system/menu/treeselect',
method: 'get'
});
};
}
// 根据角色ID查询菜单下拉树结构
export const roleMenuTreeselect = (roleId: string | number): AxiosPromise<RoleMenuTree> => {
return request({
url: '/system/menu/roleMenuTreeselect/' + roleId,
export function roleMenuTreeselect(roleId: string | number) {
return request<R<RoleMenuTree>>({
url: `/system/menu/roleMenuTreeselect/${roleId}`,
method: 'get'
});
};
}
// 新增菜单
export const addMenu = (data: MenuForm) => {
return request({
export function addMenu(data: MenuForm) {
return request<R>({
url: '/system/menu',
method: 'post',
data: data
data
});
};
}
// 修改菜单
export const updateMenu = (data: MenuForm) => {
return request({
export function updateMenu(data: MenuForm) {
return request<R>({
url: '/system/menu',
method: 'put',
data: data
data
});
};
}
// 删除菜单
export const delMenu = (menuId: string | number) => {
return request({
url: '/system/menu/' + menuId,
export function delMenu(menuId: string | number) {
return request<R>({
url: `/system/menu/${menuId}`,
method: 'delete'
});
};
}
// 级联删除菜单
export const cascadeDelMenu = (menuIds: Array<string | number>) => {
return request({
url: '/system/menu/cascade/' + menuIds,
export function cascadeDelMenu(menuIds: Array<string | number>) {
return request<R>({
url: `/system/menu/cascade/${menuIds}`,
method: 'delete'
});
};
}
+44 -70
View File
@@ -1,75 +1,51 @@
import type { MenuTypeEnum } from '@/enums/MenuTypeEnum';
/**
* 菜单树形结构类型
*/
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;
}
import type { BaseEntity } from '@/api/types';
export interface MenuForm {
parentName?: string;
parentId?: string | number;
children?: MenuForm[];
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;
orderNum: number;
path: string;
@@ -77,13 +53,11 @@ export interface MenuForm {
queryParam?: string;
isFrame?: string;
isCache?: string;
menuType?: MenuTypeEnum;
menuType: 'M' | 'C' | 'F' | string;
visible?: string;
status?: string;
icon?: string;
icon?: string | null;
activeMenu?: string;
ext?: string;
remark?: string;
query?: string;
perms?: string;
}
+4 -4
View File
@@ -1,9 +1,9 @@
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { R } from '@/api/types';
import request from '@/api/request';
import type { MessageBoxVO } from './types';
export function getMessageBox(): AxiosPromise<MessageBoxVO> {
return request({
export function getMessageBox() {
return request<R<MessageBoxVO>>({
url: '/resource/message/box',
method: 'get'
});
+3 -1
View File
@@ -1,3 +1,5 @@
import type { BaseEntity } from '@/api/types';
export interface MessageVO extends BaseEntity {
messageId: number | string;
category: string;
@@ -6,7 +8,7 @@ export interface MessageVO extends BaseEntity {
title: string;
message: string;
content?: string;
data?: Record<string, any> | null;
data?: Record<string, unknown> | null;
path?: string;
}
+14 -19
View File
@@ -1,46 +1,41 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { NoticeForm, NoticeQuery, NoticeVO } from './types';
// 查询公告列表
export function listNotice(query: NoticeQuery): AxiosPromise<PageResult<NoticeVO>> {
return request({
export function listNotice(query: NoticeQuery) {
return request<R<PageResult<NoticeVO>>>({
url: '/system/notice/list',
method: 'get',
params: query
});
}
// 查询公告详细
export function getNotice(noticeId: string | number): AxiosPromise<NoticeVO> {
return request({
url: '/system/notice/' + noticeId,
export function getNotice(noticeId: string | number) {
return request<R<NoticeVO>>({
url: `/system/notice/${noticeId}`,
method: 'get'
});
}
// 新增公告
export function addNotice(data: NoticeForm) {
return request({
return request<R>({
url: '/system/notice',
method: 'post',
data: data
data
});
}
// 修改公告
export function updateNotice(data: NoticeForm) {
return request({
return request<R>({
url: '/system/notice',
method: 'put',
data: data
data
});
}
// 删除公告
export function delNotice(noticeId: string | number | Array<string | number>) {
return request({
url: '/system/notice/' + noticeId,
return request<R>({
url: `/system/notice/${noticeId}`,
method: 'delete'
});
}
+17 -16
View File
@@ -1,26 +1,27 @@
export interface NoticeVO extends BaseEntity {
noticeId: number;
noticeTitle: string;
noticeType: string;
noticeContent: string;
status: string;
remark: string;
createByName: string;
import type { BaseEntity, PageQuery } from '@/api/types';
export interface NoticeForm {
noticeId?: number | string;
noticeTitle?: string;
noticeType?: string;
noticeContent?: string;
status?: string;
remark?: string;
createByName?: string;
}
export interface NoticeQuery extends PageQuery {
noticeTitle: string;
createByName: string;
status: string;
noticeType: string;
noticeTitle?: string;
createByName?: string;
status?: string;
noticeType?: string;
}
export interface NoticeForm {
noticeId: number | string | undefined;
export interface NoticeVO extends BaseEntity {
noticeId: number | string;
noticeTitle: string;
noticeType: string;
noticeContent: string;
status: string;
remark: string;
createByName: string;
createByName?: string;
}
+24 -17
View File
@@ -1,38 +1,45 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { OssQuery, OssUploadVO, OssVO } from './types';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { OssQuery, OssUploadResult, OssVO } from './types';
// 查询OSS对象存储列表
export function listOss(query: OssQuery): AxiosPromise<PageResult<OssVO>> {
return request({
export function listOss(query: OssQuery) {
return request<R<PageResult<OssVO>>>({
url: '/resource/oss/list',
method: 'get',
params: query
});
}
// 查询OSS对象基于id串
export function listByIds(ossId: string | number): AxiosPromise<OssVO[]> {
return request({
url: '/resource/oss/listByIds/' + ossId,
export function listOssByIds(ossId: string | number) {
return request<R<OssVO[]>>({
url: `/resource/oss/listByIds/${ossId}`,
method: 'get'
});
}
// 上传OSS对象存储
export function uploadOss(data: FormData): AxiosPromise<OssUploadVO> {
return request({
export function listByIds(ossId: string | number) {
return listOssByIds(ossId);
}
export function uploadOss(data: FormData) {
return request<R<OssUploadResult>>({
url: '/resource/oss/upload',
method: 'post',
data
});
}
// 删除OSS对象存储
export function delOss(ossId: string | number | Array<string | number>) {
return request({
url: '/resource/oss/' + ossId,
return request<R>({
url: `/resource/oss/${ossId}`,
method: 'delete'
});
}
export function downloadOss(ossId: string | number) {
return request<Blob>({
url: `/resource/oss/download/${ossId}`,
method: 'get',
responseType: 'blob'
});
}
+20 -35
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 {
ossId: string | number;
fileName: string;
originalName: string;
fileSuffix: string;
url: string;
createByName: 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;
createByName?: string;
service?: string;
}
+15 -27
View File
@@ -1,61 +1,49 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { OssConfigForm, OssConfigQuery, OssConfigVO } from './types';
// 查询对象存储配置列表
export function listOssConfig(query: OssConfigQuery): AxiosPromise<PageResult<OssConfigVO>> {
return request({
export function listOssConfig(query: OssConfigQuery) {
return request<R<PageResult<OssConfigVO>>>({
url: '/resource/oss/config/list',
method: 'get',
params: query
});
}
// 查询对象存储配置详细
export function getOssConfig(ossConfigId: string | number): AxiosPromise<OssConfigVO> {
return request({
url: '/resource/oss/config/' + ossConfigId,
export function getOssConfig(ossConfigId: string | number) {
return request<R<OssConfigVO>>({
url: `/resource/oss/config/${ossConfigId}`,
method: 'get'
});
}
// 新增对象存储配置
export function addOssConfig(data: OssConfigForm) {
return request({
return request<R>({
url: '/resource/oss/config',
method: 'post',
data: data
data
});
}
// 修改对象存储配置
export function updateOssConfig(data: OssConfigForm) {
return request({
return request<R>({
url: '/resource/oss/config',
method: 'put',
data: data
data
});
}
// 删除对象存储配置
export function delOssConfig(ossConfigId: string | number | Array<string | number>) {
return request({
url: '/resource/oss/config/' + ossConfigId,
return request<R>({
url: `/resource/oss/config/${ossConfigId}`,
method: 'delete'
});
}
// 对象存储状态修改
export function changeOssConfigStatus(ossConfigId: string | number, status: string, configKey: string) {
const data = {
ossConfigId,
status,
configKey
};
return request({
return request<R>({
url: '/resource/oss/config/changeStatus',
method: 'put',
data: data
data: { ossConfigId, status, configKey }
});
}
+28 -27
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 {
ossConfigId: number | string;
configKey: string;
accessKey: string;
secretKey: string;
bucketName: string;
prefix: string;
prefix?: string;
endpoint: string;
domainUrl: string;
domainUrl?: string;
isHttps: string;
region: string;
region?: string;
status: string;
ext1: string;
remark: string;
ext1?: 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;
}
+26 -32
View File
@@ -1,70 +1,64 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { DeptTreeVO } from '../dept/types';
import type { DeptTreeVO } from '@/api/system/dept/types';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { PostForm, PostQuery, PostVO } from './types';
// 查询岗位列表
export function listPost(query: PostQuery): AxiosPromise<PageResult<PostVO>> {
return request({
export function listPost(query: PostQuery) {
return request<R<PageResult<PostVO>>>({
url: '/system/post/list',
method: 'get',
params: query
});
}
// 查询岗位详细
export function getPost(postId: string | number): AxiosPromise<PostVO> {
return request({
url: '/system/post/' + postId,
export function getPost(postId: string | number) {
return request<R<PostVO>>({
url: `/system/post/${postId}`,
method: 'get'
});
}
// 获取岗位选择框列表
export function optionselect(deptId?: number | string, postIds?: (number | string)[]): AxiosPromise<PostVO[]> {
return request({
export function optionSelect(deptId?: number | string, postIds?: Array<number | string>) {
return request<R<PostVO[]>>({
url: '/system/post/optionselect',
method: 'get',
params: {
postIds: postIds,
deptId: deptId
postIds,
deptId
}
});
}
// 新增岗位
export function addPost(data: PostForm) {
return request({
return request<R>({
url: '/system/post',
method: 'post',
data: data
data
});
}
// 修改岗位
export function updatePost(data: PostForm) {
return request({
return request<R>({
url: '/system/post',
method: 'put',
data: data
data
});
}
// 删除岗位
export function delPost(postId: string | number | (string | number)[]) {
return request({
url: '/system/post/' + postId,
export function delPost(postId: string | number | Array<string | number>) {
return request<R>({
url: `/system/post/${postId}`,
method: 'delete'
});
}
/**
* 查询部门下拉树结构
*/
export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
return request({
export function postDeptTreeSelect() {
return request<R<DeptTreeVO[]>>({
url: '/system/post/deptTree',
method: 'get'
});
};
}
export function deptTreeSelect() {
return postDeptTreeSelect();
}
+22 -21
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 {
postId: number | string;
deptId: number | string;
@@ -7,25 +29,4 @@ export interface PostVO extends BaseEntity {
deptName: string;
postSort: number;
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;
}
+59 -104
View File
@@ -1,160 +1,115 @@
import type { UserQuery, UserVO } from '@/api/system/user/types';
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { RoleDeptTree, RoleQuery, RoleVO } from './types';
import type { UserQuery, UserVO } from '@/api/system/user/types';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { RoleDeptTree, RoleForm, RoleQuery, RoleVO } from './types';
export const listRole = (query: RoleQuery): AxiosPromise<PageResult<RoleVO>> => {
return request({
export function listRole(query: RoleQuery) {
return request<R<PageResult<RoleVO>>>({
url: '/system/role/list',
method: 'get',
params: query
});
};
}
/**
* 通过roleIds查询角色
* @param roleIds
*/
export const optionSelect = (roleIds: (number | string)[]): AxiosPromise<RoleVO[]> => {
return request({
url: '/system/role/optionselect?roleIds=' + roleIds,
export function optionSelect(roleIds: Array<number | string>) {
return request<R<RoleVO[]>>({
url: `/system/role/optionselect?roleIds=${roleIds}`,
method: 'get'
});
};
}
/**
* 查询角色详细
*/
export const getRole = (roleId: string | number): AxiosPromise<RoleVO> => {
return request({
url: '/system/role/' + roleId,
export function getRole(roleId: string | number) {
return request<R<RoleVO>>({
url: `/system/role/${roleId}`,
method: 'get'
});
};
}
/**
* 新增角色
*/
export const addRole = (data: any) => {
return request({
export function addRole(data: RoleForm) {
return request<R>({
url: '/system/role',
method: 'post',
data: data
data
});
};
}
/**
* 修改角色基础信息
* @param data
*/
export const updateRole = (data: any) => {
return request({
export function updateRole(data: RoleForm) {
return request<R>({
url: '/system/role',
method: 'put',
data: data
data
});
};
}
/**
* 修改角色权限(菜单权限 + 数据权限)
*/
export const updateRolePermission = (data: any) => {
return request({
export function updateRolePermission(data: RoleForm) {
return request<R>({
url: '/system/role/permission',
method: 'put',
data: data
data
});
};
}
/**
* 角色状态修改
*/
export const changeRoleStatus = (roleId: string | number, status: string) => {
const data = {
roleId,
status
};
return request({
export function changeRoleStatus(roleId: string | number, status: string) {
return request<R>({
url: '/system/role/changeStatus',
method: 'put',
data: data
data: {
roleId,
status
}
});
};
}
/**
* 删除角色
*/
export const delRole = (roleId: Array<string | number> | string | number) => {
return request({
url: '/system/role/' + roleId,
export function delRole(roleId: Array<string | number> | string | number) {
return request<R>({
url: `/system/role/${roleId}`,
method: 'delete'
});
};
}
/**
* 查询角色已授权用户列表
*/
export const allocatedUserList = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
return request({
export function allocatedUserList(query: UserQuery) {
return request<R<PageResult<UserVO>>>({
url: '/system/role/authUser/allocatedList',
method: 'get',
params: query
});
};
}
/**
* 查询角色未授权用户列表
*/
export const unallocatedUserList = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
return request({
export function unallocatedUserList(query: UserQuery) {
return request<R<PageResult<UserVO>>>({
url: '/system/role/authUser/unallocatedList',
method: 'get',
params: query
});
};
}
/**
* 取消用户授权角色
*/
export const authUserCancel = (data: any) => {
return request({
export function authUserCancel(data: { roleId: string | number; userId: string | number }) {
return request<R>({
url: '/system/role/authUser/cancel',
method: 'put',
data: data
data
});
};
}
/**
* 批量取消用户授权角色
*/
export const authUserCancelAll = (data: any) => {
return request({
export function authUserCancelAll(data: { roleId: string | number; userIds: string }) {
return request<R>({
url: '/system/role/authUser/cancelAll',
method: 'put',
params: data
});
};
}
/**
* 授权用户选择
*/
export const authUserSelectAll = (data: any) => {
return request({
export function authUserSelectAll(data: { roleId: string | number; userIds: string }) {
return request<R>({
url: '/system/role/authUser/selectAll',
method: 'put',
params: data
});
};
// 根据角色ID查询部门树结构
export const deptTreeSelect = (roleId: string | number): AxiosPromise<RoleDeptTree> => {
return request({
url: '/system/role/deptTree/' + roleId,
}
export function deptTreeSelect(roleId: string | number) {
return request<R<RoleDeptTree>>({
url: `/system/role/deptTree/${roleId}`,
method: 'get'
});
};
export default {
optionSelect,
listRole
};
}
+33 -37
View File
@@ -1,17 +1,35 @@
/**
* 菜单树形结构类型
*/
export interface DeptTreeOption {
id: string;
label: string;
parentId: string;
weight: number;
children?: DeptTreeOption[];
}
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 RoleDeptTree {
checkedKeys: string[];
depts: DeptTreeOption[];
checkedKeys: Array<string | number>;
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 {
@@ -20,33 +38,11 @@ export interface RoleVO extends BaseEntity {
roleKey: string;
roleSort: number;
dataScope: string;
menuCheckStrictly: boolean;
deptCheckStrictly: boolean;
menuCheckStrictly?: boolean;
deptCheckStrictly?: boolean;
status: string;
delFlag: string;
remark?: any;
flag: boolean;
admin: boolean;
menuIds?: 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>;
}
+9 -9
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) {
return request({
url: '/auth/binding/' + source,
return request<R<string>>({
url: `/auth/binding/${source}`,
method: 'get'
});
}
// 解绑账号
export function authUnlock(authId: string) {
return request({
url: '/auth/unlock/' + authId,
return request<R>({
url: `/auth/unlock/${authId}`,
method: 'delete'
});
}
//获取授权列表
export function getAuthList() {
return request({
return request<R<SocialAuthVO[]>>({
url: '/system/social/list',
method: 'get'
});
+7
View File
@@ -0,0 +1,7 @@
export interface SocialAuthVO {
id: string;
source?: string;
avatar?: string;
userName?: string;
createTime?: string;
}
+86 -161
View File
@@ -1,229 +1,154 @@
import type { DeptTreeVO } from '@/api/system/dept/types';
import type { RoleVO } from '@/api/system/role/types';
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import { parseStrEmpty } from '@/utils/ruoyi';
import type { DeptTreeVO } from './../dept/types';
import type { UserForm, UserInfoVO, UserProfileForm, UserQuery, UserVO } from './types';
/**
* 查询用户列表
* @param query
*/
export const listUser = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
return request({
export function listUser(query: UserQuery) {
return request<R<PageResult<UserVO>>>({
url: '/system/user/list',
method: 'get',
params: query
});
};
}
/**
* 通过用户ids查询用户
* @param userIds
*/
export const optionSelect = (userIds: (number | string)[]): AxiosPromise<UserVO[]> => {
return request({
url: '/system/user/optionselect?userIds=' + userIds,
export function optionSelect(userIds: Array<number | string>) {
return request<R<UserVO[]>>({
url: `/system/user/optionselect?userIds=${userIds}`,
method: 'get'
});
};
}
/**
* 获取用户详情
* @param userId
*/
export const getUser = (userId?: string | number): AxiosPromise<UserInfoVO> => {
return request({
url: '/system/user/' + parseStrEmpty(userId),
export function getUser(userId?: string | number) {
return request<R<UserInfoVO>>({
url: `/system/user/${parseStrEmpty(userId)}`,
method: 'get'
});
};
}
/**
* 新增用户
*/
export const addUser = (data: UserForm) => {
return request({
export function addUser(data: UserForm) {
return request<R>({
url: '/system/user',
method: 'post',
data: data
data
});
};
}
/**
* 修改用户
*/
export const updateUser = (data: UserForm) => {
return request({
export function updateUser(data: UserForm) {
return request<R>({
url: '/system/user',
method: 'put',
data: data
data
});
};
}
/**
* 删除用户
* @param userId 用户ID
*/
export const delUser = (userId: Array<string | number> | string | number) => {
return request({
url: '/system/user/' + userId,
export function delUser(userId: Array<string | number> | string | number) {
return request<R>({
url: `/system/user/${userId}`,
method: 'delete'
});
};
}
/**
* 用户密码重置
* @param userId 用户ID
* @param password 密码
*/
export const resetUserPwd = (userId: string | number, password: string) => {
const data = {
userId,
password
};
return request({
export function resetUserPwd(userId: string | number, password: string) {
return request<R>({
url: '/system/user/resetPwd',
method: 'put',
headers: {
isEncrypt: true,
repeatSubmit: false
},
data: data
});
};
/**
* 用户状态修改
* @param userId 用户ID
* @param status 用户状态
*/
export const changeUserStatus = (userId: number | string, status: string) => {
const data = {
data: {
userId,
status
};
return request({
password
}
});
}
export function changeUserStatus(userId: number | string, status: string) {
return request<R>({
url: '/system/user/changeStatus',
method: 'put',
data: data
data: {
userId,
status
}
});
};
}
/**
* 解锁用户
* @param userId 用户ID
*/
export const unlockUser = (userId: number | string) => {
return request({
url: '/system/user/unlock/' + userId,
export function unlockUser(userId: number | string) {
return request<R>({
url: `/system/user/unlock/${userId}`,
method: 'get'
});
};
}
/**
* 查询用户个人信息
*/
export const getUserProfile = (): AxiosPromise<UserInfoVO> => {
return request({
export function getUserProfile() {
return request<R<UserInfoVO>>({
url: '/system/user/profile',
method: 'get'
});
};
}
/**
* 修改用户个人信息
* @param data 用户信息
*/
export const updateUserProfile = (data: UserProfileForm) => {
return request({
export function updateUserProfile(data: UserProfileForm) {
return request<R>({
url: '/system/user/profile',
method: 'put',
data: data
data
});
};
}
/**
* 用户密码重置
* @param oldPassword 旧密码
* @param newPassword 新密码
*/
export const updateUserPwd = (oldPassword: string, newPassword: string) => {
const data = {
oldPassword,
newPassword
};
return request({
export function updateUserPwd(oldPassword: string, newPassword: string) {
return request<R>({
url: '/system/user/profile/updatePwd',
method: 'put',
headers: {
isEncrypt: true,
repeatSubmit: false
},
data: data
data: {
oldPassword,
newPassword
}
});
};
}
/**
* 查询授权角色
* @param userId 用户ID
*/
export const getAuthRole = (userId: string | number): AxiosPromise<{ user: UserVO; roles: RoleVO[] }> => {
return request({
url: '/system/user/authRole/' + userId,
export function importUser(data: FormData, updateSupport: boolean) {
return request<R>({
url: '/system/user/importData',
method: 'post',
params: {
updateSupport: updateSupport ? 1 : 0
},
data
});
}
export function getAuthRole(userId: string | number) {
return request<R<{ user: UserVO; roles: RoleVO[] }>>({
url: `/system/user/authRole/${userId}`,
method: 'get'
});
};
}
/**
* 保存授权角色
* @param data 用户ID
*/
export const updateAuthRole = (data: { userId: string; roleIds: string }) => {
return request({
export function updateAuthRole(data: { userId: string; roleIds: string }) {
return request<R>({
url: '/system/user/authRole',
method: 'put',
params: data
});
};
}
/**
* 查询当前部门的所有用户信息
* @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({
export function deptTreeSelect() {
return request<R<DeptTreeVO[]>>({
url: '/system/user/deptTree',
method: 'get'
});
};
}
export default {
listUser,
getUser,
optionSelect,
addUser,
updateUser,
delUser,
resetUserPwd,
changeUserStatus,
unlockUser,
getUserProfile,
updateUserProfile,
updateUserPwd,
getAuthRole,
updateAuthRole,
deptTreeSelect,
listUserByDeptId
};
export function listUserByDeptId(deptId: string | number) {
return request<R<UserVO[]>>({
url: `/system/user/list/dept/${deptId}`,
method: 'get'
});
}
+49 -71
View File
@@ -1,81 +1,35 @@
import type { DeptVO } from '@/api/system/dept/types';
import type { PostVO } from '@/api/system/post/types';
import type { RoleVO } from '@/api/system/role/types';
import type { BaseEntity, PageQuery } from '@/api/types';
/**
* 用户信息
*/
export interface UserInfo {
user: UserVO;
user: {
userId: string | number;
userName: string;
nickName?: string;
avatar?: string;
deptName?: string;
};
roles: string[];
permissions: string[];
}
/**
* 用户查询对象类型
*/
export interface UserQuery extends PageQuery {
export interface UserForm {
userId?: string | number;
deptId?: number | string;
userName?: string;
nickName?: string;
phoneNumber?: 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;
password?: string;
phoneNumber?: string;
email?: string;
gender?: string;
status: string;
status?: string;
remark?: string;
avatar?: string | number;
postIds: string[];
roleIds: string[];
postIds?: Array<string | number>;
roleIds?: Array<string | number>;
}
/**
* 个人资料表单类型
*/
export interface UserProfileForm {
nickName?: string;
phoneNumber?: string;
@@ -85,17 +39,41 @@ export interface UserProfileForm {
}
export interface UserInfoVO {
user: UserVO;
user?: UserVO;
roles: RoleVO[];
roleIds: string[];
roleIds: Array<string | number>;
posts: PostVO[];
postIds: string[];
roleGroup: string;
postGroup: string;
postIds: Array<string | number>;
roleGroup?: string;
postGroup?: string;
}
export interface ResetPwdForm {
oldPassword: string;
newPassword: string;
confirmPassword: string;
export interface UserQuery extends PageQuery {
userName?: string;
nickName?: 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;
status?: string;
deptName?: string;
roles?: RoleVO[];
admin?: boolean;
loginIp?: string;
loginDate?: string;
dept?: DeptVO;
}
+44 -47
View File
@@ -1,81 +1,78 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { DbTableForm, DbTableQuery, DbTableVO, GenTableDetailPayload, TableQuery, TableVO } from './types';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { DbTableQuery, DbTableVO, GenTableDetailPayload, TableQuery, TableVO } from './types';
export type { GenTableDetailPayload } from './types';
// 查询生成表数据
export const listTable = (query: TableQuery): AxiosPromise<PageResult<TableVO>> => {
return request({
export function listTable(query: TableQuery) {
return request<R<PageResult<TableVO>>>({
url: '/tool/gen/list',
method: 'get',
params: query
});
};
// 查询db数据库列表
export const listDbTable = (query: DbTableQuery): AxiosPromise<PageResult<DbTableVO>> => {
return request({
}
export function listDbTable(query: DbTableQuery) {
return request<R<PageResult<DbTableVO>>>({
url: '/tool/gen/db/list',
method: 'get',
params: query
});
};
}
// 查询表详细信息
export const getGenTable = (tableId: string | number): AxiosPromise<GenTableDetailPayload> => {
return request({
url: '/tool/gen/' + tableId,
export function getGenTable(tableId: string | number) {
return request<R<GenTableDetailPayload>>({
url: `/tool/gen/${tableId}`,
method: 'get'
});
};
}
// 修改代码生成信息
export const updateGenTable = (data: DbTableForm): AxiosPromise<unknown> => {
return request({
export function updateGenTable(data: DbTableVO & { columns?: unknown[]; params?: Record<string, unknown> }) {
return request<R>({
url: '/tool/gen',
method: 'put',
data: data
data
});
};
}
// 导入表
export const importTable = (data: { tables: string; dataName: string }): AxiosPromise<unknown> => {
return request({
export function importTable(data: { tables: string; dataName: string }) {
return request<R>({
url: '/tool/gen/importTable',
method: 'post',
params: data
});
};
}
// 预览生成代码
export const previewTable = (tableId: string | number) => {
return request({
url: '/tool/gen/preview/' + tableId,
export function previewTable(tableId: string | number) {
return request<R<Record<string, string>>>({
url: `/tool/gen/preview/${tableId}`,
method: 'get'
});
};
}
// 删除表数据
export const delTable = (tableId: string | number | Array<string | number>) => {
return request({
url: '/tool/gen/' + tableId,
export function delTable(tableId: string | number | Array<string | number>) {
return request<R>({
url: `/tool/gen/${tableId}`,
method: 'delete'
});
};
}
// 同步数据库
export const synchDb = (tableId: string | number) => {
return request({
url: '/tool/gen/synchDb/' + tableId,
export function synchDb(tableId: string | number) {
return request<R>({
url: `/tool/gen/synchDb/${tableId}`,
method: 'get'
});
};
}
// 获取数据源名称
export const getDataNames = () => {
return request({
export function getDataNames() {
return request<R<string[]>>({
url: '/tool/gen/getDataNames',
method: 'get'
});
};
}
export function batchGenCode(tableIdStr: string) {
return request<Blob>({
url: `/tool/gen/batchGenCode?tableIdStr=${tableIdStr}`,
method: 'get',
responseType: 'blob'
});
}
+75 -199
View File
@@ -1,215 +1,91 @@
export interface TableVO extends BaseEntity {
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;
}
import type { BaseEntity, PageQuery } from '@/api/types';
export interface DbColumnVO extends BaseEntity {
createDept?: any;
columnId?: any;
tableId?: any;
columnName?: any;
columnComment?: any;
columnType?: any;
javaType?: any;
javaField?: any;
isPk?: any;
isIncrement?: any;
isRequired?: any;
isInsert?: any;
isEdit?: any;
isList?: any;
isQuery?: any;
queryType?: any;
htmlType?: any;
dictType?: any;
sort?: any;
increment: boolean;
capJavaField?: any;
usableColumn: boolean;
superColumn: boolean;
list: boolean;
pk: boolean;
insert: boolean;
edit: boolean;
query: 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;
columnId?: string | number;
tableId?: string | number;
columnName?: string;
columnComment?: string;
columnType?: string;
javaType?: string;
javaField?: string;
isPk?: string;
isIncrement?: string;
isRequired?: string;
isInsert?: string;
isEdit?: string;
isList?: string;
isQuery?: string;
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 DbTableQuery extends PageQuery {
dataName: string;
tableName: string;
tableComment: string;
dataName?: string;
tableName?: 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 {
info: DbTableVO;
rows: DbColumnVO[];
}
export interface DbColumnForm extends BaseEntity {
createDept: number;
columnId: string;
tableId: 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 TableQuery extends PageQuery {
tableName?: string;
tableComment?: string;
dataName?: string;
}
export interface DbParamForm {
treeCode?: any;
treeName?: any;
treeParentCode?: any;
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;
export interface TableVO extends BaseEntity {
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: DbColumnForm[];
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;
tableComment?: string;
className?: string;
tplCategory?: string;
frontendType?: string;
packageName?: string;
moduleName?: string;
businessName?: string;
functionName?: string;
functionAuthor?: string;
tree?: boolean;
crud?: boolean;
}
+68 -31
View File
@@ -1,51 +1,88 @@
/**
* 注册
*/
export type RegisterForm = {
username: string;
password: string;
confirmPassword?: string;
code?: string;
uuid?: string;
userType?: string;
};
import type { MenuDataItem } from '@ant-design/pro-components';
/**
* 登录请求
*/
export interface LoginData {
export interface BaseEntity {
createBy?: string;
createTime?: string;
updateBy?: string;
updateTime?: string;
remark?: string;
}
export interface LoginParams {
username?: string;
password?: string;
code?: string;
uuid?: string;
rememberMe?: boolean;
socialCode?: string;
socialState?: string;
source?: string;
code?: string;
uuid?: string;
clientId: string;
grantType: string;
clientId?: string;
grantType?: string;
}
/**
* 登录响应
*/
export interface LoginResult {
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 {
captchaEnabled: boolean;
uuid?: string;
img?: string;
}
/**
* 分页返回结果
*/
export interface PageResult<T = any> {
total: number;
rows: T[];
export interface RouteMeta {
title?: string;
icon?: string | null;
noCache?: boolean;
link?: string;
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[];
};
+26 -53
View File
@@ -1,76 +1,49 @@
import type { CategoryForm, CategoryQuery, CategoryTreeVO, CategoryVO } from '@/api/workflow/category/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { R } from '@/api/types';
import request from '@/api/request';
import type { CategoryForm, CategoryQuery, CategoryTreeVO, CategoryVO } from './types';
/**
* 查询流程分类列表
* @param query
* @returns {*}
*/
export const listCategory = (query?: CategoryQuery): AxiosPromise<CategoryVO[]> => {
return request({
export function listCategory(query?: CategoryQuery) {
return request<R<CategoryVO[]>>({
url: '/workflow/category/list',
method: 'get',
params: query
});
};
}
/**
* 查询流程分类详细
* @param categoryId
*/
export const getCategory = (categoryId: string | number): AxiosPromise<CategoryVO> => {
return request({
url: '/workflow/category/' + categoryId,
export function getCategory(categoryId: string | number) {
return request<R<CategoryVO>>({
url: `/workflow/category/${categoryId}`,
method: 'get'
});
};
}
/**
* 新增流程分类
* @param data
*/
export const addCategory = (data: CategoryForm) => {
return request({
export function addCategory(data: CategoryForm) {
return request<R>({
url: '/workflow/category',
method: 'post',
data: data
data
});
};
}
/**
* 修改流程分类
* @param data
*/
export const updateCategory = (data: CategoryForm) => {
return request({
export function updateCategory(data: CategoryForm) {
return request<R>({
url: '/workflow/category',
method: 'put',
data: data
data
});
};
}
/**
* 删除流程分类
* @param categoryId
*/
export const delCategory = (categoryId: string | number | Array<string | number>) => {
return request({
url: '/workflow/category/' + categoryId,
export function delCategory(categoryId: string | number | Array<string | number>) {
return request<R>({
url: `/workflow/category/${categoryId}`,
method: 'delete'
});
};
}
/**
* 获取流程分类树列表
* @param query 流程实例id
* @returns
*/
export const categoryTree = (query?: CategoryForm): AxiosPromise<CategoryTreeVO[]> => {
return request({
url: `/workflow/category/categoryTree`,
export function categoryTree(query?: CategoryForm) {
return request<R<CategoryTreeVO[]>>({
url: '/workflow/category/categoryTree',
method: 'get',
params: query
});
};
}
+19 -57
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 {
id: number | string;
label: string;
parentId: number | string;
weight: number;
children: CategoryTreeVO[];
children?: CategoryTreeVO[];
}
export interface CategoryVO {
/**
* 流程分类ID
*/
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;
/**
* 显示顺序
*/
orderNum?: number;
}
export interface CategoryQuery {
/**
* 流程分类名称
*/
categoryName?: string;
categoryName: string;
orderNum: number;
createTime?: string;
children?: CategoryVO[];
}
+106 -145
View File
@@ -1,176 +1,137 @@
import type { PageResult } from '@/api/types';
import type {
definitionXmlVO,
FlowDefinitionForm,
FlowDefinitionQuery,
FlowDefinitionVo
} from '@/api/workflow/definition/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { definitionXmlVO, FlowDefinitionForm, FlowDefinitionQuery, FlowDefinitionVO } from './types';
/**
* 获取流程定义列表
* @param query 流程实例id
* @returns
*/
export const listDefinition = (query: FlowDefinitionQuery): AxiosPromise<PageResult<FlowDefinitionVo>> => {
return request({
url: `/workflow/definition/list`,
export function listDefinition(query: FlowDefinitionQuery) {
return request<R<PageResult<FlowDefinitionVO>>>({
url: '/workflow/definition/list',
method: 'get',
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
}
});
}
/**
* 发布流程定义
* @param id 流程定义id
* @returns
*/
export const publish = (id: string) => {
return request({
export function unPublishList(query: FlowDefinitionQuery) {
return request<R<PageResult<FlowDefinitionVO>>>({
url: '/workflow/definition/unPublishList',
method: 'get',
params: query
});
}
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}`,
method: 'put'
});
};
}
/**
* 取消发布流程定义
* @param id 流程定义id
* @returns
*/
export const unPublish = (id: string) => {
return request({
export function publish(id: string) {
return publishDefinition(id);
}
export function unPublish(id: string) {
return request<R>({
url: `/workflow/definition/unPublish/${id}`,
method: 'put'
});
};
}
/**
* 获取流程定义xml字符串
* @param id 流程定义id
* @returns
*/
export const xmlString = (id: string) => {
return request({
export function xmlString(id: string) {
return request<R<string>>({
url: `/workflow/definition/xmlString/${id}`,
method: 'get'
});
};
}
/**
* 新增
* @param data 参数
* @returns
*/
export const add = (data: FlowDefinitionForm) => {
return request({
url: `/workflow/definition`,
export function addDefinition(data: FlowDefinitionForm) {
return request<R>({
url: '/workflow/definition',
method: 'post',
data: data
data
});
};
}
/**
* 修改
* @param data 参数
* @returns
*/
export const edit = (data: FlowDefinitionForm) => {
return request({
url: `/workflow/definition`,
export function add(data: FlowDefinitionForm) {
return addDefinition(data);
}
export function editDefinition(data: FlowDefinitionForm) {
return request<R>({
url: '/workflow/definition',
method: 'put',
data: data
data
});
};
}
/**
* 查询详情
* @param id 参数
* @returns
*/
export const getInfo = (id: number | string) => {
return request({
export function edit(data: FlowDefinitionForm) {
return editDefinition(data);
}
export function getDefinition(id: string) {
return request<R<FlowDefinitionVO>>({
url: `/workflow/definition/${id}`,
method: 'get'
});
};
}
/**
* 复制流程定义
* @param id 流程定义id
* @returns
*/
export const copy = (id: string) => {
return request({
export function getInfo(id: string | number) {
return getDefinition(String(id));
}
export function copyDefinition(id: string) {
return request<R>({
url: `/workflow/definition/copy/${id}`,
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'
});
}
+27 -18
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 {
flowCode?: string;
flowName?: string;
category: string | number;
category?: string | number;
isPublish?: number;
}
export interface FlowDefinitionVo {
export interface FlowDefinitionVO {
id: string;
flowName: string;
flowCode: string;
formPath: string;
version: string;
isPublish: number;
activityStatus: number;
createTime: Date;
updateTime: Date;
category?: string | number;
categoryName?: string;
formPath?: string;
formCustom?: string;
modelValue?: string;
version?: string | number;
isPublish?: number;
activityStatus?: number;
ext?: string;
createTime?: string | Date;
updateTime?: string | Date;
}
export interface FlowDefinitionForm {
id: string;
flowName: string;
flowCode: string;
category: string;
ext: string;
formPath: string;
formCustom: string;
modelValue: string;
}
export type FlowDefinitionVo = FlowDefinitionVO;
export interface definitionXmlVO {
xml: string[];
+49 -91
View File
@@ -1,126 +1,84 @@
import type { PageResult } from '@/api/types';
import type { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { FlowHisTaskResult, FlowInstanceQuery, FlowInstanceVO } from './types';
/**
* 查询运行中实例列表
* @param query
* @returns {*}
*/
export const pageByRunning = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
return request({
export function pageByRunning(query: FlowInstanceQuery) {
return request<R<PageResult<FlowInstanceVO>>>({
url: '/workflow/instance/pageByRunning',
method: 'get',
params: query
});
};
}
/**
* 查询已完成实例列表
* @param query
* @returns {*}
*/
export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
return request({
export function pageByFinish(query: FlowInstanceQuery) {
return request<R<PageResult<FlowInstanceVO>>>({
url: '/workflow/instance/pageByFinish',
method: 'get',
params: query
});
};
}
/**
* 通过业务id获取历史流程图
*/
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({
export function pageByCurrent(query: FlowInstanceQuery) {
return request<R<PageResult<FlowInstanceVO>>>({
url: '/workflow/instance/pageByCurrent',
method: 'get',
params: query
});
};
}
/**
* 撤销流程
* @param data 参数
* @returns
*/
export const cancelProcessApply = (data: any) => {
return request({
url: `/workflow/instance/cancelProcessApply`,
export function cancelProcessApply(data: {
id?: string | number;
businessId?: string | number;
comment?: string;
message?: string;
}) {
return request<R>({
url: '/workflow/instance/cancelProcessApply',
method: 'put',
data: data
data
});
};
}
/**
* 获取流程变量
* @param instanceId 实例id
* @returns
*/
export const instanceVariable = (instanceId: string | number) => {
return request({
export function instanceVariable(instanceId: string | number) {
return request<R<{ variable: string }>>({
url: `/workflow/instance/instanceVariable/${instanceId}`,
method: 'get'
});
};
}
/**
* 删除
* @param instanceIds 流程实例id
* @returns
*/
export const deleteByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
return request({
export function deleteByInstanceIds(instanceIds: Array<string | number> | string | number) {
return request<R>({
url: `/workflow/instance/deleteByInstanceIds/${instanceIds}`,
method: 'delete'
});
};
}
/**
* 删除历史流程实例
* @param instanceIds
*/
export const deleteHisByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
return request({
export function deleteHisByInstanceIds(instanceIds: Array<string | number> | string | number) {
return request<R>({
url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`,
method: 'delete'
});
};
}
/**
* 作废流程
* @param data 参数
* @returns
*/
export const invalid = (data: any) => {
return request({
url: `/workflow/instance/invalid`,
export function invalid(data: { id: string | number; comment?: string }) {
return request<R>({
url: '/workflow/instance/invalid',
method: 'post',
data: data
data
});
};
/**
* 修改流程变量
* @param data 参数
* @returns
*/
export const updateVariable = (data: any) => {
return request({
url: `/workflow/instance/updateVariable`,
}
export function updateVariable(data: { instanceId?: string | number; key?: string; value?: string }) {
return request<R>({
url: '/workflow/instance/updateVariable',
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'
});
}
+27 -14
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 {
category?: string | number;
@@ -11,18 +17,25 @@ export interface FlowInstanceQuery extends PageQuery {
export interface FlowInstanceVO extends BaseEntity {
id: string | number;
definitionId: string;
flowName: string;
flowCode: string;
version: string;
definitionId?: string;
flowName?: string;
flowCode?: string;
version?: string | number;
businessId: string;
activityStatus: number;
tenantId: string;
createTime: string;
createBy: string;
flowStatus: string;
flowStatusName: string;
flowTaskList: FlowTaskVO[];
businessCode: string;
businessTitle: string;
activityStatus?: number;
tenantId?: string;
createTime?: string;
updateTime?: string;
createBy?: string;
createByName?: string;
flowStatus?: string;
flowStatusName?: string;
flowTaskList?: FlowTaskVO[];
businessCode?: string;
businessTitle?: string;
nodeName?: string;
categoryName?: string;
isSuspended?: boolean;
formCustom?: string;
formPath?: string;
}
+26 -53
View File
@@ -1,76 +1,49 @@
import type { PageResult } from '@/api/types';
import type { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { PageResult, R } from '@/api/types';
import request from '@/api/request';
import type { LeaveForm, LeaveQuery, LeaveVO } from './types';
/**
* 查询请假列表
* @param query
* @returns {*}
*/
export const listLeave = (query?: LeaveQuery): AxiosPromise<PageResult<LeaveVO>> => {
return request({
export function listLeave(query?: LeaveQuery) {
return request<R<PageResult<LeaveVO>>>({
url: '/workflow/leave/list',
method: 'get',
params: query
});
};
}
/**
* 查询请假详细
* @param id
*/
export const getLeave = (id: string | number): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave/' + id,
export function getLeave(id: string | number) {
return request<R<LeaveVO>>({
url: `/workflow/leave/${id}`,
method: 'get'
});
};
}
/**
* 新增请假
* @param data
*/
export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
export function addLeave(data: LeaveForm) {
return request<R<LeaveVO>>({
url: '/workflow/leave',
method: 'post',
data: data
data
});
};
}
/**
* 提交请假并发起流程
* @param data
*/
export const submitAndFlowStart = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
export function submitAndFlowStart(data: LeaveForm) {
return request<R<LeaveVO>>({
url: '/workflow/leave/submitAndFlowStart',
method: 'post',
data: data
data
});
};
}
/**
* 修改请假
* @param data
*/
export const updateLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
export function updateLeave(data: LeaveForm) {
return request<R<LeaveVO>>({
url: '/workflow/leave',
method: 'put',
data: data
data
});
};
}
/**
* 删除请假
* @param id
*/
export const delLeave = (id: string | number | Array<string | number>) => {
return request({
url: '/workflow/leave/' + id,
export function delLeave(id: string | number | Array<string | number>) {
return request<R>({
url: `/workflow/leave/${id}`,
method: 'delete'
});
};
}
+12 -10
View File
@@ -1,13 +1,4 @@
export interface LeaveVO {
id: string | number;
applyCode?: string;
leaveType: string;
startDate: string;
endDate: string;
leaveDays: number;
remark: string;
status?: string;
}
import type { BaseEntity, PageQuery } from '@/api/types';
export interface LeaveForm extends BaseEntity {
id?: string | number;
@@ -24,3 +15,14 @@ export interface LeaveQuery extends PageQuery {
startLeaveDays?: number;
endLeaveDays?: number;
}
export interface LeaveVO {
id: string | number;
applyCode?: string;
leaveType?: string;
startDate?: string;
endDate?: string;
leaveDays?: number;
remark?: string;
status?: string;
}

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