Compare commits

..

1 Commits

Author SHA1 Message Date
疯狂的狮子Li
2dc094c1db
!212 发布 5.4.0-2.4.0 正式版
Merge pull request !212 from 疯狂的狮子Li/dev
2025-05-29 03:18:30 +00:00
294 changed files with 9447 additions and 27011 deletions

View File

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

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

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

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

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

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

@ -1,126 +0,0 @@
# 使用案例
## 案例 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

@ -1,156 +0,0 @@
# 前端约定
## 优先参考的代码来源
- 默认远端基线:`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 壳或状态管理方式。

View File

@ -1,6 +1,5 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
# 开发环境配置
VITE_APP_ENV = 'development'
@ -17,26 +16,20 @@ VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications'
# SnailJob 控制台地址
VITE_APP_SNAILJOB_ADMIN = 'http://localhost:8800/snail-job'
# SnailAI 控制台地址
VITE_APP_SNAILAI_ADMIN = 'http://localhost:8900/snail-ai'
VITE_APP_PORT = 80
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT = true
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB'
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 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=='
VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
# 客户端id
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
# 统一消息推送开关
VITE_APP_MESSAGE_ENABLED = true
# websocket 开关 默认使用sse推送
VITE_APP_WEBSOCKET = false
# sse / websocket
VITE_APP_MESSAGE_TRANSPORT = 'sse'
# 统一消息推送路径
VITE_APP_MESSAGE_PATH = '/resource/message'
# sse 开关
VITE_APP_SSE = true

View File

@ -1,6 +1,5 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
# 生产环境配置
VITE_APP_ENV = 'production'
@ -14,9 +13,6 @@ 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'
@ -28,18 +24,15 @@ VITE_APP_PORT = 80
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT = true
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvEDuRIOM3oZPWj9Ukoc5pQklR4PFH6/clnjeFqjDLIgDyQvjxhgqAZQA+E9eD6qu6FsXPmK8djcL+nh3cFHz4pX473jDvO3Sve+8yL3VRQ0n2pRgQ2a01MJsy+WwTZCBYWf0VnLRIvANUoWQgy9vz94q7Va44dg7A1/3ICf+xAwIDAQAB'
VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 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=='
VITE_APP_RSA_PRIVATE_KEY = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE='
# 客户端id
VITE_APP_CLIENT_ID = 'e5cd7e4891bf95d1d19206ce24a7b32e'
# 统一消息推送开关
VITE_APP_MESSAGE_ENABLED = true
# websocket 开关 默认使用sse推送
VITE_APP_WEBSOCKET = false
# sse / websocket
VITE_APP_MESSAGE_TRANSPORT = 'sse'
# 统一消息推送路径
VITE_APP_MESSAGE_PATH = '/resource/message'
# sse 开关
VITE_APP_SSE = true

323
.eslintrc-auto-import.json Normal file
View File

@ -0,0 +1,323 @@
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"DirectiveBinding": true,
"EffectScope": true,
"ElLoading": true,
"ElMessage": true,
"ElMessageBox": true,
"ElNotification": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"VNode": true,
"WritableComputedRef": true,
"acceptHMRUpdate": true,
"asyncComputed": true,
"autoResetRef": true,
"computed": true,
"computedAsync": true,
"computedEager": true,
"computedInject": true,
"computedWithControl": true,
"controlledComputed": true,
"controlledRef": true,
"createApp": true,
"createEventHook": true,
"createGlobalState": true,
"createInjectionState": true,
"createPinia": true,
"createReactiveFn": true,
"createReusableTemplate": true,
"createSharedComposable": true,
"createTemplatePromise": true,
"createUnrefFn": true,
"customRef": true,
"debouncedRef": true,
"debouncedWatch": true,
"defineAsyncComponent": true,
"defineComponent": true,
"defineStore": true,
"eagerComputed": true,
"effectScope": true,
"extendRef": true,
"getActivePinia": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"ignorableWatch": true,
"inject": true,
"injectLocal": true,
"isDefined": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"makeDestructurable": true,
"mapActions": true,
"mapGetters": true,
"mapState": true,
"mapStores": true,
"mapWritableState": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeRouteLeave": true,
"onBeforeRouteUpdate": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onClickOutside": true,
"onDeactivated": true,
"onElementRemoval": true,
"onErrorCaptured": true,
"onKeyStroke": true,
"onLongPress": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onStartTyping": true,
"onUnmounted": true,
"onUpdated": true,
"onWatcherCleanup": true,
"pausableWatch": true,
"provide": true,
"provideLocal": true,
"reactify": true,
"reactifyObject": true,
"reactive": true,
"reactiveComputed": true,
"reactiveOmit": true,
"reactivePick": true,
"readonly": true,
"ref": true,
"refAutoReset": true,
"refDebounced": true,
"refDefault": true,
"refThrottled": true,
"refWithControl": true,
"resolveComponent": true,
"resolveRef": true,
"resolveUnref": true,
"setActivePinia": true,
"setMapStoreSuffix": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"storeToRefs": true,
"syncRef": true,
"syncRefs": true,
"templateRef": true,
"throttledRef": true,
"throttledWatch": true,
"toRaw": true,
"toReactive": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"tryOnBeforeMount": true,
"tryOnBeforeUnmount": true,
"tryOnMounted": true,
"tryOnScopeDispose": true,
"tryOnUnmounted": true,
"unref": true,
"unrefElement": true,
"until": true,
"useActiveElement": true,
"useAnimate": true,
"useArrayDifference": true,
"useArrayEvery": true,
"useArrayFilter": true,
"useArrayFind": true,
"useArrayFindIndex": true,
"useArrayFindLast": true,
"useArrayIncludes": true,
"useArrayJoin": true,
"useArrayMap": true,
"useArrayReduce": true,
"useArraySome": true,
"useArrayUnique": true,
"useAsyncQueue": true,
"useAsyncState": true,
"useAttrs": true,
"useBase64": true,
"useBattery": true,
"useBluetooth": true,
"useBreakpoints": true,
"useBroadcastChannel": true,
"useBrowserLocation": true,
"useCached": true,
"useClipboard": true,
"useClipboardItems": true,
"useCloned": true,
"useColorMode": true,
"useConfirmDialog": true,
"useCountdown": true,
"useCounter": true,
"useCssModule": true,
"useCssVar": true,
"useCssVars": true,
"useCurrentElement": true,
"useCycleList": true,
"useDark": true,
"useDateFormat": true,
"useDebounce": true,
"useDebounceFn": true,
"useDebouncedRefHistory": true,
"useDeviceMotion": true,
"useDeviceOrientation": true,
"useDevicePixelRatio": true,
"useDevicesList": true,
"useDisplayMedia": true,
"useDocumentVisibility": true,
"useDraggable": true,
"useDropZone": true,
"useElementBounding": true,
"useElementByPoint": true,
"useElementHover": true,
"useElementSize": true,
"useElementVisibility": true,
"useEventBus": true,
"useEventListener": true,
"useEventSource": true,
"useEyeDropper": true,
"useFavicon": true,
"useFetch": true,
"useFileDialog": true,
"useFileSystemAccess": true,
"useFocus": true,
"useFocusWithin": true,
"useFps": true,
"useFullscreen": true,
"useGamepad": true,
"useGeolocation": true,
"useId": true,
"useIdle": true,
"useImage": true,
"useInfiniteScroll": true,
"useIntersectionObserver": true,
"useInterval": true,
"useIntervalFn": true,
"useKeyModifier": true,
"useLastChanged": true,
"useLink": true,
"useLocalStorage": true,
"useMagicKeys": true,
"useManualRefHistory": true,
"useMediaControls": true,
"useMediaQuery": true,
"useMemoize": true,
"useMemory": true,
"useModel": true,
"useMounted": true,
"useMouse": true,
"useMouseInElement": true,
"useMousePressed": true,
"useMutationObserver": true,
"useNavigatorLanguage": true,
"useNetwork": true,
"useNow": true,
"useObjectUrl": true,
"useOffsetPagination": true,
"useOnline": true,
"usePageLeave": true,
"useParallax": true,
"useParentElement": true,
"usePerformanceObserver": true,
"usePermission": true,
"usePointer": true,
"usePointerLock": true,
"usePointerSwipe": true,
"usePreferredColorScheme": true,
"usePreferredContrast": true,
"usePreferredDark": true,
"usePreferredLanguages": true,
"usePreferredReducedMotion": true,
"usePreferredReducedTransparency": true,
"usePrevious": true,
"useRafFn": true,
"useRefHistory": true,
"useResizeObserver": true,
"useRoute": true,
"useRouter": true,
"useSSRWidth": true,
"useScreenOrientation": true,
"useScreenSafeArea": true,
"useScriptTag": true,
"useScroll": true,
"useScrollLock": true,
"useSessionStorage": true,
"useShare": true,
"useSlots": true,
"useSorted": true,
"useSpeechRecognition": true,
"useSpeechSynthesis": true,
"useStepper": true,
"useStorage": true,
"useStorageAsync": true,
"useStyleTag": true,
"useSupported": true,
"useSwipe": true,
"useTemplateRef": true,
"useTemplateRefsList": true,
"useTextDirection": true,
"useTextSelection": true,
"useTextareaAutosize": true,
"useThrottle": true,
"useThrottleFn": true,
"useThrottledRefHistory": true,
"useTimeAgo": true,
"useTimeout": true,
"useTimeoutFn": true,
"useTimeoutPoll": true,
"useTimestamp": true,
"useTitle": true,
"useToNumber": true,
"useToString": true,
"useToggle": true,
"useTransition": true,
"useUrlSearchParams": true,
"useUserMedia": true,
"useVModel": true,
"useVModels": true,
"useVibrate": true,
"useVirtualList": true,
"useWakeLock": true,
"useWebNotification": true,
"useWebSocket": true,
"useWebWorker": true,
"useWebWorkerFn": true,
"useWindowFocus": true,
"useWindowScroll": true,
"useWindowSize": true,
"watch": true,
"watchArray": true,
"watchAtMost": true,
"watchDebounced": true,
"watchDeep": true,
"watchEffect": true,
"watchIgnorable": true,
"watchImmediate": true,
"watchOnce": true,
"watchPausable": true,
"watchPostEffect": true,
"watchSyncEffect": true,
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true,
"Slot": true,
"Slots": true,
"createRef": true
}
}

2
.gitignore vendored
View File

@ -14,7 +14,6 @@ selenium-debug.log
# Editor directories and files
.idea
.vscode
*.iml
*.suo
*.ntvs*
*.njsproj
@ -23,6 +22,7 @@ selenium-debug.log
package-lock.json
yarn.lock
pnpm-lock.yaml
# 编译生成的文件
auto-imports.d.ts

View File

@ -1,31 +0,0 @@
{
"$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"
]
}
}

View File

@ -1,25 +0,0 @@
{
"$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/**"]
}

9
.prettierignore Normal file
View File

@ -0,0 +1,9 @@
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*

20
.prettierrc Normal file
View File

@ -0,0 +1,20 @@
{
"printWidth": 150,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "preserve",
"jsxSingleQuote": false,
"bracketSameLine": false,
"trailingComma": "none",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"endOfLine": "auto"
}

View File

@ -1,33 +1,32 @@
## 平台简介
- 本仓库为前端技术栈 [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) |
| 🔥 微服务框架 | 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分支(稳定发布主分支 生产可用)
- ts分支(稳定发布主分支 生产可用)
- dev分支(开发分支 开发过程中使用)
## 前端运行
```bash
# 安装依赖
pnpm install --registry=https://registry.npmmirror.com
npm install --registry=https://registry.npmmirror.com
# 启动服务
pnpm dev
npm run dev
# 构建生产环境
pnpm build:prod
npm run build:prod
# 前端访问地址 http://localhost:80
```
@ -36,6 +35,8 @@ pnpm build:prod
| 业务 | 功能说明 | 本框架 | RuoYi |
| ------------ | ------------------------------------------------------------- | ------ | ----------------------------- |
| 租户管理 | 系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等 | 支持 | 无 |
| 租户套餐管理 | 系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等 | 支持 | 无 |
| 用户管理 | 用户的管理配置 如:新增用户、分配用户所属部门、角色、岗位等 | 支持 | 支持 |
| 部门管理 | 配置系统组织机构(公司、部门、小组) 树结构展现支持数据权限 | 支持 | 支持 |
| 岗位管理 | 配置系统用户所属担任职务 | 支持 | 支持 |
@ -59,18 +60,24 @@ pnpm build:prod
## 演示图例
| | |
|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| ![输入图片说明](https://foruda.gitee.com/images/1780299033689126697/868ef1ea_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299052163404649/8d94165d_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299074949590692/27f5bfb5_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299108816841231/619a7c57_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299089818500856/862ba805_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299173744267947/95cb0cd3_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299193694706123/28257dc1_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299147525013883/ebcd9dfe_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299220007761523/dc7e27c9_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299235966983519/35b047e1_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299250884681522/e5731314_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299267028602229/230d5428_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299293149732467/19abcf6c_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299311267192779/e665c668_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299327888096947/283a177f_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299348897579356/caac864e_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299376680669014/452585fb_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299843158459866/ceebbb63_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299432356918392/07abdf6a_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299465584172180/a2b2be12_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299491233431530/d88bfa35_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299513358913413/f2f90032_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299527419238776/549cb852_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299553918371792/43bd3bff_1766278.png "屏幕截图") |
| ![输入图片说明](https://foruda.gitee.com/images/1780299586662735625/1107a3ee_1766278.png "屏幕截图") | ![输入图片说明](https://foruda.gitee.com/images/1780299613342135530/526d7859_1766278.png "屏幕截图") |
| | |
| ---------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| ![输入图片说明](https://foruda.gitee.com/images/1680077524361362822/270bb429_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680077619939771291/989bf9b6_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680077681751513929/1c27c5bd_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680077721559267315/74d63e23_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680077765638904515/1b75d4a6_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078026375951297/eded7a4b_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078237104531207/0eb1b6a7_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078254306078709/5931e22f_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078287971528493/0b9af60a_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078308138770249/8d3b6696_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078352553634393/db5ef880_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078378238393374/601e4357_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078414983206024/2aae27c1_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078446738419874/ecce7d59_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078475971341775/149e8634_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078491666717143/3fadece7_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078558863188826/fb8ced2a_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078574561685461/ae68a0b2_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078594932772013/9d8bfec6_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078626493093532/fcfe4ff6_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078643608812515/0295bd4f_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078685196286463/d7612c81_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078703877318597/56fce0bc_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078716586545643/b6dbd68f_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078734103217688/eb1e6aa6_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078759131415480/73c525d8_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078779416197879/75e3ed02_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078802329118061/77e10915_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078893627848351/34a1c342_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078928175016986/f126ec4a_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078941718318363/b68a0f72_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680078963175518631/3bb769a1_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680078982294090567/b31c343d_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079000642440444/77ca82a9_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680079020995074177/03b7d52e_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079039367822173/76811806_1766278.png '屏幕截图') |
| ![输入图片说明](https://foruda.gitee.com/images/1680079274333484664/4dfdc7c0_1766278.png '屏幕截图') | ![输入图片说明](https://foruda.gitee.com/images/1680079290467458224/d6715fcf_1766278.png '屏幕截图') |

12
bin/build.bat Normal file
View File

@ -0,0 +1,12 @@
@echo off
echo.
echo [信息] 打包Web工程生成dist文件。
echo.
%~d0
cd %~dp0
cd ..
yarn build:prod
pause

12
bin/package.bat Normal file
View File

@ -0,0 +1,12 @@
@echo off
echo.
echo [信息] 安装Web工程生成node_modules文件。
echo.
%~d0
cd %~dp0
cd ..
yarn --registry=https://registry.npmmirror.com
pause

12
bin/run-web.bat Normal file
View File

@ -0,0 +1,12 @@
@echo off
echo.
echo [信息] 使用 Vite 命令运行 Web 工程。
echo.
%~d0
cd %~dp0
cd ..
yarn dev
pause

44
eslint.config.ts Normal file
View File

@ -0,0 +1,44 @@
import pluginVue from 'eslint-plugin-vue';
import globals from 'globals';
import prettier from 'eslint-plugin-prettier';
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
export default defineConfigWithVueTs(
{
name: 'app/files-to-lint',
files: ['**/*.{js,cjs,ts,mts,tsx,vue}']
},
{
name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**']
},
{
languageOptions: {
globals: globals.browser
}
},
pluginVue.configs['flat/essential'],
vueTsConfigs.recommended,
skipFormatting,
{
plugins: { prettier },
rules: {
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-this-alias': 'off',
// vue
'vue/multi-word-component-names': 'off',
'vue/valid-define-props': 'off',
'vue/no-v-model-argument': 'off',
'prefer-rest-params': 'off',
// prettier
'prettier/prettier': 'error',
// 允许使用空Object类型 {}
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-unused-expressions': 'off'
}
}
);

View File

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

View File

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

View File

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

View File

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

View File

@ -144,8 +144,7 @@
font-size: 14px;
line-height: 24px;
color: #454545;
font-family:
'Microsoft YaHei UI', 'Microsoft YaHei', DengXian, SimSun, 'Segoe UI', Tahoma, Helvetica, sans-serif;
font-family: 'Microsoft YaHei UI', 'Microsoft YaHei', DengXian, SimSun, 'Segoe UI', Tahoma, Helvetica, sans-serif;
overflow-y: scroll;
}
h1 {
@ -209,18 +208,15 @@
</head>
<body style="margin-top: 50px">
<h1>请升级您的浏览器,以便我们更好的为您提供服务!</h1>
<p>
您正在使用 Internet Explorer
的早期版本IE11以下版本或使用该内核的浏览器。这意味着在升级浏览器前您将无法访问此网站。
</p>
<p>您正在使用 Internet Explorer 的早期版本IE11以下版本或使用该内核的浏览器。这意味着在升级浏览器前您将无法访问此网站。</p>
<hr />
<h2>请注意微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束</h2>
<p>
自 2016 年 1 月 12 日起Microsoft 不再为 IE 11
以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅
<a href="https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support">
微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明
</a>
<a href="https://www.microsoft.com/zh-cn/WindowsForBusiness/End-of-IE-support"
>微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明</a
>
</p>
<hr />
@ -228,28 +224,16 @@
<p>推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。</p>
<ul class="browser">
<li class="browser-chrome">
<a href="https://www.google.cn/chrome/browser/desktop/index.html?hl=zh-CN&standalone=1">
谷歌浏览器
<span>Google Chrome</span>
</a>
<a href="https://www.google.cn/chrome/browser/desktop/index.html?hl=zh-CN&standalone=1"> 谷歌浏览器<span>Google Chrome</span></a>
</li>
<li class="browser-firefox">
<a href="https://www.mozilla.org/zh-CN/firefox/new/">
火狐浏览器
<span>Mozilla Firefox</span>
</a>
<a href="https://www.mozilla.org/zh-CN/firefox/new/"> 火狐浏览器<span>Mozilla Firefox</span></a>
</li>
<li class="browser-ie">
<a href="https://windows.microsoft.com/zh-cn/internet-explorer/download-ie">
IE 11 浏览器
<span>Internet Explorer</span>
</a>
<a href="https://windows.microsoft.com/zh-cn/internet-explorer/download-ie"> IE 11 浏览器<span>Internet Explorer</span></a>
</li>
<li class="browser-360">
<a href="http://se.360.cn/">
360安全浏览器
<span>360 Chrome</span>
</a>
<a href="http://se.360.cn/"> 360安全浏览器<span>360 Chrome</span></a>
</li>
<div class="clean"></div>
</ul>

View File

@ -6,12 +6,12 @@
<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]-->
<title>RuoYi-Vue-Plus多租户管理系统</title>
<!--[if lt IE 11
]><script>
window.location.href = '/html/ie.html';
</script><!
[endif]-->
<style>
html,
body,

View File

@ -1,82 +1,96 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "ruoyi-vue-plus",
"version": "6.0.0",
"description": "RuoYi-Vue-Plus后台管理系统",
"license": "MIT",
"version": "5.4.0-2.4.0",
"description": "RuoYi-Vue-Plus多租户管理系统",
"author": "LionLi",
"license": "MIT",
"type": "module",
"scripts": {
"dev": "vite serve --mode development",
"build:prod": "vite build --mode production",
"build:dev": "vite build --mode development",
"preview": "vite preview",
"lint:eslint": "eslint",
"lint:eslint:fix": "eslint --fix",
"prettier": "prettier --write ."
},
"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",
"@element-plus/icons-vue": "2.3.1",
"@highlightjs/vue-plugin": "2.1.0",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "13.1.0",
"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",
"axios": "1.8.4",
"crypto-js": "4.2.0",
"echarts": "5.6.0",
"element-plus": "2.9.8",
"file-saver": "2.0.5",
"highlight.js": "11.9.0",
"image-conversion": "2.1.1",
"jsencrypt": "3.5.4",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"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"
"pinia": "3.0.2",
"screenfull": "6.0.2",
"vue": "3.5.13",
"vue-cropper": "1.1.1",
"vue-i18n": "11.1.3",
"vue-json-pretty": "2.4.0",
"vue-router": "4.5.0",
"vue-types": "6.0.0",
"vxe-table": "4.13.7"
},
"devDependencies": {
"@types/node": "^26.1.1",
"@iconify/json": "^2.2.276",
"@types/crypto-js": "4.2.2",
"@types/file-saver": "2.0.7",
"@types/js-cookie": "3.0.6",
"@types/node": "^22.13.4",
"@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",
"@unocss/preset-attributify": "66.0.0",
"@unocss/preset-icons": "66.0.0",
"@unocss/preset-uno": "66.0.0",
"@vitejs/plugin-vue": "5.2.3",
"@vue/compiler-sfc": "3.5.13",
"@vue/eslint-config-prettier": "10.2.0",
"@vue/eslint-config-typescript": "14.4.0",
"autoprefixer": "10.4.20",
"eslint": "9.21.0",
"eslint-plugin-prettier": "5.2.3",
"eslint-plugin-vue": "9.32.0",
"globals": "16.0.0",
"prettier": "3.5.2",
"sass": "1.87.0",
"typescript": "~5.8.3",
"unocss": "66.0.0",
"unplugin-auto-import": "19.1.2",
"unplugin-icons": "22.1.0",
"unplugin-vue-components": "28.5.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"
"vite": "6.3.2",
"vite-plugin-compression": "0.5.1",
"vite-plugin-svg-icons-ng": "^1.4.0",
"vite-plugin-vue-devtools": "7.7.5",
"vitest": "3.1.2",
"vue-tsc": "^2.2.8"
},
"overrides": {
"quill": "2.0.2"
},
"engines": {
"node": ">=18.18.0",
"npm": ">=8.9.0"
},
"browserslist": [
"Chrome >= 87",
"Edge >= 88",
"Safari >= 14",
"Firefox >= 78"
],
"engines": {
"node": ">=20.19.0",
"pnpm": ">=10.0.0"
}
]
}

4906
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -5,9 +5,9 @@
</template>
<script setup lang="ts">
import { useAppStore } from '@/store/modules/app';
import { useSettingsStore } from '@/store/modules/settings';
import { handleThemeStyle } from '@/utils/theme';
import { useAppStore } from '@/store/modules/app';
const appStore = useAppStore();

View File

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

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

View File

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

View File

@ -1,14 +1,13 @@
import type { DemoForm, DemoQuery, DemoVO } from '@/api/demo/demo/types';
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DemoVO, DemoForm, DemoQuery } from '@/api/demo/demo/types';
/**
*
* @param query
* @returns {*}
*/
export const listDemo = (query?: DemoQuery): AxiosPromise<PageResult<DemoVO>> => {
export const listDemo = (query?: DemoQuery): AxiosPromise<DemoVO[]> => {
return request({
url: '/demo/demo/list',
method: 'get',

View File

@ -1,6 +1,6 @@
import type { TreeForm, TreeQuery, TreeVO } from '@/api/demo/tree/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { TreeVO, TreeForm, TreeQuery } from '@/api/demo/tree/types';
/**
*

View File

@ -1,8 +1,7 @@
import type { UserInfo } from '@/api/system/user/types';
import type { AxiosPromise } from '@/utils/api-types';
import { closePush } from '@/utils/push';
import request from '@/utils/request';
import type { LoginData, LoginResult, VerifyCodeResult } from './types';
import { AxiosPromise } from 'axios';
import { LoginData, LoginResult, VerifyCodeResult, TenantInfo } from './types';
import { UserInfo } from '@/api/system/user/types';
// pc端固定客户端授权id
const clientId = import.meta.env.VITE_APP_CLIENT_ID;
@ -52,13 +51,9 @@ export function register(data: any) {
*
*/
export function logout() {
closePush();
if (
import.meta.env.VITE_APP_MESSAGE_ENABLED === 'true' &&
import.meta.env.VITE_APP_MESSAGE_TRANSPORT.toLowerCase() === 'sse'
) {
if (import.meta.env.VITE_APP_SSE === 'true') {
request({
url: import.meta.env.VITE_APP_MESSAGE_PATH + '/close',
url: '/resource/sse/close',
method: 'get'
});
}
@ -105,3 +100,14 @@ export function getInfo(): AxiosPromise<UserInfo> {
method: 'get'
});
}
// 获取租户列表
export function getTenantList(isToken: boolean): AxiosPromise<TenantInfo> {
return request({
url: '/auth/tenant/list',
headers: {
isToken: isToken
},
method: 'get'
});
}

View File

@ -1,6 +1,6 @@
import type { RouteRecordRaw } from 'vue-router';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { RouteRecordRaw } from 'vue-router';
// 获取路由
export function getRouters(): AxiosPromise<RouteRecordRaw[]> {

View File

@ -1,6 +1,6 @@
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { CacheVO } from './types';
import { AxiosPromise } from 'axios';
import { CacheVO } from './types';
// 查询缓存详细
export function getCache(): AxiosPromise<CacheVO> {
@ -10,3 +10,50 @@ export function getCache(): AxiosPromise<CacheVO> {
});
}
// 查询缓存名称列表
export function listCacheName() {
return request({
url: '/monitor/cache/getNames',
method: 'get'
});
}
// 查询缓存键名列表
export function listCacheKey(cacheName: string) {
return request({
url: '/monitor/cache/getKeys/' + cacheName,
method: 'get'
});
}
// 查询缓存内容
export function getCacheValue(cacheName: string, cacheKey: string) {
return request({
url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
method: 'get'
});
}
// 清理指定名称缓存
export function clearCacheName(cacheName: string) {
return request({
url: '/monitor/cache/clearCacheName/' + cacheName,
method: 'delete'
});
}
// 清理指定键名缓存
export function clearCacheKey(cacheName: string, cacheKey: string) {
return request({
url: '/monitor/cache/clearCacheKey/' + cacheName + '/' + cacheKey,
method: 'delete'
});
}
// 清理全部缓存
export function clearCacheAll() {
return request({
url: '/monitor/cache/clearCacheAll',
method: 'delete'
});
}

View File

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

View File

@ -1,10 +1,9 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { OnlineQuery, OnlineVO } from './types';
import { OnlineQuery, OnlineVO } from './types';
import { AxiosPromise } from 'axios';
// 查询在线用户列表
export function list(query: OnlineQuery): AxiosPromise<PageResult<OnlineVO>> {
export function list(query: OnlineQuery): AxiosPromise<OnlineVO[]> {
return request({
url: '/monitor/online/list',
method: 'get',

View File

@ -1,10 +1,9 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { OperLogQuery, OperLogVO } from './types';
import { OperLogQuery, OperLogVO } from './types';
import { AxiosPromise } from 'axios';
// 查询操作日志列表
export function list(query: OperLogQuery): AxiosPromise<PageResult<OperLogVO>> {
export function list(query: OperLogQuery): AxiosPromise<OperLogVO[]> {
return request({
url: '/monitor/operlog/list',
method: 'get',

View File

@ -2,12 +2,6 @@ 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;
@ -24,13 +18,7 @@ export interface OperLogVO extends BaseEntity {
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;
@ -52,13 +40,7 @@ export interface OperLogForm {
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;

View File

@ -1,7 +1,6 @@
import type { ClientForm, ClientQuery, ClientVO } from '@/api/system/client/types';
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { ClientVO, ClientForm, ClientQuery } from '@/api/system/client/types';
/**
*
@ -9,7 +8,7 @@ import request from '@/utils/request';
* @returns {*}
*/
export const listClient = (query?: ClientQuery): AxiosPromise<PageResult<ClientVO>> => {
export const listClient = (query?: ClientQuery): AxiosPromise<ClientVO[]> => {
return request({
url: '/system/client/list',
method: 'get',

View File

@ -29,26 +29,6 @@ export interface ClientVO {
*/
deviceType: string;
/**
* 访
*/
accessPath?: string;
/**
* 访
*/
accessPathList?: string[];
/**
* IP白名单
*/
ipWhitelist?: string;
/**
* IP白名单列表
*/
ipWhitelistList?: string[];
/**
* token活跃超时时间
*/
@ -96,26 +76,6 @@ export interface ClientForm extends BaseEntity {
*/
deviceType?: string;
/**
* 访
*/
accessPath?: string;
/**
* 访
*/
accessPathList?: string[];
/**
* IP白名单
*/
ipWhitelist?: string;
/**
* IP白名单列表
*/
ipWhitelistList?: string[];
/**
* token活跃超时时间
*/
@ -158,16 +118,6 @@ export interface ClientQuery extends PageQuery {
*/
deviceType?: string;
/**
* 访
*/
accessPath?: string;
/**
* IP白名单
*/
ipWhitelist?: string;
/**
* token活跃超时时间
*/

View File

@ -1,10 +1,9 @@
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';
import { ConfigForm, ConfigQuery, ConfigVO } from './types';
import { AxiosPromise } from 'axios';
// 查询参数列表
export function listConfig(query: ConfigQuery): AxiosPromise<PageResult<ConfigVO>> {
export function listConfig(query: ConfigQuery): AxiosPromise<ConfigVO[]> {
return request({
url: '/system/config/list',
method: 'get',

View File

@ -1,6 +1,6 @@
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { type DeptForm, type DeptQuery, type DeptVO } from './types';
import { AxiosPromise } from 'axios';
import { DeptForm, DeptQuery, DeptTreeVO, DeptVO } from './types';
// 查询部门列表
export const listDept = (query?: DeptQuery) => {
@ -38,6 +38,14 @@ export const getDept = (deptId: string | number): AxiosPromise<DeptVO> => {
});
};
// 查询部门下拉树结构
export const treeselect = (): AxiosPromise<DeptTreeVO[]> => {
return request({
url: '/system/dept/treeselect',
method: 'get'
});
};
// 新增部门
export const addDept = (data: DeptForm) => {
return request({

View File

@ -1,7 +1,6 @@
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';
import { AxiosPromise } from 'axios';
import { DictDataForm, DictDataQuery, DictDataVO } from './types';
// 根据字典类型查询字典数据信息
export function getDicts(dictType: string): AxiosPromise<DictDataVO[]> {
return request({
@ -11,7 +10,7 @@ export function getDicts(dictType: string): AxiosPromise<DictDataVO[]> {
}
// 查询字典数据列表
export function listData(query: DictDataQuery): AxiosPromise<PageResult<DictDataVO>> {
export function listData(query: DictDataQuery): AxiosPromise<DictDataVO[]> {
return request({
url: '/system/dict/data/list',
method: 'get',

View File

@ -1,10 +1,9 @@
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';
import { DictTypeForm, DictTypeVO, DictTypeQuery } from './types';
import { AxiosPromise } from 'axios';
// 查询字典类型列表
export function listType(query: DictTypeQuery): AxiosPromise<PageResult<DictTypeVO>> {
export function listType(query: DictTypeQuery): AxiosPromise<DictTypeVO[]> {
return request({
url: '/system/dict/type/list',
method: 'get',

View File

@ -1,6 +1,6 @@
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { MenuForm, MenuQuery, MenuTreeOption, MenuVO, RoleMenuTree } from './types';
import { AxiosPromise } from 'axios';
import { MenuQuery, MenuVO, MenuForm, MenuTreeOption, RoleMenuTree } from './types';
// 查询菜单列表
export const listMenu = (query?: MenuQuery): AxiosPromise<MenuVO[]> => {
@ -35,6 +35,14 @@ export const roleMenuTreeselect = (roleId: string | number): AxiosPromise<RoleMe
});
};
// 根据角色ID查询菜单下拉树结构
export const tenantPackageMenuTreeselect = (packageId: string | number): AxiosPromise<RoleMenuTree> => {
return request({
url: '/system/menu/tenantPackageMenuTreeselect/' + packageId,
method: 'get'
});
};
// 新增菜单
export const addMenu = (data: MenuForm) => {
return request({

View File

@ -1,4 +1,4 @@
import type { MenuTypeEnum } from '@/enums/MenuTypeEnum';
import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
/**
*
@ -8,28 +8,12 @@ export interface MenuTreeOption {
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;
checkedKeys: string[];
}
/**
@ -60,8 +44,6 @@ export interface MenuVO extends BaseEntity {
visible: string;
status: string;
icon: string;
activeMenu: string;
ext: string;
remark: string;
}
@ -81,8 +63,6 @@ export interface MenuForm {
visible?: string;
status?: string;
icon?: string;
activeMenu?: string;
ext?: string;
remark?: string;
query?: string;
perms?: string;

View File

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

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

@ -1,9 +1,8 @@
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';
import { NoticeForm, NoticeQuery, NoticeVO } from './types';
import { AxiosPromise } from 'axios';
// 查询公告列表
export function listNotice(query: NoticeQuery): AxiosPromise<PageResult<NoticeVO>> {
export function listNotice(query: NoticeQuery): AxiosPromise<NoticeVO[]> {
return request({
url: '/system/notice/list',
method: 'get',

View File

@ -1,10 +1,9 @@
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import type { OssQuery, OssUploadVO, OssVO } from './types';
import { OssQuery, OssVO } from './types';
import { AxiosPromise } from 'axios';
// 查询OSS对象存储列表
export function listOss(query: OssQuery): AxiosPromise<PageResult<OssVO>> {
export function listOss(query: OssQuery): AxiosPromise<OssVO[]> {
return request({
url: '/resource/oss/list',
method: 'get',
@ -20,15 +19,6 @@ export function listByIds(ossId: string | number): AxiosPromise<OssVO[]> {
});
}
// 上传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({

View File

@ -8,12 +8,6 @@ export interface OssVO extends BaseEntity {
service: string;
}
export interface OssUploadVO {
url: string;
fileName: string;
ossId: string;
}
export interface OssQuery extends PageQuery {
fileName: string;
originalName: string;
@ -26,17 +20,3 @@ export interface OssQuery extends PageQuery {
export interface OssForm {
file: undefined | string;
}
export interface SysOssExt {
bizType?: string;
fileSize?: number;
contentType?: string;
source?: string;
uploadIp?: string;
remark?: string;
tags?: string[];
refId?: string;
refType?: string;
isTemp?: boolean;
md5?: string;
}

View File

@ -1,10 +1,9 @@
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';
import { OssConfigForm, OssConfigQuery, OssConfigVO } from './types';
import { AxiosPromise } from 'axios';
// 查询对象存储配置列表
export function listOssConfig(query: OssConfigQuery): AxiosPromise<PageResult<OssConfigVO>> {
export function listOssConfig(query: OssConfigQuery): AxiosPromise<OssConfigVO[]> {
return request({
url: '/resource/oss/config/list',
method: 'get',

View File

@ -6,7 +6,7 @@ export interface OssConfigVO extends BaseEntity {
bucketName: string;
prefix: string;
endpoint: string;
domainUrl: string;
domain: string;
isHttps: string;
region: string;
status: string;
@ -29,7 +29,7 @@ export interface OssConfigForm {
bucketName: string;
prefix: string;
endpoint: string;
domainUrl: string;
domain: string;
isHttps: string;
accessPolicy: string;
region: string;

View File

@ -1,11 +1,9 @@
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';
import { PostForm, PostQuery, PostVO } from './types';
import { AxiosPromise } from 'axios';
// 查询岗位列表
export function listPost(query: PostQuery): AxiosPromise<PageResult<PostVO>> {
export function listPost(query: PostQuery): AxiosPromise<PostVO[]> {
return request({
url: '/system/post/list',
method: 'get',
@ -58,13 +56,3 @@ export function delPost(postId: string | number | (string | number)[]) {
method: 'delete'
});
}
/**
*
*/
export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
return request({
url: '/system/post/deptTree',
method: 'get'
});
};

View File

@ -1,10 +1,10 @@
import type { UserQuery, UserVO } from '@/api/system/user/types';
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import { UserVO } from '@/api/system/user/types';
import { UserQuery } from '@/api/system/user/types';
import { AxiosPromise } from 'axios';
import { RoleQuery, RoleVO, RoleDeptTree } from './types';
import request from '@/utils/request';
import type { RoleDeptTree, RoleQuery, RoleVO } from './types';
export const listRole = (query: RoleQuery): AxiosPromise<PageResult<RoleVO>> => {
export const listRole = (query: RoleQuery): AxiosPromise<RoleVO[]> => {
return request({
url: '/system/role/list',
method: 'get',
@ -45,7 +45,7 @@ export const addRole = (data: any) => {
};
/**
*
*
* @param data
*/
export const updateRole = (data: any) => {
@ -57,11 +57,11 @@ export const updateRole = (data: any) => {
};
/**
* +
*
*/
export const updateRolePermission = (data: any) => {
export const dataScope = (data: any) => {
return request({
url: '/system/role/permission',
url: '/system/role/dataScope',
method: 'put',
data: data
});
@ -95,7 +95,7 @@ export const delRole = (roleId: Array<string | number> | string | number) => {
/**
*
*/
export const allocatedUserList = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
export const allocatedUserList = (query: UserQuery): AxiosPromise<UserVO[]> => {
return request({
url: '/system/role/authUser/allocatedList',
method: 'get',
@ -106,7 +106,7 @@ export const allocatedUserList = (query: UserQuery): AxiosPromise<PageResult<Use
/**
*
*/
export const unallocatedUserList = (query: UserQuery): AxiosPromise<PageResult<UserVO>> => {
export const unallocatedUserList = (query: UserQuery): AxiosPromise<UserVO[]> => {
return request({
url: '/system/role/authUser/unallocatedList',
method: 'get',

View File

@ -1,10 +1,14 @@
import request from '@/utils/request';
// 获取跳转URL
export function authRouterUrl(source: string) {
// 绑定账号
export function authBinding(source: string, tenantId: string) {
return request({
url: '/auth/binding/' + source,
method: 'get'
method: 'get',
params: {
tenantId: tenantId,
domain: window.location.host
}
});
}

View File

@ -0,0 +1,101 @@
import request from '@/utils/request';
import { TenantForm, TenantQuery, TenantVO } from './types';
import { AxiosPromise } from 'axios';
// 查询租户列表
export function listTenant(query: TenantQuery): AxiosPromise<TenantVO[]> {
return request({
url: '/system/tenant/list',
method: 'get',
params: query
});
}
// 查询租户详细
export function getTenant(id: string | number): AxiosPromise<TenantVO> {
return request({
url: '/system/tenant/' + id,
method: 'get'
});
}
// 新增租户
export function addTenant(data: TenantForm) {
return request({
url: '/system/tenant',
method: 'post',
headers: {
isEncrypt: true,
repeatSubmit: false
},
data: data
});
}
// 修改租户
export function updateTenant(data: TenantForm) {
return request({
url: '/system/tenant',
method: 'put',
data: data
});
}
// 租户状态修改
export function changeTenantStatus(id: string | number, tenantId: string | number, status: string) {
const data = {
id,
tenantId,
status
};
return request({
url: '/system/tenant/changeStatus',
method: 'put',
data: data
});
}
// 删除租户
export function delTenant(id: string | number | Array<string | number>) {
return request({
url: '/system/tenant/' + id,
method: 'delete'
});
}
// 动态切换租户
export function dynamicTenant(tenantId: string | number) {
return request({
url: '/system/tenant/dynamic/' + tenantId,
method: 'get'
});
}
// 清除动态租户
export function dynamicClear() {
return request({
url: '/system/tenant/dynamic/clear',
method: 'get'
});
}
// 同步租户套餐
export function syncTenantPackage(tenantId: string | number, packageId: string | number) {
const data = {
tenantId,
packageId
};
return request({
url: '/system/tenant/syncTenantPackage',
method: 'get',
params: data
});
}
// 同步租户字典
export function syncTenantDict() {
return request({
url: '/system/tenant/syncTenantDict',
method: 'get'
});
}

View File

@ -0,0 +1,46 @@
export interface TenantVO extends BaseEntity {
id: number | string;
tenantId: number | string;
username: string;
contactUserName: string;
contactPhone: string;
companyName: string;
licenseNumber: string;
address: string;
domain: string;
intro: string;
remark: string;
packageId: string | number;
expireTime: string;
accountCount: number;
status: string;
}
export interface TenantQuery extends PageQuery {
tenantId: string | number;
contactUserName: string;
contactPhone: string;
companyName: string;
}
export interface TenantForm {
id: number | string | undefined;
tenantId: number | string | undefined;
username: string;
password: string;
contactUserName: string;
contactPhone: string;
companyName: string;
licenseNumber: string;
domain: string;
address: string;
intro: string;
remark: string;
packageId: string | number;
expireTime: string;
accountCount: number;
status: string;
}

View File

@ -0,0 +1,67 @@
import request from '@/utils/request';
import { TenantPkgForm, TenantPkgQuery, TenantPkgVO } from './types';
import { AxiosPromise } from 'axios';
// 查询租户套餐列表
export function listTenantPackage(query?: TenantPkgQuery): AxiosPromise<TenantPkgVO[]> {
return request({
url: '/system/tenant/package/list',
method: 'get',
params: query
});
}
// 查询租户套餐下拉选列表
export function selectTenantPackage(): AxiosPromise<TenantPkgVO[]> {
return request({
url: '/system/tenant/package/selectList',
method: 'get'
});
}
// 查询租户套餐详细
export function getTenantPackage(packageId: string | number): AxiosPromise<TenantPkgVO> {
return request({
url: '/system/tenant/package/' + packageId,
method: 'get'
});
}
// 新增租户套餐
export function addTenantPackage(data: TenantPkgForm) {
return request({
url: '/system/tenant/package',
method: 'post',
data: data
});
}
// 修改租户套餐
export function updateTenantPackage(data: TenantPkgForm) {
return request({
url: '/system/tenant/package',
method: 'put',
data: data
});
}
// 租户套餐状态修改
export function changePackageStatus(packageId: number | string, status: string) {
const data = {
packageId,
status
};
return request({
url: '/system/tenant/package/changeStatus',
method: 'put',
data: data
});
}
// 删除租户套餐
export function delTenantPackage(packageId: string | number | Array<string | number>) {
return request({
url: '/system/tenant/package/' + packageId,
method: 'delete'
});
}

View File

@ -0,0 +1,20 @@
export interface TenantPkgVO extends BaseEntity {
packageId: string | number;
packageName: string;
menuIds: string;
remark: string;
menuCheckStrictly: boolean;
status: string;
}
export interface TenantPkgQuery extends PageQuery {
packageName: string;
}
export interface TenantPkgForm {
packageId: string | number | undefined;
packageName: string;
menuIds: string;
remark: string;
menuCheckStrictly: boolean;
}

View File

@ -1,16 +1,15 @@
import type { RoleVO } from '@/api/system/role/types';
import type { PageResult } from '@/api/types';
import type { AxiosPromise } from '@/utils/api-types';
import { DeptTreeVO, DeptVO } from './../dept/types';
import { RoleVO } from '@/api/system/role/types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { UserForm, UserQuery, UserVO, UserInfoVO } from './types';
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>> => {
export const listUser = (query: UserQuery): AxiosPromise<UserVO[]> => {
return request({
url: '/system/user/list',
method: 'get',
@ -111,17 +110,6 @@ export const changeUserStatus = (userId: number | string, status: string) => {
});
};
/**
*
* @param userId ID
*/
export const unlockUser = (userId: number | string) => {
return request({
url: '/system/user/unlock/' + userId,
method: 'get'
});
};
/**
*
*/
@ -136,7 +124,7 @@ export const getUserProfile = (): AxiosPromise<UserInfoVO> => {
*
* @param data
*/
export const updateUserProfile = (data: UserProfileForm) => {
export const updateUserProfile = (data: UserForm) => {
return request({
url: '/system/user/profile',
method: 'put',
@ -165,6 +153,18 @@ export const updateUserPwd = (oldPassword: string, newPassword: string) => {
});
};
/**
*
* @param data
*/
export const uploadAvatar = (data: FormData) => {
return request({
url: '/system/user/profile/avatar',
method: 'post',
data: data
});
};
/**
*
* @param userId ID
@ -218,10 +218,10 @@ export default {
delUser,
resetUserPwd,
changeUserStatus,
unlockUser,
getUserProfile,
updateUserProfile,
updateUserPwd,
uploadAvatar,
getAuthRole,
updateAuthRole,
deptTreeSelect,

View File

@ -1,5 +1,5 @@
import type { PostVO } from '@/api/system/post/types';
import type { RoleVO } from '@/api/system/role/types';
import { RoleVO } from '@/api/system/role/types';
import { PostVO } from '@/api/system/post/types';
/**
*
@ -16,11 +16,11 @@ export interface UserInfo {
export interface UserQuery extends PageQuery {
userName?: string;
nickName?: string;
phoneNumber?: string;
phonenumber?: string;
status?: string;
deptId?: string | number;
roleId?: string | number;
userIds?: string | number | (string | number)[] | undefined;
userIds?: string;
}
/**
@ -34,18 +34,15 @@ export interface UserVO extends BaseEntity {
nickName: string;
userType: string;
email: string;
phoneNumber: string;
gender: string;
avatar?: string | number;
avatarUrl?: string;
phonenumber: string;
sex: string;
avatar: string;
status: string;
delFlag: string;
loginIp: string;
loginDate: string;
remark: string;
deptName: string;
/** 详情接口可能返回嵌套部门 */
dept?: { deptName?: string };
roles: RoleVO[];
roleIds: any;
postIds: any;
@ -63,27 +60,15 @@ export interface UserForm {
userName: string;
nickName?: string;
password: string;
phoneNumber?: string;
phonenumber?: string;
email?: string;
gender?: string;
sex?: 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[];

View File

@ -1,12 +1,9 @@
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';
import { DbTableQuery, DbTableVO, TableQuery, TableVO, GenTableVO, DbTableForm } from './types';
import { AxiosPromise } from 'axios';
// 查询生成表数据
export const listTable = (query: TableQuery): AxiosPromise<PageResult<TableVO>> => {
export const listTable = (query: TableQuery): AxiosPromise<TableVO[]> => {
return request({
url: '/tool/gen/list',
method: 'get',
@ -14,7 +11,7 @@ export const listTable = (query: TableQuery): AxiosPromise<PageResult<TableVO>>
});
};
// 查询db数据库列表
export const listDbTable = (query: DbTableQuery): AxiosPromise<PageResult<DbTableVO>> => {
export const listDbTable = (query: DbTableQuery): AxiosPromise<DbTableVO[]> => {
return request({
url: '/tool/gen/db/list',
method: 'get',
@ -23,7 +20,7 @@ export const listDbTable = (query: DbTableQuery): AxiosPromise<PageResult<DbTabl
};
// 查询表详细信息
export const getGenTable = (tableId: string | number): AxiosPromise<GenTableDetailPayload> => {
export const getGenTable = (tableId: string | number): AxiosPromise<GenTableVO> => {
return request({
url: '/tool/gen/' + tableId,
method: 'get'
@ -31,7 +28,7 @@ export const getGenTable = (tableId: string | number): AxiosPromise<GenTableDeta
};
// 修改代码生成信息
export const updateGenTable = (data: DbTableForm): AxiosPromise<unknown> => {
export const updateGenTable = (data: DbTableForm): AxiosPromise<GenTableVO> => {
return request({
url: '/tool/gen',
method: 'put',
@ -40,7 +37,7 @@ export const updateGenTable = (data: DbTableForm): AxiosPromise<unknown> => {
};
// 导入表
export const importTable = (data: { tables: string; dataName: string }): AxiosPromise<unknown> => {
export const importTable = (data: { tables: string; dataName: string }): AxiosPromise<GenTableVO> => {
return request({
url: '/tool/gen/importTable',
method: 'post',
@ -64,6 +61,14 @@ export const delTable = (tableId: string | number | Array<string | number>) => {
});
};
// 生成代码(自定义路径)
export const genCode = (tableId: string | number) => {
return request({
url: '/tool/gen/genCode/' + tableId,
method: 'get'
});
};
// 同步数据库
export const synchDb = (tableId: string | number) => {
return request({

View File

@ -4,14 +4,17 @@ export interface TableVO extends BaseEntity {
dataName: string;
tableName: string;
tableComment: string;
subTableName?: any;
subTableFkName?: any;
className: string;
tplCategory: string;
frontendType: string;
packageName: string;
moduleName: string;
businessName: string;
functionName: string;
functionAuthor: string;
genType: string;
genPath: string;
pkColumn?: any;
columns?: any;
options?: any;
@ -22,16 +25,6 @@ export interface TableVO extends BaseEntity {
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;
}
@ -79,14 +72,17 @@ export interface DbTableVO {
tableId?: any;
tableName: string;
tableComment: string;
subTableName?: any;
subTableFkName?: any;
className?: any;
tplCategory?: any;
frontendType?: string;
packageName?: any;
moduleName?: any;
businessName?: any;
functionName?: any;
functionAuthor?: any;
genType?: any;
genPath?: any;
pkColumn?: any;
columns: DbColumnVO[];
options?: any;
@ -97,16 +93,6 @@ export interface DbTableVO {
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;
}
@ -117,14 +103,10 @@ export interface DbTableQuery extends PageQuery {
tableComment: string;
}
/**
* data
* - info GenTable
* - rows GenTableColumn[]
*/
export interface GenTableDetailPayload {
export interface GenTableVO {
info: DbTableVO;
rows: DbColumnVO[];
tables: DbTableVO[];
}
export interface DbColumnForm extends BaseEntity {
@ -164,16 +146,6 @@ export interface DbParamForm {
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 {
@ -181,14 +153,17 @@ export interface DbTableForm extends BaseEntity {
tableId: string | string;
tableName: string;
tableComment: string;
subTableName?: any;
subTableFkName?: any;
className: string;
tplCategory: string;
frontendType: string;
packageName: string;
moduleName: string;
businessName: string;
functionName: string;
functionAuthor: string;
genType: string;
genPath: string;
pkColumn?: any;
columns: DbColumnForm[];
options: string;
@ -199,16 +174,6 @@ export interface DbTableForm extends BaseEntity {
menuIds?: any;
parentMenuId: string;
parentMenuName?: any;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
tree: boolean;
crud: boolean;
params: DbParamForm;

View File

@ -2,6 +2,7 @@
*
*/
export type RegisterForm = {
tenantId: string;
username: string;
password: string;
confirmPassword?: string;
@ -14,6 +15,7 @@ export type RegisterForm = {
*
*/
export interface LoginData {
tenantId?: string;
username?: string;
password?: string;
rememberMe?: boolean;
@ -43,9 +45,15 @@ export interface VerifyCodeResult {
}
/**
*
*
*/
export interface PageResult<T = any> {
total: number;
rows: T[];
export interface TenantVO {
companyName: string;
domain: any;
tenantId: string;
}
export interface TenantInfo {
tenantEnabled: boolean;
voList: TenantVO[];
}

View File

@ -1,6 +1,6 @@
import type { CategoryForm, CategoryQuery, CategoryTreeVO, CategoryVO } from '@/api/workflow/category/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { CategoryVO, CategoryForm, CategoryQuery, CategoryTreeVO } from '@/api/workflow/category/types';
/**
*

View File

@ -1,19 +1,13 @@
import type { PageResult } from '@/api/types';
import type {
definitionXmlVO,
FlowDefinitionForm,
FlowDefinitionQuery,
FlowDefinitionVo
} from '@/api/workflow/definition/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { FlowDefinitionQuery, definitionXmlVO, FlowDefinitionForm, FlowDefinitionVo } from '@/api/workflow/definition/types';
import { AxiosPromise } from 'axios';
/**
*
* @param query id
* @returns
*/
export const listDefinition = (query: FlowDefinitionQuery): AxiosPromise<PageResult<FlowDefinitionVo>> => {
export const listDefinition = (query: FlowDefinitionQuery): AxiosPromise<FlowDefinitionVo[]> => {
return request({
url: `/workflow/definition/list`,
method: 'get',
@ -26,7 +20,7 @@ export const listDefinition = (query: FlowDefinitionQuery): AxiosPromise<PageRes
* @param query id
* @returns
*/
export const unPublishList = (query: FlowDefinitionQuery): AxiosPromise<PageResult<FlowDefinitionVo>> => {
export const unPublishList = (query: FlowDefinitionQuery): AxiosPromise<FlowDefinitionVo[]> => {
return request({
url: `/workflow/definition/unPublishList`,
method: 'get',

View File

@ -22,10 +22,7 @@ export interface FlowDefinitionForm {
flowName: string;
flowCode: string;
category: string;
ext: string;
formPath: string;
formCustom: string;
modelValue: string;
}
export interface definitionXmlVO {

View File

@ -1,14 +1,13 @@
import type { PageResult } from '@/api/types';
import type { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types';
import { AxiosPromise } from 'axios';
/**
*
* @param query
* @returns {*}
*/
export const pageByRunning = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
export const pageByRunning = (query: FlowInstanceQuery): AxiosPromise<FlowInstanceVO[]> => {
return request({
url: '/workflow/instance/pageByRunning',
method: 'get',
@ -21,7 +20,7 @@ export const pageByRunning = (query: FlowInstanceQuery): AxiosPromise<PageResult
* @param query
* @returns {*}
*/
export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<FlowInstanceVO[]> => {
return request({
url: '/workflow/instance/pageByFinish',
method: 'get',
@ -34,7 +33,7 @@ export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<PageResult<
*/
export const flowHisTaskList = (businessId: string | number) => {
return request({
url: `/workflow/instance/flowHisTaskList/${businessId}?t=${Math.random()}`,
url: `/workflow/instance/flowHisTaskList/${businessId}` + '?t' + Math.random(),
method: 'get'
});
};
@ -44,7 +43,7 @@ export const flowHisTaskList = (businessId: string | number) => {
* @param query
* @returns {*}
*/
export const pageByCurrent = (query: FlowInstanceQuery): AxiosPromise<PageResult<FlowInstanceVO>> => {
export const pageByCurrent = (query: FlowInstanceQuery): AxiosPromise<FlowInstanceVO[]> => {
return request({
url: '/workflow/instance/pageByCurrent',
method: 'get',
@ -88,18 +87,6 @@ export const deleteByInstanceIds = (instanceIds: Array<string | number> | string
method: 'delete'
});
};
/**
*
* @param instanceIds
*/
export const deleteHisByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
return request({
url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`,
method: 'delete'
});
};
/**
*
* @param data
@ -112,15 +99,3 @@ export const invalid = (data: any) => {
data: data
});
};
/**
*
* @param data
* @returns
*/
export const updateVariable = (data: any) => {
return request({
url: `/workflow/instance/updateVariable`,
method: 'put',
data: data
});
};

View File

@ -1,11 +1,11 @@
import type { FlowTaskVO } from '@/api/workflow/task/types';
import { FlowTaskVO } from '@/api/workflow/task/types';
export interface FlowInstanceQuery extends PageQuery {
category?: string | number;
nodeName?: string;
flowCode?: string;
flowName?: string;
createByIds?: Array<string | number>;
createByIds?: string[] | number[];
businessId?: string;
}
@ -23,6 +23,4 @@ export interface FlowInstanceVO extends BaseEntity {
flowStatus: string;
flowStatusName: string;
flowTaskList: FlowTaskVO[];
businessCode: string;
businessTitle: string;
}

View File

@ -1,7 +1,6 @@
import type { PageResult } from '@/api/types';
import type { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
import type { AxiosPromise } from '@/utils/api-types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { LeaveVO, LeaveQuery, LeaveForm } from '@/api/workflow/leave/types';
/**
*
@ -9,7 +8,7 @@ import request from '@/utils/request';
* @returns {*}
*/
export const listLeave = (query?: LeaveQuery): AxiosPromise<PageResult<LeaveVO>> => {
export const listLeave = (query?: LeaveQuery): AxiosPromise<LeaveVO[]> => {
return request({
url: '/workflow/leave/list',
method: 'get',
@ -40,18 +39,6 @@ export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
});
};
/**
*
* @param data
*/
export const submitAndFlowStart = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave/submitAndFlowStart',
method: 'post',
data: data
});
};
/**
*
* @param data

View File

@ -1,6 +1,5 @@
export interface LeaveVO {
id: string | number;
applyCode?: string;
leaveType: string;
startDate: string;
endDate: string;
@ -11,7 +10,6 @@ export interface LeaveVO {
export interface LeaveForm extends BaseEntity {
id?: string | number;
applyCode?: string;
leaveType?: string;
startDate?: string;
endDate?: string;

View File

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

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

@ -1,14 +1,13 @@
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';
import { AxiosPromise } from 'axios';
import { TaskQuery, FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
/**
*
* @param query
* @returns {*}
*/
export const pageByTaskWait = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
export const pageByTaskWait = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
return request({
url: '/workflow/task/pageByTaskWait',
method: 'get',
@ -21,7 +20,7 @@ export const pageByTaskWait = (query: TaskQuery): AxiosPromise<PageResult<FlowTa
* @param query
* @returns {*}
*/
export const pageByTaskFinish = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
export const pageByTaskFinish = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
return request({
url: '/workflow/task/pageByTaskFinish',
method: 'get',
@ -34,7 +33,7 @@ export const pageByTaskFinish = (query: TaskQuery): AxiosPromise<PageResult<Flow
* @param query
* @returns {*}
*/
export const pageByTaskCopy = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
export const pageByTaskCopy = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
return request({
url: '/workflow/task/pageByTaskCopy',
method: 'get',
@ -43,11 +42,11 @@ export const pageByTaskCopy = (query: TaskQuery): AxiosPromise<PageResult<FlowTa
};
/**
*
*
* @param query
* @returns {*}
*/
export const pageByAllTaskWait = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
export const pageByAllTaskWait = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
return request({
url: '/workflow/task/pageByAllTaskWait',
method: 'get',
@ -56,11 +55,11 @@ export const pageByAllTaskWait = (query: TaskQuery): AxiosPromise<PageResult<Flo
};
/**
*
*
* @param query
* @returns {*}
*/
export const pageByAllTaskFinish = (query: TaskQuery): AxiosPromise<PageResult<FlowTaskVO>> => {
export const pageByAllTaskFinish = (query: TaskQuery): AxiosPromise<FlowTaskVO[]> => {
return request({
url: '/workflow/task/pageByAllTaskFinish',
method: 'get',
@ -149,9 +148,9 @@ export const terminationTask = (data: any) => {
*
* @returns
*/
export const getBackTaskNode = (taskId: string | number, nodeCode: string) => {
export const getBackTaskNode = (definitionId: string, nodeCode: string) => {
return request({
url: `/workflow/task/getBackTaskNode/${taskId}/${nodeCode}`,
url: `/workflow/task/getBackTaskNode/${definitionId}/${nodeCode}`,
method: 'get'
});
};
@ -192,16 +191,3 @@ export const getNextNodeList = (data: any): any => {
data: data
});
};
/**
*
* @param data参数
* @returns
*/
export const urgeTask = (data: any): any => {
return request({
url: '/workflow/task/urgeTask',
method: 'post',
data: data
});
};

View File

@ -2,7 +2,7 @@ export interface TaskQuery extends PageQuery {
nodeName?: string;
flowCode?: string;
flowName?: string;
createByIds?: Array<string | number>;
createByIds?: string[] | number[];
}
export interface ParticipantVo {
@ -30,20 +30,16 @@ export interface FlowTaskVO {
nodeRatio: string | number;
version?: string;
applyNode?: boolean;
buttonList?: ButtonList[];
copyList?: FlowCopyVo[];
varList?: Map<string, string>;
businessCode: string;
businessTitle: string;
buttonList?: buttonList[];
}
export interface ButtonList {
export interface buttonList {
code: string;
show: boolean;
}
export interface FlowCopyVo {
userId: string | number;
nickName: string;
export interface VariableVo {
key: string;
value: string;
}
export interface TaskOperationBo {
@ -53,8 +49,6 @@ export interface TaskOperationBo {
userIds?: string[];
//任务ID必填
taskId: string | number;
//消息类型
messageType?: string[];
//意见或备注信息(可选)
message?: string;
}

View File

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

View File

@ -10,5 +10,4 @@ export interface StartProcessBo {
businessId: string | number;
flowCode: string;
variables: any;
bizExt: any;
}

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,90 +0,0 @@
/* Global document styles and lightweight utility helpers. */
// --- 视口与排版根 ---
body {
height: 100%;
margin: 0;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
background: var(--app-shell-bg);
font-family:
'MiSans', 'HarmonyOS Sans SC', 'PingFang SC', 'Source Han Sans SC', 'Noto Sans SC', 'Hiragino Sans GB',
'Microsoft YaHei', sans-serif;
}
label {
font-weight: 600;
}
html {
height: 100%;
box-sizing: border-box;
}
html.dark .svg-icon,
html.dark svg {
fill: var(--el-text-color-regular);
}
#app {
height: 100%;
}
html,
body,
#app {
min-height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: ' ';
clear: both;
height: 0;
}
}
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}

View File

@ -0,0 +1,99 @@
@use './variables.module.scss' as *;
@mixin colorBtn($color) {
background: $color;
&:hover {
color: $color;
&:before,
&:after {
background: $color;
}
}
}
.blue-btn {
@include colorBtn($blue);
}
.light-blue-btn {
@include colorBtn($light-blue);
}
.red-btn {
@include colorBtn($red);
}
.pink-btn {
@include colorBtn($pink);
}
.green-btn {
@include colorBtn($green);
}
.tiffany-btn {
@include colorBtn($tiffany);
}
.yellow-btn {
@include colorBtn($yellow);
}
.pan-btn {
font-size: 14px;
color: #fff;
padding: 14px 36px;
border-radius: 8px;
border: none;
outline: none;
transition: 600ms ease all;
position: relative;
display: inline-block;
&:hover {
background: #fff;
&:before,
&:after {
width: 100%;
transition: 600ms ease all;
}
}
&:before,
&:after {
content: '';
position: absolute;
top: 0;
right: 0;
height: 2px;
width: 0;
transition: 400ms ease all;
}
&::after {
right: inherit;
top: inherit;
left: 0;
bottom: 0;
}
}
.custom-button {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
color: #fff;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
padding: 10px 15px;
font-size: 14px;
border-radius: 4px;
}

View File

@ -1,67 +0,0 @@
/* 业务列表/卡片页分页列表el-card vendors/element-plus 通用覆盖叠加入口顺序见 index.scss
* 页面模式而非 EP 公共主题故放在 components */
// --- 历史表单分区标题样式 ---
.form-header {
font-size: 15px;
color: var(--el-color-primary);
border-bottom: 1px solid var(--app-surface-border);
margin: 8px 10px 25px 10px;
padding-bottom: 5px;
}
// --- 树形容器描边暗色覆盖在 _search-panel ---
.tree-border {
margin-top: 8px;
border: 1px solid var(--app-surface-border);
background: var(--app-surface-bg);
border-radius: 22px;
width: 100%;
padding: 12px;
}
@media (max-width: 768px) {
.pagination-container .el-pagination > .el-pagination__jump {
display: none !important;
}
.pagination-container .el-pagination > .el-pagination__sizes {
display: none !important;
}
}
.el-tree-node__content > .el-checkbox {
margin-right: 8px;
}
// --- el-card 本业务页外观 EP vendors 叠加 ---
.el-card__header {
padding: 14px 16px 10px !important;
min-height: auto;
background: transparent;
border-bottom: 1px solid var(--app-surface-border);
}
.el-card__body {
padding: 16px !important;
}
.el-card {
border-radius: var(--app-radius-base);
box-shadow: var(--app-shadow-sm);
border-color: var(--app-surface-border);
overflow: hidden;
transition:
box-shadow 0.2s ease,
border-color 0.2s ease;
background: var(--app-surface-bg);
}
.el-card:hover {
box-shadow: var(--app-shadow-md);
border-color: var(--app-accent-soft);
}
.card-box {
margin-bottom: 10px;
}

View File

@ -1,76 +0,0 @@
/* 工具类 */
.pt5 {
padding-top: 5px;
}
.pr5 {
padding-right: 5px;
}
.pl5 {
padding-left: 5px;
}
.pb5 {
padding-bottom: 5px;
}
.mt5 {
margin-top: 5px;
}
.mr5 {
margin-right: 5px;
}
.mb5 {
margin-bottom: 5px;
}
.mb8 {
margin-bottom: 8px;
}
.ml5 {
margin-left: 5px;
}
.mt10 {
margin-top: 10px;
}
.mr10 {
margin-right: 10px;
}
.mb10 {
margin-bottom: 10px;
}
.ml10 {
margin-left: 10px;
}
.mt20 {
margin-top: 20px;
}
.mr20 {
margin-right: 20px;
}
.mb20 {
margin-bottom: 20px;
}
.ml20 {
margin-left: 20px;
}
.el-dialog.scrollbar .el-dialog__body {
overflow: auto;
overflow-x: hidden;
max-height: 70vh;
padding: 14px 22px 4px;
}

View File

@ -1,9 +0,0 @@
.link-type,
.link-type:focus {
color: var(--el-color-primary);
cursor: pointer;
&:hover {
color: var(--app-accent-strong);
}
}

View File

@ -1,200 +0,0 @@
/* Shared vertical page shell spacing helpers. */
.p-2 {
display: flex;
flex-direction: column;
gap: 12px;
}
.app-main > .p-2 {
padding: 0 !important;
margin: 0 !important;
}
.p-2 > .el-row,
.p-2 > .el-card,
.p-2 > .search-wrap,
.p-2 > div,
.p-2 > section {
margin-bottom: 0 !important;
}
// --- CRUD / 树表页用混入 .vue @use @include ---
@mixin action-link-buttons($background: rgba(53, 109, 255, 0.08)) {
.data-table {
:deep(.el-button.is-link) {
width: 32px;
height: 32px;
border-radius: 10px;
background: $background;
}
}
}
@mixin toolbar-responsive($mobile-breakpoint: 900px) {
@media (max-width: $mobile-breakpoint) {
.toolbar-shell {
align-items: flex-start;
}
}
}
@mixin content-stack($gap: 12px) {
.content-main {
display: flex;
flex-direction: column;
gap: $gap;
}
}
@mixin dept-tree-panel($padding-top: 6px) {
.dept-tree {
padding-top: $padding-top;
}
}
@mixin collapsible-tree-layout($mobile-breakpoint: 900px) {
.content-grid,
.selector-layout {
align-items: stretch;
}
.tree-panel-col,
.tree-content-col {
min-width: 0;
transition:
max-width 0.24s ease,
flex-basis 0.24s ease;
}
.tree-panel-col.is-collapsed {
max-width: 56px;
flex: 0 0 56px;
}
.tree-content-col.is-tree-collapsed {
max-width: calc(100% - 56px);
flex: 0 0 calc(100% - 56px);
}
.tree-panel-shell,
.side-panel {
height: 100%;
}
.tree-panel-shell {
--tree-panel-max-height: 620px;
}
.tree-panel-shell :deep(.el-card__header) {
display: block;
padding: 12px 16px !important;
}
.tree-panel-shell :deep(.el-card__body) {
display: flex;
flex-direction: column;
height: var(--tree-panel-max-height);
min-height: 0;
max-height: var(--tree-panel-max-height);
overflow: hidden;
}
.tree-panel-header {
cursor: pointer;
user-select: none;
}
.tree-panel-header::after {
display: block;
width: 9px;
min-width: 9px;
height: 9px;
margin-left: auto;
flex-shrink: 0;
transform-origin: center;
transform: rotate(135deg);
}
.tree-panel-header.is-collapsed {
justify-content: center;
}
.side-panel.is-collapsed :deep(.el-card__body) {
display: none;
}
.dept-tree,
.selector-tree {
flex: 1 1 auto;
min-height: 180px;
max-height: 100%;
overflow-y: auto;
overflow-x: hidden;
padding-right: 4px;
scrollbar-width: thin;
}
.dept-tree::-webkit-scrollbar,
.selector-tree::-webkit-scrollbar {
width: 6px;
}
.dept-tree::-webkit-scrollbar-thumb,
.selector-tree::-webkit-scrollbar-thumb {
border-radius: 999px;
background: var(--app-text-muted);
opacity: 0.55;
}
.dept-tree::-webkit-scrollbar-track,
.selector-tree::-webkit-scrollbar-track {
background: transparent;
}
.tree-panel-shell.is-collapsed :deep(.el-card__header) {
padding: 12px 0 !important;
}
.tree-panel-shell.is-collapsed .tree-panel-header::after {
margin-left: 0;
}
.tree-panel-shell.is-collapsed .tree-panel-header::after {
transform: rotate(-45deg);
}
@media (max-width: $mobile-breakpoint) {
.tree-panel-col,
.tree-content-col {
max-width: 100%;
flex: 0 0 100%;
}
.tree-panel-shell {
--tree-panel-max-height: 420px;
}
.side-panel.is-collapsed {
height: auto;
}
}
}
@mixin table-crud-page($mobile-breakpoint: 900px, $background: rgba(53, 109, 255, 0.08)) {
@include action-link-buttons($background);
@include toolbar-responsive($mobile-breakpoint);
}
@mixin tree-table-crud-page(
$mobile-breakpoint: 900px,
$gap: 12px,
$tree-padding-top: 6px,
$background: rgba(53, 109, 255, 0.08)
) {
@include content-stack($gap);
@include dept-tree-panel($tree-padding-top);
@include collapsible-tree-layout($mobile-breakpoint);
@include table-crud-page($mobile-breakpoint, $background);
}

View File

@ -1,136 +0,0 @@
/* Shared search and filter form layout. */
.query-form {
display: flex;
flex-wrap: wrap;
gap: 10px 14px;
align-items: center;
}
.query-form .el-form-item {
flex: 0 0 auto;
margin-bottom: 0;
margin-right: 0;
display: flex;
align-items: center;
min-width: 0;
}
.query-form .el-form-item__content {
min-width: 0;
}
.query-form.el-form--inline .el-form-item__label {
flex: 0 0 auto;
width: auto;
min-width: 68px;
overflow: visible;
white-space: nowrap;
word-break: keep-all;
line-break: strict;
justify-content: flex-end;
}
.query-form .el-form-item:last-child {
margin-left: auto;
align-self: center;
}
.query-form .el-form-item:last-child .el-form-item__content {
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
flex-wrap: nowrap;
}
.query-form .el-form-item:last-child .el-button {
margin-left: 0 !important;
flex-shrink: 0;
}
.query-form .el-form-item:last-child .el-button + .el-button {
margin-left: 0 !important;
}
.query-form .el-form-item[style*='width: 308px'] {
width: auto !important;
min-width: 376px;
}
.query-form .el-form-item[style*='width: 308px'] .el-form-item__content {
flex: 0 0 308px;
min-width: 308px;
}
.query-form .el-form-item[style*='width: 308px'] .el-date-editor.el-range-editor {
width: 308px;
min-width: 308px;
}
.query-form .el-input__wrapper,
.query-form .el-select__wrapper,
.query-form .el-textarea__inner,
.query-form .el-date-editor,
.query-form .el-range-editor,
.query-form .el-cascader .el-input__wrapper,
.query-form .el-input-number,
.query-form .el-input-number__decrease,
.query-form .el-input-number__increase {
border-radius: var(--app-radius-md) !important;
}
.query-form .el-button {
border-radius: var(--app-radius-md) !important;
}
html.dark .query-form .el-button--primary {
--el-button-bg-color: rgba(37, 99, 235, 0.28);
--el-button-border-color: rgba(96, 165, 250, 0.62);
--el-button-text-color: #dbeafe;
--el-button-hover-bg-color: rgba(59, 130, 246, 0.36);
--el-button-hover-border-color: rgba(147, 197, 253, 0.78);
--el-button-hover-text-color: #eff6ff;
--el-button-active-bg-color: rgba(29, 78, 216, 0.44);
--el-button-active-border-color: rgba(96, 165, 250, 0.86);
--el-button-active-text-color: #eff6ff;
}
@media (max-width: 900px) {
.query-form {
gap: 10px 12px;
}
}
@media (max-width: 640px) {
.query-form {
display: flex;
flex-direction: column;
align-items: stretch;
}
.query-form .el-form-item {
width: 100%;
}
.query-form .el-form-item[style*='width: 308px'] {
min-width: 0;
}
.query-form .el-form-item[style*='width: 308px'] .el-form-item__content {
flex: 1 1 auto;
min-width: 0;
}
.query-form .el-input,
.query-form .el-select,
.query-form .el-date-editor,
.query-form .el-cascader,
.query-form .el-input-number {
width: 100% !important;
}
.query-form .el-form-item:last-child {
margin-left: 0;
}
}

View File

@ -1,91 +0,0 @@
/* Search panel, table actions, and responsive panel shells. */
// --- 表格工具列链接按钮全局 ---
.data-table .el-button.is-link {
width: 28px !important;
height: 28px !important;
min-width: 28px !important;
padding: 0 !important;
border-radius: var(--app-radius-md) !important;
background: var(--app-accent-soft) !important;
}
.data-table .el-button.is-link + .el-button.is-link {
margin-left: 4px !important;
}
.search-wrap {
margin-bottom: 0 !important;
}
.search-panel,
.table-panel {
transform: translateZ(0);
backface-visibility: hidden;
}
.search-panel {
isolation: isolate;
.el-card__header {
display: block;
padding: 12px 16px !important;
}
.el-card__body {
padding-top: 16px !important;
overflow: hidden;
max-height: 560px;
opacity: 1;
transition:
max-height 0.32s cubic-bezier(0.22, 1, 0.36, 1),
opacity 0.24s ease,
padding-top 0.24s ease,
padding-bottom 0.24s ease;
}
}
.search-panel.is-collapsed {
.el-card__body {
max-height: 0;
opacity: 0;
padding-top: 0 !important;
padding-bottom: 0 !important;
pointer-events: none;
}
}
.search-panel-toggle {
cursor: pointer;
user-select: none;
transition: color 0.24s ease;
&:hover {
color: var(--app-accent-strong);
}
&::after {
content: '';
margin-left: auto;
width: 9px;
height: 9px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
color: var(--app-text-muted);
transform: rotate(-135deg);
transition:
transform 0.24s ease,
color 0.24s ease;
}
}
.search-panel.is-collapsed .search-panel-toggle::after {
transform: rotate(45deg);
}
html.dark {
.tree-border {
background: var(--app-surface-bg);
border-color: var(--app-surface-border);
}
}

View File

@ -1,79 +0,0 @@
/* Reusable scoped mixins for selector dialogs backed by cards and vxe tables. */
/* 为多个根选择器统一设置 gap */
@mixin shell-gap($selectors...) {
@each $selector in $selectors {
#{$selector} {
gap: 12px;
}
}
}
/* 卡片区域撑满弹窗 */
@mixin card-shell {
.selector-card {
height: 100%;
}
}
/* 按弹窗根 class 收紧 body 顶内边距 */
@mixin dialog-body-padding($dialog-class) {
.#{$dialog-class} :deep(.el-dialog__body) {
padding-top: 12px;
}
}
/* 头部标题与已选 tag 换行、窄屏左对齐 */
@mixin selector-header-tags($mobile-breakpoint: 768px) {
.selector-header {
align-items: flex-start;
}
.selector-tags {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 6px;
max-width: min(100%, 520px);
}
.selector-tags :deep(.el-tag) {
margin: 0;
}
@media (max-width: $mobile-breakpoint) {
.selector-tags {
justify-content: flex-start;
max-width: 100%;
}
}
}
/* vxe 表格圆角与表头色,与全局表格 token 一致 */
@mixin selector-table {
.selector-table {
border-radius: 10px;
overflow: hidden;
}
.selector-table :deep(.vxe-table--render-default) {
border-radius: 10px;
color: var(--app-text-title);
}
.selector-table :deep(.vxe-header--column) {
background: var(--tableHeaderBg);
color: var(--tableHeaderTextColor);
font-weight: 600;
}
.selector-table :deep(.vxe-body--column),
.selector-table :deep(.vxe-header--column) {
border-color: var(--app-surface-border);
}
.selector-table :deep(.vxe-body--row.row--hover),
.selector-table :deep(.vxe-body--row:hover) {
background-color: rgba(53, 109, 255, 0.05);
}
}

View File

@ -1,84 +0,0 @@
/* Table headers and toolbar action layouts. */
.top-right-btn {
margin-left: auto;
}
.panel-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.panel-heading h3,
.table-heading h3 {
margin: 0;
font-size: 15px;
letter-spacing: 0;
color: var(--app-text-title);
}
.panel-kicker {
display: none;
color: var(--app-accent-strong);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.toolbar-shell {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
}
.table-heading p {
margin: 4px 0 0;
font-size: 13px;
color: var(--app-text-muted);
line-height: 1.5;
}
.toolbar-actions {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 8px;
margin-left: auto;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 2px;
}
.toolbar-actions::-webkit-scrollbar {
height: 0;
}
.toolbar-actions > * {
flex-shrink: 0;
}
.toolbar-actions .top-right-btn {
margin-left: 0;
flex-shrink: 0;
}
.toolbar-actions .el-button + .el-button,
.toolbar-actions .el-dropdown + .el-button,
.toolbar-actions .el-button + .el-dropdown,
.toolbar-actions .el-dropdown + .el-dropdown,
.toolbar-actions .top-right-btn + .el-button,
.toolbar-actions .el-button + .top-right-btn,
.toolbar-actions .top-right-btn + .el-dropdown,
.toolbar-actions .el-dropdown + .top-right-btn {
margin-left: 0 !important;
}
.table-panel .toolbar-actions .el-button:not(.is-circle):not(.is-link) {
border-radius: var(--app-radius-md) !important;
}

View File

@ -1,49 +0,0 @@
/* Legacy theme helper classes and color accents. */
.el-button--cyan.is-active,
.el-button--cyan:active {
background: #20b2aa;
border-color: #20b2aa;
color: #ffffff;
}
.el-button--cyan:focus,
.el-button--cyan:hover {
background: #48d1cc;
border-color: #48d1cc;
color: #ffffff;
}
.el-button--cyan {
background-color: #20b2aa;
border-color: #20b2aa;
color: #ffffff;
}
.text-navy {
color: #1ab394;
}
.text-primary {
color: inherit;
}
.text-success {
color: #1c84c6;
}
.text-info {
color: #23c6c8;
}
.text-warning {
color: #f8ac59;
}
.text-danger {
color: #ed5565;
}
.text-muted {
color: #888888;
}

View File

@ -0,0 +1,153 @@
.el-collapse {
.collapse__title {
font-weight: 600;
padding: 0 8px;
font-size: 1.2em;
line-height: 1.1em;
}
.el-collapse-item__content {
padding: 0 8px;
}
}
.el-divider--horizontal {
margin-bottom: 10px;
margin-top: 10px;
}
.el-breadcrumb__inner,
.el-breadcrumb__inner a {
font-weight: 400 !important;
}
.el-upload {
input[type='file'] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
.cell {
.el-tag {
margin-right: 0px;
}
}
.small-padding {
.cell {
padding-left: 5px;
padding-right: 5px;
}
}
.fixed-width {
.el-button--mini {
padding: 7px 10px;
width: 60px;
}
}
.status-col {
.cell {
padding: 0 10px;
text-align: center;
.el-tag {
margin-right: 0px;
}
}
}
/*-------------Dialog-------------**/
.el-overlay {
overflow: hidden;
.el-overlay-dialog {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
.el-dialog {
margin: 0 auto !important;
.el-dialog__body {
padding: 15px !important;
}
.el-dialog__header {
padding: 16px 16px 8px 16px;
box-sizing: border-box;
border-bottom: 1px solid var(--brder-color);
margin-right: 0;
}
}
}
}
.el-dialog__body {
max-height: calc(90vh - 111px) !important;
overflow-y: auto;
overflow-x: hidden;
}
// refine element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
// dropdown
.el-dropdown-menu {
a {
display: block;
}
}
// fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner {
display: inline-flex !important;
}
// to fix el-date-picker css style
.el-range-separator {
box-sizing: content-box;
}
.el-menu--collapse > div > .el-submenu > .el-submenu__title .el-submenu__icon-arrow {
display: none;
}
.el-dropdown .el-dropdown-link {
color: var(--el-color-primary) !important;
}
/* 当 el-form 的 inline 属性为 true 时 */
/* 设置 label 的宽度默认为 68px */
.el-form--inline .el-form-item__label {
width: 68px;
}
/* 设置 el-select 的宽度默认为 240px */
.el-form--inline .el-select {
width: 240px;
}
/* 设置 el-input 的宽度默认为 240px */
.el-form--inline .el-input {
width: 240px;
}
/* 设置 el-message-box 消息弹框内容强制换行 */
.el-message-box .el-message-box__message {
word-break: break-word;
}

View File

@ -1,26 +1,211 @@
// =============================================================================
// 全局样式入口加载顺序即最终 CSS 层叠顺序后载入的同优先级规则会覆盖先载入的
// =============================================================================
@use './tokens/sass-vars' as *;
@use './tokens/theme';
@use './tokens/theme-dark';
@use './variables.module.scss' as *;
@use './mixin.scss';
@use './transition.scss';
@use './base/document';
@use './layout/surface';
@use './layout/sidebar/shell' as sidebar-shell;
@use './layout/sidebar/menu';
@use './layout/sidebar/collapsed';
@use './layout/sidebar/responsive';
@use './layout/sidebar/popper' as sidebar-popper;
@use './components/legacy-utilities';
@use './components/misc';
@use './components/page-shell';
@use './components/card-shell';
@use './components/theme-helpers';
@use './components/table-toolbar';
@use './components/query-form';
@use './components/search-panel';
@use './vendors/element-plus/index' as element-custom;
@use './element-ui.scss';
@use './sidebar.scss';
@use './btn.scss';
@use './ruoyi.scss';
@use 'animate.css';
@use 'element-plus/dist/index.css';
body {
height: 100%;
margin: 0;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family:
Helvetica Neue,
Helvetica,
PingFang SC,
Hiragino Sans GB,
Microsoft YaHei,
Arial,
sans-serif;
}
label {
font-weight: 700;
}
html {
height: 100%;
box-sizing: border-box;
}
html.dark .svg-icon,
html.dark svg {
fill: var(--el-text-color-regular);
}
#app {
height: 100%;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
.no-padding {
padding: 0px !important;
}
.padding-content {
padding: 4px 0;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
.fr {
float: right;
}
.fl {
float: left;
}
.pr-5 {
padding-right: 5px;
}
.pl-5 {
padding-left: 5px;
}
.block {
display: block;
}
.pointer {
cursor: pointer;
}
.inlineBlock {
display: block;
}
.clearfix {
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: ' ';
clear: both;
height: 0;
}
}
aside {
background: #eef1f6;
padding: 8px 24px;
margin-bottom: 20px;
border-radius: 2px;
display: block;
line-height: 32px;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
color: #2c3e50;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
a {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
}
//main-container全局样式
.app-container {
padding: 20px;
}
// search面板样式
.panel,
.search {
margin-bottom: 0.75rem;
border-radius: 0.25rem;
border: 1px solid var(--el-border-color-light);
background-color: var(--el-bg-color-overlay);
padding: 0.75rem;
transition: all ease 0.3s;
&:hover {
box-shadow: 0 2px 12px #0000001a;
transition: all ease 0.3s;
}
}
.components-container {
margin: 30px 50px;
position: relative;
}
.text-center {
text-align: center;
}
.sub-navbar {
height: 50px;
line-height: 50px;
position: relative;
width: 100%;
text-align: right;
padding-right: 20px;
transition: 600ms ease position;
background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
.subtitle {
font-size: 20px;
color: #fff;
}
&.draft {
background: #d0d0d0;
}
&.deleted {
background: #d0d0d0;
}
}
.link-type,
.link-type:focus {
color: #337ab7;
cursor: pointer;
&:hover {
color: rgb(32, 160, 255);
}
}
.filter-container {
padding-bottom: 10px;
.filter-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
}
}

View File

@ -1,24 +0,0 @@
/* 主内容区面板(.app-container、.panel、.search不依赖 Element 类名,与 components 中 EP 类补丁区分。 */
// --- 整块内容容器常见于路由视图外包一层 ---
.app-container {
}
// --- 可复用面板条hover 抬升阴影 el-card ---
.panel,
.search {
margin-bottom: 0.75rem;
border-radius: var(--app-radius-base);
border: 1px solid var(--app-surface-border);
background-color: var(--app-surface-bg);
padding: 0.75rem;
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
transition:
box-shadow 0.2s ease,
border-color 0.2s ease;
&:hover {
box-shadow: var(--app-shadow-sm);
border-color: rgba(64, 158, 255, 0.2);
}
}

View File

@ -1,141 +0,0 @@
/* 桌面端折叠侧栏:窄宽、仅图标、子菜单箭头隐藏与 tooltip 触发区域(类名 .hideSidebar。 */
#app {
.hideSidebar {
.sidebar-container {
width: 58px !important;
}
.main-container {
margin-left: 70px;
}
.sidebar-shell {
padding-left: 4px;
padding-right: 4px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
height: 40px;
width: 40px !important;
min-width: 40px !important;
margin: 3px auto !important;
display: flex;
align-items: center;
justify-content: center !important;
box-sizing: border-box;
&.is-active {
background-color: var(--side-menu-active-bg) !important;
color: var(--side-menu-active-text) !important;
box-shadow: inset 3px 0 0 var(--side-menu-active-line);
}
.el-tooltip {
padding: 0 !important;
}
.el-menu-tooltip__trigger {
width: 100%;
height: 100%;
display: inline-flex !important;
align-items: center;
justify-content: center;
line-height: 1;
}
.svg-icon {
margin-right: 0 !important;
}
}
& .el-sub-menu {
overflow: hidden;
border-radius: var(--app-radius-md);
.el-sub-menu__title,
.el-sub-menu__title.el-tooltip__trigger {
border-radius: var(--app-radius-md);
height: 40px;
padding: 0 !important;
display: flex;
align-items: center;
justify-content: center !important;
line-height: 1;
}
&.is-active .el-sub-menu__title,
&.is-active .el-sub-menu__title.el-tooltip__trigger {
background-color: var(--side-menu-active-bg) !important;
box-shadow: inset 3px 0 0 var(--side-menu-active-line);
}
& > .el-sub-menu__title {
padding: 0 !important;
}
}
.el-menu--collapse {
> div > .el-sub-menu {
width: 40px !important;
min-width: 40px !important;
margin: 3px auto !important;
}
> div > .el-sub-menu > .el-sub-menu__title,
> div > .el-sub-menu > .el-sub-menu__title.el-tooltip__trigger {
width: 100% !important;
min-width: 100% !important;
margin: 0 !important;
box-sizing: border-box;
transform: none;
position: relative;
}
.is-active .svg-icon {
fill: currentColor;
}
> div > .el-sub-menu > .el-sub-menu__title .svg-icon {
position: absolute;
left: 50%;
top: 50%;
width: 16px;
height: 16px;
transform: translate(-50%, -50%);
display: block;
margin: 0 !important;
margin-right: 0 !important;
}
> div > .el-sub-menu > .el-sub-menu__title {
justify-content: center !important;
.el-sub-menu__icon-arrow {
display: none !important;
width: 0 !important;
margin: 0 !important;
overflow: hidden !important;
}
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
& > i {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}

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