183 Commits
Author SHA1 Message Date
YunaiV 66c6463f44 【修复】商城:seckill 导致打包失败的问题 2024-05-05 11:20:08 +08:00
YunaiV f4705ad4e1 【发版】全局:2.1.0 发版 2024-05-04 21:51:06 +08:00
YunaiV 143eb655b1 【发版】全局:2.1.0 发版 2024-05-04 11:14:41 +08:00
YunaiV bbf009e15d 【优化】首页增加“项目说明” 2024-05-04 11:14:11 +08:00
YunaiV ecf9052452 Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev 2024-05-02 15:14:23 +08:00
芋道源码 869cae0675 !437 update src/views/infra/webSocket/index.vue. #434
Merge pull request !437 from AhJindeg/N/A
2024-05-02 07:14:15 +00:00
YunaiV dd36bbce42 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev 2024-05-02 15:04:52 +08:00
芋道源码 939ceb32ce !436 fix(useUpload.ts): 修复前端文件上传Content-Type异常问题
Merge pull request !436 from Khalid/master
2024-05-02 07:04:38 +00:00
YunaiV e2fe04749b 【修复】商城系统:管理后台的商城订单统计数据有问题 2024-05-02 09:24:45 +08:00
YunaiV 20a9780654 【修复】商城:秒杀时段点击编辑报错 2024-05-01 20:53:01 +08:00
YunaiV f75e8d1fd4 【新增】在菜单管理中,可通过“路由地址”添加 ? 拼接参数 2024-05-01 18:47:51 +08:00
YunaiV bde5597467 【优化】结合 vite 机制,将 .env.local-dev 修改成 .env.local 2024-04-30 21:33:35 +08:00
AhJindeg de967a0160 update src/views/infra/webSocket/index.vue. #434
Signed-off-by: AhJindeg <ahjindeg@gmail.com>
2024-04-30 08:50:55 +00:00
YunaiV 9527b41b62 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev 2024-04-30 15:04:41 +08:00
khalidlife ff14cf90f0 fix(useUpload.ts): 修复前端文件上传Content-Type异常问题 2024-04-30 09:30:43 +08:00
YunaiV dcf66749af 【移除】stylelint.vscode-stylelint 插件的推荐,避免 vscode 样式红色波浪线 2024-04-29 22:01:59 +08:00
YunaiV 500b8632df 【优化】增加 pnpm-lock 保证大家更好的打包 2024-04-29 21:47:49 +08:00
YunaiV 730da857e1 【恢复】npm run dev 使用 local-dev 配置文件 2024-04-28 12:58:35 +08:00
芋道源码 c96902d622 !433 CRM: 修复客户导入、销售漏斗。上传组件的文件列表给拓展一个下载 。
Merge pull request !433 from puhui999/dev-crm
2024-04-28 04:52:53 +00:00
puhui999 0076236280 Merge remote-tracking branch 'refs/remotes/yudao/dev' into dev-crm 2024-04-25 22:34:14 +08:00
puhui999 ae53ca6f4f feat:文件上传组件的文件列表拓展一个下载 2024-04-25 22:33:36 +08:00
puhui999 9a36d9d663 fix: import FunnelChart from echarts 2024-04-24 23:28:09 +08:00
YunaiV f55c9b7273 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev 2024-04-24 23:01:05 +08:00
puhui999 6ff31b90c7 CRM: 修复客户导入 2024-04-24 22:55:14 +08:00
YunaiV a0996bd0cf 【优化】移除 env 多余的 VITE_API_BASEPATH 配置项 2024-04-24 21:50:21 +08:00
YunaiV f405ac3293 【优化】新增 sslEnable 字段,支持 outlook 邮箱 2024-04-24 09:14:43 +08:00
YunaiV 2d999138ba 依赖升级:element-plus、axios 等依赖 2024-04-24 00:32:10 +08:00
YunaiV 1d533e49e4 crm:bugfix FunnelBusiness.vue 导致打包失败的问题 2024-04-24 00:31:44 +08:00
YunaiV dee479ce3c Merge remote-tracking branch 'origin/dev' into dev 2024-04-23 23:31:58 +08:00
YunaiV 715f37eb0b crm:code review 员工业绩统计 2024-04-23 23:31:52 +08:00
YunaiV 56543b8db4 Merge branch 'dev' of https://gitee.com/scholarli/yudao-ui-admin-vue3 into dev
# Conflicts:
#	src/views/crm/statistics/performance/components/ContractCountPerformance.vue
#	src/views/crm/statistics/performance/components/ContractPricePerformance.vue
#	src/views/crm/statistics/performance/components/ReceivablePricePerformance.vue
#	src/views/crm/statistics/performance/index.vue
2024-04-23 23:17:15 +08:00
YunaiV 7f3950018e Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3
# Conflicts:
#	src/api/infra/codegen/index.ts
#	src/views/system/sensitiveWord/index.vue
2024-04-23 00:28:26 +08:00
芋道源码 d26e0976db !430 bugfix:修复角色管理页面-角色类型字段没有被字典翻译问题
Merge pull request !430 from limz/bugfix-pull
2024-04-22 16:27:11 +00:00
芋道源码 95a51ce4f3 !432 fix:修复 issues 提到的一些问题
Merge pull request !432 from puhui999/dev-crm
2024-04-22 16:25:14 +00:00
YunaiV 0925253a9d 【移除】敏感词的管理,简化项目的复杂度 2024-04-22 19:52:05 +08:00
YunaiV 98bf5a102e 【移除】错误码的管理,简化项目的复杂度 2024-04-22 19:42:56 +08:00
YunaiV ff0c12c551 【移除】数据库文档,简化项目的复杂度 2024-04-22 19:13:57 +08:00
puhui999 243da124a8 fix: 调整价格弹窗不出来问题 2024-04-22 11:35:28 +08:00
puhui999 deb4585d4b fix: 适配 echarts map 的地名 2024-04-22 11:04:53 +08:00
puhui999 afdee2cd48 fix: 移除 form-crate 自带的富文本组件规则,使用 editorRule 替代 2024-04-22 10:56:16 +08:00
scholar f4dd1bed63 1, 修改数据列表无法刷新的问题;
2, 修改时间选择,后端按照年实现的,前端同样按照年份选择实现;
3,修改环比同比计算;
4,回款金额统计bug修复。
2024-04-19 17:46:09 +08:00
limingz d96f951113 bugfix:修复角色管理页面-角色类型字段没有被字典翻译问题 2024-04-18 14:45:43 +08:00
YunaiV f5d013187b crm:code review 销售漏斗 2024-04-18 09:28:07 +08:00
芋道源码 50ab864698 !428 CRM: 新增商机赢单转化率分析
Merge pull request !428 from puhui999/dev-crm
2024-04-18 01:10:12 +00:00
芋道源码 b1c832d27f !429 fix: Permissions filled in incorrectly
Merge pull request !429 from AhJindeg/hotfix/hasPermi
2024-04-18 01:08:58 +00:00
芋道源码 5ba6c5ee00 !427 修复第四个导航模式无法点开菜单的问题
Merge pull request !427 from moon69/N/A
2024-04-18 01:04:58 +00:00
AhJindeg ac6618a943 fix: 🐞 fix sensitiveWord hasPermi value 2024-04-16 18:24:56 +08:00
AhJindeg decdd1b25d fix: 🐞 fix post hasPermi value 2024-04-16 18:24:36 +08:00
AhJindeg 97c8cefb7d fix: 🐞 fix operatelog hasPermi value 2024-04-16 18:24:16 +08:00
AhJindeg bfb69ccccd fix: 🐞 fix template hasPermi value 2024-04-16 18:23:57 +08:00
AhJindeg 958ec41f4e fix: 🐞 fix loginlog hasPermi value 2024-04-16 18:23:39 +08:00
AhJindeg dc5bede084 fix: 🐞 fix fileConfig hasPermi value 2024-04-16 18:23:24 +08:00
AhJindeg 755526834e fix: 🐞 fix file hasPermi value 2024-04-16 18:23:08 +08:00
AhJindeg 221f78aa9a fix: 🐞 fix apiAccessLog hasPermi value 2024-04-16 18:22:46 +08:00
puhui999 4e640bae88 CRM: 新增商机赢单转化率分析 2024-04-15 16:38:24 +08:00
puhui999 014d1f8cd2 CRM: 新增商机赢单转化率分析 2024-04-15 16:32:48 +08:00
puhui999 6acfc77d08 CRM: 完善销售漏斗 2024-04-15 15:37:37 +08:00
moon69 033462d25f 修复第四个导航模式无法点开菜单的问题
Signed-off-by: moon69 <1016830869@qq.com>
2024-04-15 06:24:32 +00:00
YunaiV 2512253020 crm:code review 销售漏斗 2024-04-14 21:22:55 +08:00
YunaiV c331a83b74 crm:code review 销售漏斗 2024-04-14 21:21:52 +08:00
芋道源码 56829ff921 !426 CRM: 完善销售漏斗分析
Merge pull request !426 from puhui999/dev-crm
2024-04-14 12:19:51 +00:00
芋道源码 6037ac4052 !419 feat: 客户成交周期分析(按区域、按产品)
Merge pull request !419 from dhb52/dev
2024-04-14 10:23:40 +00:00
YunaiV 1f0ef78219 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev 2024-04-14 18:23:04 +08:00
芋道源码 1c469300f5 !422 Infra模块>代码生成器>适配达梦数据库
Merge pull request !422 from 再见七月/infraBug-by-dm
2024-04-14 10:21:53 +00:00
YunaiV 7ba611bdb7 Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 2024-04-14 18:18:53 +08:00
芋道源码 aba61120e4 !421 fix(UserImportForm): fix a bunch of bugs
Merge pull request !421 from AhJindeg/hotfix-UserImportForm
2024-04-14 10:18:31 +00:00
YunaiV e885bf2f2a Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 2024-04-14 18:17:53 +08:00
芋道源码 ddfa0a5a0f !424 fix:部署报错移除多余的代码
Merge pull request !424 from 李占宏/dev
2024-04-14 10:17:42 +00:00
芋道源码 855987d5fa !425 fix:部署报错移除多余的代码
Merge pull request !425 from 李占宏/dev
2024-04-14 10:14:42 +00:00
puhui999 bc95dc2959 CRM: 完善新增商机分析 2024-04-14 16:31:43 +08:00
puhui999 3c49ed570a CRM: 完善销售漏斗分析 2024-04-14 00:34:33 +08:00
zhanhong 2f0fdd1d36 fix:移除多余的代码 2024-04-13 12:39:41 +08:00
puhui999 2d7bab8adb CRM: 完善用户画像数据统计 2024-04-13 11:04:56 +08:00
YunaiV ab1c506226 Merge branch 'dev' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 2024-04-12 20:37:50 +08:00
YunaiV 99addf2cfa README:移除 resilience4j 组件,增加分布式锁 2024-04-11 23:51:51 +08:00
puhui999 64af029c86 CRM: 完善用户画像数据统计 2024-04-11 15:25:48 +08:00
AhJindeg 4793897511 fix(UserImportForm): 🐞 add update formLoading and dialogVisible to submitFormSuccess 2024-04-11 11:03:06 +08:00
AhJindeg 786468f437 fix(UserImportForm): 🐞 add init updateSupport to open 2024-04-11 11:00:24 +08:00
byeJuly 1f517c3faf 修复 Infra 模块:
修复项目迁移达梦8数据库后,代码生成器导入功能报错的原因:
调整策略:删除-> autoIncrement <-字段
2024-04-10 19:33:01 +08:00
AhJindeg 4c15e6675a fix(UserImportForm): 🐞 add nextTick to resetForm 2024-04-10 18:09:43 +08:00
dhb52 5358863fc6 refactor: 统一处理null数据为'未知' 2024-04-09 18:47:31 +08:00
dhb52 0e15d6c9d2 feat: 客户成交周期分析(按区域、按产品) 2024-04-09 00:21:10 +08:00
芋道源码 f6e4753b34 !414 fix(role): add nextTick for open
Merge pull request !414 from AhJindeg/hotfix-role
2024-04-07 13:28:38 +00:00
YunaiV e8a3745b5e crm:code review 公海客户分析 2024-04-07 21:27:29 +08:00
芋道源码 091ce90359 !417 feat: 增加[客户公海分析]tab
Merge pull request !417 from dhb52/dev
2024-04-07 13:17:35 +00:00
AhJindeg b5fa188805 Merge branch 'dev' of gitee.com:yudaocode/yudao-ui-admin-vue3 into hotfix-role
Signed-off-by: AhJindeg <ahjindeg@gmail.com>
2024-04-06 14:37:34 +00:00
YunaiV 8f8edc9565 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev 2024-04-06 19:39:14 +08:00
dhb52 3c54f5f565 feat: 增加[客户公海分析]tab 2024-04-06 15:38:50 +08:00
dhb52 13cf1feefb refactor: 使用try finally停止loading 2024-04-06 15:38:04 +08:00
YunaiV 6772568a58 crm:code review 客户画像 2024-04-05 14:43:55 +08:00
芋道源码 8926dd1173 !415 CRM: 完善用户画像数据统计
Merge pull request !415 from puhui999/dev-crm
2024-04-05 05:54:06 +00:00
YunaiV dcfad7a427 crm:code review 员工业绩的前端 2024-04-05 13:53:14 +08:00
AhJindeg db4795744e style: 🌈 add more readable for open 2024-04-05 13:51:27 +08:00
芋道源码 f17dfb9a31 !416 CRM员工业绩统计分析v2.0
Merge pull request !416 from scholarli/dev
2024-04-05 05:45:09 +00:00
AhJindeg 7b84ddd358 perf: 🎈 add await for nextTick() 2024-04-04 18:54:51 +08:00
YunaiV 283a9d0507 【新增】system user 和 role 接入新操作日志 2024-04-04 09:57:25 +08:00
YunaiV c01571d11d 【重构】V2 操作日志转正,基于注解的可使用变量、可以自定义函数的通用操作日志组件 2024-04-04 01:21:27 +08:00
YunaiV 757281ce34 【优化】增强访问日志,支持是否记录、脱敏、操作信息等功能 2024-04-03 19:55:54 +08:00
scholar b57a2c50a9 CRM员工业绩统计分析v2.0 2024-04-03 18:43:34 +08:00
puhui999 36385a74ba CRM: 完善用户画像数据统计 2024-04-03 12:19:09 +08:00
AhJindeg c048dcf02a fix(role): 🐞 add nextTick for open
- 增加 nextTick(),修复在权限范围为指定机构并且已选择数据时,编辑打开提示 setChecked undefined 问题
2024-04-01 16:14:30 +08:00
芋道源码 9ede8ffe4d !413 form-create-designer: 表单组件新增字典选择器和用户选择器
Merge pull request !413 from puhui999/dev-crm
2024-03-31 14:15:52 +00:00
puhui999 d16bed4ad7 form-create-designer: 表单组件新增用户选择器 2024-03-31 17:58:44 +08:00
puhui999 68dae2d1fe form-create-designer: 表单组件新增字典选择器 2024-03-30 23:18:23 +08:00
YunaiV d05c67ffaa infra:code review 表单增强 2024-03-30 21:15:01 +08:00
芋道源码 5ca3358237 !412 使用项目组件对 form-create-designer 进行增强
Merge pull request !412 from puhui999/dev-crm
2024-03-30 13:03:29 +00:00
YunaiV ffc81621d7 REPORT:【移除】Ureport 的实现,因为和 Spring Boot 兼容性较差 2024-03-30 20:41:32 +08:00
puhui999 a489d21add Merge remote-tracking branch 'yudao/dev' into dev-crm 2024-03-30 18:53:30 +08:00
puhui999 bc004b3b27 使用项目组件对 form-create-designer 进行增强 2024-03-30 18:53:05 +08:00
芋道源码 52889e3504 !411 SYSTEM:修复【系统管理/岗位管理/新增按钮与导出按钮】权限hasPermi错误
Merge pull request !411 from aqiangaqiang/master
2024-03-30 07:56:52 +00:00
芋道源码 6764f86980 !409 fix(crontab): add inputChange for crontab
Merge pull request !409 from AhJindeg/hotfix-crontab
2024-03-30 07:55:47 +00:00
芋道源码 02af138069 !402 修复数据权限无法回显的问题
Merge pull request !402 from moon69/master
2024-03-30 07:50:38 +00:00
YunaiV 427c2d5d2a MALL:修复营销的商品范围枚举不正确的问题 2024-03-30 15:42:28 +08:00
YunaiV 87d1047fa9 CRM:code review【客户画像】的实现 2024-03-30 14:17:14 +08:00
YunaiV 34b788cb88 Merge branch 'dev-crm' of https://gitee.com/puhui999/yudao-ui-admin-vue3 into dev
# Conflicts:
#	src/views/bpm/definition/index.vue
#	src/views/bpm/model/index.vue
#	src/views/bpm/processInstance/create/index.vue
#	src/views/crm/statistics/customer/index.vue
2024-03-30 12:54:51 +08:00
YunaiV 291dc51849 CRM:完善【排行版】功能 2024-03-30 11:48:41 +08:00
YunaiV 1b7d604858 CRM:code review【客户统计】的代码实现 2024-03-30 11:02:29 +08:00
YunaiV b51397fe19 CRM:优化【客户统计】的代码实现 2024-03-30 09:23:02 +08:00
sunva 68e1556378 SYSTEM:修复【系统管理/岗位管理/新增按钮与导出按钮】权限hasPermi错误 2024-03-29 17:06:56 +08:00
YunaiV 23150fcfc3 Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev
# Conflicts:
#	package.json
2024-03-28 19:30:02 +08:00
芋道源码 1f14bf4bda !404 feat: [CRM-客户分析]增加[时间间隔]选项
Merge pull request !404 from dhb52/dev
2024-03-28 11:18:52 +00:00
YunaiV b4677c4546 README:增加功能图,方便理解 2024-03-27 21:23:21 +08:00
YunaiV 9f3bc1ead8 BPM:修复 bpmnxml 为空时,展示的 key 被覆盖的问题 2024-03-27 18:51:06 +08:00
YunaiV 607c2b6c4a BPM:完善文档说明 2024-03-27 09:41:00 +08:00
puhui999 3820710c56 二次封装 form-create 组件,使用项目重新封装的文件上传组件实现文件上载 2024-03-26 10:59:03 +08:00
puhui999 cce1fadc33 CRM: 新增客户行业、来源、级别统计、区域数据统计 2024-03-24 22:22:09 +08:00
YunaiV 77971f9c96 Merge branch 'feature/bpm' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 2024-03-24 10:47:04 +08:00
YunaiV 624594bb5c BPM:处理打开 bpmn 设计器时,key 和 name 覆盖更新 2024-03-24 10:46:37 +08:00
YunaiV eed7bb2e1a BPM:流程模型的 icon 维护 2024-03-24 10:08:59 +08:00
YunaiV cc14963bc8 BPM:增加流程表达式的管理 2024-03-23 21:12:09 +08:00
YunaiV 7218e71884 BPM:增加任务监听器、执行监听器的管理 2024-03-23 19:23:19 +08:00
YunaiV faf4557783 BPM:增加任务监听器、执行监听器的管理 2024-03-23 15:58:38 +08:00
YunaiV 728cf15c45 BPM:增加「发起人自选」的任务审批人的分配策略 2024-03-23 00:54:33 +08:00
YunaiV 48f6624737 BPM:新增【流程任务】菜单,用于全部流程任务的查询 2024-03-22 09:07:04 +08:00
YunaiV 5286ad1cd6 BPM:新增【流程实例】菜单,用于全部流程实例的查询 2024-03-22 08:26:26 +08:00
jason ee12e691be 暂时去掉原有设计器的审批人设置, 避免缺少资源报错。后续需要改造 2024-03-21 21:14:16 +08:00
AhJindeg c2f98043df fix: 🐞 add inputChange for crontab 2024-03-21 17:20:01 +08:00
YunaiV 05b408d107 BPM:新增流程的重新发起 2024-03-21 00:34:10 +08:00
jason 0d4b6f6344 流程模型中增加仿钉钉设计器入口 2024-03-20 22:26:35 +08:00
jason 07dc725857 整合仿钉钉流程设计器 https://github.com/StavinLi/Workflow-Vue3 2024-03-20 22:26:35 +08:00
YunaiV d16194b794 BPM:优化 task 审批详情界面,增加开始时间、结束时间的展示 2024-03-20 21:33:48 +08:00
YunaiV d0f73344bf BPM:增强 model 流程模型部署时,各种参数校验 2024-03-20 18:51:49 +08:00
YunaiV 08dd4ed072 BPM:支持多表单,每个流程任务都可以绑定流程表单 2024-03-20 12:50:59 +08:00
YunaiV 501a1c2f4d BPM:新增流程分类表,替代现有的 bpm_category 数据字典 2024-03-19 19:49:52 +08:00
YunaiV a0f157c8b6 BPM:简化 userGroup 的实现 2024-03-19 12:13:54 +08:00
YunaiV 2d424fc9a6 BPM:优化 task 加减签的实现 2024-03-19 01:31:51 +08:00
YunaiV a40866e27f BPM:完善 task 转派的实现 2024-03-18 20:45:39 +08:00
YunaiV 60ddc45b9b BPM:增加 task 的审批建议 2024-03-18 18:41:11 +08:00
YunaiV 03b8bd5e22 BPM:调整抄送逻辑的实现,改成审批通过、不通过时,可选择抄送 2024-03-17 22:14:28 +08:00
YunaiV de29528165 BPM:简化 task 接口的 VO,做一些合并。虽然字段上有冗余,但是读代码难度降低 2024-03-17 17:25:40 +08:00
芋道源码 6b45ed7e78 !406 适配顶部导航
Merge pull request !406 from 疯狂的世界/dev
2024-03-17 01:25:50 +00:00
YunaiV 59c7c49efa BPM:流程实例的 status 状态实现,使用 Flowable 的 variables 存储,移除 bpm_process_instance_ext 表 2024-03-16 16:43:47 +08:00
YunaiV 33d59c8b7d BPM:新增顺序会签、或签的能力 2024-03-16 00:05:58 +08:00
YunaiV 5f7ccd4e7c BPM:重构流程分配人的实现,通过 BpmTaskCandidateStrategy 策略模式 2024-03-15 00:19:23 +08:00
YunaiV 960f27f6ef BPM:新增 flowable expression 表达式,替代现有 BpmTaskAssignScript,更加灵活 2024-03-14 12:54:28 +08:00
owen aa57d89bea Merge remote-tracking branch 'origin/dev' into dev 2024-03-14 00:13:58 +08:00
owen c5b1ad480f 适配顶部导航 2024-03-14 00:13:40 +08:00
YunaiV feadd022e7 BPM:重构审批人的分配规则实现,移除 bpm_task_assign_rule 表,存储在 bpmn 的 userTask 中 2024-03-13 21:18:26 +08:00
moon69 4c0a5e8c30 修复数据权限无法回显的问题 2024-03-10 11:59:49 +08:00
dhb52 b0d4c87d8e feat: [CRM-客户分析]增加[时间间隔]选项 2024-03-10 10:30:32 +08:00
芋道源码 1a0f0a11b1 !403 CRM:完善转移
Merge pull request !403 from puhui999/dev-crm
2024-03-09 10:35:39 +00:00
puhui999 d21c060903 CRM:完善转移 2024-03-09 17:57:24 +08:00
moon69 0230a4af01 修复数据权限无法回显的问题 2024-03-09 17:38:56 +08:00
芋道源码 2ed41ab164 !401 build(package): add prettier-eslint@16.3.0 #392
Merge pull request !401 from AhJindeg/N/A
2024-03-09 09:00:46 +00:00
YunaiV c89941d435 CRM:code review 客户总量统计 2024-03-09 16:51:42 +08:00
芋道源码 55856be1de !394 feat: CRM/数据统计/员工客户分析 初稿
Merge pull request !394 from dhb52/dev
2024-03-09 08:37:59 +00:00
芋道源码 a6e29c5615 !400 fix(LoginForm): update doSocialLogin
Merge pull request !400 from AhJindeg/dev
2024-03-09 07:35:04 +00:00
芋道源码 ecf9f26547 !398 删除重复的 pagination 事件
Merge pull request !398 from moon69/master
2024-03-09 07:34:06 +00:00
芋道源码 4dabfcba23 !399 CRM:完善数据权限,实现数据权限同时添加、同时转移
Merge pull request !399 from puhui999/dev-crm
2024-03-09 07:21:16 +00:00
AhJindeg 53c63ba68f build(package): 🔧 add prettier-eslint@16.3.0 #392
修复 settings.json 文件中设置格式化插件 rvest.vs-code-prettier-eslint 未生效问题。

Signed-off-by: AhJindeg <ahjindeg@gmail.com>
2024-03-07 07:42:59 +00:00
AhJindeg 2d4ed85e94 fix(LoginForm): 🐞 update doSocialLogin
- 修复手动填写租户名称时,点击取消按钮,登录按钮 loading 状态未取消,并且会继续执行 LoginApi.socialAuthRedirect 方法
2024-03-07 14:38:31 +08:00
dhb52 470c5076a3 fix: 合同摘要信息(客户转化率页面) api 地址错误 2024-03-05 23:16:57 +08:00
dhb52 762b51a5b4 fix: [CRM-客户统计]根据Code-Review 修改 2024-03-04 23:51:42 +08:00
puhui999 13e925e29f CRM:完善数据权限,实现数据权限同时添加、同时转移 2024-03-03 20:11:52 +08:00
moon69 e4e37d0cb4 删除重复的 pagination 事件 2024-03-03 16:18:53 +08:00
puhui999 3660cd26ae CRM: 完善回款合同回款计划编辑合同回显 2024-03-02 23:38:13 +08:00
puhui999 a31c995508 Merge remote-tracking branch 'yudao/dev' into dev-crm 2024-03-02 23:26:26 +08:00
dhb52 d0501ee0a6 feat: CRM/数据统计/员工客户分析 初稿 2024-03-01 00:10:38 +08:00
dhb52 3ddba81e24 Merge remote-tracking branch 'yudao/dev' into dev 2024-02-29 21:03:19 +08:00
puhui999 c9d5ec26a2 fix: 主子表 demo03 List props 2024-02-28 09:35:12 +08:00
dhb52 441b91381f feat: CRM/数据统计/客户总量分析 2024-02-27 23:45:13 +08:00
dhb52 0db264dcac Merge remote-tracking branch 'yudao/dev' into dev 2024-02-27 21:42:40 +08:00
dhb52 783f6283ca Merge remote-tracking branch 'yudao/dev' into dev 2024-02-25 21:07:23 +08:00
233 changed files with 21008 additions and 3536 deletions

No files matched your search

+2 -6
View File
@@ -1,20 +1,16 @@
# 开发环境:本地只启动前端项目,依赖开发环境(后端、APP)
NODE_ENV=development
NODE_ENV=production
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
# VITE_BASE_URL='http://dofast.demo.huizhizao.vip:20001'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
# 接口前缀
VITE_API_BASEPATH=/dev-api
# 接口地址
VITE_API_URL=/admin-api
@@ -37,4 +33,4 @@ VITE_OUT_DIR=dist
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
# 验证码的开关
VITE_APP_CAPTCHA_ENABLE=false
VITE_APP_CAPTCHA_ENABLE=true
-3
View File
@@ -11,9 +11,6 @@ VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
# 接口前缀
VITE_API_BASEPATH=/dev-api
# 接口地址
VITE_API_URL=/admin-api
-3
View File
@@ -11,9 +11,6 @@ VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
# 接口前缀
VITE_API_BASEPATH=
# 接口地址
VITE_API_URL=/admin-api
-3
View File
@@ -11,9 +11,6 @@ VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
# 接口前缀
VITE_API_BASEPATH=
# 接口地址
VITE_API_URL=/admin-api
-3
View File
@@ -11,9 +11,6 @@ VITE_UPLOAD_TYPE=server
# 上传路径
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
# 接口前缀
VITE_API_BASEPATH=
# 接口地址
VITE_API_URL=/admin-api
-1
View File
@@ -4,7 +4,6 @@ dist
dist-ssr
*.local
/dist*
*-lock.*
pnpm-debug
auto-*.d.ts
.idea
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

-1
View File
@@ -3,7 +3,6 @@
"christian-kohler.path-intellisense",
"vscode-icons-team.vscode-icons",
"davidanson.vscode-markdownlint",
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"mrmlnc.vscode-less",
+25 -21
View File
@@ -117,6 +117,8 @@
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
![功能图](/.image/common/system-feature.png)
### 工作流程
| | 功能 | 描述 |
@@ -129,6 +131,8 @@
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 |
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
![功能图](/.image/common/bpm-feature.png)
### 支付系统
| | 功能 | 描述 |
@@ -142,27 +146,27 @@ ps:核心功能已经实现,正在对接微信小程序中...
### 基础设施
| | 功能 | 描述 |
|-----|----------|----------------------------------------------|
| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
| 🚀 | 分布式锁 | 基于 Redis 实现分布式锁,满足并发场景 |
| 🚀 | 幂等组件 | 基于 Redis 实现幂等组件,解决重复请求问题 |
| 🚀 | 服务保障 | 基于 Resilience4j 实现服务的稳定性,包括限流、熔断等功能 |
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
| | 功能 | 描述 |
|----|----------|----------------------------------------------|
| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
![功能图](/.image/common/infra-feature.png)
### 数据报表
+10 -10
View File
@@ -1,15 +1,15 @@
{
"name": "yudao-ui-admin-vue3",
"version": "2.0.1-snapshot",
"version": "2.1.0-snapshot",
"description": "基于vue3、vite4、element-plus、typesScript",
"author": "xingyu",
"private": false,
"scripts": {
"i": "pnpm install",
"dev": "vite --mode local-dev",
"dev": "vite",
"dev-server": "vite --mode dev",
"ts:check": "vue-tsc --noEmit",
"build:local-dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev",
"build:local": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build",
"build:dev": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode local-dev",
"build:test": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test",
"build:stage": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage",
@@ -35,7 +35,7 @@
"@wangeditor/editor-for-vue": "^5.1.10",
"@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1",
"axios": "^1.6.7",
"axios": "^1.6.8",
"benz-amr-recorder": "^1.1.5",
"bpmn-js-token-simulation": "^0.10.0",
"camunda-bpmn-moddle": "^7.0.1",
@@ -46,7 +46,7 @@
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.5.3",
"element-plus": "2.6.1",
"fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0",
"jsencrypt": "^3.3.2",
@@ -55,15 +55,15 @@
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.0",
"pinia-plugin-persistedstate": "^3.2.1",
"qrcode": "^1.5.3",
"qs": "^6.11.2",
"qs": "^6.12.0",
"steady-xml": "^0.1.0",
"url": "^0.11.3",
"video.js": "^7.21.5",
"vue": "3.4.20",
"vue": "3.4.21",
"vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.9.1",
"vue-i18n": "9.10.2",
"vue-router": "^4.3.0",
"vue-types": "^5.1.1",
"vuedraggable": "^4.1.0",
@@ -102,6 +102,7 @@
"postcss-html": "^1.6.0",
"postcss-scss": "^4.0.9",
"prettier": "^3.2.5",
"prettier-eslint": "^16.3.0",
"rimraf": "^5.0.5",
"rollup": "^4.12.0",
"sass": "^1.69.5",
@@ -136,7 +137,6 @@
"url": "https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues"
},
"homepage": "https://gitee.com/yudaocode/yudao-ui-admin-vue3",
"packageManager": "pnpm@8.6.0",
"engines": {
"node": ">= 16.0.0",
"pnpm": ">=8.6.0"
+9614
View File
File diff suppressed because it is too large. Load diff
+43
View File
@@ -0,0 +1,43 @@
import request from '@/config/axios'
// BPM 流程分类 VO
export interface CategoryVO {
id: number // 分类编号
name: string // 分类名
code: string // 分类标志
status: number // 分类状态
sort: number // 分类排序
}
// BPM 流程分类 API
export const CategoryApi = {
// 查询流程分类分页
getCategoryPage: async (params: any) => {
return await request.get({ url: `/bpm/category/page`, params })
},
// 查询流程分类列表
getCategorySimpleList: async () => {
return await request.get({ url: `/bpm/category/simple-list` })
},
// 查询流程分类详情
getCategory: async (id: number) => {
return await request.get({ url: `/bpm/category/get?id=` + id })
},
// 新增流程分类
createCategory: async (data: CategoryVO) => {
return await request.post({ url: `/bpm/category/create`, data })
},
// 修改流程分类
updateCategory: async (data: CategoryVO) => {
return await request.put({ url: `/bpm/category/update`, data })
},
// 删除流程分类
deleteCategory: async (id: number) => {
return await request.delete({ url: `/bpm/category/delete?id=` + id })
}
}
+3 -2
View File
@@ -1,8 +1,9 @@
import request from '@/config/axios'
export const getProcessDefinitionBpmnXML = async (id: number) => {
export const getProcessDefinition = async (id: number, key: string) => {
return await request.get({
url: '/bpm/process-definition/get-bpmn-xml?id=' + id
url: '/bpm/process-definition/get',
params: { id, key }
})
}
+2 -2
View File
@@ -49,8 +49,8 @@ export const getFormPage = async (params) => {
}
// 获得动态表单的精简列表
export const getSimpleFormList = async () => {
export const getFormSimpleList = async () => {
return await request.get({
url: '/bpm/form/list-all-simple'
url: '/bpm/form/simple-list'
})
}
+1 -1
View File
@@ -2,7 +2,7 @@ import request from '@/config/axios'
export type LeaveVO = {
id: number
result: number
status: number
type: number
reason: string
processInstanceId: string
+42
View File
@@ -0,0 +1,42 @@
import request from '@/config/axios'
// BPM 流程表达式 VO
export interface ProcessExpressionVO {
id: number // 编号
name: string // 表达式名字
status: number // 表达式状态
expression: string // 表达式
}
// BPM 流程表达式 API
export const ProcessExpressionApi = {
// 查询BPM 流程表达式分页
getProcessExpressionPage: async (params: any) => {
return await request.get({ url: `/bpm/process-expression/page`, params })
},
// 查询BPM 流程表达式详情
getProcessExpression: async (id: number) => {
return await request.get({ url: `/bpm/process-expression/get?id=` + id })
},
// 新增BPM 流程表达式
createProcessExpression: async (data: ProcessExpressionVO) => {
return await request.post({ url: `/bpm/process-expression/create`, data })
},
// 修改BPM 流程表达式
updateProcessExpression: async (data: ProcessExpressionVO) => {
return await request.put({ url: `/bpm/process-expression/update`, data })
},
// 删除BPM 流程表达式
deleteProcessExpression: async (id: number) => {
return await request.delete({ url: `/bpm/process-expression/delete?id=` + id })
},
// 导出BPM 流程表达式 Excel
exportProcessExpression: async (params) => {
return await request.download({ url: `/bpm/process-expression/export-excel`, params })
}
}
+26 -28
View File
@@ -20,51 +20,49 @@ export type ProcessInstanceVO = {
endTime: string
}
export type ProcessInstanceCCVO = {
type: number,
taskName: string,
taskKey: string,
processInstanceName: string,
processInstanceKey: string,
startUserId: string,
options:string [],
export type ProcessInstanceCopyVO = {
type: number
taskName: string
taskKey: string
processInstanceName: string
processInstanceKey: string
startUserId: string
options: string[]
reason: string
}
export const getMyProcessInstancePage = async (params) => {
export const getProcessInstanceMyPage = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/my-page', params })
}
export const getProcessInstanceManagerPage = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/manager-page', params })
}
export const createProcessInstance = async (data) => {
return await request.post({ url: '/bpm/process-instance/create', data: data })
}
export const cancelProcessInstance = async (id: number, reason: string) => {
export const cancelProcessInstanceByStartUser = async (id: number, reason: string) => {
const data = {
id: id,
reason: reason
}
return await request.delete({ url: '/bpm/process-instance/cancel', data: data })
return await request.delete({ url: '/bpm/process-instance/cancel-by-start-user', data: data })
}
export const getProcessInstance = async (id: number) => {
export const cancelProcessInstanceByAdmin = async (id: number, reason: string) => {
const data = {
id: id,
reason: reason
}
return await request.delete({ url: '/bpm/process-instance/cancel-by-admin', data: data })
}
export const getProcessInstance = async (id: string) => {
return await request.get({ url: '/bpm/process-instance/get?id=' + id })
}
/**
* 抄送
* @param data 抄送数据
* @returns 是否抄送成功
*/
export const createProcessInstanceCC = async (data) => {
return await request.post({ url: '/bpm/process-instance/cc/create', data: data })
export const getProcessInstanceCopyPage = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/copy/page', params })
}
/**
* 抄送列表
* @param params
* @returns
*/
export const getProcessInstanceCCPage = async (params) => {
return await request.get({ url: '/bpm/process-instance/cc/my-page', params })
}
+40
View File
@@ -0,0 +1,40 @@
import request from '@/config/axios'
// BPM 流程监听器 VO
export interface ProcessListenerVO {
id: number // 编号
name: string // 监听器名字
type: string // 监听器类型
status: number // 监听器状态
event: string // 监听事件
valueType: string // 监听器值类型
value: string // 监听器值
}
// BPM 流程监听器 API
export const ProcessListenerApi = {
// 查询流程监听器分页
getProcessListenerPage: async (params: any) => {
return await request.get({ url: `/bpm/process-listener/page`, params })
},
// 查询流程监听器详情
getProcessListener: async (id: number) => {
return await request.get({ url: `/bpm/process-listener/get?id=` + id })
},
// 新增流程监听器
createProcessListener: async (data: ProcessListenerVO) => {
return await request.post({ url: `/bpm/process-listener/create`, data })
},
// 修改流程监听器
updateProcessListener: async (data: ProcessListenerVO) => {
return await request.put({ url: `/bpm/process-listener/update`, data })
},
// 删除流程监听器
deleteProcessListener: async (id: number) => {
return await request.delete({ url: `/bpm/process-listener/delete?id=` + id })
}
}
+26 -41
View File
@@ -4,78 +4,63 @@ export type TaskVO = {
id: number
}
export const getTodoTaskPage = async (params) => {
export const getTaskTodoPage = async (params: any) => {
return await request.get({ url: '/bpm/task/todo-page', params })
}
export const getDoneTaskPage = async (params) => {
export const getTaskDonePage = async (params: any) => {
return await request.get({ url: '/bpm/task/done-page', params })
}
export const completeTask = async (data) => {
return await request.put({ url: '/bpm/task/complete', data })
export const getTaskManagerPage = async (params: any) => {
return await request.get({ url: '/bpm/task/manager-page', params })
}
export const approveTask = async (data) => {
export const approveTask = async (data: any) => {
return await request.put({ url: '/bpm/task/approve', data })
}
export const rejectTask = async (data) => {
export const rejectTask = async (data: any) => {
return await request.put({ url: '/bpm/task/reject', data })
}
export const backTask = async (data) => {
return await request.put({ url: '/bpm/task/back', data })
}
export const updateTaskAssignee = async (data) => {
return await request.put({ url: '/bpm/task/update-assignee', data })
}
export const getTaskListByProcessInstanceId = async (processInstanceId) => {
export const getTaskListByProcessInstanceId = async (processInstanceId: string) => {
return await request.get({
url: '/bpm/task/list-by-process-instance-id?processInstanceId=' + processInstanceId
})
}
// 导出任务
export const exportTask = async (params) => {
return await request.download({ url: '/bpm/task/export', params })
}
// 获取所有可回退的节点
export const getReturnList = async (params) => {
return await request.get({ url: '/bpm/task/return-list', params })
export const getTaskListByReturn = async (id: string) => {
return await request.get({ url: '/bpm/task/list-by-return', params: { id } })
}
// 回退
export const returnTask = async (data) => {
export const returnTask = async (data: any) => {
return await request.put({ url: '/bpm/task/return', data })
}
/**
* 委派
*/
export const delegateTask = async (data) => {
// 委派
export const delegateTask = async (data: any) => {
return await request.put({ url: '/bpm/task/delegate', data })
}
/**
* 加签
*/
export const taskAddSign = async (data) => {
// 转派
export const transferTask = async (data: any) => {
return await request.put({ url: '/bpm/task/transfer', data })
}
// 加签
export const signCreateTask = async (data: any) => {
return await request.put({ url: '/bpm/task/create-sign', data })
}
/**
* 获取减签任务列表
*/
export const getChildrenTaskList = async (id: string) => {
return await request.get({ url: '/bpm/task/children-list?taskId=' + id })
}
/**
* 减签
*/
export const taskSubSign = async (data) => {
// 减签
export const signDeleteTask = async (data: any) => {
return await request.delete({ url: '/bpm/task/delete-sign', data })
}
// 获取减签任务列表
export const getChildrenTaskList = async (id: string) => {
return await request.get({ url: '/bpm/task/list-by-parent-task-id?parentTaskId=' + id })
}
-29
View File
@@ -1,29 +0,0 @@
import request from '@/config/axios'
export type TaskAssignVO = {
id: number
modelId: string
processDefinitionId: string
taskDefinitionKey: string
taskDefinitionName: string
options: string[]
type: number
}
export const getTaskAssignRuleList = async (params) => {
return await request.get({ url: '/bpm/task-assign-rule/list', params })
}
export const createTaskAssignRule = async (data: TaskAssignVO) => {
return await request.post({
url: '/bpm/task-assign-rule/create',
data: data
})
}
export const updateTaskAssignRule = async (data: TaskAssignVO) => {
return await request.put({
url: '/bpm/task-assign-rule/update',
data: data
})
}
+3 -3
View File
@@ -4,7 +4,7 @@ export type UserGroupVO = {
id: number
name: string
description: string
memberUserIds: number[]
userIds: number[]
status: number
remark: string
createTime: string
@@ -42,6 +42,6 @@ export const getUserGroupPage = async (params) => {
}
// 获取用户组精简信息列表
export const getSimpleUserGroupList = async (): Promise<UserGroupVO[]> => {
return await request.get({ url: '/bpm/user-group/list-all-simple' })
export const getUserGroupSimpleList = async (): Promise<UserGroupVO[]> => {
return await request.get({ url: '/bpm/user-group/simple-list' })
}
+1 -1
View File
@@ -1,5 +1,5 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/customer'
import { TransferReqVO } from '@/api/crm/permission'
export interface BusinessVO {
id: number
+1 -1
View File
@@ -1,5 +1,5 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/customer'
import { TransferReqVO } from '@/api/crm/permission'
export interface ClueVO {
id: number // 编号
+1 -1
View File
@@ -1,5 +1,5 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/customer'
import { TransferReqVO } from '@/api/crm/permission'
export interface ContactVO {
id: number // 编号
+1 -1
View File
@@ -1,5 +1,5 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/customer'
import { TransferReqVO } from '@/api/crm/permission'
export interface ContractVO {
id: number
+1 -6
View File
@@ -1,4 +1,5 @@
import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/permission'
export interface CustomerVO {
id: number // 编号
@@ -102,12 +103,6 @@ export const getCustomerSimpleList = async () => {
// ======================= 业务操作 =======================
export interface TransferReqVO {
id: number | undefined // 客户编号
newOwnerUserId: number | undefined // 新负责人的用户编号
oldOwnerPermissionLevel: number | undefined // 老负责人加入团队后的权限级别
}
// 客户转移
export const transferCustomer = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/customer/transfer', data })
+8
View File
@@ -6,6 +6,7 @@ export interface PermissionVO {
bizType: number // Crm 类型
bizId: number // Crm 类型数据编号
level: number // 权限级别
toBizTypes?: number[] // 同时添加至
deptName?: string // 部门名称
nickname?: string // 用户昵称
postNames?: string[] // 岗位名称数组
@@ -13,6 +14,13 @@ export interface PermissionVO {
ids?: number[]
}
export interface TransferReqVO {
id: number // 模块编号
newOwnerUserId: number // 新负责人的用户编号
oldOwnerPermissionLevel?: number // 老负责人加入团队后的权限级别
toBizTypes?: number[] // 转移客户时,需要额外有【联系人】【商机】【合同】的 checkbox 选择
}
/**
* CRM 业务类型枚举
*
+6 -4
View File
@@ -3,18 +3,20 @@ import request from '@/config/axios'
export interface ReceivableVO {
id: number
no: string
planId: number
customerId: number
planId?: number
customerId?: number
customerName?: string
contractId: number
contractId?: number
contract?: {
id?: number
name?: string
no: string
totalPrice: number
}
auditStatus: number
processInstanceId: number
returnTime: Date
returnType: string
returnType: number
price: number
ownerUserId: number
ownerUserName?: string
+1 -1
View File
@@ -11,7 +11,7 @@ export interface ReceivablePlanVO {
remindTime: Date
customerId: number
customerName?: string
contractId: number
contractId?: number
contractNo?: string
ownerUserId: number
ownerUserName?: string
+168
View File
@@ -0,0 +1,168 @@
import request from '@/config/axios'
export interface CrmStatisticsCustomerSummaryByDateRespVO {
time: string
customerCreateCount: number
customerDealCount: number
}
export interface CrmStatisticsCustomerSummaryByUserRespVO {
ownerUserName: string
customerCreateCount: number
customerDealCount: number
contractPrice: number
receivablePrice: number
}
export interface CrmStatisticsFollowUpSummaryByDateRespVO {
time: string
followUpRecordCount: number
followUpCustomerCount: number
}
export interface CrmStatisticsFollowUpSummaryByUserRespVO {
ownerUserName: string
followupRecordCount: number
followupCustomerCount: number
}
export interface CrmStatisticsFollowUpSummaryByTypeRespVO {
followUpType: string
followUpRecordCount: number
}
export interface CrmStatisticsCustomerContractSummaryRespVO {
customerName: string
contractName: string
totalPrice: number
receivablePrice: number
customerType: string
customerSource: string
ownerUserName: string
creatorUserName: string
createTime: Date
orderDate: Date
}
export interface CrmStatisticsPoolSummaryByDateRespVO {
time: string
customerPutCount: number
customerTakeCount: number
}
export interface CrmStatisticsPoolSummaryByUserRespVO {
ownerUserName: string
customerPutCount: number
customerTakeCount: number
}
export interface CrmStatisticsCustomerDealCycleByDateRespVO {
time: string
customerDealCycle: number
}
export interface CrmStatisticsCustomerDealCycleByUserRespVO {
ownerUserName: string
customerDealCycle: number
customerDealCount: number
}
export interface CrmStatisticsCustomerDealCycleByAreaRespVO {
areaName: string
customerDealCycle: number
customerDealCount: number
}
export interface CrmStatisticsCustomerDealCycleByProductRespVO {
productName: string
customerDealCycle: number
customerDealCount: number
}
// 客户分析 API
export const StatisticsCustomerApi = {
// 1.1 客户总量分析(按日期)
getCustomerSummaryByDate: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-summary-by-date',
params
})
},
// 1.2 客户总量分析(按用户)
getCustomerSummaryByUser: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-summary-by-user',
params
})
},
// 2.1 客户跟进次数分析(按日期)
getFollowUpSummaryByDate: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-follow-up-summary-by-date',
params
})
},
// 2.2 客户跟进次数分析(按用户)
getFollowUpSummaryByUser: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-follow-up-summary-by-user',
params
})
},
// 3.1 获取客户跟进方式统计数
getFollowUpSummaryByType: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-follow-up-summary-by-type',
params
})
},
// 4.1 合同摘要信息(客户转化率页面)
getContractSummary: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-contract-summary',
params
})
},
// 5.1 获取客户公海分析(按日期)
getPoolSummaryByDate: (param: any) => {
return request.get({
url: '/crm/statistics-customer/get-pool-summary-by-date',
params: param
})
},
// 5.2 获取客户公海分析(按用户)
getPoolSummaryByUser: (param: any) => {
return request.get({
url: '/crm/statistics-customer/get-pool-summary-by-user',
params: param
})
},
// 6.1 获取客户成交周期(按日期)
getCustomerDealCycleByDate: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-deal-cycle-by-date',
params
})
},
// 6.2 获取客户成交周期(按用户)
getCustomerDealCycleByUser: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-deal-cycle-by-user',
params
})
},
// 6.2 获取客户成交周期(按用户)
getCustomerDealCycleByArea: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-deal-cycle-by-area',
params
})
},
// 6.2 获取客户成交周期(按用户)
getCustomerDealCycleByProduct: (params: any) => {
return request.get({
url: '/crm/statistics-customer/get-customer-deal-cycle-by-product',
params
})
}
}
+58
View File
@@ -0,0 +1,58 @@
import request from '@/config/axios'
export interface CrmStatisticFunnelRespVO {
customerCount: number // 客户数
businessCount: number // 商机数
businessWinCount: number // 赢单数
}
export interface CrmStatisticsBusinessSummaryByDateRespVO {
time: string // 时间
businessCreateCount: number // 商机数
totalPrice: number | string // 商机金额
}
export interface CrmStatisticsBusinessInversionRateSummaryByDateRespVO {
time: string // 时间
businessCount: number // 商机数量
businessWinCount: number // 赢单商机数
}
// 客户分析 API
export const StatisticFunnelApi = {
// 1. 获取销售漏斗统计数据
getFunnelSummary: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-funnel-summary',
params
})
},
// 2. 获取商机结束状态统计
getBusinessSummaryByEndStatus: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-business-summary-by-end-status',
params
})
},
// 3. 获取新增商机分析(按日期)
getBusinessSummaryByDate: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-business-summary-by-date',
params
})
},
// 4. 获取商机转化率分析(按日期)
getBusinessInversionRateSummaryByDate: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-business-inversion-rate-summary-by-date',
params
})
},
// 5. 获取商机列表(按日期)
getBusinessPageByDate: (params: any) => {
return request.get({
url: '/crm/statistics-funnel/get-business-page-by-date',
params
})
}
}
+33
View File
@@ -0,0 +1,33 @@
import request from '@/config/axios'
export interface StatisticsPerformanceRespVO {
time: string
currentMonthCount: number
lastMonthCount: number
lastYearCount: number
}
// 排行 API
export const StatisticsPerformanceApi = {
// 员工获得合同金额统计
getContractPricePerformance: (params: any) => {
return request.get({
url: '/crm/statistics-performance/get-contract-price-performance',
params
})
},
// 员工获得回款统计
getReceivablePricePerformance: (params: any) => {
return request.get({
url: '/crm/statistics-performance/get-receivable-price-performance',
params
})
},
//员工获得签约合同数量统计
getContractCountPerformance: (params: any) => {
return request.get({
url: '/crm/statistics-performance/get-contract-count-performance',
params
})
}
}
+60
View File
@@ -0,0 +1,60 @@
import request from '@/config/axios'
export interface CrmStatisticCustomerBaseRespVO {
customerCount: number
dealCount: number
dealPortion: string | number
}
export interface CrmStatisticCustomerIndustryRespVO extends CrmStatisticCustomerBaseRespVO {
industryId: number
industryPortion: string | number
}
export interface CrmStatisticCustomerSourceRespVO extends CrmStatisticCustomerBaseRespVO {
source: number
sourcePortion: string | number
}
export interface CrmStatisticCustomerLevelRespVO extends CrmStatisticCustomerBaseRespVO {
level: number
levelPortion: string | number
}
export interface CrmStatisticCustomerAreaRespVO extends CrmStatisticCustomerBaseRespVO {
areaId: number
areaName: string
areaPortion: string | number
}
// 客户分析 API
export const StatisticsPortraitApi = {
// 1. 获取客户行业统计数据
getCustomerIndustry: (params: any) => {
return request.get({
url: '/crm/statistics-portrait/get-customer-industry-summary',
params
})
},
// 2. 获取客户来源统计数据
getCustomerSource: (params: any) => {
return request.get({
url: '/crm/statistics-portrait/get-customer-source-summary',
params
})
},
// 3. 获取客户级别统计数据
getCustomerLevel: (params: any) => {
return request.get({
url: '/crm/statistics-portrait/get-customer-level-summary',
params
})
},
// 4. 获取客户地区统计数据
getCustomerArea: (params: any) => {
return request.get({
url: '/crm/statistics-portrait/get-customer-area-summary',
params
})
}
}
+5 -1
View File
@@ -8,11 +8,15 @@ export interface ApiAccessLogVO {
applicationName: string
requestMethod: string
requestParams: string
responseBody: string
requestUrl: string
userIp: string
userAgent: string
operateModule: string
operateName: string
operateType: number
beginTime: Date
endTIme: Date
endTime: Date
duration: number
resultCode: number
resultMsg: string
-1
View File
@@ -28,7 +28,6 @@ export type CodegenColumnVO = {
columnComment: string
nullable: number
primaryKey: number
autoIncrement: boolean
ordinalPosition: number
javaType: string
javaField: string
-16
View File
@@ -1,16 +0,0 @@
import request from '@/config/axios'
// 导出Html
export const exportHtml = () => {
return request.download({ url: '/infra/db-doc/export-html' })
}
// 导出Word
export const exportWord = () => {
return request.download({ url: '/infra/db-doc/export-word' })
}
// 导出Markdown
export const exportMarkdown = () => {
return request.download({ url: '/infra/db-doc/export-markdown' })
}
+41 -37
View File
@@ -1,49 +1,53 @@
import request from '@/config/axios'
// 秒杀时段 VO
export interface SeckillConfigVO {
id: number
name: string
startTime: string
endTime: string
sliderPicUrls: string[]
status: number
id: number // 编号
name: string // 秒杀时段名称
startTime: string // 开始时间点
endTime: string // 结束时间点
sliderPicUrls: string[] // 秒杀轮播图
status: number // 活动状态
}
// 查询秒杀时段配置列表
export const getSeckillConfigPage = async (params) => {
return await request.get({ url: '/promotion/seckill-config/page', params })
}
// 秒杀时段 API
export const SeckillConfigApi = {
// 查询秒杀时段分页
getSeckillConfigPage: async (params: any) => {
return await request.get({ url: `/promotion/seckill-config/page`, params })
},
// 查询秒杀时段配置详情
export const getSeckillConfig = async (id: number) => {
return await request.get({ url: '/promotion/seckill-config/get?id=' + id })
}
// 查询秒杀时段列表
getSimpleSeckillConfigList: async () => {
return await request.get({ url: `/promotion/seckill-config/simple-list` })
},
// 获得所有开启状态的秒杀时段精简列表
export const getSimpleSeckillConfigList = async () => {
return await request.get({ url: '/promotion/seckill-config/list-all-simple' })
}
// 查询秒杀时段详情
getSeckillConfig: async (id: number) => {
return await request.get({ url: `/promotion/seckill-config/get?id=` + id })
},
// 新增秒杀时段配置
export const createSeckillConfig = async (data: SeckillConfigVO) => {
return await request.post({ url: '/promotion/seckill-config/create', data })
}
// 新增秒杀时段
createSeckillConfig: async (data: SeckillConfigVO) => {
return await request.post({ url: `/promotion/seckill-config/create`, data })
},
// 修改秒杀时段配置
export const updateSeckillConfig = async (data: SeckillConfigVO) => {
return await request.put({ url: '/promotion/seckill-config/update', data })
}
// 修改秒杀时段
updateSeckillConfig: async (data: SeckillConfigVO) => {
return await request.put({ url: `/promotion/seckill-config/update`, data })
},
// 修改时段配置状态
export const updateSeckillConfigStatus = (id: number, status: number) => {
const data = {
id,
status
// 删除秒杀时段
deleteSeckillConfig: async (id: number) => {
return await request.delete({ url: `/promotion/seckill-config/delete?id=` + id })
},
// 修改时段配置状态
updateSeckillConfigStatus: async (id: number, status: number) => {
const data = {
id,
status
}
return request.put({ url: '/promotion/seckill-config/update-status', data: data })
}
return request.put({ url: '/promotion/seckill-config/update-status', data: data })
}
// 删除秒杀时段配置
export const deleteSeckillConfig = async (id: number) => {
return await request.delete({ url: '/promotion/seckill-config/delete?id=' + id })
}
+1 -1
View File
@@ -5,7 +5,7 @@ import { formatDate } from '@/utils/formatTime'
/** 会员分析 Request VO */
export interface MemberAnalyseReqVO {
times: [dayjs.ConfigType, dayjs.ConfigType]
times: dayjs.ConfigType[]
}
/** 会员分析 Response VO */
-39
View File
@@ -1,39 +0,0 @@
import request from '@/config/axios'
export interface UReportDataVO {
id: number
name: string
status: number
content: string
remark: string
}
// 查询Ureport2报表分页
export const getUReportDataPage = async (params) => {
return await request.get({ url: `/report/ureport-data/page`, params })
}
// 查询Ureport2报表详情
export const getUReportData = async (id: number) => {
return await request.get({ url: `/report/ureport-data/get?id=` + id })
}
// 新增Ureport2报表
export const createUReportData = async (data: UReportDataVO) => {
return await request.post({ url: `/report/ureport-data/create`, data })
}
// 修改Ureport2报表
export const updateUReportData = async (data: UReportDataVO) => {
return await request.put({ url: `/report/ureport-data/update`, data })
}
// 删除Ureport2报表
export const deleteUReportData = async (id: number) => {
return await request.delete({ url: `/report/ureport-data/delete?id=` + id })
}
// 导出Ureport2报表 Excel
export const exportUReportData = async (params) => {
return await request.download({ url: `/report/ureport-data/export-excel`, params })
}
-40
View File
@@ -1,40 +0,0 @@
import request from '@/config/axios'
export interface ErrorCodeVO {
id: number | undefined
type: number
applicationName: string
code: number | undefined
message: string
memo: string
createTime: Date
}
// 查询错误码列表
export const getErrorCodePage = (params: PageParam) => {
return request.get({ url: '/system/error-code/page', params })
}
// 查询错误码详情
export const getErrorCode = (id: number) => {
return request.get({ url: '/system/error-code/get?id=' + id })
}
// 新增错误码
export const createErrorCode = (data: ErrorCodeVO) => {
return request.post({ url: '/system/error-code/create', data })
}
// 修改错误码
export const updateErrorCode = (data: ErrorCodeVO) => {
return request.put({ url: '/system/error-code/update', data })
}
// 删除错误码
export const deleteErrorCode = (id: number) => {
return request.delete({ url: '/system/error-code/delete?id=' + id })
}
// 导出错误码
export const excelErrorCode = (params) => {
return request.download({ url: '/system/error-code/export-excel', params })
}
+1
View File
@@ -8,6 +8,7 @@ export interface MailAccountVO {
host: string
port: number
sslEnable: boolean
starttlsEnable: boolean
}
// 查询邮箱账号列表
+1 -30
View File
@@ -2,30 +2,6 @@ import request from '@/config/axios'
export type OperateLogVO = {
id: number
userNickname: string
traceId: string
userId: number
module: string
name: string
type: number
content: string
exts: Map<String, Object>
requestMethod: string
requestUrl: string
userIp: string
userAgent: string
javaMethod: string
javaMethodArgs: string
startTime: Date
duration: number
resultCode: number
resultMsg: string
resultData: string
}
export type OperateLogV2VO = {
id: number
userNickname: string
traceId: string
userType: number
userId: number
@@ -42,11 +18,6 @@ export type OperateLogV2VO = {
creator: string
creatorName: string
createTime: Date
// 数据扩展,渲染时使用
title: string // 操作标题(如果为空则取 name 值)
colSize: number // 变更记录行数
contentStrList: string[]
tagsContentList: string[]
}
// 查询操作日志列表
@@ -54,6 +25,6 @@ export const getOperateLogPage = (params: PageParam) => {
return request.get({ url: '/system/operate-log/page', params })
}
// 导出操作日志
export const exportOperateLog = (params) => {
export const exportOperateLog = (params: any) => {
return request.download({ url: '/system/operate-log/export', params })
}
-58
View File
@@ -1,58 +0,0 @@
import request from '@/config/axios'
import qs from 'qs'
export interface SensitiveWordVO {
id: number
name: string
status: number
description: string
tags: string[]
createTime: Date
}
export interface SensitiveWordTestReqVO {
text: string
tag: string[]
}
// 查询敏感词列表
export const getSensitiveWordPage = (params: PageParam) => {
return request.get({ url: '/system/sensitive-word/page', params })
}
// 查询敏感词详情
export const getSensitiveWord = (id: number) => {
return request.get({ url: '/system/sensitive-word/get?id=' + id })
}
// 新增敏感词
export const createSensitiveWord = (data: SensitiveWordVO) => {
return request.post({ url: '/system/sensitive-word/create', data })
}
// 修改敏感词
export const updateSensitiveWord = (data: SensitiveWordVO) => {
return request.put({ url: '/system/sensitive-word/update', data })
}
// 删除敏感词
export const deleteSensitiveWord = (id: number) => {
return request.delete({ url: '/system/sensitive-word/delete?id=' + id })
}
// 导出敏感词
export const exportSensitiveWord = (params) => {
return request.download({ url: '/system/sensitive-word/export-excel', params })
}
// 获取所有敏感词的标签数组
export const getSensitiveWordTagList = () => {
return request.get({ url: '/system/sensitive-word/get-tags' })
}
// 获得文本所包含的不合法的敏感词数组
export const validateText = (query: SensitiveWordTestReqVO) => {
return request.get({
url: '/system/sensitive-word/validate-text?' + qs.stringify(query, { arrayFormat: 'repeat' })
})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

+5 -1
View File
@@ -503,9 +503,13 @@ const submit = () => {
emit('update:modelValue', defaultValue.value)
dialogVisible.value = false
}
const inputChange = () => {
emit('update:modelValue', defaultValue.value)
}
</script>
<template>
<el-input v-model="defaultValue" class="input-with-select" v-bind="$attrs">
<el-input v-model="defaultValue" class="input-with-select" v-bind="$attrs" @input="inputChange">
<template #append>
<el-select v-model="select" placeholder="生成器" style="width: 115px">
<el-option label="每分钟" value="0 * * * * ?" />
+3
View File
@@ -0,0 +1,3 @@
import DictSelect from './src/DictSelect.vue'
export { DictSelect }
@@ -0,0 +1,46 @@
<!-- 数据字典 Select 选择器 -->
<template>
<el-select class="w-1/1" v-bind="attrs">
<template v-if="valueType === 'int'">
<el-option
v-for="(dict, index) in getIntDictOptions(dictType)"
:key="index"
:label="dict.label"
:value="dict.value"
/>
</template>
<template v-if="valueType === 'str'">
<el-option
v-for="(dict, index) in getStrDictOptions(dictType)"
:key="index"
:label="dict.label"
:value="dict.value"
/>
</template>
<template v-if="valueType === 'bool'">
<el-option
v-for="(dict, index) in getBoolDictOptions(dictType)"
:key="index"
:label="dict.label"
:value="dict.value"
/>
</template>
</el-select>
</template>
<script lang="ts" setup>
import { getBoolDictOptions, getIntDictOptions, getStrDictOptions } from '@/utils/dict'
// 接受父组件参数
interface Props {
dictType: string // 字典类型
valueType: string // 字典值类型
}
withDefaults(defineProps<Props>(), {
dictType: '',
valueType: 'str'
})
const attrs = useAttrs()
defineOptions({ name: 'DictSelect' })
</script>
@@ -0,0 +1,90 @@
<template>
<div class="h-40px flex items-center justify-center">
<MagicCubeEditor
v-model="cellList"
class="m-b-16px"
:rows="1"
:cols="cellCount"
:cube-size="38"
@hot-area-selected="handleHotAreaSelected"
/>
<img src="@/assets/imgs/diy/app-nav-bar-mp.png" alt="" class="h-30px w-76px" v-if="isMp" />
</div>
<template v-for="(cell, cellIndex) in cellList" :key="cellIndex">
<template v-if="selectedHotAreaIndex === cellIndex">
<el-form-item label="类型" :prop="`cell[${cellIndex}].type`">
<el-radio-group v-model="cell.type">
<el-radio label="text">文字</el-radio>
<el-radio label="image">图片</el-radio>
<el-radio label="search">搜索框</el-radio>
</el-radio-group>
</el-form-item>
<!-- 1. 文字 -->
<template v-if="cell.type === 'text'">
<el-form-item label="内容" :prop="`cell[${cellIndex}].text`">
<el-input v-model="cell!.text" maxlength="10" show-word-limit />
</el-form-item>
<el-form-item label="颜色" :prop="`cell[${cellIndex}].text`">
<ColorInput v-model="cell!.textColor" />
</el-form-item>
</template>
<!-- 2. 图片 -->
<template v-else-if="cell.type === 'image'">
<el-form-item label="图片" :prop="`cell[${cellIndex}].imgUrl`">
<UploadImg v-model="cell.imgUrl" :limit="1" height="56px" width="56px">
<template #tip>建议尺寸 56*56</template>
</UploadImg>
</el-form-item>
<el-form-item label="链接" :prop="`cell[${cellIndex}].url`">
<AppLinkInput v-model="cell.url" />
</el-form-item>
</template>
<!-- 3. 搜索框 -->
<template v-else>
<el-form-item label="提示文字" :prop="`cell[${cellIndex}].placeholder`">
<el-input v-model="cell.placeholder" maxlength="10" show-word-limit />
</el-form-item>
<el-form-item label="圆角" :prop="`cell[${cellIndex}].borderRadius`">
<el-slider
v-model="cell.borderRadius"
:max="100"
:min="0"
show-input
input-size="small"
:show-input-controls="false"
/>
</el-form-item>
</template>
</template>
</template>
</template>
<script setup lang="ts">
import { NavigationBarCellProperty } from '../config'
import { usePropertyForm } from '@/components/DiyEditor/util'
// 导航栏属性面板
defineOptions({ name: 'NavigationBarCellProperty' })
const props = defineProps<{
modelValue: NavigationBarCellProperty[]
isMp: boolean
}>()
const emit = defineEmits(['update:modelValue'])
const { formData: cellList } = usePropertyForm(props.modelValue, emit)
if (!cellList.value) cellList.value = []
// 单元格数量:小程序6个(右侧胶囊按钮占了2个),其它平台8个
const cellCount = computed(() => (props.isMp ? 6 : 8))
// 选中的热区
const selectedHotAreaIndex = ref(0)
const handleHotAreaSelected = (cellValue: NavigationBarCellProperty, index: number) => {
selectedHotAreaIndex.value = index
if (!cellValue.type) {
cellValue.type = 'text'
cellValue.textColor = '#111111'
}
}
</script>
<style scoped lang="scss"></style>
@@ -2,22 +2,53 @@ import { DiyComponent } from '@/components/DiyEditor/util'
/** 顶部导航栏属性 */
export interface NavigationBarProperty {
// 页面标题
title: string
// 页面描述
description: string
// 顶部导航高度
navBarHeight: number
// 页面背景颜色
backgroundColor: string
// 页面背景图片
backgroundImage: string
// 背景类型
bgType: 'color' | 'img'
// 背景颜色
bgColor: string
// 图片链接
bgImg: string
// 样式类型:默认 | 沉浸式
styleType: 'default' | 'immersion'
styleType: 'normal' | 'inner'
// 常驻显示
alwaysShow: boolean
// 是否显示返回按钮
showGoBack: boolean
// 小程序单元格列表
mpCells: NavigationBarCellProperty[]
// 其它平台单元格列表
otherCells: NavigationBarCellProperty[]
// 本地变量
_local: {
// 预览顶部导航(小程序)
previewMp: boolean
// 预览顶部导航(非小程序)
previewOther: boolean
}
}
/** 顶部导航栏 - 单元格 属性 */
export interface NavigationBarCellProperty {
// 类型:文字 | 图片 | 搜索框
type: 'text' | 'image' | 'search'
// 宽度
width: number
// 高度
height: number
// 顶部位置
top: number
// 左侧位置
left: number
// 文字内容
text: string
// 文字颜色
textColor: string
// 图片地址
imgUrl: string
// 图片链接
url: string
// 搜索框:提示文字
placeholder: string
// 搜索框:边框圆角半径
borderRadius: number
}
// 定义组件
@@ -26,13 +57,26 @@ export const component = {
name: '顶部导航栏',
icon: 'tabler:layout-navbar',
property: {
title: '页面标题',
description: '',
navBarHeight: 35,
backgroundColor: '#fff',
backgroundImage: '',
styleType: 'default',
bgType: 'color',
bgColor: '#fff',
bgImg: '',
styleType: 'normal',
alwaysShow: true,
showGoBack: true
mpCells: [
{
type: 'text',
textColor: '#111111'
}
],
otherCells: [
{
type: 'text',
textColor: '#111111'
}
],
_local: {
previewMp: true,
previewOther: false
}
}
} as DiyComponent<NavigationBarProperty>
@@ -1,45 +1,73 @@
<template>
<div
class="navigation-bar"
:style="{
height: `${property.navBarHeight}px`,
backgroundColor: property.backgroundColor,
backgroundImage: `url(${property.backgroundImage})`
}"
>
<!-- 左侧 -->
<div class="left">
<Icon icon="ep:arrow-left" v-show="property.showGoBack" />
<div class="navigation-bar" :style="bgStyle">
<div class="h-full w-full flex items-center">
<div v-for="(cell, cellIndex) in cellList" :key="cellIndex" :style="getCellStyle(cell)">
<span v-if="cell.type === 'text'">{{ cell.text }}</span>
<img v-else-if="cell.type === 'image'" :src="cell.imgUrl" alt="" class="h-full w-full" />
<SearchBar v-else :property="getSearchProp" />
</div>
</div>
<!-- 中间 -->
<div
class="center"
:style="{
height: `${property.navBarHeight}px`,
lineHeight: `${property.navBarHeight}px`
}"
>
{{ property.title }}
</div>
<!-- 右侧 -->
<div class="right"></div>
<img
v-if="property._local?.previewMp"
src="@/assets/imgs/diy/app-nav-bar-mp.png"
alt=""
class="h-30px w-86px"
/>
</div>
</template>
<script setup lang="ts">
import { NavigationBarProperty } from './config'
import { NavigationBarCellProperty, NavigationBarProperty } from './config'
import SearchBar from '@/components/DiyEditor/components/mobile/SearchBar/index.vue'
import { StyleValue } from 'vue'
import { SearchProperty } from '@/components/DiyEditor/components/mobile/SearchBar/config'
/** 页面顶部导航栏 */
defineOptions({ name: 'NavigationBar' })
defineProps<{ property: NavigationBarProperty }>()
const props = defineProps<{ property: NavigationBarProperty }>()
// 背景
const bgStyle = computed(() => {
const background =
props.property.bgType === 'img' && props.property.bgImg
? `url(${props.property.bgImg}) no-repeat top center / 100% 100%`
: props.property.bgColor
return { background }
})
// 单元格列表
const cellList = computed(() =>
props.property._local?.previewMp ? props.property.mpCells : props.property.otherCells
)
// 单元格宽度
const cellWidth = computed(() => {
return props.property._local?.previewMp ? (375 - 80 - 86) / 6 : (375 - 90) / 8
})
// 获得单元格样式
const getCellStyle = (cell: NavigationBarCellProperty) => {
return {
width: cell.width * cellWidth.value + (cell.width - 1) * 10 + 'px',
left: cell.left * cellWidth.value + (cell.left + 1) * 10 + 'px',
position: 'absolute'
} as StyleValue
}
// 获得搜索框属性
const getSearchProp = (cell: NavigationBarCellProperty) => {
return {
height: 30,
showScan: false,
placeholder: cell.placeholder,
borderRadius: cell.borderRadius
} as SearchProperty
}
</script>
<style lang="scss" scoped>
.navigation-bar {
display: flex;
height: 35px;
height: 50px;
background: #fff;
justify-content: space-between;
align-items: center;
padding: 0 6px;
/* 左边 */
.left {
@@ -1,53 +1,73 @@
<template>
<el-form label-width="80px" :model="formData" :rules="rules">
<el-form-item label="页面标题" prop="title">
<el-input v-model="formData!.title" placeholder="页面标题" maxlength="25" show-word-limit />
</el-form-item>
<el-form-item label="页面描述" prop="description">
<el-input
type="textarea"
v-model="formData!.description"
placeholder="用户通过微信分享给朋友时,会自动显示页面描述"
/>
</el-form-item>
<el-form-item label="样式" prop="styleType">
<el-radio-group v-model="formData!.styleType">
<el-radio label="default">默认</el-radio>
<el-radio label="immersion">沉浸式</el-radio>
<el-radio label="normal">标准</el-radio>
<el-tooltip
content="沉侵式头部仅支持微信小程序、APP,建议页面第一个组件为图片展示类组件"
placement="top"
>
<el-radio label="inner">沉浸式</el-radio>
</el-tooltip>
</el-radio-group>
</el-form-item>
<el-form-item label="常驻显示" prop="alwaysShow" v-if="formData.styleType === 'immersion'">
<el-form-item label="常驻显示" prop="alwaysShow" v-if="formData.styleType === 'inner'">
<el-radio-group v-model="formData!.alwaysShow">
<el-radio :label="false">关闭</el-radio>
<el-radio :label="true">开启</el-radio>
<el-tooltip content="常驻显示关闭后,头部小组件将在页面滑动时淡入" placement="top">
<el-radio :label="true">开启</el-radio>
</el-tooltip>
</el-radio-group>
</el-form-item>
<el-form-item label="高度" prop="navBarHeight">
<el-slider
v-model="formData!.navBarHeight"
:max="100"
:min="35"
show-input
input-size="small"
/>
<el-form-item label="背景类型" prop="bgType">
<el-radio-group v-model="formData.bgType">
<el-radio label="color">纯色</el-radio>
<el-radio label="img">图片</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="返回按钮" prop="showGoBack">
<el-switch v-model="formData!.showGoBack" />
<el-form-item label="背景颜色" prop="bgColor" v-if="formData.bgType === 'color'">
<ColorInput v-model="formData.bgColor" />
</el-form-item>
<el-form-item label="背景颜色" prop="backgroundColor">
<ColorInput v-model="formData!.backgroundColor" />
</el-form-item>
<el-form-item label="背景图片" prop="backgroundImage">
<UploadImg v-model="formData!.backgroundImage" :limit="1">
<template #tip>建议宽度 750px</template>
</UploadImg>
<el-form-item label="背景图片" prop="bgImg" v-else>
<UploadImg v-model="formData.bgImg" :limit="1" width="56px" height="56px" />
</el-form-item>
<el-card class="property-group" shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span>内容(小程序)</span>
<el-form-item prop="_local.previewMp" class="m-b-0!">
<el-checkbox
v-model="formData._local.previewMp"
@change="formData._local.previewOther = !formData._local.previewMp"
>预览</el-checkbox
>
</el-form-item>
</div>
</template>
<NavigationBarCellProperty v-model="formData.mpCells" is-mp />
</el-card>
<el-card class="property-group" shadow="never">
<template #header>
<div class="flex items-center justify-between">
<span>内容(非小程序)</span>
<el-form-item prop="_local.previewOther" class="m-b-0!">
<el-checkbox
v-model="formData._local.previewOther"
@change="formData._local.previewMp = !formData._local.previewOther"
>预览</el-checkbox
>
</el-form-item>
</div>
</template>
<NavigationBarCellProperty v-model="formData.otherCells" :is-mp="false" />
</el-card>
</el-form>
</template>
<script setup lang="ts">
import { NavigationBarProperty } from './config'
import { usePropertyForm } from '@/components/DiyEditor/util'
import NavigationBarCellProperty from '@/components/DiyEditor/components/mobile/NavigationBar/components/CellProperty.vue'
// 导航栏属性面板
defineOptions({ name: 'NavigationBarProperty' })
// 表单校验
@@ -58,6 +78,9 @@ const rules = {
const props = defineProps<{ modelValue: NavigationBarProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
if (!formData.value._local) {
formData.value._local = { previewMp: true, previewOther: false }
}
</script>
<style scoped lang="scss"></style>
+3
View File
@@ -0,0 +1,3 @@
import { useFormCreateDesigner } from './src/useFormCreateDesigner'
export { useFormCreateDesigner }
@@ -0,0 +1,15 @@
import { useUploadFileRule } from './useUploadFileRule'
import { useUploadImgRule } from './useUploadImgRule'
import { useUploadImgsRule } from './useUploadImgsRule'
import { useDictSelectRule } from './useDictSelectRule'
import { useUserSelectRule } from './useUserSelectRule'
import { useEditorRule } from './useEditorRule'
export {
useUploadFileRule,
useUploadImgRule,
useUploadImgsRule,
useDictSelectRule,
useUserSelectRule,
useEditorRule
}
@@ -0,0 +1,71 @@
const selectRule = [
{ type: 'switch', field: 'multiple', title: '是否多选' },
{
type: 'switch',
field: 'disabled',
title: '是否禁用'
},
{ type: 'switch', field: 'clearable', title: '是否可以清空选项' },
{
type: 'switch',
field: 'collapseTags',
title: '多选时是否将选中值按文字的形式展示'
},
{
type: 'inputNumber',
field: 'multipleLimit',
title: '多选时用户最多可以选择的项目数,为 0 则不限制',
props: { min: 0 }
},
{
type: 'input',
field: 'autocomplete',
title: 'autocomplete 属性'
},
{ type: 'input', field: 'placeholder', title: '占位符' },
{
type: 'switch',
field: 'filterable',
title: '是否可搜索'
},
{ type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' },
{
type: 'input',
field: 'noMatchText',
title: '搜索条件无匹配时显示的文字'
},
{
type: 'switch',
field: 'remote',
title: '其中的选项是否从服务器远程加载'
},
{
type: 'Struct',
field: 'remoteMethod',
title: '自定义远程搜索方法'
},
{ type: 'input', field: 'noDataText', title: '选项为空时显示的文字' },
{
type: 'switch',
field: 'reserveKeyword',
title: '多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词'
},
{
type: 'switch',
field: 'defaultFirstOption',
title: '在输入框按下回车,选择第一个匹配项'
},
{
type: 'switch',
field: 'popperAppendToBody',
title: '是否将弹出框插入至 body 元素',
value: true
},
{
type: 'switch',
field: 'automaticDropdown',
title: '对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单'
}
]
export default selectRule
@@ -0,0 +1,59 @@
import { generateUUID } from '@/utils'
import * as DictDataApi from '@/api/system/dict/dict.type'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
import selectRule from '@/components/FormCreate/src/config/selectRule'
export const useDictSelectRule = () => {
const label = '字典选择器'
const name = 'DictSelect'
const dictOptions = ref<{ label: string; value: string }[]>([]) // 字典类型下拉数据
onMounted(async () => {
const data = await DictDataApi.getSimpleDictTypeList()
if (!data || data.length === 0) {
return
}
dictOptions.value =
data?.map((item: DictDataApi.DictTypeVO) => ({
label: item.name,
value: item.type
})) ?? []
})
return {
icon: 'icon-select',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'select',
field: 'dictType',
title: '字典类型',
value: '',
options: dictOptions.value
},
{
type: 'select',
field: 'valueType',
title: '字典值类型',
value: 'str',
options: [
{ label: '数字', value: 'int' },
{ label: '字符串', value: 'str' },
{ label: '布尔值', value: 'bool' }
]
},
...selectRule
])
}
}
}
@@ -0,0 +1,32 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
export const useEditorRule = () => {
const label = '富文本'
const name = 'Editor'
return {
icon: 'icon-editor',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'input',
field: 'height',
title: '高度'
},
{ type: 'switch', field: 'readonly', title: '是否只读' }
])
}
}
}
@@ -0,0 +1,80 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
export const useUploadFileRule = () => {
const label = '文件上传'
const name = 'UploadFile'
return {
icon: 'icon-upload',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'select',
field: 'fileType',
title: '文件类型',
value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],
options: [
{ label: 'doc', value: 'doc' },
{ label: 'xls', value: 'xls' },
{ label: 'ppt', value: 'ppt' },
{ label: 'txt', value: 'txt' },
{ label: 'pdf', value: 'pdf' }
],
props: {
multiple: true
}
},
{
type: 'switch',
field: 'autoUpload',
title: '是否在选取文件后立即进行上传',
value: true
},
{
type: 'switch',
field: 'drag',
title: '拖拽上传',
value: false
},
{
type: 'switch',
field: 'isShowTip',
title: '是否显示提示',
value: true
},
{
type: 'inputNumber',
field: 'fileSize',
title: '大小限制(MB)',
value: 5,
props: { min: 0 }
},
{
type: 'inputNumber',
field: 'limit',
title: '数量限制',
value: 5,
props: { min: 0 }
},
{
type: 'switch',
field: 'disabled',
title: '是否禁用',
value: false
}
])
}
}
}
@@ -0,0 +1,89 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
export const useUploadImgRule = () => {
const label = '单图上传'
const name = 'UploadImg'
return {
icon: 'icon-upload',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'switch',
field: 'drag',
title: '拖拽上传',
value: false
},
{
type: 'select',
field: 'fileType',
title: '图片类型限制',
value: ['image/jpeg', 'image/png', 'image/gif'],
options: [
{ label: 'image/apng', value: 'image/apng' },
{ label: 'image/bmp', value: 'image/bmp' },
{ label: 'image/gif', value: 'image/gif' },
{ label: 'image/jpeg', value: 'image/jpeg' },
{ label: 'image/pjpeg', value: 'image/pjpeg' },
{ label: 'image/svg+xml', value: 'image/svg+xml' },
{ label: 'image/tiff', value: 'image/tiff' },
{ label: 'image/webp', value: 'image/webp' },
{ label: 'image/x-icon', value: 'image/x-icon' }
],
props: {
multiple: true
}
},
{
type: 'inputNumber',
field: 'fileSize',
title: '大小限制(MB)',
value: 5,
props: { min: 0 }
},
{
type: 'input',
field: 'height',
title: '组件高度',
value: '150px'
},
{
type: 'input',
field: 'width',
title: '组件宽度',
value: '150px'
},
{
type: 'input',
field: 'borderradius',
title: '组件边框圆角',
value: '8px'
},
{
type: 'switch',
field: 'disabled',
title: '是否显示删除按钮',
value: true
},
{
type: 'switch',
field: 'showBtnText',
title: '是否显示按钮文字',
value: true
}
])
}
}
}
@@ -0,0 +1,84 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
export const useUploadImgsRule = () => {
const label = '多图上传'
const name = 'UploadImgs'
return {
icon: 'icon-upload',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [
makeRequiredRule(),
{
type: 'switch',
field: 'drag',
title: '拖拽上传',
value: false
},
{
type: 'select',
field: 'fileType',
title: '图片类型限制',
value: ['image/jpeg', 'image/png', 'image/gif'],
options: [
{ label: 'image/apng', value: 'image/apng' },
{ label: 'image/bmp', value: 'image/bmp' },
{ label: 'image/gif', value: 'image/gif' },
{ label: 'image/jpeg', value: 'image/jpeg' },
{ label: 'image/pjpeg', value: 'image/pjpeg' },
{ label: 'image/svg+xml', value: 'image/svg+xml' },
{ label: 'image/tiff', value: 'image/tiff' },
{ label: 'image/webp', value: 'image/webp' },
{ label: 'image/x-icon', value: 'image/x-icon' }
],
props: {
multiple: true
}
},
{
type: 'inputNumber',
field: 'fileSize',
title: '大小限制(MB)',
value: 5,
props: { min: 0 }
},
{
type: 'inputNumber',
field: 'limit',
title: '数量限制',
value: 5,
props: { min: 0 }
},
{
type: 'input',
field: 'height',
title: '组件高度',
value: '150px'
},
{
type: 'input',
field: 'width',
title: '组件宽度',
value: '150px'
},
{
type: 'input',
field: 'borderradius',
title: '组件边框圆角',
value: '8px'
}
])
}
}
}
@@ -0,0 +1,25 @@
import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
import selectRule from '@/components/FormCreate/src/config/selectRule'
export const useUserSelectRule = () => {
const label = '用户选择器'
const name = 'UserSelect'
return {
icon: 'icon-select',
label,
name,
rule() {
return {
type: name,
field: generateUUID(),
title: label,
info: '',
$required: false
}
},
props(_, { t }) {
return localeProps(t, name + '.props', [makeRequiredRule(), ...selectRule])
}
}
}
@@ -0,0 +1,53 @@
import {
useDictSelectRule,
useEditorRule,
useUploadFileRule,
useUploadImgRule,
useUploadImgsRule,
useUserSelectRule
} from './config'
import { Ref } from 'vue'
/**
* 表单设计器增强 hook
* 新增
* - 文件上传
* - 单图上传
* - 多图上传
* - 字典选择器
* - 系统用户选择器
* - 富文本
*/
export const useFormCreateDesigner = (designer: Ref) => {
const editorRule = useEditorRule()
const uploadFileRule = useUploadFileRule()
const uploadImgRule = useUploadImgRule()
const uploadImgsRule = useUploadImgsRule()
const dictSelectRule = useDictSelectRule()
const userSelectRule = useUserSelectRule()
onMounted(() => {
// 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代
designer.value?.removeMenuItem('upload')
// 移除自带的富文本组件规则,使用 editorRule 替代
designer.value?.removeMenuItem('fc-editor')
const components = [
editorRule,
uploadFileRule,
uploadImgRule,
uploadImgsRule,
dictSelectRule,
userSelectRule
]
components.forEach((component) => {
// 插入组件规则
designer.value?.addComponent(component)
// 插入拖拽按钮到 `main` 分类下
designer.value?.appendMenuItem('main', {
icon: component.icon,
name: component.name,
label: component.label
})
})
})
}
@@ -0,0 +1,79 @@
// TODO puhui999: 借鉴一下 form-create-designer utils 方法 🤣 (导入不了只能先 copy 过来用下)
export function makeRequiredRule() {
return {
type: 'Required',
field: 'formCreate$required',
title: '是否必填'
}
}
export const localeProps = (t, prefix, rules) => {
return rules.map((rule) => {
if (rule.field === 'formCreate$required') {
rule.title = t('props.required') || rule.title
} else if (rule.field && rule.field !== '_optionType') {
rule.title = t('components.' + prefix + '.' + rule.field) || rule.title
}
return rule
})
}
export function upper(str) {
return str.replace(str[0], str[0].toLocaleUpperCase())
}
export function makeOptionsRule(t, to, userOptions) {
console.log(userOptions[0])
const options = [
{ label: t('props.optionsType.struct'), value: 0 },
{ label: t('props.optionsType.json'), value: 1 },
{ label: '用户数据', value: 2 }
]
const control = [
{
value: 0,
rule: [
{
type: 'TableOptions',
field: 'formCreate' + upper(to).replace('.', '>'),
props: { defaultValue: [] }
}
]
},
{
value: 1,
rule: [
{
type: 'Struct',
field: 'formCreate' + upper(to).replace('.', '>'),
props: { defaultValue: [] }
}
]
},
{
value: 2,
rule: [
{
type: 'TableOptions',
field: 'formCreate' + upper(to).replace('.', '>'),
props: { modelValue: [] }
}
]
}
]
options.splice(0, 0)
control.push()
return {
type: 'radio',
title: t('props.options'),
field: '_optionType',
value: 0,
options,
props: {
type: 'button'
},
control
}
}
+1 -1
View File
@@ -189,7 +189,7 @@ const emit = defineEmits(['update:modelValue', 'hotAreaSelected'])
const emitUpdateModelValue = () => emit('update:modelValue', hotAreas)
// 热区选中
const selectedHotAreaIndex = ref(-1)
const selectedHotAreaIndex = ref(0)
const handleHotAreaSelected = (hotArea: Rect, index: number) => {
selectedHotAreaIndex.value = index
emit('hotAreaSelected', hotArea, index)
@@ -23,7 +23,7 @@
</template>
<script lang="ts" setup>
import { OperateLogV2VO } from '@/api/system/operatelog'
import { OperateLogVO } from '@/api/system/operatelog'
import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE, getDictLabel, getDictObj } from '@/utils/dict'
import { ElTag } from 'element-plus'
@@ -31,7 +31,7 @@ import { ElTag } from 'element-plus'
defineOptions({ name: 'OperateLogV2' })
interface Props {
logList: OperateLogV2VO[] // 操作日志列表
logList: OperateLogVO[] // 操作日志列表
}
withDefaults(defineProps<Props>(), {
+1 -1
View File
@@ -53,7 +53,7 @@ const props = defineProps({
}
})
const emit = defineEmits(['update:page', 'update:limit', 'pagination', 'pagination'])
const emit = defineEmits(['update:page', 'update:limit', 'pagination'])
const currentPage = computed({
get() {
return props.page
@@ -0,0 +1,237 @@
/* stylelint-disable order/properties-order */
<template>
<div class="add-node-btn-box">
<div class="add-node-btn">
<el-popover placement="right-start" v-model="visible" width="auto">
<div class="add-node-popover-body">
<a class="add-node-popover-item approver" @click="addType(1)">
<div class="item-wrapper">
<span class="iconfont"></span>
</div>
<p>审批人</p>
</a>
<a class="add-node-popover-item notifier" @click="addType(2)">
<div class="item-wrapper">
<span class="iconfont"></span>
</div>
<p>抄送人</p>
</a>
<a class="add-node-popover-item condition" @click="addType(4)">
<div class="item-wrapper">
<span class="iconfont"></span>
</div>
<p>条件分支</p>
</a>
</div>
<template #reference>
<button class="btn" type="button">
<span class="iconfont"></span>
</button>
</template>
</el-popover>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
let props = defineProps({
childNodeP: {
type: Object,
default: () => ({})
}
})
let emits = defineEmits(['update:childNodeP'])
let visible = ref(false)
const addType = (type) => {
visible.value = false
if (type != 4) {
var data
if (type == 1) {
data = {
nodeName: '审核人',
error: true,
type: 1,
settype: 1,
selectMode: 0,
selectRange: 0,
directorLevel: 1,
examineMode: 1,
noHanderAction: 1,
examineEndDirectorLevel: 0,
childNode: props.childNodeP,
nodeUserList: []
}
} else if (type == 2) {
data = {
nodeName: '抄送人',
type: 2,
ccSelfSelectFlag: 1,
childNode: props.childNodeP,
nodeUserList: []
}
}
emits('update:childNodeP', data)
} else {
emits('update:childNodeP', {
nodeName: '路由',
type: 4,
childNode: null,
conditionNodes: [
{
nodeName: '条件1',
error: true,
type: 3,
priorityLevel: 1,
conditionList: [],
nodeUserList: [],
childNode: props.childNodeP
},
{
nodeName: '条件2',
type: 3,
priorityLevel: 2,
conditionList: [],
nodeUserList: [],
childNode: null
}
]
})
}
}
</script>
<style scoped lang="scss">
.add-node-btn-box {
width: 240px;
display: inline-flex;
-ms-flex-negative: 0;
flex-shrink: 0;
-webkit-box-flex: 1;
-ms-flex-positive: 1;
position: relative;
&:before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
margin: auto;
width: 2px;
height: 100%;
background-color: #cacaca;
}
.add-node-btn {
user-select: none;
width: 240px;
padding: 20px 0 32px;
display: flex;
-webkit-box-pack: center;
justify-content: center;
flex-shrink: 0;
-webkit-box-flex: 1;
flex-grow: 1;
.btn {
outline: none;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
width: 30px;
height: 30px;
background: #3296fa;
border-radius: 50%;
position: relative;
border: none;
line-height: 30px;
-webkit-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
.iconfont {
color: #fff;
font-size: 16px;
}
&:hover {
transform: scale(1.3);
box-shadow: 0 13px 27px 0 rgba(0, 0, 0, 0.1);
}
&:active {
transform: none;
background: #1e83e9;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
}
}
}
}
.add-node-popover-body {
display: flex;
.add-node-popover-item {
margin-right: 10px;
cursor: pointer;
text-align: center;
flex: 1;
color: #191f25 !important;
.item-wrapper {
user-select: none;
display: inline-block;
width: 80px;
height: 80px;
margin-bottom: 5px;
background: #fff;
border: 1px solid #e2e2e2;
border-radius: 50%;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
.iconfont {
font-size: 35px;
line-height: 80px;
}
}
&.approver {
.item-wrapper {
color: #ff943e;
}
}
&.notifier {
.item-wrapper {
color: #3296fa;
}
}
&.condition {
.item-wrapper {
color: #15bc83;
}
}
&:hover {
.item-wrapper {
background: #3296fa;
box-shadow: 0 10px 20px 0 rgba(50, 150, 250, 0.4);
}
.iconfont {
color: #fff;
}
}
&:active {
.item-wrapper {
box-shadow: none;
background: #eaeaea;
}
.iconfont {
color: inherit;
}
}
}
}
</style>
@@ -0,0 +1,297 @@
<!-- eslint-disable vue/no-mutating-props -->
<!--
* @Date: 2022-09-21 14:41:53
* @LastEditors: StavinLi 495727881@qq.com
* @LastEditTime: 2023-05-24 15:20:24
* @FilePath: /Workflow-Vue3/src/components/nodeWrap.vue
-->
<template>
<div class="node-wrap" v-if="nodeConfig.type < 3">
<div class="node-wrap-box" :class="(nodeConfig.type == 0 ? 'start-node ' : '') +(isTried && nodeConfig.error ? 'active error' : '')">
<div class="title" :style="`background: rgb(${bgColors[nodeConfig.type]});`">
<span v-if="nodeConfig.type == 0">{{ nodeConfig.nodeName }}</span>
<template v-else>
<span class="iconfont">{{nodeConfig.type == 1?'':''}}</span>
<input
v-if="isInput"
type="text"
class="ant-input editable-title-input"
@blur="blurEvent()"
@focus="$event.currentTarget.select()"
v-focus
v-model="nodeConfig.nodeName"
:placeholder="defaultText"
/>
<span v-else class="editable-title" @click="clickEvent()">{{ nodeConfig.nodeName }}</span>
<i class="anticon anticon-close close" @click="delNode"></i>
</template>
</div>
<div class="content" @click="setPerson">
<div class="text">
<span class="placeholder" v-if="!showText">请选择{{defaultText}}</span>
{{showText}}
</div>
<i class="anticon anticon-right arrow"></i>
</div>
<div class="error_tip" v-if="isTried && nodeConfig.error">
<i class="anticon anticon-exclamation-circle"></i>
</div>
</div>
<addNode v-model:childNodeP="nodeConfig.childNode" />
</div>
<div class="branch-wrap" v-if="nodeConfig.type == 4">
<div class="branch-box-wrap">
<div class="branch-box">
<button class="add-branch" @click="addTerm">添加条件</button>
<div class="col-box" v-for="(item, index) in nodeConfig.conditionNodes" :key="index">
<div class="condition-node">
<div class="condition-node-box">
<div class="auto-judge" :class="isTried && item.error ? 'error active' : ''">
<div class="sort-left" v-if="index != 0" @click="arrTransfer(index, -1)">&lt;</div>
<div class="title-wrapper">
<input
v-if="isInputList[index]"
type="text"
class="ant-input editable-title-input"
@blur="blurEvent(index)"
@focus="$event.currentTarget.select()"
v-model="item.nodeName"
/>
<span v-else class="editable-title" @click="clickEvent(index)">{{ item.nodeName }}</span>
<span class="priority-title" @click="setPerson(item.priorityLevel)">优先级{{ item.priorityLevel }}</span>
<i class="anticon anticon-close close" @click="delTerm(index)"></i>
</div>
<div class="sort-right" v-if="index != nodeConfig.conditionNodes.length - 1" @click="arrTransfer(index)">&gt;</div>
<div class="content" @click="setPerson(item.priorityLevel)">{{ conditionStr(nodeConfig, index) }}</div>
<div class="error_tip" v-if="isTried && item.error">
<i class="anticon anticon-exclamation-circle"></i>
</div>
</div>
<addNode v-model:childNodeP="item.childNode" />
</div>
</div>
<nodeWrap v-if="item.childNode" v-model:nodeConfig="item.childNode" />
<template v-if="index == 0">
<div class="top-left-cover-line"></div>
<div class="bottom-left-cover-line"></div>
</template>
<template v-if="index == nodeConfig.conditionNodes.length - 1">
<div class="top-right-cover-line"></div>
<div class="bottom-right-cover-line"></div>
</template>
</div>
</div>
<addNode v-model:childNodeP="nodeConfig.childNode" />
</div>
</div>
<nodeWrap v-if="nodeConfig.childNode" v-model:nodeConfig="nodeConfig.childNode" />
</template>
<script setup>
import addNode from './addNode.vue'
import { onMounted, ref, watch, getCurrentInstance, computed } from 'vue'
import {
arrToStr,
conditionStr,
setApproverStr,
copyerStr,
bgColors,
placeholderList
} from './util'
import { useWorkFlowStoreWithOut } from '@/store/modules/simpleWorkflow'
let _uid = getCurrentInstance().uid
let props = defineProps({
nodeConfig: {
type: Object,
default: () => ({})
},
flowPermission: {
type: Object,
// eslint-disable-next-line vue/require-valid-default-prop
default: () => []
}
})
let defaultText = computed(() => {
return placeholderList[props.nodeConfig.type]
})
let showText = computed(() => {
if (props.nodeConfig.type == 0) return arrToStr(props.flowPermission) || '所有人'
if (props.nodeConfig.type == 1) return setApproverStr(props.nodeConfig)
return copyerStr(props.nodeConfig)
})
let isInputList = ref([])
let isInput = ref(false)
const resetConditionNodesErr = () => {
for (var i = 0; i < props.nodeConfig.conditionNodes.length; i++) {
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.conditionNodes[i].error =
conditionStr(props.nodeConfig, i) == '请设置条件' &&
i != props.nodeConfig.conditionNodes.length - 1
}
}
onMounted(() => {
if (props.nodeConfig.type == 1) {
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.error = !setApproverStr(props.nodeConfig)
} else if (props.nodeConfig.type == 2) {
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.error = !copyerStr(props.nodeConfig)
} else if (props.nodeConfig.type == 4) {
resetConditionNodesErr()
}
})
let emits = defineEmits(['update:flowPermission', 'update:nodeConfig'])
let store = useWorkFlowStoreWithOut()
let {
setPromoter,
setApprover,
setCopyer,
setCondition,
setFlowPermission,
setApproverConfig,
setCopyerConfig,
setConditionsConfig
} = store
let isTried = computed(() => store.isTried)
let flowPermission1 = computed(() => store.flowPermission1)
let approverConfig1 = computed(() => store.approverConfig1)
let copyerConfig1 = computed(() => store.copyerConfig1)
let conditionsConfig1 = computed(() => store.conditionsConfig1)
watch(flowPermission1, (flow) => {
if (flow.flag && flow.id === _uid) {
emits('update:flowPermission', flow.value)
}
})
watch(approverConfig1, (approver) => {
if (approver.flag && approver.id === _uid) {
emits('update:nodeConfig', approver.value)
}
})
watch(copyerConfig1, (copyer) => {
if (copyer.flag && copyer.id === _uid) {
emits('update:nodeConfig', copyer.value)
}
})
watch(conditionsConfig1, (condition) => {
if (condition.flag && condition.id === _uid) {
emits('update:nodeConfig', condition.value)
}
})
const clickEvent = (index) => {
if (index || index === 0) {
isInputList.value[index] = true
} else {
isInput.value = true
}
}
const blurEvent = (index) => {
if (index || index === 0) {
isInputList.value[index] = false
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.conditionNodes[index].nodeName =
props.nodeConfig.conditionNodes[index].nodeName || '条件'
} else {
isInput.value = false
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.nodeName = props.nodeConfig.nodeName || defaultText
}
}
const delNode = () => {
emits('update:nodeConfig', props.nodeConfig.childNode)
}
const addTerm = () => {
let len = props.nodeConfig.conditionNodes.length + 1
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.conditionNodes.push({
nodeName: '条件' + len,
type: 3,
priorityLevel: len,
conditionList: [],
nodeUserList: [],
childNode: null
})
resetConditionNodesErr()
emits('update:nodeConfig', props.nodeConfig)
}
const delTerm = (index) => {
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.conditionNodes.splice(index, 1)
props.nodeConfig.conditionNodes.map((item, index) => {
item.priorityLevel = index + 1
item.nodeName = `条件${index + 1}`
})
resetConditionNodesErr()
emits('update:nodeConfig', props.nodeConfig)
if (props.nodeConfig.conditionNodes.length == 1) {
if (props.nodeConfig.childNode) {
if (props.nodeConfig.conditionNodes[0].childNode) {
reData(props.nodeConfig.conditionNodes[0].childNode, props.nodeConfig.childNode)
} else {
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.conditionNodes[0].childNode = props.nodeConfig.childNode
}
}
emits('update:nodeConfig', props.nodeConfig.conditionNodes[0].childNode)
}
}
const reData = (data, addData) => {
if (!data.childNode) {
data.childNode = addData
} else {
reData(data.childNode, addData)
}
}
const setPerson = (priorityLevel) => {
var { type } = props.nodeConfig
if (type == 0) {
setPromoter(true)
setFlowPermission({
value: props.flowPermission,
flag: false,
id: _uid
})
} else if (type == 1) {
setApprover(true)
setApproverConfig({
value: {
...JSON.parse(JSON.stringify(props.nodeConfig)),
...{ settype: props.nodeConfig.settype ? props.nodeConfig.settype : 1 }
},
flag: false,
id: _uid
})
} else if (type == 2) {
setCopyer(true)
setCopyerConfig({
value: JSON.parse(JSON.stringify(props.nodeConfig)),
flag: false,
id: _uid
})
} else {
setCondition(true)
setConditionsConfig({
value: JSON.parse(JSON.stringify(props.nodeConfig)),
priorityLevel,
flag: false,
id: _uid
})
}
}
const arrTransfer = (index, type = 1) => {
//向左-1,向右1
// eslint-disable-next-line vue/no-mutating-props
props.nodeConfig.conditionNodes[index] = props.nodeConfig.conditionNodes.splice(
index + type,
1,
props.nodeConfig.conditionNodes[index]
)[0]
props.nodeConfig.conditionNodes.map((item, index) => {
item.priorityLevel = index + 1
})
resetConditionNodesErr()
emits('update:nodeConfig', props.nodeConfig)
}
</script>
@@ -0,0 +1,165 @@
/**
* todo
*/
export const arrToStr = (arr?: [{ name: string }]) => {
if (arr) {
return arr
.map((item) => {
return item.name
})
.toString()
}
}
export const setApproverStr = (nodeConfig: any) => {
if (nodeConfig.settype == 1) {
if (nodeConfig.nodeUserList.length == 1) {
return nodeConfig.nodeUserList[0].name
} else if (nodeConfig.nodeUserList.length > 1) {
if (nodeConfig.examineMode == 1) {
return arrToStr(nodeConfig.nodeUserList)
} else if (nodeConfig.examineMode == 2) {
return nodeConfig.nodeUserList.length + '人会签'
}
}
} else if (nodeConfig.settype == 2) {
const level =
nodeConfig.directorLevel == 1 ? '直接主管' : '第' + nodeConfig.directorLevel + '级主管'
if (nodeConfig.examineMode == 1) {
return level
} else if (nodeConfig.examineMode == 2) {
return level + '会签'
}
} else if (nodeConfig.settype == 4) {
if (nodeConfig.selectRange == 1) {
return '发起人自选'
} else {
if (nodeConfig.nodeUserList.length > 0) {
if (nodeConfig.selectRange == 2) {
return '发起人自选'
} else {
return '发起人从' + nodeConfig.nodeUserList[0].name + '中自选'
}
} else {
return ''
}
}
} else if (nodeConfig.settype == 5) {
return '发起人自己'
} else if (nodeConfig.settype == 7) {
return '从直接主管到通讯录中级别最高的第' + nodeConfig.examineEndDirectorLevel + '个层级主管'
}
}
export const copyerStr = (nodeConfig: any) => {
if (nodeConfig.nodeUserList.length != 0) {
return arrToStr(nodeConfig.nodeUserList)
} else {
if (nodeConfig.ccSelfSelectFlag == 1) {
return '发起人自选'
}
}
}
export const conditionStr = (nodeConfig, index) => {
const { conditionList, nodeUserList } = nodeConfig.conditionNodes[index]
if (conditionList.length == 0) {
return index == nodeConfig.conditionNodes.length - 1 &&
nodeConfig.conditionNodes[0].conditionList.length != 0
? '其他条件进入此流程'
: '请设置条件'
} else {
let str = ''
for (let i = 0; i < conditionList.length; i++) {
const {
columnId,
columnType,
showType,
showName,
optType,
zdy1,
opt1,
zdy2,
opt2,
fixedDownBoxValue
} = conditionList[i]
if (columnId == 0) {
if (nodeUserList.length != 0) {
str += '发起人属于:'
str +=
nodeUserList
.map((item) => {
return item.name
})
.join('或') + ' 并且 '
}
}
if (columnType == 'String' && showType == '3') {
if (zdy1) {
str += showName + '属于:' + dealStr(zdy1, JSON.parse(fixedDownBoxValue)) + ' 并且 '
}
}
if (columnType == 'Double') {
if (optType != 6 && zdy1) {
const optTypeStr = ['', '<', '>', '≤', '=', '≥'][optType]
str += `${showName} ${optTypeStr} ${zdy1} 并且 `
} else if (optType == 6 && zdy1 && zdy2) {
str += `${zdy1} ${opt1} ${showName} ${opt2} ${zdy2} 并且 `
}
}
}
return str ? str.substring(0, str.length - 4) : '请设置条件'
}
}
export const dealStr = (str: string, obj) => {
const arr = []
const list = str.split(',')
for (const elem in obj) {
list.map((item) => {
if (item == elem) {
arr.push(obj[elem].value)
}
})
}
return arr.join('或')
}
export const removeEle = (arr, elem, key = 'id') => {
let includesIndex
arr.map((item, index) => {
if (item[key] == elem[key]) {
includesIndex = index
}
})
arr.splice(includesIndex, 1)
}
export const bgColors = ['87, 106, 149', '255, 148, 62', '50, 150, 250']
export const placeholderList = ['发起人', '审核人', '抄送人']
export const setTypes = [
{ value: 1, label: '指定成员' },
{ value: 2, label: '主管' },
{ value: 4, label: '发起人自选' },
{ value: 5, label: '发起人自己' },
{ value: 7, label: '连续多级主管' }
]
export const selectModes = [
{ value: 1, label: '选一个人' },
{ value: 2, label: '选多个人' }
]
export const selectRanges = [
{ value: 1, label: '全公司' },
{ value: 2, label: '指定成员' },
{ value: 3, label: '指定角色' }
]
export const optTypes = [
{ value: '1', label: '小于' },
{ value: '2', label: '大于' },
{ value: '3', label: '小于等于' },
{ value: '4', label: '等于' },
{ value: '5', label: '大于等于' },
{ value: '6', label: '介于两个数之间' }
]
File diff suppressed because it is too large. Load diff
+25 -5
View File
@@ -6,7 +6,9 @@
:action="uploadUrl"
:auto-upload="autoUpload"
:before-upload="beforeUpload"
:disabled="disabled"
:drag="drag"
:http-request="httpRequest"
:limit="props.limit"
:multiple="props.limit > 1"
:on-error="excelUploadError"
@@ -15,15 +17,14 @@
:on-remove="handleRemove"
:on-success="handleFileSuccess"
:show-file-list="true"
:http-request="httpRequest"
class="upload-file-uploader"
name="file"
>
<el-button type="primary">
<el-button v-if="!disabled" type="primary">
<Icon icon="ep:upload-filled" />
选取文件
</el-button>
<template v-if="isShowTip" #tip>
<template v-if="isShowTip && !disabled" #tip>
<div style="font-size: 8px">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</div>
@@ -31,6 +32,25 @@
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b> 的文件
</div>
</template>
<template #file="row">
<div class="flex items-center">
<span>{{ row.file.name }}</span>
<div class="ml-10px">
<el-link
:href="row.file.url"
:underline="false"
download
target="_blank"
type="primary"
>
下载
</el-link>
</div>
<div class="ml-10px">
<el-button link type="danger" @click="handleRemove(row.file)"> 删除</el-button>
</div>
</div>
</template>
</el-upload>
</div>
</template>
@@ -48,13 +68,13 @@ const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
title: propTypes.string.def('文件上传'),
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']
fileSize: propTypes.number.def(5), // 大小限制(MB)
limit: propTypes.number.def(5), // 数量限制
autoUpload: propTypes.bool.def(true), // 自动上传
drag: propTypes.bool.def(false), // 拖拽上传
isShowTip: propTypes.bool.def(true) // 是否显示提示
isShowTip: propTypes.bool.def(true), // 是否显示提示
disabled: propTypes.bool.def(false) // 是否禁用上传组件 ==> 非必传(默认为 false)
})
// ========== 上传相关 ==========
+5 -6
View File
@@ -6,17 +6,18 @@
:action="uploadUrl"
:before-upload="beforeUpload"
:class="['upload', drag ? 'no-border' : '']"
:disabled="disabled"
:drag="drag"
:http-request="httpRequest"
:multiple="false"
:on-error="uploadError"
:on-success="uploadSuccess"
:show-file-list="false"
:http-request="httpRequest"
>
<template v-if="modelValue">
<img :src="modelValue" class="upload-image" />
<div class="upload-handle" @click.stop>
<div class="handle-icon" @click="editImg" v-if="!disabled">
<div v-if="!disabled" class="handle-icon" @click="editImg">
<Icon icon="ep:edit" />
<span v-if="showBtnText">{{ t('action.edit') }}</span>
</div>
@@ -77,10 +78,8 @@ const props = defineProps({
height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
borderradius: propTypes.string.def('8px'), // 组件边框圆角 ==> 非必传(默认为 8px)
// 是否显示删除按钮
showDelete: propTypes.bool.def(true),
// 是否显示按钮文字
showBtnText: propTypes.bool.def(true)
showDelete: propTypes.bool.def(true), // 是否显示删除按钮
showBtnText: propTypes.bool.def(true) // 是否显示按钮文字
})
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
+2 -1
View File
@@ -6,13 +6,14 @@
:action="uploadUrl"
:before-upload="beforeUpload"
:class="['upload', drag ? 'no-border' : '']"
:disabled="disabled"
:drag="drag"
:http-request="httpRequest"
:limit="limit"
:multiple="true"
:on-error="uploadError"
:on-exceed="handleExceed"
:on-success="uploadSuccess"
:http-request="httpRequest"
list-type="picture-card"
>
<div class="upload-empty">
+5 -1
View File
@@ -17,7 +17,11 @@ export const useUpload = () => {
// 1.2 获取文件预签名地址
const presignedInfo = await FileApi.getFilePresignedUrl(fileName)
// 1.3 上传文件(不能使用 ElUpload 的 ajaxUpload 方法的原因:其使用的是 FormData 上传,Minio 不支持)
return axios.put(presignedInfo.uploadUrl, options.file).then(() => {
return axios.put(presignedInfo.uploadUrl, options.file, {
headers: {
'Content-Type': options.file.type,
}
}).then(() => {
// 1.4. 记录文件信息到后端(异步)
createFile(presignedInfo, fileName, options.file)
// 通知成功,数据格式保持与后端上传的返回结果一致
@@ -436,7 +436,7 @@ const initBpmnModeler = () => {
// bpmnModeler.createDiagram()
console.log(bpmnModeler, 'bpmnModeler111111')
// console.log(bpmnModeler, 'bpmnModeler111111')
emit('init-finished', bpmnModeler)
initModelListeners()
}
@@ -666,10 +666,10 @@ const previewProcessJson = () => {
}
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
const processSave = async () => {
console.log(bpmnModeler, 'bpmnModelerbpmnModelerbpmnModelerbpmnModeler')
// console.log(bpmnModeler, 'bpmnModelerbpmnModelerbpmnModelerbpmnModeler')
const { err, xml } = await bpmnModeler.saveXML()
console.log(err, 'errerrerrerrerr')
console.log(xml, 'xmlxmlxmlxmlxml')
// console.log(err, 'errerrerrerrerr')
// console.log(xml, 'xmlxmlxmlxmlxml')
// 读取异常时抛出异常
if (err) {
// this.$modal.msgError('保存模型失败,请重试!')
@@ -115,19 +115,19 @@ const highlightDiagram = async () => {
if (!task) {
return
}
//进行中的任务已经高亮过了,则不高亮后面的任务了
// 进行中的任务已经高亮过了,则不高亮后面的任务了
if (findProcessTask) {
removeTaskDefinitionKeyList.push(n.id)
return
}
// 高亮任务
canvas.addMarker(n.id, getResultCss(task.result))
canvas.addMarker(n.id, getResultCss(task.status))
//标记是否高亮了进行中任务
if (task.result === 1) {
if (task.status === 1) {
findProcessTask = true
}
// 如果非通过,就不走后面的线条了
if (task.result !== 2) {
if (task.status !== 2) {
return
}
// 处理 outgoing 出线
@@ -194,6 +194,7 @@ const highlightDiagram = async () => {
})
} else if (n.$type === 'bpmn:StartEvent') {
// 开始节点
canvas.addMarker(n.id, 'highlight')
n.outgoing?.forEach((nn) => {
// outgoing 例如说【bpmn:SequenceFlow】连线
// 获得连线是否有指向目标。如果有,则进行高亮
@@ -205,10 +206,10 @@ const highlightDiagram = async () => {
})
} else if (n.$type === 'bpmn:EndEvent') {
// 结束节点
if (!processInstance.value || processInstance.value.result === 1) {
if (!processInstance.value || processInstance.value.status === 1) {
return
}
canvas.addMarker(n.id, getResultCss(processInstance.value.result))
canvas.addMarker(n.id, getResultCss(processInstance.value.status))
} else if (n.$type === 'bpmn:ServiceTask') {
//服务任务
if (activity.startTime > 0 && activity.endTime === 0) {
@@ -223,39 +224,49 @@ const highlightDiagram = async () => {
canvas.addMarker(out.id, getResultCss(2))
})
}
} else if (n.$type === 'bpmn:SequenceFlow') {
let targetActivity = activityList.find((m: any) => m.key === n.targetRef.id)
if (targetActivity) {
canvas.addMarker(n.id, getActivityHighlightCss(targetActivity))
}
}
})
if (!isEmpty(removeTaskDefinitionKeyList)) {
taskList.value = taskList.value.filter(
(item) => !removeTaskDefinitionKeyList.includes(item.definitionKey)
(item) => !removeTaskDefinitionKeyList.includes(item.taskDefinitionKey)
)
}
}
const getActivityHighlightCss = (activity) => {
return activity.endTime ? 'highlight' : 'highlight-todo'
}
const getResultCss = (result) => {
if (result === 1) {
const getResultCss = (status) => {
if (status === 1) {
// 审批中
return 'highlight-todo'
} else if (result === 2) {
} else if (status === 2) {
// 已通过
return 'highlight'
} else if (result === 3) {
} else if (status === 3) {
// 不通过
return 'highlight-reject'
} else if (result === 4) {
} else if (status === 4) {
// 已取消
return 'highlight-cancel'
} else if (result === 5) {
} else if (status === 5) {
// 退回
return 'highlight-return'
} else if (result === 6) {
} else if (status === 6) {
// 委派
return 'highlight-return'
} else if (result === 7 || result === 8 || result === 9) {
// 待后加签任务完成/待前加签任务完成/待前置任务完成
return 'highlight-return'
return 'highlight-todo'
} else if (status === 7) {
// 审批通过中
return 'highlight-todo'
} else if (status === 0) {
// 待审批
return 'highlight-todo'
}
return ''
}
@@ -296,10 +307,10 @@ const elementHover = (element) => {
!elementOverlayIds.value && (elementOverlayIds.value = {})
!overlays.value && (overlays.value = bpmnModeler.get('overlays'))
// 展示信息
console.log(activityLists.value, 'activityLists.value')
console.log(element.value, 'element.value')
// console.log(activityLists.value, 'activityLists.value')
// console.log(element.value, 'element.value')
const activity = activityLists.value.find((m) => m.key === element.value.id)
console.log(activity, 'activityactivityactivityactivity')
// console.log(activity, 'activityactivityactivityactivity')
if (!activity) {
return
}
@@ -313,15 +324,14 @@ const elementHover = (element) => {
<p>部门:${processInstance.value.startUser.deptName}</p>
<p>创建时间:${formatDate(processInstance.value.createTime)}`
} else if (element.value.type === 'bpmn:UserTask') {
// debugger
let task = taskList.value.find((m) => m.id === activity.taskId) // 找到活动对应的 taskId
if (!task) {
return
}
let optionData = getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)
let optionData = getIntDictOptions(DICT_TYPE.BPM_TASK_STATUS)
let dataResult = ''
optionData.forEach((element) => {
if (element.value == task.result) {
if (element.value == task.status) {
dataResult = element.label
}
})
@@ -333,7 +343,7 @@ const elementHover = (element) => {
// <p>部门:${task.assigneeUser.deptName}</p>
// <p>结果:${getIntDictOptions(
// DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
// task.result
// task.status
// )}</p>
// <p>创建时间:${formatDate(task.createTime)}</p>`
if (task.endTime) {
@@ -351,29 +361,30 @@ const elementHover = (element) => {
}
console.log(html)
} else if (element.value.type === 'bpmn:EndEvent' && processInstance.value) {
let optionData = getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)
let optionData = getIntDictOptions(DICT_TYPE.BPM_TASK_STATUS)
let dataResult = ''
optionData.forEach((element) => {
if (element.value == processInstance.value.result) {
if (element.value == processInstance.value.status) {
dataResult = element.label
}
})
html = `<p>结果:${dataResult}</p>`
// html = `<p>结果:${getIntDictOptions(
// DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT,
// processInstance.value.result
// processInstance.value.status
// )}</p>`
if (processInstance.value.endTime) {
html += `<p>结束时间:${formatDate(processInstance.value.endTime)}</p>`
}
}
console.log(html, 'html111111111111111')
// console.log(html, 'html111111111111111')
elementOverlayIds.value[element.value.id] = toRaw(overlays.value)?.add(element.value, {
position: { left: 0, bottom: 0 },
html: `<div class="element-overlays">${html}</div>`
})
}
}
// 流程图的元素被 out
const elementOut = (element) => {
toRaw(overlays.value).remove({ element })
@@ -389,6 +400,7 @@ onMounted(() => {
// 初始模型的监听器
initModelListeners()
})
onBeforeUnmount(() => {
// this.$once('hook:beforeDestroy', () => {
// })
@@ -427,7 +439,7 @@ watch(
)
</script>
<style>
<style lang="scss">
/** 处理中 */
.highlight-todo.djs-connection > .djs-visual > path {
stroke: #1890ff !important;
@@ -501,6 +513,10 @@ watch(
stroke: green !important;
}
.djs-element.highlight > .djs-visual > path {
stroke: green !important;
}
/** 不通过 */
.highlight-reject.djs-shape .djs-visual > :nth-child(1) {
fill: red !important;
@@ -520,6 +536,7 @@ watch(
.highlight-reject.djs-connection > .djs-visual > path {
stroke: red !important;
marker-end: url(#sequenceflow-end-white-success) !important;
}
.highlight-reject:not(.djs-connection) .djs-visual > :nth-child(1) {
@@ -332,6 +332,16 @@
"name": "multiinstance_condition",
"isAttr": true,
"type": "String"
},
{
"name": "candidateStrategy",
"isAttr": true,
"type": "String"
},
{
"name": "candidateParam",
"isAttr": true,
"type": "String"
}
]
},
@@ -319,6 +319,16 @@
"name": "priority",
"isAttr": true,
"type": "String"
},
{
"name": "candidateStrategy",
"isAttr": true,
"type": "String"
},
{
"name": "candidateParam",
"isAttr": true,
"type": "String"
}
]
},
@@ -319,6 +319,16 @@
"name": "priority",
"isAttr": true,
"type": "String"
},
{
"name": "candidateStrategy",
"isAttr": true,
"type": "String"
},
{
"name": "candidateParam",
"isAttr": true,
"type": "String"
}
]
},
@@ -24,15 +24,10 @@
</el-collapse-item>
<el-collapse-item name="condition" v-if="formVisible" key="form">
<template #title><Icon icon="ep:list" />表单</template>
<!-- <element-form :id="elementId" :type="elementType" /> -->
友情提示:使用
<router-link :to="{ path: '/bpm/manager/form' }"
><el-link type="danger">流程表单</el-link>
</router-link>
替代,提供更好的表单设计功能
<element-form :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="task" v-if="elementType.indexOf('Task') !== -1" key="task">
<template #title><Icon icon="ep:checked" />任务</template>
<template #title><Icon icon="ep:checked" />任务(审批人)</template>
<element-task :id="elementId" :type="elementType" />
</el-collapse-item>
<el-collapse-item
@@ -40,7 +35,7 @@
v-if="elementType.indexOf('Task') !== -1"
key="multiInstance"
>
<template #title><Icon icon="ep:help-filled" />多实例</template>
<template #title><Icon icon="ep:help-filled" />多实例(会签配置)</template>
<element-multi-instance :business-object="elementBusinessObject" :type="elementType" />
</el-collapse-item>
<el-collapse-item name="listeners" key="listeners">
@@ -3,13 +3,6 @@
<el-form label-width="90px" :model="needProps" :rules="rules">
<div v-if="needProps.type == 'bpmn:Process'">
<!-- 如果是 Process 信息的时候,使用自定义表单 -->
<el-link
href="https://doc.iocoder.cn/bpm/#_3-%E6%B5%81%E7%A8%8B%E5%9B%BE%E7%A4%BA%E4%BE%8B"
type="danger"
target="_blank"
>
如何实现实现会签、或签?
</el-link>
<el-form-item label="流程标识" prop="id">
<el-input
v-model="needProps.id"
@@ -68,13 +61,13 @@ const resetBaseInfo = () => {
console.log(bpmnElement.value, 'bpmnElement')
bpmnElement.value = bpmnInstances()?.bpmnElement
console.log(bpmnElement.value, 'resetBaseInfo11111111111')
// console.log(bpmnElement.value, 'resetBaseInfo11111111111')
elementBaseInfo.value = bpmnElement.value.businessObject
needProps.value['type'] = bpmnElement.value.businessObject.$type
// elementBaseInfo.value['typess'] = bpmnElement.value.businessObject.$type
// elementBaseInfo.value = JSON.parse(JSON.stringify(bpmnElement.value.businessObject))
console.log(elementBaseInfo.value, 'elementBaseInfo22222222222')
// console.log(elementBaseInfo.value, 'elementBaseInfo22222222222')
}
const handleKeyUpdate = (value) => {
// 校验 value 的值,只有 XML NCName 通过的情况下,才进行赋值。否则,会导致流程图报错,无法绘制的问题
@@ -121,11 +114,11 @@ const updateBaseInfo = (key) => {
// id: elementBaseInfo.value[key]
// // di: { id: `${elementBaseInfo.value[key]}_di` }
// }
console.log(elementBaseInfo, 'elementBaseInfo11111111111')
// console.log(elementBaseInfo, 'elementBaseInfo11111111111')
needProps.value = { ...elementBaseInfo.value, ...needProps.value }
if (key === 'id') {
console.log('jinru')
// console.log('jinru')
console.log(window, 'window')
console.log(bpmnElement.value, 'bpmnElement')
console.log(toRaw(bpmnElement.value), 'bpmnElement')
@@ -138,20 +131,11 @@ const updateBaseInfo = (key) => {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), attrObj)
}
}
onMounted(() => {
// 针对上传的 bpmn 流程图时,需要延迟 1 秒的时间,保证 key 和 name 的更新
setTimeout(() => {
console.log(props.model, 'props.model')
handleKeyUpdate(props.model.key)
handleNameUpdate(props.model.name)
console.log(props, 'propsssssssssssssssssssss')
}, 1000)
})
watch(
() => props.businessObject,
(val) => {
console.log(val, 'val11111111111111111111')
// console.log(val, 'val11111111111111111111')
if (val) {
// nextTick(() => {
resetBaseInfo()
@@ -159,6 +143,18 @@ watch(
}
}
)
watch(
() => props.model?.key,
(val) => {
// 针对上传的 bpmn 流程图时,保证 key 和 name 的更新
if (val) {
handleKeyUpdate(props.model.key)
handleNameUpdate(props.model.name)
}
}
)
// watch(
// () => ({ ...props }),
// (oldVal, newVal) => {
@@ -1,228 +1,233 @@
<template>
<div class="panel-tab__content">
<el-form label-width="80px">
<el-form-item label="表单标识">
<el-input v-model="formKey" clearable @change="updateElementFormKey" />
</el-form-item>
<el-form-item label="业务标识">
<el-select v-model="businessKey" @change="updateElementBusinessKey">
<el-option v-for="i in fieldList" :key="i.id" :value="i.id" :label="i.label" />
<el-option label="无" value="" />
<el-form-item label="流程表单">
<!-- <el-input v-model="formKey" clearable @change="updateElementFormKey" />-->
<el-select v-model="formKey" clearable @change="updateElementFormKey">
<el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
</el-select>
</el-form-item>
<!-- <el-form-item label="业务标识">-->
<!-- <el-select v-model="businessKey" @change="updateElementBusinessKey">-->
<!-- <el-option v-for="i in fieldList" :key="i.id" :value="i.id" :label="i.label" />-->
<!-- <el-option label="无" value="" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
</el-form>
<!--字段列表-->
<div class="element-property list-property">
<el-divider><Icon icon="ep:coin" /> 表单字段</el-divider>
<el-table :data="fieldList" max-height="240" fit border>
<el-table-column label="序号" type="index" width="50px" />
<el-table-column label="字段名称" prop="label" min-width="80px" show-overflow-tooltip />
<el-table-column
label="字段类型"
prop="type"
min-width="80px"
:formatter="(row) => fieldType[row.type] || row.type"
show-overflow-tooltip
/>
<el-table-column
label="默认值"
prop="defaultValue"
min-width="80px"
show-overflow-tooltip
/>
<el-table-column label="操作" width="90px">
<template #default="scope">
<el-button type="primary" link @click="openFieldForm(scope, scope.$index)"
>编辑</el-button
>
<el-divider direction="vertical" />
<el-button
type="primary"
link
style="color: #ff4d4f"
@click="removeField(scope, scope.$index)"
>移除</el-button
>
</template>
</el-table-column>
</el-table>
</div>
<div class="element-drawer__button">
<XButton type="primary" proIcon="ep:plus" title="添加字段" @click="openFieldForm(null, -1)" />
</div>
<!-- <div class="element-property list-property">-->
<!-- <el-divider><Icon icon="ep:coin" /> 表单字段</el-divider>-->
<!-- <el-table :data="fieldList" max-height="240" fit border>-->
<!-- <el-table-column label="序号" type="index" width="50px" />-->
<!-- <el-table-column label="字段名称" prop="label" min-width="80px" show-overflow-tooltip />-->
<!-- <el-table-column-->
<!-- label="字段类型"-->
<!-- prop="type"-->
<!-- min-width="80px"-->
<!-- :formatter="(row) => fieldType[row.type] || row.type"-->
<!-- show-overflow-tooltip-->
<!-- />-->
<!-- <el-table-column-->
<!-- label="默认值"-->
<!-- prop="defaultValue"-->
<!-- min-width="80px"-->
<!-- show-overflow-tooltip-->
<!-- />-->
<!-- <el-table-column label="操作" width="90px">-->
<!-- <template #default="scope">-->
<!-- <el-button type="primary" link @click="openFieldForm(scope, scope.$index)"-->
<!-- >编辑</el-button-->
<!-- >-->
<!-- <el-divider direction="vertical" />-->
<!-- <el-button-->
<!-- type="primary"-->
<!-- link-->
<!-- style="color: #ff4d4f"-->
<!-- @click="removeField(scope, scope.$index)"-->
<!-- >移除</el-button-->
<!-- >-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- </div>-->
<!-- <div class="element-drawer__button">-->
<!-- <XButton type="primary" proIcon="ep:plus" title="添加字段" @click="openFieldForm(null, -1)" />-->
<!-- </div>-->
<!--字段配置侧边栏-->
<el-drawer
v-model="fieldModelVisible"
title="字段配置"
:size="`${width}px`"
append-to-body
destroy-on-close
>
<el-form :model="formFieldForm" label-width="90px">
<el-form-item label="字段ID">
<el-input v-model="formFieldForm.id" clearable />
</el-form-item>
<el-form-item label="类型">
<el-select
v-model="formFieldForm.typeType"
placeholder="请选择字段类型"
clearable
@change="changeFieldTypeType"
>
<el-option v-for="(value, key) of fieldType" :label="value" :value="key" :key="key" />
</el-select>
</el-form-item>
<el-form-item label="类型名称" v-if="formFieldForm.typeType === 'custom'">
<el-input v-model="formFieldForm.type" clearable />
</el-form-item>
<el-form-item label="名称">
<el-input v-model="formFieldForm.label" clearable />
</el-form-item>
<el-form-item label="时间格式" v-if="formFieldForm.typeType === 'date'">
<el-input v-model="formFieldForm.datePattern" clearable />
</el-form-item>
<el-form-item label="默认值">
<el-input v-model="formFieldForm.defaultValue" clearable />
</el-form-item>
</el-form>
<!-- <el-drawer-->
<!-- v-model="fieldModelVisible"-->
<!-- title="字段配置"-->
<!-- :size="`${width}px`"-->
<!-- append-to-body-->
<!-- destroy-on-close-->
<!-- >-->
<!-- <el-form :model="formFieldForm" label-width="90px">-->
<!-- <el-form-item label="字段ID">-->
<!-- <el-input v-model="formFieldForm.id" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="类型">-->
<!-- <el-select-->
<!-- v-model="formFieldForm.typeType"-->
<!-- placeholder="请选择字段类型"-->
<!-- clearable-->
<!-- @change="changeFieldTypeType"-->
<!-- >-->
<!-- <el-option v-for="(value, key) of fieldType" :label="value" :value="key" :key="key" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="类型名称" v-if="formFieldForm.typeType === 'custom'">-->
<!-- <el-input v-model="formFieldForm.type" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="名称">-->
<!-- <el-input v-model="formFieldForm.label" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="时间格式" v-if="formFieldForm.typeType === 'date'">-->
<!-- <el-input v-model="formFieldForm.datePattern" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="默认值">-->
<!-- <el-input v-model="formFieldForm.defaultValue" clearable />-->
<!-- </el-form-item>-->
<!-- </el-form>-->
<!-- 枚举值设置 -->
<template v-if="formFieldForm.type === 'enum'">
<el-divider key="enum-divider" />
<p class="listener-filed__title" key="enum-title">
<span><Icon icon="ep:menu" />枚举值列表:</span>
<el-button type="primary" @click="openFieldOptionForm(null, -1, 'enum')"
>添加枚举值</el-button
>
</p>
<el-table :data="fieldEnumList" key="enum-table" max-height="240" fit border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="枚举值编号" prop="id" min-width="100px" show-overflow-tooltip />
<el-table-column label="枚举值名称" prop="name" min-width="100px" show-overflow-tooltip />
<el-table-column label="操作" width="90px">
<template #default="scope">
<el-button
type="primary"
link
@click="openFieldOptionForm(scope, scope.$index, 'enum')"
>编辑</el-button
>
<el-divider direction="vertical" />
<el-button
type="primary"
link
style="color: #ff4d4f"
@click="removeFieldOptionItem(scope, scope.$index, 'enum')"
>移除</el-button
>
</template>
</el-table-column>
</el-table>
</template>
<!-- &lt;!&ndash; 枚举值设置 &ndash;&gt;-->
<!-- <template v-if="formFieldForm.type === 'enum'">-->
<!-- <el-divider key="enum-divider" />-->
<!-- <p class="listener-filed__title" key="enum-title">-->
<!-- <span><Icon icon="ep:menu" />枚举值列表:</span>-->
<!-- <el-button type="primary" @click="openFieldOptionForm(null, -1, 'enum')"-->
<!-- >添加枚举值</el-button-->
<!-- >-->
<!-- </p>-->
<!-- <el-table :data="fieldEnumList" key="enum-table" max-height="240" fit border>-->
<!-- <el-table-column label="序号" width="50px" type="index" />-->
<!-- <el-table-column label="枚举值编号" prop="id" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="枚举值名称" prop="name" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="操作" width="90px">-->
<!-- <template #default="scope">-->
<!-- <el-button-->
<!-- type="primary"-->
<!-- link-->
<!-- @click="openFieldOptionForm(scope, scope.$index, 'enum')"-->
<!-- >编辑</el-button-->
<!-- >-->
<!-- <el-divider direction="vertical" />-->
<!-- <el-button-->
<!-- type="primary"-->
<!-- link-->
<!-- style="color: #ff4d4f"-->
<!-- @click="removeFieldOptionItem(scope, scope.$index, 'enum')"-->
<!-- >移除</el-button-->
<!-- >-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- </template>-->
<!-- 校验规则 -->
<el-divider key="validation-divider" />
<p class="listener-filed__title" key="validation-title">
<span><Icon icon="ep:menu" />约束条件列表:</span>
<el-button type="primary" @click="openFieldOptionForm(null, -1, 'constraint')"
>添加约束</el-button
>
</p>
<el-table :data="fieldConstraintsList" key="validation-table" max-height="240" fit border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="约束名称" prop="name" min-width="100px" show-overflow-tooltip />
<el-table-column label="约束配置" prop="config" min-width="100px" show-overflow-tooltip />
<el-table-column label="操作" width="90px">
<template #default="scope">
<el-button
type="primary"
link
@click="openFieldOptionForm(scope, scope.$index, 'constraint')"
>编辑</el-button
>
<el-divider direction="vertical" />
<el-button
type="primary"
link
style="color: #ff4d4f"
@click="removeFieldOptionItem(scope, scope.$index, 'constraint')"
>移除</el-button
>
</template>
</el-table-column>
</el-table>
<!-- &lt;!&ndash; 校验规则 &ndash;&gt;-->
<!-- <el-divider key="validation-divider" />-->
<!-- <p class="listener-filed__title" key="validation-title">-->
<!-- <span><Icon icon="ep:menu" />约束条件列表:</span>-->
<!-- <el-button type="primary" @click="openFieldOptionForm(null, -1, 'constraint')"-->
<!-- >添加约束</el-button-->
<!-- >-->
<!-- </p>-->
<!-- <el-table :data="fieldConstraintsList" key="validation-table" max-height="240" fit border>-->
<!-- <el-table-column label="序号" width="50px" type="index" />-->
<!-- <el-table-column label="约束名称" prop="name" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="约束配置" prop="config" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="操作" width="90px">-->
<!-- <template #default="scope">-->
<!-- <el-button-->
<!-- type="primary"-->
<!-- link-->
<!-- @click="openFieldOptionForm(scope, scope.$index, 'constraint')"-->
<!-- >编辑</el-button-->
<!-- >-->
<!-- <el-divider direction="vertical" />-->
<!-- <el-button-->
<!-- type="primary"-->
<!-- link-->
<!-- style="color: #ff4d4f"-->
<!-- @click="removeFieldOptionItem(scope, scope.$index, 'constraint')"-->
<!-- >移除</el-button-->
<!-- >-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- 表单属性 -->
<el-divider key="property-divider" />
<p class="listener-filed__title" key="property-title">
<span><Icon icon="ep:menu" />字段属性列表:</span>
<el-button type="primary" @click="openFieldOptionForm(null, -1, 'property')"
>添加属性</el-button
>
</p>
<el-table :data="fieldPropertiesList" key="property-table" max-height="240" fit border>
<el-table-column label="序号" width="50px" type="index" />
<el-table-column label="属性编号" prop="id" min-width="100px" show-overflow-tooltip />
<el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />
<el-table-column label="操作" width="90px">
<template #default="scope">
<el-button
type="primary"
link
@click="openFieldOptionForm(scope, scope.$index, 'property')"
>编辑</el-button
>
<el-divider direction="vertical" />
<el-button
type="primary"
link
style="color: #ff4d4f"
@click="removeFieldOptionItem(scope, scope.$index, 'property')"
>移除</el-button
>
</template>
</el-table-column>
</el-table>
<!-- &lt;!&ndash; 表单属性 &ndash;&gt;-->
<!-- <el-divider key="property-divider" />-->
<!-- <p class="listener-filed__title" key="property-title">-->
<!-- <span><Icon icon="ep:menu" />字段属性列表:</span>-->
<!-- <el-button type="primary" @click="openFieldOptionForm(null, -1, 'property')"-->
<!-- >添加属性</el-button-->
<!-- >-->
<!-- </p>-->
<!-- <el-table :data="fieldPropertiesList" key="property-table" max-height="240" fit border>-->
<!-- <el-table-column label="序号" width="50px" type="index" />-->
<!-- <el-table-column label="属性编号" prop="id" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="属性值" prop="value" min-width="100px" show-overflow-tooltip />-->
<!-- <el-table-column label="操作" width="90px">-->
<!-- <template #default="scope">-->
<!-- <el-button-->
<!-- type="primary"-->
<!-- link-->
<!-- @click="openFieldOptionForm(scope, scope.$index, 'property')"-->
<!-- >编辑</el-button-->
<!-- >-->
<!-- <el-divider direction="vertical" />-->
<!-- <el-button-->
<!-- type="primary"-->
<!-- link-->
<!-- style="color: #ff4d4f"-->
<!-- @click="removeFieldOptionItem(scope, scope.$index, 'property')"-->
<!-- >移除</el-button-->
<!-- >-->
<!-- </template>-->
<!-- </el-table-column>-->
<!-- </el-table>-->
<!-- 底部按钮 -->
<div class="element-drawer__button">
<el-button>取 消</el-button>
<el-button type="primary" @click="saveField">保 存</el-button>
</div>
</el-drawer>
<!-- &lt;!&ndash; 底部按钮 &ndash;&gt;-->
<!-- <div class="element-drawer__button">-->
<!-- <el-button>取 消</el-button>-->
<!-- <el-button type="primary" @click="saveField">保 存</el-button>-->
<!-- </div>-->
<!-- </el-drawer>-->
<el-dialog
v-model="fieldOptionModelVisible"
:title="optionModelTitle"
width="600px"
append-to-body
destroy-on-close
>
<el-form :model="fieldOptionForm" label-width="96px">
<el-form-item label="编号/ID" v-if="fieldOptionType !== 'constraint'" key="option-id">
<el-input v-model="fieldOptionForm.id" clearable />
</el-form-item>
<el-form-item label="名称" v-if="fieldOptionType !== 'property'" key="option-name">
<el-input v-model="fieldOptionForm.name" clearable />
</el-form-item>
<el-form-item label="配置" v-if="fieldOptionType === 'constraint'" key="option-config">
<el-input v-model="fieldOptionForm.config" clearable />
</el-form-item>
<el-form-item label="值" v-if="fieldOptionType === 'property'" key="option-value">
<el-input v-model="fieldOptionForm.value" clearable />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="fieldOptionModelVisible = false">取 消</el-button>
<el-button type="primary" @click="saveFieldOption">确 定</el-button>
</template>
</el-dialog>
<!-- <el-dialog-->
<!-- v-model="fieldOptionModelVisible"-->
<!-- :title="optionModelTitle"-->
<!-- width="600px"-->
<!-- append-to-body-->
<!-- destroy-on-close-->
<!-- >-->
<!-- <el-form :model="fieldOptionForm" label-width="96px">-->
<!-- <el-form-item label="编号/ID" v-if="fieldOptionType !== 'constraint'" key="option-id">-->
<!-- <el-input v-model="fieldOptionForm.id" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="名称" v-if="fieldOptionType !== 'property'" key="option-name">-->
<!-- <el-input v-model="fieldOptionForm.name" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="配置" v-if="fieldOptionType === 'constraint'" key="option-config">-->
<!-- <el-input v-model="fieldOptionForm.config" clearable />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="值" v-if="fieldOptionType === 'property'" key="option-value">-->
<!-- <el-input v-model="fieldOptionForm.value" clearable />-->
<!-- </el-form-item>-->
<!-- </el-form>-->
<!-- <template #footer>-->
<!-- <el-button @click="fieldOptionModelVisible = false">取 消</el-button>-->
<!-- <el-button type="primary" @click="saveFieldOption">确 定</el-button>-->
<!-- </template>-->
<!-- </el-dialog>-->
</div>
</template>
<script lang="ts" setup>
import * as FormApi from '@/api/bpm/form'
defineOptions({ name: 'ElementForm' })
const props = defineProps({
@@ -263,6 +268,9 @@ const bpmnInstances = () => (window as any)?.bpmnInstances
const resetFormList = () => {
bpmnELement.value = bpmnInstances().bpmnElement
formKey.value = bpmnELement.value.businessObject.formKey
if (formKey.value?.length > 0) {
formKey.value = parseInt(formKey.value)
}
// 获取元素扩展属性 或者 创建扩展属性
elExtensionElements.value =
bpmnELement.value.businessObject.get('extensionElements') ||
@@ -421,7 +429,7 @@ const saveField = () => {
// 移除某个 字段的 配置项
const removeFieldOptionItem = (option, index, type) => {
console.log(option, 'option')
// console.log(option, 'option')
if (type === 'property') {
fieldPropertiesList.value.splice(index, 1)
return
@@ -451,6 +459,11 @@ const updateElementExtensions = () => {
})
}
const formList = ref([]) // 流程表单的下拉框的数据
onMounted(async () => {
formList.value = await FormApi.getFormSimpleList()
})
watch(
() => props.id,
(val) => {
@@ -26,8 +26,16 @@
type="primary"
preIcon="ep:plus"
title="添加监听器"
size="small"
@click="openListenerForm(null)"
/>
<XButton
type="success"
preIcon="ep:select"
title="选择监听器"
size="small"
@click="openProcessListenerDialog"
/>
</div>
<!-- 监听器 编辑/创建 部分 -->
@@ -240,11 +248,21 @@
</template>
</el-dialog>
</div>
<!-- 选择弹窗 -->
<ProcessListenerDialog ref="processListenerDialogRef" @select="selectProcessListener" />
</template>
<script lang="ts" setup>
import { ElMessageBox } from 'element-plus'
import { createListenerObject, updateElementExtensions } from '../../utils'
import { initListenerType, initListenerForm, listenerType, fieldType } from './utilSelf'
import {
initListenerType,
initListenerForm,
listenerType,
fieldType,
initListenerForm2
} from './utilSelf'
import ProcessListenerDialog from './ProcessListenerDialog.vue'
defineOptions({ name: 'ElementListeners' })
@@ -284,6 +302,7 @@ const resetListenersList = () => {
}
// 打开 监听器详情 侧边栏
const openListenerForm = (listener, index?) => {
// debugger
if (listener) {
listenerForm.value = initListenerForm(listener)
editingListenerIndex.value = index
@@ -321,6 +340,7 @@ const openListenerFieldForm = (field, index?) => {
}
// 保存监听器注入字段
const saveListenerFiled = async () => {
// debugger
let validateStatus = await listenerFieldFormRef.value.validate()
if (!validateStatus) return // 验证不通过直接返回
if (editingListenerFieldIndex.value === -1) {
@@ -337,6 +357,7 @@ const saveListenerFiled = async () => {
}
// 移除监听器字段
const removeListenerField = (index) => {
// debugger
ElMessageBox.confirm('确认移除该字段吗?', '提示', {
confirmButtonText: '确 认',
cancelButtonText: '取 消'
@@ -349,6 +370,7 @@ const removeListenerField = (index) => {
}
// 移除监听器
const removeListener = (index) => {
debugger
ElMessageBox.confirm('确认移除该监听器吗?', '提示', {
confirmButtonText: '确 认',
cancelButtonText: '取 消'
@@ -365,6 +387,7 @@ const removeListener = (index) => {
}
// 保存监听器配置
const saveListenerConfig = async () => {
// debugger
let validateStatus = await listenerFormRef.value.validate()
if (!validateStatus) return // 验证不通过直接返回
const listenerObject = createListenerObject(listenerForm.value, false, prefix)
@@ -389,6 +412,28 @@ const saveListenerConfig = async () => {
listenerForm.value = {}
}
// 打开监听器弹窗
const processListenerDialogRef = ref()
const openProcessListenerDialog = async () => {
processListenerDialogRef.value.open('execution')
}
const selectProcessListener = (listener) => {
const listenerForm = initListenerForm2(listener)
const listenerObject = createListenerObject(listenerForm, false, prefix)
bpmnElementListeners.value.push(listenerObject)
elementListenersList.value.push(listenerForm)
// 保存其他配置
otherExtensionList.value =
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
(ex) => ex.$type !== `${prefix}:ExecutionListener`
) ?? []
updateElementExtensions(
bpmnElement.value,
otherExtensionList.value.concat(bpmnElementListeners.value)
)
}
watch(
() => props.id,
(val) => {
@@ -0,0 +1,83 @@
<!-- 执行器选择 -->
<template>
<Dialog title="请选择监听器" v-model="dialogVisible" width="1024px">
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="类型" align="center" prop="type">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_LISTENER_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="事件" align="center" prop="event" />
<el-table-column label="值类型" align="center" prop="valueType">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE"
:value="scope.row.valueType"
/>
</template>
</el-table-column>
<el-table-column label="值" align="center" prop="value" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="primary" @click="select(scope.row)"> 选择 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</Dialog>
</template>
<script setup lang="ts">
import { ProcessListenerApi, ProcessListenerVO } from '@/api/bpm/processListener'
import { DICT_TYPE } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
/** BPM 流程 表单 */
defineOptions({ name: 'ProcessListenerDialog' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const loading = ref(true) // 列表的加载中
const list = ref<ProcessListenerVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
type: undefined,
status: CommonStatusEnum.ENABLE
})
/** 打开弹窗 */
const open = async (type: string) => {
dialogVisible.value = true
loading.value = true
try {
queryParams.pageNo = 1
queryParams.type = type
const data = await ProcessListenerApi.getProcessListenerPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const select = async (row) => {
dialogVisible.value = false
// 发送操作成功的事件
emit('select', row)
}
</script>
@@ -39,6 +39,13 @@
title="添加监听器"
@click="openListenerForm(null)"
/>
<XButton
type="success"
preIcon="ep:select"
title="选择监听器"
size="small"
@click="openProcessListenerDialog"
/>
</div>
<!-- 监听器 编辑/创建 部分 -->
@@ -286,11 +293,22 @@
</template>
</el-dialog>
</div>
<!-- 选择弹窗 -->
<ProcessListenerDialog ref="processListenerDialogRef" @select="selectProcessListener" />
</template>
<script lang="ts" setup>
import { ElMessageBox } from 'element-plus'
import { createListenerObject, updateElementExtensions } from '../../utils'
import { initListenerForm, initListenerType, eventType, listenerType, fieldType } from './utilSelf'
import {
initListenerForm,
initListenerType,
eventType,
listenerType,
fieldType,
initListenerForm2
} from './utilSelf'
import ProcessListenerDialog from '@/components/bpmnProcessDesigner/package/penal/listeners/ProcessListenerDialog.vue'
defineOptions({ name: 'UserTaskListeners' })
@@ -437,6 +455,28 @@ const removeListenerField = (field, index) => {
.catch(() => console.info('操作取消'))
}
// 打开监听器弹窗
const processListenerDialogRef = ref()
const openProcessListenerDialog = async () => {
processListenerDialogRef.value.open('task')
}
const selectProcessListener = (listener) => {
const listenerForm = initListenerForm2(listener)
const listenerObject = createListenerObject(listenerForm, true, prefix)
bpmnElementListeners.value.push(listenerObject)
elementListenersList.value.push(listenerForm)
// 保存其他配置
otherExtensionList.value =
bpmnElement.value.businessObject?.extensionElements?.values?.filter(
(ex) => ex.$type !== `${prefix}:TaskListener`
) ?? []
updateElementExtensions(
bpmnElement.value,
otherExtensionList.value.concat(bpmnElementListeners.value)
)
}
watch(
() => props.id,
(val) => {
@@ -40,6 +40,33 @@ export function initListenerType(listener) {
}
}
/** 将 ProcessListenerDO 转换成 initListenerForm 想同的 Form 对象 */
export function initListenerForm2(processListener) {
if (processListener.valueType === 'class') {
return {
listenerType: 'classListener',
class: processListener.value,
event: processListener.event,
fields: []
}
} else if (processListener.valueType === 'expression') {
return {
listenerType: 'expressionListener',
expression: processListener.value,
event: processListener.event,
fields: []
}
} else if (processListener.valueType === 'delegateExpression') {
return {
listenerType: 'delegateExpressionListener',
delegateExpression: processListener.value,
event: processListener.event,
fields: []
}
}
throw new Error('未知的监听器类型')
}
export const listenerType = {
classListener: 'Java 类',
expressionListener: '表达式',
@@ -1,11 +1,15 @@
<template>
<div class="panel-tab__content">
<el-form label-width="90px">
<el-form-item label="回路特性">
<el-form-item label="快捷配置">
<el-button size="small" @click="changeConfig('依次审批')">依次审批</el-button>
<el-button size="small" @click="changeConfig('会签')">会签</el-button>
<el-button size="small" @click="changeConfig('或签')">或签</el-button>
</el-form-item>
<el-form-item label="会签类型">
<el-select v-model="loopCharacteristics" @change="changeLoopCharacteristicsType">
<el-option label="并行多重事件" value="ParallelMultiInstance" />
<el-option label="时序多重事件" value="SequentialMultiInstance" />
<el-option label="循环事件" value="StandardLoop" />
<el-option label="无" value="Null" />
</el-select>
</el-form-item>
@@ -15,7 +19,7 @@
loopCharacteristics === 'SequentialMultiInstance'
"
>
<el-form-item label="循环基数" key="loopCardinality">
<el-form-item label="循环数量" key="loopCardinality">
<el-input
v-model="loopInstanceForm.loopCardinality"
clearable
@@ -25,7 +29,8 @@
<el-form-item label="集合" key="collection" v-show="false">
<el-input v-model="loopInstanceForm.collection" clearable @change="updateLoopBase" />
</el-form-item>
<el-form-item label="元素变量" key="elementVariable">
<!-- add by 芋艿:由于「元素变量」暂时用不到,所以这里 display 为 none -->
<el-form-item label="元素变量" key="elementVariable" style="display: none">
<el-input v-model="loopInstanceForm.elementVariable" clearable @change="updateLoopBase" />
</el-form-item>
<el-form-item label="完成条件" key="completionCondition">
@@ -35,7 +40,8 @@
@change="updateLoopCondition"
/>
</el-form-item>
<el-form-item label="异步状态" key="async">
<!-- add by 芋艿:由于「异步状态」暂时用不到,所以这里 display 为 none -->
<el-form-item label="异步状态" key="async" style="display: none">
<el-checkbox
v-model="loopInstanceForm.asyncBefore"
label="异步前"
@@ -124,6 +130,7 @@ const getElementLoop = (businessObject) => {
businessObject.loopCharacteristics.extensionElements.values[0].body
}
}
const changeLoopCharacteristicsType = (type) => {
// this.loopInstanceForm = { ...this.defaultLoopInstanceForm }; // 切换类型取消原表单配置
// 取消多实例配置
@@ -160,6 +167,7 @@ const changeLoopCharacteristicsType = (type) => {
loopCharacteristics: toRaw(multiLoopInstance.value)
})
}
// 循环基数
const updateLoopCardinality = (cardinality) => {
let loopCardinality = null
@@ -176,6 +184,7 @@ const updateLoopCardinality = (cardinality) => {
}
)
}
// 完成条件
const updateLoopCondition = (condition) => {
let completionCondition = null
@@ -192,6 +201,7 @@ const updateLoopCondition = (condition) => {
}
)
}
// 重试周期
const updateLoopTimeCycle = (timeCycle) => {
const extensionElements = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
@@ -209,6 +219,7 @@ const updateLoopTimeCycle = (timeCycle) => {
}
)
}
// 直接更新的基础信息
const updateLoopBase = () => {
bpmnInstances().modeling.updateModdleProperties(
@@ -220,6 +231,7 @@ const updateLoopBase = () => {
}
)
}
// 各异步状态
const updateLoopAsync = (key) => {
const { asyncBefore, asyncAfter } = loopInstanceForm.value
@@ -238,6 +250,20 @@ const updateLoopAsync = (key) => {
)
}
const changeConfig = (config) => {
if (config === '依次审批') {
changeLoopCharacteristicsType('SequentialMultiInstance')
updateLoopCardinality('1')
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }')
} else if (config === '会签') {
changeLoopCharacteristicsType('ParallelMultiInstance')
updateLoopCondition('${ nrOfCompletedInstances >= nrOfInstances }')
} else if (config === '或签') {
changeLoopCharacteristicsType('ParallelMultiInstance')
updateLoopCondition('${ nrOfCompletedInstances > 0 }')
}
}
onBeforeUnmount(() => {
multiLoopInstance.value = null
bpmnElement.value = null
@@ -1,7 +1,8 @@
<template>
<div class="panel-tab__content">
<el-form size="small" label-width="90px">
<el-form-item label="异步延续">
<!-- add by 芋艿:由于「异步延续」暂时用不到,所以这里 display 为 none -->
<el-form-item label="异步延续" style="display: none">
<el-checkbox
v-model="taskConfigForm.asyncBefore"
label="异步前"
@@ -0,0 +1,68 @@
<!-- 表达式选择 -->
<template>
<Dialog title="请选择表达式" v-model="dialogVisible" width="1024px">
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="表达式" align="center" prop="expression" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="primary" @click="select(scope.row)"> 选择 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</Dialog>
</template>
<script setup lang="ts">
import { CommonStatusEnum } from '@/utils/constants'
import { ProcessExpressionApi, ProcessExpressionVO } from '@/api/bpm/processExpression'
/** BPM 流程 表单 */
defineOptions({ name: 'ProcessExpressionDialog' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const loading = ref(true) // 列表的加载中
const list = ref<ProcessExpressionVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
type: undefined,
status: CommonStatusEnum.ENABLE
})
/** 打开弹窗 */
const open = async (type: string) => {
dialogVisible.value = true
loading.value = true
try {
queryParams.pageNo = 1
queryParams.type = type
const data = await ProcessExpressionApi.getProcessExpressionPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const select = async (row) => {
dialogVisible.value = false
// 发送操作成功的事件
emit('select', row)
}
</script>
@@ -1,85 +1,204 @@
<template>
<div style="margin-top: 16px">
<!-- <el-form-item label="处理用户">-->
<!-- <el-select v-model="userTaskForm.assignee" @change="updateElementTask('assignee')">-->
<!-- <el-option v-for="ak in mockData" :key="'ass-' + ak" :label="`用户${ak}`" :value="`user${ak}`" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="候选用户">-->
<!-- <el-select v-model="userTaskForm.candidateUsers" multiple collapse-tags @change="updateElementTask('candidateUsers')">-->
<!-- <el-option v-for="uk in mockData" :key="'user-' + uk" :label="`用户${uk}`" :value="`user${uk}`" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<!-- <el-form-item label="候选分组">-->
<!-- <el-select v-model="userTaskForm.candidateGroups" multiple collapse-tags @change="updateElementTask('candidateGroups')">-->
<!-- <el-option v-for="gk in mockData" :key="'ass-' + gk" :label="`分组${gk}`" :value="`group${gk}`" />-->
<!-- </el-select>-->
<!-- </el-form-item>-->
<el-form-item label="到期时间">
<el-input v-model="userTaskForm.dueDate" clearable @change="updateElementTask('dueDate')" />
</el-form-item>
<el-form-item label="跟踪时间">
<el-input
v-model="userTaskForm.followUpDate"
<el-form label-width="100px">
<el-form-item label="规则类型" prop="candidateStrategy">
<el-select
v-model="userTaskForm.candidateStrategy"
clearable
@change="updateElementTask('followUpDate')"
style="width: 100%"
@change="changeCandidateStrategy"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_CANDIDATE_STRATEGY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy == 10"
label="指定角色"
prop="candidateParam"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
multiple
style="width: 100%"
@change="updateElementTask"
>
<el-option v-for="item in roleOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy == 20 || userTaskForm.candidateStrategy == 21"
label="指定部门"
prop="candidateParam"
span="24"
>
<el-tree-select
ref="treeRef"
v-model="userTaskForm.candidateParam"
:data="deptTreeOptions"
:props="defaultProps"
empty-text="加载中,请稍后"
multiple
node-key="id"
show-checkbox
@change="updateElementTask"
/>
</el-form-item>
<el-form-item label="优先级">
<el-input v-model="userTaskForm.priority" clearable @change="updateElementTask('priority')" />
<el-form-item
v-if="userTaskForm.candidateStrategy == 22"
label="指定岗位"
prop="candidateParam"
span="24"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
multiple
style="width: 100%"
@change="updateElementTask"
>
<el-option v-for="item in postOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
友情提示:任务的分配规则,使用
<router-link target="_blank" :to="{ path: '/bpm/manager/model' }"
><el-link type="danger">流程模型</el-link>
</router-link>
下的【分配规则】替代,提供指定角色、部门负责人、部门成员、岗位、工作组、自定义脚本等 7
种维护的任务分配维度,更加灵活!
</div>
<el-form-item
v-if="userTaskForm.candidateStrategy == 30"
label="指定用户"
prop="candidateParam"
span="24"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
multiple
style="width: 100%"
@change="updateElementTask"
>
<el-option
v-for="item in userOptions"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy === 40"
label="指定用户组"
prop="candidateParam"
>
<el-select
v-model="userTaskForm.candidateParam"
clearable
multiple
style="width: 100%"
@change="updateElementTask"
>
<el-option
v-for="item in userGroupOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="userTaskForm.candidateStrategy === 60"
label="流程表达式"
prop="candidateParam"
>
<el-input
type="textarea"
v-model="userTaskForm.candidateParam[0]"
clearable
style="width: 72%"
@change="updateElementTask"
/>
<el-button class="ml-5px" size="small" type="success" @click="openProcessExpressionDialog"
>选择表达式</el-button
>
<!-- 选择弹窗 -->
<ProcessExpressionDialog ref="processExpressionDialogRef" @select="selectProcessExpression" />
</el-form-item>
</el-form>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { defaultProps, handleTree } from '@/utils/tree'
import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup'
import ProcessExpressionDialog from './ProcessExpressionDialog.vue'
defineOptions({ name: 'UserTask' })
const props = defineProps({
id: String,
type: String
})
const defaultTaskForm = ref({
assignee: '',
candidateUsers: [],
candidateGroups: [],
dueDate: '',
followUpDate: '',
priority: ''
const userTaskForm = ref({
candidateStrategy: undefined, // 分配规则
candidateParam: [] // 分配选项
})
const userTaskForm = ref<any>({})
// const mockData=ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
const bpmnElement = ref()
const bpmnInstances = () => (window as any)?.bpmnInstances
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
const deptTreeOptions = ref() // 部门树
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
const resetTaskForm = () => {
for (let key in defaultTaskForm.value) {
let value
if (key === 'candidateUsers' || key === 'candidateGroups') {
value = bpmnElement.value?.businessObject[key]
? bpmnElement.value.businessObject[key].split(',')
: []
const businessObject = bpmnElement.value.businessObject
if (!businessObject) {
return
}
if (businessObject.candidateStrategy != undefined) {
userTaskForm.value.candidateStrategy = parseInt(businessObject.candidateStrategy) as any
} else {
userTaskForm.value.candidateStrategy = undefined
}
if (businessObject.candidateParam && businessObject.candidateParam.length > 0) {
if (userTaskForm.value.candidateStrategy === 60) {
// 特殊:流程表达式,只有一个 input 输入框
userTaskForm.value.candidateParam = [businessObject.candidateParam]
} else {
value = bpmnElement.value?.businessObject[key] || defaultTaskForm.value[key]
userTaskForm.value.candidateParam = businessObject.candidateParam
.split(',')
.map((item) => +item)
}
userTaskForm.value[key] = value
} else {
userTaskForm.value.candidateParam = []
}
}
const updateElementTask = (key) => {
const taskAttr = Object.create(null)
if (key === 'candidateUsers' || key === 'candidateGroups') {
taskAttr[key] =
userTaskForm.value[key] && userTaskForm.value[key].length
? userTaskForm.value[key].join()
: null
} else {
taskAttr[key] = userTaskForm.value[key] || null
}
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), taskAttr)
/** 更新 candidateStrategy 字段时,需要清空 candidateParam,并触发 bpmn 图更新 */
const changeCandidateStrategy = () => {
userTaskForm.value.candidateParam = []
updateElementTask()
}
/** 选中某个 options 时候,更新 bpmn 图 */
const updateElementTask = () => {
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
candidateStrategy: userTaskForm.value.candidateStrategy,
candidateParam: userTaskForm.value.candidateParam.join(',')
})
}
// 打开监听器弹窗
const processExpressionDialogRef = ref()
const openProcessExpressionDialog = async () => {
processExpressionDialogRef.value.open()
}
const selectProcessExpression = (expression) => {
userTaskForm.value.candidateParam = [expression.expression]
}
watch(
@@ -92,6 +211,21 @@ watch(
},
{ immediate: true }
)
onMounted(async () => {
// 获得角色列表
roleOptions.value = await RoleApi.getSimpleRoleList()
// 获得部门列表
const deptOptions = await DeptApi.getSimpleDeptList()
deptTreeOptions.value = handleTree(deptOptions, 'id')
// 获得岗位列表
postOptions.value = await PostApi.getSimplePostList()
// 获得用户列表
userOptions.value = await UserApi.getSimpleUserList()
// 获得用户组列表
userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList()
})
onBeforeUnmount(() => {
bpmnElement.value = null
})
@@ -2,6 +2,7 @@ import { toRaw } from 'vue'
const bpmnInstances = () => (window as any)?.bpmnInstances
// 创建监听器实例
export function createListenerObject(options, isTask, prefix) {
debugger
const listenerObj = Object.create(null)
listenerObj.event = options.event
isTask && (listenerObj.id = options.id) // 任务监听器特有的 id 字段
@@ -195,7 +195,7 @@ export default defineComponent({
</div>
<Menu
class={[
'!absolute top-0',
'!absolute top-0 z-11',
{
'!left-[var(--tab-menu-min-width)]': unref(collapse),
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
+11 -9
View File
@@ -2,23 +2,24 @@ import * as echarts from 'echarts/core'
import {
BarChart,
FunnelChart,
GaugeChart,
LineChart,
PieChart,
MapChart,
PictorialBarChart,
RadarChart,
GaugeChart
PieChart,
RadarChart
} from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
GridComponent,
PolarComponent,
AriaComponent,
ParallelComponent,
GridComponent,
LegendComponent,
ParallelComponent,
PolarComponent,
TitleComponent,
ToolboxComponent,
TooltipComponent,
VisualMapComponent
} from 'echarts/components'
@@ -41,7 +42,8 @@ echarts.use([
CanvasRenderer,
PictorialBarChart,
RadarChart,
GaugeChart
GaugeChart,
FunnelChart
])
export default echarts
+21 -9
View File
@@ -1,22 +1,27 @@
import type { App } from 'vue'
// 👇使用 form-create 需额外全局引入 element plus 组件
import {
ElAlert,
ElAside,
ElPopconfirm,
ElHeader,
ElMain,
ElContainer,
ElDivider,
ElTransfer,
ElAlert,
ElTabs,
ElHeader,
ElMain,
ElPopconfirm,
ElTable,
ElTableColumn,
ElTabPane
ElTabPane,
ElTabs,
ElTransfer
} from 'element-plus'
import FcDesigner from '@form-create/designer'
import formCreate from '@form-create/element-ui'
import install from '@form-create/element-ui/auto-import'
//======================= 自定义组件 =======================
import { UploadFile, UploadImg, UploadImgs } from '@/components/UploadFile'
import { DictSelect } from '@/components/DictSelect'
import UserSelect from '@/views/system/user/components/UserSelect.vue'
import { Editor } from '@/components/Editor'
const components = [
ElAside,
@@ -30,7 +35,13 @@ const components = [
ElTabs,
ElTable,
ElTableColumn,
ElTabPane
ElTabPane,
UploadImg,
UploadImgs,
UploadFile,
DictSelect,
UserSelect,
Editor
]
// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档
@@ -40,4 +51,5 @@ export const setupFormCreate = (app: App<Element>) => {
})
formCreate.use(install)
app.use(formCreate)
app.use(FcDesigner)
}
+19 -30
View File
@@ -243,7 +243,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
},
children: [
{
path: '/manager/form/edit',
path: 'manager/form/edit',
component: () => import('@/views/bpm/form/editor/index.vue'),
name: 'BpmFormEditor',
meta: {
@@ -255,7 +255,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
}
},
{
path: '/manager/model/edit',
path: 'manager/model/edit',
component: () => import('@/views/bpm/model/editor/index.vue'),
name: 'BpmModelEditor',
meta: {
@@ -267,7 +267,19 @@ const remainingRouter: AppRouteRecordRaw[] = [
}
},
{
path: '/manager/definition',
path: 'manager/simple/workflow/model/edit',
component: () => import('@/views/bpm/simpleWorkflow/index.vue'),
name: 'SimpleWorkflowDesignEditor',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '仿钉钉设计流程',
activeMenu: '/bpm/manager/model'
}
},
{
path: 'manager/definition',
component: () => import('@/views/bpm/definition/index.vue'),
name: 'BpmProcessDefinition',
meta: {
@@ -279,30 +291,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
}
},
{
path: '/manager/task-assign-rule',
component: () => import('@/views/bpm/taskAssignRule/index.vue'),
name: 'BpmTaskAssignRuleList',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '任务分配规则'
}
},
{
path: '/process-instance/create',
component: () => import('@/views/bpm/processInstance/create/index.vue'),
name: 'BpmProcessInstanceCreate',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '发起流程',
activeMenu: 'bpm/processInstance/create'
}
},
{
path: '/process-instance/detail',
path: 'process-instance/detail',
component: () => import('@/views/bpm/processInstance/detail/index.vue'),
name: 'BpmProcessInstanceDetail',
meta: {
@@ -310,11 +299,11 @@ const remainingRouter: AppRouteRecordRaw[] = [
hidden: true,
canTo: true,
title: '流程详情',
activeMenu: 'bpm/processInstance/detail'
activeMenu: '/bpm/task/my'
}
},
{
path: '/bpm/oa/leave/create',
path: 'oa/leave/create',
component: () => import('@/views/bpm/oa/leave/create.vue'),
name: 'OALeaveCreate',
meta: {
@@ -326,7 +315,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
}
},
{
path: '/bpm/oa/leave/detail',
path: 'oa/leave/detail',
component: () => import('@/views/bpm/oa/leave/detail.vue'),
name: 'OALeaveDetail',
meta: {
+55
View File
@@ -0,0 +1,55 @@
import { store } from '../index'
import { defineStore } from 'pinia'
export const useWorkFlowStore = defineStore('simpleWorkflow', {
state: () => ({
tableId: '',
isTried: false,
promoterDrawer: false,
flowPermission1: {},
approverDrawer: false,
approverConfig1: {},
copyerDrawer: false,
copyerConfig1: {},
conditionDrawer: false,
conditionsConfig1: {
conditionNodes: []
}
}),
actions: {
setTableId(payload) {
this.tableId = payload
},
setIsTried(payload) {
this.isTried = payload
},
setPromoter(payload) {
this.promoterDrawer = payload
},
setFlowPermission(payload) {
this.flowPermission1 = payload
},
setApprover(payload) {
this.approverDrawer = payload
},
setApproverConfig(payload) {
this.approverConfig1 = payload
},
setCopyer(payload) {
this.copyerDrawer = payload
},
setCopyerConfig(payload) {
this.copyerConfig1 = payload
},
setCondition(payload) {
this.conditionDrawer = payload
},
setConditionsConfig(payload) {
this.conditionsConfig1 = payload
}
}
})
export const useWorkFlowStoreWithOut = () => {
return useWorkFlowStore(store)
}
+3 -3
View File
@@ -248,15 +248,15 @@ export const CouponTemplateTakeTypeEnum = {
*/
export const PromotionProductScopeEnum = {
ALL: {
scope: 10,
scope: 1,
name: '通用劵'
},
SPU: {
scope: 20,
scope: 2,
name: '商品劵'
},
CATEGORY: {
scope: 30,
scope: 3,
name: '品类劵'
}
}
+16 -14
View File
@@ -1,8 +1,8 @@
/**
* 数据字典工具类
*/
import { useDictStoreWithOut } from '@/store/modules/dict'
import { ElementPlusInfoType } from '@/types/elementPlus'
import {useDictStoreWithOut} from '@/store/modules/dict'
import {ElementPlusInfoType} from '@/types/elementPlus'
const dictStore = useDictStoreWithOut()
@@ -104,6 +104,7 @@ export enum DICT_TYPE {
USER_TYPE = 'user_type',
COMMON_STATUS = 'common_status',
TERMINAL = 'terminal', // 终端
DATE_INTERVAL = 'date_interval', // 数据间隔
// ========== SYSTEM 模块 ==========
SYSTEM_USER_SEX = 'system_user_sex',
@@ -111,7 +112,6 @@ export enum DICT_TYPE {
SYSTEM_ROLE_TYPE = 'system_role_type',
SYSTEM_DATA_SCOPE = 'system_data_scope',
SYSTEM_NOTICE_TYPE = 'system_notice_type',
SYSTEM_OPERATE_TYPE = 'system_operate_type',
SYSTEM_LOGIN_TYPE = 'system_login_type',
SYSTEM_LOGIN_RESULT = 'system_login_result',
SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code',
@@ -134,15 +134,16 @@ export enum DICT_TYPE {
INFRA_CODEGEN_FRONT_TYPE = 'infra_codegen_front_type',
INFRA_CODEGEN_SCENE = 'infra_codegen_scene',
INFRA_FILE_STORAGE = 'infra_file_storage',
INFRA_OPERATE_TYPE = 'infra_operate_type',
// ========== BPM 模块 ==========
BPM_MODEL_CATEGORY = 'bpm_model_category',
BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',
BPM_TASK_ASSIGN_RULE_TYPE = 'bpm_task_assign_rule_type',
BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy',
BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status',
BPM_PROCESS_INSTANCE_RESULT = 'bpm_process_instance_result',
BPM_TASK_ASSIGN_SCRIPT = 'bpm_task_assign_script',
BPM_TASK_STATUS = 'bpm_task_status',
BPM_OA_LEAVE_TYPE = 'bpm_oa_leave_type',
BPM_PROCESS_LISTENER_TYPE = 'bpm_process_listener_type',
BPM_PROCESS_LISTENER_VALUE_TYPE = 'bpm_process_listener_value_type',
// ========== PAY 模块 ==========
PAY_CHANNEL_CODE = 'pay_channel_code', // 支付渠道编码类型
@@ -157,7 +158,7 @@ export enum DICT_TYPE {
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型
// ========== MALL - 会员模块 ==========
// ========== Member 会员模块 ==========
MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型
MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型
@@ -196,14 +197,15 @@ export enum DICT_TYPE {
// ========== CRM - 客户管理模块 ==========
CRM_AUDIT_STATUS = 'crm_audit_status', // CRM 审批状态
CRM_BIZ_TYPE = 'crm_biz_type', // CRM 业务类型
CRM_BUSINESS_END_STATUS_TYPE = 'crm_business_end_status_type', // CRM 商机结束状态类型
CRM_RECEIVABLE_RETURN_TYPE = 'crm_receivable_return_type', // CRM 回款的还款方式
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry',
CRM_CUSTOMER_LEVEL = 'crm_customer_level',
CRM_CUSTOMER_SOURCE = 'crm_customer_source',
CRM_PRODUCT_STATUS = 'crm_product_status',
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry', // CRM 客户所属行业
CRM_CUSTOMER_LEVEL = 'crm_customer_level', // CRM 客户级别
CRM_CUSTOMER_SOURCE = 'crm_customer_source', // CRM 客户来源
CRM_PRODUCT_STATUS = 'crm_product_status', // CRM 商品状态
CRM_PERMISSION_LEVEL = 'crm_permission_level', // CRM 数据权限的级别
CRM_PRODUCT_UNIT = 'crm_product_unit', // 产品单位
CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type', // 跟进方式
CRM_PRODUCT_UNIT = 'crm_product_unit', // CRM 产品单位
CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type', // CRM 跟进方式
// ========== ERP - 企业资源计划模块 ==========
ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态
+9 -6
View File
@@ -28,7 +28,7 @@ export const decodeFields = (fields: string[]) => {
return rule
}
// 设置表单的 Conf 和 Fields
// 设置表单的 Conf 和 Fields,适用 FcDesigner 场景
export const setConfAndFields = (designerRef: object, conf: string, fields: string) => {
// @ts-ignore
designerRef.value.setOption(JSON.parse(conf))
@@ -36,19 +36,22 @@ export const setConfAndFields = (designerRef: object, conf: string, fields: stri
designerRef.value.setRule(decodeFields(fields))
}
// 设置表单的 Conf 和 Fields
// 设置表单的 Conf 和 Fields,适用 form-create 场景
export const setConfAndFields2 = (
detailPreview: object,
conf: string,
fields: string,
fields: string[],
value?: object
) => {
if (isRef(detailPreview)) {
detailPreview = detailPreview.value
}
// @ts-ignore
detailPreview.value.option = JSON.parse(conf)
detailPreview.option = JSON.parse(conf)
// @ts-ignore
detailPreview.value.rule = decodeFields(fields)
detailPreview.rule = decodeFields(fields)
if (value) {
// @ts-ignore
detailPreview.value.value = value
detailPreview.value = value
}
}
Loaded 100 of 233 files, more files were not shown because too many files have changed in this diff. Show more