8 Commits

Author SHA1 Message Date
003c43140b chore(projects): 更换图标 2025-08-16 13:34:25 +08:00
6850347b89 Merge branch 'master' into tauri 2025-08-16 11:56:14 +08:00
fb5a59d81b Merge branch 'master' into tauri
# Conflicts:
#	package.json
#	pnpm-lock.yaml
2025-07-24 17:49:12 +08:00
8147f1652e chore(release): release tauri v1.1.0 2025-07-01 10:53:41 +08:00
d5be9dc08e Merge branch 'master' into tauri 2025-06-05 22:16:58 +08:00
2f9576f53d Merge branch 'master' into tauri 2025-05-21 11:02:20 +08:00
ba7395ac18 Merge branch 'dev' into tauri 2025-05-17 20:17:20 +08:00
d49728a796 feat: 新增 tauri 适配 2025-05-16 17:50:23 +08:00
149 changed files with 13167 additions and 10343 deletions

View File

@ -15,8 +15,6 @@ VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT=Y
# AES 加密头标识
VITE_HEADER_FLAG=encrypt-key
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -12,8 +12,6 @@ VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT=Y
# AES 加密头标识
VITE_HEADER_FLAG=encrypt-key
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -12,8 +12,6 @@ VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT=Y
# AES 加密头标识
VITE_HEADER_FLAG=encrypt-key
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -2,55 +2,15 @@
## [v1.1.3](https://gitee.com/xlsea/ruoyi-plus-soybean/compare/v1.1.2...v1.1.3) (2025-08-16)
###    🚀 新功能
- 对接工作流分类模块 &nbsp;-&nbsp; by **AN** [<samp>(25790)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/25790d4b)
- 新增流程定义页面 &nbsp;-&nbsp; by @m-xlsea [<samp>(11aba)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/11aba9e2)
- 添加流程实例功能 &nbsp;-&nbsp; by **AN** [<samp>(f9d57)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f9d57f1b)
- 增加流程作废功能并优化按钮组件 &nbsp;-&nbsp; by **AN** [<samp>(350de)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/350de08f)
- 流程实例,查看变量功能 &nbsp;-&nbsp; by **AN** [<samp>(92b9c)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/92b9c213)
- 更新工作流分类选择组件,修复值回显问题并优化添加数据操作 &nbsp;-&nbsp; by **AN** [<samp>(14a29)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/14a29070)
- 添加请假申请功能 &nbsp;-&nbsp; by **AN** [<samp>(d7e05)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/d7e0516c)
- 更新请假申请功能,添加日期范围选择和请假天数自动计算 &nbsp;-&nbsp; by **AN** [<samp>(89a2a)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/89a2a6cb)
- 添加请假申请详情接口,优化请假操作表单样式 &nbsp;-&nbsp; by **AN** [<samp>(ab1d3)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ab1d3a23)
- 更新请假申请表单,添加流程类型选择和流程启动功能 &nbsp;-&nbsp; by **AN** [<samp>(49521)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/49521b66)
- 更新工作流任务申请模态框样式,添加消息类型选择禁用功能 &nbsp;-&nbsp; by **AN** [<samp>(4614b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4614b977)
- **components**:
- 增强审批信息面板,优化附件处理 &nbsp;-&nbsp; by **AN** [<samp>(49224)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/49224afe)
- 面板添加流程图 &nbsp;-&nbsp; by **AN** [<samp>(ae5c7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ae5c7e83)
- **projects**:
- 优化组件,完成流程实例-流程预览 &nbsp;-&nbsp; by **AN** [<samp>(50e7b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/50e7b515)
- 新增group-tag组件待办任务查看功能 &nbsp;-&nbsp; by **AN** [<samp>(1c322)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/1c322e28)
- 新增用户选择器组件,添加流程干预按钮 &nbsp;-&nbsp; by **AN** [<samp>(b8c77)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b8c771cd)
- 新增转办和终止功能 &nbsp;-&nbsp; by **AN** [<samp>(80faf)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/80faf4b4)
- 新增加签功能 &nbsp;-&nbsp; by **AN** [<samp>(55dce)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/55dceca2)
- 新增减签功能 &nbsp;-&nbsp; by **AN** [<samp>(f1d7b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f1d7b973)
- 新增 '我发起的' 功能 &nbsp;-&nbsp; by **AN** [<samp>(a77ed)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a77edc2e)
- 新增抄送、下一审批人提交功能,优化组件通用性 &nbsp;-&nbsp; by **AN** [<samp>(523ac)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/523aca6b)
- 新增我的待办功能,新增审批,驳回组件 &nbsp;-&nbsp; by **AN** [<samp>(130ee)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/130ee1dc)
- 新增我的已办,我的抄送功能 &nbsp;-&nbsp; by **AN** [<samp>(01d42)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/01d42722)
- 补充搜索条件 &nbsp;-&nbsp; by **AN** [<samp>(b6c7b)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b6c7b1b3)
- 新增流程表达式功能 &nbsp;-&nbsp; by **AN** [<samp>(d562f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/d562f8c1)
- **types**:
- 补充类型定义 &nbsp;-&nbsp; by **AN** [<samp>(bd6b5)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/bd6b575a)
### &nbsp;&nbsp;&nbsp;🐞 Bug 修复
- 修复控制台报错Message compilation errori18n的@为特殊符号 &nbsp;-&nbsp; by **AN** [<samp>(1c646)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/1c646937)
- 修复运行状态变化时数据加载顺序问题 &nbsp;-&nbsp; by **AN** [<samp>(ed118)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ed118069)
- 修复添加行时的操作顺序问题 &nbsp;-&nbsp; by **AN** [<samp>(f004e)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f004e75c)
- **hooks**:
- 非安全环境下不使用流式下载 &nbsp;-&nbsp; by @m-xlsea [<samp>(f8983)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/f8983557)
- 修复oss下载时未转码问题 &nbsp;-&nbsp; by **AN** [<samp>(2d31d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2d31d7dc)
- **other**:
- 修复代码生成字典相关问题 &nbsp;-&nbsp; by @m-xlsea [<samp>(9aa65)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9aa6597d)
- **project**:
- 关闭多租户功能后仍然遍历租户列表导致控制台报错的问题 &nbsp;-&nbsp; by **wang_rui** [<samp>(b96c4)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b96c46ba)
- 关闭多租户功能后仍然遍历租户列表导致控制台报错的问题 Merge pull request !25 from littleghost2016/dev &nbsp;-&nbsp; by **不寻俗** [<samp>(90276)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/9027632b)
- **projects**:
- 修复更新后产生问题 &nbsp;-&nbsp; by **AN** [<samp>(400ea)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/400eaf89)
- 修复动态组件弹窗动画问题 &nbsp;-&nbsp; by @m-xlsea [<samp>(2e029)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2e029929)
- 修复抽屉问题 &nbsp;-&nbsp; by **AN** [<samp>(a9c58)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a9c58b25)
- 修复一级菜单隐藏失效问题 &nbsp;-&nbsp; by **AN** [<samp>(8fcc7)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/8fcc70d7)
- 修复日期搜索条件清除问题 &nbsp;-&nbsp; by **AN** [<samp>(52318)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/52318c10)
- 修复登录过期事件监听未被重置 &nbsp;-&nbsp; by @m-xlsea [<samp>(71037)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/71037439)
@ -61,43 +21,13 @@
- **readme**:
- update GitHub stars and forks links for gitee &nbsp;-&nbsp; by @soybeanjs [<samp>(923eb)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/923eb98a)
### &nbsp;&nbsp;&nbsp;🛠 优化
- **projects**:
- 统一button的添加方式 &nbsp;-&nbsp; by **AN** [<samp>(b3f81)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b3f81ba3)
- 动态加载组件方法抽取为公共函数 &nbsp;-&nbsp; by **AN** [<samp>(2f8a6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/2f8a6b4b)
- 优化代码 &nbsp;-&nbsp; by **AN** [<samp>(59a69)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/59a69dd9)
- 优化代码 &nbsp;-&nbsp; by **AN** [<samp>(6c608)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/6c6086f8)
- 优化搜索FormItem展示 &nbsp;-&nbsp; by **AN** [<samp>(94faf)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/94fafe39)
- **types**:
- 补充标签类型 &nbsp;-&nbsp; by **AN** [<samp>(30316)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/30316d7e)
- 补充标签类型 &nbsp;-&nbsp; by **AN** [<samp>(71297)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/71297cf2)
### &nbsp;&nbsp;&nbsp;💅 重构
- remove WorkflowLeaveForm type from Vue typings &nbsp;-&nbsp; by **AN** [<samp>(394db)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/394db6fe)
- 优化代码 &nbsp;-&nbsp; by **AN** [<samp>(56d6d)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/56d6d77d)
- **menu**:
- 菜单管理中隐藏的菜单显示灰色 &nbsp;-&nbsp; by **NicholasLD** [<samp>(adca2)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/adca2e26)
- 菜单管理中隐藏的菜单显示灰色 Merge pull request !24 from NicholasLD/N/A &nbsp;-&nbsp; by **不寻俗** [<samp>(4eb77)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/4eb77eac)
- **projects**:
- 调整为批量上传文件 &nbsp;-&nbsp; by **AN** [<samp>(ffd62)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/ffd6211e)
- 重构流程设计菜单层级 &nbsp;-&nbsp; by **AN** [<samp>(b6f4f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/b6f4fb5a)
- 修改流程实例动态引入组件 &nbsp;-&nbsp; by **AN** [<samp>(a3dce)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/a3dcee4a)
- 文件命名修正 &nbsp;-&nbsp; by **AN** [<samp>(5e496)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/5e49622e)
- 移动工作流组件位置 &nbsp;-&nbsp; by @m-xlsea [<samp>(7ffaa)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7ffaac58)
- 菜单列表新增禁用菜单样式 &nbsp;-&nbsp; by @m-xlsea [<samp>(e5383)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e538355f)
- **types**:
- 移除无用type &nbsp;-&nbsp; by **AN** [<samp>(e86a6)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/e86a6d1b)
- **utils**:
- 简化加载组件方法 &nbsp;-&nbsp; by **AN** [<samp>(22710)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/22710ecb)
### &nbsp;&nbsp;&nbsp;📖 文档
- **projects**:
- 流程定义菜单更新 &nbsp;-&nbsp; by **AN** [<samp>(1af4e)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/1af4e963)
- 新增工作流更新sql &nbsp;-&nbsp; by **AN** [<samp>(89e7e)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/89e7edb3)
- 流程表达式菜单sql更新 &nbsp;-&nbsp; by **AN** [<samp>(7484f)</samp>](https://gitee.com/xlsea/ruoyi-plus-soybean/commit/7484f79b)
### &nbsp;&nbsp;&nbsp;🏡 杂项

View File

@ -1,4 +1,4 @@
import type { ProxyOptions } from 'vite';
import type { HttpProxy, ProxyOptions } from 'vite';
import { bgRed, bgYellow, green, lightBlue } from 'kolorist';
import { consola } from 'consola';
import { createServiceConfig } from '../../src/utils/service';
@ -34,7 +34,7 @@ function createProxyItem(item: App.Service.ServiceConfigItem, enableLog: boolean
target: item.baseURL,
changeOrigin: true,
ws: item.ws,
configure: (_proxy, options) => {
configure: (_proxy: HttpProxy.Server, options: ProxyOptions) => {
_proxy.on('proxyReq', (_proxyReq, req, _res) => {
if (!enableLog) return;

View File

@ -35,23 +35,4 @@ UPDATE `sys_menu` SET `component` = 'FrameView', `query_param` = 'https://previe
UPDATE `sys_menu` SET `path` = 'https://gitee.com/xlsea/ruoyi-plus-soybean', `component` = 'FrameView', `icon` = 'local-icon-gitee', `menu_name` = 'RuoYi-Plus-Soybean' WHERE `menu_id` = 4;
-- plus-ui 需要禁用的页面
UPDATE `sys_menu` SET `status` = '1' WHERE `menu_id` IN ( '116', '130', '131', '132' );
-- 工作流要启用的页面
UPDATE `sys_menu` SET `status` = '0' WHERE `menu_id` IN ( '11616', '11618', '11700', '11701' );
-- 工作流菜单
UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'hugeicons:flow-square' WHERE `menu_id` = 11616;
UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'fluent:notepad-person-16-regular' WHERE `menu_id` = 11618;
UPDATE `sys_menu` SET `component` = 'workflow/task/taskWaiting/index', `icon` = 'ri:todo-line' WHERE `menu_id` = 11619;
UPDATE `sys_menu` SET `icon` = 'weui:setting-outlined' WHERE `menu_id` = 11620;
UPDATE `sys_menu` SET `icon` = 'ri:instance-line' WHERE `menu_id` = 11621;
UPDATE `sys_menu` SET `icon` = 'carbon:category' WHERE `menu_id` = 11622;
UPDATE `sys_menu` SET `component` = 'workflow/task/myDocument/index', `icon` = 'hugeicons:start-up-02' WHERE `menu_id` = 11629;
UPDATE `sys_menu` SET `component` = 'Layout', `icon` = 'lucide:monitor-cog' WHERE `menu_id` = 11630;
UPDATE `sys_menu` SET `component` = 'workflow/task/allTaskWaiting/index', `icon` = 'ri:todo-line' WHERE `menu_id` = 11631;
UPDATE `sys_menu` SET `component` = 'workflow/task/taskFinish/index', `icon` = 'hugeicons:task-done-01' WHERE `menu_id` = 11632;
UPDATE `sys_menu` SET `path` = 'taskCopy', `component` = 'workflow/task/taskCopy/index', `icon` = 'mynaui:copy' WHERE `menu_id` = 11633;
UPDATE `sys_menu` SET `icon` = 'ic:twotone-time-to-leave' WHERE `menu_id` = 11638;
UPDATE `sys_menu` SET `icon` = 'material-symbols:design-services-outline', `path` = 'design', `component` = 'workflow/design/index' WHERE `menu_id` = 11700;
UPDATE `sys_menu` SET `icon` = 'ic:twotone-time-to-leave' WHERE `menu_id` = 11701;
UPDATE `sys_menu` SET `icon` = 'material-symbols:regular-expression-rounded' WHERE `menu_id` = 11801;
UPDATE `sys_menu` SET `status` = '1' WHERE `menu_id` IN ( '116', '130', '131', '132', '11700', '11701' );

View File

@ -41,18 +41,21 @@
"scripts": {
"build": "vite build --mode prod",
"build:dev": "vite build --mode dev",
"build:tauri": "pnpm tauri build",
"build:test": "vite build --mode test",
"cleanup": "sa cleanup",
"commit": "sa git-commit",
"commit:zh": "sa git-commit -l=zh-cn",
"dev": "vite --mode dev",
"dev:prod": "vite --mode prod",
"dev:tauri": "pnpm tauri dev",
"dev:test": "vite --mode test",
"gen-route": "sa gen-route",
"lint": "eslint . --fix",
"prepare": "simple-git-hooks",
"preview": "vite preview",
"release": "sa release",
"tauri-icon": "pnpm tauri icon ./public/logo.png",
"typecheck": "vue-tsc --noEmit --skipLibCheck",
"update-pkg": "sa update-pkg"
},
@ -65,12 +68,13 @@
"@sa/materials": "workspace:*",
"@sa/tinymce": "workspace:*",
"@sa/utils": "workspace:*",
"@tauri-apps/api": "2.5.0",
"@types/streamsaver": "^2.0.5",
"@vueuse/core": "13.8.0",
"@vueuse/core": "13.5.0",
"clipboard": "2.0.11",
"dayjs": "1.11.14",
"dayjs": "1.11.13",
"defu": "6.1.4",
"echarts": "6.0.0",
"echarts": "5.6.0",
"highlight.js": "^11.11.1",
"jsencrypt": "^3.3.2",
"json5": "2.2.3",
@ -80,47 +84,47 @@
"pinia": "3.0.3",
"streamsaver": "^2.0.6",
"tailwind-merge": "3.3.1",
"vue": "3.5.20",
"vue": "3.5.17",
"vue-advanced-cropper": "^2.8.9",
"vue-draggable-plus": "0.6.0",
"vue-i18n": "11.1.11",
"vue-router": "4.5.1",
"xlsx": "0.18.5"
"vue-i18n": "11.1.9",
"vue-router": "4.5.1"
},
"devDependencies": {
"@elegant-router/vue": "0.3.8",
"@iconify/json": "2.2.378",
"@iconify/json": "2.2.357",
"@sa/scripts": "workspace:*",
"@sa/uno-preset": "workspace:*",
"@soybeanjs/eslint-config": "1.7.1",
"@types/node": "24.3.0",
"@tauri-apps/cli": "2.5.0",
"@types/node": "24.0.13",
"@types/nprogress": "0.2.3",
"@unocss/eslint-config": "66.4.2",
"@unocss/preset-icons": "66.4.2",
"@unocss/preset-uno": "66.4.2",
"@unocss/transformer-directives": "66.4.2",
"@unocss/transformer-variant-group": "66.4.2",
"@unocss/vite": "66.4.2",
"@vitejs/plugin-vue": "6.0.1",
"@vitejs/plugin-vue-jsx": "5.1.0",
"@unocss/eslint-config": "66.3.3",
"@unocss/preset-icons": "66.3.3",
"@unocss/preset-uno": "66.3.3",
"@unocss/transformer-directives": "66.3.3",
"@unocss/transformer-variant-group": "66.3.3",
"@unocss/vite": "66.3.3",
"@vitejs/plugin-vue": "6.0.0",
"@vitejs/plugin-vue-jsx": "5.0.1",
"consola": "3.4.2",
"eslint": "9.34.0",
"eslint-plugin-vue": "10.4.0",
"eslint": "9.31.0",
"eslint-plugin-vue": "10.3.0",
"kolorist": "1.8.0",
"sass": "1.91.0",
"simple-git-hooks": "2.13.1",
"tsx": "4.20.5",
"typescript": "5.9.2",
"unplugin-icons": "22.2.0",
"unplugin-vue-components": "29.0.0",
"vite": "7.1.3",
"sass": "1.89.2",
"simple-git-hooks": "2.13.0",
"tsx": "4.20.3",
"typescript": "5.8.3",
"unplugin-icons": "22.1.0",
"unplugin-vue-components": "28.8.0",
"vite": "7.0.4",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-progress": "0.0.7",
"vite-plugin-static-copy": "^3.1.0",
"vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-devtools": "8.0.1",
"vite-plugin-vue-devtools": "7.7.7",
"vue-eslint-parser": "10.2.0",
"vue-tsc": "3.0.6"
"vue-tsc": "3.0.1"
},
"simple-git-hooks": {
"commit-msg": "pnpm sa git-commit-verify",

View File

@ -11,7 +11,7 @@
},
"dependencies": {
"@sa/utils": "workspace:*",
"axios": "1.11.0",
"axios": "1.10.0",
"axios-retry": "4.5.0",
"qs": "6.14.0"
},

View File

@ -64,10 +64,7 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
const { loading, startLoading, endLoading } = useLoading();
const { bool: empty, setBool: setEmpty } = useBoolean();
const { transformer, immediate = true, getColumnChecks, getColumns } = config;
let currentApiFn = config.apiFn;
const apiParams = config.apiParams;
const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config;
const searchParams: NonNullable<Parameters<A>[0]> = reactive(jsonClone({ ...apiParams }));
@ -97,7 +94,7 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
const formattedParams = formatSearchParams(searchParams);
const response = await currentApiFn(formattedParams);
const response = await apiFn(formattedParams);
const transformed = transformer(response as Awaited<ReturnType<A>>);
@ -122,10 +119,6 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
return formattedParams;
}
function updateApiFn(newApiFn: A) {
currentApiFn = newApiFn;
}
/**
* update search params
*
@ -155,7 +148,6 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
getData,
searchParams,
updateSearchParams,
resetSearchParams,
updateApiFn
resetSearchParams
};
}

View File

@ -14,14 +14,14 @@
},
"devDependencies": {
"@soybeanjs/changelog": "0.3.24",
"bumpp": "10.2.3",
"c12": "3.2.0",
"bumpp": "10.2.0",
"c12": "3.0.4",
"cac": "6.7.14",
"consola": "3.4.2",
"enquirer": "2.4.1",
"execa": "9.6.0",
"kolorist": "1.8.0",
"npm-check-updates": "18.0.3",
"npm-check-updates": "18.0.1",
"rimraf": "6.0.1"
}
}

2559
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

3
src-tauri/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Generated by Cargo
# will have compiled files and executables
/target/

4580
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

26
src-tauri/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
default-run = "app"
edition = "2021"
rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "2", features = [] }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
# If you use cargo directly instead of tauri's cli you can use this feature flag to switch between tauri's `dev` and `build` modes.
# DO NOT REMOVE!!
custom-protocol = [ "tauri/custom-protocol" ]

3
src-tauri/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@ -0,0 +1,7 @@
{
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": ["main"],
"permissions": ["core:default"]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
{
"migrated": {
"identifier": "migrated",
"description": "permissions that were migrated from v1",
"local": true,
"windows": ["main"],
"permissions": ["core:default"]
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
src-tauri/icons/64x64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

8
src-tauri/src/main.rs Normal file
View File

@ -0,0 +1,8 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

57
src-tauri/tauri.conf.json Normal file
View File

@ -0,0 +1,57 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"frontendDist": "../dist",
"devUrl": "http://localhost:9527"
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "RuoYi-Plus-Soybean",
"targets": "all",
"externalBin": [],
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
},
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"linux": {
"deb": {
"depends": []
}
}
},
"productName": "RuoYi-Plus-Soybean",
"mainBinaryName": "RuoYi-Plus-Soybean",
"version": "1.0.0",
"identifier": "org.dromara.admin",
"plugins": {},
"app": {
"windows": [
{
"fullscreen": false,
"height": 768,
"resizable": true,
"title": "RuoYi-Plus-Soybean",
"width": 1366,
"useHttpsScheme": true
}
],
"security": {
"csp": null
}
}
}

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import type { TagProps } from 'naive-ui';
import { jsonClone } from '@sa/utils';
import { useDict } from '@/hooks/business/dict';
import { isNotNull } from '@/utils/common';
import { $t } from '@/locales';
@ -29,7 +28,7 @@ const { transformDictData } = useDict(props.dictCode, props.immediate);
const dictTagData = computed<Api.System.DictData[]>(() => {
if (props.dictData) {
const dictData = jsonClone(props.dictData);
const dictData = props.dictData;
if (dictData.dictLabel?.startsWith(`dict.${dictData.dictType}.`)) {
dictData.dictLabel = $t(dictData.dictLabel as App.I18n.I18nKey);
}

View File

@ -2,7 +2,6 @@
import { computed } from 'vue';
import hljs from 'highlight.js/lib/core';
import json from 'highlight.js/lib/languages/json';
import { twMerge } from 'tailwind-merge';
hljs.registerLanguage('json', json);
@ -11,19 +10,15 @@ defineOptions({
});
interface Props {
class?: string;
code?: string;
showLineNumbers?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
class: '',
code: '',
showLineNumbers: false
});
const DEFAULT_CLASS = 'max-h-500px';
/** 格式化JSON数据 */
const jsonData = computed<string>(() => {
if (!props.code) return '';
@ -38,9 +33,9 @@ const jsonData = computed<string>(() => {
</script>
<template>
<NScrollbar :class="twMerge(DEFAULT_CLASS, props.class)">
<NCode :code="jsonData" :hljs="hljs" language="json" :show-line-numbers="showLineNumbers" :word-wrap="true" />
</NScrollbar>
<div class="json-preview">
<NCode :code="jsonData" :hljs="hljs" language="json" :show-line-numbers="showLineNumbers" />
</div>
</template>
<style lang="scss">
@ -49,4 +44,18 @@ html[class='dark'] {
background-color: #7c7777;
}
}
.json-preview {
width: 100%;
max-height: 500px;
overflow-y: auto;
@include scrollbar();
.empty-data {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
color: #999;
font-size: 14px;
}
}
</style>

View File

@ -1,79 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { NPopover, NSpace, NTag } from 'naive-ui';
interface Props {
value: string | any[];
type?: NaiveUI.ThemeColor;
size?: 'small' | 'medium' | 'large';
placeholder?: string;
closable?: boolean;
threadshold?: number; // 超过该数量显示popover
}
const props = withDefaults(defineProps<Props>(), {
type: 'info',
size: 'small',
placeholder: '无',
closable: false,
threadshold: 1 // 默认超过1个就显示popover
});
interface Emits {
(e: 'close', index?: number): void;
}
const emit = defineEmits<Emits>();
// 统一解析 value 成数组
const tags = computed(() => {
if (!props.value) return [];
return Array.isArray(props.value) ? props.value : props.value.split(',');
});
function handleClose(index?: number) {
emit('close', index);
}
</script>
<template>
<template v-if="tags.length === 0">
<NTag :size="size">
{{ placeholder }}
</NTag>
</template>
<template v-else-if="tags.length <= threadshold">
<NTag
v-for="(tag, index) in tags"
:key="index"
:type="type"
class="m-1"
:size="size"
:closable="closable"
@close="handleClose(index)"
>
{{ tag }}
</NTag>
</template>
<template v-else>
<NPopover trigger="hover" placement="bottom">
<template #trigger>
<NTag :type="type" :size="size" class="cursor-pointer">{{ tags[0] }}...({{ tags.length }})</NTag>
</template>
<NSpace vertical size="small">
<NTag
v-for="(tag, index) in tags"
:key="index"
:type="type"
:size="size"
:closable="closable"
@close="handleClose(index)"
>
{{ tag }}
</NTag>
</NSpace>
</NPopover>
</template>
</template>

View File

@ -1,370 +0,0 @@
<script setup lang="tsx">
import { computed, ref, watch } from 'vue';
import { NButton } from 'naive-ui';
import { useLoading } from '@sa/hooks';
import { fetchGetDeptTree, fetchGetUserList } from '@/service/api/system';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { useDict } from '@/hooks/business/dict';
import { $t } from '@/locales';
import UserSearch from '@/views/system/user/modules/user-search.vue';
import DictTag from './dict-tag.vue';
defineOptions({
name: 'UserSelectModal'
});
interface Props {
title?: string;
multiple?: boolean;
/** 禁选用户ID */
disabledIds?: CommonType.IdType[];
rowKeys?: CommonType.IdType[];
searchUserIds?: string | null;
}
const props = withDefaults(defineProps<Props>(), {
title: '用户选择',
multiple: false,
disabledIds: () => [],
rowKeys: () => [],
searchUserIds: null
});
interface Emits {
(e: 'confirm', value: CommonType.IdType[], rows?: Api.System.User[]): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
useDict('sys_normal_disable');
const appStore = useAppStore();
const {
columns,
columnChecks,
data,
getData,
getDataByPage,
loading,
mobilePagination,
searchParams,
resetSearchParams
} = useTable({
apiFn: fetchGetUserList,
apiParams: {
pageNum: 1,
pageSize: 10,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
deptId: null,
userName: null,
nickName: null,
phonenumber: null,
status: null,
params: {}
},
immediate: false,
columns: () => [
{
type: 'selection',
multiple: props.multiple,
align: 'center',
width: 48,
disabled: row => props.disabledIds.includes(row.userId.toString())
},
{
key: 'index',
title: $t('common.index'),
align: 'center',
width: 64
},
{
key: 'userName',
title: $t('page.system.user.userName'),
align: 'center',
minWidth: 120,
ellipsis: true
},
{
key: 'nickName',
title: $t('page.system.user.nickName'),
align: 'center',
minWidth: 120,
ellipsis: true
},
{
key: 'deptName',
title: $t('page.system.user.deptName'),
align: 'center',
minWidth: 120,
ellipsis: true
},
{
key: 'phonenumber',
title: $t('page.system.user.phonenumber'),
align: 'center',
minWidth: 120,
ellipsis: true
},
{
key: 'status',
title: $t('page.system.user.status'),
align: 'center',
minWidth: 80,
render(row) {
return <DictTag dict-code="sys_normal_disable" value={row.status} />;
}
},
{
key: 'createTime',
title: $t('page.system.user.createTime'),
align: 'center',
minWidth: 120
}
]
});
const { checkedRowKeys } = useTableOperate(data, getData);
// 存储所有页面的用户数据,用于跨页选择
const allPagesData = ref<Api.System.User[]>([]);
// 更新allPagesData保存当前页数据
function updateAllPagesData() {
// 将当前页数据添加到allPagesData中避免重复
data.value.forEach(user => {
const existIndex = allPagesData.value.findIndex(item => item.userId === user.userId);
if (existIndex === -1) {
allPagesData.value.push(user);
} else {
// 更新已存在的数据
allPagesData.value[existIndex] = user;
}
});
}
const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTreeLoading } = useLoading();
const deptPattern = ref<string>();
const deptData = ref<Api.Common.CommonTreeRecord>([]);
const selectedKeys = ref<string[]>([]);
async function getTreeData() {
startTreeLoading();
const { data: tree, error } = await fetchGetDeptTree();
if (!error) {
deptData.value = tree;
}
endTreeLoading();
}
function handleClickTree(keys: string[]) {
searchParams.deptId = keys.length ? keys[0] : null;
checkedRowKeys.value = [];
getDataByPage();
}
function handleResetTreeData() {
deptPattern.value = undefined;
getTreeData();
}
const expandedKeys = ref<CommonType.IdType[]>([100]);
const selectable = computed(() => {
return !loading.value;
});
function handleResetSearch() {
resetSearchParams();
selectedKeys.value = [];
}
function closeModal() {
checkedRowKeys.value = [];
allPagesData.value = [];
visible.value = false;
}
function handleConfirm() {
if (checkedRowKeys.value.length === 0) {
window.$message?.error('请选择用户');
return;
}
// 获取选中行对应的用户对象(从所有页面数据中筛选)
const selectedUsers = allPagesData.value.filter(item => checkedRowKeys.value.includes(item.userId.toString()));
emit('confirm', checkedRowKeys.value, selectedUsers);
closeModal();
}
function getRowProps(row: Api.System.User) {
return {
onClick: (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (target.closest('.n-data-table-td--selection')) {
return;
}
if (props.disabledIds.includes(row.userId.toString())) {
return;
}
// 将userId转为字符串
const userId = row.userId.toString();
if (props.multiple) {
const index = checkedRowKeys.value.findIndex(key => key === userId);
if (index > -1) {
checkedRowKeys.value.splice(index, 1);
} else {
checkedRowKeys.value.push(userId);
}
} else {
checkedRowKeys.value = [userId];
}
}
};
}
// 监听数据变化(页面切换时)
watch(
data,
() => {
updateAllPagesData();
},
{ deep: true }
);
watch(visible, () => {
if (visible.value) {
getTreeData();
if (props.searchUserIds) {
searchParams.userIds = props.searchUserIds;
}
allPagesData.value = [];
getDataByPage();
checkedRowKeys.value = [...props.rowKeys];
}
});
</script>
<template>
<NModal
v-model:show="visible"
class="user-select-modal max-h-800px max-w-90% w-1400px"
preset="card"
size="medium"
:title="props.title"
>
<TableSiderLayout class="bg-gray-50 p-2" :sider-title="$t('page.system.dept.title')">
<template #header-extra>
<NButton size="small" text class="h-18px" @click.stop="() => handleResetTreeData()">
<template #icon>
<SvgIcon icon="ic:round-refresh" />
</template>
</NButton>
</template>
<template #sider>
<NInput v-model:value="deptPattern" clearable :placeholder="$t('common.keywordSearch')" />
<NSpin class="dept-tree" :show="treeLoading">
<NTree
v-model:expanded-keys="expandedKeys"
v-model:selected-keys="selectedKeys"
block-node
show-line
:data="deptData as []"
:show-irrelevant-nodes="false"
:pattern="deptPattern"
class="infinite-scroll h-full min-h-200px py-3"
key-field="id"
label-field="label"
virtual-scroll
:selectable="selectable"
@update:selected-keys="handleClickTree"
>
<template #empty>
<NEmpty :description="$t('page.system.dept.empty')" class="h-full min-h-200px justify-center" />
</template>
</NTree>
</NSpin>
</template>
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:max-h-500px lt-sm:overflow-auto">
<UserSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
<TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
<NAlert v-if="props.disabledIds.length > 0" type="warning">
<span>已存在的用户无法被选择</span>
</NAlert>
<NCard
:title="$t('page.system.user.title')"
:bordered="false"
size="small"
class="card-wrapper sm:flex-1-hidden lt-sm:overflow-auto"
>
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:loading="loading"
:show-add="false"
:show-delete="false"
:show-export="false"
@refresh="getData"
></TableHeaderOperation>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="962"
:loading="loading"
:row-props="getRowProps"
remote
:row-key="row => row.userId.toString()"
:pagination="mobilePagination"
class="h-full lt-sm:max-h-300px"
/>
</NCard>
</div>
</TableSiderLayout>
<template #footer>
<NSpace justify="end" :size="16">
<NButton @click="closeModal">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleConfirm">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
</NModal>
</template>
<style scoped lang="scss">
:deep(.n-layout) {
height: 600px;
@media (max-width: 639px) {
height: auto;
max-height: 500px;
}
}
.user-select-modal {
@media (max-width: 639px) {
:deep(.n-card-content) {
overflow: hidden;
}
:deep(.n-data-table) {
max-height: 300px;
}
}
}
.n-alert {
--n-padding: 5px 13px !important;
--n-icon-margin: 6px 8px 0 12px !important;
--n-icon-size: 20px !important;
}
</style>

View File

@ -1,524 +1,61 @@
<!-- Copyright By https://github.com/Daymychen/art-design-pro/blob/main/src/components/core/views/login/LoginLeftView.vue -->
<script lang="ts" setup>
import { useThemeStore } from '@/store/modules/theme';
import { computed } from 'vue';
import { getPaletteColorByNumber } from '@sa/color';
defineOptions({ name: 'WaveBg' });
const themeStore = useThemeStore();
function toggleThemeScheme() {
if (themeStore.darkMode) {
themeStore.setThemeScheme('light');
return;
}
themeStore.setThemeScheme('dark');
interface Props {
/** Theme color */
themeColor: string;
}
const props = defineProps<Props>();
const lightColor = computed(() => getPaletteColorByNumber(props.themeColor, 200));
const darkColor = computed(() => getPaletteColorByNumber(props.themeColor, 500));
</script>
<template>
<div class="wave-bg">
<!-- 几何装饰元素 -->
<div class="geometric-decorations">
<!-- 基础几何形状 -->
<div class="geo-element circle-outline animate-fade-in-up animate-delay-0s"></div>
<div class="geo-element square-rotated animate-fade-in-left animate-delay-0s"></div>
<div class="geo-element circle-small animate-fade-in-up animate-delay-0.3s"></div>
<div class="geo-element square-bottom-right animate-fade-in-right animate-delay-0s"></div>
<!-- 背景泡泡 -->
<div class="geo-element bg-bubble animate-scale-in animate-delay-0.5s"></div>
<!-- 太阳/月亮 -->
<div
class="geo-element circle-top-right animate-fade-in-down animate-delay-0.5s"
@click="toggleThemeScheme"
></div>
<!-- 装饰点 -->
<div class="geo-element dot dot-top-left animate-bounce-in animate-delay-0s"></div>
<div class="geo-element dot dot-top-right animate-bounce-in animate-delay-0s"></div>
<div class="geo-element dot dot-center-right animate-bounce-in animate-delay-0s"></div>
<!-- 叠加方块组 -->
<div class="squares-group">
<i class="geo-element square square-blue animate-fade-in-left-rotated-blue animate-delay-0.2s"></i>
<i class="geo-element square square-pink animate-fade-in-left-rotated-pink animate-delay-0.4s"></i>
<i class="geo-element square square-purple animate-fade-in-left-no-rotation animate-delay-0.6s"></i>
</div>
<div class="absolute-lt z-1 size-full overflow-hidden">
<div class="absolute -right-300px -top-900px lt-sm:(-right-100px -top-1170px)">
<svg height="1337" width="1337">
<defs>
<path
id="path-1"
opacity="1"
fill-rule="evenodd"
d="M1337,668.5 C1337,1037.455193874239 1037.455193874239,1337 668.5,1337 C523.6725684305388,1337 337,1236 370.50000000000006,1094 C434.03835568300906,824.6732385973953 6.906089672974592e-14,892.6277623047779 0,668.5000000000001 C0,299.5448061257611 299.5448061257609,1.1368683772161603e-13 668.4999999999999,0 C1037.455193874239,0 1337,299.544806125761 1337,668.5Z"
/>
<linearGradient id="linearGradient-2" x1="0.79" y1="0.62" x2="0.21" y2="0.86">
<stop offset="0" :stop-color="lightColor" stop-opacity="1" />
<stop offset="1" :stop-color="darkColor" stop-opacity="1" />
</linearGradient>
</defs>
<g opacity="1">
<use xlink:href="#path-1" fill="url(#linearGradient-2)" fill-opacity="1" />
</g>
</svg>
</div>
<div class="absolute -bottom-400px -left-200px lt-sm:(-bottom-760px -left-100px)">
<svg height="896" width="967.8852157128662">
<defs>
<path
id="path-2"
opacity="1"
fill-rule="evenodd"
d="M896,448 C1142.6325445712241,465.5747656464056 695.2579309733121,896 448,896 C200.74206902668806,896 5.684341886080802e-14,695.2579309733121 0,448.0000000000001 C0,200.74206902668806 200.74206902668791,5.684341886080802e-14 447.99999999999994,0 C695.2579309733121,0 475,418 896,448Z"
/>
<linearGradient id="linearGradient-3" x1="0.5" y1="0" x2="0.5" y2="1">
<stop offset="0" :stop-color="darkColor" stop-opacity="1" />
<stop offset="1" :stop-color="lightColor" stop-opacity="1" />
</linearGradient>
</defs>
<g opacity="1">
<use xlink:href="#path-2" fill="url(#linearGradient-3)" fill-opacity="1" />
</g>
</svg>
</div>
</div>
</template>
<style lang="scss" scoped>
// 颜色变量定义
$primary-light-7: rgb(var(--primary-50-color));
$primary-light-8: rgb(var(--primary-100-color));
$primary-light-9: rgb(var(--primary-200-color));
$primary-base: rgb(var(--primary-color));
$main-bg: rgb(var(--primary-50-color));
// 混合颜色函数
$bg-mix-light-9: color-mix(in srgb, $primary-light-9 100%, $main-bg);
$bg-mix-light-8: color-mix(in srgb, $primary-light-8 80%, $main-bg);
$bg-mix-light-7: color-mix(in srgb, $primary-light-7 80%, $main-bg);
.wave-bg {
.geometric-decorations {
.geo-element {
position: absolute;
opacity: 0;
animation-fill-mode: forwards;
animation-duration: 0.8s;
animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
// 动画 mixin
@mixin fadeAnimation($direction: '', $rotation: 0deg) {
from {
opacity: 0;
@if $direction == 'up' {
transform: translateY(30px) rotate($rotation);
} @else if $direction == 'down' {
transform: translateY(-30px) rotate($rotation);
} @else if $direction == 'left' {
transform: translateX(-30px) rotate($rotation);
} @else if $direction == 'right' {
transform: translateX(30px) rotate($rotation);
}
}
to {
opacity: 1;
@if $direction == 'up' or $direction == 'down' {
transform: translateY(0) rotate($rotation);
} @else {
transform: translateX(0) rotate($rotation);
}
}
}
// 动画定义
@keyframes fadeInUp {
@include fadeAnimation('up');
}
@keyframes fadeInDown {
@include fadeAnimation('down');
}
@keyframes fadeInLeft {
@include fadeAnimation('left');
}
@keyframes fadeInLeftRotated {
@include fadeAnimation('left', -25deg);
}
@keyframes fadeInRight {
@include fadeAnimation('right');
}
@keyframes fadeInRightRotated {
@include fadeAnimation('right', 45deg);
}
@keyframes fadeInLeftRotatedBlue {
@include fadeAnimation('left', -10deg);
}
@keyframes fadeInLeftRotatedPink {
@include fadeAnimation('left', 10deg);
}
@keyframes fadeInLeftNoRotation {
@include fadeAnimation('left');
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes bounceIn {
0% {
opacity: 0;
transform: scale(0.3);
}
50% {
opacity: 1;
transform: scale(1.05);
}
70% {
transform: scale(0.9);
}
100% {
opacity: 1;
transform: scale(1);
}
}
@keyframes lineGrow {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
// 动画类
.animate-fade-in-up {
animation-name: fadeInUp;
}
.animate-fade-in-down {
animation-name: fadeInDown;
}
.animate-fade-in-left {
animation-name: fadeInLeft;
}
.animate-fade-in-right {
animation-name: fadeInRight;
}
.animate-scale-in {
animation-name: scaleIn;
animation-duration: 1.2s;
}
.animate-bounce-in {
animation-name: bounceIn;
animation-duration: 0.6s;
}
.animate-fade-in-left-rotated-blue {
animation-name: fadeInLeftRotatedBlue;
}
.animate-fade-in-left-rotated-pink {
animation-name: fadeInLeftRotatedPink;
}
.animate-fade-in-left-no-rotation {
animation-name: fadeInLeftNoRotation;
}
// 基础几何形状
.circle-outline {
top: 10%;
left: 25%;
width: 42px;
height: 42px;
border: 2px solid $primary-light-8;
border-radius: 50%;
}
.square-rotated {
top: 50%;
left: 16%;
width: 60px;
height: 60px;
background-color: $bg-mix-light-8;
&.animate-fade-in-left {
animation-name: fadeInLeftRotated;
}
}
.circle-small {
bottom: 26%;
left: 30%;
width: 18px;
height: 18px;
background-color: $primary-light-8;
border-radius: 50%;
}
// 太阳/月亮效果
.circle-top-right {
top: 3%;
right: 3%;
z-index: 100;
width: 50px;
height: 50px;
cursor: pointer;
background: $bg-mix-light-7;
border-radius: 50%;
transition: all 0.3s;
&::after {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
content: '';
background: linear-gradient(to right, #fcbb04, #fffc00);
border-radius: 50%;
opacity: 0;
transition: all 0.5s;
transform: translate(-50%, -50%);
}
&:hover {
box-shadow: 0 0 36px #fffc00;
&::after {
opacity: 1;
}
}
}
.square-bottom-right {
right: 10%;
bottom: 10%;
width: 50px;
height: 50px;
background-color: $primary-light-8;
&.animate-fade-in-right {
animation-name: fadeInRightRotated;
}
}
// 背景泡泡
.bg-bubble {
top: -120px;
right: -120px;
width: 360px;
height: 360px;
background-color: $bg-mix-light-8;
border-radius: 50%;
}
// 装饰点
.dot {
width: 14px;
height: 14px;
background-color: $primary-light-7;
border-radius: 50%;
&.dot-top-left {
top: 140px;
left: 100px;
}
&.dot-top-right {
top: 140px;
right: 120px;
}
&.dot-center-right {
top: 46%;
right: 22%;
background-color: $primary-light-8;
}
}
// 叠加方块组
.squares-group {
position: absolute;
bottom: 18px;
left: 20px;
width: 140px;
height: 140px;
pointer-events: none;
.square {
position: absolute;
display: block;
border-radius: 8px;
box-shadow: 0 8px 24px rgb(64 87 167 / 12%);
&.square-blue {
top: 12px;
left: 30px;
z-index: 2;
width: 50px;
height: 50px;
background-color: rgb(from $primary-base r g b / 30%);
}
&.square-pink {
top: 30px;
left: 48px;
z-index: 1;
width: 70px;
height: 70px;
background-color: rgb(from $primary-base r g b / 15%);
}
&.square-purple {
top: 66px;
left: 86px;
z-index: 3;
width: 32px;
height: 32px;
background-color: rgb(from $primary-base r g b / 45%);
}
}
// 装饰线条
&::after {
position: absolute;
top: 86px;
left: 72px;
width: 80px;
height: 1px;
content: '';
background: linear-gradient(90deg, var(--el-color-primary-light-6), transparent);
opacity: 0;
transform: rotate(50deg);
animation: lineGrow 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
animation-delay: 1.2s;
}
}
}
@media only screen and (max-width: 1600px) {
width: 60vw;
.text-wrap {
bottom: 40px;
}
}
@media only screen and (max-width: 1280px) {
width: auto;
height: auto;
padding: 0;
// 隐藏背景和其他内容,只保留 logo
background: transparent;
.left-img,
.text-wrap,
.geometric-decorations {
display: none;
}
.logo {
position: fixed;
top: 15px;
left: 25px;
z-index: 1000;
}
}
}
// 暗色主题
.dark .wave-bg {
background-color: color-mix(in srgb, $primary-light-9 60%, #070707);
@media only screen and (max-width: 1280px) {
background: transparent;
}
.geometric-decorations {
// 月亮效果
.circle-top-right {
background-color: $bg-mix-light-8;
box-shadow: 0 0 25px #333 inset;
transition: all 0.3s ease-in-out 0.1s;
rotate: -48deg;
&::before {
position: absolute;
top: 0;
left: 15px;
width: 50px;
height: 50px;
content: '';
background-color: $bg-mix-light-9;
border-radius: 50%;
transition: all 0.3s ease-in-out;
}
&:hover {
background-color: transparent;
box-shadow: 0 40px 25px #ddd inset;
&::before {
left: 18px;
}
&::after {
opacity: 0;
}
}
}
.bg-bubble {
background-color: $bg-mix-light-9;
}
// 其他元素颜色调整
.square-rotated {
background-color: $bg-mix-light-9;
}
.circle-small,
.dot {
background-color: $primary-light-8;
}
.square-bottom-right {
background-color: $primary-light-9;
}
.dot.dot-top-right {
background-color: $primary-light-8;
}
}
// 方块组暗色调整
.squares-group {
.square {
box-shadow: none;
&.square-blue {
background-color: rgb(from $primary-base r g b / 18%);
}
&.square-pink {
background-color: rgb(from $primary-base r g b / 10%);
}
&.square-purple {
background-color: rgb(from $primary-base r g b / 20%);
}
}
&::after {
background: linear-gradient(90deg, $primary-light-8, transparent);
}
}
}
</style>
<style scoped></style>

View File

@ -1,178 +0,0 @@
<script setup lang="tsx">
import { ref } from 'vue';
import { NPopover, NSpace, NTag } from 'naive-ui';
import { useLoading } from '@sa/hooks';
import { fetchGetFlowHisTaskList } from '@/service/api/workflow/instance';
import { fetchGetOssListByIds } from '@/service/api/system/oss';
import { useDict } from '@/hooks/business/dict';
import { useDownload } from '@/hooks/business/download';
import DictTag from '@/components/custom/dict-tag.vue';
import TagGroup from '@/components/custom/tag-group.vue';
defineOptions({
name: 'ApprovalInfoPanel'
});
interface Props {
/** 业务id */
businessId: CommonType.IdType;
}
const props = defineProps<Props>();
useDict('wf_task_status');
const activeTab = ref('image');
const { loading, startLoading, endLoading } = useLoading();
const { oss } = useDownload();
const columns = ref<NaiveUI.TableColumn<Api.Workflow.HisTask>[]>([
{
title: '任务名称',
key: 'nodeName',
align: 'center',
width: 100
},
{
title: '办理人',
key: 'approveName',
align: 'center',
width: 100,
render: row => {
return <TagGroup value={row.approveName} />;
}
},
{
title: '任务状态',
key: 'flowStatus',
align: 'center',
width: 100,
render: row => {
return <DictTag size="small" value={row.flowStatus} dict-code="wf_task_status" />;
}
},
{
title: '审批意见',
key: 'message',
align: 'center',
width: 100
},
{
title: '开始时间',
key: 'createTime',
align: 'center',
width: 120
},
{
title: '结束时间',
key: 'updateTime',
align: 'center',
width: 120
},
{
title: '运行时间',
key: 'runDuration',
align: 'center',
width: 100
},
{
title: '附件',
key: 'attachmentList',
align: 'center',
width: 120,
render: row => {
if (!row.attachmentList || row.attachmentList.length === 0) return null;
if (row.attachmentList.length === 1) {
return (
<NTag size="small" type="info" class="cursor-pointer">
<div class="flex items-center gap-2" onClick={() => oss(row.attachmentList[0].ossId)}>
{row.attachmentList[0].originalName}
</div>
</NTag>
);
}
return (
<NPopover trigger="hover" placement="bottom">
{{
trigger: () => (
<NTag size="small" type="info" class="cursor-pointer">
{row.attachmentList[0].originalName}...({row.attachmentList.length})
</NTag>
),
default: () => (
<NSpace vertical size="small">
{row.attachmentList.map(item => (
<NTag key={item.ossId} size="small" type="info" class="cursor-pointer">
<div class="flex items-center gap-2" onClick={() => oss(item.ossId)}>
{item.originalName}
</div>
</NTag>
))}
</NSpace>
)
}}
</NPopover>
);
}
}
]);
const instanceId = ref<CommonType.IdType>();
const hisTask = ref<Api.Workflow.HisTask[]>([]);
/** 初始化数据 */
async function initData() {
activeTab.value = 'image';
instanceId.value = undefined;
hisTask.value = [];
await getData();
}
async function getData() {
startLoading();
const { error, data } = await fetchGetFlowHisTaskList(props.businessId);
if (error) {
window.$message?.error(error.message);
return;
}
instanceId.value = data?.instanceId || '';
const rawList = data?.list || [];
if (rawList.length === 0) {
hisTask.value = [];
return;
}
const promises = rawList.map(async (item: Api.Workflow.HisTask) => {
if (item.ext) {
const { error: err, data: ossList } = await fetchGetOssListByIds(item.ext.split(','));
if (!err) {
item.attachmentList = ossList;
}
}
});
await Promise.all(promises);
hisTask.value = rawList;
endLoading();
}
defineExpose({
initData
});
</script>
<template>
<NDivider />
<div class="h-full">
<NTabs v-model:value="activeTab" type="segment" animated>
<NTabPane display-directive="show" bar-width="100px" name="image" tab="流程图">
<FlowPreview v-if="instanceId" :instance-id="instanceId" />
</NTabPane>
<NTabPane bar-width="100px" name="info" tab="审批信息">
<NDataTable size="small" :scroll-x="760" :columns="columns" :data="hisTask" :loading="loading" />
</NTabPane>
</NTabs>
</div>
</template>

View File

@ -1,142 +0,0 @@
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue';
import type { UploadFileInfo } from 'naive-ui';
import { useLoading } from '@sa/hooks';
import { messageTypeOptions } from '@/constants/workflow';
import { fetchBackTask, fetchGetBackNode } from '@/service/api/workflow/task';
defineOptions({
name: 'BackTaskModal'
});
interface Props {
task: Api.Workflow.Task;
}
const props = defineProps<Props>();
interface Emits {
(e: 'submit'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const title = defineModel<string>('title', {
default: '驳回'
});
const { loading: backFormLoading, startLoading: startBackFormLoading, endLoading: endBackFormLoading } = useLoading();
const { loading: backBtnLoading, startLoading: startBackBtnLoading, endLoading: endBackBtnLoading } = useLoading();
const accept = ref<string>('.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.pdf,.jpg,.jpeg,.png,.gif,.bmp,.webp');
type Model = Api.Workflow.BackOperateParams;
const backModel = reactive(createBackModel());
function createBackModel(): Model {
return {
taskId: null,
fileId: null,
messageType: ['1'],
nodeCode: null,
message: null,
notice: null,
variables: null
};
}
const fileList = ref<UploadFileInfo[]>([]);
const backTaskNodeOptions = ref<CommonType.Option<string, string>[]>([]);
async function initDefault() {
startBackFormLoading();
startBackBtnLoading();
Object.assign(backModel, createBackModel());
const { error, data } = await fetchGetBackNode(props.task.definitionId, props.task.nodeCode);
endBackFormLoading();
endBackBtnLoading();
if (error) return;
backTaskNodeOptions.value = data.map(item => ({
label: item.nodeName,
value: item.nodeCode
}));
backModel.nodeCode = data[0].nodeCode;
}
async function handleSubmit() {
backModel.taskId = props.task.id;
if (fileList.value?.length) {
const fileIds = fileList.value.map(item => item.id);
backModel.fileId = fileIds.join(',');
}
window.$dialog?.warning({
title: '提示',
content: `是否确认驳回?`,
positiveText: `确认驳回`,
positiveButtonProps: {
type: 'primary'
},
negativeText: '取消',
onPositiveClick: async () => {
startBackBtnLoading();
startBackFormLoading();
const { error } = await fetchBackTask(backModel);
endBackBtnLoading();
endBackFormLoading();
if (error) return;
window.$message?.success('驳回成功');
closeDrawer();
emit('submit');
}
});
}
function closeDrawer() {
visible.value = false;
}
watch(visible, () => {
if (visible.value) {
initDefault();
}
});
</script>
<template>
<NModal v-model:show="visible" preset="card" class="w-800px" :title="title" :native-scrollbar="false" closable>
<NSpin :show="backFormLoading">
<NForm v-if="task.flowStatus === 'waiting'" :model="backModel">
<NFormItem label="驳回节点" path="nodeCode">
<NSelect v-model:value="backModel.nodeCode" :options="backTaskNodeOptions" />
</NFormItem>
<NFormItem label="通知方式" path="messageType">
<NCheckboxGroup v-model:value="backModel.messageType">
<NSpace item-style="display: flex;">
<NCheckbox
v-for="item in messageTypeOptions"
:key="item.value"
:disabled="item.value === '1'"
:value="item.value"
:label="item.label"
/>
</NSpace>
</NCheckboxGroup>
</NFormItem>
<NFormItem label="附件" path="fileId">
<FileUpload v-model:file-list="fileList" :file-size="20" :max="20" upload-type="file" :accept="accept" />
</NFormItem>
<NFormItem label="审批意见" path="message">
<NInput v-model:value="backModel.message" type="textarea" />
</NFormItem>
</NForm>
</NSpin>
<template #footer>
<NSpace justify="end" :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton :loading="backBtnLoading" type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
</NModal>
</template>

View File

@ -1,62 +0,0 @@
<script setup lang="tsx">
import { computed, useAttrs } from 'vue';
import type { TreeSelectProps } from 'naive-ui';
import { useLoading } from '@sa/hooks';
import { fetchGetCategoryTree } from '@/service/api/workflow';
import { isNull } from '@/utils/common';
defineOptions({ name: 'FlowCategorySelect' });
interface Props {
[key: string]: any;
}
defineProps<Props>();
const rawValue = defineModel<CommonType.IdType | null>('value', { required: false });
const options = defineModel<Api.Common.CommonTreeRecord>('options', { required: false, default: [] });
const expandedKeys = defineModel<CommonType.IdType[]>('expandedKeys', { required: false, default: [] });
const attrs: TreeSelectProps = useAttrs();
const { loading, startLoading, endLoading } = useLoading();
/** 转换为strid可能是number类型或者String类型导致回显失败 */
const strValue = computed({
get() {
return isNull(rawValue.value) ? null : rawValue.value?.toString();
},
set(val) {
rawValue.value = val;
}
});
async function getCategoryList() {
startLoading();
const { error, data } = await fetchGetCategoryTree();
if (error) return;
options.value = data;
// 设置默认展开的节点
if (data?.length && !expandedKeys.value.length) {
expandedKeys.value = [data[0].id];
}
endLoading();
}
getCategoryList();
</script>
<template>
<NTreeSelect
v-model:value="strValue"
v-model:expanded-keys="expandedKeys"
filterable
class="h-full"
:loading="loading"
key-field="id"
label-field="label"
:options="options as []"
v-bind="attrs"
/>
</template>
<style scoped></style>

View File

@ -1,96 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
import { $t } from '@/locales';
interface Props {
/** 抽屉是否可见 */
visible?: boolean;
/** 抽屉标题 */
title: string;
/** 是否显示加载状态 */
loading?: boolean;
/** 抽屉宽度 */
width?: number;
operateType: CommonType.WorkflowTableOperateType;
status?: string | null;
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
loading: false,
width: 1200,
status: null
});
interface Emits {
(e: 'update:visible', visible: boolean): void;
(e: 'close'): void;
(e: 'saveDraft'): void;
(e: 'submit'): void;
(e: 'approval'): void;
}
const emit = defineEmits<Emits>();
const visibleValue = computed({
get: () => props.visible,
set: value => {
emit('update:visible', value);
}
});
const showSubmit = computed(
() =>
props.operateType === 'add' ||
(props.operateType === 'edit' &&
props.status &&
(props.status === 'draft' || props.status === 'cancel' || props.status === 'back'))
);
const showApproval = computed(() => props.operateType === 'approval' && props.status && props.status === 'waiting');
function handleClose() {
emit('close');
}
function handleSaveDraft() {
emit('saveDraft');
}
function handleSubmit() {
emit('submit');
}
function handleApproval() {
emit('approval');
}
defineExpose({
handleClose,
handleSaveDraft,
handleSubmit,
handleApproval
});
</script>
<template>
<NDrawer v-model:show="visibleValue" :title="title" display-directive="show" :width="width" class="max-w-90%">
<NDrawerContent :title="title" :native-scrollbar="false" closable @close="handleClose">
<NSpin :show="loading">
<slot></slot>
</NSpin>
<template #footer>
<slot name="footer">
<div>
<NSpace :size="16">
<NButton v-if="showSubmit || showApproval" @click="handleClose">{{ $t('common.cancel') }}</NButton>
<NButton v-if="showSubmit" type="warning" @click="handleSaveDraft">暂存</NButton>
<NButton v-if="showSubmit" type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
<NButton v-if="showApproval" type="primary" @click="handleApproval">办理</NButton>
</NSpace>
</div>
</slot>
</template>
</NDrawerContent>
</NDrawer>
</template>
<style scoped></style>

View File

@ -1,222 +0,0 @@
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue';
import { useBoolean, useLoading } from '@sa/hooks';
import { fetchGetTask, fetchTaskOperate, fetchTerminateTask } from '@/service/api/workflow/task';
import ReduceSignatureModal from './reduce-signature-modal.vue';
defineOptions({
name: 'FlowInterveneModal'
});
const { loading, startLoading, endLoading } = useLoading();
const { bool: addSignatureVisible, setTrue: openAddSignatureModal } = useBoolean();
const { bool: transferVisible, setTrue: openTransferModal } = useBoolean();
const { bool: reduceSignatureVisible, setTrue: openReduceSignatureModal } = useBoolean();
interface Props {
taskId: CommonType.IdType;
assigneeIds: CommonType.IdType[];
assigneeNames: string[];
}
const props = defineProps<Props>();
interface Emits {
(e: 'refresh'): void;
}
const emit = defineEmits<Emits>();
const taskInfo = ref<Api.Workflow.Task>();
const isWaiting = computed(() => taskInfo.value?.flowStatus === 'waiting');
// 流程签署比例值 大于0为票签会签
const isTicketOrSignInstance = computed(() => Number(taskInfo.value?.nodeRatio) > 0);
const visible = defineModel<boolean>('visible', {
default: false
});
type Model = Api.Workflow.TaskOperateParams;
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
taskId: null,
userId: undefined,
userIds: undefined,
message: ''
};
}
type TerminateModel = Api.Workflow.TerminateTaskOperateParams;
const terminateModel: TerminateModel = reactive(createDefaultTerminateModel());
function createDefaultTerminateModel(): TerminateModel {
return {
taskId: null,
comment: ''
};
}
function handleTransferConfirm(ids: CommonType.IdType[]) {
model.userId = ids[0];
model.taskId = props.taskId;
window.$dialog?.warning({
title: '提示',
content: '是否确认转办?',
positiveText: '确认转办',
positiveButtonProps: {
type: 'primary'
},
negativeText: '取消',
onPositiveClick: async () => {
const { error } = await fetchTaskOperate(model, 'transferTask');
if (error) return;
window.$message?.success('转办成功');
visible.value = false;
emit('refresh');
}
});
}
function handleAddSignatureConfirm(ids: CommonType.IdType[]) {
model.userIds = ids;
window.$dialog?.warning({
title: '提示',
content: '是否确认加签?',
positiveText: '确认加签',
positiveButtonProps: {
type: 'primary'
},
negativeText: '取消',
onPositiveClick: async () => {
const { error } = await fetchTaskOperate(model, 'addSignature');
if (error) return;
window.$message?.success('加签成功');
visible.value = false;
emit('refresh');
}
});
}
function handleTerminate() {
terminateModel.taskId = props.taskId;
window.$dialog?.warning({
title: '提示',
content: '是否确认终止?',
positiveText: '确认',
positiveButtonProps: {
type: 'primary'
},
negativeText: '取消',
onPositiveClick: async () => {
const { error } = await fetchTerminateTask(terminateModel);
if (error) return;
window.$message?.success('终止成功');
visible.value = false;
emit('refresh');
}
});
}
function handleReduceSubmit() {
visible.value = false;
emit('refresh');
}
async function getTaskInfo() {
startLoading();
const { error, data } = await fetchGetTask(props.taskId);
if (error) return;
taskInfo.value = data;
endLoading();
}
watch(visible, () => {
if (visible.value) {
getTaskInfo();
}
});
</script>
<template>
<NModal
v-model:show="visible"
class="max-h-520px max-w-90% w-700px"
title="流程干预"
preset="card"
size="medium"
:native-scrollbar="false"
>
<NSpin :show="loading">
<NDescriptions
:title="`${taskInfo?.flowName} (${taskInfo?.flowCode})`"
label-placement="left"
:column="2"
size="small"
bordered
>
<NDescriptionsItem label="任务名称">
{{ taskInfo?.nodeName }}
</NDescriptionsItem>
<NDescriptionsItem label="节点编码">
{{ taskInfo?.nodeCode }}
</NDescriptionsItem>
<NDescriptionsItem label="开始时间">
{{ taskInfo?.createTime }}
</NDescriptionsItem>
<NDescriptionsItem label="流程实例ID">
{{ taskInfo?.instanceId }}
</NDescriptionsItem>
<NDescriptionsItem label="办理人">
<TagGroup :value="assigneeNames" />
</NDescriptionsItem>
<NDescriptionsItem label="版本号">
<NTag type="info" size="small">v{{ taskInfo?.version }}.0</NTag>
</NDescriptionsItem>
<NDescriptionsItem label="业务ID">
{{ taskInfo?.businessId }}
</NDescriptionsItem>
</NDescriptions>
</NSpin>
<template #footer>
<NSpace justify="end" :size="16">
<NButton v-if="isWaiting" :loading="loading" type="primary" @click="openTransferModal">转办</NButton>
<NButton
v-if="isWaiting && isTicketOrSignInstance"
:loading="loading"
type="primary"
@click="openAddSignatureModal"
>
加签
</NButton>
<NButton
v-if="isWaiting && isTicketOrSignInstance"
:loading="loading"
type="primary"
@click="openReduceSignatureModal"
>
减签
</NButton>
<NButton v-if="isWaiting" :loading="loading" type="error" @click="handleTerminate">终止</NButton>
</NSpace>
</template>
<!-- 转办用户选择器 -->
<UserSelectModal v-model:visible="transferVisible" :disabled-ids="assigneeIds" @confirm="handleTransferConfirm" />
<!-- 加签用户选择器 -->
<UserSelectModal
v-model:visible="addSignatureVisible"
multiple
:disabled-ids="assigneeIds"
@confirm="handleAddSignatureConfirm"
/>
<!-- 减签用户 -->
<ReduceSignatureModal
v-model:visible="reduceSignatureVisible"
:task="taskInfo!"
@reduce-submit="handleReduceSubmit"
/>
</NModal>
</template>

View File

@ -1,30 +0,0 @@
<script setup lang="ts">
import { stringify } from 'qs';
import { getToken } from '@/store/modules/auth/shared';
import { getServiceBaseURL } from '@/utils/service';
defineOptions({
name: 'FlowPreview'
});
interface Props {
instanceId: CommonType.IdType;
}
const props = defineProps<Props>();
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
const urlParams = {
type: 'FlowChart',
id: props.instanceId,
Authorization: `Bearer ${getToken()}`,
clientid: import.meta.env.VITE_APP_CLIENT_ID || ''
};
const iframeUrl = `${baseURL}/warm-flow-ui/index.html?${stringify(urlParams)}`;
</script>
<template>
<div>
<iframe :src="iframeUrl" class="h-[450px] w-full" />
</div>
</template>

View File

@ -1,496 +0,0 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import type { UploadFileInfo } from 'naive-ui';
import { useBoolean, useLoading } from '@sa/hooks';
import { messageTypeOptions } from '@/constants/workflow';
import {
fetchCompleteTask,
fetchGetTask,
fetchGetkNextNode,
fetchTaskOperate,
fetchTerminateTask
} from '@/service/api/workflow';
import FileUpload from '@/components/custom/file-upload.vue';
import ReduceSignatureModal from './reduce-signature-modal.vue';
import BackTaskModal from './back-task-modal.vue';
defineOptions({
name: 'FlowTaskApprovalModal'
});
interface Props {
/** 任务id */
taskId: CommonType.IdType;
/** 任务变量 */
taskVariables: { [key: string]: any };
}
const props = defineProps<Props>();
interface Emits {
(e: 'finished'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const { loading: baseFormLoading, startLoading: startBaseFormLoading, endLoading: endBaseFormLoading } = useLoading();
const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading();
const { bool: copyVisible, setTrue: openCopyModal } = useBoolean();
const { bool: assigneeVisible, setTrue: openAssigneeModal } = useBoolean();
const { bool: delegateVisible, setTrue: openDelegateModal, setFalse: closeDelegateModal } = useBoolean();
const { bool: transferVisible, setTrue: openTransferModal, setFalse: closeTransferModal } = useBoolean();
const { bool: addSignatureVisible, setTrue: openAddSignatureModal, setFalse: closeAddSignatureModal } = useBoolean();
const { bool: reduceSignatureVisible, setTrue: openReduceSignatureModal } = useBoolean();
const { bool: backVisible, setTrue: openBackModal } = useBoolean();
const title = defineModel<string>('title', {
default: '流程发起'
});
const accept = ref<string>('.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.pdf,.jpg,.jpeg,.png,.gif,.bmp,.webp');
type Model = Api.Workflow.CompleteTaskOperateParams;
const task = ref<Api.Workflow.Task>();
const isWaiting = computed(() => task.value?.flowStatus === 'waiting');
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
taskId: null,
fileId: null,
flowCopyList: [],
messageType: ['1'],
taskVariables: null,
variables: null,
assigneeMap: null
};
}
const fileList = ref<UploadFileInfo[]>([]);
// 抄送人
const selectCopyUserList = ref<Api.System.User[]>([]);
// 抄送人id
const selectCopyUserIds = ref<CommonType.IdType[]>([]);
// 下一节点列表
const nestNodeList = ref<Api.Workflow.FlowNode[]>([]);
const nickNameMap = ref<{ [key: string]: string }>({});
const assigneeSearchUserIds = ref<string | null>(null);
const selectAssigneeIds = ref<string[]>([]);
// 节点编码
const nodeCode = ref<string>('');
// 按钮权限
interface ButtonPerm {
pop: boolean;
trust: boolean;
transfer: boolean;
addSign: boolean;
subSign: boolean;
termination: boolean;
back: boolean;
copy: boolean;
}
const buttonPerm = reactive<ButtonPerm>(createDefaultButtonPerm());
function createDefaultButtonPerm(): ButtonPerm {
return {
pop: false,
trust: false,
transfer: false,
addSign: false,
subSign: false,
termination: false,
back: false,
copy: false
};
}
function initDefault() {
selectCopyUserList.value = [];
selectCopyUserIds.value = [];
nickNameMap.value = {};
assigneeSearchUserIds.value = null;
selectAssigneeIds.value = [];
nodeCode.value = '';
Object.assign(model, createDefaultModel());
Object.assign(buttonPerm, createDefaultButtonPerm());
}
async function getTask() {
startBtnLoading();
startBaseFormLoading();
const { error, data } = await fetchGetTask(props.taskId);
if (error) {
endBtnLoading();
endBaseFormLoading();
return;
}
task.value = data;
task.value.buttonList?.forEach(item => {
buttonPerm[item.code as keyof ButtonPerm] = item.show!;
});
endBtnLoading();
const { error: nextNodeError, data: nextNodeData } = await fetchGetkNextNode({
taskId: props.taskId,
taskVariables: props.taskVariables
});
endBaseFormLoading();
if (nextNodeError) {
return;
}
nestNodeList.value = nextNodeData;
}
async function handleSubmit() {
if (buttonPerm.pop && nestNodeList.value?.length) {
const hasEmptyAssignee = nestNodeList.value.some(e => !model.assigneeMap || !model.assigneeMap[e.nodeCode]);
if (hasEmptyAssignee) {
window.$message?.error('请选择审批人!');
return;
}
} else {
model.assigneeMap = {};
}
if (selectCopyUserList.value?.length) {
model.flowCopyList = selectCopyUserList.value.map(e => ({
userId: e.userId,
userName: e.nickName
}));
}
if (fileList.value?.length) {
const fileIds = fileList.value.map(item => item.id);
model.fileId = fileIds.join(',');
}
model.taskId = props.taskId;
model.variables = props.taskVariables;
startBtnLoading();
startBaseFormLoading();
try {
const { error } = await fetchCompleteTask(model);
if (error) return;
window.$message?.success('提交成功');
visible.value = false;
emit('finished');
} catch (error) {
window.$message?.error(`提交失败,请稍后重试,${error}`);
} finally {
endBtnLoading();
endBaseFormLoading();
}
}
function handleCopyConfirm(userIds: CommonType.IdType[], users?: Api.System.User[]) {
selectCopyUserList.value = users || [];
selectCopyUserIds.value = userIds;
}
function handleAssigneeOpen(item: Api.Workflow.FlowNode) {
if (!item.permissionFlag) {
window.$message?.error('没有可选人员,请联系管理员!');
return;
}
assigneeSearchUserIds.value = item.permissionFlag;
nodeCode.value = item.nodeCode;
selectAssigneeIds.value = model.assigneeMap?.[item.nodeCode]?.split(',') || [];
openAssigneeModal();
}
function handleAssigneeConfirm(userIds: CommonType.IdType[], users?: Api.System.User[]) {
// 更新当前节点的审批人
if (!model.assigneeMap) model.assigneeMap = {};
model.assigneeMap[nodeCode.value] = userIds.join(',');
nickNameMap.value[nodeCode.value] = users?.map(item => item.nickName).join(',') || '';
}
function handleCopyTagClose(index?: number) {
if (index !== undefined) {
// 删除指定索引的用户
selectCopyUserIds.value = selectCopyUserIds.value.filter((_, i) => i !== index);
selectCopyUserList.value = selectCopyUserList.value.filter((_, i) => i !== index);
} else {
// 清空所有用户
selectCopyUserList.value = [];
selectCopyUserIds.value = [];
model.flowCopyList = [];
}
}
function handleAssigneeTagClose(code: string, index?: number) {
if (!model.assigneeMap?.[code]) return;
// 获取当前节点的用户ID列表和名称列表
const userIds = model.assigneeMap[code].split(',');
const nickNames = nickNameMap.value[code]?.split(',') || [];
if (index !== undefined) {
// 删除指定索引的用户
// 使用filter方式移除指定索引的元素
const newUserIds = userIds.filter((_, i) => i !== index);
const newNickNames = nickNames.filter((_, i) => i !== index);
// 更新数据
model.assigneeMap[code] = newUserIds.join(',');
nickNameMap.value[code] = newNickNames.join(',');
} else {
// 清空所有用户
model.assigneeMap[code] = '';
nickNameMap.value[code] = '';
}
}
interface TaskOperationOptions {
userIds: CommonType.IdType[];
operation: 'delegateTask' | 'transferTask' | 'addSignature';
confirmText: string;
successMessage: string;
closeModal: () => void;
}
function handleTaskOperationConfirm(options: TaskOperationOptions) {
const { userIds, operation, confirmText, successMessage, closeModal } = options;
const taskModel = {
taskId: props.taskId,
userId: userIds[0],
message: model.message
};
window.$dialog?.warning({
title: '提示',
content: `是否确认${confirmText}?`,
positiveText: `确认${confirmText}`,
positiveButtonProps: {
type: 'primary'
},
negativeText: '取消',
onPositiveClick: async () => {
startBtnLoading();
startBaseFormLoading();
const { error } = await fetchTaskOperate(taskModel, operation);
endBtnLoading();
endBaseFormLoading();
if (error) return;
window.$message?.success(successMessage);
closeModal();
visible.value = false;
emit('finished');
}
});
}
// 委托
function handleDelegateConfirm(userIds: CommonType.IdType[]) {
handleTaskOperationConfirm({
userIds,
operation: 'delegateTask',
confirmText: '委托',
successMessage: '委托成功',
closeModal: closeDelegateModal
});
}
// 转办
function handleTransferConfirm(userIds: CommonType.IdType[]) {
handleTaskOperationConfirm({
userIds,
operation: 'transferTask',
confirmText: '转办',
successMessage: '转办成功',
closeModal: closeTransferModal
});
}
// 加签
function handleAddSignatureConfirm(userIds: CommonType.IdType[]) {
handleTaskOperationConfirm({
userIds,
operation: 'addSignature',
confirmText: '加签',
successMessage: '加签成功',
closeModal: closeAddSignatureModal
});
}
// 减签
function handleReduceSubmit() {
visible.value = false;
emit('finished');
}
// 终止
function handleTerminate() {
const terminateModel = {
taskId: props.taskId,
comment: model.message
};
window.$dialog?.warning({
title: '提示',
content: '是否确认终止?',
positiveText: '确认',
positiveButtonProps: {
type: 'primary'
},
negativeText: '取消',
onPositiveClick: async () => {
startBtnLoading();
startBaseFormLoading();
const { error } = await fetchTerminateTask(terminateModel);
endBtnLoading();
endBaseFormLoading();
if (error) return;
window.$message?.success('终止成功');
visible.value = false;
emit('finished');
}
});
}
function handleBackSubmit() {
visible.value = false;
emit('finished');
}
watch(visible, () => {
if (visible.value) {
initDefault();
getTask();
}
});
</script>
<template>
<NModal v-model:show="visible" preset="card" class="w-800px" :title="title" :native-scrollbar="false" closable>
<NSpin :show="baseFormLoading">
<NForm :model="model" label-placement="left" :label-width="100">
<NFormItem label="通知方式" path="messageType">
<NCheckboxGroup v-model:value="model.messageType">
<NSpace item-style="display: flex;">
<NCheckbox
v-for="item in messageTypeOptions"
:key="item.value"
:disabled="item.value === '1'"
:value="item.value"
:label="item.label"
/>
</NSpace>
</NCheckboxGroup>
</NFormItem>
<NFormItem label="附件" path="fileId">
<FileUpload v-model:file-list="fileList" :file-size="20" :max="20" upload-type="file" :accept="accept" />
</NFormItem>
<NFormItem v-if="buttonPerm.copy" label="抄送人员">
<NSpace>
<NButton ghost type="primary" @click="openCopyModal">选择抄送人员</NButton>
<TagGroup
size="large"
:value="selectCopyUserList.map(item => item.nickName)"
:closable="true"
@close="handleCopyTagClose"
/>
</NSpace>
</NFormItem>
<NFormItem
v-if="buttonPerm.pop && nestNodeList && nestNodeList.length > 0"
label="下一步审批人"
path="assigneeMap"
>
<NSpace>
<div v-for="(item, index) in nestNodeList" :key="index">
<span>{{ item.nodeName }}</span>
<NSpace>
<NButton ghost type="primary" @click="handleAssigneeOpen(item)">选择审批人员</NButton>
<NInput v-if="false" v-model:value="model.assigneeMap![item.nodeCode]" />
<TagGroup
size="large"
:value="nickNameMap[item.nodeCode]"
:closable="true"
@close="index => handleAssigneeTagClose(item.nodeCode, index)"
/>
</NSpace>
</div>
</NSpace>
</NFormItem>
<NFormItem v-if="isWaiting" label="审批意见" path="message">
<NInput v-model:value="model.message" type="textarea" />
</NFormItem>
</NForm>
</NSpin>
<template #footer>
<NSpace justify="end" :size="16">
<NButton @click="visible = false">{{ $t('common.cancel') }}</NButton>
<!-- 委托 -->
<NButton v-if="isWaiting && buttonPerm.trust" :loading="btnLoading" type="warning" @click="openDelegateModal">
委托
</NButton>
<!-- 转办 -->
<NButton
v-if="isWaiting && buttonPerm.transfer"
:loading="btnLoading"
type="warning"
@click="openTransferModal"
>
转办
</NButton>
<!-- 加签 -->
<NButton
v-if="isWaiting && buttonPerm.addSign && Number(task?.nodeRatio) > 0"
:loading="btnLoading"
type="warning"
@click="openAddSignatureModal"
>
加签
</NButton>
<!-- 减签 -->
<NButton
v-if="isWaiting && buttonPerm.subSign && Number(task?.nodeRatio) > 0"
:loading="btnLoading"
type="warning"
@click="openReduceSignatureModal"
>
减签
</NButton>
<!-- 终止 -->
<NButton v-if="isWaiting && buttonPerm.termination" :loading="btnLoading" type="error" @click="handleTerminate">
终止
</NButton>
<!-- 驳回 -->
<NButton v-if="isWaiting && buttonPerm.back" :loading="btnLoading" type="error" @click="openBackModal">
驳回
</NButton>
<NButton :loading="btnLoading" type="primary" @click="handleSubmit">提交</NButton>
</NSpace>
</template>
<!-- 抄送人员选择 -->
<UserSelectModal
v-model:visible="copyVisible"
:row-keys="selectCopyUserIds"
multiple
@confirm="handleCopyConfirm"
/>
<!-- 下一步审批人员选择 -->
<UserSelectModal
v-model:visible="assigneeVisible"
:row-keys="selectAssigneeIds"
:search-user-ids="assigneeSearchUserIds"
multiple
@confirm="handleAssigneeConfirm"
/>
<!-- 转办 -->
<UserSelectModal v-model:visible="transferVisible" @confirm="handleTransferConfirm" />
<!-- 委托 -->
<UserSelectModal v-model:visible="delegateVisible" @confirm="handleDelegateConfirm" />
<!-- 加签 -->
<UserSelectModal v-model:visible="addSignatureVisible" @confirm="handleAddSignatureConfirm" />
<!-- 减签 -->
<ReduceSignatureModal v-model:visible="reduceSignatureVisible" :task="task!" @reduce-submit="handleReduceSubmit" />
<!-- 驳回 -->
<BackTaskModal v-model:visible="backVisible" :task="task!" @submit="handleBackSubmit" />
</NModal>
</template>

View File

@ -1,95 +0,0 @@
<script lang="ts" setup>
import { reactive, watch } from 'vue';
import { messageTypeOptions } from '@/constants/workflow';
import { fetchTaskUrge } from '@/service/api/workflow';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
defineOptions({
name: 'FlowUrgeModal'
});
const { createRequiredRule } = useFormRules();
const { formRef, validate } = useNaiveForm();
interface Props {
taskIds: CommonType.IdType[];
}
const props = defineProps<Props>();
interface Emits {
(e: 'submit'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
type Model = Api.Workflow.TaskUrgeOperateParams;
const model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
taskIdList: props.taskIds,
messageType: ['1'],
message: ''
};
}
type RuleKey = Extract<keyof Model, 'taskIdList' | 'messageType' | 'message'>;
const rules: Record<RuleKey, App.Global.FormRule> = {
taskIdList: createRequiredRule('任务ID不能为空'),
messageType: createRequiredRule('消息提醒不能为空'),
message: createRequiredRule('消息内容不能为空')
};
function closeDrawer() {
visible.value = false;
}
watch(visible, () => {
if (visible.value) {
Object.assign(model, createDefaultModel());
}
});
async function handleSubmit() {
await validate();
const { error } = await fetchTaskUrge(model);
if (error) return;
window.$message?.success('催办成功');
closeDrawer();
emit('submit');
}
</script>
<template>
<NModal v-model:show="visible" preset="card" class="w-800px" title="催办" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem label="消息提醒" path="messageType">
<NCheckboxGroup v-model:value="model.messageType">
<NSpace item-style="display: flex;">
<NCheckbox
v-for="item in messageTypeOptions"
:key="item.value"
:disabled="item.value === '1'"
:value="item.value"
:label="item.label"
/>
</NSpace>
</NCheckboxGroup>
</NFormItem>
<NFormItem label="消息呢用" path="message">
<NInput v-model:value="model.message" type="textarea" />
</NFormItem>
</NForm>
<template #footer>
<NSpace justify="end" :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
</NModal>
</template>

View File

@ -1,317 +0,0 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import dayjs from 'dayjs';
import { useBoolean, useLoading } from '@sa/hooks';
import { flowCodeTypeOptions, flowCodeTypeRecord, leaveTypeOptions, leaveTypeRecord } from '@/constants/workflow';
import { fetchCreateLeave, fetchGetLeaveDetail, fetchStartWorkflow, fetchUpdateLeave } from '@/service/api/workflow';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { useDict } from '@/hooks/business/dict';
import { $t } from '@/locales';
import ApprovalInfoPanel from '@/components/workflow/approval-info-panel.vue';
import FlowTaskApprovalModal from '@/components/workflow/flow-task-approval-modal.vue';
import FlowDrawer from '@/components/workflow/flow-drawer.vue';
defineOptions({
name: 'LeaveEdit'
});
useDict('wf_task_status');
interface Props {
operateType: CommonType.WorkflowTableOperateType;
/** 业务ID */
businessId?: CommonType.IdType;
taskId?: CommonType.IdType;
/** the edit row data */
rowData?: Api.Workflow.Leave | null;
}
const props = withDefaults(defineProps<Props>(), {
rowData: null,
businessId: undefined,
taskId: undefined
});
interface Emits {
(e: 'submitted'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const approvalInfoPanelRef = ref<InstanceType<typeof ApprovalInfoPanel>>();
const { bool: taskApplyVisible, setTrue: setTaskApplyVisible } = useBoolean();
const { loading, startLoading, endLoading } = useLoading();
const { formRef, validate, restoreValidation } = useNaiveForm();
const { createRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<CommonType.WorkflowTableOperateType, string> = {
add: '新增请假申请',
edit: '编辑请假申请',
detail: '查看请假申请',
approval: '审批请假申请'
};
return titles[props.operateType];
});
const readonly = computed(() => {
return props.operateType === 'detail' || props.operateType === 'approval';
});
const taskId = ref<CommonType.IdType>(props.taskId!);
const respLeave = ref<Api.Workflow.Leave>();
const startWorkflowResult = ref<Api.Workflow.StartWorkflowResult>();
type Model = Api.Workflow.LeaveOperateParams & {
flowCode: Api.Workflow.FlowCodeType;
};
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
flowCode: 'leave1',
leaveType: null,
startDate: null,
endDate: null,
leaveDays: null,
remark: ''
};
}
type ModelDetail = Api.Workflow.LeaveDetail & {
flowCode: Api.Workflow.FlowCodeType;
};
const modelDetail: ModelDetail = reactive(createDefaultModelDetail());
function createDefaultModelDetail(): ModelDetail {
return {
flowCode: 'leave1',
status: null,
id: null,
leaveType: null,
startDate: null,
endDate: null,
leaveDays: null,
remark: ''
};
}
const showApprovalInfoPanel = computed(() => {
return modelDetail.status !== 'draft';
});
type StartWorkflowModel = Api.Workflow.StartWorkflowOperateParams;
const startWorkflowModel: StartWorkflowModel = reactive(createDefaultStartWorkflowModel());
function createDefaultStartWorkflowModel(): StartWorkflowModel {
return {
flowCode: null,
businessId: null,
flowInstanceBizExtBo: null,
variables: {}
};
}
const dateRange = computed<[string, string] | null>({
get: () => {
if (!model.startDate || !model.endDate) return null;
return [model.startDate, model.endDate] as [string, string];
},
set: (value: [string, string] | null) => {
if (value) {
model.startDate = value[0];
model.endDate = value[1];
// 计算请假天数
const start = dayjs(value[0]);
const end = dayjs(value[1]);
model.leaveDays = end.diff(start, 'day') + 1;
} else {
model.startDate = null;
model.endDate = null;
model.leaveDays = null;
}
}
});
type RuleKey = Extract<keyof Model, 'id' | 'leaveType' | 'leaveDays' | 'startDate' | 'endDate'> | 'flowCode';
const rules: Record<RuleKey, App.Global.FormRule> = {
id: createRequiredRule('id不能为空'),
flowCode: createRequiredRule('流程类型不能为空'),
leaveType: createRequiredRule('请假类型不能为空'),
startDate: createRequiredRule('请假时间不能为空'),
endDate: createRequiredRule('结束时间不能为空'),
leaveDays: createRequiredRule('请假天数不能为空')
};
async function handleUpdateModelWhenEdit() {
if (props.operateType === 'add') {
Object.assign(model, createDefaultModel());
return;
}
if (props.rowData) {
Object.assign(model, props.rowData);
Object.assign(modelDetail, props.rowData);
} else {
const { error, data } = await fetchGetLeaveDetail(props.businessId!);
if (error) {
window.$message?.error(error.message);
return;
}
Object.assign(model, data);
Object.assign(modelDetail, data);
}
}
function closeDrawer() {
visible.value = false;
}
async function handleOperate() {
await validate();
// request
if (props.operateType === 'add') {
const { leaveType, startDate, endDate, leaveDays, remark } = model;
const { error, data } = await fetchCreateLeave({ leaveType, startDate, endDate, leaveDays, remark });
if (error) return;
respLeave.value = data;
}
if (props.operateType === 'edit') {
const { id, leaveType, startDate, endDate, leaveDays, remark } = model;
const { error, data } = await fetchUpdateLeave({ id, leaveType, startDate, endDate, leaveDays, remark });
if (error) return;
respLeave.value = data;
}
}
async function handleSaveDraft() {
await handleOperate();
window.$message?.success($t('common.updateSuccess'));
closeDrawer();
emit('submitted');
}
const taskVariables = ref<{ [key: string]: any }>({});
async function handleSubmit() {
await handleOperate();
window.$message?.success($t('common.updateSuccess'));
// 提交流程
startWorkflowModel.businessId = respLeave.value?.id;
startWorkflowModel.flowCode = model.flowCode;
startWorkflowModel.flowInstanceBizExtBo = {
businessCode: respLeave.value?.applyCode,
businessTitle: '请假申请'
};
taskVariables.value = {
leaveDays: respLeave.value?.leaveDays,
userList: ['1', '3', '4']
};
startWorkflowModel.variables = taskVariables.value;
const { error, data } = await fetchStartWorkflow(startWorkflowModel);
if (error) return;
startWorkflowResult.value = data;
taskId.value = data.taskId!;
setTaskApplyVisible();
}
function handleTaskFinished() {
closeDrawer();
emit('submitted');
}
function handleApproval() {
setTaskApplyVisible();
}
async function initializeData() {
if (visible.value) {
startLoading();
await handleUpdateModelWhenEdit();
restoreValidation();
if (showApprovalInfoPanel.value) {
approvalInfoPanelRef.value?.initData();
}
endLoading();
}
}
watch(visible, initializeData, { immediate: true });
</script>
<template>
<FlowDrawer
v-model:visible="visible"
:title="title"
:loading="loading"
:operate-type="operateType"
:status="modelDetail.status"
@close="closeDrawer"
@save-draft="handleSaveDraft"
@submit="handleSubmit"
@approval="handleApproval"
>
<div :class="loading ? 'hidden' : ''">
<div v-if="!readonly" class="h-full">
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem label="流程类型" path="flowCode">
<NSelect v-model:value="model.flowCode" placeholder="请输入流程类型" :options="flowCodeTypeOptions" />
</NFormItem>
<NFormItem label="请假类型" path="leaveType">
<NSelect v-model:value="model.leaveType" placeholder="请输入请假类型" :options="leaveTypeOptions" />
</NFormItem>
<NFormItem label="请假时间" path="startDate">
<NDatePicker
v-model:formatted-value="dateRange"
class="w-full"
type="datetimerange"
format="yyyy-MM-dd HH:mm:ss"
clearable
/>
</NFormItem>
<NFormItem label="请假天数" path="leaveDays">
<NInputNumber v-model:value="model.leaveDays" class="w-full" disabled placeholder="请输入请假天数" />
</NFormItem>
<NFormItem label="请假原因" path="remark">
<NInput v-model:value="model.remark" placeholder="请输入请假原因" />
</NFormItem>
</NForm>
</div>
<div v-else>
<NDescriptions size="small" bordered :column="2" label-placement="left">
<NDescriptionsItem label="流程类型">
{{ flowCodeTypeRecord[modelDetail.flowCode] }}
</NDescriptionsItem>
<NDescriptionsItem label="请假类型">
<NTag type="info">{{ leaveTypeRecord[modelDetail.leaveType!] }}</NTag>
</NDescriptionsItem>
<NDescriptionsItem label="请假时间">
{{ `${modelDetail.startDate}${modelDetail.endDate}` }}
</NDescriptionsItem>
<NDescriptionsItem label="请假天数">{{ modelDetail.leaveDays }} </NDescriptionsItem>
<NDescriptionsItem label="请假原因">
{{ modelDetail.remark || '-' }}
</NDescriptionsItem>
</NDescriptions>
<!-- 审批信息 -->
<ApprovalInfoPanel v-if="showApprovalInfoPanel" ref="approvalInfoPanelRef" :business-id="modelDetail.id!" />
</div>
</div>
</FlowDrawer>
<FlowTaskApprovalModal
v-model:visible="taskApplyVisible"
:task-id="taskId"
:task-variables="taskVariables"
@finished="handleTaskFinished"
/>
</template>
<style scoped></style>

View File

@ -1,154 +0,0 @@
<script lang="tsx" setup>
import { reactive, ref, watch } from 'vue';
import { useLoading } from '@sa/hooks';
import { fetchGetCurrentTaskAllUser, fetchTaskOperate } from '@/service/api/workflow/task';
import { $t } from '@/locales';
import ButtonIcon from '@/components/custom/button-icon.vue';
defineOptions({
name: 'ReduceSignatureModal'
});
interface Props {
task: Api.Workflow.Task;
}
const props = defineProps<Props>();
const visible = defineModel<boolean>('visible', {
default: false
});
interface Emits {
(e: 'reduceSubmit'): void;
}
const emit = defineEmits<Emits>();
const { loading, startLoading, endLoading } = useLoading();
type UserTaskModel = Api.System.User & { nodeName: string };
const userData = ref<UserTaskModel[]>([]);
type Model = Api.Workflow.TaskOperateParams;
const model: Model = reactive(createDefaultModel());
const checkedRowKeys = ref<CommonType.IdType[]>([]);
function createDefaultModel(): Model {
return {
taskId: null,
userId: undefined,
userIds: undefined,
message: ''
};
}
const columns = ref<NaiveUI.TableColumn<UserTaskModel>[]>([
{
type: 'selection',
align: 'center',
width: 50
},
{
title: '节点名称',
key: 'nodeName',
align: 'center',
minWidth: 120
},
{
title: '办理人员',
key: 'nickName',
align: 'center',
minWidth: 120
},
{
key: 'operate',
title: $t('common.operate'),
align: 'center',
width: 130,
render(row) {
return (
<ButtonIcon
text
type="error"
icon="material-symbols:delete-outline"
tooltipContent={'减签'}
popconfirmContent={'是否确认减签?'}
onPositiveClick={() => handleReduceSignature([row.userId])}
/>
);
}
}
]);
async function handleReduceSignature(userIds: CommonType.IdType[]) {
model.taskId = props.task.id;
model.userIds = userIds;
const { error } = await fetchTaskOperate(model, 'reductionSignature');
if (error) return;
window.$message?.success('减签成功');
handleCloseDrawer();
}
async function getTaskAllUser() {
startLoading();
const { error, data } = await fetchGetCurrentTaskAllUser(props.task.id);
if (error) return;
userData.value = data.map(item => ({
...item,
nodeName: props.task.nodeName
}));
endLoading();
}
function handleCloseDrawer() {
visible.value = false;
emit('reduceSubmit');
}
watch(visible, async () => {
if (visible.value) {
await getTaskAllUser();
}
});
</script>
<template>
<NModal v-model:show="visible" class="w-700px" preset="card" title="待减签人员">
<NCard class="h-full card-wrapper">
<NSpace wrap justify="space-between" class="mb-16px lt-sm:w-200px">
<TableRowCheckAlert v-model:checked-row-keys="checkedRowKeys" />
<NButton
size="small"
ghost
type="error"
:disabled="checkedRowKeys.length === 0"
@click="handleReduceSignature(checkedRowKeys)"
>
<template #icon>
<icon-material-symbols:delete-outline class="text-icon" />
</template>
批量减签
</NButton>
</NSpace>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
class="h-400px"
flex-height
:row-key="row => row.userId"
size="small"
:columns="columns"
:data="userData"
:loading="loading"
/>
</NCard>
</NModal>
</template>
<style scoped lang="scss">
.n-alert {
--n-padding: 5px 13px !important;
--n-icon-margin: 6px 8px 0 12px !important;
--n-icon-size: 20px !important;
}
</style>

View File

@ -1,90 +0,0 @@
import { transformRecordToOption } from '@/utils/common';
export const cooperateTypeRecord: Record<Api.Workflow.CooperateType, string> = {
1: '审批',
2: '转办',
3: '委派',
4: '会签',
5: '票签',
6: '加签',
7: '减签'
};
export const cooperateTypeOptions = transformRecordToOption(cooperateTypeRecord);
export const businessStatusRecord: Record<Api.Workflow.BusinessStatus, string> = {
cancel: '已撤销',
draft: '草稿',
waiting: '待审批',
finish: '已完成',
invalid: '已作废',
back: '已退回',
termination: '已终止'
};
export const businessStatusOptions = transformRecordToOption(businessStatusRecord);
export const messageTypeRecord: Record<Api.Workflow.MessageType, string> = {
'1': '站内信',
'2': '邮件',
'3': '短信'
};
export const messageTypeOptions = transformRecordToOption(messageTypeRecord);
export const flowCodeTypeRecord: Record<Api.Workflow.FlowCodeType, string> = {
leave1: '请假申请-普通',
leave2: '请假申请-排他网关',
leave3: '请假申请-并行网关',
leave4: '请假申请-会签',
leave5: '请假申请-并行会签网关',
leave6: '请假申请-排他并行会签'
};
export const flowCodeTypeOptions = transformRecordToOption(flowCodeTypeRecord);
/** leave type */
export const leaveTypeRecord: Record<Api.Workflow.LeaveType, string> = {
'1': '事假',
'2': '调休',
'3': '病假',
'4': '婚假'
};
export const leaveTypeOptions = transformRecordToOption(leaveTypeRecord);
/** workflow publish status */
export const workflowPublishStatusRecord: Record<Api.Workflow.WorkflowPublishStatus, string> = {
'0': '未发布',
'1': '已发布',
'9': '失效'
};
export const workflowPublishStatusOptions = transformRecordToOption(workflowPublishStatusRecord);
/** node type */
export const workflowNodeTypeRecord: Record<Api.Workflow.WorkflowNodeType, string> = {
0: '开始节点',
1: '中间节点',
2: '结束节点',
3: '互斥网关',
4: '并行网关'
};
export const workflowNodeTypeOptions = transformRecordToOption(workflowNodeTypeRecord);
/** definition designer mode */
export const definitionDesignerModeRecord: Record<Api.Workflow.DefinitionDesignerMode, string> = {
CLASSICS: '经典模式',
MIMIC: '仿钉钉模式'
};
export const definitionDesignerModeOptions = transformRecordToOption(definitionDesignerModeRecord);
/** activity status */
export const workflowActivityStatusRecord: Record<Api.Workflow.WorkflowActivityStatus, string> = {
0: '挂起',
1: '激活'
};
export const workflowActivityStatusOptions = transformRecordToOption(workflowActivityStatusRecord);

View File

@ -113,10 +113,6 @@ export function useDownload() {
const response = await fetch(fullUrl, requestOptions);
if (response.status !== 200) {
throw new Error(errorCodeRecord.default);
}
await handleResponse(response);
const rawHeader = response.headers.get('Download-Filename');

View File

@ -32,8 +32,7 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
getData,
searchParams,
updateSearchParams,
resetSearchParams,
updateApiFn
resetSearchParams
} = useHookTable<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>({
apiFn,
apiParams,
@ -213,8 +212,7 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
getDataByPage,
searchParams,
updateSearchParams,
resetSearchParams,
updateApiFn
resetSearchParams
};
}

View File

@ -33,7 +33,7 @@ const toGitee = () => {
</NBadge>
</NButton>
</template>
{{ $t('page.home.message') }}
消息
</NTooltip>
</template>
<NCard

View File

@ -231,23 +231,10 @@ const local: App.I18n.Schema = {
demo: 'Demo',
demo_demo: 'Demo Table',
demo_tree: 'Demo Tree',
workflow: 'Workflow',
workflow_category: 'Workflow Category',
exception: 'Exception',
exception_403: '403',
exception_404: '404',
exception_500: '500',
workflow_design: 'Process Design',
workflow_spel: 'Process Spel',
'workflow_process-definition': 'Process Definition',
'workflow_process-instance': 'Process Instance',
workflow_task: 'Task',
'workflow_task_all-task-waiting': 'All Task Waiting',
workflow_leave: 'Leave Apply',
'workflow_task_my-document': 'My Document',
'workflow_task_task-waiting': 'My Task Waiting',
'workflow_task_task-finish': 'My Task Finish',
'workflow_task_task-copy': 'My Task Copy'
exception_500: '500'
},
menu: {
system_tenant: 'Tenant Management',
@ -341,8 +328,6 @@ const local: App.I18n.Schema = {
page: {
login: {
common: {
title: 'Modern enterprise-level multi-tenant management system',
subTitle: 'Provides developers with a complete enterprise management solution',
loginOrRegister: 'Login / Register',
register: 'Register',
userNamePlaceholder: 'Please enter user name',

View File

@ -231,23 +231,10 @@ const local: App.I18n.Schema = {
demo: '测试',
demo_demo: '测试单表',
demo_tree: '测试树表',
workflow: '流程管理',
workflow_category: '流程分类',
exception: '异常页',
exception_403: '403',
exception_404: '404',
exception_500: '500',
workflow_design: '流程设计',
workflow_spel: '流程表达式',
'workflow_process-definition': '流程定义',
'workflow_process-instance': '流程实例',
workflow_task: '任务',
'workflow_task_all-task-waiting': '待办任务',
workflow_leave: '请假申请',
'workflow_task_my-document': '我发起的',
'workflow_task_task-waiting': '我的待办',
'workflow_task_task-finish': '我的已办',
'workflow_task_task-copy': '我的抄送'
exception_500: '500'
},
menu: {
system_tenant: '租户管理',
@ -341,8 +328,6 @@ const local: App.I18n.Schema = {
page: {
login: {
common: {
title: '现代化的企业级多租户管理系统',
subTitle: '为开发者提供了完整的企业管理解决方案',
loginOrRegister: '登录 / 注册',
register: '注册',
userNamePlaceholder: '请输入用户名',

View File

@ -43,15 +43,4 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
system_tenant: () => import("@/views/system/tenant/index.vue"),
system_user: () => import("@/views/system/user/index.vue"),
tool_gen: () => import("@/views/tool/gen/index.vue"),
workflow_category: () => import("@/views/workflow/category/index.vue"),
workflow_design: () => import("@/views/workflow/design/index.vue"),
workflow_leave: () => import("@/views/workflow/leave/index.vue"),
"workflow_process-definition": () => import("@/views/workflow/process-definition/index.vue"),
"workflow_process-instance": () => import("@/views/workflow/process-instance/index.vue"),
workflow_spel: () => import("@/views/workflow/spel/index.vue"),
"workflow_task_all-task-waiting": () => import("@/views/workflow/task/all-task-waiting/index.vue"),
"workflow_task_my-document": () => import("@/views/workflow/task/my-document/index.vue"),
"workflow_task_task-copy": () => import("@/views/workflow/task/task-copy/index.vue"),
"workflow_task_task-finish": () => import("@/views/workflow/task/task-finish/index.vue"),
"workflow_task_task-waiting": () => import("@/views/workflow/task/task-waiting/index.vue"),
};

View File

@ -331,125 +331,5 @@ export const generatedRoutes: GeneratedRoute[] = [
icon: 'material-symbols:account-circle-full',
hideInMenu: true
}
},
{
name: 'workflow',
path: '/workflow',
component: 'layout.base',
meta: {
title: 'workflow',
i18nKey: 'route.workflow'
},
children: [
{
name: 'workflow_category',
path: '/workflow/category',
component: 'view.workflow_category',
meta: {
title: 'workflow_category',
i18nKey: 'route.workflow_category'
}
},
{
name: 'workflow_design',
path: '/workflow/design',
component: 'view.workflow_design',
meta: {
title: 'workflow_design',
i18nKey: 'route.workflow_design'
}
},
{
name: 'workflow_leave',
path: '/workflow/leave',
component: 'view.workflow_leave',
meta: {
title: 'workflow_leave',
i18nKey: 'route.workflow_leave'
}
},
{
name: 'workflow_process-definition',
path: '/workflow/process-definition',
component: 'view.workflow_process-definition',
meta: {
title: 'workflow_process-definition',
i18nKey: 'route.workflow_process-definition'
}
},
{
name: 'workflow_process-instance',
path: '/workflow/process-instance',
component: 'view.workflow_process-instance',
meta: {
title: 'workflow_process-instance',
i18nKey: 'route.workflow_process-instance'
}
},
{
name: 'workflow_spel',
path: '/workflow/spel',
component: 'view.workflow_spel',
meta: {
title: 'workflow_spel',
i18nKey: 'route.workflow_spel'
}
},
{
name: 'workflow_task',
path: '/workflow/task',
meta: {
title: 'workflow_task',
i18nKey: 'route.workflow_task'
},
children: [
{
name: 'workflow_task_all-task-waiting',
path: '/workflow/task/all-task-waiting',
component: 'view.workflow_task_all-task-waiting',
meta: {
title: 'workflow_task_all-task-waiting',
i18nKey: 'route.workflow_task_all-task-waiting'
}
},
{
name: 'workflow_task_my-document',
path: '/workflow/task/my-document',
component: 'view.workflow_task_my-document',
meta: {
title: 'workflow_task_my-document',
i18nKey: 'route.workflow_task_my-document'
}
},
{
name: 'workflow_task_task-copy',
path: '/workflow/task/task-copy',
component: 'view.workflow_task_task-copy',
meta: {
title: 'workflow_task_task-copy',
i18nKey: 'route.workflow_task_task-copy'
}
},
{
name: 'workflow_task_task-finish',
path: '/workflow/task/task-finish',
component: 'view.workflow_task_task-finish',
meta: {
title: 'workflow_task_task-finish',
i18nKey: 'route.workflow_task_task-finish'
}
},
{
name: 'workflow_task_task-waiting',
path: '/workflow/task/task-waiting',
component: 'view.workflow_task_task-waiting',
meta: {
title: 'workflow_task_task-waiting',
i18nKey: 'route.workflow_task_task-waiting'
}
}
]
}
]
}
];

View File

@ -198,20 +198,7 @@ const routeMap: RouteMap = {
"system_user": "/system/user",
"tool": "/tool",
"tool_gen": "/tool/gen",
"user-center": "/user-center",
"workflow": "/workflow",
"workflow_category": "/workflow/category",
"workflow_design": "/workflow/design",
"workflow_leave": "/workflow/leave",
"workflow_process-definition": "/workflow/process-definition",
"workflow_process-instance": "/workflow/process-instance",
"workflow_spel": "/workflow/spel",
"workflow_task": "/workflow/task",
"workflow_task_all-task-waiting": "/workflow/task/all-task-waiting",
"workflow_task_my-document": "/workflow/task/my-document",
"workflow_task_task-copy": "/workflow/task/task-copy",
"workflow_task_task-finish": "/workflow/task/task-finish",
"workflow_task_task-waiting": "/workflow/task/task-waiting"
"user-center": "/user-center"
};
/**

View File

@ -1,53 +0,0 @@
import { request } from '@/service/request';
/** 获取测试树列表 */
export function fetchGetCategoryList(params?: Api.Workflow.WorkflowCategorySearchParams) {
return request<Api.Workflow.WorkflowCategoryList>({
url: '/workflow/category/list',
method: 'get',
params
});
}
/** 新增测试树 */
export function fetchCreateCategory(data: Api.Workflow.WorkflowCategoryOperateParams) {
return request<boolean>({
url: '/workflow/category',
method: 'post',
data
});
}
/** 修改测试树 */
export function fetchUpdateCategory(data: Api.Workflow.WorkflowCategoryOperateParams) {
return request<boolean>({
url: '/workflow/category',
method: 'put',
data
});
}
/** 删除分类 */
export function fetchDeleteCategory(id: CommonType.IdType) {
return request<boolean>({
url: `/workflow/category/${id}`,
method: 'delete'
});
}
/** 导出工作流分类 */
export function fetchExportCategory(params?: Api.Workflow.WorkflowCategorySearchParams) {
return request<boolean>({
url: '/workflow/category/export',
method: 'post',
params
});
}
/** 获取分类树 */
export function fetchGetCategoryTree() {
return request<Api.Common.CommonTreeRecord>({
url: '/workflow/category/categoryTree',
method: 'get'
});
}

View File

@ -1,72 +0,0 @@
import { request } from '@/service/request';
/** 获取流程定义列表 */
export function fetchGetDefinitionList(params?: Api.Workflow.DefinitionSearchParams) {
return request<Api.Workflow.DefinitionList>({
url: '/workflow/definition/list',
method: 'get',
params
});
}
/** 获取未发布流程定义列表 */
export function fetchGetUnPublishDefinitionList(params?: Api.Workflow.DefinitionSearchParams) {
return request<Api.Workflow.DefinitionList>({
url: '/workflow/definition/unPublishList',
method: 'get',
params
});
}
/** 新增流程定义 */
export function fetchCreateDefinition(data: Api.Workflow.DefinitionOperateParams) {
return request<boolean>({
url: '/workflow/definition',
method: 'post',
data
});
}
/** 修改流程定义 */
export function fetchUpdateDefinition(data: Api.Workflow.DefinitionOperateParams) {
return request<boolean>({
url: '/workflow/definition',
method: 'put',
data
});
}
/** 批量删除流程定义 */
export function fetchBatchDeleteDefinition(ids: CommonType.IdType[]) {
return request<boolean>({
url: `/workflow/definition/${ids.join(',')}`,
method: 'delete'
});
}
/** 激活/挂起流程定义 */
export function fetchActiveDefinition(id: CommonType.IdType, active: boolean) {
return request<boolean>({
url: `/workflow/definition/active/${id}`,
method: 'put',
params: {
active
}
});
}
/** 发布流程定义 */
export function fetchPublishDefinition(id: CommonType.IdType) {
return request<boolean>({
url: `/workflow/definition/publish/${id}`,
method: 'put'
});
}
/** 复制流程定义 */
export function fetchCopyDefinition(id: CommonType.IdType) {
return request<boolean>({
url: `/workflow/definition/copy/${id}`,
method: 'post'
});
}

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