252 Commits

Author SHA1 Message Date
疯狂的狮子Li
6bc3c618fe !262 发布 v5.6.0-v2.6.0 版本 新年第一版
Merge pull request !262 from 疯狂的狮子Li/dev
2026-03-24 03:49:37 +00: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
5c9c940588 !257 发布 5.5.3-2.5.3 版本 提前祝大家新年快乐
Merge pull request !257 from 疯狂的狮子Li/dev
2026-01-23 06:04:29 +00: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
dfd1dc29d1 !256 fix 修复 代码漏改问题
Merge pull request !256 from 疯狂的狮子Li/dev
2025-12-23 05:56:54 +00: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
b411505b19 !255 发布 5.5.2-2.5.2 版本 2025年最后一版
Merge pull request !255 from 疯狂的狮子Li/dev
2025-12-23 01:41:45 +00: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
52ea8895d6 !246 发布 5.5.1-2.5.1 日常依赖升级bug修复
Merge pull request !246 from 疯狂的狮子Li/dev
2025-10-28 03:22:49 +00: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
1b46739799 !241 发布 5.5.0-2.5.0 喜迎国庆
Merge pull request !241 from 疯狂的狮子Li/dev
2025-09-22 03:18:41 +00: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
疯狂的狮子Li
b000788785 !222 发布 5.4.1-2.4.1 小步迭代修复问题
Merge pull request !222 from 疯狂的狮子Li/dev
2025-07-01 01:13:49 +00:00
疯狂的狮子Li
62f7d393f3 🐳🐳🐳发布 5.4.1-2.4.1 小步迭代修复问题 2025-07-01 09:11:36 +08:00
疯狂的狮子Li
31037db627 update 优化 访问流程图页面缓存问题 参数增加时间戳 临时解决 2025-06-27 16:38:56 +08:00
疯狂的狮子Li
4e0d946676 update 优化 删除后端不存在的搜索条件 2025-06-27 16:25:14 +08:00
疯狂的狮子Li
71dceeacc2 update 优化 删除展开折叠按钮 菜单数据量大的清空下 展开会导致页面卡顿问题(在懒加载数据的清空下这个功能不推荐使用了) 2025-06-24 11:08:10 +08:00
疯狂的狮子Li
d59259737f !219 fix 修复菜单改为懒加载后,修改数据没有刷新的问题
Merge pull request !219 from Lau/dev
2025-06-24 02:14:01 +00:00
lau
8afe7c3931 fix: 修复菜单改为懒加载后,修改数据没有刷新的问题 2025-06-24 09:25:13 +08:00
疯狂的狮子Li
d59738b473 !217 update: 优化菜单页面渲染方式避免长时间卡住
Merge pull request !217 from Lau/dev
2025-06-23 08:55:19 +00:00
lau
2f35342782 fix: 修复菜单管理改为懒加载后展开/折叠只能展开一级菜单的问题 2025-06-23 16:11:58 +08:00
lau
720c822bb3 update: 优化菜单页面渲染方式避免长时间卡住 2025-06-22 21:25:05 +08:00
疯狂的狮子Li
48b5d595df fix 修复 从无缓存页面切换到有缓存页面 缓存失效问题 2025-06-16 13:49:24 +08:00
疯狂的狮子Li
1034399fe4 update 优化 租户套餐菜单查询过滤掉 租户管理相关菜单 2025-06-05 18:28:19 +08:00
疯狂的狮子Li
ba257e2357 fix 修复 提交组件变量名使用错误 2025-06-04 09:57:25 +08:00
疯狂的狮子Li
8bd26758dd !214 fix: 修复父组件中UserSelect回调处理逻辑,解决取消选择后参数未正确处理的问题
Merge pull request !214 from burningimlam/dev
2025-06-03 10:00:26 +00:00
imlam
1f1cd489be fix: 修复父组件中UserSelect回调处理逻辑,解决取消选择后参数未正确处理的问题 2025-06-03 17:26:56 +08:00
疯狂的狮子Li
2dc094c1db !212 发布 5.4.0-2.4.0 正式版
Merge pull request !212 from 疯狂的狮子Li/dev
2025-05-29 03:18:30 +00:00
疯狂的狮子Li
9c528d9a8c Merge branch 'ts' of gitee.com:JavaLionLi/plus-ui into dev
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2025-05-29 03:18:17 +00:00
疯狂的狮子Li
0472b823e7 🐳🐳🐳发布 5.4.0-2.4.0 正式版 2025-05-29 11:16:20 +08:00
疯狂的狮子Li
f71cf3cfb4 !210 update 优化用户管理查询条件
Merge pull request !210 from AprilWind/dev
2025-05-29 02:32:42 +00:00
AprilWind
8179ee8196 update 优化用户管理查询条件 2025-05-29 09:56:22 +08:00
gssong
8b8099ad09 update 调整流程图渲染样式 2025-05-27 22:28:28 +08:00
gssong
fd30362267 remove 删除无用代码 2025-05-27 22:23:18 +08:00
gssong
1878f49e8d update 调整查询流程图渲染空指针错误,优化流程图样式 2025-05-27 22:20:30 +08:00
疯狂的狮子Li
ca0fe5ebae update 删除logicflow依赖与文件 直接使用warmflow自带的页面 2025-05-27 17:14:28 +08:00
疯狂的狮子Li
ba78f8cc0d !209 新增通过前端显示流程图方式和新增办理人转换接口
Merge pull request !209 from 晓华/dev
2025-05-27 09:02:28 +00:00
warm
a614dee5c6 feat 新增通过前端显示流程图方式
feat 办理人权限处理器,新增办理人转换接口,比如角色转用户
update 升级warm-flow-1.7.3-m1
2025-05-27 16:58:18 +08:00
疯狂的狮子Li
463faba9b9 update 优化 替换过期的写法 2025-05-27 14:52:10 +08:00
疯狂的狮子Li
9dea8369e3 fix 修复 菜单取消按钮不工作问题 2025-05-27 14:47:22 +08:00
疯狂的狮子Li
592fb84aa7 update 优化 添加页签图标显示开关功能 2025-05-27 14:13:45 +08:00
gssong
3019701856 add 增加logicflow流程图预览 2025-05-25 11:47:09 +08:00
gssong
1ea70dd3ce update 表格增加border 2025-05-24 13:13:45 +08:00
gssong
2a5ad70155 update 修改菜单级联删除弹窗无法取消 2025-05-24 13:06:04 +08:00
疯狂的狮子Li
9c8e3404bb !207 feat 新增批量级联删除菜单接口
Merge pull request !207 from 马铃薯头/dev
2025-05-23 10:04:52 +00:00
xlsea
385bbb77a9 feat 新增批量级联删除菜单接口 2025-05-23 14:03:03 +08:00
疯狂的狮子Li
70f7c06e55 update 优化 动态路由迁移到菜单管理 2025-05-22 18:15:02 +08:00
gssong
369f48ced5 update 优化审批按钮,封装成公共组件 2025-05-16 21:00:48 +08:00
疯狂的狮子Li
7f15f0e15a update 优化 执行eslint:fix优化代码 2025-05-15 14:52:41 +08:00
疯狂的狮子Li
7b48bd44a2 !203 修改navbar中消息图标样式与同行元素保持一致
Merge pull request !203 from 愿丶/dev
2025-05-12 02:03:58 +00:00
疯狂的狮子Li
7affcd27b7 update 更新 readme 增加新成员项目 2025-05-12 09:34:40 +08:00
疯狂的狮子Li
7de9f23226 update README.md.
Signed-off-by: 疯狂的狮子Li <15040126243@163.com>
2025-05-08 14:24:37 +00:00
疯狂的狮子Li
afc35feb8c update 更新 readme 2025-05-08 22:11:04 +08:00
愿丶
84d682a4a2 update 修改navbar中消息图标样式与同行元素保持一致
Signed-off-by: 愿丶 <1319542051@qq.com>
2025-04-29 13:06:34 +00:00
gssong
b29c5bd2fa update 放开申请人附件与抄送限制 附件改为按钮权限控制 2025-04-26 12:52:52 +08:00
疯狂的狮子Li
e4c24a511a !202 [任务]: 工作流分类与流程设计新增联动
Merge pull request !202 from MoMyles/dev
2025-04-24 03:52:05 +00:00
疯狂的狮子Li
9955a52059 update 优化 增加oss站点与域名 默认前缀避免填错 2025-04-24 11:36:51 +08:00
疯狂的狮子Li
de22609196 update 升级全部依赖版本
update element-plus 2.9.8
update pinia 3.0.2
update vue-router 4.5.0
update vue-types 6.0.0
update vxe-table 4.13.7
update sass 1.87.0
update typescript 5.8.3
update vite 6.3.2
........等等很多版本
2025-04-22 15:23:36 +08:00
疯狂的狮子Li
e5de3f4e9d update 优化代码 2025-04-22 13:32:15 +08:00
疯狂的狮子Li
660d5b3d4f update 优化 关于excel说明 2025-04-18 17:47:55 +08:00
疯狂的狮子Li
d5eac17097 update 优化 角色禁用不允许分配 2025-04-17 15:19:58 +08:00
疯狂的狮子Li
6fe2317681 update vite 5.4.11 => 5.4.18 2025-04-16 15:28:14 +08:00
Myles
e9e8a2eaaf 优化赋值 2025-04-14 13:14:57 +08:00
疯狂的狮子Li
5ec984ac7d update 增加 流程变量注释 2025-04-11 15:32:27 +08:00
Myles
c98a14e2ac if添加括号 2025-04-10 15:41:30 +08:00
Myles
bbc656a26c [任务]: 工作流分类与流程设计新增联动 2025-04-10 11:33:12 +08:00
疯狂的狮子Li
722acf0ae7 update 优化 删除无用组件 2025-04-07 16:22:04 +08:00
疯狂的狮子Li
15acd995f9 fix 修复 请假时间 时间组件没法和rule规则联动问题(ele的bug手动设置必填) 2025-04-02 14:45:23 +08:00
疯狂的狮子Li
35b90aa746 fix 修复 请假提交未取消按钮loading问题 2025-04-02 14:37:32 +08:00
疯狂的狮子Li
de926211ef update 优化 登出之后清理tabs 2025-03-31 09:40:09 +08:00
疯狂的狮子Li
30e1ea1c6d fix 修复 前端download方法响应json异常问题 2025-03-28 22:51:01 +08:00
疯狂的狮子Li
8fa765f7be !199 发布 5.3.1-2.3.0 正式版
Merge pull request !199 from 疯狂的狮子Li/dev
2025-03-27 02:54:40 +00:00
疯狂的狮子Li
7321b4c4ca 🐳发布 5.3.1-2.3.0 正式版 2025-03-27 10:48:24 +08:00
疯狂的狮子Li
85608594bc update 删除无用组件 2025-03-27 10:37:09 +08:00
疯狂的狮子Li
fc8b795bd9 !198 漏洞扫描出现yui2.9.0版本漏洞fix
Merge pull request !198 from dxldxl/dev
2025-03-27 02:35:48 +00:00
dxldxl
3a7fad80a8 漏洞扫描出现yui2.9.0版本漏洞fix 2025-03-27 10:10:50 +08:00
疯狂的狮子Li
f5d557fe80 !197 发布 5.3.1-BETA2_2.3.0-BETA2 公测版本
Merge pull request !197 from 疯狂的狮子Li/dev
2025-03-21 07:26:30 +00:00
疯狂的狮子Li
8bff98ac72 发布 5.3.1-BETA2_2.3.0-BETA2 公测版本 2025-03-21 15:19:37 +08:00
疯狂的狮子Li
8fadab9741 update 优化 删除无用代码 2025-03-19 16:42:38 +08:00
疯狂的狮子Li
fba121f5c3 update 优化 findPathNum 方法 更高效 2025-03-19 10:30:14 +08:00
疯狂的狮子Li
48e9f2c5c0 update 优化 登录与注册页面表头从配置文件内导入 2025-03-18 17:45:01 +08:00
疯狂的狮子Li
d8a395bfd1 update 优化 简化代码 2025-03-17 09:36:39 +08:00
gssong
597c8370d3 update 补充参数 2025-03-14 23:41:26 +08:00
疯狂的狮子Li
6b2838141a update 优化 如果不存在属性 则做兼容 2025-03-14 16:14:57 +08:00
疯狂的狮子Li
b3edce5a20 !196 update 统一流程定义编码,增加流程分类标识
Merge pull request !196 from AprilWind/dev
2025-03-14 02:28:10 +00:00
AprilWind
53424765f9 update 统一流程定义编码,增加流程分类标识 2025-03-14 10:25:16 +08:00
疯狂的狮子Li
b23b123613 !195 发布 5.3.1-BETA_2.3.0-BETA 公测版本
Merge pull request !195 from 疯狂的狮子Li/dev
2025-03-13 05:30:32 +00:00
疯狂的狮子Li
3eab423da5 发布 5.3.1-BETA_2.3.0-BETA 公测版本 2025-03-13 13:29:38 +08:00
疯狂的狮子Li
2e627832ae update 优化 删除不应该传的参数 2025-03-13 11:55:24 +08:00
疯狂的狮子Li
8b5cf9a35f update 优化 代码 2025-03-13 11:47:49 +08:00
疯狂的狮子Li
78798f99ea !194 chore: 升级svg-icons-ng版本
Merge pull request !194 from yangxu52/feat/update-deps
2025-03-12 07:28:39 +00:00
yangxu52
57f288c892 chore: 升级svg-icons-ng版本 2025-03-12 15:20:16 +08:00
疯狂的狮子Li
0815fa2978 fix 修复 pr书写错误问题 2025-03-12 12:14:51 +08:00
疯狂的狮子Li
7feead9afc update 优化代码 统一store用法 2025-03-12 12:08:29 +08:00
gssong
98728828ad add 增加示例 调整提交组件 2025-03-08 00:09:46 +08:00
疯狂的狮子Li
d9218fac24 update 优化代码 2025-03-07 14:31:17 +08:00
疯狂的狮子Li
6d290785ef fix 修复 idea 误报类型异常问题 2025-03-07 14:15:23 +08:00
疯狂的狮子Li
f6400f3cbe fix 修复 pr导致的代码报错 2025-03-07 13:28:25 +08:00
疯狂的狮子Li
9d7f3101b9 !192 chore: 标准化tsconfig和优化postcss配置
Merge pull request !192 from yangxu52/feture/standardize
2025-03-07 05:19:20 +00:00
yangxu52
7784709cae chore: 优化postcss配置
- 移除autoprefixer的浏览器版本覆盖,使用package.json来定义,
- browerserlist定义对齐vite的构建目标 https://vite.dev/config/build-options.html#build-target
- atRule中charset已经限定配置charset,判断多余
- 顺手删了~路径别名,此项未使用,且tsconfig也没配置
2025-03-07 12:24:03 +08:00
yangxu52
b5a4ebe2f6 chore: 标准化tsconfig,并改了错误的$schema 2025-03-07 12:17:41 +08:00
疯狂的狮子Li
b814fb5105 !190 chore: 统一style标签的字段顺序
Merge pull request !190 from yangxu52/feat/standardize
2025-03-06 16:26:11 +00:00
yangxu52
e98d43f50d chore: 统一style标签的字段顺序
<style lang="scss" scope> **部分全局作用域未添加scope**
2025-03-07 00:02:15 +08:00
yangxu52
71b9d5d468 chore: 统一script标签的字段顺序
<script setup lang="ts">
2025-03-07 00:01:37 +08:00
yangxu52
e341b45429 chore: 移除冗余的类型声明
使用defineConfig自带类型推断,无需单独声明类型
2025-03-06 23:54:06 +08:00
疯狂的狮子Li
b2219cabec update 优化代码 2025-03-06 23:13:35 +08:00
疯狂的狮子Li
abc6e4f454 update 优化代码 2025-03-06 23:08:06 +08:00
疯狂的狮子Li
b1b63ebf7f !189 chore: 修改svg-icon配置以启用svgo优化
Merge pull request !189 from yangxu52/feat/fix-svgo-optimize
2025-03-06 14:59:17 +00:00
yangxu52
acc760a20f chore: 修改svg-icon配置以启用svgo优化
- /src/assets/icons/build.svg导致svgo优化失败
 - 现已修复,故启用svgo(默认即启用,故移除覆盖的`false`)
2025-03-06 22:45:42 +08:00
yangxu52
62e3351bc7 fix: 删除多余的闭标签</path> 2025-03-06 22:44:50 +08:00
gssong
4fe828faa4 add 增加选人判断 2025-03-06 22:23:12 +08:00
gssong
21466ca8a1 Merge branch 'dev' of https://gitee.com/JavaLionLi/plus-ui into dev 2025-03-06 20:58:03 +08:00
gssong
21234379b3 update 调整选人警告 2025-03-06 20:57:49 +08:00
疯狂的狮子Li
44bf7e7212 fix 修复 按钮权限类型书写错误 2025-03-06 16:28:53 +08:00
疯狂的狮子Li
e91d11876f update 优化 增加自动导入函数 2025-03-06 11:25:25 +08:00
疯狂的狮子Li
a90f38734f update 优化 增加 编译文件忽略 避免大批量的git记录出现 2025-03-06 10:35:59 +08:00
疯狂的狮子Li
761f97e143 !188 chore: 替换svg-icons插件
Merge pull request !188 from yangxu52/feat/replace-svg-icons-plugin
2025-03-06 02:16:30 +00:00
yangxu52
bfcffc50e2 chore: 替换svg-icons插件
替换vite-plugin-svg-icons插件, 以修复依赖警告、安全漏洞警告
 - 替换vite-plugin-svg-icons为vite-plugin-svg-icons-ng
 - 移除vite-plugin-svg-icons的依赖fast-glob
 - 调整插件的导入,并修改svgo优化参数配置项
2025-03-05 23:57:11 +08:00
gssong
07b8bc65ec update 调整选择审批人 2025-03-05 22:37:15 +08:00
gssong
f241c187b3 Merge branch 'dev' of https://gitee.com/JavaLionLi/plus-ui into dev 2025-03-05 22:26:55 +08:00
gssong
ef535a3f33 add 添加设置下一审批人 2025-03-05 22:26:46 +08:00
疯狂的狮子Li
a01545bc84 !187 perf: 上传组件添加accept属性
Merge pull request !187 from lu_ming/dev
2025-03-05 03:32:32 +00:00
can
bdaddb4bf6 perf: 上传组件添加accept属性 2025-03-05 11:25:39 +08:00
疯狂的狮子Li
ace672dd0c update 优化 页面样式 2025-03-05 09:52:48 +08:00
gssong
0539fa3c1f add 增加弹窗选人 2025-03-04 21:48:27 +08:00
疯狂的狮子Li
18f89055e1 fix 修复 图片组件变量错误 2025-03-04 15:01:52 +08:00
疯狂的狮子Li
7a8620c994 fix 修复 变量漏改 2025-03-04 10:05:25 +08:00
疯狂的狮子Li
e38d286c11 update 优化 标注node与npm版本 2025-03-03 14:25:13 +08:00
疯狂的狮子Li
05e7e93cf1 update 优化 顶部菜单搜索栏为多层级显示 2025-03-03 13:47:52 +08:00
疯狂的狮子Li
f2adc5e5fa update 优化 前端处理路由函数代码 2025-03-03 13:47:41 +08:00
疯狂的狮子Li
7be0723c31 update 优化 优化前端树结构拼接性能 2025-03-03 13:41:33 +08:00
疯狂的狮子Li
eab4345198 update 优化 分页组件样式 pagination更换成flex布局 2025-03-03 13:31:05 +08:00
疯狂的狮子Li
e040820dae update 优化 文件上传增加禁用按钮 增加文件类型 2025-03-03 13:29:15 +08:00
gssong
61b81f4692 add 增加按钮权限 2025-03-01 23:46:46 +08:00
LiuHao
aef5a02097 update 升级部分依赖,优化eslint语法以及scss语法 2025-02-25 13:30:57 +08:00
疯狂的狮子Li
cc38d23d14 update 优化 更改版权信息2025 2025-02-21 20:32:29 +08:00
疯狂的狮子Li
74c29dc13e fix 修复 拼写错误 2025-02-21 17:07:23 +08:00
疯狂的狮子Li
b474a1cffc update vueuse 11.3 => 12.7 2025-02-21 10:18:04 +08:00
疯狂的狮子Li
e3219c434a fix 修复 el-dropdown-item 标签无法使用 v-has-permi自定义标签 问题 2025-02-20 10:37:25 +08:00
疯狂的狮子Li
c28fbdfb27 update 优化 调整注释 2025-02-13 16:57:29 +08:00
疯狂的狮子Li
ef19e97109 update 优化 删除已经没有实际作用的依赖 2025-02-11 09:53:14 +08:00
疯狂的狮子Li
bb90dbc35b fix 修复 代码生成 下拉框选项没法清空问题 2025-02-09 22:14:42 +08:00
疯狂的狮子Li
770861ed33 update 优化 调整客户端管理 label长度 2025-02-08 16:08:23 +08:00
疯狂的狮子Li
edf9529a10 !184 update src/views/workflow/processDefinition/index.vue. 修复【表单路径】prop错误
Merge pull request !184 from JiaoYue/N/A
2025-02-08 07:43:54 +00:00
JiaoYue
a11679dc0d update src/views/workflow/processDefinition/index.vue. 修复【表单路径】prop错误
Fix:修复【表单路径】prop错误

Signed-off-by: JiaoYue <502583281@qq.com>
2025-02-08 07:41:34 +00:00
疯狂的狮子Li
4d6a1ee73b update 优化 增加配置注释 2025-02-07 18:35:15 +08:00
疯狂的狮子Li
b43158914b Revert "update 优化 删除无用配置"
This reverts commit 66580a05a8.
2025-02-07 09:27:59 +00:00
疯狂的狮子Li
66580a05a8 update 优化 删除无用配置 2025-02-07 16:56:37 +08:00
疯狂的狮子Li
dccfa219d7 update 优化 删除无用配置 2025-02-07 15:20:21 +08:00
疯狂的狮子Li
b1f0b3c096 !183 同步修复一些问题
Merge pull request !183 from 疯狂的狮子Li/dev
2025-02-07 06:20:16 +00:00
疯狂的狮子Li
b95a49c7d7 reset 回滚有问题的修改 2025-02-07 13:08:34 +08:00
疯狂的狮子Li
2155d9f4b0 !181 fix 修复 路由守卫白名单通配符正则覆盖问题
Merge pull request !181 from QianRj/dev
2025-02-07 03:23:51 +00:00
QianRj
fb7bca27eb fix 修复 路由守卫白名单通配符正则覆盖问题 2025-02-06 20:20:40 +08:00
疯狂的狮子Li
904ee32b24 !180 fix 修复 消息弹框内容过长不换行
Merge pull request !180 from zst_2001/dev
2025-02-05 01:19:53 +00:00
zst_2001
4839a5152f fix 修复 消息弹框内容过长不换行 2025-02-04 21:11:48 +08:00
疯狂的狮子Li
c454efd713 !177 错别字修改
Merge pull request !177 from WeiHan/dev
2025-01-24 07:15:17 +00:00
WeiHan
e8bbe0ac15 update src/components/Process/approvalRecord.vue.
Signed-off-by: WeiHan <1844152414@qq.com>
2025-01-24 07:10:25 +00:00
157 changed files with 4017 additions and 2289 deletions

View File

@@ -1,5 +1,6 @@
# 页面标题
VITE_APP_TITLE = RuoYi-Vue-Plus多租户管理系统
VITE_APP_LOGO_TITLE = RuoYi-Vue-Plus
# 开发环境配置
VITE_APP_ENV = 'development'
@@ -11,7 +12,7 @@ VITE_APP_BASE_API = '/dev-api'
VITE_APP_CONTEXT_PATH = '/'
# 监控地址
VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/applications'
VITE_APP_MONITOR_ADMIN = 'http://localhost:9090/admin/applications'
# SnailJob 控制台地址
VITE_APP_SNAILJOB_ADMIN = 'http://localhost:8800/snail-job'

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

@@ -1,46 +1,28 @@
{
"globals": {
"ComponentInternalInstance": true,
"TransferKey": true,
"ElFormRules": true,
"CheckboxValueType": true,
"PropType": true,
"DateModelType": true,
"UploadFile": true,
"ElFormInstance": true,
"ElTableInstance": true,
"ElTreeInstance": true,
"ElTreeSelectInstance": true,
"ElSelectInstance": true,
"ElUploadInstance": true,
"ElCardInstance": true,
"ElDialogInstance": true,
"ElInputInstance": true,
"ElInputNumberInstance": true,
"ElRadioInstance": true,
"ElRadioGroupInstance": true,
"ElRadioButtonInstance": true,
"ElCheckboxInstance": true,
"ElCheckboxGroupInstance": true,
"ElSwitchInstance": true,
"ElDatePickerInstance": true,
"ElTimePickerInstance": true,
"ElTimeSelectInstance": true,
"ElScrollbarInstance": true,
"ElCascaderInstance": true,
"ElColorPickerInstance": true,
"ElRateInstance": true,
"ElSliderInstance": true,
"useRouter": true,
"useRoute": true,
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"DirectiveBinding": true,
"EffectScope": true,
"ElTable": true,
"ElSelect": true,
"ElUpload": true,
"ElForm": true,
"ElTree": true,
"ElLoading": true,
"ElMessage": true,
"ElMessageBox": true,
"ElNotification": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"MaybeRef": true,
"MaybeRefOrGetter": true,
"PropType": true,
"Ref": true,
"ShallowRef": true,
"Slot": true,
"Slots": true,
"VNode": true,
"WritableComputedRef": true,
"acceptHMRUpdate": true,
"asyncComputed": true,
"autoResetRef": true,
"computed": true,
@@ -54,36 +36,53 @@
"createEventHook": true,
"createGlobalState": true,
"createInjectionState": true,
"createPinia": true,
"createReactiveFn": true,
"createRef": true,
"createReusableTemplate": true,
"createSharedComposable": true,
"createTemplatePromise": true,
"createUnrefFn": true,
"customRef": true,
"debouncedRef": true,
"debouncedWatch": true,
"defineAsyncComponent": true,
"defineComponent": true,
"defineStore": true,
"eagerComputed": true,
"effectScope": true,
"extendRef": true,
"getActivePinia": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"getCurrentWatcher": true,
"h": true,
"ignorableWatch": true,
"inject": true,
"injectLocal": true,
"isDefined": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"isShallow": true,
"makeDestructurable": true,
"mapActions": true,
"mapGetters": true,
"mapState": true,
"mapStores": true,
"mapWritableState": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeRouteLeave": true,
"onBeforeRouteUpdate": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onClickOutside": true,
"onDeactivated": true,
"onElementRemoval": true,
"onErrorCaptured": true,
"onKeyStroke": true,
"onLongPress": true,
@@ -95,8 +94,10 @@
"onStartTyping": true,
"onUnmounted": true,
"onUpdated": true,
"onWatcherCleanup": true,
"pausableWatch": true,
"provide": true,
"provideLocal": true,
"reactify": true,
"reactifyObject": true,
"reactive": true,
@@ -108,15 +109,17 @@
"refAutoReset": true,
"refDebounced": true,
"refDefault": true,
"refManualReset": true,
"refThrottled": true,
"refWithControl": true,
"resolveComponent": true,
"resolveDirective": true,
"resolveRef": true,
"resolveUnref": true,
"setActivePinia": true,
"setMapStoreSuffix": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"storeToRefs": true,
"syncRef": true,
"syncRefs": true,
"templateRef": true,
@@ -126,6 +129,7 @@
"toReactive": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"tryOnBeforeMount": true,
"tryOnBeforeUnmount": true,
@@ -136,11 +140,14 @@
"unrefElement": true,
"until": true,
"useActiveElement": true,
"useAnimate": true,
"useArrayDifference": true,
"useArrayEvery": true,
"useArrayFilter": true,
"useArrayFind": true,
"useArrayFindIndex": true,
"useArrayFindLast": true,
"useArrayIncludes": true,
"useArrayJoin": true,
"useArrayMap": true,
"useArrayReduce": true,
@@ -157,11 +164,14 @@
"useBrowserLocation": true,
"useCached": true,
"useClipboard": true,
"useClipboardItems": true,
"useCloned": true,
"useColorMode": true,
"useConfirmDialog": true,
"useCountdown": true,
"useCounter": true,
"useCssModule": true,
"useCssSupports": true,
"useCssVar": true,
"useCssVars": true,
"useCurrentElement": true,
@@ -198,6 +208,7 @@
"useFullscreen": true,
"useGamepad": true,
"useGeolocation": true,
"useId": true,
"useIdle": true,
"useImage": true,
"useInfiniteScroll": true,
@@ -206,6 +217,7 @@
"useIntervalFn": true,
"useKeyModifier": true,
"useLastChanged": true,
"useLink": true,
"useLocalStorage": true,
"useMagicKeys": true,
"useManualRefHistory": true,
@@ -213,6 +225,7 @@
"useMediaQuery": true,
"useMemoize": true,
"useMemory": true,
"useModel": true,
"useMounted": true,
"useMouse": true,
"useMouseInElement": true,
@@ -226,6 +239,8 @@
"useOnline": true,
"usePageLeave": true,
"useParallax": true,
"useParentElement": true,
"usePerformanceObserver": true,
"usePermission": true,
"usePointer": true,
"usePointerLock": true,
@@ -235,10 +250,14 @@
"usePreferredDark": true,
"usePreferredLanguages": true,
"usePreferredReducedMotion": true,
"usePreferredReducedTransparency": true,
"usePrevious": true,
"useRafFn": true,
"useRefHistory": true,
"useResizeObserver": true,
"useRoute": true,
"useRouter": true,
"useSSRWidth": true,
"useScreenOrientation": true,
"useScreenSafeArea": true,
"useScriptTag": true,
@@ -256,6 +275,7 @@
"useStyleTag": true,
"useSupported": true,
"useSwipe": true,
"useTemplateRef": true,
"useTemplateRefsList": true,
"useTextDirection": true,
"useTextSelection": true,
@@ -264,6 +284,7 @@
"useThrottleFn": true,
"useThrottledRefHistory": true,
"useTimeAgo": true,
"useTimeAgoIntl": true,
"useTimeout": true,
"useTimeoutFn": true,
"useTimeoutPoll": true,
@@ -291,8 +312,10 @@
"watchArray": true,
"watchAtMost": true,
"watchDebounced": true,
"watchDeep": true,
"watchEffect": true,
"watchIgnorable": true,
"watchImmediate": true,
"watchOnce": true,
"watchPausable": true,
"watchPostEffect": true,
@@ -300,13 +323,6 @@
"watchThrottled": true,
"watchTriggerable": true,
"watchWithFilter": true,
"whenever": true,
"ImportOption": true,
"TreeType": true,
"FieldOption": true,
"PageData": true,
"storeToRefs": true,
"DictDataOption": true,
"UploadOption": true
"whenever": true
}
}

View File

@@ -1,17 +1,24 @@
## 平台简介
- 本仓库为前端技术栈 [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)
- 配套后端代码仓库地址
- [RuoYi-Vue-Plus 5.X(注意版本号)](https://gitee.com/dromara/RuoYi-Vue-Plus)
- [RuoYi-Cloud-Plus 2.X(注意版本号)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
- 成员项目: 基于 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)
## 配套后端代码仓库地址
| 介绍 | 项目名 | 项目地址 |
|------------|:-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 🔥 分布式集群框架 | RuoYi-Vue-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus)<br> - [GitHub](https://github.com/dromara/RuoYi-Vue-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Vue-Plus) |
| 🔥 微服务框架 | RuoYi-Cloud-Plus | - [Gitee](https://gitee.com/dromara/RuoYi-Cloud-Plus)<br>- [GitHub](https://github.com/dromara/RuoYi-Cloud-Plus)<br> - [GitCode](https://gitcode.com/dromara/RuoYi-Cloud-Plus) |
## 分支说明
- ts分支(稳定发布主分支 生产可用)
- dev分支(开发分支 开发过程中使用)
## 前端运行
```bash
# 克隆项目
git clone https://gitee.com/JavaLionLi/plus-ui.git
# 安装依赖
npm install --registry=https://registry.npmmirror.com

View File

@@ -1,86 +0,0 @@
import globals from 'globals';
import pluginJs from '@eslint/js';
import tseslint from 'typescript-eslint';
import pluginVue from 'eslint-plugin-vue';
import { readFile } from 'node:fs/promises';
import prettier from 'eslint-plugin-prettier';
/**
* https://blog.csdn.net/sayUonly/article/details/123482912
* 自动导入的配置
*/
const autoImportFile = new URL('./.eslintrc-auto-import.json', import.meta.url);
const autoImportGlobals = JSON.parse(await readFile(autoImportFile, 'utf8'));
/** @type {import('eslint').Linter.Config[]} */
export default [
{
/**
* 不需要.eslintignore文件 而是在这里配置
*/
ignores: [
'*.sh',
'node_modules',
'*.md',
'*.woff',
'*.ttf',
'.vscode',
'.idea',
'dist',
'/public',
'/docs',
'.husky',
'.local',
'/bin',
'.eslintrc.cjs',
'prettier.config.js',
'src/assets',
'tailwind.config.js'
]
},
{ files: ['**/*.{js,mjs,cjs,ts,vue}'] },
{
languageOptions: {
globals: globals.browser
}
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/essential'],
{
files: ['**/*.vue'],
languageOptions: {
parserOptions: {
parser: tseslint.parser
}
}
},
{
languageOptions: {
globals: {
// 自动导入的配置 undef
...autoImportGlobals.globals,
DialogOption: 'readonly',
LayoutSetting: 'readonly'
}
},
plugins: { prettier },
rules: {
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-this-alias': 'off',
// vue
'vue/multi-word-component-names': 'off',
'vue/valid-define-props': 'off',
'vue/no-v-model-argument': 'off',
'prefer-rest-params': 'off',
// prettier
'prettier/prettier': 'error',
// 允许使用空Object类型 {}
'@typescript-eslint/no-empty-object-type': 'off',
'@typescript-eslint/no-unused-expressions': 'off'
}
}
];

44
eslint.config.ts Normal file
View File

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

View File

@@ -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/tsconfig",
"$schema": "https://json.schemastore.org/package",
"name": "ruoyi-vue-plus",
"version": "5.3.0",
"version": "5.6.0-2.6.0",
"description": "RuoYi-Vue-Plus多租户管理系统",
"author": "LionLi",
"license": "MIT",
@@ -20,68 +20,76 @@
"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": "11.3.0",
"@vueuse/core": "14.2.1",
"animate.css": "4.1.1",
"await-to-js": "3.0.0",
"axios": "1.7.8",
"axios": "1.13.6",
"crypto-js": "4.2.0",
"diagram-js": "12.3.0",
"didi": "9.0.2",
"echarts": "5.5.0",
"element-plus": "2.8.8",
"echarts": "6.0.0",
"element-plus": "2.13.5",
"file-saver": "2.0.5",
"fuse.js": "7.0.0",
"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": "2.2.6",
"pinia": "3.0.4",
"screenfull": "6.0.2",
"vue": "3.5.13",
"vue-cropper": "1.1.1",
"vue-i18n": "10.0.5",
"vue-json-pretty": "2.4.0",
"vue-router": "4.4.5",
"vue-types": "5.1.3",
"vxe-table": "4.5.22"
"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.18.1"
},
"devDependencies": {
"@eslint/js": "9.15.0",
"@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": "18.18.2",
"@types/node": "^25.4.0",
"@types/nprogress": "0.2.3",
"@unocss/preset-attributify": "0.64.1",
"@unocss/preset-icons": "0.64.1",
"@unocss/preset-uno": "0.64.1",
"@vitejs/plugin-vue": "5.0.4",
"@vue/compiler-sfc": "3.4.23",
"autoprefixer": "10.4.18",
"eslint": "9.15.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "9.31.0",
"fast-glob": "3.3.2",
"globals": "15.12.0",
"postcss": "8.4.36",
"prettier": "3.2.5",
"sass": "1.72.0",
"typescript": "5.7.2",
"typescript-eslint": "8.16.0",
"unocss": "0.64.1",
"unplugin-auto-import": "0.17.5",
"unplugin-icons": "0.18.5",
"unplugin-vue-components": "0.26.0",
"@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.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": "5.4.11",
"vite-plugin-compression": "0.5.1",
"vite-plugin-svg-icons": "2.0.1",
"vitest": "1.5.0",
"vue-tsc": "2.0.13"
}
"vite": "7.3.1",
"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": "1.3.7"
},
"engines": {
"node": ">=20.19.0",
"npm": ">=8.19.0"
},
"browserslist": [
"Chrome >= 87",
"Edge >= 88",
"Safari >= 14",
"Firefox >= 78"
]
}

View File

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

View File

@@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import {DeptForm, DeptQuery, DeptTreeVO, DeptVO} from './types';
import { DeptForm, DeptQuery, DeptTreeVO, DeptVO } from './types';
// 查询部门列表
export const listDept = (query?: DeptQuery) => {
@@ -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

@@ -68,3 +68,11 @@ export const delMenu = (menuId: string | number) => {
method: 'delete'
});
};
// 级联删除菜单
export const cascadeDelMenu = (menuIds: Array<string | number>) => {
return request({
url: '/system/menu/cascade/' + menuIds,
method: 'delete'
});
};

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

@@ -15,10 +15,12 @@ export interface UserInfo {
*/
export interface UserQuery extends PageQuery {
userName?: string;
nickName?: string;
phonenumber?: string;
status?: string;
deptId?: string | number;
roleId?: string | number;
userIds?: string | number | (string | number)[] | undefined;
}
/**

View File

@@ -6,7 +6,6 @@ export interface CategoryTreeVO {
children: CategoryTreeVO[];
}
export interface CategoryVO {
/**
* 流程分类ID
*/
@@ -39,7 +38,6 @@ export interface CategoryVO {
}
export interface CategoryForm extends BaseEntity {
/**
* 流程分类ID
*/
@@ -59,14 +57,11 @@ export interface CategoryForm extends BaseEntity {
* 显示顺序
*/
orderNum?: number;
}
export interface CategoryQuery {
/**
* 流程分类名称
*/
categoryName?: string;
}

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

@@ -31,9 +31,9 @@ export const pageByFinish = (query: FlowInstanceQuery): AxiosPromise<FlowInstanc
/**
* 通过业务id获取历史流程图
*/
export const flowImage = (businessId: string | number) => {
export const flowHisTaskList = (businessId: string | number) => {
return request({
url: `/workflow/instance/flowImage/${businessId}` + '?t' + Math.random(),
url: `/workflow/instance/flowHisTaskList/${businessId}` + '?t' + Math.random(),
method: 'get'
});
};
@@ -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'
});
};
@@ -178,3 +178,29 @@ export const currentTaskAllUser = (taskId: string | number) => {
method: 'get'
});
};
/**
* 获取下一节点写
* @param data参数
* @returns
*/
export const getNextNodeList = (data: any): any => {
return request({
url: '/workflow/task/getNextNodeList',
method: 'post',
data: data
});
};
/**
* 催办任务
* @param data参数
* @returns
*/
export const urgeTask = (data: any): any => {
return request({
url: '/workflow/task/urgeTask',
method: 'post',
data: data
});
};

View File

@@ -29,11 +29,21 @@ export interface FlowTaskVO {
nodeType: number;
nodeRatio: string | number;
version?: string;
applyNode?: boolean;
buttonList?: ButtonList[];
copyList?: FlowCopyVo[];
varList?: Map<string, string>;
businessCode: string;
businessTitle: string;
}
export interface VariableVo {
key: string;
value: string;
export interface ButtonList {
code: string;
show: boolean;
}
export interface FlowCopyVo {
userId: string | number;
nickName: string;
}
export interface TaskOperationBo {
@@ -43,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

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

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,4 +1,4 @@
@import './variables.module.scss';
@use './variables.module.scss' as *;
@mixin colorBtn($color) {
background: $color;
@@ -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-lg);
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;
}
@@ -146,3 +151,127 @@
.el-form--inline .el-input {
width: 240px;
}
/* 设置 el-message-box 消息弹框内容强制换行 */
.el-message-box .el-message-box__message {
word-break: break-word;
}
.el-message-box {
border-radius: var(--app-radius-lg);
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-lg);
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-lg);
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

@@ -1,12 +1,12 @@
@import './variables.module.scss';
@import './mixin.scss';
@import './transition.scss';
@import './element-ui.scss';
@import './sidebar.scss';
@import './btn.scss';
@import './ruoyi.scss';
@import 'animate.css';
@import 'element-plus/dist/index.css';
@use './variables.module.scss' as *;
@use './mixin.scss';
@use './transition.scss';
@use './element-ui.scss';
@use './sidebar.scss';
@use './btn.scss';
@use './ruoyi.scss';
@use 'animate.css';
@use 'element-plus/dist/index.css';
body {
height: 100%;
@@ -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,27 @@ aside {
//main-container全局样式
.app-container {
padding: 20px;
background: var(--app-surface-bg);
border: 1px solid var(--app-surface-border);
border-radius: var(--app-radius-lg);
box-shadow: var(--app-shadow-sm);
}
// search面板样式
.panel,
.search {
margin-bottom: 0.75rem;
border-radius: 0.25rem;
border-radius: var(--app-radius-lg);
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);
}
}
@@ -162,10 +177,6 @@ aside {
position: relative;
}
.pagination-container {
margin-top: 30px;
}
.text-center {
text-align: center;
}

View File

@@ -1,3 +1,5 @@
@use './variables.module.scss' as *;
/**
* 通用css样式布局处理
* Copyright (c) 2019 ruoyi
@@ -72,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;
@@ -114,10 +116,9 @@ h6 {
/** 表格布局 **/
.pagination-container {
// position: relative;
height: 25px;
margin-bottom: 10px;
margin-top: 15px;
display: flex;
justify-content: flex-end;
margin-top: 20px;
padding: 10px 20px !important;
}
@@ -126,15 +127,10 @@ h6 {
margin-top: 5px;
border: 1px solid #e5e6e7;
background: #ffffff none;
border-radius: 4px;
border-radius: var(--app-radius-md);
width: 100%;
}
.pagination-container .el-pagination {
//right: 0;
//position: absolute;
}
@media (max-width: 768px) {
.pagination-container .el-pagination > .el-pagination__jump {
display: none !important;
@@ -194,15 +190,28 @@ 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 {
padding-right: 15px;
padding-left: 15px;
margin-bottom: 10px;
}
@@ -288,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

@@ -1,9 +1,12 @@
@use './variables.module.scss' as *;
#app {
.main-container {
height: 100%;
transition: margin-left 0.28s;
margin-left: $base-sidebar-width;
position: relative;
background: var(--el-bg-color-page);
}
.sidebarHide {
@@ -24,7 +27,7 @@
z-index: 1001;
overflow: hidden;
-webkit-box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
// reset element-ui css
.horizontal-collapse-transition {
@@ -63,7 +66,7 @@
}
.svg-icon {
margin-right: 16px;
margin-right: 10px;
}
.el-menu {
@@ -86,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;
}
@@ -103,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;
}
}
@@ -112,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;
@@ -146,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 {
@@ -230,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,24 @@
// 添加 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-sm: 6px;
--app-radius-md: 10px;
--app-radius-lg: 14px;
--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 +66,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 +175,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 +282,4 @@ $base-sidebar-width: 200px;
dangerColor: $--color-danger;
infoColor: $--color-info;
warningColor: $--color-warning;
}
}

View File

@@ -11,7 +11,7 @@
<script setup lang="ts">
import { RouteLocationMatched } from 'vue-router';
import usePermissionStore from '@/store/modules/permission';
import { usePermissionStore } from '@/store/modules/permission';
const route = useRoute();
const router = useRouter();
@@ -40,16 +40,11 @@ const getBreadcrumb = () => {
levelList.value = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false);
};
const findPathNum = (str, char = '/') => {
let index = str.indexOf(char);
let num = 0;
while (index !== -1) {
num++;
index = str.indexOf(char, index + 1);
}
return num;
if (typeof str !== 'string' || str.length === 0) return 0;
return str.split(char).length - 1;
};
const getMatched = (pathList, routeList, matched) => {
let data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]);
const data = routeList.find((item) => item.path == pathList[0] || (item.name += '').toLowerCase() == pathList[0]);
if (data) {
matched.push(data);
if (data.children && pathList.length) {

View File

@@ -1,61 +0,0 @@
<template>
<!-- 代码构建 -->
<div>
<v-form-designer
ref="buildRef"
class="build"
:designer-config="{ importJsonButton: true, exportJsonButton: true, exportCodeButton: true, generateSFCButton: true, formTemplates: true }"
>
<template v-if="showBtn" #customToolButtons>
<el-button link type="primary" icon="Select" @click="getJson">保存</el-button>
</template>
</v-form-designer>
</div>
</template>
<script setup lang="ts">
interface Props {
showBtn: boolean;
formJson: any;
}
const props = withDefaults(defineProps<Props>(), {
showBtn: true,
formJson: ''
});
const buildRef = ref();
const emits = defineEmits(['reJson', 'saveDesign']);
//获取表单json
const getJson = () => {
const formJson = JSON.stringify(buildRef.value.getFormJson());
const fieldJson = JSON.stringify(buildRef.value.getFieldWidgets());
let data = {
formJson,
fieldJson
};
emits('saveDesign', data);
};
onMounted(() => {
if (props.formJson) {
buildRef.value.setFormJson(props.formJson);
}
});
</script>
<style lang="scss">
.build {
margin: 0 !important;
overflow-y: auto !important;
& header.main-header {
display: none;
}
& .right-toolbar-con {
text-align: right !important;
}
}
</style>

View File

@@ -1,57 +0,0 @@
<template>
<div class="">
<v-form-render ref="vFormRef" :form-json="formJson" :form-data="formData" />
</div>
</template>
<!-- 动态表单渲染 -->
<script setup name="Render" lang="ts">
interface Props {
formJson: string | object;
formData: string | object;
isView: boolean;
}
const props = withDefaults(defineProps<Props>(), {
formJson: '',
formData: '',
isView: false
});
const vFormRef = ref();
// 获取表单数据-异步
const getFormData = () => {
return vFormRef.value.getFormData();
};
/**
* 设置表单内容
* @param {表单配置} formConf
* formConfig{ formTemplate表单模板formData表单数据hiddenField需要隐藏的字段字符串集合disabledField需要禁用的自读字符串集合}
*/
const initForm = (formConf: any) => {
const { formTemplate, formData, hiddenField, disabledField } = toRaw(formConf);
if (formTemplate) {
vFormRef.value.setFormJson(formTemplate);
if (formData) {
vFormRef.value.setFormData(formData);
}
if (disabledField && disabledField.length > 0) {
setTimeout(() => {
vFormRef.value.disableWidgets(disabledField);
}, 200);
}
if (hiddenField && hiddenField.length > 0) {
setTimeout(() => {
vFormRef.value.hideWidgets(hiddenField);
}, 200);
}
if (props.isView) {
setTimeout(() => {
vFormRef.value.disableForm();
}, 100);
}
}
};
defineExpose({ getFormData, initForm });
</script>

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,9 +86,13 @@ const handleArray = (array: Array<string | number>) => {
return pre + ' ' + cur;
});
};
const isValueMatch = (itemValue: any) => {
return values.value.some(val => val == itemValue)
}
</script>
<style scoped>
<style lang="scss" scoped>
.el-tag + .el-tag {
margin-left: 10px;
}

View File

@@ -1,15 +1,13 @@
<template>
<div>
<el-upload
v-if="type === 'url'"
:action="upload.url"
v-if="type"
action=""
:before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:http-request="handleUploadRequest"
class="editor-img-uploader"
name="file"
:show-file-list="false"
:headers="upload.headers"
>
<i ref="uploadRef"></i>
</el-upload>
@@ -31,7 +29,7 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { QuillEditor, Quill } from '@vueup/vue-quill';
import { propTypes } from '@/utils/propTypes';
import { globalHeaders } from '@/utils/request';
import type { UploadRequestHandler, UploadRequestOptions } from 'element-plus';
defineEmits(['update:modelValue']);
@@ -47,15 +45,11 @@ const props = defineProps({
/* 上传文件大小限制(MB) */
fileSize: propTypes.number.def(5),
/* 类型base64格式、url格式 */
type: propTypes.string.def('url')
type: propTypes.string.def('base64')
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const upload = reactive<UploadOption>({
headers: globalHeaders(),
url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
});
const quillEditorRef = ref();
const uploadRef = ref<HTMLDivElement>();
@@ -95,7 +89,7 @@ const options = ref<any>({
});
const styles = computed(() => {
let style: any = {};
const style: any = {};
if (props.minHeight) {
style.minHeight = `${props.minHeight}px`;
}
@@ -116,28 +110,9 @@ watch(
{ immediate: true }
);
// 图片上传成功返回图片地址
const handleUploadSuccess = (res: any) => {
// 如果上传成功
if (res.code === 200) {
// 获取富文本实例
let quill = toRaw(quillEditorRef.value).getQuill();
// 获取光标位置
let length = quill.selection.savedRange.index;
// 插入图片res为服务器返回的图片链接地址
quill.insertEmbed(length, 'image', res.data.url);
// 调整光标到最后
quill.setSelection(length + 1);
proxy?.$modal.closeLoading();
} else {
proxy?.$modal.msgError('图片插入失败');
proxy?.$modal.closeLoading();
}
};
// 图片上传前拦截
const handleBeforeUpload = (file: any) => {
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg'];
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg', 'image/svg+xml'];
const isJPG = type.includes(file.type);
//检验文件格式
if (!isJPG) {
@@ -156,9 +131,41 @@ const handleBeforeUpload = (file: any) => {
return true;
};
// 图片失败拦截
const handleUploadError = (err: any) => {
proxy?.$modal.msgError('上传文件失败');
// base64 模式插入图片
const handleUploadRequest: UploadRequestHandler = (options: UploadRequestOptions) => {
return new Promise<void>((resolve, reject) => {
const file = options.file as File;
const quill = toRaw(quillEditorRef.value)?.getQuill();
if (!quill) {
proxy?.$modal.msgError('编辑器未就绪');
proxy?.$modal.closeLoading();
reject(new Error('editor not ready'));
return;
}
const reader = new FileReader();
reader.onload = () => {
const base64 = reader.result as string;
const range = quill.selection?.savedRange;
const length = range ? range.index : quill.getLength();
quill.insertEmbed(length, 'image', base64);
quill.setSelection(length + 1);
proxy?.$modal.closeLoading();
options.onSuccess?.({ url: base64 });
resolve();
};
reader.onerror = () => {
proxy?.$modal.msgError('图片插入失败');
proxy?.$modal.closeLoading();
const err = Object.assign(new Error('read image failed'), {
status: 0,
method: 'POST',
url: options.action || ''
});
options.onError?.(err as any);
reject(err);
};
reader.readAsDataURL(file);
});
};
</script>

View File

@@ -7,18 +7,20 @@
:before-upload="handleBeforeUpload"
:file-list="fileList"
:limit="limit"
:accept="fileAccept"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:show-file-list="false"
:headers="headers"
class="upload-file-uploader"
v-if="!disabled"
>
<!-- 上传按钮 -->
<el-button type="primary">选取文件</el-button>
</el-upload>
<!-- 上传提示 -->
<div v-if="showTip" class="el-upload__tip">
<div v-if="showTip && !disabled" class="el-upload__tip">
请上传
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
@@ -35,7 +37,7 @@
<span class="el-icon-document"> {{ getFileName(file.name) }} </span>
</el-link>
<div class="ele-upload-list__item-content-action">
<el-button type="danger" link @click="handleDelete(index)">删除</el-button>
<el-button type="danger" v-if="!disabled" link @click="handleDelete(index)">删除</el-button>
</div>
</li>
</transition-group>
@@ -57,9 +59,11 @@ const props = defineProps({
// 大小限制(MB)
fileSize: propTypes.number.def(5),
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']),
fileType: propTypes.array.def(['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'pdf']),
// 是否显示提示
isShowTip: propTypes.bool.def(true)
isShowTip: propTypes.bool.def(true),
// 禁用组件(仅查看文件)
disabled: propTypes.bool.def(false)
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -76,6 +80,9 @@ const showTip = computed(() => props.isShowTip && (props.fileType || props.fileS
const fileUploadRef = ref<ElUploadInstance>();
// 监听 fileType 变化,更新 fileAccept
const fileAccept = computed(() => props.fileType.map((type) => `.${type}`).join(','));
watch(
() => props.modelValue,
async (val) => {
@@ -169,7 +176,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
// 删除文件
const handleDelete = (index: number) => {
let ossId = fileList.value[index].ossId;
const ossId = fileList.value[index].ossId;
delOss(ossId);
fileList.value.splice(index, 1);
emit('update:modelValue', listToString(fileList.value));
@@ -209,7 +216,7 @@ const listToString = (list: any[], separator?: string) => {
};
</script>
<style scoped lang="scss">
<style lang="scss" scoped>
.upload-file-uploader {
margin-bottom: 5px;
}

View File

@@ -1,195 +0,0 @@
<template>
<div :class="{ show: show }" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-select
ref="headerSearchSelectRef"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
remote
placeholder="Search"
class="header-search-select"
@change="change"
>
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
</el-select>
</div>
</template>
<script setup lang="ts" name="HeaderSearch">
import Fuse from 'fuse.js';
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router';
type Router = Array<{
path: string;
title: string[];
}>;
const search = ref('');
const options = ref<any>([]);
const searchPool = ref<Router>([]);
const show = ref(false);
const fuse = ref();
const headerSearchSelectRef = ref<ElSelectInstance>();
const router = useRouter();
const routes = computed(() => usePermissionStore().getRoutes());
const click = () => {
show.value = !show.value;
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus();
}
};
const close = () => {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur();
options.value = [];
show.value = false;
};
const change = (val: any) => {
const path = val.path;
const query = val.query;
if (isHttp(path)) {
// http(s):// 路径新窗口打开
const pindex = path.indexOf('http');
window.open(path.substr(pindex, path.length), '_blank');
} else {
if (query) {
router.push({ path: path, query: JSON.parse(query) });
} else {
router.push(path);
}
}
search.value = '';
options.value = [];
nextTick(() => {
show.value = false;
});
};
const initFuse = (list: Router) => {
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
minMatchCharLength: 1,
keys: [
{
name: 'title',
weight: 0.7
},
{
name: 'path',
weight: 0.3
}
]
});
};
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
const generateRoutes = (routes: RouteRecordRaw[], basePath = '', prefixTitle: string[] = []) => {
let res: Router = [];
routes.forEach((r) => {
// skip hidden router
if (!r.hidden) {
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
const data = {
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle],
query: ''
};
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title];
if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data);
}
}
if (r.query) {
data.query = r.query;
}
// recursive child routes
if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title);
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes];
}
}
}
});
return res;
};
const querySearch = (query: string) => {
if (query !== '') {
options.value = fuse.value.search(query);
} else {
options.value = [];
}
};
onMounted(() => {
searchPool.value = generateRoutes(routes.value);
});
// watchEffect(() => {
// searchPool.value = generateRoutes(routes.value)
// })
watch(show, (value) => {
if (value) {
document.body.addEventListener('click', close);
} else {
document.body.removeEventListener('click', close);
}
});
watch(searchPool, (list: Router) => {
initFuse(list);
});
</script>
<style lang="scss" scoped>
.header-search {
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
:deep(.el-input__inner) {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
}
}
</style>

View File

@@ -65,7 +65,7 @@ const selectedIcon = (iconName: string) => {
};
</script>
<style scoped lang="scss">
<style lang="scss" scoped>
.el-scrollbar {
max-height: calc(50vh - 100px) !important;
overflow-y: auto;

View File

@@ -27,7 +27,7 @@ const realSrc = computed(() => {
if (!props.src) {
return;
}
let real_src = props.src.split(',')[0];
const real_src = props.src.split(',')[0];
return real_src;
});
@@ -35,8 +35,8 @@ const realSrcList = computed(() => {
if (!props.src) {
return [];
}
let real_src_list = props.src.split(',');
let srcList: string[] = [];
const real_src_list = props.src.split(',');
const srcList: string[] = [];
real_src_list.forEach((item: string) => {
if (item.trim() === '') {
return;

View File

@@ -1,13 +1,14 @@
<template>
<div class="component-upload-image">
<el-upload
ref="imageUpload"
ref="imageUploadRef"
multiple
:action="uploadImgUrl"
list-type="picture-card"
:on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload"
:limit="limit"
:accept="fileAccept"
:on-error="handleUploadError"
:on-exceed="handleExceed"
:before-remove="handleDelete"
@@ -87,6 +88,9 @@ const showTip = computed(() => props.isShowTip && (props.fileType || props.fileS
const imageUploadRef = ref<ElUploadInstance>();
// 监听 fileType 变化,更新 fileAccept
const fileAccept = computed(() => props.fileType.map((type) => `.${type}`).join(','));
watch(
() => props.modelValue,
async (val: string) => {
@@ -185,7 +189,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
const handleDelete = (file: UploadFile): boolean => {
const findex = fileList.value.map((f) => f.name).indexOf(file.name);
if (findex > -1 && uploadList.value.length === number.value) {
let ossId = fileList.value[findex].ossId;
const ossId = fileList.value[findex].ossId;
delOss(ossId);
fileList.value.splice(findex, 1);
emit('update:modelValue', listToString(fileList.value));
@@ -221,7 +225,7 @@ const handlePictureCardPreview = (file: any) => {
const listToString = (list: any[], separator?: string) => {
let strs = '';
separator = separator || ',';
for (let i in list) {
for (const i in list) {
if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
strs += list[i].ossId + separator;
}
@@ -230,7 +234,7 @@ const listToString = (list: any[], separator?: string) => {
};
</script>
<style scoped lang="scss">
<style lang="scss" scoped>
// .el-upload--picture-card 控制加号部分
:deep(.hide .el-upload--picture-card) {
display: none;

View File

@@ -14,13 +14,7 @@
</div>
</template>
<script lang="ts">
export default {
name: 'Pagination'
};
</script>
<script setup lang="ts">
<script setup name="Pagination" lang="ts">
import { scrollTo } from '@/utils/scroll-to';
import { propTypes } from '@/utils/propTypes';
@@ -28,10 +22,7 @@ const props = defineProps({
total: propTypes.number,
page: propTypes.number.def(1),
limit: propTypes.number.def(20),
pageSizes: {
type: Array,
default: () => [10, 20, 30, 50]
},
pageSizes: { type: Array<number>, default: () => [10, 20, 30, 50] },
// 移动端页码按钮的数量端默认值5
pagerCount: propTypes.number.def(document.body.clientWidth < 992 ? 5 : 7),
layout: propTypes.string.def('total, sizes, prev, pager, next, jumper'),
@@ -77,7 +68,6 @@ function handleCurrentChange(val: number) {
<style lang="scss" scoped>
.pagination-container {
padding: 32px 16px;
.el-pagination {
float: v-bind(float);
}

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

@@ -0,0 +1,57 @@
<template>
<div style="display: flex; justify-content: space-between">
<div>
<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 />
</div>
<div>
<el-button style="float: right" @click="goBack()">返回</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
status: propTypes.string.def(''),
pageType: propTypes.string.def(''),
buttonLoading: propTypes.bool.def(false),
id: propTypes.string.def('') || propTypes.number.def(),
mode: propTypes.bool.def(false)
});
const emits = defineEmits(['submitForm', 'approvalVerifyOpen', 'handleApprovalRecord']);
//暂存,提交
const submitForm = async (type, mode) => {
emits('submitForm', type, mode);
};
//审批
const approvalVerifyOpen = async () => {
emits('approvalVerifyOpen');
};
//审批记录
const handleApprovalRecord = () => {
emits('handleApprovalRecord');
};
//校验提交按钮是否显示
const submitButtonShow = computed(() => {
return (
props.pageType === 'add' ||
(props.pageType === 'update' && props.status && (props.status === 'draft' || props.status === 'cancel' || props.status === 'back'))
);
});
//校验审批按钮是否显示
const approvalButtonShow = computed(() => {
return props.pageType === 'approval' && props.status && props.status === 'waiting';
});
//返回
const goBack = () => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
};
</script>

View File

@@ -3,21 +3,7 @@
<el-dialog v-model="visible" draggable title="审批记录" :width="props.width" :height="props.height" :close-on-click-modal="false">
<el-tabs v-model="tabActiveName" class="demo-tabs">
<el-tab-pane v-loading="loading" label="流程图" name="image" style="height: 68vh">
<div
ref="imageWrapperRef"
class="image-wrapper"
@wheel="handleMouseWheel"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseleave="handleMouseLeave"
@dblclick="resetTransform"
:style="transformStyle"
>
<el-card class="box-card">
<el-image :src="imgUrl" class="scalable-image" />
</el-card>
</div>
<flowChart :ins-id="insId" v-if="insId" />
</el-tab-pane>
<el-tab-pane v-loading="loading" label="审批信息" name="info">
<div>
@@ -42,7 +28,7 @@
<el-table-column prop="updateTime" label="结束时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
<el-table-column
prop="runDuration"
label="运行时"
label="运行时"
width="140"
:show-overflow-tooltip="true"
sortable
@@ -72,11 +58,11 @@
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { flowImage } from '@/api/workflow/instance';
<script setup lang="ts">
import { flowHisTaskList } from '@/api/workflow/instance';
import { propTypes } from '@/utils/propTypes';
import { listByIds } from '@/api/system/oss';
import FlowChart from '@/components/Process/flowChart.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { wf_task_status } = toRefs<any>(proxy?.useDict('wf_task_status'));
const props = defineProps({
@@ -87,7 +73,7 @@ const loading = ref(false);
const visible = ref(false);
const historyList = ref<Array<any>>([]);
const tabActiveName = ref('image');
const imgUrl = ref('');
const insId = ref(null);
//初始化查询审批记录
const init = async (businessId: string | number) => {
@@ -95,10 +81,10 @@ const init = async (businessId: string | number) => {
loading.value = true;
tabActiveName.value = 'image';
historyList.value = [];
flowImage(businessId).then((resp) => {
flowHisTaskList(businessId).then((resp) => {
if (resp.data) {
historyList.value = resp.data.list;
imgUrl.value = 'data:image/gif;base64,' + resp.data.image;
insId.value = resp.data.instanceId;
if (historyList.value.length > 0) {
historyList.value.forEach((item) => {
if (item.ext) {
@@ -124,109 +110,6 @@ const handleDownload = (ossId: string) => {
proxy?.$download.oss(ossId);
};
const imageWrapperRef = ref<HTMLElement | null>(null);
const scale = ref(1); // 初始缩放比例
const maxScale = 3; // 最大缩放比例
const minScale = 0.5; // 最小缩放比例
let isDragging = false;
let startX = 0;
let startY = 0;
let currentTranslateX = 0;
let currentTranslateY = 0;
const handleMouseWheel = (event: WheelEvent) => {
event.preventDefault();
let newScale = scale.value - event.deltaY / 1000;
newScale = Math.max(minScale, Math.min(newScale, maxScale));
if (newScale !== scale.value) {
scale.value = newScale;
resetDragPosition(); // 重置拖拽位置,使图片居中
}
};
const handleMouseDown = (event: MouseEvent) => {
if (scale.value > 1) {
event.preventDefault(); // 阻止默认行为,防止拖拽
isDragging = true;
startX = event.clientX;
startY = event.clientY;
}
};
const handleMouseMove = (event: MouseEvent) => {
if (!isDragging || !imageWrapperRef.value) return;
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
startX = event.clientX;
startY = event.clientY;
currentTranslateX += deltaX;
currentTranslateY += deltaY;
// 边界检测,防止图片被拖出容器
const bounds = getBounds();
if (currentTranslateX > bounds.maxTranslateX) {
currentTranslateX = bounds.maxTranslateX;
} else if (currentTranslateX < bounds.minTranslateX) {
currentTranslateX = bounds.minTranslateX;
}
if (currentTranslateY > bounds.maxTranslateY) {
currentTranslateY = bounds.maxTranslateY;
} else if (currentTranslateY < bounds.minTranslateY) {
currentTranslateY = bounds.minTranslateY;
}
applyTransform();
};
const handleMouseUp = () => {
isDragging = false;
};
const handleMouseLeave = () => {
isDragging = false;
};
const resetTransform = () => {
scale.value = 1;
currentTranslateX = 0;
currentTranslateY = 0;
applyTransform();
};
const resetDragPosition = () => {
currentTranslateX = 0;
currentTranslateY = 0;
applyTransform();
};
const applyTransform = () => {
if (imageWrapperRef.value) {
imageWrapperRef.value.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${scale.value})`;
}
};
const getBounds = () => {
if (!imageWrapperRef.value) return { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0 };
const imgRect = imageWrapperRef.value.getBoundingClientRect();
const containerRect = imageWrapperRef.value.parentElement?.getBoundingClientRect() ?? imgRect;
const minTranslateX = (containerRect.width - imgRect.width * scale.value) / 2;
const maxTranslateX = -(containerRect.width - imgRect.width * scale.value) / 2;
const minTranslateY = (containerRect.height - imgRect.height * scale.value) / 2;
const maxTranslateY = -(containerRect.height - imgRect.height * scale.value) / 2;
return { minTranslateX, maxTranslateX, minTranslateY, maxTranslateY };
};
const transformStyle = computed(() => ({
transition: isDragging ? 'none' : 'transform 0.2s ease'
}));
/**
* 对外暴露子组件方法
*/
@@ -235,46 +118,10 @@ defineExpose({
});
</script>
<style lang="scss" scoped>
.triangle {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
border-radius: 6px;
}
.triangle::after {
content: ' ';
position: absolute;
top: 8em;
right: 215px;
border: 15px solid;
border-color: transparent #fff transparent transparent;
}
.container {
:deep(.el-dialog .el-dialog__body) {
max-height: calc(100vh - 170px) !important;
min-height: calc(100vh - 170px) !important;
}
}
.image-wrapper {
width: 100%;
overflow: hidden;
position: relative;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
user-select: none; /* 禁用文本选择 */
cursor: grab; /* 设置初始鼠标指针为可拖动 */
}
.image-wrapper:active {
cursor: grabbing; /* 当正在拖动时改变鼠标指针 */
}
.scalable-image {
object-fit: contain;
width: 100%;
padding: 15px;
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<div>
<div style="height: 68vh" class="iframe-wrapper">
<iframe :src="iframeUrl" style="width: 100%; height: 100%" frameborder="0" scrolling="no" class="custom-iframe" />
</div>
</div>
</template>
<script setup lang="ts">
import { getToken } from '@/utils/auth';
// Props 定义方式变化
const props = defineProps({
insId: {
type: [String, Number],
default: null
}
});
const iframeUrl = ref('');
const baseUrl = import.meta.env.VITE_APP_BASE_API;
onMounted(async () => {
const url = baseUrl + `/warm-flow-ui/index.html?id=${props.insId}&type=FlowChart&t=${Date.now()}`;
iframeUrl.value = url + '&Authorization=Bearer ' + getToken() + '&clientid=' + import.meta.env.VITE_APP_CLIENT_ID;
});
</script>
<style scoped>
.iframe-wrapper {
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.custom-iframe {
width: 100%;
border: none;
background: transparent;
}
</style>

View File

@@ -0,0 +1,154 @@
<template>
<div
ref="imageWrapperRef"
class="image-wrapper"
@wheel="handleMouseWheel"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseleave="handleMouseLeave"
@dblclick="resetTransform"
:style="transformStyle"
>
<el-card class="box-card">
<el-image :src="props.imgUrl" class="scalable-image" />
</el-card>
</div>
</template>
<script setup lang="ts">
// Props 定义方式变化
const props = defineProps({
imgUrl: {
type: String,
default: () => ''
}
});
const imageWrapperRef = ref<HTMLElement | null>(null);
const scale = ref(1); // 初始缩放比例
const maxScale = 3; // 最大缩放比例
const minScale = 0.5; // 最小缩放比例
let isDragging = false;
let startX = 0;
let startY = 0;
let currentTranslateX = 0;
let currentTranslateY = 0;
const handleMouseWheel = (event: WheelEvent) => {
event.preventDefault();
let newScale = scale.value - event.deltaY / 1000;
newScale = Math.max(minScale, Math.min(newScale, maxScale));
if (newScale !== scale.value) {
scale.value = newScale;
resetDragPosition(); // 重置拖拽位置,使图片居中
}
};
const handleMouseDown = (event: MouseEvent) => {
if (scale.value > 1) {
event.preventDefault(); // 阻止默认行为,防止拖拽
isDragging = true;
startX = event.clientX;
startY = event.clientY;
}
};
const handleMouseMove = (event: MouseEvent) => {
if (!isDragging || !imageWrapperRef.value) return;
const deltaX = event.clientX - startX;
const deltaY = event.clientY - startY;
startX = event.clientX;
startY = event.clientY;
currentTranslateX += deltaX;
currentTranslateY += deltaY;
// 边界检测,防止图片被拖出容器
const bounds = getBounds();
if (currentTranslateX > bounds.maxTranslateX) {
currentTranslateX = bounds.maxTranslateX;
} else if (currentTranslateX < bounds.minTranslateX) {
currentTranslateX = bounds.minTranslateX;
}
if (currentTranslateY > bounds.maxTranslateY) {
currentTranslateY = bounds.maxTranslateY;
} else if (currentTranslateY < bounds.minTranslateY) {
currentTranslateY = bounds.minTranslateY;
}
applyTransform();
};
const handleMouseUp = () => {
isDragging = false;
};
const handleMouseLeave = () => {
isDragging = false;
};
const resetTransform = () => {
scale.value = 1;
currentTranslateX = 0;
currentTranslateY = 0;
applyTransform();
};
const resetDragPosition = () => {
currentTranslateX = 0;
currentTranslateY = 0;
applyTransform();
};
const applyTransform = () => {
if (imageWrapperRef.value) {
imageWrapperRef.value.style.transform = `translate(${currentTranslateX}px, ${currentTranslateY}px) scale(${scale.value})`;
}
};
const getBounds = () => {
if (!imageWrapperRef.value) return { minTranslateX: 0, maxTranslateX: 0, minTranslateY: 0, maxTranslateY: 0 };
const imgRect = imageWrapperRef.value.getBoundingClientRect();
const containerRect = imageWrapperRef.value.parentElement?.getBoundingClientRect() ?? imgRect;
const minTranslateX = (containerRect.width - imgRect.width * scale.value) / 2;
const maxTranslateX = -(containerRect.width - imgRect.width * scale.value) / 2;
const minTranslateY = (containerRect.height - imgRect.height * scale.value) / 2;
const maxTranslateY = -(containerRect.height - imgRect.height * scale.value) / 2;
return { minTranslateX, maxTranslateX, minTranslateY, maxTranslateY };
};
const transformStyle = computed(() => ({
transition: isDragging ? 'none' : 'transform 0.2s ease'
}));
</script>
<style scoped>
.image-wrapper {
width: 100%;
overflow: hidden;
position: relative;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
user-select: none; /* 禁用文本选择 */
cursor: grab; /* 设置初始鼠标指针为可拖动 */
}
.image-wrapper:active {
cursor: grabbing; /* 当正在拖动时改变鼠标指针 */
}
.scalable-image {
object-fit: contain;
width: 100%;
padding: 15px;
}
</style>

View File

@@ -49,7 +49,7 @@
</el-dialog>
</el-dialog>
</template>
<script lang="ts" setup>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
import { FlowTaskVO, TaskOperationBo } from '@/api/workflow/task/types';
import UserSelect from '@/components/UserSelect';
@@ -85,9 +85,13 @@ const task = ref<FlowTaskVO>({
nodeName: undefined,
flowCode: undefined,
flowStatus: undefined,
formCustom: undefined,
formPath: undefined,
nodeType: undefined,
nodeRatio: undefined,
version: undefined
version: undefined,
applyNode: undefined,
buttonList: []
});
const open = (taskId: string) => {
@@ -109,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;
@@ -135,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;
@@ -159,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;
@@ -171,7 +178,7 @@ const deleteMultiInstanceUser = async (row) => {
};
//获取办理人
const handleTaskUser = async () => {
let data = await currentTaskAllUser(task.value.id);
const data = await currentTaskAllUser(task.value.id);
deleteUserList.value = data.data;
if (deleteUserList.value && deleteUserList.value.length > 0) {
deleteUserList.value.forEach((e) => {
@@ -183,7 +190,7 @@ const handleTaskUser = async () => {
//终止任务
const handleTerminationTask = async () => {
let params = {
const params = {
taskId: task.value.id,
comment: ''
};

View File

@@ -8,15 +8,26 @@
<el-checkbox value="3" name="type">短信</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item v-if="task.flowStatus === 'waiting'" 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="抄送">
<el-form-item label="抄送" v-if="buttonObj.copy">
<el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" />
<el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)">
{{ user.nickName }}
</el-tag>
</el-form-item>
<el-form-item v-if="buttonObj.pop && nestNodeList && nestNodeList.length > 0" label="下一步审批人" prop="assigneeMap">
<div v-for="(item, index) in nestNodeList" :key="index" style="margin-bottom: 5px; width: 500px">
<span>{{ item.nodeName }}</span>
<el-input v-if="false" v-model="form.assigneeMap[item.nodeCode]" />
<el-input placeholder="请选择审批人" readonly v-model="nickName[item.nodeCode]">
<template v-slot:append>
<el-button @click="choosePeople(item)" icon="search">选择</el-button>
</template>
</el-input>
</div>
</el-form-item>
<el-form-item v-if="task.flowStatus === 'waiting'" label="审批意见">
<el-input v-model="form.message" type="textarea" resize="none" />
</el-form-item>
@@ -24,10 +35,14 @@
<template #footer>
<span class="dialog-footer">
<el-button :disabled="buttonDisabled" type="primary" @click="handleCompleteTask"> 提交 </el-button>
<el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openDelegateTask"> 委托 </el-button>
<el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="primary" @click="openTransferTask"> 转办 </el-button>
<el-button v-if="task.flowStatus === 'waiting' && buttonObj.trust" :disabled="buttonDisabled" type="primary" @click="openDelegateTask">
委托
</el-button>
<el-button v-if="task.flowStatus === 'waiting' && buttonObj.transfer" :disabled="buttonDisabled" type="primary" @click="openTransferTask">
转办
</el-button>
<el-button
v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0"
v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0 && buttonObj.addSign"
:disabled="buttonDisabled"
type="primary"
@click="openMultiInstanceUser"
@@ -35,15 +50,24 @@
加签
</el-button>
<el-button
v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0"
v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0 && buttonObj.subSign"
:disabled="buttonDisabled"
type="primary"
@click="handleTaskUser"
>
减签
</el-button>
<el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleTerminationTask"> 终止 </el-button>
<el-button v-if="task.flowStatus === 'waiting'" :disabled="buttonDisabled" type="danger" @click="handleBackProcessOpen"> 退回 </el-button>
<el-button
v-if="task.flowStatus === 'waiting' && buttonObj.termination"
:disabled="buttonDisabled"
type="danger"
@click="handleTerminationTask"
>
终止
</el-button>
<el-button v-if="task.flowStatus === 'waiting' && buttonObj.back" :disabled="buttonDisabled" type="danger" @click="handleBackProcessOpen">
退回
</el-button>
<el-button :disabled="buttonDisabled" @click="cancel">取消</el-button>
</span>
</template>
@@ -55,6 +79,14 @@
<UserSelect ref="delegateTaskRef" :multiple="false" @confirm-call-back="handleDelegateTask"></UserSelect>
<!-- 加签组件 -->
<UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></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">
@@ -72,7 +104,11 @@
</el-checkbox-group>
</el-form-item>
<el-form-item v-if="task.flowStatus === 'waiting'" label="附件">
<fileUpload v-model="backForm.fileId" :file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']" :file-size="20" />
<fileUpload
v-model="backForm.fileId"
:file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']"
:file-size="20"
/>
</el-form-item>
<el-form-item label="审批意见">
<el-input v-model="backForm.message" type="textarea" resize="none" />
@@ -102,21 +138,30 @@
</el-dialog>
</template>
<script lang="ts" setup>
<script setup lang="ts">
import { ref } from 'vue';
import { ComponentInternalInstance } from 'vue';
import { ElForm } from 'element-plus';
import { completeTask, backProcess, getTask, taskOperation, terminationTask, getBackTaskNode, currentTaskAllUser } from '@/api/workflow/task';
import {
completeTask,
backProcess,
getTask,
taskOperation,
terminationTask,
getBackTaskNode,
currentTaskAllUser,
getNextNodeList
} from '@/api/workflow/task';
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>>();
const delegateTaskRef = ref<InstanceType<typeof UserSelect>>();
const multiInstanceUserRef = ref<InstanceType<typeof UserSelect>>();
const porUserRef = ref<InstanceType<typeof UserSelect>>();
const props = defineProps({
taskVariables: {
@@ -131,17 +176,35 @@ 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
const popUserIds = ref<any>([]);
//驳回是否显示
const backVisible = ref(false);
const backLoading = ref(true);
const backButtonDisabled = ref(true);
// 可驳回得任务节点
const taskNodeList = ref([]);
const nickName = ref({});
//节点编码
const nodeCode = ref<string>('');
const buttonObj = ref<any>({
pop: false,
trust: false,
transfer: false,
addSign: false,
subSign: false,
termination: false,
back: false
});
//下一节点列表
const nestNodeList = ref([]);
//任务
const task = ref<FlowTaskVO>({
id: undefined,
@@ -159,7 +222,13 @@ const task = ref<FlowTaskVO>({
formCustom: undefined,
formPath: undefined,
nodeType: undefined,
nodeRatio: undefined
nodeRatio: undefined,
applyNode: false,
buttonList: [],
copyList: [],
varList: undefined,
businessCode: undefined,
businessTitle: undefined
});
const dialog = reactive<DialogOption>({
visible: false,
@@ -170,6 +239,7 @@ const deleteSignatureVisible = ref(false);
const form = ref<Record<string, any>>({
taskId: undefined,
message: undefined,
assigneeMap: {},
variables: {},
messageType: ['1'],
flowCopyList: []
@@ -181,8 +251,9 @@ const backForm = ref<Record<string, any>>({
variables: {},
messageType: ['1']
});
//打开弹窗
const openDialog = (id?: string) => {
const openDialog = async (id?: string) => {
selectCopyUserIds.value = undefined;
selectCopyUserList.value = [];
form.value.fileId = undefined;
@@ -191,13 +262,27 @@ const openDialog = (id?: string) => {
dialog.visible = true;
loading.value = true;
buttonDisabled.value = true;
nextTick(() => {
getTask(taskId.value).then((response) => {
task.value = response.data;
loading.value = false;
buttonDisabled.value = false;
});
const response = await getTask(taskId.value);
task.value = response.data;
buttonObj.value = {};
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;
try {
const data = {
taskId: taskId.value,
variables: props.taskVariables
};
const nextData = await getNextNodeList(data);
nestNodeList.value = nextData.data;
} finally {
loading.value = false;
}
};
onMounted(() => {});
@@ -206,13 +291,32 @@ const emits = defineEmits(['submitCallback', 'cancelCallback']);
/** 办理流程 */
const handleCompleteTask = async () => {
form.value.taskId = taskId.value;
form.value.taskVariables = props.taskVariables;
form.value.variables = props.taskVariables;
let verify = false;
if (buttonObj.value.pop && nestNodeList.value && nestNodeList.value.length > 0) {
nestNodeList.value.forEach((e) => {
if (
Object.keys(form.value.assigneeMap).length === 0 ||
form.value.assigneeMap[e.nodeCode] === '' ||
form.value.assigneeMap[e.nodeCode] === null ||
form.value.assigneeMap[e.nodeCode] === undefined
) {
verify = true;
}
});
if (verify) {
proxy?.$modal.msgWarning('请选择审批人!');
return false;
}
} else {
form.value.assigneeMap = {};
}
if (selectCopyUserList.value && selectCopyUserList.value.length > 0) {
let flowCopyList = [];
const flowCopyList = [];
selectCopyUserList.value.forEach((e) => {
let copyUser = {
const copyUser = {
userId: e.userId,
userName: e.nickName
nickName: e.nickName
};
flowCopyList.push(copyUser);
});
@@ -239,7 +343,7 @@ const handleBackProcessOpen = async () => {
backVisible.value = true;
backLoading.value = true;
backButtonDisabled.value = true;
let 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;
@@ -266,6 +370,8 @@ const handleBackProcess = async () => {
const cancel = async () => {
dialog.visible = false;
buttonDisabled.value = false;
nickName.value = {};
form.value.assigneeMap = {};
emits('cancelCallback');
};
//打开抄送人员
@@ -273,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);
@@ -297,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;
@@ -321,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;
@@ -341,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;
@@ -368,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;
@@ -386,7 +496,7 @@ const handleDelegateTask = async (data) => {
};
//终止任务
const handleTerminationTask = async () => {
let params = {
const params = {
taskId: taskId.value,
comment: form.value.message
};
@@ -402,7 +512,7 @@ const handleTerminationTask = async () => {
proxy?.$modal.msgSuccess('操作成功');
};
const handleTaskUser = async () => {
let data = await currentTaskAllUser(taskId.value);
const data = await currentTaskAllUser(taskId.value);
deleteUserList.value = data.data;
if (deleteUserList.value && deleteUserList.value.length > 0) {
deleteUserList.value.forEach((e) => {
@@ -411,6 +521,26 @@ const handleTaskUser = async () => {
}
deleteSignatureVisible.value = true;
};
// 选择人员
const choosePeople = async (data) => {
if (!data.permissionFlag) {
proxy?.$modal.msgError('没有可选择的人员,请联系管理员!');
}
popUserIds.value = data.permissionFlag;
nodeCode.value = data.nodeCode;
porUserRef.value.open();
};
//确认选择
const handlePopUser = async (userList) => {
const userIds = userList.map((item) => {
return item.userId;
});
const nickNames = userList.map((item) => {
return item.nickName;
});
form.value.assigneeMap[nodeCode.value] = userIds.join(',');
nickName.value[nodeCode.value] = nickNames.join(',');
};
/**
* 对外暴露子组件方法

View File

@@ -16,7 +16,7 @@
:data="columns"
show-checkbox
node-key="key"
:props="{ label: 'label', children: 'children' }"
:props="{ label: 'label', children: 'children' } as any"
@check="columnChange"
></el-tree>
<template #reference>

View File

@@ -4,7 +4,7 @@
</div>
</template>
<script setup>
<script setup lang="ts">
const url = ref('https://plus-doc.dromara.org/');
function goto() {

View File

@@ -4,7 +4,7 @@
</div>
</template>
<script setup>
<script setup lang="ts">
const url = ref('https://gitee.com/dromara/RuoYi-Vue-Plus');
function goto() {

View File

@@ -16,7 +16,7 @@
</template>
<script setup lang="ts">
import useAppStore from '@/store/modules/app';
import { useAppStore } from '@/store/modules/app';
const appStore = useAppStore();
const size = computed(() => appStore.size);

View File

@@ -21,7 +21,7 @@ const svgClass = computed(() => {
});
</script>
<style scope lang="scss">
<style lang="scss" scoped>
.sub-el-icon,
.nav-icon {
display: inline-block;

View File

@@ -22,9 +22,9 @@
<script setup lang="ts">
import { constantRoutes } from '@/router';
import { isHttp } from '@/utils/validate';
import useAppStore from '@/store/modules/app';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { useAppStore } from '@/store/modules/app';
import { useSettingsStore } from '@/store/modules/settings';
import { usePermissionStore } from '@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router';
// 顶部栏初始数
@@ -47,11 +47,11 @@ const routers = computed(() => permissionStore.getTopbarRoutes());
// 顶部显示菜单
const topMenus = computed(() => {
let topMenus: RouteRecordRaw[] = [];
const topMenus: RouteRecordRaw[] = [];
routers.value.map((menu) => {
if (menu.hidden !== true) {
// 兼容顶部栏一级菜单内部跳转
if (menu.path === '/') {
if (menu.path === '/' && menu.children) {
topMenus.push(menu.children ? menu.children[0] : menu);
} else {
topMenus.push(menu);
@@ -63,7 +63,7 @@ const topMenus = computed(() => {
// 设置子路由
const childrenMenus = computed(() => {
let childrenMenus: RouteRecordRaw[] = [];
const childrenMenus: RouteRecordRaw[] = [];
routers.value.map((router) => {
router.children?.forEach((item) => {
if (item.parentPath === undefined) {
@@ -118,7 +118,7 @@ const handleSelect = (key: string) => {
// 没有子路由路径内部打开
const routeMenu = childrenMenus.value.find((item) => item.path === key);
if (routeMenu && routeMenu.query) {
let query = JSON.parse(routeMenu.query);
const query = JSON.parse(routeMenu.query);
router.push({ path: key, query: query });
} else {
router.push({ path: key });
@@ -132,7 +132,7 @@ const handleSelect = (key: string) => {
};
const activeRoutes = (key: string) => {
let routes: RouteRecordRaw[] = [];
const routes: RouteRecordRaw[] = [];
if (childrenMenus.value && childrenMenus.value.length > 0) {
childrenMenus.value.map((item) => {
if (key == item.parentPath || (key == 'index' && '' == item.path)) {

View File

@@ -1,147 +0,0 @@
<template>
<div class="el-tree-select">
<el-select
ref="treeSelect"
v-model="valueId"
style="width: 100%"
:filterable="true"
:clearable="true"
:filter-method="selectFilterData"
:placeholder="placeholder"
@clear="clearHandle"
>
<el-option :value="valueId" :label="valueTitle">
<el-tree
id="tree-option"
ref="selectTree"
:accordion="accordion"
:data="options"
:props="objMap"
:node-key="objMap.value"
:expand-on-click-node="false"
:default-expanded-keys="defaultExpandedKey"
:filter-node-method="filterNode"
@node-click="handleNodeClick"
></el-tree>
</el-option>
</el-select>
</div>
</template>
<script setup lang="ts">
interface ObjMap {
value: string;
label: string;
children: string;
}
interface Props {
objMap: ObjMap;
accordion: boolean;
value: string | number;
options: any[];
placeholder: string;
}
const props = withDefaults(defineProps<Props>(), {
objMap: () => {
return {
value: 'id',
label: 'label',
children: 'children'
};
},
accordion: false,
value: '',
options: () => [],
placeholder: ''
});
const selectTree = ref<ElTreeSelectInstance>();
const emit = defineEmits(['update:value']);
const valueId = computed({
get: () => props.value,
set: (val) => {
emit('update:value', val);
}
});
const valueTitle = ref('');
const defaultExpandedKey = ref<any[]>([]);
const initHandle = () => {
nextTick(() => {
const selectedValue = valueId.value;
if (selectedValue !== null && typeof selectedValue !== 'undefined') {
const node = selectTree.value?.getNode(selectedValue);
if (node) {
valueTitle.value = node.data[props.objMap.label];
selectTree.value?.setCurrentKey(selectedValue); // 设置默认选中
defaultExpandedKey.value = [selectedValue]; // 设置默认展开
}
} else {
clearHandle();
}
});
};
const handleNodeClick = (node: any) => {
valueTitle.value = node[props.objMap.label];
valueId.value = node[props.objMap.value];
defaultExpandedKey.value = [];
selectTree.value?.blur();
selectFilterData('');
};
const selectFilterData = (val: any) => {
selectTree.value?.filter(val);
};
const filterNode = (value: any, data: any) => {
if (!value) return true;
return data[props.objMap['label']].indexOf(value) !== -1;
};
const clearHandle = () => {
valueTitle.value = '';
valueId.value = '';
defaultExpandedKey.value = [];
clearSelected();
};
const clearSelected = () => {
const allNode = document.querySelectorAll('#tree-option .el-tree-node');
allNode.forEach((element) => element.classList.remove('is-current'));
};
onMounted(() => {
initHandle();
});
watch(valueId, () => {
initHandle();
});
</script>
<style lang="scss" scoped>
@import '@/assets/styles/variables.module.scss';
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
padding: 0;
background-color: #fff;
height: auto;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
ul li .el-tree .el-tree-node__content {
height: auto;
padding: 0 20px;
box-sizing: border-box;
}
:deep(.el-tree-node__content:hover),
:deep(.el-tree-node__content:active),
:deep(.is-current > div:first-child),
:deep(.el-tree-node__content:focus) {
background-color: mix(#fff, $--color-primary, 90%);
color: $--color-primary;
}
</style>

View File

@@ -11,7 +11,7 @@
class="mt-2"
node-key="id"
:data="deptOptions"
:props="{ label: 'label', children: 'children' }"
:props="{ label: 'label', children: 'children' } as any"
:expand-on-click-node="false"
:filter-node-method="filterNode"
highlight-current
@@ -108,11 +108,13 @@ interface PropType {
modelValue?: UserVO[] | UserVO | undefined;
multiple?: boolean;
data?: string | number | (string | number)[] | undefined;
userIds?: string | number | (string | number)[] | undefined;
}
const prop = withDefaults(defineProps<PropType>(), {
multiple: true,
modelValue: undefined,
data: undefined
data: undefined,
userIds: undefined
});
const emit = defineEmits(['update:modelValue', 'confirmCallBack']);
@@ -143,7 +145,8 @@ const queryParams = ref<UserQuery>({
phonenumber: '',
status: '',
deptId: '',
roleId: ''
roleId: '',
userIds: ''
});
const defaultSelectUserIds = computed(() => computedIds(prop.data));
@@ -165,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));
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 [];
@@ -192,6 +198,7 @@ const getTreeSelect = async () => {
/** 查询用户列表 */
const getList = async () => {
loading.value = true;
queryParams.value.userIds = prop.userIds;
const res = await api.listUser(proxy?.addDateRange(queryParams.value, dateRange.value));
loading.value = false;
userList.value = res.rows;
@@ -302,5 +309,3 @@ defineExpose({
close: userDialog.closeDialog
});
</script>
<style lang="scss" scoped></style>

View File

@@ -1,5 +1,5 @@
import { Directive, DirectiveBinding } from 'vue';
import useUserStore from '@/store/modules/user';
import { useUserStore } from '@/store/modules/user';
/**
* 操作权限处理
*/

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

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

View File

@@ -1,22 +1,19 @@
<template>
<section class="app-main">
<router-view v-slot="{ Component, route }">
<transition v-if="!route.meta.noCache" :enter-active-class="animante" mode="out-in">
<keep-alive v-if="!route.meta.noCache" :include="tagsViewStore.cachedViews">
<transition :enter-active-class="animate" mode="out-in">
<keep-alive :include="tagsViewStore.cachedViews">
<component :is="Component" v-if="!route.meta.link" :key="route.path" />
</keep-alive>
</transition>
<transition v-if="route.meta.noCache" :enter-active-class="animante" mode="out-in">
<component :is="Component" v-if="!route.meta.link && route.meta.noCache" :key="route.path" />
</transition>
</router-view>
<iframe-toggle />
</section>
</template>
<script setup name="AppMain" lang="ts">
import useSettingsStore from '@/store/modules/settings';
import useTagsViewStore from '@/store/modules/tagsView';
import { useSettingsStore } from '@/store/modules/settings';
import { useTagsViewStore } from '@/store/modules/tagsView';
import IframeToggle from './IframeToggle/index.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -24,32 +21,32 @@ const route = useRoute();
const tagsViewStore = useTagsViewStore();
// 随机动画集合
const animante = ref<string>('');
const animate = ref<string>('');
const animationEnable = ref(useSettingsStore().animationEnable);
watch(
() => useSettingsStore().animationEnable,
(val: boolean) => {
animationEnable.value = val;
if (val) {
animante.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
animate.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
} else {
animante.value = proxy?.animate.defaultAnimate as string;
animate.value = proxy?.animate.defaultAnimate as string;
}
},
{ immediate: true }
);
onMounted(() => {
addIframe()
})
addIframe();
});
watchEffect((route) => {
addIframe()
})
watchEffect(() => {
addIframe();
});
function addIframe() {
if (route.meta.link) {
useTagsViewStore().addIframeView(route)
useTagsViewStore().addIframeView(route);
}
}
</script>
@@ -92,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

@@ -11,14 +11,14 @@
<script setup lang="ts">
import InnerLink from '../InnerLink/index.vue';
import useTagsViewStore from '@/store/modules/tagsView';
import { useTagsViewStore } from '@/store/modules/tagsView';
const route = useRoute();
const tagsViewStore = useTagsViewStore();
function iframeUrl(url: string | undefined, query: any) {
if (Object.keys(query).length > 0) {
let params = Object.keys(query)
const params = Object.keys(query)
.map((key) => key + '=' + query[key])
.join('&');
return url + '?' + params;

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
@@ -21,7 +26,6 @@
<template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
</el-select>
<!-- <header-search id="header-search" class="right-menu-item" /> -->
<search-menu ref="searchMenuRef" />
<el-tooltip content="搜索" effect="dark" placement="bottom">
<div class="right-menu-item hover-effect" @click="openSearchMenu">
@@ -34,7 +38,7 @@
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
<template #reference>
<el-badge :value="newNotice > 0 ? newNotice : ''" :max="99">
<svg-icon icon-class="message" />
<div class="right-menu-item hover-effect" style="display: block"><svg-icon icon-class="message" /></div>
</el-badge>
</template>
<template #default>
@@ -90,15 +94,19 @@
<script setup lang="ts">
import SearchMenu from './TopBar/search.vue';
import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user';
import useSettingsStore from '@/store/modules/settings';
import useNoticeStore from '@/store/modules/notice';
import { useAppStore } from '@/store/modules/app';
import { useUserStore } from '@/store/modules/user';
import { useSettingsStore } from '@/store/modules/settings';
import { useNoticeStore } from '@/store/modules/notice';
import { getTenantList } from '@/api/login';
import { dynamicClear, dynamicTenant } from '@/api/system/tenant';
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[]>([]);
// 是否切换了租户
@@ -128,8 +139,8 @@ const dynamicTenantEvent = async (tenantId: string) => {
await dynamicTenant(tenantId);
dynamic.value = true;
await proxy?.$router.push('/');
await proxy?.proxy.$tab.closeAllPage();
await proxy?.proxy.$tab.refreshPage();
await proxy?.$tab.closeAllPage();
await proxy?.$tab.refreshPage();
}
};
@@ -137,8 +148,8 @@ const dynamicClearEvent = async () => {
await dynamicClear();
dynamic.value = false;
await proxy?.$router.push('/');
await proxy?.proxy.$tab.closeAllPage();
await proxy?.proxy.$tab.refreshPage();
await proxy?.$tab.closeAllPage();
await proxy?.$tab.refreshPage();
};
/** 租户列表 */
@@ -163,7 +174,7 @@ const logout = async () => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} as ElMessageBoxOptions);
userStore.logout().then(() => {
router.replace({
path: '/login',
@@ -171,6 +182,7 @@ const logout = async () => {
redirect: encodeURIComponent(router.currentRoute.value.fullPath || '/')
}
});
proxy?.$tab.closeAllPage();
});
};
@@ -200,6 +212,12 @@ watch(
</script>
<style lang="scss" scoped>
.navbar.navtop {
.hamburger-container {
display: none !important;
}
}
:deep(.el-select .el-input__wrapper) {
height: 30px;
}
@@ -220,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 {
@@ -245,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;
@@ -265,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 {
@@ -273,7 +313,7 @@ watch(
transition: background 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.025);
background: var(--el-fill-color-lighter);
}
}
}
@@ -289,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">
@@ -46,16 +81,16 @@
<h3 class="drawer-title">系统布局配置</h3>
<div class="drawer-item">
<span>开启 TopNav</span>
<span>开启 Tags-Views</span>
<span class="comp-style">
<el-switch v-model="settingsStore.topNav" class="drawer-switch" @change="topNavChange" />
<el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
</span>
</div>
<div class="drawer-item">
<span>开启 Tags-Views</span>
<span>显示页签图标</span>
<span class="comp-style">
<el-switch v-model="settingsStore.tagsView" class="drawer-switch" />
<el-switch v-model="settingsStore.tagsIcon" :disabled="!settingsStore.tagsView" class="drawer-switch" />
</span>
</div>
@@ -89,11 +124,12 @@
<script setup lang="ts">
import { useDynamicTitle } from '@/utils/dynamicTitle';
import useAppStore from '@/store/modules/app';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { useAppStore } from '@/store/modules/app';
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;
@@ -106,7 +142,7 @@ 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 isDark = useDark({
storageKey: 'useDarkKey',
@@ -123,11 +159,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 = () => {
@@ -151,13 +202,14 @@ 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;
settings.value.sidebarLogo = storeSettings.value.sidebarLogo;
settings.value.dynamicTitle = storeSettings.value.dynamicTitle;
settings.value.sideTheme = storeSettings.value.sideTheme;
settings.value.theme = storeSettings.value.theme;
settings.value.navType = storeSettings.value.navType;
setTimeout(() => {
proxy?.$modal.closeLoading();
}, 1000);
@@ -235,4 +287,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>
@@ -24,8 +23,9 @@
<script setup lang="ts">
import variables from '@/assets/styles/variables.module.scss';
import logo from '@/assets/logo/logo.png';
import useSettingsStore from '@/store/modules/settings';
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

@@ -86,7 +86,7 @@ const resolvePath = (routePath: string, routeQuery?: string): any => {
return props.basePath;
}
if (routeQuery) {
let query = JSON.parse(routeQuery);
const query = JSON.parse(routeQuery);
return { path: getNormalPath(props.basePath + '/' + routePath), query: query };
}
return getNormalPath(props.basePath + '/' + routePath);

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" />
@@ -24,9 +25,9 @@
import Logo from './Logo.vue';
import SidebarItem from './SidebarItem.vue';
import variables from '@/assets/styles/variables.module.scss';
import useAppStore from '@/store/modules/app';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { useAppStore } from '@/store/modules/app';
import { useSettingsStore } from '@/store/modules/settings';
import { usePermissionStore } from '@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;

View File

@@ -63,9 +63,9 @@ const loginByCode = async (data: LoginData) => {
const init = async () => {
// 如果域名不相等 则重定向处理
let host = window.location.host;
const host = window.location.host;
if (domain !== host) {
let urlFull = new URL(window.location.href);
const urlFull = new URL(window.location.href);
urlFull.host = domain;
window.location.href = urlFull.toString();
return;

View File

@@ -6,7 +6,7 @@
<script setup lang="ts">
import { RouteLocationNormalized } from 'vue-router';
import useTagsViewStore from '@/store/modules/tagsView';
import { useTagsViewStore } from '@/store/modules/tagsView';
const tagAndTagSpacing = ref(4);

View File

@@ -5,14 +5,15 @@
v-for="tag in visitedViews"
:key="tag.path"
:data-path="tag.path"
:class="isActive(tag) ? 'active' : ''"
:class="{ 'active': isActive(tag), 'has-icon': tagsIcon }"
:to="{ path: tag.path ? tag.path : '', query: tag.query, fullPath: tag.fullPath ? tag.fullPath : '' }"
class="tags-view-item"
:style="activeStyle(tag)"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)"
>
{{ tag.title }}
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon"/>
<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>
@@ -32,9 +33,9 @@
<script setup lang="ts">
import ScrollPane from './ScrollPane.vue';
import { getNormalPath } from '@/utils/ruoyi';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import useTagsViewStore from '@/store/modules/tagsView';
import { useSettingsStore } from '@/store/modules/settings';
import { usePermissionStore } from '@/store/modules/permission';
import { useTagsViewStore } from '@/store/modules/tagsView';
import { RouteRecordRaw, RouteLocationNormalized } from 'vue-router';
const visible = ref(false);
@@ -51,6 +52,7 @@ const router = useRouter();
const visitedViews = computed(() => useTagsViewStore().getVisitedViews());
const routes = computed(() => usePermissionStore().getRoutes());
const theme = computed(() => useSettingsStore().theme);
const tagsIcon = computed(() => useSettingsStore().tagsIcon)
watch(route, () => {
addTags();
@@ -242,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;
@@ -259,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;
@@ -285,6 +291,13 @@ 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);
@@ -292,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

@@ -28,7 +28,7 @@
<script setup lang="ts" name="layoutBreadcrumbSearch">
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import { usePermissionStore } from '@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router';
type Router = Array<{
path: string;
@@ -67,7 +67,7 @@ const closeSearch = () => {
};
// 菜单搜索数据过滤
const menuSearch = (queryString: string, cb: (options: any[]) => void) => {
let options = state.menuList.filter((item) => {
const options = state.menuList.filter((item) => {
return item.title.indexOf(queryString) > -1;
});
cb(options);
@@ -130,7 +130,7 @@ defineExpose({
});
</script>
<style scoped lang="scss">
<style lang="scss" scoped>
.layout-search-dialog {
position: relative;
:deep(.el-dialog) {

View File

@@ -24,10 +24,9 @@
</template>
<script setup lang="ts" name="layoutBreadcrumbUserNews">
import { storeToRefs } from 'pinia';
import useNoticeStore from '@/store/modules/notice';
import { useNoticeStore } from '@/store/modules/notice';
const noticeStore = storeToRefs(useNoticeStore());
const noticeStore = useNoticeStore();
const { readAll } = useNoticeStore();
// 定义变量内容
@@ -42,7 +41,7 @@ const newsList = ref([]) as any;
*/
const getTableData = async () => {
state.loading = true;
newsList.value = noticeStore.state.value.notices;
newsList.value = noticeStore.state.notices;
state.loading = false;
};
@@ -50,7 +49,7 @@ const getTableData = async () => {
const onNewsClick = (item: any) => {
newsList.value[item].read = true;
//并且写入pinia
noticeStore.state.value.notices = newsList.value;
noticeStore.state.notices = newsList.value;
};
// 前往通知中心点击
@@ -65,7 +64,7 @@ onMounted(() => {
});
</script>
<style scoped lang="scss">
<style lang="scss" scoped>
.layout-navbars-breadcrumb-user-news {
.head-box {
display: flex;

View File

@@ -24,8 +24,9 @@
<script setup lang="ts">
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 { 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,
@@ -68,7 +76,7 @@ onMounted(() => {
});
onMounted(() => {
let protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
initWebSocket(protocol + window.location.host + import.meta.env.VITE_APP_BASE_API + '/resource/websocket');
});
@@ -86,11 +94,11 @@ const setLayout = () => {
</script>
<style lang="scss" scoped>
@import '@/assets/styles/mixin.scss';
@import '@/assets/styles/variables.module.scss';
@use '@/assets/styles/mixin.scss';
@use '@/assets/styles/variables.module.scss' as *;
.app-wrapper {
@include clearfix;
@include mixin.clearfix;
position: relative;
height: 100%;
width: 100%;

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';
@@ -34,7 +34,7 @@ import i18n from '@/lang/index';
// vxeTable
import VXETable from 'vxe-table';
import 'vxe-table/lib/style.css';
VXETable.config({
VXETable.setConfig({
zIndex: 999999
});

View File

@@ -5,21 +5,22 @@ import 'nprogress/nprogress.css';
import { getToken } from '@/utils/auth';
import { isHttp, isPathMatch } from '@/utils/validate';
import { isRelogin } from '@/utils/request';
import useUserStore from '@/store/modules/user';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { useUserStore } from '@/store/modules/user';
import { useSettingsStore } from '@/store/modules/settings';
import { usePermissionStore } from '@/store/modules/permission';
import { ElMessage } from 'element-plus/es';
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*'];
const isWhiteList = (path: string) => {
return whiteList.some(pattern => isPathMatch(pattern, path))
}
return whiteList.some((pattern) => isPathMatch(pattern, path));
};
router.beforeEach(async (to, from, next) => {
NProgress.start();
if (getToken()) {
to.meta.title && useSettingsStore().setTitle(to.meta.title);
to.meta.title && useSettingsStore().setTitle(to.meta.title as string);
/* has token*/
if (to.path === '/login') {
next({ path: '/' });

View File

@@ -1,4 +1,4 @@
import useUserStore from '@/store/modules/user';
import { useUserStore } from '@/store/modules/user';
const authPermission = (permission: string): boolean => {
const all_permission = '*:*:*';

View File

@@ -1,6 +1,6 @@
import router from '@/router';
import { RouteLocationMatched, RouteLocationNormalized, RouteLocationRaw } from 'vue-router';
import useTagsViewStore from '@/store/modules/tagsView';
import { useTagsViewStore } from '@/store/modules/tagsView';
export default {
/**

View File

@@ -93,104 +93,7 @@ export const constantRoutes: RouteRecordRaw[] = [
// 动态路由,基于用户权限动态去加载
export const dynamicRoutes: RouteRecordRaw[] = [
{
path: '/system/user-auth',
component: Layout,
hidden: true,
permissions: ['system:user:edit'],
children: [
{
path: 'role/:userId(\\d+)',
component: () => import('@/views/system/user/authRole.vue'),
name: 'AuthRole',
meta: { title: '分配角色', activeMenu: '/system/user', icon: '', noCache: true }
}
]
},
{
path: '/system/role-auth',
component: Layout,
hidden: true,
permissions: ['system:role:edit'],
children: [
{
path: 'user/:roleId(\\d+)',
component: () => import('@/views/system/role/authUser.vue'),
name: 'AuthUser',
meta: { title: '分配用户', activeMenu: '/system/role', icon: '', noCache: true }
}
]
},
{
path: '/system/dict-data',
component: Layout,
hidden: true,
permissions: ['system:dict:list'],
children: [
{
path: 'index/:dictId(\\d+)',
component: () => import('@/views/system/dict/data.vue'),
name: 'Data',
meta: { title: '字典数据', activeMenu: '/system/dict', icon: '', noCache: true }
}
]
},
{
path: '/system/oss-config',
component: Layout,
hidden: true,
permissions: ['system:ossConfig:list'],
children: [
{
path: 'index',
component: () => import('@/views/system/oss/config.vue'),
name: 'OssConfig',
meta: { title: '配置管理', activeMenu: '/system/oss', icon: '', noCache: true }
}
]
},
{
path: '/tool/gen-edit',
component: Layout,
hidden: true,
permissions: ['tool:gen:edit'],
children: [
{
path: 'index/:tableId(\\d+)',
component: () => import('@/views/tool/gen/editTable.vue'),
name: 'GenEdit',
meta: { title: '修改生成配置', activeMenu: '/tool/gen', icon: '', noCache: true }
}
]
},
{
path: '/workflow/leaveEdit',
component: Layout,
hidden: true,
permissions: ['workflow:leave:edit'],
children: [
{
path: 'index',
component: () => import('@/views/workflow/leave/leaveEdit.vue'),
name: 'leaveEdit',
meta: { title: '请假申请', activeMenu: '/workflow/leave', noCache: true }
}
]
},
{
path: '/workflow/design',
component: Layout,
hidden: true,
permissions: ['workflow:leave:edit'],
children: [
{
path: 'index',
component: () => import('@/views/workflow/processDefinition/design.vue'),
name: 'design',
meta: { title: '流程设计', activeMenu: '/workflow/processDefinition', noCache: true }
}
]
}
];
/**

View File

@@ -1,4 +1,5 @@
import { LanguageEnum } from '@/enums/LanguageEnum';
import { NavTypeEnum } from '@/enums/NavTypeEnum';
const setting: DefaultSettings = {
/**
@@ -18,19 +19,24 @@ const setting: DefaultSettings = {
showSettings: true,
/**
* 是否显示顶部导航
* 默认布局
*/
topNav: false,
navType: NavTypeEnum.LEFT,
/**
* 是否显示 tagsView
*/
tagsView: true,
/**
* 显示页签图标
*/
tagsIcon: false,
/**
* 是否固定头部
*/
fixedHeader: false,
fixedHeader: true,
/**
* 是否显示logo
@@ -43,20 +49,28 @@ const setting: DefaultSettings = {
dynamicTitle: false,
/**
* @type {string | array} 'production' | ['production', 'development']
* @description Need show err logs component.
* The default is only used in the production env
* If you want to also use it in dev, you can pass ['production', 'development']
* 是否开启动画 开启随机 关闭渐进渐出
*/
errorLog: 'production',
animationEnable: false,
/**
* 是否暗黑模式
*/
dark: false,
/**
* 默认语言
*/
language: LanguageEnum.zh_CN,
/**
* 默认大小
*/
size: 'default',
/**
* 默认布局
*/
layout: ''
};
export default setting;

View File

@@ -1,3 +1,5 @@
import { createPinia } from 'pinia';
const store = createPinia();
export default store;

View File

@@ -1,5 +1,8 @@
import zhCN from 'element-plus/es/locale/lang/zh-cn';
import enUS from 'element-plus/es/locale/lang/en';
import { defineStore } from 'pinia';
import { useStorage } from '@vueuse/core';
import { ref, reactive, computed } from 'vue';
export const useAppStore = defineStore('app', () => {
const sidebarStatus = useStorage('sidebarStatus', '1');
@@ -68,5 +71,3 @@ export const useAppStore = defineStore('app', () => {
toggleSideBarHide
};
});
export default useAppStore;

View File

@@ -1,3 +1,5 @@
import { defineStore } from 'pinia';
export const useDictStore = defineStore('dict', () => {
const dict = ref<Map<string, DictDataOption[]>>(new Map());
@@ -61,5 +63,3 @@ export const useDictStore = defineStore('dict', () => {
cleanDict
};
});
export default useDictStore;

View File

@@ -1,4 +1,5 @@
import { defineStore } from 'pinia';
import { reactive } from 'vue';
interface NoticeItem {
title?: string;
@@ -38,5 +39,3 @@ export const useNoticeStore = defineStore('notice', () => {
clearNotice
};
});
export default useNoticeStore;

View File

@@ -4,11 +4,10 @@ import store from '@/store';
import { getRouters } from '@/api/menu';
import auth from '@/plugins/auth';
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/layout/index.vue';
import ParentView from '@/components/ParentView/index.vue';
import InnerLink from '@/layout/components/InnerLink/index.vue';
import { ref } from 'vue';
import { createCustomNameComponent } from '@/utils/createCustomNameComponent';
// 匹配views里面所有的.vue文件
@@ -23,6 +22,9 @@ export const usePermissionStore = defineStore('permission', () => {
const getRoutes = (): RouteRecordRaw[] => {
return routes.value as RouteRecordRaw[];
};
const getDefaultRoutes = (): RouteRecordRaw[] => {
return defaultRoutes.value as RouteRecordRaw[];
};
const getSidebarRoutes = (): RouteRecordRaw[] => {
return sidebarRouters.value as RouteRecordRaw[];
};
@@ -98,27 +100,12 @@ export const usePermissionStore = defineStore('permission', () => {
const filterChildren = (childrenMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw): RouteRecordRaw[] => {
let children: RouteRecordRaw[] = [];
childrenMap.forEach((el) => {
if (el.children && el.children.length) {
if (el.component?.toString() === 'ParentView' && !lastRouter) {
el.children.forEach((c) => {
c.path = el.path + '/' + c.path;
if (c.children && c.children.length) {
children = children.concat(filterChildren(c.children, c));
return;
}
children.push(c);
});
return;
}
el.path = lastRouter ? lastRouter.path + '/' + el.path : el.path;
if (el.children && el.children.length && el.component?.toString() === 'ParentView') {
children = children.concat(filterChildren(el.children, el));
} else {
children.push(el);
}
if (lastRouter) {
el.path = lastRouter.path + '/' + el.path;
if (el.children && el.children.length) {
children = children.concat(filterChildren(el.children, el));
return;
}
}
children = children.concat(el);
});
return children;
};
@@ -129,6 +116,7 @@ export const usePermissionStore = defineStore('permission', () => {
defaultRoutes,
getRoutes,
getDefaultRoutes,
getSidebarRoutes,
getTopbarRoutes,
@@ -217,5 +205,3 @@ function duplicateRouteChecker(localRoutes: Route[], routes: Route[]) {
nameList.push(route.name.toString());
});
}
export default usePermissionStore;

View File

@@ -1,28 +1,34 @@
import { defineStore } from 'pinia';
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', {
topNav: defaultSettings.topNav,
tagsView: defaultSettings.tagsView,
tagsIcon: defaultSettings.tagsIcon,
fixedHeader: defaultSettings.fixedHeader,
sidebarLogo: defaultSettings.sidebarLogo,
dynamicTitle: defaultSettings.dynamicTitle,
sideTheme: defaultSettings.sideTheme,
theme: defaultSettings.theme
theme: defaultSettings.theme,
navType: defaultSettings.navType
});
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);
const sidebarLogo = ref<boolean>(storageSetting.value.sidebarLogo);
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 setTitle = (value: string) => {
title.value = value;
@@ -33,15 +39,14 @@ export const useSettingsStore = defineStore('setting', () => {
theme,
sideTheme,
showSettings,
topNav,
tagsView,
tagsIcon,
fixedHeader,
sidebarLogo,
dynamicTitle,
animationEnable,
dark,
navType,
setTitle
};
});
export default useSettingsStore;

View File

@@ -1,4 +1,6 @@
import { RouteLocationNormalized } from 'vue-router';
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useTagsViewStore = defineStore('tagsView', () => {
const visitedViews = ref<RouteLocationNormalized[]>([]);
@@ -230,4 +232,3 @@ export const useTagsViewStore = defineStore('tagsView', () => {
delIframeView
};
});
export default useTagsViewStore;

View File

@@ -3,7 +3,8 @@ import { getToken, removeToken, setToken } from '@/utils/auth';
import { login as loginApi, logout as logoutApi, getInfo as getUserInfo } from '@/api/login';
import { LoginData } from '@/api/types';
import defAva from '@/assets/images/profile.jpg';
import store from '@/store';
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useUserStore = defineStore('user', () => {
const token = ref(getToken());
@@ -83,9 +84,3 @@ export const useUserStore = defineStore('user', () => {
setAvatar
};
});
export default useUserStore;
// 非setup
export function useUserStoreHook() {
return useUserStore(store);
}

11
src/types/global.d.ts vendored
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,14 +91,18 @@ declare global {
}
declare interface LayoutSetting {
/**
* 是否显示顶部导航
* 默认布局
*/
topNav: boolean;
navType: NavTypeEnum;
/**
* 是否显示多标签导航
*/
tagsView: boolean;
/**
* 显示页签图标
*/
tagsIcon: boolean;
/**
* 是否固定头部
*/
@@ -157,8 +162,6 @@ declare global {
* false: 明亮模式
*/
dark: boolean;
errorLog: string;
}
}
export {};

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;
@@ -48,4 +48,3 @@ export type ObjKeysToUnion<T, P extends string = ''> = T extends object
[K in keyof T]: ObjKeysToUnion<T[K], P extends '' ? `${K & string}` : `${P}.${K & string}`>;
}[keyof T]
: P;

View File

@@ -7,21 +7,20 @@ export const useDict = (...args: string[]): { [key: string]: DictDataOption[] }
const res = ref<{
[key: string]: DictDataOption[];
}>({});
return (() => {
args.forEach(async (dictType) => {
res.value[dictType] = [];
const dicts = useDictStore().getDict(dictType);
if (dicts) {
res.value[dictType] = dicts;
} else {
await getDicts(dictType).then((resp) => {
res.value[dictType] = resp.data.map(
(p): DictDataOption => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })
);
useDictStore().setDict(dictType, res.value[dictType]);
});
}
});
return res.value;
})();
args.forEach(async (dictType) => {
res.value[dictType] = [];
const dicts = useDictStore().getDict(dictType);
if (dicts) {
res.value[dictType] = dicts;
} else {
await getDicts(dictType).then((resp) => {
res.value[dictType] = resp.data.map(
(p): DictDataOption => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })
);
useDictStore().setDict(dictType, res.value[dictType]);
});
}
});
return res.value;
};

View File

@@ -1,4 +1,4 @@
import useUserStore from '@/store/modules/user';
import { useUserStore } from '@/store/modules/user';
/**
* 字符权限校验

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
}
});
// 请求拦截器
@@ -191,7 +195,8 @@ export function download(url: string, params: any, fileName: string) {
const blob = new Blob([resp]);
FileSaver.saveAs(blob, fileName);
} else {
const resText = await resp.data.text();
const blob = new Blob([resp]);
const resText = await blob.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
ElMessage.error(errMsg);

View File

@@ -167,39 +167,24 @@ export const handleTree = <T>(data: any[], id?: string, parentId?: string, child
};
const childrenListMap: any = {};
const nodeIds: any = {};
const tree: T[] = [];
for (const d of data) {
const parentId = d[config.parentId];
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = [];
const id = d[config.id];
childrenListMap[id] = d;
if (!d[config.childrenList]) {
d[config.childrenList] = [];
}
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
}
for (const d of data) {
const parentId = d[config.parentId];
if (nodeIds[parentId] == null) {
const parentObj = childrenListMap[parentId];
if (!parentObj) {
tree.push(d);
} else {
parentObj[config.childrenList].push(d);
}
}
const adaptToChildrenList = (o: any) => {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]];
}
if (o[config.childrenList]) {
for (const c of o[config.childrenList]) {
adaptToChildrenList(c);
}
}
};
for (const t of tree) {
adaptToChildrenList(t);
}
return tree;
};

View File

@@ -1,6 +1,6 @@
import { getToken } from '@/utils/auth';
import { ElNotification } from 'element-plus';
import useNoticeStore from '@/store/modules/notice';
import { useNoticeStore } from '@/store/modules/notice';
// 初始化
export const initSSE = (url: any) => {
@@ -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

@@ -5,9 +5,13 @@
* @returns {Boolean}
*/
export function isPathMatch(pattern: string, path: string) {
const regexPattern = pattern.replace(/\//g, '\\/').replace(/\*\*/g, '.*').replace(/\*/g, '[^\\/]*')
const regex = new RegExp(`^${regexPattern}$`)
return regex.test(path)
const regexPattern = pattern
.replace(/\//g, '\\/')
.replace(/\*\*/g, '__DOUBLE_STAR__')
.replace(/\*/g, '[^\\/]*')
.replace(/__DOUBLE_STAR__/g, '.*');
const regex = new RegExp(`^${regexPattern}$`);
return regex.test(path);
}
/**

View File

@@ -1,6 +1,6 @@
import { getToken } from '@/utils/auth';
import { ElNotification } from 'element-plus';
import useNoticeStore from '@/store/modules/notice';
import { useNoticeStore } from '@/store/modules/notice';
// 初始化socket
export const initWebSocket = (url: any) => {

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