106 Commits

Author SHA1 Message Date
疯狂的狮子Li
e77fe0e618 update 布局设置增加 页面圆角大小 控制器 可以根据喜好自行设置页面圆角样式 2026-04-10 11:32:16 +08:00
疯狂的狮子Li
493d1131bf update vite 7.3.2 修复漏洞问题 2026-04-10 09:49:15 +08:00
疯狂的狮子Li
f37af6f48c update 将富文本编辑器重新改为oss存储(大家还是希望用oss) 2026-04-09 11:34:03 +08:00
疯狂的狮子Li
a79f2fb6c8 fix 修复 插件冲突导致样式一直重复加载问题 2026-03-31 09:24:53 +08:00
疯狂的狮子Li
eb6827765c fix 修复 搜索弹窗样式错误 2026-03-27 16:28:14 +08:00
疯狂的狮子Li
0076f5f6f7 🦁🦁🦁发布 v5.6.0-v2.6.0 版本 新年第一版 2026-03-24 11:47:41 +08:00
疯狂的狮子Li
ef5ea98a03 update 替代有问题的插件 2026-03-19 15:55:39 +08:00
gssong
73f2374c72 add 增加流程实例权限 2026-03-19 10:27:06 +08:00
gssong
9dcb392220 Merge remote-tracking branch 'origin/dev' into dev 2026-03-19 09:58:02 +08:00
gssong
54636ac14f add 补充流程定义权限 2026-03-19 09:57:56 +08:00
Lau
51a852caea update 修改vben5前端仓库地址 2026-03-13 14:25:48 +00:00
疯狂的狮子Li
767b00c257 Revert "update 优化 将logininfor规范化为loginInfo"
This reverts commit c9f9fbed49.
2026-03-13 15:05:10 +08:00
疯狂的狮子Li
c9f9fbed49 update 优化 将logininfor规范化为loginInfo 2026-03-13 14:58:55 +08:00
疯狂的狮子Li
41c8e06c54 update 升级 package.json 全部依赖 2026-03-11 15:07:47 +08:00
疯狂的狮子Li
33a397032c update 将富文本编辑器改为base64存储图片 图片访问权限随文章访问权限走 不走oss存储了 2026-03-11 14:20:55 +08:00
疯狂的狮子Li
5b55687a76 update 删除debug保护工具性能问题严重 容易导致浏览器卡死 2026-03-11 13:44:02 +08:00
疯狂的狮子Li
7fd45ab2e8 update 优化 字典管理按钮样式 2026-03-11 13:26:57 +08:00
疯狂的狮子Li
3e592c1c5e update 优化 字典管理改为左右卡片式 2026-03-11 13:20:34 +08:00
疯狂的狮子Li
2db01677e3 update 优化整体页面样式 更圆滑 2026-03-11 11:59:47 +08:00
疯狂的狮子Li
656366d610 update 优化统一用户昵称 2026-03-10 15:37:39 +08:00
疯狂的狮子Li
eb8bdd5655 !260 add 支持顶部导航
Merge pull request !260 from Lau/dev
2026-03-10 01:17:41 +00:00
lau
1681a32dbc add 支持顶部导航 2026-03-09 23:43:53 +08:00
gssong
705e68759d add 增加转办等消息提示 2026-03-06 18:36:32 +08:00
疯狂的狮子Li
6208a2d0ca fix 修复 富文本 无需列表无效问题 2026-02-27 09:40:43 +08:00
疯狂的狮子Li
9cae1bb675 update 优化 字典类型属性提醒说明 2026-02-11 16:39:46 +08:00
疯狂的狮子Li
6b9802dfe1 update 优化 字典类型属性提醒说明 2026-02-05 13:40:49 +08:00
疯狂的狮子Li
419e7bde9a 🧨🧨🧨发布 5.5.3-2.5.3 版本 提前祝大家新年快乐 2026-01-23 14:02:39 +08:00
疯狂的狮子Li
3c7691a6b7 update 优化 修改前端推荐的node版本号 2025-12-29 11:00:55 +08:00
疯狂的狮子Li
32ee077f1a fix 修复 角色删除按钮权限标识符不正确问题 2025-12-24 11:21:29 +08:00
疯狂的狮子Li
6b8600a989 fix 修复 代码漏改问题 2025-12-23 13:40:55 +08:00
疯狂的狮子Li
3724baa93a fix 修复 一个奇奇怪怪的问题(特殊芯片电脑可复现 https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IBTNM1) 2025-12-23 10:47:55 +08:00
疯狂的狮子Li
1e5f89817e !254 update 优化 字典组件值宽松匹配values的问题
Merge pull request !254 from 加多宝/N/A
2025-12-23 01:40:27 +00:00
疯狂的狮子Li
c28a224d78 🧨🧨🧨发布 5.5.2-2.5.2 版本 2025年最后一版 2025-12-23 09:29:05 +08:00
加多宝
3008a8d7b0 update 优化 字典组件值宽松匹配values的问题
Signed-off-by: 加多宝 <945324621@qq.com>
2025-12-20 08:35:49 +00:00
疯狂的狮子Li
56bb05d547 update 优化 更改方法命名避免误会 2025-12-19 17:54:34 +08:00
疯狂的狮子Li
b4282f1423 update 优化 字典组件值宽松匹配 2025-12-18 09:39:56 +08:00
疯狂的狮子Li
f9c3958d5d !252 feat: 添加开发者工具保护功能,防止调试
Merge pull request !252 from mubai576/dev
2025-12-01 01:30:19 +00:00
mubai576
0e210b90a2 feat: 添加开发者工具保护功能,防止调试 2025-11-30 17:36:27 +08:00
疯狂的狮子Li
6a17a0735d fix 修复 附件按钮权限不生效 2025-11-06 10:00:50 +08:00
beginner
8284a87d36 !247 feat(login): 刷新验证码时清空验证码输入框
* feat(login): 刷新验证码时清空验证码输入框
* feat(login): 添加了验证码错误时清空输入框的功能
2025-11-05 01:25:06 +00:00
疯狂的狮子Li
cdad26bba6 !248 update 前端依赖小版本升级
Merge pull request !248 from Lau/dev
2025-11-05 01:24:40 +00:00
lau
ab9b1a1367 update 前端依赖小版本升级 2025-11-05 00:38:41 +08:00
lau
8048d80baa update 前端依赖小版本升级
JSEncrypt导包方式更换
Axios增加更明确的超时错误配置
2025-11-05 00:19:50 +08:00
疯狂的狮子Li
f1ef2b1083 update 优化 增加隐藏子菜单激活路由选项编辑功能 2025-11-04 15:05:47 +08:00
疯狂的狮子Li
5e1d44c2af 🐳🐳🐳发布 5.5.1-2.5.1 日常依赖升级bug修复 2025-10-28 11:20:14 +08:00
疯狂的狮子Li
55691695c4 fix 修复 前端变量名错误 2025-10-07 16:22:36 +08:00
Lau
53e7d03a1c !245 update 页面中的标题都从配置项获取
* update 规范环境变量命名
* update 将页面中的标题都从配置项获取
2025-09-28 03:27:45 +00:00
疯狂的狮子Li
9c84bf242c !244 update 挂载全局属性改为操作vue模块
Merge pull request !244 from Lau/dev
2025-09-28 02:31:40 +00:00
lau
b89e9cee7f update 挂载全局属性改为操作vue模块 2025-09-28 10:23:17 +08:00
疯狂的狮子Li
014bedd301 update 优化 禁止选择动态表单(无此功能) 2025-09-26 15:20:19 +08:00
疯狂的狮子Li
ceb6de9044 !243 update 升级unocss版本, 解决 nodejs lts 22 版本兼容问题
Merge pull request !243 from JackyTang/dev
2025-09-26 04:33:55 +00:00
JackyTang
04c6131fb0 update 升级unocss版本, 解决 nodejs lts 22 版本兼容问题 2025-09-26 12:17:08 +08:00
疯狂的狮子Li
c9cfefdc3e add 增加 同步租户参数配置功能 2025-09-26 11:57:33 +08:00
疯狂的狮子Li
fbe9254114 fix 修复 按钮权限不设置导致的问题 2025-09-25 11:34:52 +08:00
疯狂的狮子Li
88056a5067 发布 5.5.0-2.5.0 喜迎国庆🧨🧨🧨 2025-09-22 11:16:38 +08:00
lau
3da18c9464 update 调整菜单栏收起时的样式 2025-09-09 20:11:27 +08:00
疯狂的狮子Li
b4a40c94dc update 优化 岗位页面查询权限问题 2025-09-03 14:14:53 +08:00
lau
c11b91a48e fix 修复选择审批人选择组件没有回显的问题 2025-08-29 19:05:24 +08:00
lau
65da8dfa93 fix 修复菜单栏有二级菜单和无二级菜单缩进不一致的问题 2025-08-29 17:51:29 +08:00
疯狂的狮子Li
e10ef50288 fix 修复 路由参数缓存导致分页错误 2025-08-29 17:37:45 +08:00
疯狂的狮子Li
4c607f6915 fix 修复 初始化用户选择组件 数据为空导致的问题 2025-08-29 11:38:14 +08:00
疯狂的狮子Li
43b4e74c9c update 优化 支持前端返回节点扩展数据(按钮权限 抄送人 扩展变量) 2025-08-28 17:55:21 +08:00
疯狂的狮子Li
f84e95d735 fix 修复 遗漏字段 2025-08-25 10:23:51 +08:00
疯狂的狮子Li
dba12f25e2 !239 update 代码生成预览增加高亮
Merge pull request !239 from Lau/dev
2025-08-20 14:27:16 +00:00
lau
257ececa52 update 代码生成预览增加高亮 2025-08-20 22:09:48 +08:00
疯狂的狮子Li
7d36621c44 !238 fix 修复手机号校验的正则表达式错误
Merge pull request !238 from undefined/dev
2025-08-19 10:11:52 +00:00
疯狂的狮子Li
a29d03b231 !237 update: tag和菜单栏样式调整,增加圆角和缩进
Merge pull request !237 from Lau/dev
2025-08-19 10:08:57 +00:00
疯狂的狮子Li
ab99104240 fix 修复 错误提交 2025-08-19 18:03:45 +08:00
ymj666
153758df82 fix 修复手机号校验的正则表达式错误 2025-08-19 17:00:49 +08:00
gssong
5e5fca8f6b add 增加业务扩展 2025-08-15 21:15:47 +08:00
lau
d23bf73a2e update: tag和菜单栏样式调整,增加圆角和缩进 2025-08-14 20:16:26 +08:00
疯狂的狮子Li
ad7058b739 !236 update 收起菜单时从展开列表中移除对应菜单
Merge pull request !236 from Lau/dev
2025-08-14 01:17:48 +00:00
lau
0dd5044bbe update 收起菜单时从展开列表中移除对应菜单 2025-08-14 09:07:35 +08:00
疯狂的狮子Li
ae5dd09ba2 remove 删除无用按钮 2025-08-12 16:10:32 +08:00
疯狂的狮子Li
0e20743c28 update transition enter 2025-08-11 10:36:33 +08:00
疯狂的狮子Li
9223fabde7 !233 fix 修复 流程实例页面deleteHisByInstanceIds函数未定义导致流程实例界面无法正常渲染
Merge pull request !233 from Lapwing/dev
2025-08-04 08:14:21 +00:00
midsumor
1282839f67 fix 修复 流程实例页面deleteHisByInstanceIds函数未定义导致流程实例界面无法正常渲染 2025-08-04 15:59:17 +08:00
gssong
952f56ca2e add 升级1.8增加钉钉设计器 2025-08-03 17:04:11 +08:00
gssong
e08f41dd72 update 升级1.8设计器 2025-08-03 16:41:55 +08:00
疯狂的狮子Li
2392f64233 update 优化 getBackTaskNode 获取驳回节点接口 如果是委派直接返回当前节点 不允许驳回到其他节点 2025-07-28 16:25:35 +08:00
may
1565ec1996 add 增加下一节点执行人是当前任务处理人自动审批
fix 修复已完成的实例删除失败
2025-07-27 10:45:33 +08:00
may
d95f358d1b update 调整变量修改 2025-07-25 23:20:03 +08:00
may
7ea5199fd2 add 增加变量修改 2025-07-25 23:03:43 +08:00
may
4280c7177d add 增加催办 2025-07-25 18:22:32 +08:00
疯狂的狮子Li
4472b24def update 优化 代码结构 2025-07-23 14:15:04 +08:00
疯狂的狮子Li
33cf333b2a !230 fix 修复菜单查询没有正确显示顶级菜单的问题
Merge pull request !230 from Lau/dev
2025-07-23 06:12:41 +00:00
lau
27a427eb97 fix 修复菜单查询没有正确显示顶级菜单的问题 2025-07-23 14:09:41 +08:00
疯狂的狮子Li
e2e1ce4091 !229 fix 修改暗黑模式样式无法覆盖element默认样式的问题
Merge pull request !229 from Lau/dev
2025-07-22 07:17:27 +00:00
lau
4013c06fea fix 修复修改暗黑模式样式无法覆盖element默认样式的问题 2025-07-17 20:57:17 +08:00
疯狂的狮子Li
8a029f6c4c update 优化 增加请求流程后端发起demo案例 2025-07-17 14:32:54 +08:00
疯狂的狮子Li
c785a9fb7f update 优化 增加请求流程后端发起demo案例 2025-07-17 14:28:45 +08:00
疯狂的狮子Li
95cbd2f3af update 优化 将请假申请流程选择框直接放到表单内 减少弹窗 2025-07-17 13:16:12 +08:00
疯狂的狮子Li
edacb79ccb update 优化 删除后端已经不存在的接口 2025-07-15 14:07:24 +08:00
疯狂的狮子Li
0872624adc update 优化 roleOptions 去重处理 2025-07-14 11:37:51 +08:00
疯狂的狮子Li
1bf03053e1 update 优化 sse重试改为5次 避免掉线频繁连接 2025-07-07 15:36:07 +08:00
疯狂的狮子Li
47c2724058 update 优化 删除无用接口 2025-07-07 15:25:01 +08:00
疯狂的狮子Li
219ab65eb7 fix 修复 流程表达式页面权限标识符错误 2025-07-07 15:22:55 +08:00
疯狂的狮子Li
107b2d444b !227 更新 pr!226 页面展示
Merge pull request !227 from MichelleChung/dev
2025-07-06 06:02:40 +00:00
Michelle.Chung
a8bb81c984 update: 更新流程spel页面展示 ; 2025-07-06 13:51:26 +08:00
疯狂的狮子Li
0ca453d549 update 优化 流程表达式页面 2025-07-06 11:47:34 +08:00
疯狂的狮子Li
093c05bda0 fix 修复 提交流程报错 loading未关闭问题 2025-07-06 11:33:18 +08:00
疯狂的狮子Li
94dcc28c8a !226 add: 新增流程spel表达式相关菜单 ;
Merge pull request !226 from MichelleChung/dev
2025-07-06 02:54:15 +00:00
Michelle.Chung
35b016b3ba add: 新增流程spel表达式相关菜单 ; 2025-07-05 19:11:46 +08:00
疯狂的狮子Li
f6d69e2bea update 优化 用户编辑页面展示逻辑 2025-07-04 14:51:56 +08:00
疯狂的狮子Li
9573343afc !225 fix: 菜单级联删除添加按钮权限
Merge pull request !225 from 有梦的人/dev
2025-07-04 04:38:41 +00:00
有梦的人
8f99c76e72 fix: 菜单级联删除添加按钮权限 2025-07-04 12:29:01 +08:00
81 changed files with 2836 additions and 870 deletions

View File

@@ -1,5 +1,6 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
# 开发环境配置
VITE_APP_ENV = 'development'

View File

@@ -1,5 +1,6 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
# 生产环境配置
VITE_APP_ENV = 'production'

View File

@@ -17,6 +17,9 @@
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"ShallowRef": true,
"Slot": true,
"Slots": true,
"VNode": true,
"WritableComputedRef": true,
"acceptHMRUpdate": true,
@@ -35,6 +38,7 @@
"createInjectionState": true,
"createPinia": true,
"createReactiveFn": true,
"createRef": true,
"createReusableTemplate": true,
"createSharedComposable": true,
"createTemplatePromise": true,
@@ -51,6 +55,7 @@
"getActivePinia": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"getCurrentWatcher": true,
"h": true,
"ignorableWatch": true,
"inject": true,
@@ -60,6 +65,7 @@
"isReactive": true,
"isReadonly": true,
"isRef": true,
"isShallow": true,
"makeDestructurable": true,
"mapActions": true,
"mapGetters": true,
@@ -103,11 +109,11 @@
"refAutoReset": true,
"refDebounced": true,
"refDefault": true,
"refManualReset": true,
"refThrottled": true,
"refWithControl": true,
"resolveComponent": true,
"resolveRef": true,
"resolveUnref": true,
"setActivePinia": true,
"setMapStoreSuffix": true,
"shallowReactive": true,
@@ -165,6 +171,7 @@
"useCountdown": true,
"useCounter": true,
"useCssModule": true,
"useCssSupports": true,
"useCssVar": true,
"useCssVars": true,
"useCurrentElement": true,
@@ -277,6 +284,7 @@
"useThrottleFn": true,
"useThrottledRefHistory": true,
"useTimeAgo": true,
"useTimeAgoIntl": true,
"useTimeout": true,
"useTimeoutFn": true,
"useTimeoutPoll": true,
@@ -315,9 +323,6 @@
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true,
"Slot": true,
"Slots": true,
"createRef": true
"whenever": true
}
}

View File

@@ -1,7 +1,7 @@
## 平台简介
- 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://github.com/imdap/ruoyi-plus-vben5)
- 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
## 配套后端代码仓库地址

View File

@@ -6,7 +6,7 @@
<meta name="renderer" content="webkit" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<link rel="icon" href="/favicon.ico" />
<title>RuoYi-Vue-Plus多租户管理系统</title>
<title>%VITE_APP_TITLE%</title>
<!--[if lt IE 11
]><script>
window.location.href = '/html/ie.html';

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "ruoyi-vue-plus",
"version": "5.4.1-2.4.1",
"version": "5.6.0-2.6.0",
"description": "RuoYi-Vue-Plus多租户管理系统",
"author": "LionLi",
"license": "MIT",
@@ -20,72 +20,71 @@
"url": "https://gitee.com/JavaLionLi/plus-ui.git"
},
"dependencies": {
"@element-plus/icons-vue": "2.3.1",
"@highlightjs/vue-plugin": "2.1.0",
"@element-plus/icons-vue": "2.3.2",
"@highlightjs/vue-plugin": "2.1.2",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "13.1.0",
"@vueuse/core": "14.2.1",
"animate.css": "4.1.1",
"await-to-js": "3.0.0",
"axios": "1.8.4",
"axios": "1.13.6",
"crypto-js": "4.2.0",
"echarts": "5.6.0",
"element-plus": "2.9.8",
"echarts": "6.0.0",
"element-plus": "2.13.5",
"file-saver": "2.0.5",
"highlight.js": "11.9.0",
"highlight.js": "11.11.1",
"image-conversion": "2.1.1",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"jsencrypt": "3.5.4",
"nprogress": "0.2.0",
"pinia": "3.0.2",
"pinia": "3.0.4",
"screenfull": "6.0.2",
"vue": "3.5.13",
"vue-cropper": "1.1.1",
"vue-i18n": "11.1.3",
"vue-json-pretty": "2.4.0",
"vue-router": "4.5.0",
"vue": "3.5.30",
"vue-cropper": "1.1.4",
"vue-i18n": "11.3.0",
"vue-json-pretty": "2.6.0",
"vue-router": "5.0.3",
"vue-types": "6.0.0",
"vxe-table": "4.13.7"
"vxe-table": "4.18.1"
},
"devDependencies": {
"@iconify/json": "^2.2.276",
"@iconify/json": "^2.2.448",
"@types/crypto-js": "4.2.2",
"@types/file-saver": "2.0.7",
"@types/js-cookie": "3.0.6",
"@types/node": "^22.13.4",
"@types/node": "^25.4.0",
"@types/nprogress": "0.2.3",
"@unocss/preset-attributify": "66.0.0",
"@unocss/preset-icons": "66.0.0",
"@unocss/preset-uno": "66.0.0",
"@vitejs/plugin-vue": "5.2.3",
"@vue/compiler-sfc": "3.5.13",
"@unocss/preset-attributify": "66.6.6",
"@unocss/preset-icons": "66.6.6",
"@unocss/preset-uno": "66.6.6",
"@vitejs/plugin-vue": "6.0.4",
"@vue/compiler-sfc": "3.5.30",
"@vue/eslint-config-prettier": "10.2.0",
"@vue/eslint-config-typescript": "14.4.0",
"autoprefixer": "10.4.20",
"eslint": "9.21.0",
"eslint-plugin-prettier": "5.2.3",
"eslint-plugin-vue": "9.32.0",
"globals": "16.0.0",
"prettier": "3.5.2",
"sass": "1.87.0",
"typescript": "~5.8.3",
"unocss": "66.0.0",
"unplugin-auto-import": "19.1.2",
"unplugin-icons": "22.1.0",
"unplugin-vue-components": "28.5.0",
"@vue/eslint-config-typescript": "14.6.0",
"autoprefixer": "10.4.27",
"eslint": "9.39.1",
"eslint-plugin-prettier": "5.5.5",
"eslint-plugin-vue": "9.33.0",
"globals": "17.4.0",
"prettier": "3.8.1",
"sass": "1.98.0",
"typescript": "~5.9.3",
"unocss": "66.6.6",
"unplugin-auto-import": "21.0.0",
"unplugin-icons": "23.0.1",
"unplugin-vue-components": "31.0.0",
"unplugin-vue-setup-extend-plus": "1.0.1",
"vite": "6.3.2",
"vite-plugin-compression": "0.5.1",
"vite-plugin-svg-icons-ng": "^1.4.0",
"vite-plugin-vue-devtools": "7.7.5",
"vitest": "3.1.2",
"vue-tsc": "^2.2.8"
"vite": "7.3.2",
"vite-plugin-svg-icons-ng": "^1.5.2",
"vite-plugin-vue-devtools": "8.0.7",
"vitest": "4.0.18",
"vue-tsc": "^3.2.5"
},
"overrides": {
"quill": "2.0.2"
"quill": "1.3.7"
},
"engines": {
"node": ">=18.18.0",
"npm": ">=8.9.0"
"node": ">=20.19.0",
"npm": ">=8.19.0"
},
"browserslist": [
"Chrome >= 87",

View File

@@ -38,14 +38,6 @@ export const getDept = (deptId: string | number): AxiosPromise<DeptVO> => {
});
};
// 查询部门下拉树结构
export const treeselect = (): AxiosPromise<DeptTreeVO[]> => {
return request({
url: '/system/dept/treeselect',
method: 'get'
});
};
// 新增部门
export const addDept = (data: DeptForm) => {
return request({

View File

@@ -1,6 +1,7 @@
import request from '@/utils/request';
import { PostForm, PostQuery, PostVO } from './types';
import { AxiosPromise } from 'axios';
import { DeptTreeVO } from '../dept/types';
// 查询岗位列表
export function listPost(query: PostQuery): AxiosPromise<PostVO[]> {
@@ -56,3 +57,13 @@ export function delPost(postId: string | number | (string | number)[]) {
method: 'delete'
});
}
/**
* 查询部门下拉树结构
*/
export const deptTreeSelect = (): AxiosPromise<DeptTreeVO[]> => {
return request({
url: '/system/post/deptTree',
method: 'get'
});
};

View File

@@ -1,7 +1,7 @@
import request from '@/utils/request';
// 绑定账号
export function authBinding(source: string, tenantId: string) {
// 获取跳转URL
export function authRouterUrl(source: string, tenantId: string) {
return request({
url: '/auth/binding/' + source,
method: 'get',

View File

@@ -99,3 +99,11 @@ export function syncTenantDict() {
method: 'get'
});
}
// 同步租户字典
export function syncTenantConfig() {
return request({
url: '/system/tenant/syncTenantConfig',
method: 'get'
});
}

View File

@@ -1,4 +1,4 @@
import { DeptTreeVO, DeptVO } from './../dept/types';
import { DeptTreeVO } from './../dept/types';
import { RoleVO } from '@/api/system/role/types';
import request from '@/utils/request';
import { AxiosPromise } from 'axios';

View File

@@ -20,7 +20,7 @@ export interface UserQuery extends PageQuery {
status?: string;
deptId?: string | number;
roleId?: string | number;
userIds?: string;
userIds?: string | number | (string | number)[] | undefined;
}
/**

View File

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

View File

@@ -87,6 +87,18 @@ export const deleteByInstanceIds = (instanceIds: Array<string | number> | string
method: 'delete'
});
};
/**
* 删除历史流程实例
* @param instanceIds
*/
export const deleteHisByInstanceIds = (instanceIds: Array<string | number> | string | number) => {
return request({
url: `/workflow/instance/deleteHisByInstanceIds/${instanceIds}`,
method: 'delete'
});
};
/**
* 作废流程
* @param data 参数
@@ -99,3 +111,15 @@ export const invalid = (data: any) => {
data: data
});
};
/**
* 修改流程变量
* @param data 参数
* @returns
*/
export const updateVariable = (data: any) => {
return request({
url: `/workflow/instance/updateVariable`,
method: 'put',
data: data
});
};

View File

@@ -23,4 +23,6 @@ export interface FlowInstanceVO extends BaseEntity {
flowStatus: string;
flowStatusName: string;
flowTaskList: FlowTaskVO[];
businessCode: string;
businessTitle: string;
}

View File

@@ -39,6 +39,18 @@ export const addLeave = (data: LeaveForm): AxiosPromise<LeaveVO> => {
});
};
/**
* 提交请假并发起流程
* @param data
*/
export const submitAndFlowStart = (data: LeaveForm): AxiosPromise<LeaveVO> => {
return request({
url: '/workflow/leave/submitAndFlowStart',
method: 'post',
data: data
});
};
/**
* 修改请假
* @param data

View File

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

View File

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

View File

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

View File

@@ -148,9 +148,9 @@ export const terminationTask = (data: any) => {
* 获取可驳回得任务节点
* @returns
*/
export const getBackTaskNode = (definitionId: string, nodeCode: string) => {
export const getBackTaskNode = (taskId: string | number, nodeCode: string) => {
return request({
url: `/workflow/task/getBackTaskNode/${definitionId}/${nodeCode}`,
url: `/workflow/task/getBackTaskNode/${taskId}/${nodeCode}`,
method: 'get'
});
};
@@ -191,3 +191,16 @@ export const getNextNodeList = (data: any): any => {
data: data
});
};
/**
* 催办任务
* @param data参数
* @returns
*/
export const urgeTask = (data: any): any => {
return request({
url: '/workflow/task/urgeTask',
method: 'post',
data: data
});
};

View File

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

View File

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

View File

@@ -45,7 +45,7 @@
font-size: 14px;
color: #fff;
padding: 14px 36px;
border-radius: 8px;
border-radius: var(--app-radius-md);
border: none;
outline: none;
transition: 600ms ease all;
@@ -95,5 +95,5 @@
margin: 0;
padding: 10px 15px;
font-size: 14px;
border-radius: 4px;
border-radius: var(--app-radius-sm);
}

View File

@@ -74,6 +74,9 @@
.el-dialog {
margin: 0 auto !important;
border-radius: var(--app-radius-base);
box-shadow: var(--app-shadow-md);
overflow: hidden;
.el-dialog__body {
padding: 15px !important;
@@ -108,6 +111,8 @@
// dropdown
.el-dropdown-menu {
border-radius: var(--app-radius-md);
box-shadow: var(--app-shadow-sm);
a {
display: block;
}
@@ -151,3 +156,122 @@
.el-message-box .el-message-box__message {
word-break: break-word;
}
.el-message-box {
border-radius: var(--app-radius-base);
box-shadow: var(--app-shadow-md);
}
.el-message,
.el-notification,
.el-alert {
border-radius: var(--app-radius-md);
box-shadow: var(--app-shadow-sm);
}
// Modern rounded inputs
.el-input__wrapper,
.el-textarea__inner,
.el-select__wrapper,
.el-date-editor,
.el-range-editor,
.el-input-number,
.el-input-number__decrease,
.el-input-number__increase {
border-radius: var(--app-radius-md);
transition: box-shadow 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}
.el-input__wrapper.is-focus,
.el-textarea__inner:focus,
.el-select__wrapper.is-focus,
.el-date-editor.is-focus,
.el-range-editor.is-focus {
box-shadow: 0 0 0 2px var(--el-color-primary-light-8);
}
// Buttons
.el-button {
border-radius: var(--app-radius-md);
transition: transform 0.15s ease, box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
}
.el-button:not(.is-text):not(.is-link):hover {
transform: translateY(-1px);
box-shadow: var(--app-shadow-sm);
}
// Tags and badges
.el-tag {
border-radius: var(--app-radius-md);
}
// Cards, popovers, drawers
.el-popover,
.el-tooltip__popper,
.el-popper {
border-radius: var(--app-radius-md);
box-shadow: var(--app-shadow-sm);
}
.el-drawer {
border-radius: var(--app-radius-base);
box-shadow: var(--app-shadow-md);
}
.el-drawer__header {
margin-bottom: 0;
padding: 16px 20px;
border-bottom: 1px solid var(--el-border-color-light);
}
// Table polish
.el-table {
border-radius: var(--app-radius-base);
overflow: hidden;
box-shadow: var(--app-shadow-sm);
border: 1px solid var(--el-border-color-lighter);
}
.el-table__header-wrapper,
.el-table__body-wrapper {
background: var(--el-bg-color);
}
.el-table__row:hover td.el-table__cell {
background-color: var(--el-fill-color-light) !important;
}
// Tabs
.el-tabs__header {
margin: 0 0 12px 0;
}
.el-tabs__item {
border-radius: var(--app-radius-md);
margin: 0 2px;
}
.el-tabs__nav-wrap::after {
background-color: var(--el-border-color-lighter);
}
// Pagination
.el-pagination .btn-prev,
.el-pagination .btn-next,
.el-pagination .el-pager li {
border-radius: var(--app-radius-sm);
transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}
.el-pagination .el-pager li.is-active {
box-shadow: var(--app-shadow-sm);
}
// Breadcrumb
.el-breadcrumb {
padding: 0;
background: transparent;
border: none;
border-radius: 0;
}

View File

@@ -14,18 +14,20 @@ body {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
background: var(--el-bg-color-page);
font-family:
Helvetica Neue,
Helvetica,
PingFang SC,
Hiragino Sans GB,
Microsoft YaHei,
Arial,
'MiSans',
'HarmonyOS Sans SC',
'PingFang SC',
'Source Han Sans SC',
'Noto Sans SC',
'Hiragino Sans GB',
'Microsoft YaHei',
sans-serif;
}
label {
font-weight: 700;
font-weight: 600;
}
html {
@@ -116,11 +118,18 @@ aside {
background: #eef1f6;
padding: 8px 24px;
margin-bottom: 20px;
border-radius: 2px;
border-radius: var(--app-radius-md);
display: block;
line-height: 32px;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
font-family:
'MiSans',
'HarmonyOS Sans SC',
'PingFang SC',
'Source Han Sans SC',
'Noto Sans SC',
'Hiragino Sans GB',
'Microsoft YaHei',
sans-serif;
color: #2c3e50;
-webkit-font-smoothing: antialiased;
@@ -139,21 +148,26 @@ aside {
//main-container全局样式
.app-container {
padding: 20px;
background: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
border-radius: var(--app-radius-base);
box-shadow: var(--app-shadow-sm);
}
// search面板样式
.panel,
.search {
margin-bottom: 0.75rem;
border-radius: 0.25rem;
border-radius: var(--app-radius-base);
border: 1px solid var(--el-border-color-light);
background-color: var(--el-bg-color-overlay);
padding: 0.75rem;
transition: all ease 0.3s;
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
&:hover {
box-shadow: 0 2px 12px #0000001a;
transition: all ease 0.3s;
box-shadow: var(--app-shadow-sm);
border-color: var(--el-border-color);
transform: translateY(-1px);
}
}

View File

@@ -74,7 +74,7 @@ h6 {
}
.el-form .el-form-item__label {
font-weight: 700;
font-weight: 600;
}
.el-dialog:not(.is-fullscreen) {
margin-top: 6vh !important;
@@ -127,7 +127,7 @@ h6 {
margin-top: 5px;
border: 1px solid #e5e6e7;
background: #ffffff none;
border-radius: 4px;
border-radius: var(--app-radius-md);
width: 100%;
}
@@ -190,12 +190,27 @@ h6 {
.el-card__header {
padding: 14px 15px 7px !important;
min-height: 40px;
background: var(--el-fill-color-blank);
border-bottom: 1px solid var(--el-border-color-light);
}
.el-card__body {
padding: 15px 20px 20px 20px !important;
}
.el-card {
border-radius: var(--app-radius-lg);
box-shadow: var(--app-shadow-sm);
border-color: var(--el-border-color-lighter);
overflow: hidden;
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.el-card:hover {
box-shadow: var(--app-shadow-md);
transform: translateY(-1px);
}
.card-box {
margin-bottom: 10px;
}
@@ -282,3 +297,9 @@ h6 {
.top-right-btn {
margin-left: auto;
}
/* horizontal el menu */
.el-menu--horizontal .el-menu-item .svg-icon + span,
.el-menu--horizontal .el-sub-menu__title .svg-icon + span {
margin-left: 3px;
}

View File

@@ -6,6 +6,7 @@
transition: margin-left 0.28s;
margin-left: $base-sidebar-width;
position: relative;
background: var(--el-bg-color-page);
}
.sidebarHide {
@@ -65,7 +66,7 @@
}
.svg-icon {
margin-right: 16px;
margin-right: 10px;
}
.el-menu {
@@ -88,12 +89,16 @@
// menu hover
.theme-dark .sub-menu-title-noDropdown,
.theme-dark .el-sub-menu__title {
border-radius: var(--app-radius-md);
margin: 1px 5px 1px 5px;
&:hover {
background-color: $base-sub-menu-title-hover !important;
}
}
.sub-menu-title-noDropdown,
.el-sub-menu__title {
border-radius: var(--app-radius-md);
margin: 1px 5px 1px 5px;
&:hover {
background-color: rgba(0, 0, 0, 0.05) !important;
}
@@ -105,8 +110,11 @@
& .nest-menu .el-sub-menu > .el-sub-menu__title,
& .el-sub-menu .el-menu-item {
min-width: $base-sidebar-width !important;
&:hover {
min-width: calc($base-sidebar-width - 20px) !important;
border-radius: var(--app-radius-md);
height: 45px;
margin: 1px 5px 1px 5px;
&:not(.is-active):hover {
background-color: rgba(0, 0, 0, 0.1) !important;
}
}
@@ -114,28 +122,54 @@
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
& .theme-dark .el-sub-menu .el-menu-item {
background-color: $base-sub-menu-background !important;
border-radius: var(--app-radius-md);
height: 45px;
margin: 1px 5px 1px 5px;
&:hover {
&.is-active {
background-color: var(--el-menu-active-color) !important;
color: #fff;
}
&:not(.is-active):hover {
// you can use $sub-menuHover
background-color: $base-sub-menu-hover !important;
}
}
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
& .theme-dark .el-menu-item {
&:hover {
border-radius: var(--app-radius-md);
height: 45px;
margin: 1px 5px 1px 5px;
&.is-active {
background-color: var(--el-menu-active-color) !important;
color: #fff;
}
&:not(.is-active):hover {
// you can use $sub-menuHover
background-color: $base-menu-hover !important;
}
}
& .nest-menu .el-sub-menu > .el-sub-menu__title,
& .el-menu-item {
&:hover {
border-radius: var(--app-radius-md);
height: 45px;
margin: 1px 5px 1px 5px;
&.is-active {
background-color: var(--el-menu-active-color) !important;
color: #fff;
}
&:not(.is-active):hover {
// you can use $sub-menuHover
background-color: rgba(0, 0, 0, 0.04) !important;
}
}
}
// 收起菜单后的样式
.hideSidebar {
.sidebar-container {
width: 54px !important;
@@ -148,29 +182,48 @@
.sub-menu-title-noDropdown {
padding: 0 !important;
position: relative;
height: 45px;
// 选中状态的菜单
&.is-active {
background-color: var(--el-menu-active-color) !important;
color: #fff !important;
}
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
}
}
.el-sub-menu {
& .el-sub-menu {
overflow: hidden;
border-radius: var(--app-radius-md);
.el-sub-menu__title.el-tooltip__trigger {
border-radius: var(--app-radius-md);
height: 45px;
}
// 选中状态的菜单
&.is-active .el-sub-menu__title.el-tooltip__trigger {
background-color: var(--el-menu-active-color) !important;
}
& > .el-sub-menu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
}
}
.el-menu--collapse {
.is-active .svg-icon {
fill: #fff;
}
.svg-icon {
display: flex;
margin: auto;
height: 100%;
// 这里设置width会跟随sidebar-container的transition 不符合预期
}
.el-sub-menu {
& > .el-sub-menu__title {
& > span {
@@ -232,3 +285,13 @@
}
}
}
// 收起菜单后悬浮的菜单样式
.el-popper.is-pure{
border-radius: var(--app-radius-md);
.el-menu--popup{
border-radius: var(--app-radius-md);
}
.el-menu-item{
border-radius: var(--app-radius-sm);
}
}

View File

@@ -6,7 +6,7 @@
transition: opacity 0.28s;
}
.fade-enter,
.fade-enter-from,
.fade-leave-active {
opacity: 0;
}
@@ -18,7 +18,7 @@
transition: all 0.5s;
}
.fade-transform-enter {
.fade-transform-enter-from {
opacity: 0;
transform: translateX(-30px);
}
@@ -34,7 +34,7 @@
transition: all 0.5s;
}
.breadcrumb-enter,
.breadcrumb-enter-from,
.breadcrumb-leave-active {
opacity: 0;
transform: translateX(20px);

View File

@@ -1,6 +1,6 @@
// 全局SCSS变量
:root {
--menuBg: #304156;
--menuBg: #1f2d3d;
--menuColor: #bfcbd9;
--menuActiveText: #f4f4f5;
--menuHover: #263445;
@@ -10,6 +10,11 @@
--subMenuHover: #001528;
--subMenuTitleHover: #293444;
// 菜单栏缩进
--el-menu-base-level-padding: 12px;
--el-menu-level-padding: 8px;
--el-menu-item-height: 50px;
--fixedHeaderBg: #ffffff;
--tableHeaderBg: #f8f8f9;
--tableHeaderTextColor: #515a6e;
@@ -20,6 +25,25 @@
// 添加 tag 相关变量
--tags-view-active-bg: var(--el-color-primary);
--tags-view-active-border-color: var(--el-color-primary);
// Modern rounded style + soft shadows
--app-radius-base: 14px;
--app-radius-sm: calc(var(--app-radius-base) - 8px);
--app-radius-md: calc(var(--app-radius-base) - 4px);
--app-radius-lg: var(--app-radius-base);
--app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08), 0 6px 16px rgba(15, 23, 42, 0.08);
--app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.12);
--app-shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.16);
--app-surface-bg: #ffffff;
--app-surface-border: var(--el-border-color-lighter);
// Element Plus tokens
--el-border-radius-base: var(--app-radius-md);
--el-border-radius-small: var(--app-radius-sm);
--el-border-radius-round: 999px;
--el-box-shadow-light: var(--app-shadow-sm);
--el-box-shadow: var(--app-shadow-md);
--el-bg-color-page: #f5f7fb;
}
html.dark {
@@ -43,8 +67,103 @@ html.dark {
}
.el-button--primary {
--el-button-bg-color: var(--el-color-primary-dark-6);
--el-button-border-color: var(--el-color-primary-light-2);
--el-button-bg-color: #2b6bd3;
--el-button-border-color: #3a7be8;
--el-button-text-color: #eef4ff;
--el-button-hover-bg-color: #3a7be8;
--el-button-hover-border-color: #3a7be8;
--el-button-active-bg-color: #255fb8;
--el-button-active-border-color: #255fb8;
}
.el-button--primary.is-plain {
--el-button-bg-color: rgba(43, 107, 211, 0.12);
--el-button-border-color: rgba(58, 123, 232, 0.5);
--el-button-text-color: #dbe8ff;
--el-button-hover-bg-color: rgba(58, 123, 232, 0.2);
--el-button-hover-border-color: rgba(58, 123, 232, 0.7);
--el-button-active-bg-color: rgba(43, 107, 211, 0.28);
--el-button-active-border-color: rgba(43, 107, 211, 0.8);
}
.el-button--success {
--el-button-bg-color: #1f8a5a;
--el-button-border-color: #29a46d;
--el-button-text-color: #eefaf4;
--el-button-hover-bg-color: #29a46d;
--el-button-hover-border-color: #29a46d;
--el-button-active-bg-color: #1b784f;
--el-button-active-border-color: #1b784f;
}
.el-button--success.is-plain {
--el-button-bg-color: rgba(31, 138, 90, 0.12);
--el-button-border-color: rgba(41, 164, 109, 0.5);
--el-button-text-color: #dbf6e8;
--el-button-hover-bg-color: rgba(41, 164, 109, 0.2);
--el-button-hover-border-color: rgba(41, 164, 109, 0.7);
--el-button-active-bg-color: rgba(31, 138, 90, 0.28);
--el-button-active-border-color: rgba(31, 138, 90, 0.8);
}
.el-button--warning {
--el-button-bg-color: #b87922;
--el-button-border-color: #d6953b;
--el-button-text-color: #fff7e6;
--el-button-hover-bg-color: #d6953b;
--el-button-hover-border-color: #d6953b;
--el-button-active-bg-color: #a56c1d;
--el-button-active-border-color: #a56c1d;
}
.el-button--warning.is-plain {
--el-button-bg-color: rgba(184, 121, 34, 0.12);
--el-button-border-color: rgba(214, 149, 59, 0.5);
--el-button-text-color: #ffecc8;
--el-button-hover-bg-color: rgba(214, 149, 59, 0.2);
--el-button-hover-border-color: rgba(214, 149, 59, 0.7);
--el-button-active-bg-color: rgba(184, 121, 34, 0.28);
--el-button-active-border-color: rgba(184, 121, 34, 0.8);
}
.el-button--danger {
--el-button-bg-color: #b24a4a;
--el-button-border-color: #d16060;
--el-button-text-color: #ffecec;
--el-button-hover-bg-color: #d16060;
--el-button-hover-border-color: #d16060;
--el-button-active-bg-color: #9c3f3f;
--el-button-active-border-color: #9c3f3f;
}
.el-button--danger.is-plain {
--el-button-bg-color: rgba(178, 74, 74, 0.12);
--el-button-border-color: rgba(209, 96, 96, 0.5);
--el-button-text-color: #ffd6d6;
--el-button-hover-bg-color: rgba(209, 96, 96, 0.2);
--el-button-hover-border-color: rgba(209, 96, 96, 0.7);
--el-button-active-bg-color: rgba(178, 74, 74, 0.28);
--el-button-active-border-color: rgba(178, 74, 74, 0.8);
}
.el-button--info {
--el-button-bg-color: #4b5563;
--el-button-border-color: #667085;
--el-button-text-color: #f3f4f6;
--el-button-hover-bg-color: #667085;
--el-button-hover-border-color: #667085;
--el-button-active-bg-color: #3f4753;
--el-button-active-border-color: #3f4753;
}
.el-button--info.is-plain {
--el-button-bg-color: rgba(75, 85, 99, 0.16);
--el-button-border-color: rgba(102, 112, 133, 0.55);
--el-button-text-color: #e5e7eb;
--el-button-hover-bg-color: rgba(102, 112, 133, 0.22);
--el-button-hover-border-color: rgba(102, 112, 133, 0.75);
--el-button-active-bg-color: rgba(75, 85, 99, 0.3);
--el-button-active-border-color: rgba(75, 85, 99, 0.85);
}
.el-switch {
@@ -57,9 +176,41 @@ html.dark {
--el-tag-border-color: var(--el-color-primary-light-2);
}
.el-tag--success {
--el-tag-bg-color: rgba(31, 138, 90, 0.18);
--el-tag-border-color: rgba(41, 164, 109, 0.6);
--el-tag-text-color: #c7f2df;
}
.el-tag--warning {
--el-tag-bg-color: rgba(184, 121, 34, 0.18);
--el-tag-border-color: rgba(214, 149, 59, 0.6);
--el-tag-text-color: #ffe6bb;
}
.el-tag--danger {
--el-tag-bg-color: rgba(178, 74, 74, 0.18);
--el-tag-border-color: rgba(209, 96, 96, 0.6);
--el-tag-text-color: #ffd0d0;
}
.el-tag--info {
--el-tag-bg-color: rgba(75, 85, 99, 0.18);
--el-tag-border-color: rgba(102, 112, 133, 0.6);
--el-tag-text-color: #e5e7eb;
}
// 在深色模式下使用更深的颜色
--tags-view-active-bg: var(--el-color-primary-dark-6);
--tags-view-active-border-color: var(--el-color-primary-light-2);
// Modern rounded style + soft shadows (dark)
--app-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.28), 0 8px 18px rgba(0, 0, 0, 0.25);
--app-shadow-md: 0 10px 26px rgba(0, 0, 0, 0.35);
--app-shadow-lg: 0 14px 34px rgba(0, 0, 0, 0.4);
--app-surface-bg: #151922;
--app-surface-border: var(--el-border-color);
--el-bg-color-page: #0f1115;
// vxe-table 主题
--vxe-font-color: #98989e;
--vxe-primary-color: #2c7ecf;
@@ -132,4 +283,4 @@ $base-sidebar-width: 200px;
dangerColor: $--color-danger;
infoColor: $--color-info;
warningColor: $--color-warning;
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div>
<template v-for="(item, index) in options">
<template v-if="values.includes(item.value)">
<template v-if="isValueMatch(item.value)">
<span
v-if="(item.elTagType === 'default' || item.elTagType === '') && (item.elTagClass === '' || item.elTagClass == null)"
:key="item.value"
@@ -50,6 +50,7 @@ const props = withDefaults(defineProps<Props>(), {
const values = computed(() => {
if (props.value === '' || props.value === null || typeof props.value === 'undefined') return [];
if (typeof props.value === 'number' || typeof props.value === 'boolean') return [props.value]
return Array.isArray(props.value) ? props.value.map((item) => '' + item) : String(props.value).split(props.separator);
});
@@ -58,7 +59,7 @@ const unmatch = computed(() => {
// 传入值为非数组
let unmatch = false; // 添加一个标志来判断是否有未匹配项
values.value.forEach((item) => {
if (!props.options.some((v) => v.value === item)) {
if (!props.options.some((v) => v.value == item)) {
unmatch = true; // 如果有未匹配项将标志设置为true
}
});
@@ -85,6 +86,10 @@ const handleArray = (array: Array<string | number>) => {
return pre + ' ' + cur;
});
};
const isValueMatch = (itemValue: any) => {
return values.value.some(val => val == itemValue)
}
</script>
<style lang="scss" scoped>

View File

@@ -0,0 +1,100 @@
<template>
<el-dialog v-model="visible" :title="props.title" width="50%" draggable :before-close="cancel" center :close-on-click-modal="false">
<el-form v-loading="loading" ref="ruleFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="消息提醒" prop="messageType">
<el-checkbox-group v-model="form.messageType">
<el-checkbox value="1" name="type" disabled>站内信</el-checkbox>
<el-checkbox value="2" name="type">邮件</el-checkbox>
<el-checkbox value="3" name="type">短信</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="消息内容" prop="message">
<el-input v-model="form.message" type="textarea" resize="none" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer" style="float: right; padding-bottom: 20px">
<el-button :disabled="buttonDisabled" type="primary" @click="submit(ruleFormRef)">确认</el-button>
<el-button :disabled="buttonDisabled" @click="cancel">取消</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ComponentInternalInstance } from 'vue';
import { ElForm, FormInstance } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const emits = defineEmits(['submitCallback', 'cancelCallback']);
const props = defineProps({
title: {
type: String,
default: '提示'
}
});
const ruleFormRef = ref<FormInstance>();
//遮罩层
const loading = ref(true);
const visible = ref(false);
const buttonDisabled = ref(true);
const form = ref<Record<string, any>>({
message: undefined,
messageType: ['1']
});
const rules = reactive<Record<string, any>>({
messageType: [
{
required: true,
message: '请选择消息提醒',
trigger: 'change'
}
],
message: [
{
required: true,
message: '请输入消息内容',
trigger: 'blur'
}
]
});
//确认
//打开弹窗
const open = async () => {
reset();
visible.value = true;
loading.value = false;
buttonDisabled.value = false;
};
//关闭弹窗
const close = async () => {
reset();
visible.value = false;
};
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
emits('submitCallback', form.value);
}
});
};
//取消
const cancel = async () => {
visible.value = false;
buttonDisabled.value = false;
emits('cancelCallback');
};
//重置
const reset = async () => {
form.value.taskIdList = [];
form.value.message = '';
form.value.messageType = ['1'];
};
/**
* 对外暴露子组件方法
*/
defineExpose({
open,
close
});
</script>

View File

@@ -1,8 +1,8 @@
<template>
<div style="display: flex; justify-content: space-between">
<div>
<el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="info" @click="submitForm('draft')">暂存</el-button>
<el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="primary" @click="submitForm('submit')"> </el-button>
<el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="info" @click="submitForm('draft', mode)">暂存</el-button>
<el-button v-if="submitButtonShow" :loading="props.buttonLoading" type="primary" @click="submitForm('submit', mode)"> </el-button>
<el-button v-if="approvalButtonShow" :loading="props.buttonLoading" type="primary" @click="approvalVerifyOpen">审批</el-button>
<el-button v-if="props.id && props.status !== 'draft'" type="primary" @click="handleApprovalRecord">流程进度</el-button>
<slot />
@@ -19,12 +19,13 @@ const props = defineProps({
status: propTypes.string.def(''),
pageType: propTypes.string.def(''),
buttonLoading: propTypes.bool.def(false),
id: propTypes.string.def('') || propTypes.number.def()
id: propTypes.string.def('') || propTypes.number.def(),
mode: propTypes.bool.def(false)
});
const emits = defineEmits(['submitForm', 'approvalVerifyOpen', 'handleApprovalRecord']);
//暂存,提交
const submitForm = async (type) => {
emits('submitForm', type);
const submitForm = async (type, mode) => {
emits('submitForm', type, mode);
};
//审批
const approvalVerifyOpen = async () => {

View File

@@ -113,7 +113,8 @@ const handleTransferTask = async (data) => {
const taskOperationBo = reactive<TaskOperationBo>({
userId: data[0].userId,
taskId: task.value.id,
message: ''
message: '',
messageType: ['1']
});
await proxy?.$modal.confirm('是否确认提交?');
loading.value = true;
@@ -139,7 +140,8 @@ const addMultiInstanceUser = async (data) => {
const taskOperationBo = reactive<TaskOperationBo>({
userIds: data.map((e) => e.userId),
taskId: task.value.id,
message: ''
message: '',
messageType: ['1']
});
await proxy?.$modal.confirm('是否确认提交?');
loading.value = true;
@@ -163,7 +165,8 @@ const deleteMultiInstanceUser = async (row) => {
const taskOperationBo = reactive<TaskOperationBo>({
userIds: [row.userId],
taskId: task.value.id,
message: ''
message: '',
messageType: ['1']
});
await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
loading.value = false;

View File

@@ -8,7 +8,7 @@
<el-checkbox value="3" name="type">短信</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="附件">
<el-form-item label="附件" v-if="buttonObj.file">
<fileUpload v-model="form.fileId" :file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']" :file-size="20" />
</el-form-item>
<el-form-item label="抄送" v-if="buttonObj.copy">
@@ -80,7 +80,13 @@
<!-- 加签组件 -->
<UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
<!-- 弹窗选人 -->
<UserSelect ref="porUserRef" :multiple="true" :userIds="popUserIds" @confirm-call-back="handlePopUser"></UserSelect>
<UserSelect
ref="porUserRef"
:data="form.assigneeMap[nodeCode]"
:multiple="true"
:userIds="popUserIds"
@confirm-call-back="handlePopUser"
></UserSelect>
<!-- 驳回开始 -->
<el-dialog v-model="backVisible" draggable title="驳回" width="40%" :close-on-click-modal="false">
@@ -149,8 +155,7 @@ import {
import UserSelect from '@/components/UserSelect';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { UserVO } from '@/api/system/user/types';
import { FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
import { FlowCopyVo, FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
const userSelectCopyRef = ref<InstanceType<typeof UserSelect>>();
const transferTaskRef = ref<InstanceType<typeof UserSelect>>();
@@ -171,9 +176,11 @@ const buttonDisabled = ref(true);
//任务id
const taskId = ref<string>('');
//抄送人
const selectCopyUserList = ref<UserVO[]>([]);
const selectCopyUserList = ref<FlowCopyVo[]>([]);
//抄送人id
const selectCopyUserIds = ref<string>(undefined);
//自定义节点变量
const varNodeList = ref<Map<string, string>>(undefined);
//可减签的人员
const deleteUserList = ref<any>([]);
//弹窗可选择的人员id
@@ -217,7 +224,11 @@ const task = ref<FlowTaskVO>({
nodeType: undefined,
nodeRatio: undefined,
applyNode: false,
buttonList: []
buttonList: [],
copyList: [],
varList: undefined,
businessCode: undefined,
businessTitle: undefined
});
const dialog = reactive<DialogOption>({
visible: false,
@@ -254,17 +265,24 @@ const openDialog = async (id?: string) => {
const response = await getTask(taskId.value);
task.value = response.data;
buttonObj.value = {};
task.value.buttonList.forEach((e) => {
task.value.buttonList?.forEach((e) => {
buttonObj.value[e.code] = e.show;
});
selectCopyUserList.value = task.value.copyList;
selectCopyUserIds.value = task.value.copyList.map((e) => e.userId).join(',');
varNodeList.value = task.value.varList;
console.log('varNodeList', varNodeList.value);
buttonDisabled.value = false;
const data = {
taskId: taskId.value,
variables: props.taskVariables
};
const nextData = await getNextNodeList(data);
nestNodeList.value = nextData.data;
loading.value = false;
try {
const data = {
taskId: taskId.value,
variables: props.taskVariables
};
const nextData = await getNextNodeList(data);
nestNodeList.value = nextData.data;
} finally {
loading.value = false;
}
};
onMounted(() => {});
@@ -298,7 +316,7 @@ const handleCompleteTask = async () => {
selectCopyUserList.value.forEach((e) => {
const copyUser = {
userId: e.userId,
userName: e.nickName
nickName: e.nickName
};
flowCopyList.push(copyUser);
});
@@ -325,7 +343,7 @@ const handleBackProcessOpen = async () => {
backVisible.value = true;
backLoading.value = true;
backButtonDisabled.value = true;
const data = await getBackTaskNode(task.value.definitionId, task.value.nodeCode);
const data = await getBackTaskNode(task.value.id, task.value.nodeCode);
taskNodeList.value = data.data;
backLoading.value = false;
backButtonDisabled.value = false;
@@ -361,14 +379,14 @@ const openUserSelectCopy = () => {
userSelectCopyRef.value.open();
};
//确认抄送人员
const userSelectCopyCallBack = (data: UserVO[]) => {
const userSelectCopyCallBack = (data: FlowCopyVo[]) => {
if (data && data.length > 0) {
selectCopyUserList.value = data;
selectCopyUserIds.value = selectCopyUserList.value.map((item) => item.userId).join(',');
}
};
//删除抄送人员
const handleCopyCloseTag = (user: UserVO) => {
const handleCopyCloseTag = (user: FlowCopyVo) => {
const userId = user.userId;
// 使用split删除用户
const index = selectCopyUserList.value.findIndex((item) => item.userId === userId);
@@ -385,7 +403,8 @@ const addMultiInstanceUser = async (data) => {
const taskOperationBo = reactive<TaskOperationBo>({
userIds: data.map((e) => e.userId),
taskId: taskId.value,
message: form.value.message
message: form.value.message,
messageType: ['1']
});
await proxy?.$modal.confirm('是否确认提交?');
loading.value = true;
@@ -409,7 +428,8 @@ const deleteMultiInstanceUser = async (row) => {
const taskOperationBo = reactive<TaskOperationBo>({
userIds: [row.userId],
taskId: taskId.value,
message: form.value.message
message: form.value.message,
messageType: ['1']
});
await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
loading.value = false;
@@ -429,7 +449,8 @@ const handleTransferTask = async (data) => {
const taskOperationBo = reactive<TaskOperationBo>({
userId: data[0].userId,
taskId: taskId.value,
message: form.value.message
message: form.value.message,
messageType: ['1']
});
await proxy?.$modal.confirm('是否确认提交?');
loading.value = true;
@@ -456,7 +477,8 @@ const handleDelegateTask = async (data) => {
const taskOperationBo = reactive<TaskOperationBo>({
userId: data[0].userId,
taskId: taskId.value,
message: form.value.message
message: form.value.message,
messageType: ['1']
});
await proxy?.$modal.confirm('是否确认提交?');
loading.value = true;

View File

@@ -168,12 +168,15 @@ const confirm = () => {
};
const computedIds = (data) => {
if (data === '' || data === null || data === undefined) {
return [];
}
if (data instanceof Array) {
return data.map((item) => String(item));
} else if (typeof data === 'string') {
return data.split(',');
} else if (typeof data === 'number') {
return [data];
return [String(data)];
} else {
console.warn('<UserSelect> The data type of data should be array or string or number, but I received other');
return [];

17
src/enums/NavTypeEnum.ts Normal file
View File

@@ -0,0 +1,17 @@
/**
* 导航栏布局枚举
*/
export enum NavTypeEnum {
/**
* 左侧导航
*/
LEFT = 'left',
/**
* 顶部导航
*/
TOP = 'top',
/**
* 混合导航
*/
MIX = 'mix'
}

View File

@@ -89,11 +89,11 @@ function addIframe() {
}
::-webkit-scrollbar-track {
background-color: #f1f1f1;
background-color: var(--el-fill-color-lighter);
}
::-webkit-scrollbar-thumb {
background-color: #c0c0c0;
border-radius: 3px;
background-color: var(--el-text-color-placeholder);
border-radius: 999px;
}
</style>

View File

@@ -1,9 +1,14 @@
<template>
<div class="navbar">
<div class="navbar" :class="'nav' + navType">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
<breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
<breadcrumb v-if="navType == NavTypeEnum.LEFT" id="breadcrumb-container" class="breadcrumb-container" />
<top-nav v-if="navType == NavTypeEnum.MIX" id="topmenu-container" class="topmenu-container" />
<template v-if="navType == NavTypeEnum.TOP">
<logo v-show="showLogo" :collapse="false"></logo>
<top-bar id="topbar-container" class="topbar-container" />
</template>
<div class="right-menu flex align-center">
<template v-if="appStore.device !== 'mobile'">
<el-select
@@ -99,6 +104,9 @@ import { TenantVO } from '@/api/types';
import notice from './notice/index.vue';
import router from '@/router';
import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
import Logo from "@/layout/components/Sidebar/Logo.vue";
import TopBar from './TopBar'
const appStore = useAppStore();
const userStore = useUserStore();
@@ -109,6 +117,9 @@ const newNotice = ref(<number>0);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userId = ref(userStore.userId);
const navType = computed(() => settingsStore.navType);
const showLogo = computed(() => settingsStore.sidebarLogo);
const companyName = ref(undefined);
const tenantList = ref<TenantVO[]>([]);
// 是否切换了租户
@@ -201,6 +212,12 @@ watch(
</script>
<style lang="scss" scoped>
.navbar.navtop {
.hamburger-container {
display: none !important;
}
}
:deep(.el-select .el-input__wrapper) {
height: 30px;
}
@@ -221,24 +238,34 @@ watch(
height: 50px;
overflow: hidden;
position: relative;
//background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
background: var(--el-bg-color);
border-bottom: 1px solid var(--el-border-color-lighter);
box-shadow: none;
display: flex;
align-items: center;
// padding: 0 8px;
box-sizing: border-box;
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
//float: left;
cursor: pointer;
transition: background 0.3s;
-webkit-tap-highlight-color: transparent;
display: flex;
align-items: center;
flex-shrink: 0;
margin-right: 8px;
&:hover {
background: rgba(0, 0, 0, 0.025);
background: var(--el-fill-color-lighter);
}
}
.breadcrumb-container {
float: left;
//float: left;
flex-shrink: 0;
}
.topmenu-container {
@@ -246,16 +273,28 @@ watch(
left: 50px;
}
.topbar-container {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
overflow: hidden;
margin-left: 8px;
}
.errLog-container {
display: inline-block;
vertical-align: top;
}
.right-menu {
float: right;
//float: right;
height: 100%;
line-height: 50px;
display: flex;
align-items: center;
margin-left: auto;
&:focus {
outline: none;
@@ -266,7 +305,7 @@ watch(
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
color: var(--el-text-color-regular);
vertical-align: text-bottom;
&.hover-effect {
@@ -274,7 +313,7 @@ watch(
transition: background 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.025);
background: var(--el-fill-color-lighter);
}
}
}
@@ -290,7 +329,7 @@ watch(
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
border-radius: var(--app-radius-md);
margin-top: 10px;
}

View File

@@ -1,5 +1,40 @@
<template>
<el-drawer v-model="showSettings" :with-header="false" direction="rtl" size="300px" close-on-click-modal>
<h3 class="drawer-title">菜单导航设置</h3>
<div class="nav-wrap">
<el-tooltip content="左侧菜单" placement="bottom">
<div
class="item left"
@click="handleNavType(NavTypeEnum.LEFT)"
:style="{ '--theme': theme }"
:class="{ activeItem: navType == NavTypeEnum.LEFT }"
>
<b></b><b></b>
</div>
</el-tooltip>
<el-tooltip content="混合菜单" placement="bottom">
<div
class="item mix"
@click="handleNavType(NavTypeEnum.MIX)"
:style="{ '--theme': theme }"
:class="{ activeItem: navType == NavTypeEnum.MIX }"
>
<b></b><b></b>
</div>
</el-tooltip>
<el-tooltip content="顶部菜单" placement="bottom">
<div
class="item top"
@click="handleNavType(NavTypeEnum.TOP)"
:style="{ '--theme': theme }"
:class="{ activeItem: navType == NavTypeEnum.TOP }"
>
<b></b><b></b>
</div>
</el-tooltip>
</div>
<h3 class="drawer-title">主题风格设置</h3>
<div class="setting-drawer-block-checbox">
@@ -40,18 +75,17 @@
<el-switch v-model="isDark" class="drawer-switch" @change="toggleDark" />
</span>
</div>
<div class="drawer-item">
<span>页面圆角</span>
<span class="comp-style">
<el-slider v-model="radiusBase" :min="0" :max="32" :step="2" style="width: 120px" @change="radiusBaseChange" />
</span>
</div>
<el-divider />
<h3 class="drawer-title">系统布局配置</h3>
<div class="drawer-item">
<span>开启 TopNav</span>
<span class="comp-style">
<el-switch v-model="settingsStore.topNav" class="drawer-switch" @change="topNavChange" />
</span>
</div>
<div class="drawer-item">
<span>开启 Tags-Views</span>
<span class="comp-style">
@@ -101,6 +135,7 @@ import { useSettingsStore } from '@/store/modules/settings';
import { usePermissionStore } from '@/store/modules/permission';
import { handleThemeStyle } from '@/utils/theme';
import { SideThemeEnum } from '@/enums/SideThemeEnum';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
import defaultSettings from '@/settings';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -113,7 +148,8 @@ const theme = ref(settingsStore.theme);
const sideTheme = ref(settingsStore.sideTheme);
const storeSettings = computed(() => settingsStore);
const predefineColors = ref(['#409EFF', '#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585']);
const navType = ref(settingsStore.navType);
const radiusBase = ref(settingsStore.radiusBase);
// 是否暗黑模式
const isDark = useDark({
storageKey: 'useDarkKey',
@@ -130,11 +166,26 @@ watch(isDark, () => {
});
const toggleDark = () => useToggle(isDark);
const topNavChange = (val: any) => {
if (!val) {
appStore.toggleSideBarHide(false);
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
}
/** 菜单导航设置 */
watch(
() => navType,
(val: string) => {
if (val.value === NavTypeEnum.TOP) {
appStore.toggleSideBarHide(true);
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
} else if (val.value === NavTypeEnum.LEFT) {
appStore.toggleSideBarHide(false);
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
} else if (val.value === NavTypeEnum.MIX) {
appStore.toggleSideBarHide(false);
}
},
{ immediate: true, deep: true }
);
const handleNavType = (val: NavTypeEnum) => {
settingsStore.navType = val;
navType.value = val;
};
const dynamicTitleChange = () => {
@@ -146,6 +197,10 @@ const themeChange = (val: string) => {
settingsStore.theme = val;
handleThemeStyle(val);
};
const radiusBaseChange = (val: number) => {
settingsStore.radiusBase = val;
document.documentElement.style.setProperty('--app-radius-base', `${val}px`);
};
const handleTheme = (val: string) => {
sideTheme.value = val;
if (isDark.value && val === SideThemeEnum.LIGHT) {
@@ -158,7 +213,6 @@ const handleTheme = (val: string) => {
const saveSetting = () => {
proxy?.$modal.loading('正在保存到本地,请稍候...');
const settings = useStorage<LayoutSetting>('layout-setting', defaultSettings);
settings.value.topNav = storeSettings.value.topNav;
settings.value.tagsView = storeSettings.value.tagsView;
settings.value.tagsIcon = storeSettings.value.tagsIcon;
settings.value.fixedHeader = storeSettings.value.fixedHeader;
@@ -166,6 +220,8 @@ const saveSetting = () => {
settings.value.dynamicTitle = storeSettings.value.dynamicTitle;
settings.value.sideTheme = storeSettings.value.sideTheme;
settings.value.theme = storeSettings.value.theme;
settings.value.navType = storeSettings.value.navType;
settings.value.radiusBase = storeSettings.value.radiusBase;
setTimeout(() => {
proxy?.$modal.closeLoading();
}, 1000);
@@ -179,6 +235,10 @@ const openSetting = () => {
showSettings.value = true;
};
onMounted(() => {
radiusBaseChange(storeSettings.value.radiusBase);
});
defineExpose({
openSetting
});
@@ -243,4 +303,67 @@ defineExpose({
margin: -3px 8px 0px 0px;
}
}
// 导航模式
.nav-wrap {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 10px;
margin-bottom: 20px;
.activeItem {
border: 2px solid #{'var(--theme)'} !important;
}
.item {
position: relative;
margin-right: 16px;
cursor: pointer;
width: 56px;
height: 48px;
border-radius: 4px;
background: #f0f2f5;
border: 2px solid transparent;
}
.left {
b:first-child {
display: block;
height: 30%;
background: #fff;
}
b:last-child {
width: 30%;
background: #1b2a47;
position: absolute;
height: 100%;
top: 0;
border-radius: 4px 0 0 4px;
}
}
.mix {
b:first-child {
border-radius: 4px 4px 0 0;
display: block;
height: 30%;
background: #1b2a47;
}
b:last-child {
width: 30%;
background: #1b2a47;
position: absolute;
height: 70%;
border-radius: 0 0 0 4px;
}
}
.top {
b:first-child {
display: block;
height: 30%;
background: #1b2a47;
border-radius: 4px 4px 0 0;
}
}
}
</style>

View File

@@ -2,18 +2,17 @@
<div
class="sidebar-logo-container"
:class="{ collapse: collapse }"
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
>
<transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
<h1 v-else class="sidebar-title">
{{ title }}
</h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
<h1 class="sidebar-title">
{{ title }}
</h1>
</router-link>
@@ -26,6 +25,7 @@ import variables from '@/assets/styles/variables.module.scss';
import logo from '@/assets/logo/logo.png';
import { useSettingsStore } from '@/store/modules/settings';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { NavTypeEnum } from '@/enums/NavTypeEnum';
defineProps({
collapse: {
@@ -34,9 +34,31 @@ defineProps({
}
});
const title = ref('RuoYi-Vue-Plus');
const title = import.meta.env.VITE_APP_LOGO_TITLE;
const settingsStore = useSettingsStore();
const sideTheme = computed(() => settingsStore.sideTheme);
// 获取Logo背景色
const getLogoBackground = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-bg)'
}
if (settingsStore.navType == NavTypeEnum.TOP) {
return variables.menuLightBackground
}
return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBackground
})
// 获取Logo文字颜色
const getLogoTextColor = computed(() => {
if (settingsStore.isDark) {
return 'var(--sidebar-text)'
}
if (settingsStore.navType == NavTypeEnum.TOP) {
return variables.logoLightTitleColor
}
return sideTheme.value === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor
})
</script>
<style lang="scss" scoped>
@@ -51,10 +73,9 @@ const sideTheme = computed(() => settingsStore.sideTheme);
.sidebar-logo-container {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
background: #2b2f3a;
background: v-bind(getLogoBackground);
text-align: center;
overflow: hidden;
@@ -67,21 +88,17 @@ const sideTheme = computed(() => settingsStore.sideTheme);
height: 32px;
vertical-align: middle;
margin-right: 12px;
margin-left: 12px;
}
& .sidebar-title {
display: inline-block;
margin: 0;
color: #fff;
color: v-bind(getLogoTextColor);
font-weight: 600;
line-height: 50px;
font-size: 14px;
font-family:
Avenir,
Helvetica Neue,
Arial,
Helvetica,
sans-serif;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
}

View File

@@ -11,6 +11,7 @@
:unique-opened="true"
:active-text-color="theme"
:collapse-transition="false"
:popper-offset="12"
mode="vertical"
>
<sidebar-item v-for="(r, index) in sidebarRouters" :key="r.path + index" :item="r" :base-path="r.path" />

View File

@@ -13,7 +13,7 @@
@contextmenu.prevent="openMenu(tag, $event)"
>
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon"/>
{{ tag.title }}
<span class="tags-view-item-title">{{ tag.title }}</span>
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
<close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
</span>
@@ -244,16 +244,15 @@ onMounted(() => {
width: 100%;
background-color: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
box-shadow:
0 1px 3px 0 rgba(0, 0, 0, 0.12),
0 0 3px 0 rgba(0, 0, 0, 0.04);
border-radius: var(--app-radius-md);
box-shadow: var(--app-shadow-sm);
.tags-view-wrapper {
.tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 26px;
line-height: 23px;
line-height: 25px;
background-color: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
color: #495060;
@@ -261,8 +260,13 @@ onMounted(() => {
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
border-radius: var(--app-radius-md);
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease, color 0.2s ease;
&:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary-light-5);
box-shadow: var(--app-shadow-sm);
transform: translateY(-1px);
}
&:first-of-type {
margin-left: 15px;
@@ -290,6 +294,10 @@ onMounted(() => {
.tags-view-item.active.has-icon::before {
content: none !important;
}
.tags-view-item-title {
margin-left: 4px;
margin-right: 3px;
}
.contextmenu {
margin: 0;
background: var(--el-bg-color);
@@ -297,16 +305,16 @@ onMounted(() => {
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
border-radius: var(--app-radius-md);
font-size: 12px;
font-weight: 400;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
box-shadow: var(--app-shadow-md);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
background: var(--el-fill-color-light);
}
}
}

View File

@@ -0,0 +1,103 @@
<template>
<el-menu class="topbar-menu" :ellipsis="false" :default-active="activeMenu" :active-text-color="theme" mode="horizontal">
<sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" />
<el-sub-menu index="more" class="el-sub-menu__hide-arrow" v-if="moreRoutes.length > 0">
<template #title>
<span>更多菜单</span>
</template>
<sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" />
</el-sub-menu>
</el-menu>
</template>
<script setup>
import SidebarItem from '../Sidebar/SidebarItem'
import {useAppStore} from '@/store/modules/app'
import {useSettingsStore} from '@/store/modules/settings'
import {usePermissionStore} from '@/store/modules/permission'
const route = useRoute()
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const sidebarRouters = computed(() => permissionStore.sidebarRouters)
const theme = computed(() => settingsStore.theme)
const device = computed(() => appStore.device)
const activeMenu = computed(() => {
const { meta, path } = route
if (meta.activeMenu) {
return meta.activeMenu
}
return path
})
const visibleNumber = ref(5)
const topMenus = computed(() => {
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value)
})
const moreRoutes = computed(() => {
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(visibleNumber.value, sidebarRouters.value.length - visibleNumber.value)
})
function setVisibleNumber() {
let width = document.body.getBoundingClientRect().width
if (width >= 1000) {
width -= 500
}
visibleNumber.value = parseInt(width / 3 / 85)
}
onMounted(() => {
window.addEventListener('resize', setVisibleNumber)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', setVisibleNumber)
})
onMounted(() => {
setVisibleNumber()
})
</script>
<style lang="scss">
/* menu item */
#app .topbar-menu.el-menu--horizontal .el-sub-menu__title, #app .topbar-menu.el-menu--horizontal .el-menu-item {
padding: 0 10px !important;
}
.topbar-menu.el-menu--horizontal > .el-menu-item {
float: left;
height: 50px !important;
line-height: 50px !important;
color: #303133 !important;
padding: 0 5px !important;
margin: 0 10px !important;
}
.el-sub-menu.is-active .svg-icon, .el-menu-item.is-active .svg-icon + span, .el-sub-menu.is-active .svg-icon + span, .el-sub-menu.is-active .el-sub-menu__title span {
color: v-bind(theme);
}
/* sub-menu item */
.topbar-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
float: left;
line-height: 50px !important;
color: #303133 !important;
margin: 0 15px -3px!important;
}
/* topbar more arrow */
.topbar-menu .el-sub-menu .el-sub-menu__icon-arrow {
position: static;
margin-left: 8px;
margin-top: 0px;
display: block !important;
}
/* menu__title el-menu-item */
.topbar-menu.el-menu--horizontal .el-sub-menu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
height: 60px;
}
</style>

View File

@@ -1,10 +1,11 @@
<template>
<div class="layout-search-dialog">
<el-dialog v-model="state.isShowSearch" destroy-on-close :show-close="false">
<template #footer>
<el-dialog v-model="state.isShowSearch" destroy-on-close :show-close="false" width="600px" top="12vh">
<div class="layout-search-dialog__content">
<el-autocomplete
ref="layoutMenuAutocompleteRef"
v-model="state.menuQuery"
class="layout-search-dialog__autocomplete"
:fetch-suggestions="menuSearch"
placeholder="搜索"
:fit-input-width="true"
@@ -20,7 +21,7 @@
</div>
</template>
</el-autocomplete>
</template>
</div>
</el-dialog>
</div>
</template>
@@ -132,27 +133,29 @@ defineExpose({
<style lang="scss" scoped>
.layout-search-dialog {
position: relative;
:deep(.el-dialog) {
padding: 0;
border-radius: var(--app-radius-base);
overflow: visible;
.el-dialog__header,
.el-dialog__body {
.el-dialog__footer {
display: none;
}
.el-dialog__footer {
width: 100%;
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -53vh;
.el-dialog__body {
padding: 20px;
}
}
:deep(.el-autocomplete) {
width: 560px;
position: absolute;
top: 150px;
left: 50%;
transform: translateX(-50%);
&__content {
width: 100%;
}
&__autocomplete {
width: 100%;
}
:deep(.el-input__wrapper) {
min-height: 44px;
border-radius: var(--app-radius-md);
}
}
</style>

View File

@@ -26,6 +26,7 @@ import SideBar from './components/Sidebar/index.vue';
import { AppMain, Navbar, Settings, TagsView } from './components';
import { useAppStore } from '@/store/modules/app';
import { useSettingsStore } from '@/store/modules/settings';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
import { initWebSocket } from '@/utils/websocket';
import { initSSE } from '@/utils/sse';
@@ -35,6 +36,13 @@ const sidebar = computed(() => useAppStore().sidebar);
const device = computed(() => useAppStore().device);
const needTagsView = computed(() => settingsStore.tagsView);
const fixedHeader = computed(() => settingsStore.fixedHeader);
const layout = computed(() => settingsStore.navType);
// 根据布局模式判断是否显示侧边栏
const showSidebar = computed(() => {
if (sidebar.value.hide) return false;
return layout.value === NavTypeEnum.LEFT || layout.value === NavTypeEnum.MIX;
});
const classObj = computed(() => ({
hideSidebar: !sidebar.value.opened,

View File

@@ -1,8 +1,8 @@
import { createApp } from 'vue';
// global css
import 'virtual:uno.css';
import '@/assets/styles/index.scss';
import 'element-plus/theme-chalk/dark/css-vars.css';
import '@/assets/styles/index.scss';
// App、router、store
import App from './App.vue';

View File

@@ -1,4 +1,5 @@
import { LanguageEnum } from '@/enums/LanguageEnum';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
const setting: DefaultSettings = {
/**
@@ -18,9 +19,9 @@ const setting: DefaultSettings = {
showSettings: true,
/**
* 是否显示顶部导航
* 默认布局
*/
topNav: false,
navType: NavTypeEnum.LEFT,
/**
* 是否显示 tagsView
@@ -35,7 +36,7 @@ const setting: DefaultSettings = {
/**
* 是否固定头部
*/
fixedHeader: false,
fixedHeader: true,
/**
* 是否显示logo
@@ -70,6 +71,11 @@ const setting: DefaultSettings = {
/**
* 默认布局
*/
layout: ''
layout: '',
/**
* 页面圆角大小
*/
radiusBase: 14
};
export default setting;

View File

@@ -3,6 +3,7 @@ import defaultSettings from '@/settings';
import { useDynamicTitle } from '@/utils/dynamicTitle';
import { useStorage } from '@vueuse/core';
import { ref } from 'vue';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
export const useSettingsStore = defineStore('setting', () => {
const storageSetting = useStorage<LayoutSetting>('layout-setting', {
@@ -13,13 +14,14 @@ export const useSettingsStore = defineStore('setting', () => {
sidebarLogo: defaultSettings.sidebarLogo,
dynamicTitle: defaultSettings.dynamicTitle,
sideTheme: defaultSettings.sideTheme,
theme: defaultSettings.theme
theme: defaultSettings.theme,
navType: defaultSettings.navType,
radiusBase: defaultSettings.radiusBase
});
const title = ref<string>(defaultSettings.title);
const theme = ref<string>(storageSetting.value.theme);
const sideTheme = ref<string>(storageSetting.value.sideTheme);
const showSettings = ref<boolean>(defaultSettings.showSettings);
const topNav = ref<boolean>(storageSetting.value.topNav);
const tagsView = ref<boolean>(storageSetting.value.tagsView);
const tagsIcon = ref<boolean>(storageSetting.value.tagsIcon);
const fixedHeader = ref<boolean>(storageSetting.value.fixedHeader);
@@ -27,6 +29,8 @@ export const useSettingsStore = defineStore('setting', () => {
const dynamicTitle = ref<boolean>(storageSetting.value.dynamicTitle);
const animationEnable = ref<boolean>(defaultSettings.animationEnable);
const dark = ref<boolean>(defaultSettings.dark);
const navType = ref<NavTypeEnum>(storageSetting.value.navType || NavTypeEnum.LEFT);
const radiusBase = ref<number>(storageSetting.value.radiusBase ?? defaultSettings.radiusBase);
const setTitle = (value: string) => {
title.value = value;
@@ -37,7 +41,6 @@ export const useSettingsStore = defineStore('setting', () => {
theme,
sideTheme,
showSettings,
topNav,
tagsView,
tagsIcon,
fixedHeader,
@@ -45,6 +48,8 @@ export const useSettingsStore = defineStore('setting', () => {
dynamicTitle,
animationEnable,
dark,
navType,
radiusBase,
setTitle
};
});

View File

@@ -1,5 +1,6 @@
import type { PropType as VuePropType, ComponentInternalInstance as ComponentInstance } from 'vue';
import { LanguageEnum } from '@/enums/LanguageEnum';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
declare global {
/** vue Instance */
@@ -90,9 +91,9 @@ declare global {
}
declare interface LayoutSetting {
/**
* 是否显示顶部导航
* 默认布局
*/
topNav: boolean;
navType: NavTypeEnum;
/**
* 是否显示多标签导航
@@ -122,6 +123,10 @@ declare global {
* 主题模式
*/
theme: string;
/**
* 页面圆角大小
*/
radiusBase: number;
}
declare interface DefaultSettings extends LayoutSetting {

View File

@@ -12,7 +12,7 @@ import type { LanguageType } from '@/lang';
export {};
declare module '@vue/runtime-core' {
declare module 'vue' {
interface ComponentCustomProperties {
// 全局方法声明
$modal: typeof modal;

View File

@@ -1,4 +1,4 @@
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min.js';
import JSEncrypt from 'jsencrypt';
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = import.meta.env.VITE_APP_RSA_PUBLIC_KEY;

View File

@@ -28,7 +28,11 @@ axios.defaults.headers['clientid'] = import.meta.env.VITE_APP_CLIENT_ID;
// 创建 axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 50000
timeout: 50000,
transitional: {
// 超时错误更明确
clarifyTimeoutError: true
}
});
// 请求拦截器

View File

@@ -11,10 +11,10 @@ export const initSSE = (url: any) => {
url = url + '?Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
const { data, error } = useEventSource(url, [], {
autoReconnect: {
retries: 10,
delay: 3000,
retries: 5,
delay: 5000,
onFailed() {
console.log('Failed to connect after 10 retries');
console.log('Failed to connect after 5 retries');
}
}
});

View File

@@ -33,7 +33,7 @@
* 部署方式 Docker 容器编排 一键部署业务集群<br />
* 国际化 SpringMessage Spring标准国际化方案<br />
</p>
<p><b>当前版本:</b> <span>v5.4.1</span></p>
<p><b>当前版本:</b> <span>v5.6.0</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>
@@ -77,7 +77,7 @@
* 分布式监控 PrometheusGrafana 全方位性能监控<br />
* 其余与 Vue 版本一致<br />
</p>
<p><b>当前版本:</b> <span>v2.4.1</span></p>
<p><b>当前版本:</b> <span>v2.6.0</span></p>
<p>
<el-tag type="danger">&yen;免费开源</el-tag>
</p>

View File

@@ -73,14 +73,14 @@
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
</div>
</div>
</template>
<script setup lang="ts">
import { getCodeImg, getTenantList } from '@/api/login';
import { authBinding } from '@/api/system/social/auth';
import { authRouterUrl } from '@/api/system/social/auth';
import { useUserStore } from '@/store/modules/user';
import { LoginData, TenantVO } from '@/api/types';
import { to } from 'await-to-js';
@@ -176,6 +176,8 @@ const getCode = async () => {
const { data } = res;
captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
if (captchaEnabled.value) {
// 刷新验证码时清空输入框
loginForm.value.code = '';
codeUrl.value = 'data:image/gif;base64,' + data.img;
loginForm.value.uuid = data.uuid;
}
@@ -213,7 +215,7 @@ const initTenantList = async () => {
* @param type
*/
const doSocialLogin = (type: string) => {
authBinding(type, loginForm.value.tenantId).then((res: any) => {
authRouterUrl(type, loginForm.value.tenantId).then((res: any) => {
if (res.code === HttpStatus.SUCCESS) {
// 获取授权地址跳转
window.location.href = res.data;
@@ -238,29 +240,38 @@ onMounted(() => {
height: 100%;
background-image: url('../assets/images/login-background.jpg');
background-size: cover;
background-position: center;
}
.title-box {
display: flex;
align-items: center;
gap: 8px;
.title {
margin: 0px auto 30px auto;
margin: 0px auto 26px auto;
text-align: center;
color: #707070;
color: var(--el-text-color-primary);
font-weight: 600;
letter-spacing: 0.5px;
}
:deep(.lang-select--style) {
line-height: 0;
color: #7483a3;
color: var(--el-text-color-secondary);
}
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
border-radius: var(--app-radius-lg);
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(255, 255, 255, 0.5);
width: min(420px, 90vw);
padding: 32px 30px 12px 30px;
z-index: 1;
box-shadow: var(--app-shadow-lg);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
.el-input {
height: 40px;
input {
@@ -281,14 +292,48 @@ onMounted(() => {
color: #bfbfbf;
}
.login-form :deep(.el-input__wrapper) {
background-color: rgba(255, 255, 255, 0.9);
}
.login-form :deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
.login-form :deep(.el-button--primary) {
border-radius: var(--app-radius-md);
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25);
}
.login-form :deep(.el-button.is-circle) {
background: rgba(15, 23, 42, 0.04);
border: 1px solid rgba(15, 23, 42, 0.08);
color: var(--el-text-color-regular);
}
.login-form :deep(.el-button.is-circle:hover) {
background: rgba(59, 130, 246, 0.1);
border-color: rgba(59, 130, 246, 0.2);
}
.login-code {
width: 33%;
width: calc(37% - 10px);
height: 40px;
float: right;
margin-left: 10px;
box-sizing: border-box;
border-radius: var(--app-radius-sm);
overflow: hidden;
background: rgba(255, 255, 255, 0.9);
border: 1px solid var(--el-border-color-light);
img {
cursor: pointer;
vertical-align: middle;
display: block;
width: 100%;
height: 40px;
object-fit: cover;
}
}
@@ -299,7 +344,7 @@ onMounted(() => {
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
color: rgba(255, 255, 255, 0.75);
font-family: Arial, serif;
font-size: 12px;
letter-spacing: 1px;
@@ -307,6 +352,27 @@ onMounted(() => {
.login-code-img {
height: 40px;
padding-left: 12px;
padding-left: 0;
}
:global(html.dark) {
.login-form {
background: rgba(17, 24, 39, 0.9);
border-color: rgba(148, 163, 184, 0.2);
}
.login-form :deep(.el-input__wrapper) {
background-color: rgba(17, 24, 39, 0.7);
}
.login-form :deep(.el-button.is-circle) {
background: rgba(148, 163, 184, 0.12);
border-color: rgba(148, 163, 184, 0.25);
color: #e5e7eb;
}
.el-login-footer {
color: rgba(226, 232, 240, 0.65);
}
}
</style>

View File

@@ -67,7 +67,7 @@
</el-form>
<!-- 底部 -->
<div class="el-register-footer">
<span>Copyright © 2018-2025 疯狂的狮子Li All Rights Reserved.</span>
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
</div>
</div>
</template>

View File

@@ -197,7 +197,7 @@ const initData: PageData<DeptForm, DeptQuery> = {
deptName: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
phone: [{ pattern: /^1[3456789][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
}
};
const data = reactive<PageData<DeptForm, DeptQuery>>(initData);

View File

@@ -1,309 +0,0 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="字典名称" prop="dictType">
<el-select v-model="queryParams.dictType">
<el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
</el-select>
</el-form-item>
<el-form-item label="字典标签" prop="dictLabel">
<el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="dataList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
<el-table-column label="字典标签" align="center" prop="dictLabel">
<template #default="scope">
<span
v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)"
>{{ scope.row.dictLabel }}</span
>
<el-tag
v-else
:type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass"
:class="scope.row.cssClass"
>{{ scope.row.dictLabel }}</el-tag
>
</template>
</el-table-column>
<el-table-column label="字典键值" align="center" prop="dictValue" />
<el-table-column label="字典排序" align="center" prop="dictSort" />
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改参数配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="dataFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="字典类型">
<el-input v-model="form.dictType" :disabled="true" />
</el-form-item>
<el-form-item label="数据标签" prop="dictLabel">
<el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
</el-form-item>
<el-form-item label="数据键值" prop="dictValue">
<el-input v-model="form.dictValue" placeholder="请输入数据键值" />
</el-form-item>
<el-form-item label="样式属性" prop="cssClass">
<el-input v-model="form.cssClass" placeholder="请输入样式属性" />
</el-form-item>
<el-form-item label="显示排序" prop="dictSort">
<el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="回显样式" prop="listClass">
<el-select v-model="form.listClass">
<el-option
v-for="item in listClassOptions"
:key="item.value"
:label="item.label + '(' + item.value + ')'"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Data" lang="ts">
import { useDictStore } from '@/store/modules/dict';
import { optionselect as getDictOptionselect, getType } from '@/api/system/dict/type';
import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
import { DictTypeVO } from '@/api/system/dict/type/types';
import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
import { RouteLocationNormalized } from 'vue-router';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const dataList = ref<DictDataVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const defaultDictType = ref('');
const typeOptions = ref<DictTypeVO[]>([]);
const dataFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 数据标签回显样式
const listClassOptions = ref<Array<{ value: string; label: string }>>([
{ value: 'default', label: '默认' },
{ value: 'primary', label: '主要' },
{ value: 'success', label: '成功' },
{ value: 'info', label: '信息' },
{ value: 'warning', label: '警告' },
{ value: 'danger', label: '危险' }
]);
const initFormData: DictDataForm = {
dictCode: undefined,
dictLabel: '',
dictValue: '',
cssClass: '',
listClass: 'primary',
dictSort: 0,
remark: ''
};
const data = reactive<PageData<DictDataForm, DictDataQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
dictName: '',
dictType: '',
dictLabel: ''
},
rules: {
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询字典类型详细 */
const getTypes = async (dictId: string | number) => {
const { data } = await getType(dictId);
queryParams.value.dictType = data.dictType;
defaultDictType.value = data.dictType;
getList();
};
/** 查询字典类型列表 */
const getTypeList = async () => {
const res = await getDictOptionselect();
typeOptions.value = res.data;
};
/** 查询字典数据列表 */
const getList = async () => {
loading.value = true;
const res = await listData(queryParams.value);
dataList.value = res.rows;
total.value = res.total;
loading.value = false;
};
/** 取消按钮 */
const cancel = () => {
dialog.visible = false;
reset();
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
dataFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 返回按钮操作 */
const handleClose = () => {
const obj: RouteLocationNormalized = {
fullPath: '',
hash: '',
matched: [],
meta: undefined,
name: undefined,
params: undefined,
query: undefined,
redirectedFrom: undefined,
path: '/system/dict'
};
proxy?.$tab.closeOpenPage(obj);
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.dictType = defaultDictType.value;
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
form.value.dictType = queryParams.value.dictType;
dialog.visible = true;
dialog.title = '添加字典数据';
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DictDataVO[]) => {
ids.value = selection.map((item) => item.dictCode);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DictDataVO) => {
reset();
const dictCode = row?.dictCode || ids.value[0];
const res = await getData(dictCode);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改字典数据';
};
/** 提交按钮 */
const submitForm = () => {
dataFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.dictCode ? await updateData(form.value) : await addData(form.value);
useDictStore().removeDict(queryParams.value.dictType);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DictDataVO) => {
const dictCodes = row?.dictCode || ids.value;
await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
await delData(dictCodes);
await getList();
proxy?.$modal.msgSuccess('删除成功');
useDictStore().removeDict(queryParams.value.dictType);
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'system/dict/data/export',
{
...queryParams.value
},
`dict_data_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getTypes(route.params && (route.params.dictId as string));
getTypeList();
});
</script>

View File

@@ -1,106 +1,274 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="字典类型" prop="dictType">
<el-input v-model="queryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
</el-col>
<el-col :span="1.5">
<template>
<div class="p-2 dict-page">
<el-row :gutter="16" class="dict-grid">
<!-- 字典类型 -->
<el-col :xs="24" :lg="12">
<el-card shadow="hover" class="dict-card">
<template #header>
<div class="dict-card__header">
<div class="dict-card__title">字典管理</div>
<right-toolbar v-model:show-search="showTypeSearch" @query-table="getTypeList" />
</div>
</template>
<div v-show="showTypeSearch" class="dict-form-scroll">
<el-form ref="typeQueryFormRef" :model="typeQueryParams" :inline="true">
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="typeQueryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleTypeQuery" />
</el-form-item>
<el-form-item label="字典类型" prop="dictType">
<el-input v-model="typeQueryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleTypeQuery" />
</el-form-item>
<el-form-item label="创建时间" style="width: 308px">
<el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleTypeQuery">搜索</el-button>
<el-button icon="Refresh" @click="handleTypeResetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="dict-actions">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleTypeAdd">新增</el-button>
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="typeSingle" @click="handleTypeUpdate()"
>修改</el-button
>
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="typeMultiple" @click="handleTypeDelete()"
>删除</el-button
>
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleTypeExport">导出</el-button>
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
</div>
<el-table v-loading="loading" border :data="typeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
<el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
<el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
<template #default="scope">
<router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
<span>{{ scope.row.dictType }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div class="dict-table-wrap">
<el-table
ref="typeTableRef"
v-loading="typeLoading"
border
:data="typeList"
highlight-current-row
@row-click="handleTypeRowClick"
@selection-change="handleTypeSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
<el-table-column label="字典名称" align="center" prop="dictName" width="120" />
<el-table-column label="字典类型" align="center" prop="dictType" width="160">
<template #default="scope">
<span class="link-type" @click.stop="handleTypeRowClick(scope.row)">{{ scope.row.dictType }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" width="160"/>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" align="center" width="120" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleTypeUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleTypeDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
</el-card>
<!-- 添加或修改参数配置对话框 -->
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
<el-form ref="dictFormRef" :model="form" :rules="rules" label-width="80px">
<pagination
v-show="typeTotal > 0"
v-model:page="typeQueryParams.pageNum"
v-model:limit="typeQueryParams.pageSize"
:total="typeTotal"
@pagination="getTypeList"
/>
</el-card>
</el-col>
<!-- 字典数据 -->
<el-col :xs="24" :lg="12">
<el-card shadow="hover" class="dict-card">
<template #header>
<div class="dict-card__header">
<div class="dict-card__title">
字典数据
<span class="dict-card__subtitle">{{ currentDictLabel }}</span>
</div>
<right-toolbar v-model:show-search="showDataSearch" @query-table="getDataList" />
</div>
</template>
<div v-show="showDataSearch" class="dict-form-scroll">
<el-form ref="dataQueryFormRef" :model="dataQueryParams" :inline="true">
<el-form-item label="字典标签" prop="dictLabel">
<el-input
v-model="dataQueryParams.dictLabel"
placeholder="请输入字典标签"
clearable
:disabled="!hasCurrentDict"
@keyup.enter="handleDataQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" :disabled="!hasCurrentDict" @click="handleDataQuery">搜索</el-button>
<el-button icon="Refresh" :disabled="!hasCurrentDict" @click="handleDataResetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="dict-actions">
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" :disabled="!hasCurrentDict" @click="handleDataAdd"
>新增</el-button
>
<el-button
v-hasPermi="['system:dict:edit']"
type="success"
plain
icon="Edit"
:disabled="dataSingle || !hasCurrentDict"
@click="handleDataUpdate()"
>修改</el-button
>
<el-button
v-hasPermi="['system:dict:remove']"
type="danger"
plain
icon="Delete"
:disabled="dataMultiple || !hasCurrentDict"
@click="handleDataDelete()"
>删除</el-button
>
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" :disabled="!hasCurrentDict" @click="handleDataExport"
>导出</el-button
>
</div>
<div class="dict-table-wrap">
<el-table v-loading="dataLoading" border :data="dataList" @selection-change="handleDataSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
<el-table-column label="字典标签" align="center" prop="dictLabel" width="80">
<template #default="scope">
<span
v-if="
(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)
"
>{{ scope.row.dictLabel }}</span
>
<el-tag
v-else
:type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass"
:class="scope.row.cssClass"
>{{ scope.row.dictLabel }}</el-tag
>
</template>
</el-table-column>
<el-table-column label="字典键值" align="center" prop="dictValue" width="80" />
<el-table-column label="字典排序" align="center" prop="dictSort" width="80" />
<el-table-column label="备注" align="center" prop="remark" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" align="center" width="120" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleDataUpdate(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDataDelete(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
<pagination
v-show="dataTotal > 0"
v-model:page="dataQueryParams.pageNum"
v-model:limit="dataQueryParams.pageSize"
:total="dataTotal"
@pagination="getDataList"
/>
</el-card>
</el-col>
</el-row>
<!-- 字典类型对话框 -->
<el-dialog v-model="typeDialog.visible" :title="typeDialog.title" width="500px" append-to-body>
<el-form ref="typeFormRef" :model="typeForm" :rules="typeRules" label-width="100px">
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="form.dictName" placeholder="请输入字典名称" />
<el-input v-model="typeForm.dictName" placeholder="请输入字典名称" />
</el-form-item>
<el-form-item label="字典类型" prop="dictType">
<el-input v-model="form.dictType" placeholder="请输入字典类型" />
<el-form-item prop="dictType">
<el-input v-model="typeForm.dictType" placeholder="请输入字典类型" maxlength="100" />
<span slot="label">
<el-tooltip content="数据存储中的Key值sys_user_sex" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
字典类型
</span>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
<el-input v-model="typeForm.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="submitTypeForm">确 定</el-button>
<el-button @click="cancelType">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 字典数据对话框 -->
<el-dialog v-model="dataDialog.visible" :title="dataDialog.title" width="500px" append-to-body>
<el-form ref="dataFormRef" :model="dataForm" :rules="dataRules" label-width="80px">
<el-form-item label="字典类型">
<el-input v-model="dataForm.dictType" :disabled="true" />
</el-form-item>
<el-form-item label="数据标签" prop="dictLabel">
<el-input v-model="dataForm.dictLabel" placeholder="请输入数据标签" />
</el-form-item>
<el-form-item label="数据键值" prop="dictValue">
<el-input v-model="dataForm.dictValue" placeholder="请输入数据键值" />
</el-form-item>
<el-form-item label="样式属性" prop="cssClass">
<el-input v-model="dataForm.cssClass" placeholder="请输入样式属性" />
</el-form-item>
<el-form-item label="显示排序" prop="dictSort">
<el-input-number v-model="dataForm.dictSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="回显样式" prop="listClass">
<el-select v-model="dataForm.listClass">
<el-option
v-for="item in listClassOptions"
:key="item.value"
:label="item.label + '(' + item.value + ')'"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="dataForm.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitDataForm">确 定</el-button>
<el-button @click="cancelData">取 消</el-button>
</div>
</template>
</el-dialog>
@@ -110,35 +278,39 @@
<script setup name="Dict" lang="ts">
import { useDictStore } from '@/store/modules/dict';
import { listType, getType, delType, addType, updateType, refreshCache } from '@/api/system/dict/type';
import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const typeList = ref<DictTypeVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const typeLoading = ref(true);
const showTypeSearch = ref(true);
const typeIds = ref<Array<number | string>>([]);
const typeSingle = ref(true);
const typeMultiple = ref(true);
const typeTotal = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const dictFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const typeFormRef = ref<ElFormInstance>();
const typeQueryFormRef = ref<ElFormInstance>();
const typeTableRef = ref<ElTableInstance>();
const dialog = reactive<DialogOption>({
const typeDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: DictTypeForm = {
const typeInitFormData: DictTypeForm = {
dictId: undefined,
dictName: '',
dictType: '',
remark: ''
};
const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
form: { ...initFormData },
const typeState = reactive<PageData<DictTypeForm, DictTypeQuery>>({
form: { ...typeInitFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
@@ -151,96 +323,353 @@ const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
}
});
const { queryParams, form, rules } = toRefs(data);
const { queryParams: typeQueryParams, form: typeForm, rules: typeRules } = toRefs(typeState);
/** 查询字典类型列表 */
const getList = () => {
loading.value = true;
listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
const currentDict = ref<DictTypeVO | null>(null);
const hasCurrentDict = computed(() => !!currentDict.value);
const currentDictLabel = computed(() => {
if (!currentDict.value) return '请先选择字典';
return `${currentDict.value.dictName} / ${currentDict.value.dictType}`;
});
const dataList = ref<DictDataVO[]>([]);
const dataLoading = ref(false);
const showDataSearch = ref(true);
const dataIds = ref<Array<string | number>>([]);
const dataSingle = ref(true);
const dataMultiple = ref(true);
const dataTotal = ref(0);
const dataFormRef = ref<ElFormInstance>();
const dataQueryFormRef = ref<ElFormInstance>();
const dataDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const listClassOptions = ref<Array<{ value: string; label: string }>>([
{ value: 'default', label: '默认' },
{ value: 'primary', label: '主要' },
{ value: 'success', label: '成功' },
{ value: 'info', label: '信息' },
{ value: 'warning', label: '警告' },
{ value: 'danger', label: '危险' }
]);
const dataInitFormData: DictDataForm = {
dictCode: undefined,
dictLabel: '',
dictValue: '',
cssClass: '',
listClass: 'primary',
dictSort: 0,
remark: ''
};
const dataState = reactive<PageData<DictDataForm, DictDataQuery>>({
form: { ...dataInitFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
dictName: '',
dictType: '',
dictLabel: ''
},
rules: {
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
}
});
const { queryParams: dataQueryParams, form: dataForm, rules: dataRules } = toRefs(dataState);
const getTypeList = () => {
typeLoading.value = true;
listType(proxy?.addDateRange(typeQueryParams.value, dateRange.value)).then((res) => {
typeList.value = res.rows;
total.value = res.total;
loading.value = false;
typeTotal.value = res.total;
typeLoading.value = false;
ensureCurrentType();
});
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
const ensureCurrentType = () => {
if (!typeList.value.length) {
currentDict.value = null;
dataQueryParams.value.dictType = '';
dataList.value = [];
dataTotal.value = 0;
return;
}
const current = currentDict.value && typeList.value.find((item) => item.dictId === currentDict.value?.dictId);
const nextRow = current || typeList.value[0];
setCurrentType(nextRow);
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
dictFormRef.value?.resetFields();
const setCurrentType = (row: DictTypeVO) => {
currentDict.value = row;
dataQueryParams.value.dictType = row.dictType;
dataQueryParams.value.pageNum = 1;
dataQueryParams.value.dictLabel = '';
getDataList();
nextTick(() => typeTableRef.value?.setCurrentRow(row));
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
const handleTypeRowClick = (row: DictTypeVO) => {
setCurrentType(row);
};
/** 重置按钮操作 */
const resetQuery = () => {
const cancelType = () => {
resetTypeForm();
typeDialog.visible = false;
};
const resetTypeForm = () => {
typeForm.value = { ...typeInitFormData };
typeFormRef.value?.resetFields();
};
const handleTypeQuery = () => {
typeQueryParams.value.pageNum = 1;
getTypeList();
};
const handleTypeResetQuery = () => {
dateRange.value = ['', ''];
queryFormRef.value?.resetFields();
handleQuery();
typeQueryFormRef.value?.resetFields();
handleTypeQuery();
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加字典类型';
const handleTypeAdd = () => {
resetTypeForm();
typeDialog.visible = true;
typeDialog.title = '添加字典类型';
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: DictTypeVO[]) => {
ids.value = selection.map((item) => item.dictId);
single.value = selection.length != 1;
multiple.value = !selection.length;
const handleTypeSelectionChange = (selection: DictTypeVO[]) => {
typeIds.value = selection.map((item) => item.dictId);
typeSingle.value = selection.length != 1;
typeMultiple.value = !selection.length;
};
/** 修改按钮操作 */
const handleUpdate = async (row?: DictTypeVO) => {
reset();
const dictId = row?.dictId || ids.value[0];
const handleTypeUpdate = async (row?: DictTypeVO) => {
resetTypeForm();
const dictId = row?.dictId || typeIds.value[0];
const res = await getType(dictId);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改字典类型';
Object.assign(typeForm.value, res.data);
typeDialog.visible = true;
typeDialog.title = '修改字典类型';
};
/** 提交按钮 */
const submitForm = () => {
dictFormRef.value?.validate(async (valid: boolean) => {
const submitTypeForm = () => {
typeFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.dictId ? await updateType(form.value) : await addType(form.value);
typeForm.value.dictId ? await updateType(typeForm.value) : await addType(typeForm.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
getList();
typeDialog.visible = false;
getTypeList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: DictTypeVO) => {
const dictIds = row?.dictId || ids.value;
const handleTypeDelete = async (row?: DictTypeVO) => {
const dictIds = row?.dictId || typeIds.value;
await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
await delType(dictIds);
getList();
getTypeList();
proxy?.$modal.msgSuccess('删除成功');
};
/** 导出按钮操作 */
const handleExport = () => {
const handleTypeExport = () => {
proxy?.download(
'system/dict/type/export',
{
...queryParams.value
...typeQueryParams.value
},
`dict_${new Date().getTime()}.xlsx`
);
};
/** 刷新缓存按钮操作 */
const handleRefreshCache = async () => {
await refreshCache();
proxy?.$modal.msgSuccess('刷新成功');
useDictStore().cleanDict();
};
const getDataList = async () => {
if (!currentDict.value) {
dataList.value = [];
dataTotal.value = 0;
dataLoading.value = false;
return;
}
dataLoading.value = true;
const res = await listData(dataQueryParams.value);
dataList.value = res.rows;
dataTotal.value = res.total;
dataLoading.value = false;
};
const cancelData = () => {
dataDialog.visible = false;
resetDataForm();
};
const resetDataForm = () => {
dataForm.value = { ...dataInitFormData };
dataFormRef.value?.resetFields();
};
const handleDataQuery = () => {
if (!currentDict.value) return;
dataQueryParams.value.pageNum = 1;
getDataList();
};
const handleDataResetQuery = () => {
dataQueryFormRef.value?.resetFields();
dataQueryParams.value.dictLabel = '';
handleDataQuery();
};
const handleDataAdd = () => {
if (!currentDict.value) {
proxy?.$modal.msgWarning('请先选择字典');
return;
}
resetDataForm();
dataForm.value.dictType = currentDict.value.dictType;
dataDialog.visible = true;
dataDialog.title = '添加字典数据';
};
const handleDataSelectionChange = (selection: DictDataVO[]) => {
dataIds.value = selection.map((item) => item.dictCode);
dataSingle.value = selection.length != 1;
dataMultiple.value = !selection.length;
};
const handleDataUpdate = async (row?: DictDataVO) => {
if (!currentDict.value) {
proxy?.$modal.msgWarning('请先选择字典');
return;
}
resetDataForm();
const dictCode = row?.dictCode || dataIds.value[0];
const res = await getData(dictCode);
Object.assign(dataForm.value, res.data);
dataDialog.visible = true;
dataDialog.title = '修改字典数据';
};
const submitDataForm = () => {
dataFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
dataForm.value.dictCode ? await updateData(dataForm.value) : await addData(dataForm.value);
useDictStore().removeDict(dataQueryParams.value.dictType);
proxy?.$modal.msgSuccess('操作成功');
dataDialog.visible = false;
await getDataList();
}
});
};
const handleDataDelete = async (row?: DictDataVO) => {
if (!currentDict.value) {
proxy?.$modal.msgWarning('请先选择字典');
return;
}
const dictCodes = row?.dictCode || dataIds.value;
await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
await delData(dictCodes);
await getDataList();
proxy?.$modal.msgSuccess('删除成功');
useDictStore().removeDict(dataQueryParams.value.dictType);
};
const handleDataExport = () => {
if (!currentDict.value) {
proxy?.$modal.msgWarning('请先选择字典');
return;
}
proxy?.download(
'system/dict/data/export',
{
...dataQueryParams.value
},
`dict_data_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getList();
getTypeList();
});
</script>
<style lang="scss" scoped>
.dict-grid {
row-gap: 16px;
}
.dict-card__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.dict-card__title {
display: inline-flex;
align-items: baseline;
gap: 8px;
font-weight: 600;
}
.dict-card__subtitle {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.dict-form-scroll {
max-height: 200px;
overflow: auto;
margin-bottom: 12px;
padding-right: 6px;
padding-bottom: 4px;
}
.dict-form-scroll :deep(.el-form) {
display: flex;
flex-wrap: wrap;
column-gap: 12px;
row-gap: 10px;
}
.dict-form-scroll :deep(.el-form-item) {
margin: 0;
}
.dict-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 8px 0 12px;
}
.dict-actions :deep(.el-button) {
height: 32px;
padding: 0 14px;
}
.dict-actions :deep(.el-button + .el-button) {
margin-left: 0;
}
.dict-table-wrap {
overflow-x: auto;
}
</style>

View File

@@ -25,10 +25,10 @@
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
<el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
<el-button v-hasPermi="['system:menu:remove']" type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
@@ -44,6 +44,7 @@
:default-expand-all="false"
lazy
:load="getChildrenList"
:expand-change="expandMenuHandle"
>
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
<el-table-column prop="icon" label="图标" align="center" width="100">
@@ -251,6 +252,21 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.visible !== '0'" :span="12">
<el-form-item label="激活路径" prop="form.remark">
<template #label>
<span>
<el-tooltip content="隐藏菜单填写默认激活路由,比如激活父菜单的路由 /system/user" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
激活路由
</span>
</template>
<el-input v-model="form.remark" placeholder="请输入激活路径" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
@@ -353,6 +369,13 @@ const getChildrenList = async (row: any, treeNode: unknown, resolve: (data: any[
resolve(children);
};
/** 收起菜单时从menuExpandMap中删除对应菜单id数据 */
const expandMenuHandle = async (row: any, expanded: boolean) => {
if (!expanded) {
menuExpandMap.value[row.menuId] = undefined;
}
};
/** 刷新展开的菜单数据 */
const refreshLoadTree = (parentId: string | number) => {
if (menuExpandMap.value[parentId]) {
@@ -388,12 +411,16 @@ const getList = async () => {
}
tempMap[parentId].push(menu);
}
// 创建一个当前所有 menuId 的 Set用于查找父菜单是否存在于当前数据中
const menuIdSet = new Set();
// 设置有没有子菜单
for (const menu of res.data) {
menu['hasChildren'] = tempMap[menu.menuId]?.length > 0;
menuIdSet.add(menu.menuId);
}
menuChildrenListMap.value = tempMap;
menuList.value = tempMap[0] || [];
// 找出所有父ID不在当前菜单ID集合中的菜单项作为新的顶层菜单
menuList.value = res.data.filter((menu) => !menuIdSet.has(menu.parentId));
// 根据新数据重新加载子菜单数据
refreshAllExpandMenuData();
loading.value = false;

View File

@@ -170,10 +170,9 @@
</template>
<script setup name="Post" lang="ts">
import { listPost, addPost, delPost, getPost, updatePost } from '@/api/system/post';
import { listPost, addPost, delPost, getPost, updatePost, deptTreeSelect } from '@/api/system/post';
import { PostForm, PostQuery, PostVO } from '@/api/system/post/types';
import { DeptVO } from '@/api/system/dept/types';
import api from '@/api/system/user';
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
@@ -186,7 +185,7 @@ const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const deptOptions = ref<DeptTreeVO[]>([]);
const deptTreeRef = ref<ElTreeInstance>();
const postFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
@@ -212,6 +211,8 @@ const data = reactive<PageData<PostForm, PostQuery>>({
queryParams: {
pageNum: 1,
pageSize: 10,
deptId: undefined,
belongDeptId: undefined,
postCode: '',
postName: '',
postCategory: '',
@@ -245,7 +246,7 @@ watchEffect(
/** 查询部门下拉树结构 */
const getTreeSelect = async () => {
const res = await api.deptTreeSelect();
const res = await deptTreeSelect();
deptOptions.value = res.data;
};

View File

@@ -46,7 +46,7 @@
<el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
<el-button v-hasPermi="['system:role:remove']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>

View File

@@ -47,6 +47,9 @@
<el-col :span="1.5">
<el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantDict">同步租户字典</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-if="userId === 1" type="success" plain icon="Refresh" @click="handleSyncTenantConfig">同步租户参数配置</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
@@ -152,7 +155,8 @@ import {
updateTenant,
changeTenantStatus,
syncTenantPackage,
syncTenantDict
syncTenantDict,
syncTenantConfig
} from '@/api/system/tenant';
import { selectTenantPackage } from '@/api/system/tenantPackage';
import { useUserStore } from '@/store/modules/user';
@@ -365,6 +369,13 @@ const handleSyncTenantDict = async () => {
proxy?.$modal.msgSuccess(res.msg);
};
/**同步租户参数配置*/
const handleSyncTenantConfig = async () => {
await proxy?.$modal.confirm('确认要同步所有租户参数配置吗?');
const res = await syncTenantConfig();
proxy?.$modal.msgSuccess(res.msg);
};
onMounted(() => {
getList();
});

View File

@@ -154,7 +154,7 @@
<el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
<el-form-item label="归属部门" prop="deptId">
<el-tree-select
v-model="form.deptId"
@@ -209,7 +209,7 @@
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
<el-form-item label="岗位">
<el-select v-model="form.postIds" multiple placeholder="请选择">
<el-option
@@ -222,7 +222,7 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
<el-form-item label="角色" prop="roleIds">
<el-select v-model="form.roleIds" filterable multiple placeholder="请选择">
<el-option
@@ -293,13 +293,12 @@ import api from '@/api/system/user';
import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
import { RoleVO } from '@/api/system/role/types';
import { PostQuery, PostVO } from '@/api/system/post/types';
import { treeselect } from '@/api/system/dept';
import { PostVO } from '@/api/system/post/types';
import { globalHeaders } from '@/utils/request';
import { to } from 'await-to-js';
import { optionselect } from '@/api/system/post';
import { hasPermi } from '@/directive/permission';
import { checkPermi } from '@/utils/permission';
import { useUserStore } from '@/store/modules/user';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -411,7 +410,7 @@ const initData: PageData<UserForm, UserQuery> = {
],
phonenumber: [
{
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
pattern: /^1[3456789][0-9]\d{8}$/,
message: '请输入正确的手机号码',
trigger: 'blur'
}
@@ -616,7 +615,9 @@ const handleUpdate = async (row?: UserForm) => {
dialog.title = '修改用户';
Object.assign(form.value, data.user);
postOptions.value = data.posts;
roleOptions.value = data.roles;
roleOptions.value = Array.from(
new Map([...data.roles, ...data.user.roles].map(role => [role.roleId, role])).values()
);
form.value.postIds = data.postIds;
form.value.roleIds = data.roleIds;
form.value.password = '';
@@ -626,7 +627,17 @@ const handleUpdate = async (row?: UserForm) => {
const submitForm = () => {
userFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.userId ? await api.updateUser(form.value) : await api.addUser(form.value);
if (form.value.userId) {
// 自己编辑自己的情况下 不允许编辑角色部门岗位
if (form.value.userId == useUserStore().userId) {
form.value.roleIds = null;
form.value.deptId = null;
form.value.postIds = null;
}
await api.updateUser(form.value);
} else {
await api.addUser(form.value);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();

View File

@@ -56,7 +56,7 @@
</template>
<script setup lang="ts">
import { authUnlock, authBinding } from '@/api/system/social/auth';
import { authUnlock, authRouterUrl } from '@/api/system/social/auth';
import { propTypes } from '@/utils/propTypes';
import { useUserStore } from '@/store/modules/user';
@@ -84,7 +84,7 @@ const unlockAuth = (row: any) => {
};
const authUrl = (source: string) => {
authBinding(source, useUserStore().tenantId).then((res: any) => {
authRouterUrl(source, useUserStore().tenantId).then((res: any) => {
if (res.code === 200) {
window.location.href = res.data;
} else {

View File

@@ -48,7 +48,7 @@ const rule: ElFormRules = {
message: '手机号码不能为空',
trigger: 'blur'
},
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
{ pattern: /^1[3456789][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
};
const rules = ref<ElFormRules>(rule);

View File

@@ -104,7 +104,7 @@
<el-link v-copyText="value" v-copyText:callback="copyTextSuccess" :underline="false" icon="DocumentCopy" style="float: right">
&nbsp;复制
</el-link>
<pre>{{ value }}</pre>
<highlightjs :code="value" />
</el-tab-pane>
</el-tabs>
</el-dialog>
@@ -248,3 +248,12 @@ onMounted(() => {
getDataNameList();
});
</script>
<style lang="scss" scoped>
.el-tab-pane {
background-color: #282c34;
.el-link {
color: #fff;
}
}
</style>

View File

@@ -1,6 +1,8 @@
<template>
<div class="p-2">
<el-card shadow="never">
<!-- mode用于直接后端发起流程 不同接口实现方式可查看具体后端代码 -->
<!-- 默认前端发起 前端发起更多样性 比如可以选审批人 选抄送人 上传附件等等 后端发起需要用户自行编写代码传这些参数 -->
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@@ -9,10 +11,16 @@
:id="form.id"
:status="form.status"
:pageType="routeParams.type"
:mode="false"
/>
</el-card>
<el-card shadow="never" style="height: 78vh; overflow-y: auto">
<el-form ref="leaveFormRef" v-loading="loading" :disabled="routeParams.type === 'view'" :model="form" :rules="rules" label-width="80px">
<el-form-item label="流程定义" v-if="routeParams.type === 'add'">
<el-select v-model="flowCode" placeholder="选择流程定义" style="width: 100%">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="请假类型" prop="leaveType">
<el-select v-model="form.leaveType" placeholder="请选择请假类型" style="width: 100%">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
@@ -42,22 +50,11 @@
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<!-- 审批记录 -->
<approvalRecord ref="approvalRecordRef" />
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" :before-close="handleClose" width="500">
<el-select v-model="flowCode" placeholder="Select" style="width: 240px">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="submitFlow()"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Leave" lang="ts">
import { addLeave, getLeave, updateLeave } from '@/api/workflow/leave';
import { addLeave, getLeave, submitAndFlowStart, updateLeave } from '@/api/workflow/leave';
import { LeaveForm, LeaveQuery, LeaveVO } from '@/api/workflow/leave/types';
import { startWorkFlow } from '@/api/workflow/task';
import SubmitVerify from '@/components/Process/submitVerify.vue';
@@ -116,28 +113,24 @@ const flowCodeOptions = [
label: '请假申请-排他并行会签'
}
];
// 自定义流程可不选择 直接填写flowCode 例如 'leave1'
const flowCode = ref<string>('leave1');
const flowCode = ref<string>('');
const dialogVisible = reactive<DialogOption>({
visible: false,
title: '流程定义'
});
//提交组件
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
//审批记录组件
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
//按钮组件
const approvalButtonRef = ref<InstanceType<typeof ApprovalButton>>();
const leaveFormRef = ref<ElFormInstance>();
const submitFormData = ref<StartProcessBo>({
businessId: '',
flowCode: '',
variables: {}
variables: {},
bizExt: {}
});
const taskVariables = ref<Record<string, any>>({});
const bizExt = ref<Record<string, any>>({});
const initFormData: LeaveForm = {
id: undefined,
@@ -164,11 +157,6 @@ const data = reactive<PageData<LeaveForm, LeaveQuery>>({
}
});
const handleClose = () => {
dialogVisible.visible = false;
flowCode.value = '';
buttonLoading.value = false;
};
const { form, rules } = toRefs(data);
/** 表单重置 */
@@ -200,7 +188,7 @@ const getInfo = () => {
};
/** 提交按钮 */
const submitForm = (status: string) => {
const submitForm = (status: string, mode: boolean) => {
if (leaveTime.value.length === 0) {
proxy?.$modal.msgError('请假时间不能为空');
return;
@@ -211,29 +199,30 @@ const submitForm = (status: string) => {
form.value.endDate = leaveTime.value[1];
if (valid) {
buttonLoading.value = true;
let res: AxiosResponse<LeaveVO>;
if (form.value.id) {
res = await updateLeave(form.value).finally(() => (buttonLoading.value = false));
} else {
res = await addLeave(form.value).finally(() => (buttonLoading.value = false));
}
form.value = res.data;
if (status === 'draft') {
// 设置后端发起和不等于草稿状态 直接走流程发起
if (mode && status != 'draft') {
const res = await submitAndFlowStart(form.value).finally(() => (buttonLoading.value = false));
form.value = res.data;
buttonLoading.value = false;
proxy?.$modal.msgSuccess('暂存成功');
proxy?.$modal.msgSuccess('操作成功');
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
if ((form.value.status === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
return;
let res;
if (form.value.id) {
res = await updateLeave(form.value).finally(() => (buttonLoading.value = false));
} else {
res = await addLeave(form.value).finally(() => (buttonLoading.value = false));
}
//说明启动过先随意穿个参数
if (flowCode.value === '' || flowCode.value === null) {
flowCode.value = 'xx';
form.value = res.data;
if (status === 'draft') {
buttonLoading.value = false;
proxy?.$modal.msgSuccess('暂存成功');
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
await handleStartWorkFlow(res.data);
}
await handleStartWorkFlow(res.data);
}
}
});
@@ -242,10 +231,6 @@ const submitForm = (status: string) => {
}
};
const submitFlow = async () => {
handleStartWorkFlow(form.value);
dialogVisible.visible = false;
};
//提交申请
const handleStartWorkFlow = async (data: LeaveForm) => {
try {
@@ -258,7 +243,13 @@ const handleStartWorkFlow = async (data: LeaveForm) => {
// leave4/5 使用的流程变量
userList: ['1', '3', '4']
};
//流程实例业务扩展字段
bizExt.value = {
businessTitle: '请假申请',
businessCode: data.applyCode
};
submitFormData.value.variables = taskVariables.value;
submitFormData.value.bizExt = bizExt.value;
const resp = await startWorkFlow(submitFormData.value);
if (submitVerifyRef.value) {
buttonLoading.value = false;

View File

@@ -24,7 +24,7 @@ const iframeLoaded = () => {
};
};
const open = async (definitionId, disabled) => {
const url = baseUrl + `/warm-flow-ui/index.html?id=${definitionId}&disabled=${disabled}`;
const url = baseUrl + `/warm-flow-ui/index.html?id=${definitionId}&onlyDesignShow=true`;
iframeUrl.value = url + '&Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
};
/** 关闭按钮 */

View File

@@ -42,19 +42,19 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="Plus" @click="handleAdd()">添加</el-button>
<el-button v-hasPermi="['workflow:definition:add']" type="primary" icon="Plus" @click="handleAdd()">添加</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
<el-button v-hasPermi="['workflow:definition:edit']" type="success" icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
<el-button v-hasPermi="['workflow:definition:remove']" type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">部署流程文件</el-button>
<el-button v-hasPermi="['workflow:definition:import']" type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">部署流程文件</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
<el-button v-hasPermi="['workflow:definition:export']" type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
@@ -74,6 +74,7 @@
<el-table-column align="center" prop="activityStatus" label="激活状态" width="130">
<template #default="scope">
<el-switch
v-hasPermi="['workflow:definition:active']"
v-model="scope.row.activityStatus"
:active-value="1"
:inactive-value="0"
@@ -92,21 +93,21 @@
<template #default="scope">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
<el-button v-hasPermi="['workflow:definition:remove']" link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
</el-col>
<el-col :span="1.5">
<el-button link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
<el-button v-hasPermi="['workflow:definition:copy']" link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
</el-col>
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)"
<el-button v-hasPermi="['workflow:definition:query']" link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)"
>流程设计</el-button
>
<el-button link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
<el-button v-hasPermi="['workflow:definition:query']" link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
</el-col>
<el-col v-if="scope.row.isPublish !== 1" :span="1.5">
<el-button link type="primary" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">发布流程</el-button>
<el-button v-hasPermi="['workflow:definition:publish']" link type="primary" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">发布流程</el-button>
</el-col>
</el-row>
</template>
@@ -159,7 +160,7 @@
<!-- 新增/编辑流程定义 -->
<el-dialog v-model="modelDialog.visible" :title="modelDialog.title" width="650px" append-to-body :close-on-click-modal="false">
<template #footer>
<el-form ref="defFormRef" :model="form" :rules="rules" label-width="110px">
<el-form ref="defFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="流程类别" prop="category">
<el-tree-select
v-model="form.category"
@@ -178,6 +179,21 @@
<el-form-item label="流程名称" prop="flowName">
<el-input v-model="form.flowName" placeholder="请输入流程名称" maxlength="100" show-word-limit />
</el-form-item>
<el-form-item label="设计器模式" prop="modelValue">
<el-radio-group v-model="form.modelValue" :disabled="!!form.id">
<el-radio value="CLASSICS" size="large" border>经典模式</el-radio>
<el-radio value="MIMIC" size="large" border>仿钉钉模式</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="流程配置">
<el-checkbox v-model="autoPass" label="下一节点执行人是当前任务处理人自动审批" />
</el-form-item>
<el-form-item label="是否动态表单" prop="formCustom">
<el-radio-group v-model="form.formCustom">
<el-radio value="Y" size="large" border disabled></el-radio>
<el-radio value="N" size="large" border></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="表单路径" prop="formPath">
<el-input v-model="form.formPath" placeholder="请输入表单路径" maxlength="100" show-word-limit />
</el-form-item>
@@ -215,6 +231,7 @@ const uploadDialogLoading = ref(false);
const processDefinitionList = ref<FlowDefinitionVo[]>([]);
const categoryOptions = ref<CategoryTreeVO[]>([]);
const categoryName = ref('');
const autoPass = ref(false);
/** 部署文件分类选择 */
const selectCategory = ref();
const defFormRef = ref<ElFormInstance>();
@@ -245,6 +262,8 @@ const queryParams = ref<FlowDefinitionQuery>({
const rules = {
category: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],
flowName: [{ required: true, message: '流程定义名称不能为空', trigger: 'blur' }],
formCustom: [{ required: true, message: '请选择是否动态表单', trigger: 'change' }],
modelValue: [{ required: true, message: '设计器模式不能为空', trigger: 'change' }],
flowCode: [{ required: true, message: '流程定义编码不能为空', trigger: 'blur' }]
};
const initFormData: FlowDefinitionForm = {
@@ -252,7 +271,10 @@ const initFormData: FlowDefinitionForm = {
flowName: '',
flowCode: '',
category: '',
formPath: ''
ext: '',
formPath: '',
formCustom: '',
modelValue: ''
};
//流程定义参数
const form = ref<FlowDefinitionForm>({
@@ -260,7 +282,10 @@ const form = ref<FlowDefinitionForm>({
flowName: '',
flowCode: '',
category: '',
formPath: ''
ext: '',
formPath: '',
formCustom: '',
modelValue: ''
});
onMounted(() => {
getPageList();
@@ -329,6 +354,7 @@ const getPageList = async () => {
const query = proxy.$route.query;
if (query.activeName) {
activeName.value = query.activeName;
proxy.$route.query.activeName = '';
}
if (activeName.value === '0') {
getList();
@@ -469,6 +495,8 @@ const handleAdd = async () => {
if (queryParams.value.category != '') {
form.value.category = queryParams.value.category;
}
form.value.modelValue = 'CLASSICS';
form.value.formCustom = 'N';
modelDialog.visible = true;
modelDialog.title = '新增流程';
};
@@ -478,6 +506,13 @@ const handleUpdate = async (row?: FlowDefinitionVo) => {
const id = row?.id || ids.value[0];
const res = await getInfo(id);
Object.assign(form.value, res.data);
autoPass.value = false;
if (form.value.ext != null && form.value.ext != '') {
const extJson = JSON.parse(form.value.ext);
if (extJson.autoPass != null && extJson.autoPass != '') {
autoPass.value = extJson.autoPass;
}
}
modelDialog.visible = true;
modelDialog.title = '修改流程';
};
@@ -486,10 +521,14 @@ const handleSubmit = async () => {
defFormRef.value.validate(async (valid: boolean) => {
if (valid) {
loading.value = true;
const ext = {};
ext.autoPass = autoPass.value;
form.value.ext = JSON.stringify(ext);
if (form.value.id) {
await edit(form.value).finally(() => (loading.value = false));
} else {
await add(form.value).finally(() => (loading.value = false));
activeName.value = '1';
}
proxy?.$modal.msgSuccess('操作成功');
modelDialog.visible = false;

View File

@@ -20,14 +20,6 @@
</el-card>
</el-col>
<el-col :lg="20" :xs="24">
<!-- <div class="mb-[10px]">
<el-card shadow="hover" class="text-center">
<el-radio-group v-model="tab" @change="changeTab(tab)">
<el-radio-button value="running">运行中</el-radio-button>
<el-radio-button value="finish">已完成</el-radio-button>
</el-radio-group>
</el-card>
</div>-->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
@@ -58,7 +50,7 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
<el-button v-hasPermi="['workflow:instance:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
@@ -69,32 +61,34 @@
<el-table v-loading="loading" border :data="processInstanceList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" align="center" label="流程定义名称">
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
<el-table-column :show-overflow-tooltip="true" align="center" width="120" label="流程定义名称">
<template #default="scope">
<span>{{ scope.row.flowName }}v{{ scope.row.version }}</span>
</template>
</el-table-column>
<el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="flowCode" width="120" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
<el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
<el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" prop="createByName" :show-overflow-tooltip="true" label="申请人"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="状态" min-width="70">
<el-table-column v-if="tab === 'running'" v-hasPermi="['workflow:instance:active']" align="center" prop="isSuspended" label="状态" min-width="70">
<template #default="scope">
<el-tag v-if="!scope.row.isSuspended" type="success">激活</el-tag>
<el-tag v-else type="danger">挂起</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="流程状态" min-width="70">
<el-table-column align="center" label="流程状态" min-width="80">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="createTime" label="启动时间" width="160"></el-table-column>
<el-table-column v-if="tab === 'finish'" align="center" prop="updateTime" label="结束时间" width="160"></el-table-column>
<el-table-column label="操作" align="center" :width="165">
<el-table-column label="操作" align="center" :width="165" fixed="right">
<template #default="scope">
<el-row v-if="tab === 'running'" :gutter="10" class="mb8">
<el-col :span="1.5">
@@ -110,15 +104,15 @@
</el-popover>
</el-col>
<el-col :span="1.5">
<el-button type="danger" size="small" icon="Delete" @click="handleDelete(scope.row)">删除 </el-button>
<el-button v-hasPermi="['workflow:instance:remove']" type="danger" size="small" icon="Delete" @click="handleDelete(scope.row)">删除 </el-button>
</el-col>
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
<el-button v-hasPermi="['workflow:instance:query']" type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" size="small" icon="Document" @click="handleInstanceVariable(scope.row)"> 变量 </el-button>
<el-button v-hasPermi="['workflow:instance:variableQuery']" type="primary" size="small" icon="Document" @click="handleInstanceVariable(scope.row)"> 变量 </el-button>
</el-col>
</el-row>
</template>
@@ -144,7 +138,7 @@
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column align="center" prop="suspensionState" label="状态" min-width="70">
<el-table-column v-hasPermi="['workflow:instance:active']" align="center" prop="suspensionState" label="状态" min-width="70">
<template #default="scope">
<el-tag v-if="scope.row.suspensionState == 1" type="success">激活</el-tag>
<el-tag v-else type="danger">挂起</el-tag>
@@ -154,8 +148,8 @@
</el-table>
</el-dialog>
<!-- 流程变量开始 -->
<el-dialog v-model="variableVisible" draggable title="流程变量" width="60%" :close-on-click-modal="false">
<el-card v-loading="variableLoading" class="box-card">
<el-dialog v-model="variableVisible" v-if="variableVisible" draggable title="流程变量" width="60%" :close-on-click-modal="false">
<el-card v-loading="variableLoading">
<template #header>
<div class="clearfix">
<span
@@ -167,6 +161,19 @@
<VueJsonPretty :data="formatToJsonObject(variables)" />
</div>
</el-card>
<el-card v-loading="variableLoading">
<el-form ref="ruleFormRef" :model="form" :inline="true" :rules="rules" label-width="120px">
<el-form-item label="变量KEY" prop="key">
<el-input v-model="form.key" placeholder="请输入变量KEY" />
</el-form-item>
<el-form-item label="变量值" prop="value">
<el-input v-model="form.value" placeholder="请输入变量值" />
</el-form-item>
<el-form-item>
<el-button v-hasPermi="['workflow:instance:variable']" type="primary" @click="handleVariable(ruleFormRef)">确认</el-button>
</el-form-item>
</el-form>
</el-card>
</el-dialog>
<!-- 流程变量结束 -->
@@ -176,7 +183,15 @@
</template>
<script setup lang="ts">
import { pageByRunning, pageByFinish, deleteByInstanceIds, instanceVariable, invalid } from '@/api/workflow/instance';
import {
pageByRunning,
pageByFinish,
deleteByInstanceIds,
deleteHisByInstanceIds,
instanceVariable,
invalid,
updateVariable
} from '@/api/workflow/instance';
import { categoryTree } from '@/api/workflow/category';
import { CategoryTreeVO } from '@/api/workflow/category/types';
import { FlowInstanceQuery, FlowInstanceVO } from '@/api/workflow/instance/types';
@@ -185,6 +200,7 @@ import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css';
import UserSelect from '@/components/UserSelect/index.vue';
import { ElForm, FormInstance } from 'element-plus';
//审批记录组件
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
@@ -192,7 +208,12 @@ const queryFormRef = ref<ElFormInstance>();
const categoryTreeRef = ref<ElTreeInstance>();
import { ref } from 'vue';
import { UserVO } from '@/api/system/user/types';
const form = ref<Record<string, any>>({
instanceId: undefined,
key: undefined,
value: undefined
});
const ruleFormRef = ref<FormInstance>();
const userSelectRef = ref<InstanceType<typeof UserSelect>>();
// 遮罩层
const loading = ref(true);
@@ -208,6 +229,8 @@ const multiple = ref(true);
const showSearch = ref(true);
// 总条数
const total = ref(0);
// 实例id
const instanceId = ref(undefined);
// 流程变量是否显示
const variableVisible = ref(false);
@@ -333,7 +356,7 @@ const handleDelete = async (row: FlowInstanceVO) => {
await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
getProcessInstanceRunningList();
} else {
await deleteByInstanceIds(instanceIdList).finally(() => (loading.value = false));
await deleteHisByInstanceIds(instanceIdList).finally(() => (loading.value = false));
getProcessInstanceFinishList();
}
proxy?.$modal.msgSuccess('删除成功');
@@ -378,12 +401,16 @@ const handleView = (row) => {
//查询流程变量
const handleInstanceVariable = async (row: FlowInstanceVO) => {
instanceId.value = row.id;
variableLoading.value = true;
variableVisible.value = true;
processDefinitionName.value = row.flowName;
const data = await instanceVariable(row.id);
variables.value = data.data.variable;
variableLoading.value = false;
form.value.instanceId = undefined;
form.value.key = undefined;
form.value.value = undefined;
};
/**
@@ -414,6 +441,36 @@ const userSelectCallBack = (data: UserVO[]) => {
queryParams.value.createByIds = selectUserIds.value;
}
};
const rules = reactive<Record<string, any>>({
key: [
{
required: true,
message: '请输入KEY',
trigger: 'blur'
}
],
value: [
{
required: true,
message: '请输入变量值',
trigger: 'blur'
}
]
});
const handleVariable = async (formEl: FormInstance | undefined) => {
await formEl.validate(async (valid, fields) => {
if (valid) {
form.value.instanceId = instanceId.value;
await proxy?.$modal.confirm('是否确认提交?');
await updateVariable(form.value);
proxy?.$modal.msgSuccess('操作成功');
const data = await instanceVariable(instanceId.value);
variables.value = data.data.variable;
}
});
};
onMounted(() => {
getProcessInstanceRunningList();
getTreeselect();

View File

@@ -0,0 +1,360 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="组件名称" prop="componentName">
<el-input v-model="queryParams.componentName" placeholder="请输入组件名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="方法名" prop="methodName">
<el-input v-model="queryParams.methodName" placeholder="请输入方法名" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['workflow:spel:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['workflow:spel:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['workflow:spel:remove']">删除</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="spelList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="60" align="center">
<template #default="scope">
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
</template>
</el-table-column>
<el-table-column label="组件名称" align="center">
<template #default="scope">
{{ scope.row.componentName || '-' }}
</template>
</el-table-column>
<el-table-column label="方法名称" align="center">
<template #default="scope">
{{ scope.row.methodName || '-' }}
</template>
</el-table-column>
<el-table-column label="参数名称" align="center">
<template #default="scope">
{{ scope.row.methodParams || '-' }}
</template>
</el-table-column>
<el-table-column label="SPEL表达式" align="center" prop="viewSpel" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-tag v-if="scope.row.status === '0'">正常</el-tag>
<el-tag v-else>停用</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" align="center">
<template #default="scope">
{{ scope.row.remark || '-' }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:spel:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:spel:remove']"></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改流程spel表达式定义对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="550px" append-to-body>
<el-form ref="spelFormRef" :model="form" :rules="rules" label-width="100px">
<!-- 组件名称 -->
<el-form-item label="组件名称" prop="componentName">
<el-input v-model="form.componentName" placeholder="请输入组件名称" @input="updateViewSpel" />
<template #label>
<span>
<el-tooltip content="注册到Spring容器中的组件名spelRuleComponent" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
组件名称
</span>
</template>
</el-form-item>
<el-form-item label="方法名称" prop="methodName">
<el-input v-model="form.methodName" placeholder="请输入方法名称" @input="updateViewSpel" />
<template #label>
<span>
<el-tooltip content="组件中的方法名称selectDeptLeaderById" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
方法名称
</span>
</template>
</el-form-item>
<el-form-item label="方法参数" prop="methodParams">
<el-input v-model="form.methodParams" placeholder="请输入方法参数" @input="updateViewSpel" />
<template #label>
<span>
<el-tooltip content="方法参数deptId, 多个使用 ',' 分隔,单参数变量仅支持单个方法参数" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
方法参数
</span>
</template>
</el-form-item>
<!-- 改为只读文本展示 -->
<el-form-item label="SPEL表达式">
<span class="preview-box">
{{ form.viewSpel || '例如:#{@组件名.方法名(#方法参数)} 或 ${方法参数}' }}
</span>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Spel" lang="ts">
import { listSpel, getSpel, delSpel, addSpel, updateSpel } from '@/api/workflow/spel';
import { SpelVO, SpelQuery, SpelForm } from '@/api/workflow/spel/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_show_hide, sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_show_hide', 'sys_normal_disable'));
const spelList = ref<SpelVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const spelFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: SpelForm = {
id: undefined,
componentName: undefined,
methodName: undefined,
methodParams: undefined,
viewSpel: undefined,
status: '0',
remark: undefined,
}
const data = reactive<PageData<SpelForm, SpelQuery>>({
form: {...initFormData},
queryParams: {
pageNum: 1,
pageSize: 10,
componentName: undefined,
methodName: undefined,
methodParams: undefined,
viewSpel: undefined,
status: '0',
params: {
}
},
rules: {
status: [
{ required: true, message: "状态不能为空", trigger: "change" }
],
}
});
const { queryParams, form, rules } = toRefs(data);
/** 查询流程spel表达式定义列表 */
const getList = async () => {
loading.value = true;
const res = await listSpel(queryParams.value);
spelList.value = res.rows;
total.value = res.total;
loading.value = false;
}
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
form.value = {...initFormData};
spelFormRef.value?.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
}
/** 多选框选中数据 */
const handleSelectionChange = (selection: SpelVO[]) => {
ids.value = selection.map(item => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = "添加流程spel表达式定义";
}
/** 修改按钮操作 */
const handleUpdate = async (row?: SpelVO) => {
reset();
const _id = row?.id || ids.value[0]
const res = await getSpel(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = "修改流程spel表达式定义";
}
/** 提交按钮 */
const submitForm = () => {
spelFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateSpel(form.value).finally(() => buttonLoading.value = false);
} else {
await addSpel(form.value).finally(() => buttonLoading.value = false);
}
proxy?.$modal.msgSuccess("操作成功");
dialog.visible = false;
await getList();
}
});
}
/** 删除按钮操作 */
const handleDelete = async (row?: SpelVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除流程spel表达式定义编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
await delSpel(_ids);
proxy?.$modal.msgSuccess("删除成功");
await getList();
}
/** 控制是否显示 viewSpel 输入框 */
const showViewSpelInput = ref(false);
/** 更新 spel 预览值并决定是否显示输入框 */
const updateViewSpel = () => {
const comp = (form.value.componentName || '').trim();
const method = (form.value.methodName || '').trim();
const paramStr = (form.value.methodParams || '').trim();
if (!comp && !method && !paramStr) {
form.value.viewSpel = '';
return;
}
// 替换变量值:只有参数存在,组件和方法都不存在
if (!comp && !method && paramStr) {
const paramList = paramStr.split(',')
.map(p => p.trim())
.filter(p => p.length > 0);
if (paramList.length === 1) {
form.value.viewSpel = `\${${paramList[0]}}`;
return;
}
}
// 如果缺少组件或方法,提示填写
if (!comp || !method) {
form.value.viewSpel = '请填写组件名称和方法名';
return;
}
let paramList = [];
if (paramStr) {
// 分割并过滤掉空参数
paramList = paramStr.split(',')
.map(p => p.trim())
.filter(p => p.length > 0);
}
const paramPart = paramList.length > 0
? '(' + paramList.map(p => `#${p}`).join(',') + ')'
: '()';
form.value.viewSpel = `#{@${comp}.${method}${paramPart}}`;
};
/** 监听所有字段变化 */
watch(
() => [form.value.componentName, form.value.methodName, form.value.methodParams],
updateViewSpel
);
onMounted(() => {
getList();
});
</script>
<style lang="scss">
.preview-box {
width: 100%;
padding: 10px 12px;
background-color: #f5f7fa;
border-radius: 4px;
color: #333;
font-family: monospace; /* 等宽字体更清晰 */
white-space: nowrap; /* 禁止换行 */
overflow-x: auto; /* 超出宽度时显示水平滚动条 */
min-height: 36px; /* 与 el-input 高度对齐 */
line-height: 1.5;
}
</style>

View File

@@ -27,7 +27,12 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5" v-if="tab === 'waiting'">
<el-button type="primary" plain icon="Edit" :disabled="multiple" @click="handleUpdate">修改办理人 </el-button>
<el-button class="todo-action-btn todo-action-btn--primary" type="primary" plain icon="Edit" :disabled="multiple" @click="handleUserOpen()"
>修改办理人
</el-button>
<el-button class="todo-action-btn todo-action-btn--warning" type="warning" plain icon="Bell" :disabled="multiple" @click="handleUrgeTaskOpen()"
>催办
</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
</el-row>
@@ -38,14 +43,16 @@
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" width="120" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" width="120" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
<el-table-column align="center" prop="nodeName" :show-overflow-tooltip="true" label="任务名称"></el-table-column>
<el-table-column align="center" prop="createByName" :show-overflow-tooltip="true" label="申请人"></el-table-column>
<el-table-column align="center" label="办理人">
<template #default="scope">
<template v-if="tab === 'waiting'">
@@ -97,21 +104,24 @@
</el-tabs>
</el-card>
<!-- 选人组件 -->
<UserSelect ref="userSelectRef" :multiple="false" @confirm-call-back="submitCallback"></UserSelect>
<UserSelect ref="userSelectRef" :multiple="userMultiple" @confirm-call-back="submitCallback"></UserSelect>
<!-- 流程干预组件 -->
<processMeddle ref="processMeddleRef" @submitCallback="getWaitingList"></processMeddle>
<!-- 申请人 -->
<UserSelect ref="applyUserSelectRef" :multiple="true" :data="selectUserIds" @confirm-call-back="userSelectCallBack"></UserSelect>
<!-- 流程干预组件 -->
<messageType ref="messageTypeRef" @submitCallback="handleUserTask"></messageType>
</div>
</template>
<script setup lang="ts">
import { pageByAllTaskWait, pageByAllTaskFinish, updateAssignee } from '@/api/workflow/task';
import { pageByAllTaskWait, pageByAllTaskFinish, updateAssignee, urgeTask } from '@/api/workflow/task';
import UserSelect from '@/components/UserSelect';
import { TaskQuery } from '@/api/workflow/task/types';
import workflowCommon from '@/api/workflow/workflowCommon';
import { RouterJumpVo } from '@/api/workflow/workflowCommon/types';
import processMeddle from '@/components/Process/processMeddle';
import messageType from '@/components/Process/MessageType';
import { UserVO } from '@/api/system/user/types';
import { TabsPaneContext } from 'element-plus';
//选人组件
@@ -120,6 +130,8 @@ const userSelectRef = ref<InstanceType<typeof UserSelect>>();
const processMeddleRef = ref<InstanceType<typeof processMeddle>>();
//选人组件
const applyUserSelectRef = ref<InstanceType<typeof UserSelect>>();
//消息组件
const messageTypeRef = ref<InstanceType<typeof messageType>>();
const queryFormRef = ref<ElFormInstance>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
@@ -132,13 +144,14 @@ const ids = ref<Array<any>>([]);
const single = ref(true);
// 非多个禁用
const multiple = ref(true);
const userMultiple = ref(false);
// 显示搜索条件
const showSearch = ref(true);
// 总条数
const total = ref(0);
// 模型定义表格数据
const taskList = ref([]);
const title = ref('');
const buttonType = ref('');
//申请人id
const selectUserIds = ref<Array<number | string>>([]);
//申请人选择数量
@@ -204,10 +217,24 @@ const getFinishList = () => {
loading.value = false;
});
};
// 打开催办
const handleUrgeTaskOpen = () => {
messageTypeRef.value.open();
};
//打开修改选人
const handleUpdate = () => {
const handleUserOpen = () => {
userSelectRef.value.open();
};
//打开修改选人
const handleUserTask = async (data) => {
await proxy?.$modal.confirm('是否确认提交?');
data.taskIdList = ids.value;
await urgeTask(data);
messageTypeRef.value.close();
proxy?.$modal.msgSuccess('操作成功');
handleQuery();
};
//修改办理人
const submitCallback = async (data) => {
if (data && data.length > 0) {
@@ -227,8 +254,7 @@ const handleView = (row) => {
taskId: row.id,
type: 'view',
formCustom: row.formCustom,
formPath: row.formPath,
instanceId: row.instanceId
formPath: row.formPath
});
workflowCommon.routerJump(routerJumpVo, proxy);
};
@@ -255,3 +281,59 @@ onMounted(() => {
getWaitingList();
});
</script>
<style lang="scss" scoped>
.todo-action-btn {
height: 32px;
padding: 0 14px;
border-radius: var(--app-radius-md);
font-weight: 600;
--el-button-bg-color: var(--el-fill-color-lighter);
--el-button-border-color: var(--el-border-color-light);
--el-button-text-color: var(--el-text-color-regular);
--el-button-hover-bg-color: var(--el-fill-color-light);
--el-button-hover-border-color: var(--el-border-color);
}
.todo-action-btn--primary {
--el-button-text-color: var(--el-color-primary);
--el-button-border-color: var(--el-color-primary-light-5);
--el-button-bg-color: var(--el-color-primary-light-9);
--el-button-hover-bg-color: var(--el-color-primary-light-8);
--el-button-hover-border-color: var(--el-color-primary-light-5);
}
.todo-action-btn--warning {
--el-button-text-color: var(--el-color-warning);
--el-button-border-color: var(--el-color-warning-light-5);
--el-button-bg-color: var(--el-color-warning-light-9);
--el-button-hover-bg-color: var(--el-color-warning-light-8);
--el-button-hover-border-color: var(--el-color-warning-light-5);
}
:global(html.dark) {
.todo-action-btn {
--el-button-bg-color: rgba(148, 163, 184, 0.12);
--el-button-border-color: rgba(148, 163, 184, 0.3);
--el-button-text-color: var(--el-text-color-regular);
--el-button-hover-bg-color: rgba(148, 163, 184, 0.2);
--el-button-hover-border-color: rgba(148, 163, 184, 0.45);
}
.todo-action-btn--primary {
--el-button-bg-color: rgba(99, 113, 150, 0.14);
--el-button-border-color: rgba(99, 113, 150, 0.45);
--el-button-text-color: #c4cfdd;
--el-button-hover-bg-color: rgba(99, 113, 150, 0.22);
--el-button-hover-border-color: rgba(99, 113, 150, 0.65);
}
.todo-action-btn--warning {
--el-button-bg-color: rgba(214, 149, 59, 0.16);
--el-button-border-color: rgba(214, 149, 59, 0.55);
--el-button-text-color: #ffe6bb;
--el-button-hover-bg-color: rgba(214, 149, 59, 0.24);
--el-button-hover-border-color: rgba(214, 149, 59, 0.75);
}
}
</style>

View File

@@ -31,6 +31,8 @@
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>

View File

@@ -36,14 +36,16 @@
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column align="center" prop="flowName" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
<el-table-column align="center" prop="flowName" width="120" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" width="120" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>
<el-table-column align="center" prop="version" label="版本号" width="90">
<template #default="scope"> v{{ scope.row.version }}.0</template>
</el-table-column>
<el-table-column align="center" prop="nodeName" label="任务名称"></el-table-column>
<el-table-column align="center" prop="createByName" label="申请人"></el-table-column>
<el-table-column align="center" prop="nodeName" :show-overflow-tooltip="true" label="任务名称"></el-table-column>
<el-table-column align="center" prop="createByName" :show-overflow-tooltip="true" label="申请人"></el-table-column>
<el-table-column align="center" prop="approverName" label="办理人">
<template #default="scope">
<el-tag type="success">
@@ -51,17 +53,17 @@
</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="流程状态" prop="flowStatus" min-width="70">
<el-table-column align="center" label="流程状态" prop="flowStatus" min-width="80">
<template #default="scope">
<dict-tag :options="wf_business_status" :value="scope.row.flowStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" label="任务状态" prop="flowTaskStatus" min-width="70">
<el-table-column align="center" label="任务状态" prop="flowTaskStatus" min-width="80">
<template #default="scope">
<dict-tag :options="wf_task_status" :value="scope.row.flowTaskStatus"></dict-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="createTime" label="创建时间" width="160"></el-table-column>
<el-table-column align="center" prop="createTime" label="创建时间" :show-overflow-tooltip="true" width="150"></el-table-column>
<el-table-column label="操作" align="center" width="200">
<template #default="scope">
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>

View File

@@ -36,6 +36,8 @@
<el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column align="center" type="index" label="序号" width="60"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessCode" align="center" label="业务编码"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="businessTitle" align="center" label="业务标题"></el-table-column>
<el-table-column :show-overflow-tooltip="true" prop="flowName" align="center" label="流程定义名称"></el-table-column>
<el-table-column align="center" prop="flowCode" label="流程定义编码"></el-table-column>
<el-table-column align="center" prop="categoryName" label="流程分类"></el-table-column>

View File

@@ -36,7 +36,6 @@ export default defineConfig(({ mode, command }) => {
scss: {
// additionalData: '@use "@/assets/styles/variables.module.scss as *";'
// javascriptEnabled: true
api: 'modern-compiler'
}
},
postcss: {

View File

@@ -12,7 +12,9 @@ export default (path: any) => {
},
resolvers: [
// 自动导入 Element Plus 相关函数ElMessage, ElMessageBox... (带样式)
ElementPlusResolver()
ElementPlusResolver({
importStyle: false
})
],
vueTemplate: true, // 是否在 vue 模板中自动导入
dts: path.resolve(path.resolve(__dirname, '../../src'), 'types', 'auto-imports.d.ts')

View File

@@ -6,7 +6,9 @@ export default (path: any) => {
return Components({
resolvers: [
// 自动导入 Element Plus 组件
ElementPlusResolver(),
ElementPlusResolver({
importStyle: false
}),
// 自动注册图标组件
IconsResolver({
enabledCollections: ['ep']

View File

@@ -1,28 +1,110 @@
import compression from 'vite-plugin-compression';
import { promises as fs } from 'fs';
import path from 'path';
import zlib from 'zlib';
import { promisify } from 'util';
import type { Plugin, ResolvedConfig } from 'vite';
export default (env: any) => {
const { VITE_BUILD_COMPRESS } = env;
const plugin: any[] = [];
if (VITE_BUILD_COMPRESS) {
const compressList = VITE_BUILD_COMPRESS.split(',');
if (compressList.includes('gzip')) {
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
plugin.push(
compression({
ext: '.gz',
deleteOriginFile: false
})
);
}
if (compressList.includes('brotli')) {
plugin.push(
compression({
ext: '.br',
algorithm: 'brotliCompress',
deleteOriginFile: false
})
);
}
const gzip = promisify(zlib.gzip);
const brotliCompress = promisify(zlib.brotliCompress);
const compressibleFileRE = /\.(js|mjs|json|css|html)$/i;
const defaultThreshold = 1025;
type CompressionKind = 'gzip' | 'brotli';
const compressionHandlers: Record<CompressionKind, { ext: string; compress: (content: Buffer) => Promise<Buffer> }> = {
gzip: {
ext: '.gz',
compress: (content) => gzip(content, { level: zlib.constants.Z_BEST_COMPRESSION })
},
brotli: {
ext: '.br',
compress: (content) =>
brotliCompress(content, {
params: {
[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT
}
})
}
return plugin;
};
async function collectFiles(rootDir: string): Promise<string[]> {
const entries = await fs.readdir(rootDir, { withFileTypes: true });
const files = await Promise.all(
entries.map(async (entry) => {
const fullPath = path.join(rootDir, entry.name);
if (entry.isDirectory()) {
return collectFiles(fullPath);
}
return compressibleFileRE.test(entry.name) ? [fullPath] : [];
})
);
return files.flat();
}
function createCompressionPlugin(kind: CompressionKind): Plugin {
const handler = compressionHandlers[kind];
let config: ResolvedConfig | undefined;
return {
name: `local:compression:${kind}`,
apply: 'build',
enforce: 'post',
configResolved(resolvedConfig) {
config = resolvedConfig;
},
async closeBundle() {
const outputDir = path.resolve(process.cwd(), config?.build.outDir ?? 'dist');
const files = await collectFiles(outputDir);
const compressedEntries: Array<{ file: string; originalKb: string; compressedKb: string }> = [];
await Promise.all(
files.map(async (filePath) => {
const stat = await fs.stat(filePath);
if (stat.size < defaultThreshold) {
return;
}
const content = await fs.readFile(filePath);
const compressed = await handler.compress(content);
const outputFile = `${filePath}${handler.ext}`;
await fs.writeFile(outputFile, compressed);
compressedEntries.push({
file: path.relative(outputDir, outputFile).replaceAll('\\', '/'),
originalKb: (stat.size / 1024).toFixed(2),
compressedKb: (compressed.byteLength / 1024).toFixed(2)
});
})
);
if (!compressedEntries.length) {
return;
}
compressedEntries.sort((a, b) => a.file.localeCompare(b.file));
config?.logger.info(`\n[compression:${kind}] generated ${compressedEntries.length} files`);
for (const entry of compressedEntries) {
config?.logger.info(`${path.basename(outputDir)}/${entry.file} ${entry.originalKb}kb -> ${entry.compressedKb}kb`);
}
config?.logger.info('');
}
};
}
export default (env: Record<string, string>) => {
const { VITE_BUILD_COMPRESS } = env;
const plugins: Plugin[] = [];
if (!VITE_BUILD_COMPRESS) {
return plugins;
}
const compressionList = VITE_BUILD_COMPRESS.split(',').map((item) => item.trim()) as CompressionKind[];
if (compressionList.includes('gzip')) {
plugins.push(createCompressionPlugin('gzip'));
}
if (compressionList.includes('brotli')) {
plugins.push(createCompressionPlugin('brotli'));
}
return plugins;
};

View File

@@ -3,6 +3,7 @@ import Icons from 'unplugin-icons/vite';
export default () => {
return Icons({
// 自动安装图标库
autoInstall: true
autoInstall: true,
compiler: "vue3"
});
};