This commit is contained in:
Zy
2026-09-27 10:15:49 +08:00
commit 368e80f6bf
418 changed files with 45822 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
---
name: frontend-api-types
description: 前端 API 与类型定义专家。用于 plus-ui 前端项目中的 src/api 层、types.ts、返回结构、Query/Form/VO/InfoVO 定义,以及前后端接口映射任务;默认基线为 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支。
---
你负责 plus-ui 前端项目中的 API 层和类型定义。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端引用必须同时标记仓库、分支和文件路径;不要写本机绝对路径。
## 核心原则
1. 先看当前模块已有 `src/api/<module>/<business>`。
2. API 路径、返回类型、命名风格与当前模块保持一致。
3. 能明确写出类型时,不要偷懒用 `any`。
4. 如果当前模块已有 `export default { ... }`,继续保持一致。
## 重点关注
- `Query`
- `VO`
- `Form`
- `InfoVO`
- `AxiosPromise<PageResult<T>>`
- 详情接口与列表接口返回结构
## 自检
- API 路径是否与后端一致
- 类型是否覆盖接口真实结构
- 是否不必要地把类型写宽了

View File

@@ -0,0 +1,22 @@
---
name: frontend-crud-coding
description: 前端总入口。用于 plus-ui 前端项目中的标准 CRUD 页面、新增 API/types、复杂列表页增强、树筛选、导入导出、权限按钮与弹窗表单等任务,并根据任务类型选择合适的前端子 agent;默认基线为 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支。
---
你是 plus-ui 前端项目的总入口 agent。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端引用必须同时标记仓库、分支和文件路径;不要在 agent 文档中写本机绝对路径。
先判断任务类型,再按下面规则处理:
1. 如果是新增标准 CRUD 页面、补 `src/api`、`types.ts`、`index.vue`,优先使用 `frontend-crud-page.md`。
2. 如果是修改已有列表页、增强导入导出、树筛选、更多菜单、状态切换,优先使用 `frontend-page-enhancement.md`。
3. 如果只改接口层和类型定义,优先使用 `frontend-api-types.md`。
通用要求:
- 先读当前目录下最近似页面和 API,再动代码。
- 冲突时优先相信当前项目真实页面,其次是公共组件和工具,再其次才是 plus-ui Gitee 仓库 `6.X-Vue` 分支的 `gen` 模板。
- 默认直接产出可落地代码,而不是只给抽象建议。

View File

@@ -0,0 +1,41 @@
---
name: frontend-crud-page
description: 前端标准 CRUD 页面专家。用于 plus-ui 前端项目中的新建列表页、弹窗表单页、标准 API/types/index.vue 骨架,以及 gen 模板到项目风格的落地任务;默认参考 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支。
---
你负责 plus-ui 前端项目中的标准 CRUD 页面实现。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端模板引用必须同时标记仓库、分支和路径,例如 `branch=6.X-Vue, path=gen/index.vue.ftl`;不要写本机绝对路径。
## 核心原则
1. 先看当前模块最近似页面。
2. 再参考 plus-ui Gitee 仓库 `6.X-Vue` 分支中的 `gen` 模板。
3. 默认同时维护:
`src/api/<module>/<business>/index.ts`
`src/api/<module>/<business>/types.ts`
`src/views/<module>/<business>/index.vue`
## 页面规则
- 页面优先使用 `<script setup name="Xxx" lang="ts">`
- 标准结构通常包含:
搜索区、表格区、工具栏、分页、编辑弹窗
- 常见状态:
`loading`、`showSearch`、`ids`、`single`、`multiple`、`total`
- 查询与表单优先使用 `reactive<PageData<Form, Query>>({...})`
## API / types 规则
- 请求统一通过 `@/utils/request`
- 同目录维护 `index.ts` 与 `types.ts`
- 标准 CRUD 通常包含:列表、详情、新增、修改、删除
- 列表接口通常返回 `AxiosPromise<PageResult<XxxVO>>`
## 自检
- API 路径是否与后端一致
- `index.ts` 与 `types.ts` 是否同步补齐
- 页面是否只是模板裸输出,如果是要继续补强到当前项目风格

View File

@@ -0,0 +1,31 @@
---
name: frontend-page-enhancement
description: 复杂前端页面增强专家。用于修改 plus-ui 前端项目中已经存在的列表页、树筛选页、带导入导出和更多菜单的页面,强调增量修改和保留现有交互能力;默认基线为 Gitee 仓库 JavaLionLi/plus-ui 的 6.X-Vue 分支。
---
你负责 plus-ui 前端项目中已有页面的增强,不是重写页面。
基线仓库:`https://gitee.com/JavaLionLi/plus-ui`
默认分支:`6.X-Vue`
远端引用必须同时标记仓库、分支和文件路径;不要写本机绝对路径。
## 核心原则
1. 优先阅读当前页面完整实现。
2. 增量修改,不重写整页。
3. 保留已有树筛选、导入导出、列显隐、更多菜单、状态切换、路由跳转、SCSS 页面壳。
4. 不要把复杂页面退化成 generator 式基础列表页。
## 常见任务
- 调整工具栏和更多菜单
- 增加筛选条件和日期范围
- 增加导入导出能力
- 增加状态切换、快捷操作、确认弹窗
- 补复杂页面的小型子功能
## 自检
- 是否破坏了原页面结构和样式
- 是否误删了已有权限控制或交互能力
- 是否应该拆成子组件而不是继续堆主页面

View File

@@ -0,0 +1,151 @@
---
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 代码生成模板。
---
# 前端编码规范
先对齐当前前端项目里的真实实现,再参考项目内 `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 文档中写本机绝对路径;本地文件读取以运行时工作目录为准。
- 如果用户指定其他分支,先按用户分支读取同一仓库的对应文件,并在结果中说明使用的分支。
## 执行流程
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`。
## 文档读取规则
- 前端 API、types、页面、hooks、样式和验证规则,先读 [references/frontend.md](references/frontend.md)。
- 不确定任务边界、需要标准用例或提问方式时,再读 [references/examples.md](references/examples.md)。
- reference 只约束实现方式和自检范围;发生冲突时,以当前模块真实代码和实际调用点为准。
## 优先级规则
发生冲突时按下面顺序决策:
1. 目标目录下最近似页面、API、types 的真实实现。
2. 当前项目公共 hooks、组件、工具和样式约定。
3. 项目内 `gen` 代码生成模板。
4. 通用 Vue 3 / Element Plus 习惯。
也就是说:
- 同模块已有页面怎么写,优先怎么写。
- 没有现成页面时,使用项目内 `gen` 模板作为骨架,再改成当前项目风格。
- 复杂模块不能为了“标准 CRUD”退化成裸模板页。
## 仓库通用规则
- 遵循 [`.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`。
- 新页面不要无故引入另一套状态管理、请求封装、样式体系或权限写法。
## 目录映射规则
通常按下面关系组织代码:
- 后端 `/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/*`
标准新增通常至少包含:
- `src/api/<module>/<business>/index.ts`
- `src/api/<module>/<business>/types.ts`
- `src/views/<module>/<business>/index.vue`
按业务复杂度,可能继续补:
- 导入弹窗
- 详情抽屉或详情页
- 树筛选面板
- 列显隐配置
- 分配/授权子页面
- 自定义 SCSS 样式
## 任务分型
### 1. 标准单表 CRUD
以 `gen/index.vue.ftl`、`gen/api.ts.ftl`、`gen/types.ts.ftl` 和 `src/views/demo/demo/index.vue` 为主要起点,补齐列表、搜索、分页、新增、编辑、删除、导出、权限、类型和验证。
### 2. 树表 CRUD
以 `src/views/demo/tree/index.vue`、`src/views/workflow/category/index.vue` 为主要起点。列表接口通常返回数组而不是 `PageResult`,页面使用 `handleTree`、`useTreeTableExpand`,`Query` 通常不继承 `PageQuery`。
### 3. 强业务页面
如果页面包含树筛选、导入导出、更多菜单、状态切换、角色分配、详情抽屉、复杂校验、联动选择或独立路由,优先增量修改现有页面。不要重写成简单 CRUD。
### 4. 工作流页面
workflow 目录优先参考 `src/views/workflow/*`。流程定义、流程实例、任务列表、请假申请等页面通常有业务按钮、弹窗和路由跳转,不要硬套 system 模块。
### 5. 只补 API 和 types
只维护 `src/api/<module>/<business>/index.ts` 与 `types.ts`,但仍要与后端路由、返回结构、当前模块导入方式和类型入口一致。
## 输出要求
使用本 skill 时,默认期望产出应满足:
- 类型完整,不把页面逻辑大量写成 `any`。
- API 路径、函数名、权限标识与后端接口保持一致。
- 标准页查询、重置、分页、弹窗、提交、删除、导出流程闭环完整。
- 复杂页面保留原有交互能力和业务约束。
- 代码体现当前项目 hooks、页面壳和下载方式,而不是 `gen` 模板裸输出。
- 交付前说明运行过的验证命令;如果无法验证,说明原因。
## 快速检查清单
- `AxiosPromise` 是否来自 `@/utils/api-types`。
- `PageResult` 是否来自 `@/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`。
## 推荐提问方式
推荐把请求描述到下面粒度:
- 目标模块和业务名
- 后端接口前缀
- 是新增页面、修改页面,还是只补 API/types
- 是否需要导入、导出、树筛选、树表、状态切换、字典、权限按钮
- 希望参考哪个现有页面
例如:
- 使用 `$frontend-crud-coding` 为 `/system/client` 补一套标准 CRUD 页面,参考 `gen` 模板、`demo/demo` 和 `system/client`。
- 使用 `$frontend-crud-coding` 修改 `workflow/category` 列表页,增加导出按钮和状态筛选,保持当前 workflow 风格。

View File

@@ -0,0 +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 模板实现前端修改。"
policy:
allow_implicit_invocation: true

View File

@@ -0,0 +1,126 @@
# 使用案例
## 案例 1:新增标准 CRUD 页面
### 用户提问示例
```text
使用 $frontend-crud-coding 为 system/client 补一套前端 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/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`。
## 案例 2:新增树表页面
### 用户提问示例
```text
使用 $frontend-crud-coding 为 demo/tree2 新增树表 CRUD,接口返回数组,字段包含 id、parentId、name、orderNum。
参考 src/views/demo/tree/index.vue 和 workflow/category。
```
### 期望执行方式
- 优先判断这是树表,不生成分页 `PageResult` 页面。
- API 列表返回 `AxiosPromise<Tree2VO[]>`。
- `Query` 不继承 `PageQuery`。
- 页面使用 `handleTree`、`row-key`、`tree-props`、`useTreeTableExpand`、`el-tree-select`。
- 新增子节点时从当前行带入 `parentId`。
## 案例 3:修改已有复杂列表页
### 用户提问示例
```text
使用 $frontend-crud-coding 修改 system/user 页面:
1. 新增一个创建时间快捷筛选
2. 导出按钮保留在更多菜单中
3. 保持现有树筛选、导入、列显隐、详情抽屉和角色分配不变
```
### 期望执行方式
- 判断这是“已有复杂页面增强”,不是重新生成 CRUD。
- 优先阅读 `src/views/system/user/index.vue`。
- 保留 `TreePanel`、导入弹窗、`right-toolbar` 列显隐、`UserViewDrawer`、角色分配路由、权限控制。
- 只增量修改搜索和查询参数处理。
## 案例 4:修改 workflow 页面
### 用户提问示例
```text
使用 $frontend-crud-coding 为 workflow/category 增加状态筛选和导出按钮,保持 workflow 模块自己的树表风格。
```
### 期望执行方式
- 优先看 `src/views/workflow/category/index.vue` 和 `src/api/workflow/category/*`。
- 判断是否需要后端新增导出接口;前端导出路径保持 `workflow/category/export`。
- 不迁移 system/user 的用户专属逻辑。
- 保留树表、`useTreeTableExpand`、`handleTree` 和分类弹窗逻辑。
## 案例 5:只补 API 和 types
### 用户提问示例
```text
使用 $frontend-crud-coding 为 monitor/cache 补全前端 API 和 types,页面先不改。
```
### 期望执行方式
- 只维护 `src/api/monitor/cache/index.ts` 和 `src/api/monitor/cache/types.ts`。
- 仍然检查同目录 monitor API 的 `export function` / `export const` 风格。
- 返回类型使用 `AxiosPromise` from `@/utils/api-types`。
- 不创建页面,不改路由。
## 案例 6:接入后端新增状态切换接口
### 用户提问示例
```text
使用 $frontend-crud-coding 给 system/client 页面接入 PUT /system/client/changeStatus,状态字段 status,参考 gen 模板。
```
### 期望执行方式
- API 增加 `changeClientStatus(id, status)`。
- types 确认 `status` 类型是 string、number 还是 boolean。
- 表格列用 `el-switch`,active/inactive 值跟后端字段类型一致。
- 切换失败时回滚原状态。
- 权限使用 `system:client:edit` 或后端实际权限。
## 推荐的高质量任务描述
```text
使用 $frontend-crud-coding 在当前前端项目中新增 `/system/notice` 列表页增强:
1. 保留现有页面
2. 新增状态筛选和导出
3. API 路径沿用后端接口
4. 参考 system/config 的工具栏与导出交互
5. 参考 plus-ui Gitee 仓库 6.X-Vue 分支的 gen 模板补齐缺失 types
```
## 不推荐的任务描述
```text
帮我写个后台页面
```
更好的写法至少补充:
- 模块名
- 业务名
- 后端接口前缀
- 是新增还是修改
- 是否需要分页、导出、树表、字典、权限
- 想参考哪个现有页面

View File

@@ -0,0 +1,156 @@
# 前端约定
## 优先参考的代码来源
- 默认远端基线:`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`
## 基础栈与格式
- 技术栈是 Vue 3 + TypeScript + Element Plus + Vite + Pinia。
- 包管理按仓库现状使用 pnpm。
- `.editorconfig` 要求 UTF-8、LF、2 空格缩进。
- 当前仓库没有 `.prettierrc`;格式化使用 `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`。
- 标准函数命名:
`listXxx` -> `GET /<module>/<business>/list`
`getXxx` -> `GET /<module>/<business>/{id}`
`addXxx` -> `POST /<module>/<business>`
`updateXxx` -> `PUT /<module>/<business>`
`delXxx` -> `DELETE /<module>/<business>/{id or ids}`
`changeXxxStatus` -> `PUT /<module>/<business>/changeStatus`
- query string 用 `params`,请求体用 `data`。
- 加密、防重复提交等 headers 直接写在请求配置里,例如用户重置密码中的 `isEncrypt`、`repeatSubmit`。
- 当前仓库有些 API 使用 `export const`,有些使用 `export function`;新增标准 CRUD 优先跟随 `gen/api.ts.ftl` 和相邻模块。
- 只有相邻模块已有 `export default { ... }` 聚合时才新增默认导出。
## 类型文件规则
- 标准类型定义 `VO`、`Form`、`Query`,必要时补 `InfoVO`、`TreeVO`、`ResetPwdForm` 等扩展类型。
- `Form` 通常继承 `BaseEntity`。
- 非树表 `Query` 通常继承 `PageQuery`。
- 树表 `Query` 通常不继承 `PageQuery`。
- ID 字段通常使用 `string | number`,批量删除参数使用 `string | number | Array<string | number>`。
- Java 数值类型映射为 `number`,Boolean 映射为 `boolean`,日期/文本默认 `string`。
- 日期范围查询保留 `params?: any`,不要因为它看起来宽松就删掉。
- 列表对象、表单对象、查询对象职责分开;字段不一致时不要强行复用一个接口。
- 能明确写出类型时不要用 `any`;组件库、字典或历史接口确实无法收窄时再保留。
## Vue 页面结构规则
- 页面优先使用 `<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)`。
## 页面行为规则
- `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` 或流程定义页面。
## 字典、权限与公共工具
- 字典使用 `useDict`:
`const { sys_normal_disable } = toRefs<any>(useDict('sys_normal_disable'));`
- 新增代码默认使用当前项目主流 `v-hasPermi`。
- 如果正在修改的文件已经混用或使用 `v-has-permi`,保持同文件现状,不为统一写法而重排无关代码。
- `el-dropdown-item` 延迟加载导致权限指令不可靠时,使用 `v-if="checkPermi([...])"`,参考 `system/user`。
- 常用工具:
`modal` from `@/plugins/modal`
`download as requestDownload` from `@/utils/request`
`useDict` from `@/utils/dict`
`checkPermi` from `@/utils/permission`
`handleTree`、`parseStrEmpty` from `@/utils/ruoyi`
## 组件与样式规则
- 优先复用公共组件:`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 使用。
- 不要为了单页需求修改全局组件样式。
- 类名保持模块语义:`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`。
- 新增子节点时从当前行回填 `parentId`。
- 删除确认文案优先使用业务名称,而不是批量 ID 文案。
## 与 gen 模板的关系
- `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` 的简化逻辑。
## 验证规则
- 只改文档或 skill:运行 skill 基础校验即可。
- 改前端 TS/Vue/API/types:优先运行 `pnpm exec vue-tsc --noEmit`。
- 改页面模板、import、权限或较多文件:再运行 `pnpm lint`。
- 改公共 hooks、组件、构建相关或大范围页面:再运行 `pnpm build`。
- 如果验证因为环境、依赖或权限失败,交付时说明失败命令和原因。
## 避免事项
- 不要从 `axios` 引入 `AxiosPromise`。
- 不要绕开 `request` 或 `requestDownload` 自造请求/下载封装。
- 不要跳过 `types.ts`,把类型全写在页面里。
- 不要删除日期范围 `params`、权限指令、导出、导入、树筛选、列显隐等现有能力。
- 不要为了“更整洁”重写复杂页面的大块业务逻辑。
- 不要在新增标准页里使用与仓库不一致的 UI 壳或状态管理方式。

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
# 告诉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
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

42
.env.development Normal file
View File

@@ -0,0 +1,42 @@
# 页面标题
VITE_APP_TITLE = 智净云 · 智能家居管理后台
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
# 开发环境配置
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 = 54123
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
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'

45
.env.production Normal file
View File

@@ -0,0 +1,45 @@
# 页面标题
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'

29
.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
.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
*.iml
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
package-lock.json
yarn.lock
# 编译生成的文件
auto-imports.d.ts
components.d.ts

31
.oxfmtrc.json Normal file
View File

@@ -0,0 +1,31 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"ignorePatterns": [
"src/types/components.d.ts",
"src/types/auto-imports.d.ts",
".ai_state",
".claude",
".codex",
"doc"
],
"printWidth": 120,
"singleQuote": true,
"trailingComma": "none",
"arrowParens": "avoid",
"htmlWhitespaceSensitivity": "ignore",
"experimentalSortPackageJson": {
"sortScripts": true
},
"sortImports": {
"newlinesBetween": false,
"groups": [
"type-import",
["value-builtin", "value-external"],
"type-internal",
"value-internal",
["type-parent", "type-sibling", "type-index"],
["value-parent", "value-sibling", "value-index"],
"unknown"
]
}
}

25
.oxlintrc.json Normal file
View File

@@ -0,0 +1,25 @@
{
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
"plugins": ["eslint", "typescript", "unicorn", "oxc", "import", "vue"],
"rules": {
"typescript/no-empty-function": "off",
"typescript/no-explicit-any": "off",
"typescript/no-unused-vars": "off",
"typescript/no-this-alias": "off",
"typescript/no-empty-object-type": "off",
"typescript/no-unused-expressions": "off",
"prefer-rest-params": "off",
"import/no-unassigned-import": "off",
"import/no-named-as-default-member": "off",
"import/no-named-as-default": "off",
"no-shadow": "off",
"unicorn/prefer-add-event-listener": "off",
"unicorn/consistent-function-scoping": "off",
"unicorn/no-instanceof-builtins": "off"
},
"categories": {
"correctness": "error",
"suspicious": "error"
},
"ignorePatterns": [".ai_state", ".claude", ".codex", "doc", "dist/**", "dist-ssr/**", "coverage/**"]
}

20
LICENSE Normal file
View File

@@ -0,0 +1,20 @@
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.

76
README.md Normal file
View File

@@ -0,0 +1,76 @@
## 平台简介
- 本仓库为前端技术栈 [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/)
- 成员项目: 基于 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) |
## 分支说明
- 6.X分支(稳定发布主分支 生产可用)
- dev分支(开发分支 开发过程中使用)
## 前端运行
```bash
# 安装依赖
pnpm install --registry=https://registry.npmmirror.com
# 启动服务
pnpm dev
# 构建生产环境
pnpm build:prod
# 前端访问地址 http://localhost:80
```
## 本框架与RuoYi的业务差异
| 业务 | 功能说明 | 本框架 | RuoYi |
| ------------ | ------------------------------------------------------------- | ------ | ----------------------------- |
| 用户管理 | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等 | 支持 | 支持 |
| 部门管理 | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限 | 支持 | 支持 |
| 岗位管理 | 配置系统用户所属担任职务 | 支持 | 支持 |
| 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等 | 支持 | 支持 |
| 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | 支持 | 支持 |
| 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | 支持 | 支持 |
| 参数管理 | 对系统动态配置常用参数 | 支持 | 支持 |
| 通知公告 | 系统通知公告信息发布维护 | 支持 | 支持 |
| 操作日志 | 系统正常操作日志记录和查询 系统异常信息日志记录和查询 | 支持 | 支持 |
| 登录日志 | 系统登录日志记录查询包含登录异常 | 支持 | 支持 |
| 文件管理 | 系统文件展示、上传、下载、删除等管理 | 支持 | 无 |
| 文件配置管理 | 系统文件上传、下载所需要的配置信息动态添加、修改、删除等管理 | 支持 | 无 |
| 在线用户管理 | 已登录系统的在线用户信息监控与强制踢出操作 | 支持 | 支持 |
| 定时任务 | 运行报表、任务管理(添加、修改、删除)、日志管理、执行器管理等 | 支持 | 仅支持任务与日志管理 |
| 代码生成 | 多数据源前后端代码的生成(java、html、xml、sql)支持CRUD下载 | 支持 | 仅支持单数据源 |
| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 |
| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 |
| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 |
| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 |
| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 |
## 演示图例
| | |
|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| ![输入图片说明](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 "屏幕截图") |

104
gen/api.ts.ftl Normal file
View File

@@ -0,0 +1,104 @@
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';
/**
* 查询${functionName}列表
* @param query
* @returns {*}
*/
export const list${BusinessName} = (query?: ${BusinessName}Query): AxiosPromise<<#if table.tree>${BusinessName}VO[]<#else>PageResult<${BusinessName}VO></#if>> => {
return request({
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({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'get'
});
};
/**
* 新增${functionName}
* @param data
*/
export const add${BusinessName} = (data: ${BusinessName}Form) => {
return request({
url: '/${moduleName}/${businessName}',
method: 'post',
data: data
});
};
/**
* 修改${functionName}
* @param data
*/
export const update${BusinessName} = (data: ${BusinessName}Form) => {
return request({
url: '/${moduleName}/${businessName}',
method: 'put',
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({
url: '/${moduleName}/${businessName}/changeStatus',
method: 'put',
data: {
${pkColumn.javaField},
${statusField}: status
}
});
};
</#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({
url: '/${moduleName}/${businessName}/updateSort',
method: 'put',
data: {
${pkColumn.javaField},
${sortField}: sortValue
}
});
};
</#if>
/**
* 删除${functionName}
* @param ${pkColumn.javaField}
*/
export const del${BusinessName} = (${pkColumn.javaField}: string | number | Array<string | number>) => {
return request({
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'delete'
});
};

691
gen/index-tree.vue.ftl Normal file
View File

@@ -0,0 +1,691 @@
<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>

615
gen/index.vue.ftl Normal file
View File

@@ -0,0 +1,615 @@
<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>

48
gen/types.ts.ftl Normal file
View File

@@ -0,0 +1,48 @@
export interface ${BusinessName}VO {
<#list columns as column>
<#if column.list>
/**
* ${column.columnComment}
*/
${column.javaField}: ${column.tsType};
<#if column.htmlType == "imageUpload">
/**
* ${column.columnComment}Url
*/
${column.javaField}Url: string;
</#if>
</#if>
</#list>
<#if table.tree>
/**
* 子对象
*/
children: ${BusinessName}VO[];
</#if>
}
export interface ${BusinessName}Form extends BaseEntity {
<#list columns as column>
<#if column.insert || column.edit>
/**
* ${column.columnComment}
*/
${column.javaField}?: ${column.tsType};
</#if>
</#list>
}
export interface ${BusinessName}Query<#if !table.tree> extends PageQuery</#if> {
<#list columns as column>
<#if column.query>
/**
* ${column.columnComment}
*/
${column.javaField}?: ${column.tsType};
</#if>
</#list>
/**
* 日期范围参数
*/
params?: any;
}

214
index.html Normal file
View File

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

82
package.json Normal file
View File

@@ -0,0 +1,82 @@
{
"$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",
"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",
"fmt": "oxfmt .",
"lint": "oxlint src",
"lint:fix": "oxlint --fix src",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "2.3.2",
"@highlightjs/vue-plugin": "2.1.2",
"@iconify/vue": "^5.0.1",
"@noble/ciphers": "^2.4.0",
"@vueuse/core": "14.4.0",
"@wangeditor-next/editor": "6.4.1",
"@wangeditor-next/editor-for-vue": "6.4.1",
"animate.css": "4.1.1",
"await-to-js": "3.0.0",
"axios": "1.20.0",
"echarts": "6.1.0",
"element-plus": "2.14.5",
"highlight.js": "11.12.0",
"image-conversion": "2.1.1",
"jsencrypt": "3.5.4",
"nprogress": "0.2.0",
"pinia": "4.0.3",
"vue": "3.5.42",
"vue-cropper": "1.1.4",
"vue-i18n": "11.4.10",
"vue-json-pretty": "2.6.0",
"vue-router": "5.3.1",
"vue-types": "7.0.0",
"vxe-pc-ui": "4.18.9",
"vxe-table": "4.21.9"
},
"devDependencies": {
"@types/node": "^26.1.1",
"@types/nprogress": "0.2.3",
"@unocss/preset-attributify": "66.10.1",
"@unocss/preset-wind3": "66.10.1",
"@vitejs/plugin-vue": "^6.0.8",
"@vue/compiler-sfc": "3.5.42",
"autoprefixer": "10.5.5",
"oxfmt": "^0.67.0",
"oxlint": "^1.82.0",
"sass": "1.104.0",
"typescript": "^6.0.3",
"unocss": "66.10.1",
"unplugin-auto-import": "21.1.0",
"unplugin-vue-components": "32.1.0",
"unplugin-vue-setup-extend-plus": "1.0.1",
"vite": "^8.2.2",
"vite-plugin-svg-icons-ng": "^1.9.3",
"vitest": "4.1.11",
"vue-tsc": "^3.3.11"
},
"browserslist": [
"Chrome >= 87",
"Edge >= 88",
"Safari >= 14",
"Firefox >= 78"
],
"engines": {
"node": ">=20.19.0",
"pnpm": ">=10.0.0"
}
}

4898
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

10
pnpm-workspace.yaml Normal file
View File

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

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

258
public/ie.html Normal file

File diff suppressed because one or more lines are too long

20
src/App.vue Normal file
View File

@@ -0,0 +1,20 @@
<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
src/animate.ts Normal file
View File

@@ -0,0 +1,48 @@
// 前缀
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
};

10
src/api/ai/agent/index.ts Normal file
View File

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

View File

@@ -0,0 +1,6 @@
export interface SnailOpenApiUser {
openId: string;
nickname?: string;
externalId?: string;
created?: boolean;
}

2
src/api/ai/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './agent';
export * from './agent/types';

View File

@@ -0,0 +1,63 @@
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';
/**
* 查询测试单列表
* @param query
* @returns {*}
*/
export const listDemo = (query?: DemoQuery): AxiosPromise<PageResult<DemoVO>> => {
return request({
url: '/demo/demo/list',
method: 'get',
params: query
});
};
/**
* 查询测试单详细
* @param id
*/
export const getDemo = (id: string | number): AxiosPromise<DemoVO> => {
return request({
url: '/demo/demo/' + id,
method: 'get'
});
};
/**
* 新增测试单
* @param data
*/
export const addDemo = (data: DemoForm) => {
return request({
url: '/demo/demo',
method: 'post',
data: data
});
};
/**
* 修改测试单
* @param data
*/
export const updateDemo = (data: DemoForm) => {
return request({
url: '/demo/demo',
method: 'put',
data: data
});
};
/**
* 删除测试单
* @param id
*/
export const delDemo = (id: string | number | Array<string | number>) => {
return request({
url: '/demo/demo/' + id,
method: 'delete'
});
};

View File

@@ -0,0 +1,90 @@
export interface DemoVO {
/**
* 主键
*/
id: string | number;
/**
* 部门id
*/
deptId: string | number;
/**
* 用户id
*/
userId: string | number;
/**
* 排序号
*/
orderNum: number;
/**
* key键
*/
testKey: string;
/**
* 值
*/
value: string;
}
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;
}

View File

@@ -0,0 +1,62 @@
import type { TreeForm, TreeQuery, TreeVO } from '@/api/demo/tree/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
/**
* 查询测试树列表
* @param query
* @returns {*}
*/
export const listTree = (query?: TreeQuery): AxiosPromise<TreeVO[]> => {
return request({
url: '/demo/tree/list',
method: 'get',
params: query
});
};
/**
* 查询测试树详细
* @param id
*/
export const getTree = (id: string | number): AxiosPromise<TreeVO> => {
return request({
url: '/demo/tree/' + id,
method: 'get'
});
};
/**
* 新增测试树
* @param data
*/
export const addTree = (data: TreeForm) => {
return request({
url: '/demo/tree',
method: 'post',
data: data
});
};
/**
* 修改测试树
* @param data
*/
export const updateTree = (data: TreeForm) => {
return request({
url: '/demo/tree',
method: 'put',
data: data
});
};
/**
* 删除测试树
* @param id
*/
export const delTree = (id: string | number | Array<string | number>) => {
return request({
url: '/demo/tree/' + id,
method: 'delete'
});
};

View File

@@ -0,0 +1,80 @@
export interface TreeVO {
/**
* 主键
*/
id: string | number;
/**
* 父id
*/
parentId: string | number;
/**
* 部门id
*/
deptId: string | number;
/**
* 用户id
*/
userId: string | number;
/**
* 值
*/
treeName: string;
/**
* 子对象
*/
children: TreeVO[];
}
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;
}

107
src/api/login.ts Normal file
View File

@@ -0,0 +1,107 @@
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';
// 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({
url: '/auth/login',
headers: {
isToken: false,
isEncrypt: true,
repeatSubmit: false
},
method: 'post',
data: params
});
}
// 注册方法
export function register(data: any) {
const params = {
...data,
clientId: clientId,
grantType: 'password'
};
return request({
url: '/auth/register',
headers: {
isToken: false,
isEncrypt: true,
repeatSubmit: false
},
method: 'post',
data: params
});
}
/**
* 注销
*/
export function logout() {
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({
url: '/auth/logout',
method: 'post'
});
}
/**
* 获取验证码
*/
export function getCodeImg(): AxiosPromise<VerifyCodeResult> {
return request({
url: '/auth/code',
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({
url: '/system/user/getInfo',
method: 'get'
});
}

11
src/api/menu.ts Normal file
View File

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

12
src/api/monitor/cache/index.ts vendored Normal file
View File

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

7
src/api/monitor/cache/types.ts vendored Normal file
View File

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

View File

@@ -0,0 +1,37 @@
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'
});
}

View File

@@ -0,0 +1,20 @@
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;
}

View File

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

View File

@@ -0,0 +1,15 @@
export interface OnlineQuery extends PageQuery {
ipaddr: string;
userName: string;
}
export interface OnlineVO extends BaseEntity {
tokenId: string;
deptName: string;
userName: string;
ipaddr: string;
loginLocation: string;
browser: string;
os: string;
loginTime: number;
}

View File

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

View File

@@ -0,0 +1,71 @@
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;
}
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;
}

View File

@@ -0,0 +1,81 @@
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';
/**
* 查询客户端管理列表
* @param query
* @returns {*}
*/
export const listClient = (query?: ClientQuery): AxiosPromise<PageResult<ClientVO>> => {
return request({
url: '/system/client/list',
method: 'get',
params: query
});
};
/**
* 查询客户端管理详细
* @param id
*/
export const getClient = (id: string | number): AxiosPromise<ClientVO> => {
return request({
url: '/system/client/' + id,
method: 'get'
});
};
/**
* 新增客户端管理
* @param data
*/
export const addClient = (data: ClientForm) => {
return request({
url: '/system/client',
method: 'post',
data: data
});
};
/**
* 修改客户端管理
* @param data
*/
export const updateClient = (data: ClientForm) => {
return request({
url: '/system/client',
method: 'put',
data: data
});
};
/**
* 删除客户端管理
* @param id
*/
export const delClient = (id: string | number | Array<string | number>) => {
return request({
url: '/system/client/' + id,
method: 'delete'
});
};
/**
* 状态修改
* @param clientId 客户端id
* @param status 状态
*/
export function changeStatus(clientId: string, status: string) {
const data = {
clientId,
status
};
return request({
url: '/system/client/changeStatus',
method: 'put',
data: data
});
}

View File

@@ -0,0 +1,185 @@
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;
}
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;
}

View File

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

View File

@@ -0,0 +1,23 @@
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;
}

View File

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

View File

@@ -0,0 +1,60 @@
/**
* 部门查询参数
*/
export interface DeptQuery extends PageQuery {
deptName?: string;
deptCategory?: string;
status?: number;
}
/**
* 部门类型
*/
export interface DeptVO extends BaseEntity {
id: number | string;
parentName: string;
parentId: number | string;
children: DeptVO[];
deptId: number | string;
deptName: string;
deptCategory: string;
orderNum: number;
leader: string;
phone: string;
email: string;
status: string;
delFlag: string;
ancestors: string;
menuId: string | number;
}
/**
* 部门类型
*/
export interface DeptTreeVO extends BaseEntity {
id: number | string;
label: string;
parentId: number | string;
weight: number;
children: DeptTreeVO[];
disabled: boolean;
}
/**
* 部门表单类型
*/
export interface DeptForm {
parentName?: string;
parentId?: number | string;
children?: DeptForm[];
deptId?: number | string;
deptName?: string;
deptCategory?: string;
orderNum?: number;
leader?: string;
phone?: string;
email?: string;
status?: string;
delFlag?: string;
ancestors?: string;
}

View File

@@ -0,0 +1,54 @@
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,
method: 'get'
});
}
// 查询字典数据列表
export function listData(query: DictDataQuery): AxiosPromise<PageResult<DictDataVO>> {
return request({
url: '/system/dict/data/list',
method: 'get',
params: query
});
}
// 查询字典数据详细
export function getData(dictCode: string | number): AxiosPromise<DictDataVO> {
return request({
url: '/system/dict/data/' + dictCode,
method: 'get'
});
}
// 新增字典数据
export function addData(data: DictDataForm) {
return request({
url: '/system/dict/data',
method: 'post',
data: data
});
}
// 修改字典数据
export function updateData(data: DictDataForm) {
return request({
url: '/system/dict/data',
method: 'put',
data: data
});
}
// 删除字典数据
export function delData(dictCode: string | number | Array<string | number>) {
return request({
url: '/system/dict/data/' + dictCode,
method: 'delete'
});
}

View File

@@ -0,0 +1,26 @@
export interface DictDataQuery extends PageQuery {
dictName: string;
dictType: string;
dictLabel: string;
}
export interface DictDataVO extends BaseEntity {
dictCode: string;
dictLabel: string;
dictValue: string;
cssClass: string;
listClass: ElTagType;
dictSort: number;
remark: string;
}
export interface DictDataForm {
dictType?: string;
dictCode: string | undefined;
dictLabel: string;
dictValue: string;
cssClass: string;
listClass: ElTagType;
dictSort: number;
remark: string;
}

View File

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

View File

@@ -0,0 +1,18 @@
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;
}

View File

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

View File

@@ -0,0 +1,89 @@
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;
}
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?: MenuTypeEnum;
visible?: string;
status?: string;
icon?: string;
activeMenu?: string;
ext?: string;
remark?: string;
query?: string;
perms?: string;
}

View File

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

View File

@@ -0,0 +1,17 @@
export interface MessageVO extends BaseEntity {
messageId: number | string;
category: string;
type: string;
source: string;
title: string;
message: string;
content?: string;
data?: Record<string, any> | null;
path?: string;
}
export interface MessageBoxVO {
systemList: MessageVO[];
noticeList: MessageVO[];
workflowList: MessageVO[];
}

View File

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

View File

@@ -0,0 +1,26 @@
export interface NoticeVO extends BaseEntity {
noticeId: number;
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;
}
export interface NoticeForm {
noticeId: number | string | undefined;
noticeTitle: string;
noticeType: string;
noticeContent: string;
status: string;
remark: string;
createByName: string;
}

View File

@@ -0,0 +1,38 @@
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';
// 查询OSS对象存储列表
export function listOss(query: OssQuery): AxiosPromise<PageResult<OssVO>> {
return request({
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,
method: 'get'
});
}
// 上传OSS对象存储
export function uploadOss(data: FormData): AxiosPromise<OssUploadVO> {
return request({
url: '/resource/oss/upload',
method: 'post',
data
});
}
// 删除OSS对象存储
export function delOss(ossId: string | number | Array<string | number>) {
return request({
url: '/resource/oss/' + ossId,
method: 'delete'
});
}

View File

@@ -0,0 +1,42 @@
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;
}

View File

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

View File

@@ -0,0 +1,38 @@
export interface OssConfigVO extends BaseEntity {
ossConfigId: number | string;
configKey: string;
accessKey: string;
secretKey: string;
bucketName: string;
prefix: string;
endpoint: string;
domainUrl: string;
isHttps: string;
region: string;
status: string;
ext1: string;
remark: string;
accessPolicy: string;
}
export interface OssConfigQuery extends PageQuery {
configKey: string;
bucketName: string;
status: string;
}
export interface OssConfigForm {
ossConfigId: string | number | undefined;
configKey: string;
accessKey: string;
secretKey: string;
bucketName: string;
prefix: string;
endpoint: string;
domainUrl: string;
isHttps: string;
accessPolicy: string;
region: string;
status: string;
remark: string;
}

View File

@@ -0,0 +1,70 @@
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 { PostForm, PostQuery, PostVO } from './types';
// 查询岗位列表
export function listPost(query: PostQuery): AxiosPromise<PageResult<PostVO>> {
return request({
url: '/system/post/list',
method: 'get',
params: query
});
}
// 查询岗位详细
export function getPost(postId: string | number): AxiosPromise<PostVO> {
return request({
url: '/system/post/' + postId,
method: 'get'
});
}
// 获取岗位选择框列表
export function optionselect(deptId?: number | string, postIds?: (number | string)[]): AxiosPromise<PostVO[]> {
return request({
url: '/system/post/optionselect',
method: 'get',
params: {
postIds: postIds,
deptId: deptId
}
});
}
// 新增岗位
export function addPost(data: PostForm) {
return request({
url: '/system/post',
method: 'post',
data: data
});
}
// 修改岗位
export function updatePost(data: PostForm) {
return request({
url: '/system/post',
method: 'put',
data: data
});
}
// 删除岗位
export function delPost(postId: string | number | (string | number)[]) {
return request({
url: '/system/post/' + postId,
method: 'delete'
});
}
/**
* 查询部门下拉树结构
*/
export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
return request({
url: '/system/post/deptTree',
method: 'get'
});
};

View File

@@ -0,0 +1,31 @@
export interface PostVO extends BaseEntity {
postId: number | string;
deptId: number | string;
postCode: string;
postName: string;
postCategory: string;
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;
}

View File

@@ -0,0 +1,160 @@
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';
export const listRole = (query: RoleQuery): AxiosPromise<PageResult<RoleVO>> => {
return request({
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,
method: 'get'
});
};
/**
* 查询角色详细
*/
export const getRole = (roleId: string | number): AxiosPromise<RoleVO> => {
return request({
url: '/system/role/' + roleId,
method: 'get'
});
};
/**
* 新增角色
*/
export const addRole = (data: any) => {
return request({
url: '/system/role',
method: 'post',
data: data
});
};
/**
* 修改角色基础信息
* @param data
*/
export const updateRole = (data: any) => {
return request({
url: '/system/role',
method: 'put',
data: data
});
};
/**
* 修改角色权限(菜单权限 + 数据权限)
*/
export const updateRolePermission = (data: any) => {
return request({
url: '/system/role/permission',
method: 'put',
data: data
});
};
/**
* 角色状态修改
*/
export const changeRoleStatus = (roleId: string | number, status: string) => {
const data = {
roleId,
status
};
return request({
url: '/system/role/changeStatus',
method: 'put',
data: data
});
};
/**
* 删除角色
*/
export const delRole = (roleId: Array<string | number> | string | number) => {
return request({
url: '/system/role/' + roleId,
method: 'delete'
});
};
/**
* 查询角色已授权用户列表
*/
export const allocatedUserList = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
return request({
url: '/system/role/authUser/allocatedList',
method: 'get',
params: query
});
};
/**
* 查询角色未授权用户列表
*/
export const unallocatedUserList = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
return request({
url: '/system/role/authUser/unallocatedList',
method: 'get',
params: query
});
};
/**
* 取消用户授权角色
*/
export const authUserCancel = (data: any) => {
return request({
url: '/system/role/authUser/cancel',
method: 'put',
data: data
});
};
/**
* 批量取消用户授权角色
*/
export const authUserCancelAll = (data: any) => {
return request({
url: '/system/role/authUser/cancelAll',
method: 'put',
params: data
});
};
/**
* 授权用户选择
*/
export const authUserSelectAll = (data: any) => {
return request({
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,
method: 'get'
});
};
export default {
optionSelect,
listRole
};

View File

@@ -0,0 +1,52 @@
/**
* 菜单树形结构类型
*/
export interface DeptTreeOption {
id: string;
label: string;
parentId: string;
weight: number;
children?: DeptTreeOption[];
}
export interface RoleDeptTree {
checkedKeys: string[];
depts: DeptTreeOption[];
}
export interface RoleVO extends BaseEntity {
roleId: string | number;
roleName: string;
roleKey: string;
roleSort: number;
dataScope: string;
menuCheckStrictly: boolean;
deptCheckStrictly: boolean;
status: string;
delFlag: string;
remark?: any;
flag: 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>;
}

View File

@@ -0,0 +1,24 @@
import request from '@/utils/request';
// 获取跳转URL
export function authRouterUrl(source: string) {
return request({
url: '/auth/binding/' + source,
method: 'get'
});
}
// 解绑账号
export function authUnlock(authId: string) {
return request({
url: '/auth/unlock/' + authId,
method: 'delete'
});
}
//获取授权列表
export function getAuthList() {
return request({
url: '/system/social/list',
method: 'get'
});
}

View File

@@ -0,0 +1,229 @@
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 { 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({
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,
method: 'get'
});
};
/**
* 获取用户详情
* @param userId
*/
export const getUser = (userId?: string | number): AxiosPromise<UserInfoVO> => {
return request({
url: '/system/user/' + parseStrEmpty(userId),
method: 'get'
});
};
/**
* 新增用户
*/
export const addUser = (data: UserForm) => {
return request({
url: '/system/user',
method: 'post',
data: data
});
};
/**
* 修改用户
*/
export const updateUser = (data: UserForm) => {
return request({
url: '/system/user',
method: 'put',
data: data
});
};
/**
* 删除用户
* @param userId 用户ID
*/
export const delUser = (userId: Array<string | number> | string | number) => {
return request({
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({
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 = {
userId,
status
};
return request({
url: '/system/user/changeStatus',
method: 'put',
data: data
});
};
/**
* 解锁用户
* @param userId 用户ID
*/
export const unlockUser = (userId: number | string) => {
return request({
url: '/system/user/unlock/' + userId,
method: 'get'
});
};
/**
* 查询用户个人信息
*/
export const getUserProfile = (): AxiosPromise<UserInfoVO> => {
return request({
url: '/system/user/profile',
method: 'get'
});
};
/**
* 修改用户个人信息
* @param data 用户信息
*/
export const updateUserProfile = (data: UserProfileForm) => {
return request({
url: '/system/user/profile',
method: 'put',
data: data
});
};
/**
* 用户密码重置
* @param oldPassword 旧密码
* @param newPassword 新密码
*/
export const updateUserPwd = (oldPassword: string, newPassword: string) => {
const data = {
oldPassword,
newPassword
};
return request({
url: '/system/user/profile/updatePwd',
method: 'put',
headers: {
isEncrypt: true,
repeatSubmit: false
},
data: data
});
};
/**
* 查询授权角色
* @param userId 用户ID
*/
export const getAuthRole = (userId: string | number): AxiosPromise<{ user: UserVO; roles: RoleVO[] }> => {
return request({
url: '/system/user/authRole/' + userId,
method: 'get'
});
};
/**
* 保存授权角色
* @param data 用户ID
*/
export const updateAuthRole = (data: { userId: string; roleIds: string }) => {
return request({
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({
url: '/system/user/deptTree',
method: 'get'
});
};
export default {
listUser,
getUser,
optionSelect,
addUser,
updateUser,
delUser,
resetUserPwd,
changeUserStatus,
unlockUser,
getUserProfile,
updateUserProfile,
updateUserPwd,
getAuthRole,
updateAuthRole,
deptTreeSelect,
listUserByDeptId
};

View File

@@ -0,0 +1,101 @@
import type { PostVO } from '@/api/system/post/types';
import type { RoleVO } from '@/api/system/role/types';
/**
* 用户信息
*/
export interface UserInfo {
user: UserVO;
roles: string[];
permissions: string[];
}
/**
* 用户查询对象类型
*/
export interface UserQuery extends PageQuery {
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;
phoneNumber?: string;
email?: string;
gender?: string;
status: string;
remark?: string;
avatar?: string | number;
postIds: string[];
roleIds: string[];
}
/**
* 个人资料表单类型
*/
export interface UserProfileForm {
nickName?: string;
phoneNumber?: string;
email?: string;
gender?: string;
avatar?: string | number;
}
export interface UserInfoVO {
user: UserVO;
roles: RoleVO[];
roleIds: string[];
posts: PostVO[];
postIds: string[];
roleGroup: string;
postGroup: string;
}
export interface ResetPwdForm {
oldPassword: string;
newPassword: string;
confirmPassword: string;
}

81
src/api/tool/gen/index.ts Normal file
View File

@@ -0,0 +1,81 @@
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';
export type { GenTableDetailPayload } from './types';
// 查询生成表数据
export const listTable = (query: TableQuery): AxiosPromise<PageResult<TableVO>> => {
return request({
url: '/tool/gen/list',
method: 'get',
params: query
});
};
// 查询db数据库列表
export const listDbTable = (query: DbTableQuery): AxiosPromise<PageResult<DbTableVO>> => {
return request({
url: '/tool/gen/db/list',
method: 'get',
params: query
});
};
// 查询表详细信息
export const getGenTable = (tableId: string | number): AxiosPromise<GenTableDetailPayload> => {
return request({
url: '/tool/gen/' + tableId,
method: 'get'
});
};
// 修改代码生成信息
export const updateGenTable = (data: DbTableForm): AxiosPromise<unknown> => {
return request({
url: '/tool/gen',
method: 'put',
data: data
});
};
// 导入表
export const importTable = (data: { tables: string; dataName: string }): AxiosPromise<unknown> => {
return request({
url: '/tool/gen/importTable',
method: 'post',
params: data
});
};
// 预览生成代码
export const previewTable = (tableId: string | number) => {
return request({
url: '/tool/gen/preview/' + tableId,
method: 'get'
});
};
// 删除表数据
export const delTable = (tableId: string | number | Array<string | number>) => {
return request({
url: '/tool/gen/' + tableId,
method: 'delete'
});
};
// 同步数据库
export const synchDb = (tableId: string | number) => {
return request({
url: '/tool/gen/synchDb/' + tableId,
method: 'get'
});
};
// 获取数据源名称
export const getDataNames = () => {
return request({
url: '/tool/gen/getDataNames',
method: 'get'
});
};

215
src/api/tool/gen/types.ts Normal file
View File

@@ -0,0 +1,215 @@
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;
}
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;
}
export interface DbTableQuery extends PageQuery {
dataName: string;
tableName: string;
tableComment: 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 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;
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;
}

51
src/api/types.ts Normal file
View File

@@ -0,0 +1,51 @@
/**
* 注册
*/
export type RegisterForm = {
username: string;
password: string;
confirmPassword?: string;
code?: string;
uuid?: string;
userType?: string;
};
/**
* 登录请求
*/
export interface LoginData {
username?: string;
password?: string;
rememberMe?: boolean;
socialCode?: string;
socialState?: string;
source?: string;
code?: string;
uuid?: string;
clientId: string;
grantType: string;
}
/**
* 登录响应
*/
export interface LoginResult {
access_token: string;
}
/**
* 验证码返回
*/
export interface VerifyCodeResult {
captchaEnabled: boolean;
uuid?: string;
img?: string;
}
/**
* 分页返回结果
*/
export interface PageResult<T = any> {
total: number;
rows: T[];
}

View File

@@ -0,0 +1,76 @@
import type { CategoryForm, CategoryQuery, CategoryTreeVO, CategoryVO } from '@/api/workflow/category/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
/**
* 查询流程分类列表
* @param query
* @returns {*}
*/
export const listCategory = (query?: CategoryQuery): AxiosPromise<CategoryVO[]> => {
return request({
url: '/workflow/category/list',
method: 'get',
params: query
});
};
/**
* 查询流程分类详细
* @param categoryId
*/
export const getCategory = (categoryId: string | number): AxiosPromise<CategoryVO> => {
return request({
url: '/workflow/category/' + categoryId,
method: 'get'
});
};
/**
* 新增流程分类
* @param data
*/
export const addCategory = (data: CategoryForm) => {
return request({
url: '/workflow/category',
method: 'post',
data: data
});
};
/**
* 修改流程分类
* @param data
*/
export const updateCategory = (data: CategoryForm) => {
return request({
url: '/workflow/category',
method: 'put',
data: data
});
};
/**
* 删除流程分类
* @param categoryId
*/
export const delCategory = (categoryId: string | number | Array<string | number>) => {
return request({
url: '/workflow/category/' + categoryId,
method: 'delete'
});
};
/**
* 获取流程分类树列表
* @param query 流程实例id
* @returns
*/
export const categoryTree = (query?: CategoryForm): AxiosPromise<CategoryTreeVO[]> => {
return request({
url: `/workflow/category/categoryTree`,
method: 'get',
params: query
});
};

View File

@@ -0,0 +1,67 @@
export interface CategoryTreeVO {
id: number | string;
label: string;
parentId: number | string;
weight: number;
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;
}

View File

@@ -0,0 +1,176 @@
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';
/**
* 获取流程定义列表
* @param query 流程实例id
* @returns
*/
export const listDefinition = (query: FlowDefinitionQuery): AxiosPromise<PageResult<FlowDefinitionVo>> => {
return request({
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({
url: `/workflow/definition/publish/${id}`,
method: 'put'
});
};
/**
* 取消发布流程定义
* @param id 流程定义id
* @returns
*/
export const unPublish = (id: string) => {
return request({
url: `/workflow/definition/unPublish/${id}`,
method: 'put'
});
};
/**
* 获取流程定义xml字符串
* @param id 流程定义id
* @returns
*/
export const xmlString = (id: string) => {
return request({
url: `/workflow/definition/xmlString/${id}`,
method: 'get'
});
};
/**
* 新增
* @param data 参数
* @returns
*/
export const add = (data: FlowDefinitionForm) => {
return request({
url: `/workflow/definition`,
method: 'post',
data: data
});
};
/**
* 修改
* @param data 参数
* @returns
*/
export const edit = (data: FlowDefinitionForm) => {
return request({
url: `/workflow/definition`,
method: 'put',
data: data
});
};
/**
* 查询详情
* @param id 参数
* @returns
*/
export const getInfo = (id: number | string) => {
return request({
url: `/workflow/definition/${id}`,
method: 'get'
});
};
/**
* 复制流程定义
* @param id 流程定义id
* @returns
*/
export const copy = (id: string) => {
return request({
url: `/workflow/definition/copy/${id}`,
method: 'post'
});
};

View File

@@ -0,0 +1,34 @@
export interface FlowDefinitionQuery extends PageQuery {
flowCode?: string;
flowName?: string;
category: string | number;
isPublish?: number;
}
export interface FlowDefinitionVo {
id: string;
flowName: string;
flowCode: string;
formPath: string;
version: string;
isPublish: number;
activityStatus: number;
createTime: Date;
updateTime: Date;
}
export interface FlowDefinitionForm {
id: string;
flowName: string;
flowCode: string;
category: string;
ext: string;
formPath: string;
formCustom: string;
modelValue: string;
}
export interface definitionXmlVO {
xml: string[];
xmlStr: string;
}

View File

@@ -0,0 +1,126 @@
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';
/**
* 查询运行中实例列表
* @param query
* @returns {*}
*/
export const pageByRunning = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
return request({
url: '/workflow/instance/pageByRunning',
method: 'get',
params: query
});
};
/**
* 查询已完成实例列表
* @param query
* @returns {*}
*/
export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
return request({
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({
url: '/workflow/instance/pageByCurrent',
method: 'get',
params: query
});
};
/**
* 撤销流程
* @param data 参数
* @returns
*/
export const cancelProcessApply = (data: any) => {
return request({
url: `/workflow/instance/cancelProcessApply`,
method: 'put',
data: data
});
};
/**
* 获取流程变量
* @param instanceId 实例id
* @returns
*/
export const instanceVariable = (instanceId: string | number) => {
return request({
url: `/workflow/instance/instanceVariable/${instanceId}`,
method: 'get'
});
};
/**
* 删除
* @param instanceIds 流程实例id
* @returns
*/
export const deleteByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
return request({
url: `/workflow/instance/deleteByInstanceIds/${instanceIds}`,
method: 'delete'
});
};
/**
* 删除历史流程实例
* @param instanceIds
*/
export const deleteHisByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
return request({
url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`,
method: 'delete'
});
};
/**
* 作废流程
* @param data 参数
* @returns
*/
export const invalid = (data: any) => {
return request({
url: `/workflow/instance/invalid`,
method: 'post',
data: data
});
};
/**
* 修改流程变量
* @param data 参数
* @returns
*/
export const updateVariable = (data: any) => {
return request({
url: `/workflow/instance/updateVariable`,
method: 'put',
data: data
});
};

View File

@@ -0,0 +1,28 @@
import type { FlowTaskVO } from '@/api/workflow/task/types';
export interface FlowInstanceQuery extends PageQuery {
category?: string | number;
nodeName?: string;
flowCode?: string;
flowName?: string;
createByIds?: Array<string | number>;
businessId?: string;
}
export interface FlowInstanceVO extends BaseEntity {
id: string | number;
definitionId: string;
flowName: string;
flowCode: string;
version: string;
businessId: string;
activityStatus: number;
tenantId: string;
createTime: string;
createBy: string;
flowStatus: string;
flowStatusName: string;
flowTaskList: FlowTaskVO[];
businessCode: string;
businessTitle: string;
}

View File

@@ -0,0 +1,76 @@
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';
/**
* 查询请假列表
* @param query
* @returns {*}
*/
export const listLeave = (query?: LeaveQuery): AxiosPromise<PageResult<LeaveVO>> => {
return request({
url: '/workflow/leave/list',
method: 'get',
params: query
});
};
/**
* 查询请假详细
* @param id
*/
export const getLeave = (id: string | number): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave/' + id,
method: 'get'
});
};
/**
* 新增请假
* @param data
*/
export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave',
method: 'post',
data: data
});
};
/**
* 提交请假并发起流程
* @param data
*/
export const submitAndFlowStart = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave/submitAndFlowStart',
method: 'post',
data: data
});
};
/**
* 修改请假
* @param data
*/
export const updateLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave',
method: 'put',
data: data
});
};
/**
* 删除请假
* @param id
*/
export const delLeave = (id: string | number | Array<string | number>) => {
return request({
url: '/workflow/leave/' + id,
method: 'delete'
});
};

View File

@@ -0,0 +1,26 @@
export interface LeaveVO {
id: string | number;
applyCode?: string;
leaveType: string;
startDate: string;
endDate: string;
leaveDays: number;
remark: string;
status?: string;
}
export interface LeaveForm extends BaseEntity {
id?: string | number;
applyCode?: string;
leaveType?: string;
startDate?: string;
endDate?: string;
leaveDays?: number;
remark?: string;
status?: string;
}
export interface LeaveQuery extends PageQuery {
startLeaveDays?: number;
endLeaveDays?: number;
}

View File

@@ -0,0 +1,64 @@
import type { PageResult } from '@/api/types';
import type { SpelForm, SpelQuery, SpelVO } from '@/api/workflow/spel/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
/**
* 查询流程spel表达式定义列表
* @param query
* @returns {*}
*/
export const listSpel = (query?: SpelQuery): AxiosPromise<PageResult<SpelVO>> => {
return request({
url: '/workflow/spel/list',
method: 'get',
params: query
});
};
/**
* 查询流程spel表达式定义详细
* @param id
*/
export const getSpel = (id: string | number): AxiosPromise<SpelVO> => {
return request({
url: '/workflow/spel/' + id,
method: 'get'
});
};
/**
* 新增流程spel表达式定义
* @param data
*/
export const addSpel = (data: SpelForm) => {
return request({
url: '/workflow/spel',
method: 'post',
data: data
});
};
/**
* 修改流程spel表达式定义
* @param data
*/
export const updateSpel = (data: SpelForm) => {
return request({
url: '/workflow/spel',
method: 'put',
data: data
});
};
/**
* 删除流程spel表达式定义
* @param id
*/
export const delSpel = (id: string | number | Array<string | number>) => {
return request({
url: '/workflow/spel/' + id,
method: 'delete'
});
};

View File

@@ -0,0 +1,105 @@
export interface SpelVO {
/**
* 主键id
*/
id: string | number;
/**
* 组件名称
*/
componentName: string;
/**
* 方法名
*/
methodName: string;
/**
* 参数
*/
methodParams: string;
/**
* 预览spel值
*/
viewSpel: string;
/**
* 状态(0正常 1停用)
*/
status: string;
/**
* 备注
*/
remark?: string;
}
export interface SpelForm extends BaseEntity {
/**
* 主键id
*/
id?: string | number;
/**
* 组件名称
*/
componentName?: string;
/**
* 方法名
*/
methodName?: string;
/**
* 参数
*/
methodParams?: string;
/**
* 预览spel值
*/
viewSpel?: string;
/**
* 状态(0正常 1停用)
*/
status?: string;
/**
* 备注
*/
remark?: string;
}
export interface SpelQuery extends PageQuery {
/**
* 组件名称
*/
componentName?: string;
/**
* 方法名
*/
methodName?: string;
/**
* 参数
*/
methodParams?: string;
/**
* 预览spel值
*/
viewSpel?: string;
/**
* 状态(0正常 1停用)
*/
status?: string;
/**
* 日期范围参数
*/
params?: any;
}

View File

@@ -0,0 +1,207 @@
import type { PageResult } from '@/api/types';
import type { FlowTaskVO, TaskOperationBo, TaskQuery } from '@/api/workflow/task/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
/**
* 查询待办列表
* @param query
* @returns {*}
*/
export const pageByTaskWait = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByTaskWait',
method: 'get',
params: query
});
};
/**
* 查询已办列表
* @param query
* @returns {*}
*/
export const pageByTaskFinish = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByTaskFinish',
method: 'get',
params: query
});
};
/**
* 查询当前用户的抄送列表
* @param query
* @returns {*}
*/
export const pageByTaskCopy = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByTaskCopy',
method: 'get',
params: query
});
};
/**
* 查询全部待办任务
* @param query
* @returns {*}
*/
export const pageByAllTaskWait = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByAllTaskWait',
method: 'get',
params: query
});
};
/**
* 查询全部已办任务
* @param query
* @returns {*}
*/
export const pageByAllTaskFinish = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
return request({
url: '/workflow/task/pageByAllTaskFinish',
method: 'get',
params: query
});
};
/**
* 启动流程
* @param data
* @returns {*}
*/
export const startWorkFlow = (data: object): any => {
return request({
url: '/workflow/task/startWorkFlow',
method: 'post',
data: data
});
};
/**
* 办理流程
* @param data
* @returns {*}
*/
export const completeTask = (data: object) => {
return request({
url: '/workflow/task/completeTask',
method: 'post',
data: data
});
};
/**
* 任务驳回
* @param data
* @returns {*}
*/
export const backProcess = (data: any): any => {
return request({
url: '/workflow/task/backProcess',
method: 'post',
data: data
});
};
/**
* 获取当前任务
* @param taskId
* @returns
*/
export const getTask = (taskId: string) => {
return request({
url: '/workflow/task/getTask/' + taskId,
method: 'get'
});
};
/**
* 修改任务办理人
* @param taskIdList
* @param userId
* @returns
*/
export const updateAssignee = (taskIdList: Array<string>, userId: string) => {
return request({
url: `/workflow/task/updateAssignee/${userId}`,
method: 'put',
data: taskIdList
});
};
/**
* 终止任务
* @returns
*/
export const terminationTask = (data: any) => {
return request({
url: `/workflow/task/terminationTask`,
method: 'post',
data: data
});
};
/**
* 获取可驳回得任务节点
* @returns
*/
export const getBackTaskNode = (taskId: string | number, nodeCode: string) => {
return request({
url: `/workflow/task/getBackTaskNode/${taskId}/${nodeCode}`,
method: 'get'
});
};
/**
* 任务操作 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature
* @returns
*/
export const taskOperation = (data: TaskOperationBo, operation: string) => {
return request({
url: `/workflow/task/taskOperation/${operation}`,
method: 'post',
data: data
});
};
/**
* 获取当前任务办理人
* @param taskId 任务id
* @returns
*/
export const currentTaskAllUser = (taskId: string | number) => {
return request({
url: `/workflow/task/currentTaskAllUser/${taskId}`,
method: 'get'
});
};
/**
* 获取下一节点写
* @param data参数
* @returns
*/
export const getNextNodeList = (data: any): any => {
return request({
url: '/workflow/task/getNextNodeList',
method: 'post',
data: data
});
};
/**
* 催办任务
* @param data参数
* @returns
*/
export const urgeTask = (data: any): any => {
return request({
url: '/workflow/task/urgeTask',
method: 'post',
data: data
});
};

View File

@@ -0,0 +1,60 @@
export interface TaskQuery extends PageQuery {
nodeName?: string;
flowCode?: string;
flowName?: string;
createByIds?: Array<string | number>;
}
export interface ParticipantVo {
groupIds?: string[] | number[];
candidate: string[] | number[];
candidateName: string[];
claim: boolean;
}
export interface FlowTaskVO {
id: string | number;
createTime?: Date;
updateTime?: Date;
tenantId?: string;
definitionId?: string;
instanceId: string;
flowName: string;
businessId: string;
nodeCode: string;
nodeName: string;
flowCode: string;
flowStatus: string;
formCustom: string;
formPath: string;
nodeType: number;
nodeRatio: string | number;
version?: string;
applyNode?: boolean;
buttonList?: ButtonList[];
copyList?: FlowCopyVo[];
varList?: Map<string, string>;
businessCode: string;
businessTitle: string;
}
export interface ButtonList {
code: string;
show: boolean;
}
export interface FlowCopyVo {
userId: string | number;
nickName: string;
}
export interface TaskOperationBo {
//委派/转办人的用户ID(必填,准对委派/转办人操作)
userId?: string;
//加签/减签人的用户ID列表(必填,针对加签/减签操作)
userIds?: string[];
//任务ID(必填)
taskId: string | number;
//消息类型
messageType?: string[];
//意见或备注信息(可选)
message?: string;
}

View File

@@ -0,0 +1,17 @@
import type { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
import tab from '@/plugins/tab';
import router from '@/router';
export default {
routerJump(routerJumpVo: RouterJumpVo) {
tab.closePage(router.currentRoute.value);
router.push({
path: routerJumpVo.formPath,
query: {
id: routerJumpVo.businessId,
type: routerJumpVo.type,
taskId: routerJumpVo.taskId
}
});
}
};

View File

@@ -0,0 +1,14 @@
export interface RouterJumpVo {
businessId: string;
taskId: string | number;
type: string;
formCustom: string;
formPath: string;
}
export interface StartProcessBo {
businessId: string | number;
flowCode: string;
variables: any;
bizExt: any;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M127.88 73.143c0 1.412-.506 2.635-1.518 3.669-1.011 1.033-2.209 1.55-3.592 1.55h-17.887c0 9.296-1.783 17.178-5.35 23.645l16.609 17.044c1.011 1.034 1.517 2.257 1.517 3.67 0 1.412-.506 2.635-1.517 3.668-.958 1.033-2.155 1.55-3.593 1.55-1.438 0-2.635-.517-3.593-1.55l-15.811-16.063a15.49 15.49 0 0 1-1.196 1.06c-.532.434-1.65 1.208-3.353 2.322a50.104 50.104 0 0 1-5.192 2.974c-1.758.87-3.94 1.658-6.546 2.364-2.607.706-5.189 1.06-7.748 1.06V47.044H58.89v73.062c-2.716 0-5.417-.367-8.106-1.102-2.688-.734-5.003-1.631-6.945-2.692a66.769 66.769 0 0 1-5.268-3.179c-1.571-1.057-2.73-1.94-3.476-2.65L33.9 109.34l-14.611 16.877c-1.066 1.14-2.344 1.711-3.833 1.711-1.277 0-2.422-.434-3.434-1.304-1.012-.978-1.557-2.187-1.635-3.627-.079-1.44.333-2.705 1.236-3.794l16.129-18.51c-3.087-6.197-4.63-13.644-4.63-22.342H5.235c-1.383 0-2.58-.517-3.592-1.55S.125 74.545.125 73.132c0-1.412.506-2.635 1.518-3.668 1.012-1.034 2.21-1.55 3.592-1.55h17.887V43.939L9.308 29.833c-1.012-1.033-1.517-2.256-1.517-3.669 0-1.412.505-2.635 1.517-3.668 1.012-1.034 2.21-1.55 3.593-1.55s2.58.516 3.593 1.55l13.813 14.106h67.396l13.814-14.106c1.012-1.034 2.21-1.55 3.592-1.55 1.384 0 2.581.516 3.593 1.55 1.012 1.033 1.518 2.256 1.518 3.668 0 1.413-.506 2.636-1.518 3.67l-13.814 14.105v23.975h17.887c1.383 0 2.58.516 3.593 1.55 1.011 1.033 1.517 2.256 1.517 3.668l-.005.01zM89.552 26.175H38.448c0-7.23 2.489-13.386 7.466-18.469C50.892 2.623 56.92.082 64 .082c7.08 0 13.108 2.541 18.086 7.624 4.977 5.083 7.466 11.24 7.466 18.469z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1568899741379" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2054" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M960 591.424V368.96c0-0.288 0.16-0.512 0.16-0.768S960 367.68 960 367.424V192a32 32 0 0 0-32-32H96a32 32 0 0 0-32 32v175.424c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768v222.464c0 0.288-0.16 0.512-0.16 0.768s0.16 0.48 0.16 0.768V864a32 32 0 0 0 32 32h832a32 32 0 0 0 32-32v-271.04c0-0.288 0.16-0.512 0.16-0.768S960 591.68 960 591.424z m-560-31.232v-160H608v160h-208z m208 64V832h-208v-207.808H608z m-480-224h208v160H128v-160z m544 0h224v160h-224v-160zM896 224v112.192H128V224h768zM128 624.192h208V832H128v-207.808zM672 832v-207.808h224V832h-224z" p-id="2055"></path></svg>

After

Width:  |  Height:  |  Size: 954 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1588670460195" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1314" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M230.4 307.712c13.824 0 25.088-11.264 25.088-25.088 0-100.352 81.92-182.272 182.272-182.272s182.272 81.408 182.272 182.272c0 13.824 11.264 25.088 25.088 25.088s25.088-11.264 24.576-25.088c0-127.488-103.936-231.936-231.936-231.936S205.824 154.624 205.824 282.624c-0.512 14.336 10.752 25.088 24.576 25.088z m564.736 234.496c-11.264 0-21.504 2.048-31.232 6.144 0-44.544-40.448-81.92-88.064-81.92-14.848 0-28.16 3.584-39.936 10.24-13.824-28.16-44.544-48.128-78.848-48.128-12.288 0-24.576 2.56-35.328 7.68V284.16c0-45.568-37.888-81.92-84.48-81.92s-84.48 36.864-84.48 81.92v348.672l-69.12-112.64c-18.432-28.16-58.368-36.864-91.136-19.968-26.624 14.336-46.592 47.104-30.208 88.064 3.072 8.192 76.8 205.312 171.52 311.296 0 0 28.16 24.576 43.008 58.88 4.096 9.728 13.312 15.36 22.528 15.36 3.072 0 6.656-0.512 9.728-2.048 12.288-5.12 18.432-19.968 12.8-32.256-19.456-44.544-53.76-74.752-53.76-74.752C281.6 768 209.408 573.44 208.384 570.88c-5.12-12.8-2.56-20.992 7.168-26.112 9.216-4.608 21.504-4.608 26.112 2.56l113.152 184.32c4.096 8.704 12.8 14.336 22.528 14.336 13.824 0 25.088-10.752 25.088-25.088V284.16c0-17.92 15.36-32.256 34.816-32.256s34.816 14.336 34.816 32.256v284.16c0 13.824 10.24 25.088 24.576 25.088 13.824 0 25.088-11.264 25.088-25.088v-57.344c0-17.92 15.36-32.768 34.816-32.768 19.968 0 37.376 15.36 37.376 32.768v95.232c0 7.168 3.072 13.312 7.68 17.92 4.608 4.608 10.752 7.168 17.92 7.168 13.824 0 24.576-11.264 24.576-25.088V547.84c0-18.432 13.824-32.256 32.256-32.256 20.48 0 38.912 15.36 38.912 32.256v95.232c0 13.824 11.264 25.088 25.088 25.088s24.576-11.264 25.088-25.088v-18.944c0-18.944 12.8-32.256 30.72-32.256 18.432 0 22.528 18.944 22.528 31.744 0 1.024-11.776 99.84-50.688 173.056-30.72 58.368-45.056 112.128-51.2 146.944-2.56 13.312 6.656 26.112 19.968 28.672 1.536 0 3.072 0.512 4.608 0.512 11.776 0 22.016-8.192 24.064-20.48 5.632-31.232 18.432-79.36 46.08-132.608 43.52-81.92 55.808-186.88 56.32-193.536-0.512-50.688-29.696-83.968-72.704-83.968z"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M321.94 98L158.82 237.78a24 24 0 000 36.44L321.94 414c15.57 13.34 39.62 2.28 39.62-18.22v-279.6c0-20.5-24.05-31.56-39.62-18.18z"/></svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512"><path d="M190.06 414l163.12-139.78a24 24 0 000-36.44L190.06 98c-15.57-13.34-39.62-2.28-39.62 18.22v279.6c0 20.5 24.05 31.56 39.62 18.18z"/></svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1576153230908" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="971" xmlns:xlink="http://www.w3.org/1999/xlink" width="81" height="81"><defs><style type="text/css"></style></defs><path d="M772.87036133 734.06115723c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714843H475.90991211c-56.60705567 0-102.66723633-46.06018067-102.66723633-102.66723633V600.82446289h305.859375c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012S827.9942627 467.50537109 772.87036133 467.50537109c-43.34106445 0-80.00793458 27.93273926-93.76831055 66.57714844H373.24267578V401.01062011h321.92687989c55.12390137 0 99.94812012-44.82421875 99.94812011-99.94812011V190.07312011C795.11767578 134.94921875 750.29345703 90.125 695.16955567 90.125H251.12963867C196.0057373 90.125 151.18151855 134.94921875 151.18151855 190.07312011V301.0625c0 55.12390137 44.82421875 99.94812012 99.94812012 99.94812012h55.53588867v296.96044921c0 93.35632325 75.97045898 169.32678223 169.32678224 169.32678223h203.19213866c13.76037598 38.64440918 50.42724609 66.57714844 93.76831055 66.57714844 55.12390137 0 99.94812012-44.82421875 99.94812012-99.94812012s-44.90661622-99.86572266-100.03051758-99.86572265z m0-199.89624024c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857423s-14.91394043 33.28857422-33.28857422 33.28857421-33.28857422-14.91394043-33.28857422-33.28857421 14.91394043-33.28857422 33.28857422-33.28857422zM217.75866699 301.0625V190.07312011c0-18.37463379 14.91394043-33.28857422 33.28857423-33.28857421h444.03991698c18.37463379 0 33.28857422 14.91394043 33.28857422 33.28857422V301.0625c0 18.37463379-14.91394043 33.28857422-33.28857422 33.28857422H251.12963867c-18.37463379 0-33.37097168-14.91394043-33.37097168-33.28857422z m555.11169434 566.23535156c-18.37463379 0-33.28857422-14.91394043-33.28857422-33.28857422 0-18.37463379 14.91394043-33.28857422 33.28857422-33.28857422s33.28857422 14.91394043 33.28857422 33.28857422c0.08239747 18.29223633-14.91394043 33.28857422-33.28857422 33.28857422z" p-id="972"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1 @@
<svg t="1715954426124" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3305" width="200" height="200"><path d="M664.081597 1023.943114a78.246037 78.246037 0 0 1-78.985549-76.795456v-284.996471a78.27448 78.27448 0 0 1 78.985549-76.93767h280.843828A78.189152 78.189152 0 0 1 1023.939417 662.151187v284.996471a78.246037 78.246037 0 0 1-79.013992 76.795456z m-585.067605 0a78.246037 78.246037 0 0 1-78.985549-76.795456v-284.996471a78.160709 78.160709 0 0 1 78.985549-76.93767h280.786942a78.302923 78.302923 0 0 1 79.042434 76.93767v284.996471h-0.170656a78.246037 78.246037 0 0 1-78.985549 76.795456z m0-585.096048a78.217594 78.217594 0 0 1-78.985549-76.93767V76.912925a78.189152 78.189152 0 0 1 78.957106-76.795456h280.786942a78.27448 78.27448 0 0 1 79.042435 76.93767v284.996471a78.27448 78.27448 0 0 1-79.013992 76.795456z m589.675333-5.688552a77.193655 77.193655 0 0 1-77.990052-75.885288V75.888985a77.25054 77.25054 0 0 1 77.990052-75.942173h277.26004a77.25054 77.25054 0 0 1 77.961609 75.942173v281.384241a77.421197 77.421197 0 0 1-78.132266 75.885288z" p-id="3306" fill="currentColor"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

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