mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2026-10-06 14:13:14 +08:00
Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro
# Conflicts: # yudao-module-ai/pom.xml # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/mindmap/AiMindMapController.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/music/AiMusicController.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/write/AiWriteController.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/ErrorCodeConstants.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/baichuan/BaiChuanChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/doubao/DouBaoChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/grok/GrokChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/hunyuan/HunYuanChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/xinghuo/XingHuoChatModel.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageService.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapService.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/workflow/AiWorkflowServiceImpl.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteService.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java # yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DouBaoChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/HunYuanChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MiniMaxChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MoonshotChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/XingHuoChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/YiYanChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/ZhiPuAiChatModelTests.java # yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/OpenAiImageModelTests.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskRespVO.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/DictTypeConstants.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java # yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsFunnelController.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsPerformanceController.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/funnel/CrmStatisticsFunnelReqVO.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsFunnelMapper.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsPerformanceMapper.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelService.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsFunnelServiceImpl.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceService.java # yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java # yudao-module-crm/src/main/resources/mapper/statistics/CrmStatisticsFunnelMapper.xml # yudao-module-crm/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml # yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImChannelMessageManagerController.java # yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/rtc/core/LiveKitClient.java # yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageService.java # yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImChannelMessageServiceImpl.java # yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineAbstractTest.java # yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineUniappTest.java # yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/ts/index # yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/detail/index # yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/form/index # yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/search-form # yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java # yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java # yudao-module-member/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java # yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/checkrecord/MesDvCheckRecordLineController.java # yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/maintenrecord/MesDvMaintenRecordLineController.java # yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/repair/MesDvRepairLineController.java # yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java # yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/result/MesWmStockTakingTaskResultRespVO.java # yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/repair/MesDvRepairLineService.java # yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/repair/MesDvRepairLineServiceImpl.java # yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceTest.java # yudao-module-report/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmOnlDragExternalServiceImpl.java # yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java # yudao-module-system/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java
This commit is contained in:
@@ -36,7 +36,7 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2026.06-jdk8-SNAPSHOT</revision>
|
||||
<revision>2026.07-jdk8-SNAPSHOT</revision>
|
||||
<!-- Maven 相关 -->
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
|
||||
@@ -15,7 +15,7 @@ bash verify.sh
|
||||
- 7880:HTTP / WebSocket 信令;
|
||||
- 7881:WebRTC TCP fallback;
|
||||
- 7882/UDP:WebRTC 媒体;
|
||||
- macOS / Windows:当前 `docker-compose.yml` 走端口映射模式,webhook URL 用 `host.docker.internal:48080` 让容器访问到宿主机 yudao 后端;
|
||||
- macOS / Windows:当前 `docker-compose.yml` 走端口映射模式,`rtc.node_ip` 使用 `127.0.0.1` 让浏览器访问映射后的媒体端口,webhook URL 用 `host.docker.internal:48080` 让容器访问到宿主机 yudao 后端;
|
||||
- macOS 上 host network(`network_mode: host`)需要 Docker Desktop 4.34+ 并在 Settings → Resources → Network 勾选「Enable host networking」,老版本静默失败(容器跑得起来但端口完全不通);
|
||||
- Linux:可以把 `docker-compose.yml` 改成 `network_mode: host` + 删 `ports:` 段,并把 `livekit.yaml` 的 webhook URL 改为 `http://127.0.0.1:48080/admin-api/im/livekit/webhook`。
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ rtc:
|
||||
tcp_port: 7881
|
||||
udp_port: 7882
|
||||
use_external_ip: false
|
||||
# macOS / Windows 端口映射模式下显式广播宿主机回环地址,避免把容器 172.x 地址发给浏览器
|
||||
node_ip: 127.0.0.1
|
||||
|
||||
# Webhook:成员离开 / 房间结束等事件回调到 yudao 后端做业务态兜底清理
|
||||
# host.docker.internal 让容器访问宿主机 macOS / Windows 上的 yudao 后端
|
||||
|
||||
@@ -462,7 +462,7 @@ CREATE TABLE `system_dict_data` (
|
||||
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1061137 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1061147 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典数据表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of system_dict_data
|
||||
@@ -814,6 +814,16 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1512, 6, '委派中', '6', 'bpm_task_status', 0, 'primary', '', '', '1', '2024-03-17 10:06:22', '1', '2024-03-08 22:41:40', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1513, 7, '审批通过中', '7', 'bpm_task_status', 0, 'success', '', '', '1', '2024-03-17 10:06:47', '1', '2024-03-08 22:41:41', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1514, 0, '待审批', '0', 'bpm_task_status', 0, 'info', '', '', '1', '2024-03-17 10:07:11', '1', '2024-03-08 22:41:42', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061137, 0, '评论', '0', 'bpm_comment_type', 0, 'primary', '', '流程评论类型 - 评论', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061138, 1, '审批通过', '1', 'bpm_comment_type', 0, 'success', '', '流程评论类型 - 审批通过', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061139, 2, '不通过', '2', 'bpm_comment_type', 0, 'danger', '', '流程评论类型 - 不通过', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061140, 3, '已取消', '3', 'bpm_comment_type', 0, 'info', '', '流程评论类型 - 已取消', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061141, 4, '退回', '4', 'bpm_comment_type', 0, 'warning', '', '流程评论类型 - 退回', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061142, 5, '委派发起', '5', 'bpm_comment_type', 0, 'primary', '', '流程评论类型 - 委派发起', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061143, 6, '委派完成', '6', 'bpm_comment_type', 0, 'primary', '', '流程评论类型 - 委派完成', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061144, 7, '转派', '7', 'bpm_comment_type', 0, 'warning', '', '流程评论类型 - 转派', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061145, 8, '加签', '8', 'bpm_comment_type', 0, 'primary', '', '流程评论类型 - 加签', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1061146, 9, '减签', '9', 'bpm_comment_type', 0, 'info', '', '流程评论类型 - 减签', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1515, 35, '发起人自选', '35', 'bpm_task_candidate_strategy', 0, '', '', '', '1', '2024-03-22 19:45:16', '1', '2024-03-22 19:45:16', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1516, 1, '执行监听器', 'execution', 'bpm_process_listener_type', 0, 'primary', '', '', '1', '2024-03-23 12:54:03', '1', '2024-03-23 19:14:19', b'0');
|
||||
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (1517, 1, '任务监听器', 'task', 'bpm_process_listener_type', 0, 'success', '', '', '1', '2024-03-23 12:54:13', '1', '2024-03-23 19:14:24', b'0');
|
||||
@@ -1513,7 +1523,7 @@ CREATE TABLE `system_dict_type` (
|
||||
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
|
||||
`deleted_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1061099 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1061100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '字典类型表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of system_dict_type
|
||||
@@ -1726,6 +1736,7 @@ INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creat
|
||||
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061096, 'IM 通话参与角色', 'im_rtc_participant_role', 0, '1=发起人;2=被邀请者;3=主动加入者', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL);
|
||||
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061097, 'IM 通话参与状态', 'im_rtc_participant_status', 0, '10=邀请中;20=已加入;30=已拒绝;40=未应答;50=已离开', 'admin', '2026-05-18 03:36:12', 'admin', '2026-05-18 03:36:12', b'0', NULL);
|
||||
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061098, 'IM 频道素材内容类型', 'im_channel_material_type', 0, '1=站内富文本 / 2=外链', '1', '2026-05-19 14:09:25', '1', '2026-05-19 14:09:25', b'0', NULL);
|
||||
INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `deleted_time`) VALUES (1061099, '流程评论类型', 'bpm_comment_type', 0, 'Flowable 评论类型', '1', '2026-07-02 09:20:00', '1', '2026-07-02 09:20:00', b'0', NULL);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
|
||||
+53
-42
@@ -14,7 +14,7 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2026.06-jdk8-SNAPSHOT</revision>
|
||||
<revision>2026.07-jdk8-SNAPSHOT</revision>
|
||||
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.framework.version>5.3.39</spring.framework.version>
|
||||
@@ -41,9 +41,9 @@
|
||||
<!-- 服务保障相关 -->
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<!-- 监控相关 -->
|
||||
<skywalking.version>8.12.0</skywalking.version>
|
||||
<skywalking.version>9.6.0</skywalking.version>
|
||||
<spring-boot-admin.version>2.7.15</spring-boot-admin.version>
|
||||
<opentracing.version>0.33.0</opentracing.version>
|
||||
<opentelemetry.version>1.39.0</opentelemetry.version>
|
||||
<!-- Test 测试相关 -->
|
||||
<podam.version>7.2.11.RELEASE</podam.version> <!-- Spring Boot 2.X 最多使用 7.2.11 版本 -->
|
||||
<jedis-mock.version>1.1.15</jedis-mock.version>
|
||||
@@ -60,8 +60,7 @@
|
||||
<hutool-5.version>5.8.46</hutool-5.version>
|
||||
<fastexcel.version>1.3.0</fastexcel.version>
|
||||
<velocity.version>2.4</velocity.version> <!-- JDK8 不能从 2.4 升级到 2.4.1,会报包不存在!!!! -->
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<fastjson2.version>2.0.62</fastjson2.version>
|
||||
<fastjson2.version>2.0.63</fastjson2.version>
|
||||
<guava.version>33.6.0-jre</guava.version>
|
||||
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||
<commons-net.version>3.13.0</commons-net.version>
|
||||
@@ -76,8 +75,8 @@
|
||||
<okhttp.version>4.12.0</okhttp.version>
|
||||
<californium.version>3.14.0</californium.version>
|
||||
<j2mod.version>3.3.0</j2mod.version>
|
||||
<httpclient5.version>5.5.2</httpclient5.version> <!-- WxJava 4.8.x 需要 HttpClient5 5.4+,Spring Boot 2.7 默认 5.1.4 不兼容 -->
|
||||
<httpcore5.version>5.3.6</httpcore5.version> <!-- 配套 httpclient5 5.5.2,Spring Boot 2.7 默认 5.1.5 不兼容 -->
|
||||
<httpclient5.version>5.5.1</httpclient5.version> <!-- WxJava 4.8.x 需要 HttpClient5 5.4+,Spring Boot 2.7 默认 5.1.4 不兼容;JDK8 兼容不能升级到 5.5.2+ -->
|
||||
<httpcore5.version>5.3.6</httpcore5.version> <!-- 配套 httpclient5 5.5.1,Spring Boot 2.7 默认 5.1.5 不兼容 -->
|
||||
<!-- 三方云服务相关 -->
|
||||
<awssdk.version>2.46.17</awssdk.version>
|
||||
<justauth.version>1.16.7</justauth.version>
|
||||
@@ -257,6 +256,12 @@
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>easy-trans-anno</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -328,6 +333,12 @@
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||
<version>${rocketmq-spring.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- 服务保障相关 -->
|
||||
@@ -357,45 +368,25 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-trace</artifactId>
|
||||
<version>${skywalking.version}</version>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-api</artifactId>
|
||||
<version>${opentelemetry.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-sdk</artifactId>
|
||||
<version>${opentelemetry.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-exporter-otlp</artifactId>
|
||||
<version>${opentelemetry.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-logback-1.x</artifactId>
|
||||
<version>${skywalking.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-opentracing</artifactId>
|
||||
<version>${skywalking.version}</version>
|
||||
<!-- <exclusions>-->
|
||||
<!-- <exclusion>-->
|
||||
<!-- <artifactId>opentracing-api</artifactId>-->
|
||||
<!-- <groupId>io.opentracing</groupId>-->
|
||||
<!-- </exclusion>-->
|
||||
<!-- <exclusion>-->
|
||||
<!-- <artifactId>opentracing-util</artifactId>-->
|
||||
<!-- <groupId>io.opentracing</groupId>-->
|
||||
<!-- </exclusion>-->
|
||||
<!-- </exclusions>-->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentracing</groupId>
|
||||
<artifactId>opentracing-api</artifactId>
|
||||
<version>${opentracing.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentracing</groupId>
|
||||
<artifactId>opentracing-util</artifactId>
|
||||
<version>${opentracing.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentracing</groupId>
|
||||
<artifactId>opentracing-noop</artifactId>
|
||||
<version>${opentracing.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.codecentric</groupId>
|
||||
@@ -523,7 +514,7 @@
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
<version>${fastjson2.version}</version> <!-- fastjson2 官方提供的 1.x API 兼容实现 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
@@ -641,7 +632,7 @@
|
||||
<version>${j2mod.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- WxJava 4.8.x 需要 HttpClient5 5.4+,覆盖 Spring Boot 2.7 默认的 5.1.4 -->
|
||||
<!-- WxJava 4.8.x 需要 HttpClient5 5.4+,覆盖 Spring Boot 2.7 默认的 5.1.4;JDK8 兼容不能升级到 5.5.2+ -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
@@ -669,6 +660,12 @@
|
||||
<groupId>me.zhyd.oauth</groupId>
|
||||
<artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
|
||||
<version>${justauth.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xkcoding.justauth</groupId>
|
||||
@@ -692,6 +689,10 @@
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
@@ -734,6 +735,12 @@
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
<artifactId>jimureport-spring-boot-starter</artifactId>
|
||||
<version>${jimureport.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.jimureport</groupId>
|
||||
@@ -748,6 +755,10 @@
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -66,8 +66,8 @@
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-trace</artifactId>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
@@ -133,6 +133,11 @@
|
||||
<artifactId>transmittable-thread-local</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId> <!-- JustAuth、Jimu 等三方库仍使用 1.x 包名,由项目统一提供 fastjson2 兼容实现 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId> <!-- VO 数据翻译 -->
|
||||
<artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因,方便 xxx-module-api 包使用 -->
|
||||
|
||||
+9
@@ -37,6 +37,15 @@ public interface OAuth2TokenCommonApi {
|
||||
*/
|
||||
OAuth2AccessTokenRespDTO removeAccessToken(String accessToken);
|
||||
|
||||
/**
|
||||
* 移除用户的所有访问令牌
|
||||
* 注意:该流程中,会移除相关的刷新令牌
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param userType 用户类型
|
||||
*/
|
||||
void removeAccessToken(Long userId, Integer userType);
|
||||
|
||||
/**
|
||||
* 刷新访问令牌
|
||||
*
|
||||
|
||||
+5
-3
@@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.framework.common.util.monitor;
|
||||
|
||||
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.SpanContext;
|
||||
|
||||
/**
|
||||
* 链路追踪工具类
|
||||
@@ -18,13 +19,14 @@ public class TracerUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得链路追踪编号,直接返回 SkyWalking 的 TraceId。
|
||||
* 获得链路追踪编号,直接返回 OpenTelemetry 的 TraceId。
|
||||
* 如果不存在的话为空字符串!!!
|
||||
*
|
||||
* @return 链路追踪编号
|
||||
*/
|
||||
public static String getTraceId() {
|
||||
return TraceContext.traceId();
|
||||
SpanContext context = Span.current().getSpanContext();
|
||||
return context.isValid() ? context.getTraceId() : "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
package cn.iocoder.yudao.framework.excel.core.convert;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.idev.excel.converters.Converter;
|
||||
import cn.idev.excel.enums.CellDataTypeEnum;
|
||||
import cn.idev.excel.metadata.GlobalConfiguration;
|
||||
import cn.idev.excel.metadata.data.ReadCellData;
|
||||
import cn.idev.excel.metadata.data.WriteCellData;
|
||||
import cn.idev.excel.metadata.property.ExcelContentProperty;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Excel 多值数据字典转换器
|
||||
*
|
||||
* 数据库存储值使用半角逗号分隔,例如 {@code 1,2}
|
||||
* Excel 展示使用顿号分隔,例如 {@code 男、女}
|
||||
* 使用时,需要在字段上同时配置
|
||||
* {@code @ExcelProperty(converter = MultiDictConvert.class)} 和 {@link DictFormat}
|
||||
*
|
||||
* @author NaCl
|
||||
*/
|
||||
@Slf4j
|
||||
public class MultiDictConvert implements Converter<Object> {
|
||||
|
||||
private static final String EXCEL_SEPARATOR = "、";
|
||||
private static final String DB_SEPARATOR = ",";
|
||||
private static final String EXCEL_SEPARATOR_REGEX = "[、,,]";
|
||||
|
||||
@Override
|
||||
public Class<?> supportJavaTypeKey() {
|
||||
throw new UnsupportedOperationException("暂不支持,也不需要");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellDataTypeEnum supportExcelTypeKey() {
|
||||
throw new UnsupportedOperationException("暂不支持,也不需要");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
// 空时,返回空值
|
||||
Field field = contentProperty.getField();
|
||||
String labels = readCellData.getStringValue();
|
||||
if (StrUtil.isBlank(labels)) {
|
||||
return convertToFieldValue(field, new ArrayList<>());
|
||||
}
|
||||
|
||||
// 使用字典解析
|
||||
String type = getType(contentProperty);
|
||||
String[] labelArray = labels.split(EXCEL_SEPARATOR_REGEX);
|
||||
List<String> values = new ArrayList<>(labelArray.length);
|
||||
for (String item : labelArray) {
|
||||
String label = item.trim();
|
||||
if (StrUtil.isBlank(label)) {
|
||||
continue;
|
||||
}
|
||||
String value = DictFrameworkUtils.parseDictDataValue(type, label);
|
||||
if (value == null) {
|
||||
log.error("[convertToJavaData][type({}) 解析不掉 label({})]", type, label);
|
||||
return null;
|
||||
}
|
||||
values.add(value);
|
||||
}
|
||||
// 将 String 的 value 转换成对应的属性
|
||||
return convertToFieldValue(field, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty,
|
||||
GlobalConfiguration globalConfiguration) {
|
||||
// 空时,返回空
|
||||
if (object == null) {
|
||||
return new WriteCellData<>("");
|
||||
}
|
||||
|
||||
// 使用字典格式化
|
||||
String type = getType(contentProperty);
|
||||
List<String> values = convertToStringList(object);
|
||||
List<String> labels = new ArrayList<>(values.size());
|
||||
for (String value : values) {
|
||||
String label = DictFrameworkUtils.parseDictDataLabel(type, value);
|
||||
if (label == null) {
|
||||
log.error("[convertToExcelData][type({}) 转换不了 value({})]", type, value);
|
||||
return new WriteCellData<>("");
|
||||
}
|
||||
labels.add(label);
|
||||
}
|
||||
// 生成 Excel 小表格
|
||||
return new WriteCellData<>(String.join(EXCEL_SEPARATOR, labels));
|
||||
}
|
||||
|
||||
private static Object convertToFieldValue(Field field, List<String> values) {
|
||||
Class<?> fieldClazz = field.getType();
|
||||
if (String.class == fieldClazz) {
|
||||
return String.join(DB_SEPARATOR, values);
|
||||
}
|
||||
if (fieldClazz.isArray()) {
|
||||
return convertToArray(fieldClazz.getComponentType(), values);
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(fieldClazz)) {
|
||||
return convertToCollection(field, values);
|
||||
}
|
||||
return Convert.convert(fieldClazz, String.join(DB_SEPARATOR, values));
|
||||
}
|
||||
|
||||
private static Object convertToArray(Class<?> componentType, List<String> values) {
|
||||
Object array = Array.newInstance(componentType, values.size());
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
Array.set(array, i, Convert.convert(componentType, values.get(i)));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private static Collection<?> convertToCollection(Field field, List<String> values) {
|
||||
Class<?> elementClazz = getCollectionElementClazz(field);
|
||||
Collection<Object> result = createCollection(field.getType());
|
||||
for (String value : values) {
|
||||
result.add(Convert.convert(elementClazz, value));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Collection<Object> createCollection(Class<?> fieldClazz) {
|
||||
if (!fieldClazz.isInterface() && !Modifier.isAbstract(fieldClazz.getModifiers())) {
|
||||
try {
|
||||
return (Collection<Object>) fieldClazz.getDeclaredConstructor().newInstance();
|
||||
} catch (Exception ignored) {
|
||||
// 使用默认集合实现
|
||||
}
|
||||
}
|
||||
return Set.class.isAssignableFrom(fieldClazz) ? new LinkedHashSet<>() : new ArrayList<>();
|
||||
}
|
||||
|
||||
private static Class<?> getCollectionElementClazz(Field field) {
|
||||
Type genericType = field.getGenericType();
|
||||
if (!(genericType instanceof ParameterizedType)) {
|
||||
return String.class;
|
||||
}
|
||||
Type actualType = ((ParameterizedType) genericType).getActualTypeArguments()[0];
|
||||
if (actualType instanceof Class<?>) {
|
||||
return (Class<?>) actualType;
|
||||
}
|
||||
if (actualType instanceof ParameterizedType
|
||||
&& ((ParameterizedType) actualType).getRawType() instanceof Class<?>) {
|
||||
return (Class<?>) ((ParameterizedType) actualType).getRawType();
|
||||
}
|
||||
return String.class;
|
||||
}
|
||||
|
||||
private static List<String> convertToStringList(Object object) {
|
||||
List<String> values = new ArrayList<>();
|
||||
if (object instanceof String) {
|
||||
String[] valueArray = ((String) object).split(DB_SEPARATOR);
|
||||
for (String value : valueArray) {
|
||||
addStringValue(values, value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
if (object instanceof Collection<?>) {
|
||||
for (Object item : (Collection<?>) object) {
|
||||
addStringValue(values, item);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
if (object.getClass().isArray()) {
|
||||
int length = Array.getLength(object);
|
||||
for (int i = 0; i < length; i++) {
|
||||
addStringValue(values, Array.get(object, i));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
addStringValue(values, object);
|
||||
return values;
|
||||
}
|
||||
|
||||
private static void addStringValue(List<String> values, Object value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
String str = String.valueOf(value).trim();
|
||||
if (StrUtil.isBlank(str)) {
|
||||
return;
|
||||
}
|
||||
values.add(str);
|
||||
}
|
||||
|
||||
private static String getType(ExcelContentProperty contentProperty) {
|
||||
return contentProperty.getField().getAnnotation(DictFormat.class).value();
|
||||
}
|
||||
|
||||
}
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
package cn.iocoder.yudao.framework.excel.core.convert;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.idev.excel.metadata.data.ReadCellData;
|
||||
import cn.idev.excel.metadata.data.WriteCellData;
|
||||
import cn.idev.excel.metadata.property.ExcelContentProperty;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.dict.DictDataCommonApi;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
|
||||
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link MultiDictConvert} 的单元测试
|
||||
*
|
||||
* @author NaCl
|
||||
*/
|
||||
public class MultiDictConvertTest extends BaseMockitoUnitTest {
|
||||
|
||||
private static final String DICT_TYPE = "test_dict";
|
||||
|
||||
@Mock
|
||||
private DictDataCommonApi dictDataApi;
|
||||
|
||||
private MultiDictConvert convert;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
convert = new MultiDictConvert();
|
||||
DictFrameworkUtils.init(dictDataApi);
|
||||
DictFrameworkUtils.clearCache();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToExcelData_string() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
WriteCellData<String> result = convert.convertToExcelData("1,2", mockContentProperty("stringValues"), null);
|
||||
// 断言
|
||||
assertEquals("男、女", result.getStringValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToExcelData_single() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
WriteCellData<String> result = convert.convertToExcelData("1", mockContentProperty("stringValues"), null);
|
||||
// 断言
|
||||
assertEquals("男", result.getStringValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToExcelData_order() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
WriteCellData<String> result = convert.convertToExcelData("2,1", mockContentProperty("stringValues"), null);
|
||||
// 断言
|
||||
assertEquals("女、男", result.getStringValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToExcelData_null() {
|
||||
// 调用
|
||||
WriteCellData<String> result = convert.convertToExcelData(null, null, null);
|
||||
// 断言
|
||||
assertEquals("", result.getStringValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToExcelData_collection() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
WriteCellData<String> result = convert.convertToExcelData(ListUtil.of(1, 2),
|
||||
mockContentProperty("listValues"), null);
|
||||
// 断言
|
||||
assertEquals("男、女", result.getStringValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToExcelData_array() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
WriteCellData<String> result = convert.convertToExcelData(new int[]{1, 2},
|
||||
mockContentProperty("stringValues"), null);
|
||||
// 断言
|
||||
assertEquals("男、女", result.getStringValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToExcelData_unknownValue() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
WriteCellData<String> result = convert.convertToExcelData("1,3", mockContentProperty("stringValues"), null);
|
||||
// 断言
|
||||
assertEquals("", result.getStringValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToJavaData_string() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData("男、女"),
|
||||
mockContentProperty("stringValues"), null);
|
||||
// 断言
|
||||
assertEquals("1,2", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToJavaData_single() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData("男"),
|
||||
mockContentProperty("stringValues"), null);
|
||||
// 断言
|
||||
assertEquals("1", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToJavaData_blank() throws NoSuchFieldException {
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData(""),
|
||||
mockContentProperty("stringValues"), null);
|
||||
// 断言
|
||||
assertEquals("", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToJavaData_blankList() throws NoSuchFieldException {
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData(""),
|
||||
mockContentProperty("listValues"), null);
|
||||
// 断言
|
||||
assertTrue(result instanceof List);
|
||||
assertTrue(((List<?>) result).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToJavaData_blankSet() throws NoSuchFieldException {
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData(""),
|
||||
mockContentProperty("setValues"), null);
|
||||
// 断言
|
||||
assertTrue(result instanceof LinkedHashSet);
|
||||
assertTrue(((Set<?>) result).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToJavaData_blankArray() throws NoSuchFieldException {
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData(""),
|
||||
mockContentProperty("arrayValues"), null);
|
||||
// 断言
|
||||
assertArrayEquals(new Integer[0], (Integer[]) result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testConvertToJavaData_list() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData("男、女"),
|
||||
mockContentProperty("listValues"), null);
|
||||
// 断言
|
||||
assertEquals(ListUtil.of(1, 2), (List<Integer>) result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testConvertToJavaData_set() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData("男,女"),
|
||||
mockContentProperty("setValues"), null);
|
||||
// 断言
|
||||
assertEquals(new LinkedHashSet<>(Arrays.asList(1L, 2L)), (Set<Long>) result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("SimplifiableAssertion")
|
||||
public void testConvertToJavaData_hashSet() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData("男,女"),
|
||||
mockContentProperty("hashSetValues"), null);
|
||||
// 断言
|
||||
assertTrue(result instanceof HashSet);
|
||||
assertEquals(new HashSet<>(Arrays.asList(1L, 2L)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToJavaData_array() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData("男,女"),
|
||||
mockContentProperty("arrayValues"), null);
|
||||
// 断言
|
||||
assertArrayEquals(new Integer[]{1, 2}, (Integer[]) result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertToJavaData_unknownLabel() throws NoSuchFieldException {
|
||||
// mock 数据
|
||||
mockDictDatas();
|
||||
|
||||
// 调用
|
||||
Object result = convert.convertToJavaData(mockReadCellData("男、未知"),
|
||||
mockContentProperty("stringValues"), null);
|
||||
// 断言
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
private void mockDictDatas() {
|
||||
List<DictDataRespDTO> dictDatas = ListUtil.of(
|
||||
randomPojo(DictDataRespDTO.class, o -> o.setDictType(DICT_TYPE).setValue("1").setLabel("男")),
|
||||
randomPojo(DictDataRespDTO.class, o -> o.setDictType(DICT_TYPE).setValue("2").setLabel("女"))
|
||||
);
|
||||
when(dictDataApi.getDictDataList(eq(DICT_TYPE))).thenReturn(dictDatas);
|
||||
}
|
||||
|
||||
private static ReadCellData<?> mockReadCellData(String value) {
|
||||
ReadCellData<?> readCellData = mock(ReadCellData.class);
|
||||
when(readCellData.getStringValue()).thenReturn(value);
|
||||
return readCellData;
|
||||
}
|
||||
|
||||
private static ExcelContentProperty mockContentProperty(String fieldName) throws NoSuchFieldException {
|
||||
Field field = TestExcelVO.class.getDeclaredField(fieldName);
|
||||
ExcelContentProperty contentProperty = mock(ExcelContentProperty.class);
|
||||
when(contentProperty.getField()).thenReturn(field);
|
||||
return contentProperty;
|
||||
}
|
||||
|
||||
static class TestExcelVO {
|
||||
|
||||
@DictFormat(DICT_TYPE)
|
||||
private String stringValues;
|
||||
|
||||
@DictFormat(DICT_TYPE)
|
||||
private List<Integer> listValues;
|
||||
|
||||
@DictFormat(DICT_TYPE)
|
||||
private Set<Long> setValues;
|
||||
|
||||
@DictFormat(DICT_TYPE)
|
||||
private HashSet<Long> hashSetValues;
|
||||
|
||||
@DictFormat(DICT_TYPE)
|
||||
private Integer[] arrayValues;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,20 +35,25 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有 TraceFilter 使用 -->
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>io.opentracing</groupId>
|
||||
<artifactId>opentracing-util</artifactId>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-api</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-trace</artifactId>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-sdk</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.opentelemetry</groupId>
|
||||
<artifactId>opentelemetry-exporter-otlp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -56,11 +61,6 @@
|
||||
<artifactId>apm-toolkit-logback-1.x</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.skywalking</groupId>
|
||||
<artifactId>apm-toolkit-opentracing</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Micrometer 对 Prometheus 的支持 -->
|
||||
<dependency>
|
||||
|
||||
+20
-16
@@ -1,9 +1,14 @@
|
||||
package cn.iocoder.yudao.framework.tracer.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
|
||||
import cn.iocoder.yudao.framework.tracer.core.aop.BizTraceAspect;
|
||||
import cn.iocoder.yudao.framework.tracer.core.filter.TraceFilter;
|
||||
import io.opentelemetry.api.GlobalOpenTelemetry;
|
||||
import io.opentelemetry.api.trace.Tracer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
@@ -16,28 +21,27 @@ import org.springframework.context.annotation.Bean;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(name = {
|
||||
"org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer", // 来自 apm-toolkit-opentracing.jar
|
||||
// "io.opentracing.Tracer", // 来自 opentracing-api.jar
|
||||
"io.opentelemetry.api.trace.Tracer", // 来自 opentelemetry-api.jar
|
||||
"javax.servlet.Filter"
|
||||
})
|
||||
@EnableConfigurationProperties(TracerProperties.class)
|
||||
@ConditionalOnProperty(prefix = "yudao.tracer", value = "enable", matchIfMissing = true)
|
||||
public class YudaoTracerAutoConfiguration {
|
||||
|
||||
// TODO @芋艿:skywalking 不兼容最新的 opentracing 版本。同时,opentracing 也停止了维护,尬住了!后续换 opentelemetry 即可!
|
||||
// @Bean
|
||||
// public BizTraceAspect bizTracingAop() {
|
||||
// return new BizTraceAspect(tracer());
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public Tracer tracer() {
|
||||
// // 创建 SkywalkingTracer 对象
|
||||
// SkywalkingTracer tracer = new SkywalkingTracer();
|
||||
// // 设置为 GlobalTracer 的追踪器
|
||||
// GlobalTracer.registerIfAbsent(tracer);
|
||||
// return tracer;
|
||||
// }
|
||||
@Value("${spring.application.name:application}")
|
||||
private String applicationName;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Tracer tracer() {
|
||||
return GlobalOpenTelemetry.getTracer(applicationName);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public BizTraceAspect bizTracingAop(Tracer tracer) {
|
||||
return new BizTraceAspect(tracer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 TraceFilter 过滤器,响应 header 设置 traceId
|
||||
|
||||
+10
-10
@@ -5,9 +5,9 @@ import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.tracer.core.annotation.BizTrace;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import cn.iocoder.yudao.framework.tracer.core.util.TracerFrameworkUtils;
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.Tracer;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.Tracer;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
@@ -36,10 +36,10 @@ public class BizTraceAspect {
|
||||
public Object around(ProceedingJoinPoint joinPoint, BizTrace trace) throws Throwable {
|
||||
// 创建 span
|
||||
String operationName = getOperationName(joinPoint, trace);
|
||||
Span span = tracer.buildSpan(operationName)
|
||||
.withTag(Tags.COMPONENT.getKey(), "biz")
|
||||
.start();
|
||||
try {
|
||||
Span span = tracer.spanBuilder(operationName)
|
||||
.setAttribute("component", "biz")
|
||||
.startSpan();
|
||||
try (Scope ignored = span.makeCurrent()) {
|
||||
// 执行原有方法
|
||||
return joinPoint.proceed();
|
||||
} catch (Throwable throwable) {
|
||||
@@ -49,7 +49,7 @@ public class BizTraceAspect {
|
||||
// 设置 Span 的 biz 属性
|
||||
setBizTag(span, joinPoint, trace);
|
||||
// 完成 Span
|
||||
span.finish();
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ public class BizTraceAspect {
|
||||
private void setBizTag(Span span, ProceedingJoinPoint joinPoint, BizTrace trace) {
|
||||
try {
|
||||
Map<String, Object> result = SpringExpressionUtils.parseExpressions(joinPoint, asList(trace.type(), trace.id()));
|
||||
span.setTag(BizTrace.TYPE_TAG, MapUtil.getStr(result, trace.type()));
|
||||
span.setTag(BizTrace.ID_TAG, MapUtil.getStr(result, trace.id()));
|
||||
span.setAttribute(BizTrace.TYPE_TAG, MapUtil.getStr(result, trace.type()));
|
||||
span.setAttribute(BizTrace.ID_TAG, MapUtil.getStr(result, trace.id()));
|
||||
} catch (Exception ex) {
|
||||
log.error("[setBizTag][解析 bizType 与 bizId 发生异常]", ex);
|
||||
}
|
||||
|
||||
+15
-18
@@ -1,12 +1,10 @@
|
||||
package cn.iocoder.yudao.framework.tracer.core.util;
|
||||
|
||||
import io.opentracing.Span;
|
||||
import io.opentracing.tag.Tags;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.StatusCode;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 链路追踪 Util
|
||||
@@ -22,25 +20,24 @@ public class TracerFrameworkUtils {
|
||||
* @param span Span
|
||||
*/
|
||||
public static void onError(Throwable throwable, Span span) {
|
||||
Tags.ERROR.set(span, Boolean.TRUE);
|
||||
if (throwable != null) {
|
||||
span.log(errorLogs(throwable));
|
||||
// 忽略无效 Span
|
||||
if (span == null || !span.getSpanContext().isValid()) {
|
||||
return;
|
||||
}
|
||||
// 标记异常状态
|
||||
if (throwable == null) {
|
||||
span.setStatus(StatusCode.ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> errorLogs(Throwable throwable) {
|
||||
Map<String, Object> errorLogs = new HashMap<String, Object>(10);
|
||||
errorLogs.put("event", Tags.ERROR.getKey());
|
||||
errorLogs.put("error.object", throwable);
|
||||
errorLogs.put("error.kind", throwable.getClass().getName());
|
||||
// 记录异常事件
|
||||
span.recordException(throwable);
|
||||
String message = throwable.getCause() != null ? throwable.getCause().getMessage() : throwable.getMessage();
|
||||
if (message != null) {
|
||||
errorLogs.put("message", message);
|
||||
}
|
||||
span.setStatus(StatusCode.ERROR, message == null ? "" : message);
|
||||
// 记录异常堆栈
|
||||
StringWriter sw = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(sw));
|
||||
errorLogs.put("stack", sw.toString());
|
||||
return errorLogs;
|
||||
span.setAttribute("error.stack", sw.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -52,6 +53,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Order(0) // 优先于三方库默认的全局异常处理器,例如 JimuReport
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* MiniMax {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MiniMaxChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.minimaxi.com/v1";
|
||||
|
||||
public static final String MODEL_DEFAULT = "MiniMax-M3";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 月之暗面 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MoonshotChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.moonshot.cn";
|
||||
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "kimi-k2.6";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 阶跃星辰 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class StepFunChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.stepfun.com";
|
||||
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "step-3.7-flash";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 文心一言 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class YiYanChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://qianfan.baidubce.com/v2";
|
||||
|
||||
public static final String MODEL_DEFAULT = "ernie-5.1";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 智谱 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ZhiPuChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
||||
|
||||
public static final String MODEL_DEFAULT = "glm-5.2";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return deepSeekChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link GrokChatModel} 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class GrokChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("GROK_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("GROK_MODEL",
|
||||
GrokChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final GrokChatModel chatModel = new GrokChatModel(OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.baseUrl(GrokChatModel.BASE_URL)
|
||||
.completionsPath(GrokChatModel.COMPLETE_PATH)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link StepFunChatModel} 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class StepFunChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("STEPFUN_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("STEPFUN_MODEL",
|
||||
StepFunChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final StepFunChatModel chatModel = new StepFunChatModel(DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(StepFunChatModel.BASE_URL)
|
||||
.completionsPath(StepFunChatModel.COMPLETE_PATH)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
// System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
System.out.println(response.getResult() != null ? response.getResult().getOutput() : null);
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package cn.iocoder.yudao.module.bpm.controller.admin.comment;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.comment.vo.BpmCommentCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.comment.vo.BpmCommentRespVO;
|
||||
import cn.iocoder.yudao.module.bpm.service.comment.BpmCommentService;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import org.flowable.engine.task.Comment;
|
||||
import org.flowable.task.api.history.HistoricTaskInstance;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
|
||||
@Tag(name = "管理后台 - 流程评论")
|
||||
@RestController
|
||||
@RequestMapping("/bpm/comment")
|
||||
@Validated
|
||||
public class BpmCommentController {
|
||||
|
||||
@Resource
|
||||
private BpmCommentService commentService;
|
||||
@Resource
|
||||
private BpmTaskService taskService;
|
||||
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@GetMapping("/list-by-process-instance-id")
|
||||
@Operation(summary = "获得指定流程实例的评论列表")
|
||||
@Parameter(name = "processInstanceId", description = "流程实例的编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
|
||||
public CommonResult<List<BpmCommentRespVO>> getCommentListByProcessInstanceId(
|
||||
@RequestParam("processInstanceId") String processInstanceId) {
|
||||
// 获得评论列表
|
||||
List<Comment> commentList = commentService.getCommentListByProcessInstanceId(processInstanceId);
|
||||
if (CollUtil.isEmpty(commentList)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
|
||||
// 拼接 VO
|
||||
Map<String, HistoricTaskInstance> taskMap = taskService.getHistoricTaskMap(
|
||||
convertSet(commentList, Comment::getTaskId));
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
|
||||
convertSet(commentList, comment -> NumberUtils.parseLong(comment.getUserId())));
|
||||
return success(convertList(commentList, comment -> BeanUtils.toBean(comment, BpmCommentRespVO.class, commentVO -> {
|
||||
commentVO.setMessage(comment.getFullMessage())
|
||||
.setCreateTime(DateUtils.of(comment.getTime()))
|
||||
.setTask(BeanUtils.toBean(taskMap.get(comment.getTaskId()), BpmCommentRespVO.Task.class))
|
||||
.setUser(BeanUtils.toBean(userMap.get(NumberUtils.parseLong(comment.getUserId())), UserSimpleBaseVO.class));
|
||||
})));
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建流程评论")
|
||||
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
|
||||
public CommonResult<Boolean> createComment(@Valid @RequestBody BpmCommentCreateReqVO reqVO) {
|
||||
commentService.createComment(reqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.yudao.module.bpm.controller.admin.comment.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 流程评论创建 Request VO")
|
||||
@Data
|
||||
public class BpmCommentCreateReqVO {
|
||||
|
||||
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
@NotEmpty(message = "任务编号不能为空")
|
||||
private String taskId;
|
||||
|
||||
@Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "请关注报销发票附件")
|
||||
@NotBlank(message = "评论内容不能为空")
|
||||
@Size(max = 500, message = "评论内容不能超过 500 个字符")
|
||||
private String message;
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.bpm.controller.admin.comment.vo;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 流程评论 Response VO")
|
||||
@Data
|
||||
public class BpmCommentRespVO {
|
||||
|
||||
@Schema(description = "评论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "任务编号", example = "2048")
|
||||
private String taskId;
|
||||
|
||||
@Schema(description = "任务")
|
||||
private Task task;
|
||||
|
||||
@Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
|
||||
private String processInstanceId;
|
||||
|
||||
@Schema(description = "评论类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "请关注报销发票附件")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private UserSimpleBaseVO user;
|
||||
|
||||
@Schema(description = "任务")
|
||||
@Data
|
||||
public static class Task {
|
||||
|
||||
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
|
||||
private String id;
|
||||
|
||||
@Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "经理审批")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
|
||||
private String taskDefinitionKey;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.comment;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.comment.vo.BpmCommentCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
|
||||
import javax.validation.Valid;
|
||||
import org.flowable.engine.task.Comment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流程评论 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface BpmCommentService {
|
||||
|
||||
/**
|
||||
* 获得指定流程实例的评论列表
|
||||
*
|
||||
* @param processInstanceId 流程实例的编号
|
||||
* @return 评论列表
|
||||
*/
|
||||
List<Comment> getCommentListByProcessInstanceId(String processInstanceId);
|
||||
|
||||
/**
|
||||
* 创建流程评论
|
||||
*
|
||||
* @param reqVO 评论请求
|
||||
*/
|
||||
void createComment(@Valid BpmCommentCreateReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 创建流程评论
|
||||
*
|
||||
* @param taskId 任务编号
|
||||
* @param processInstanceId 流程实例编号
|
||||
* @param type 评论类型
|
||||
* @param params 评论模板参数
|
||||
*/
|
||||
void createComment(String taskId, String processInstanceId, BpmCommentTypeEnum type, Object... params);
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.comment;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.comment.vo.BpmCommentCreateReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmCommentTypeEnum;
|
||||
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.engine.task.Comment;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流程评论 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
public class BpmCommentServiceImpl implements BpmCommentService {
|
||||
|
||||
@Resource
|
||||
private TaskService taskService;
|
||||
@Resource
|
||||
@Lazy // 延迟加载,避免循环依赖
|
||||
private BpmTaskService bpmTaskService;
|
||||
|
||||
@Override
|
||||
public List<Comment> getCommentListByProcessInstanceId(String processInstanceId) {
|
||||
return taskService.getProcessInstanceComments(processInstanceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createComment(@Valid BpmCommentCreateReqVO reqVO) {
|
||||
Task task = bpmTaskService.validateTaskExists(reqVO.getTaskId());
|
||||
createComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.COMMENT, reqVO.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createComment(String taskId, String processInstanceId, BpmCommentTypeEnum type, Object... params) {
|
||||
taskService.addComment(taskId, processInstanceId, type.getType(), type.formatComment(params));
|
||||
}
|
||||
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
package cn.iocoder.yudao.module.bpm.service.definition;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeTypeEnum;
|
||||
import org.flowable.engine.RepositoryService;
|
||||
import org.flowable.engine.repository.Model;
|
||||
import org.flowable.engine.repository.ModelQuery;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
|
||||
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.AdditionalMatchers.aryEq;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* {@link BpmModelServiceImpl} 的单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class BpmModelServiceImplTest extends BaseMockitoUnitTest {
|
||||
|
||||
private static final Long TENANT_ID = 1L;
|
||||
private static final String MODEL_ID = "model-id";
|
||||
|
||||
@InjectMocks
|
||||
private BpmModelServiceImpl modelService;
|
||||
|
||||
@Mock
|
||||
private RepositoryService repositoryService;
|
||||
@Mock
|
||||
private ModelQuery modelQuery;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
TenantContextHolder.setTenantId(TENANT_ID);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportModel_bpmn() {
|
||||
// 准备参数
|
||||
Model model = mockModel(BpmModelTypeEnum.BPMN.getType());
|
||||
String bpmnXml = "<definitions />";
|
||||
// mock 方法(repositoryService)
|
||||
mockGetModel(model);
|
||||
when(repositoryService.getModelEditorSource(eq(MODEL_ID))).thenReturn(StrUtil.utf8Bytes(bpmnXml));
|
||||
|
||||
// 调用
|
||||
BpmModelSaveReqVO result = modelService.exportModel(MODEL_ID);
|
||||
|
||||
// 断言
|
||||
assertEquals(model.getKey(), result.getKey());
|
||||
assertEquals(model.getName(), result.getName());
|
||||
assertEquals(model.getCategory(), result.getCategory());
|
||||
assertEquals(BpmModelTypeEnum.BPMN.getType(), result.getType());
|
||||
assertEquals(bpmnXml, result.getBpmnXml());
|
||||
assertNull(result.getSimpleModel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportModel_simple() {
|
||||
// 准备参数
|
||||
Model model = mockModel(BpmModelTypeEnum.SIMPLE.getType());
|
||||
BpmSimpleModelNodeVO simpleModel = new BpmSimpleModelNodeVO();
|
||||
simpleModel.setId("start");
|
||||
simpleModel.setType(BpmSimpleModelNodeTypeEnum.START_NODE.getType());
|
||||
// mock 方法(repositoryService)
|
||||
mockGetModel(model);
|
||||
when(repositoryService.getModelEditorSourceExtra(eq(MODEL_ID)))
|
||||
.thenReturn(JsonUtils.toJsonByte(simpleModel));
|
||||
|
||||
// 调用
|
||||
BpmModelSaveReqVO result = modelService.exportModel(MODEL_ID);
|
||||
|
||||
// 断言
|
||||
assertEquals(BpmModelTypeEnum.SIMPLE.getType(), result.getType());
|
||||
assertNotNull(result.getSimpleModel());
|
||||
assertEquals(simpleModel.getId(), result.getSimpleModel().getId());
|
||||
assertEquals(simpleModel.getType(), result.getSimpleModel().getType());
|
||||
assertNull(result.getBpmnXml());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportModel_notExists() {
|
||||
// mock 方法(repositoryService)
|
||||
when(repositoryService.createModelQuery()).thenReturn(modelQuery);
|
||||
when(modelQuery.modelId(eq(MODEL_ID))).thenReturn(modelQuery);
|
||||
when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery);
|
||||
when(modelQuery.singleResult()).thenReturn(null);
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> modelService.exportModel(MODEL_ID), MODEL_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportModel_bpmn() {
|
||||
// 准备参数
|
||||
BpmModelSaveReqVO reqVO = new BpmModelSaveReqVO();
|
||||
reqVO.setKey("test_process");
|
||||
reqVO.setName("测试流程");
|
||||
reqVO.setCategory("OA");
|
||||
reqVO.setType(BpmModelTypeEnum.BPMN.getType());
|
||||
reqVO.setBpmnXml("<definitions />");
|
||||
reqVO.setStartUserIds(Arrays.asList(10L, 20L));
|
||||
reqVO.setStartDeptIds(Collections.singletonList(30L));
|
||||
reqVO.setManagerUserIds(Collections.singletonList(40L));
|
||||
Model model = mock(Model.class);
|
||||
when(model.getId()).thenReturn(MODEL_ID);
|
||||
// mock 方法(repositoryService)
|
||||
when(repositoryService.createModelQuery()).thenReturn(modelQuery);
|
||||
when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery);
|
||||
when(modelQuery.modelKey(eq(reqVO.getKey()))).thenReturn(modelQuery);
|
||||
when(modelQuery.singleResult()).thenReturn(null);
|
||||
when(repositoryService.newModel()).thenReturn(model);
|
||||
|
||||
// 调用
|
||||
String result = modelService.importModel(reqVO);
|
||||
|
||||
// 断言
|
||||
assertEquals(MODEL_ID, result);
|
||||
verify(model).setTenantId(TENANT_ID.toString());
|
||||
verify(repositoryService).saveModel(same(model));
|
||||
verify(repositoryService).addModelEditorSource(eq(MODEL_ID),
|
||||
aryEq(StrUtil.utf8Bytes(reqVO.getBpmnXml())));
|
||||
ArgumentCaptor<String> metaInfoCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(model).setMetaInfo(metaInfoCaptor.capture());
|
||||
BpmModelMetaInfoVO metaInfo = JsonUtils.parseObject(metaInfoCaptor.getValue(), BpmModelMetaInfoVO.class);
|
||||
assertNotNull(metaInfo);
|
||||
assertEquals(Arrays.asList(10L, 20L), metaInfo.getStartUserIds());
|
||||
assertEquals(Collections.singletonList(30L), metaInfo.getStartDeptIds());
|
||||
assertEquals(Collections.singletonList(40L), metaInfo.getManagerUserIds());
|
||||
}
|
||||
|
||||
private Model mockModel(Integer type) {
|
||||
Model model = mock(Model.class);
|
||||
when(model.getKey()).thenReturn("test_process");
|
||||
when(model.getName()).thenReturn("测试流程");
|
||||
when(model.getCategory()).thenReturn("OA");
|
||||
when(model.getCreateTime()).thenReturn(new Date(1_000L));
|
||||
BpmModelMetaInfoVO metaInfo = new BpmModelMetaInfoVO();
|
||||
metaInfo.setType(type);
|
||||
when(model.getMetaInfo()).thenReturn(JsonUtils.toJsonString(metaInfo));
|
||||
return model;
|
||||
}
|
||||
|
||||
private void mockGetModel(Model model) {
|
||||
when(repositoryService.createModelQuery()).thenReturn(modelQuery);
|
||||
when(modelQuery.modelId(eq(MODEL_ID))).thenReturn(modelQuery);
|
||||
when(modelQuery.modelTenantId(eq(TENANT_ID.toString()))).thenReturn(modelQuery);
|
||||
when(modelQuery.singleResult()).thenReturn(model);
|
||||
}
|
||||
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.performance;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigPageReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigSaveReqVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.service.performance.CrmPerformanceConfigService;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
@Tag(name = "管理后台 - CRM 业绩目标设置")
|
||||
@RestController
|
||||
@RequestMapping("/crm/performance-config")
|
||||
@Validated
|
||||
public class CrmPerformanceConfigController {
|
||||
|
||||
@Resource
|
||||
private CrmPerformanceConfigService performanceConfigService;
|
||||
|
||||
@Resource
|
||||
private DeptApi deptApi;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建业绩目标")
|
||||
@PreAuthorize("@ss.hasPermission('crm:performance-config:create')")
|
||||
public CommonResult<Long> createPerformanceConfig(@Valid @RequestBody CrmPerformanceConfigSaveReqVO createReqVO) {
|
||||
return success(performanceConfigService.createPerformanceConfig(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新业绩目标")
|
||||
@PreAuthorize("@ss.hasPermission('crm:performance-config:update')")
|
||||
public CommonResult<Boolean> updatePerformanceConfig(@Valid @RequestBody CrmPerformanceConfigSaveReqVO updateReqVO) {
|
||||
performanceConfigService.updatePerformanceConfig(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除业绩目标")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('crm:performance-config:delete')")
|
||||
public CommonResult<Boolean> deletePerformanceConfig(@RequestParam("id") Long id) {
|
||||
performanceConfigService.deletePerformanceConfig(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得业绩目标")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('crm:performance-config:query')")
|
||||
public CommonResult<CrmPerformanceConfigRespVO> getPerformanceConfig(@RequestParam("id") Long id) {
|
||||
// 1. 获取业绩目标
|
||||
CrmPerformanceConfigDO performanceConfig = performanceConfigService.getPerformanceConfig(id);
|
||||
// 2. 拼接数据
|
||||
return success(buildPerformanceConfigDetail(performanceConfig));
|
||||
}
|
||||
|
||||
private CrmPerformanceConfigRespVO buildPerformanceConfigDetail(CrmPerformanceConfigDO performanceConfig) {
|
||||
if (performanceConfig == null) {
|
||||
return null;
|
||||
}
|
||||
// 1. 转换成 VO
|
||||
CrmPerformanceConfigRespVO respVO = BeanUtils.toBean(performanceConfig, CrmPerformanceConfigRespVO.class);
|
||||
// 2. 拼接目标对象名称
|
||||
return buildPerformanceConfigObjectName(respVO);
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得业绩目标分页")
|
||||
@PreAuthorize("@ss.hasPermission('crm:performance-config:query')")
|
||||
public CommonResult<PageResult<CrmPerformanceConfigRespVO>> getPerformanceConfigPage(@Valid CrmPerformanceConfigPageReqVO pageReqVO) {
|
||||
// 1. 查询业绩目标分页
|
||||
PageResult<CrmPerformanceConfigDO> pageResult = performanceConfigService.getPerformanceConfigPage(pageReqVO);
|
||||
if (CollUtil.isEmpty(pageResult.getList())) {
|
||||
return success(PageResult.empty(pageResult.getTotal()));
|
||||
}
|
||||
// 2. 拼接数据
|
||||
return success(BeanUtils.toBean(pageResult, CrmPerformanceConfigRespVO.class)
|
||||
.setList(buildPerformanceConfigDetailList(pageResult.getList())));
|
||||
}
|
||||
|
||||
private List<CrmPerformanceConfigRespVO> buildPerformanceConfigDetailList(List<CrmPerformanceConfigDO> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return emptyList();
|
||||
}
|
||||
// 1. 转换成 VO
|
||||
List<CrmPerformanceConfigRespVO> voList = BeanUtils.toBean(list, CrmPerformanceConfigRespVO.class);
|
||||
// 2. 拼接目标对象名称
|
||||
return buildPerformanceConfigObjectNameList(voList);
|
||||
}
|
||||
|
||||
// ========== VO 拼装 ==========
|
||||
|
||||
private CrmPerformanceConfigRespVO buildPerformanceConfigObjectName(CrmPerformanceConfigRespVO config) {
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
return buildPerformanceConfigObjectNameList(singletonList(config)).get(0);
|
||||
}
|
||||
|
||||
private List<CrmPerformanceConfigRespVO> buildPerformanceConfigObjectNameList(List<CrmPerformanceConfigRespVO> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return emptyList();
|
||||
}
|
||||
// 1.1 获取部门列表
|
||||
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(list, CrmPerformanceConfigRespVO::getObjectId,
|
||||
item -> Objects.equals(item.getObjectType(), CrmPerformanceConfigObjectTypeEnum.DEPT.getObjectType())));
|
||||
// 1.2 获取员工列表
|
||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(list, CrmPerformanceConfigRespVO::getObjectId,
|
||||
item -> Objects.equals(item.getObjectType(), CrmPerformanceConfigObjectTypeEnum.USER.getObjectType())));
|
||||
// 2. 设置目标对象名称
|
||||
list.forEach(item -> {
|
||||
if (Objects.equals(item.getObjectType(), CrmPerformanceConfigObjectTypeEnum.DEPT.getObjectType())) {
|
||||
findAndThen(deptMap, item.getObjectId(), dept -> item.setObjectName(dept.getName()));
|
||||
} else if (Objects.equals(item.getObjectType(), CrmPerformanceConfigObjectTypeEnum.USER.getObjectType())) {
|
||||
findAndThen(userMap, item.getObjectId(), user -> item.setObjectName(user.getNickname()));
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 业绩目标设置分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class CrmPerformanceConfigPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "目标对象编号", example = "1")
|
||||
private Long objectId;
|
||||
|
||||
@Schema(description = "目标对象类型", example = "2")
|
||||
@InEnum(value = CrmPerformanceConfigObjectTypeEnum.class, message = "目标对象类型,必须是 {value}")
|
||||
private Integer objectType;
|
||||
|
||||
@Schema(description = "年份", example = "2026")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "目标类型", example = "5")
|
||||
@InEnum(value = CrmPerformanceConfigBizTypeEnum.class, message = "目标类型,必须是 {value}")
|
||||
private Integer bizType;
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 业绩目标设置 Response VO")
|
||||
@Data
|
||||
public class CrmPerformanceConfigRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "目标对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long objectId;
|
||||
|
||||
@Schema(description = "目标对象名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "销售部")
|
||||
private String objectName;
|
||||
|
||||
@Schema(description = "目标对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private Integer objectType;
|
||||
|
||||
@Schema(description = "年份", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "一月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal januaryTargetPrice;
|
||||
|
||||
@Schema(description = "二月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal februaryTargetPrice;
|
||||
|
||||
@Schema(description = "三月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal marchTargetPrice;
|
||||
|
||||
@Schema(description = "四月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal aprilTargetPrice;
|
||||
|
||||
@Schema(description = "五月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal mayTargetPrice;
|
||||
|
||||
@Schema(description = "六月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal juneTargetPrice;
|
||||
|
||||
@Schema(description = "七月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal julyTargetPrice;
|
||||
|
||||
@Schema(description = "八月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal augustTargetPrice;
|
||||
|
||||
@Schema(description = "九月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal septemberTargetPrice;
|
||||
|
||||
@Schema(description = "十月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal octoberTargetPrice;
|
||||
|
||||
@Schema(description = "十一月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal novemberTargetPrice;
|
||||
|
||||
@Schema(description = "十二月目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal decemberTargetPrice;
|
||||
|
||||
@Schema(description = "目标类型",
|
||||
requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
|
||||
private Integer bizType;
|
||||
|
||||
@Schema(description = "年度目标金额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private BigDecimal yearTargetPrice;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 业绩目标设置新增/修改 Request VO")
|
||||
@Data
|
||||
public class CrmPerformanceConfigSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "目标对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "目标对象编号不能为空")
|
||||
private Long objectId;
|
||||
|
||||
@Schema(description = "目标对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
@NotNull(message = "目标对象类型不能为空")
|
||||
@InEnum(value = CrmPerformanceConfigObjectTypeEnum.class, message = "目标对象类型,必须是 {value}")
|
||||
private Integer objectType;
|
||||
|
||||
@Schema(description = "年份", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026")
|
||||
@NotNull(message = "年份不能为空")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "一月目标金额")
|
||||
private BigDecimal januaryTargetPrice;
|
||||
|
||||
@Schema(description = "二月目标金额")
|
||||
private BigDecimal februaryTargetPrice;
|
||||
|
||||
@Schema(description = "三月目标金额")
|
||||
private BigDecimal marchTargetPrice;
|
||||
|
||||
@Schema(description = "四月目标金额")
|
||||
private BigDecimal aprilTargetPrice;
|
||||
|
||||
@Schema(description = "五月目标金额")
|
||||
private BigDecimal mayTargetPrice;
|
||||
|
||||
@Schema(description = "六月目标金额")
|
||||
private BigDecimal juneTargetPrice;
|
||||
|
||||
@Schema(description = "七月目标金额")
|
||||
private BigDecimal julyTargetPrice;
|
||||
|
||||
@Schema(description = "八月目标金额")
|
||||
private BigDecimal augustTargetPrice;
|
||||
|
||||
@Schema(description = "九月目标金额")
|
||||
private BigDecimal septemberTargetPrice;
|
||||
|
||||
@Schema(description = "十月目标金额")
|
||||
private BigDecimal octoberTargetPrice;
|
||||
|
||||
@Schema(description = "十一月目标金额")
|
||||
private BigDecimal novemberTargetPrice;
|
||||
|
||||
@Schema(description = "十二月目标金额")
|
||||
private BigDecimal decemberTargetPrice;
|
||||
|
||||
@Schema(description = "目标类型",
|
||||
requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
|
||||
@NotNull(message = "目标类型不能为空")
|
||||
@InEnum(value = CrmPerformanceConfigBizTypeEnum.class, message = "目标类型,必须是 {value}")
|
||||
private Integer bizType;
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetRespVO;
|
||||
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsPerformanceTargetService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - CRM 业绩目标统计")
|
||||
@RestController
|
||||
@RequestMapping("/crm/statistics-performance-target")
|
||||
@Validated
|
||||
public class CrmStatisticsPerformanceTargetController {
|
||||
|
||||
@Resource
|
||||
private CrmStatisticsPerformanceTargetService performanceTargetService;
|
||||
|
||||
@GetMapping("/get-performance-target-summary")
|
||||
@Operation(summary = "获取业绩目标完成情况")
|
||||
@PreAuthorize("@ss.hasPermission('crm:statistics-performance-target:query')")
|
||||
public CommonResult<List<CrmStatisticsPerformanceTargetRespVO>> getPerformanceTargetSummary(@Valid CrmStatisticsPerformanceTargetReqVO reqVO) {
|
||||
return success(performanceTargetService.getPerformanceTargetSummary(reqVO));
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO;
|
||||
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsProductService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - CRM 产品统计")
|
||||
@RestController
|
||||
@RequestMapping("/crm/statistics-product")
|
||||
@Validated
|
||||
public class CrmStatisticsProductController {
|
||||
|
||||
@Resource
|
||||
private CrmStatisticsProductService productService;
|
||||
|
||||
@GetMapping("/get-product-sales-list")
|
||||
@Operation(summary = "获取产品销售情况统计")
|
||||
@PreAuthorize("@ss.hasPermission('crm:statistics-product:query')")
|
||||
public CommonResult<List<CrmStatisticsProductSalesRespVO>> getProductSalesList(@Valid CrmStatisticsProductReqVO reqVO) {
|
||||
return success(productService.getProductSalesList(reqVO));
|
||||
}
|
||||
|
||||
@GetMapping("/get-product-category-summary")
|
||||
@Operation(summary = "获取产品分类销售分析")
|
||||
@PreAuthorize("@ss.hasPermission('crm:statistics-product:query')")
|
||||
public CommonResult<List<CrmStatisticsProductCategoryRespVO>> getProductCategorySummary(@Valid CrmStatisticsProductReqVO reqVO) {
|
||||
return success(productService.getProductCategorySummary(reqVO));
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 商机阶段统计 Response VO")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class CrmStatisticsBusinessSummaryByStatusRespVO {
|
||||
|
||||
@Schema(description = "商机阶段编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long statusId;
|
||||
|
||||
@Schema(description = "商机阶段名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "初步接洽")
|
||||
private String statusName;
|
||||
|
||||
@Schema(description = "赢单率,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
|
||||
private Integer statusPercent;
|
||||
|
||||
@Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer sort;
|
||||
|
||||
@Schema(description = "商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long businessCount;
|
||||
|
||||
@Schema(description = "商机总金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private BigDecimal totalPrice;
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 员工业绩合同汇总 Response VO")
|
||||
@Data
|
||||
public class CrmStatisticsPerformanceSummaryRespVO {
|
||||
|
||||
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
|
||||
private String time;
|
||||
|
||||
@Schema(description = "合同数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Integer contractCount;
|
||||
|
||||
@Schema(description = "合同金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000.00")
|
||||
private BigDecimal contractPrice;
|
||||
|
||||
@Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000.00")
|
||||
private BigDecimal receivablePrice;
|
||||
|
||||
@Schema(description = "未回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000.00")
|
||||
private BigDecimal unreceivedPrice;
|
||||
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 业绩目标完成实际值 Response VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class CrmStatisticsPerformanceTargetActualRespVO {
|
||||
|
||||
@Schema(description = "月份", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer month;
|
||||
|
||||
@Schema(description = "完成金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000.00")
|
||||
private BigDecimal currentPrice;
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 业绩目标完成情况 Request VO")
|
||||
@Data
|
||||
public class CrmStatisticsPerformanceTargetReqVO {
|
||||
|
||||
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "部门 id 不能为空")
|
||||
private Long deptId;
|
||||
|
||||
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
|
||||
private List<Long> userIds;
|
||||
|
||||
@Schema(description = "年份", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024")
|
||||
@NotNull(message = "年份不能为空")
|
||||
private Integer year;
|
||||
|
||||
@Schema(description = "目标类型",
|
||||
requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
|
||||
@NotNull(message = "目标类型不能为空")
|
||||
@InEnum(value = CrmPerformanceConfigBizTypeEnum.class, message = "目标类型,必须是 {value}")
|
||||
private Integer bizType;
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 业绩目标完成情况 Response VO")
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class CrmStatisticsPerformanceTargetRespVO {
|
||||
|
||||
@Schema(description = "月份", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Integer month;
|
||||
|
||||
@Schema(description = "目标金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000.00")
|
||||
private BigDecimal targetPrice;
|
||||
|
||||
@Schema(description = "完成金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "8000.00")
|
||||
private BigDecimal currentPrice;
|
||||
|
||||
@Schema(description = "完成率", requiredMode = Schema.RequiredMode.REQUIRED, example = "80.00")
|
||||
private BigDecimal completionRate;
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 产品分类销售分析 Response VO")
|
||||
@Data
|
||||
public class CrmStatisticsProductCategoryRespVO {
|
||||
|
||||
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "产品分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "软件")
|
||||
private String categoryName;
|
||||
|
||||
@Schema(description = "合同数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
|
||||
private Integer contractCount;
|
||||
|
||||
@Schema(description = "销售数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
|
||||
private BigDecimal productCount;
|
||||
|
||||
@Schema(description = "销售金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20000.00")
|
||||
private BigDecimal productTotalPrice;
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 产品分析 Request VO")
|
||||
@Data
|
||||
public class CrmStatisticsProductReqVO {
|
||||
|
||||
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "部门 id 不能为空")
|
||||
private Long deptId;
|
||||
|
||||
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
|
||||
private List<Long> userIds;
|
||||
|
||||
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
@NotEmpty(message = "请选择时间范围")
|
||||
@Size(min = 2, max = 2, message = "请选择时间范围")
|
||||
private LocalDateTime[] times;
|
||||
|
||||
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "10")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "产品分类编号集合", hidden = true, example = "10")
|
||||
private List<Long> categoryIds;
|
||||
|
||||
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "20")
|
||||
private Long productId;
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(description = "管理后台 - CRM 产品销售情况 Response VO")
|
||||
@Data
|
||||
public class CrmStatisticsProductSalesRespVO {
|
||||
|
||||
@Schema(description = "产品分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
||||
private Long categoryId;
|
||||
|
||||
@Schema(description = "产品分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "软件")
|
||||
private String categoryName;
|
||||
|
||||
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "CRM 专业版")
|
||||
private String productName;
|
||||
|
||||
@Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
|
||||
private Long contractId;
|
||||
|
||||
@Schema(description = "合同编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "HT20240101001")
|
||||
private String contractNo;
|
||||
|
||||
@Schema(description = "合同名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "CRM 采购合同")
|
||||
private String contractName;
|
||||
|
||||
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long ownerUserId;
|
||||
|
||||
@Schema(description = "负责人姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
private String ownerUserName;
|
||||
|
||||
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "40")
|
||||
private Long customerId;
|
||||
|
||||
@Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试客户")
|
||||
private String customerName;
|
||||
|
||||
@Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
|
||||
private BigDecimal productPrice;
|
||||
|
||||
@Schema(description = "销售数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
|
||||
private BigDecimal productCount;
|
||||
|
||||
@Schema(description = "销售金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "200.00")
|
||||
private BigDecimal productTotalPrice;
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package cn.iocoder.yudao.module.crm.dal.dataobject.performance;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* CRM 业绩目标 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("crm_performance_config")
|
||||
@KeySequence("crm_performance_config_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CrmPerformanceConfigDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 目标类型
|
||||
*
|
||||
* 枚举 {@link CrmPerformanceConfigBizTypeEnum}
|
||||
*/
|
||||
private Integer bizType;
|
||||
/**
|
||||
* 目标对象编号
|
||||
*/
|
||||
private Long objectId;
|
||||
/**
|
||||
* 目标对象类型
|
||||
*
|
||||
* 枚举 {@link CrmPerformanceConfigObjectTypeEnum}
|
||||
*/
|
||||
private Integer objectType;
|
||||
|
||||
/**
|
||||
* 年份
|
||||
*/
|
||||
private Integer year;
|
||||
/**
|
||||
* 年度目标金额
|
||||
*/
|
||||
private BigDecimal yearTargetPrice;
|
||||
/**
|
||||
* 一月目标金额
|
||||
*/
|
||||
private BigDecimal januaryTargetPrice;
|
||||
/**
|
||||
* 二月目标金额
|
||||
*/
|
||||
private BigDecimal februaryTargetPrice;
|
||||
/**
|
||||
* 三月目标金额
|
||||
*/
|
||||
private BigDecimal marchTargetPrice;
|
||||
/**
|
||||
* 四月目标金额
|
||||
*/
|
||||
private BigDecimal aprilTargetPrice;
|
||||
/**
|
||||
* 五月目标金额
|
||||
*/
|
||||
private BigDecimal mayTargetPrice;
|
||||
/**
|
||||
* 六月目标金额
|
||||
*/
|
||||
private BigDecimal juneTargetPrice;
|
||||
/**
|
||||
* 七月目标金额
|
||||
*/
|
||||
private BigDecimal julyTargetPrice;
|
||||
/**
|
||||
* 八月目标金额
|
||||
*/
|
||||
private BigDecimal augustTargetPrice;
|
||||
/**
|
||||
* 九月目标金额
|
||||
*/
|
||||
private BigDecimal septemberTargetPrice;
|
||||
/**
|
||||
* 十月目标金额
|
||||
*/
|
||||
private BigDecimal octoberTargetPrice;
|
||||
/**
|
||||
* 十一月目标金额
|
||||
*/
|
||||
private BigDecimal novemberTargetPrice;
|
||||
/**
|
||||
* 十二月目标金额
|
||||
*/
|
||||
private BigDecimal decemberTargetPrice;
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package cn.iocoder.yudao.module.crm.dal.dataobject.permission;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* CRM 负责人变更记录 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("crm_owner_record")
|
||||
@KeySequence("crm_owner_record_seq")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CrmOwnerRecordDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* CRM 业务类型
|
||||
*
|
||||
* 枚举 {@link CrmBizTypeEnum}
|
||||
*/
|
||||
private Integer bizType;
|
||||
/**
|
||||
* CRM 业务编号
|
||||
*/
|
||||
private Long bizId;
|
||||
/**
|
||||
* 变更前负责人
|
||||
*/
|
||||
private Long preOwnerUserId;
|
||||
/**
|
||||
* 变更后负责人
|
||||
*/
|
||||
private Long postOwnerUserId;
|
||||
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package cn.iocoder.yudao.module.crm.dal.mysql.performance;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigPageReqVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CRM 业绩目标配置 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface CrmPerformanceConfigMapper extends BaseMapperX<CrmPerformanceConfigDO> {
|
||||
|
||||
default PageResult<CrmPerformanceConfigDO> selectPage(CrmPerformanceConfigPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<CrmPerformanceConfigDO>()
|
||||
.eqIfPresent(CrmPerformanceConfigDO::getObjectId, reqVO.getObjectId())
|
||||
.eqIfPresent(CrmPerformanceConfigDO::getObjectType, reqVO.getObjectType())
|
||||
.eqIfPresent(CrmPerformanceConfigDO::getYear, reqVO.getYear())
|
||||
.eqIfPresent(CrmPerformanceConfigDO::getBizType, reqVO.getBizType())
|
||||
.orderByDesc(CrmPerformanceConfigDO::getYear)
|
||||
.orderByAsc(CrmPerformanceConfigDO::getObjectType)
|
||||
.orderByAsc(CrmPerformanceConfigDO::getObjectId)
|
||||
.orderByAsc(CrmPerformanceConfigDO::getBizType));
|
||||
}
|
||||
|
||||
default CrmPerformanceConfigDO selectByObjectIdAndObjectTypeAndYearAndBizType(Long objectId, Integer objectType,
|
||||
Integer year, Integer bizType) {
|
||||
return selectOne(new LambdaQueryWrapperX<CrmPerformanceConfigDO>()
|
||||
.eq(CrmPerformanceConfigDO::getObjectId, objectId)
|
||||
.eq(CrmPerformanceConfigDO::getObjectType, objectType)
|
||||
.eq(CrmPerformanceConfigDO::getYear, year)
|
||||
.eq(CrmPerformanceConfigDO::getBizType, bizType));
|
||||
}
|
||||
|
||||
default List<CrmPerformanceConfigDO> selectListByObjectIdsAndObjectTypeAndYearAndBizType(Collection<Long> objectIds,
|
||||
Integer objectType,
|
||||
Integer year,
|
||||
Integer bizType) {
|
||||
return selectList(new LambdaQueryWrapperX<CrmPerformanceConfigDO>()
|
||||
.in(CrmPerformanceConfigDO::getObjectId, objectIds)
|
||||
.eq(CrmPerformanceConfigDO::getObjectType, objectType)
|
||||
.eq(CrmPerformanceConfigDO::getYear, year)
|
||||
.eq(CrmPerformanceConfigDO::getBizType, bizType));
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.crm.dal.mysql.permission;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmOwnerRecordDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CRM 负责人变更记录 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface CrmOwnerRecordMapper extends BaseMapperX<CrmOwnerRecordDO> {
|
||||
|
||||
/**
|
||||
* 进入公海客户数(按日期)
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 统计数据
|
||||
*/
|
||||
List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 公海领取客户数(按日期)
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 统计数据
|
||||
*/
|
||||
List<CrmStatisticsPoolSummaryByDateRespVO> selectPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 进入公海客户数(按用户)
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 统计数据
|
||||
*/
|
||||
List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 公海领取客户数(按用户)
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 统计数据
|
||||
*/
|
||||
List<CrmStatisticsPoolSummaryByUserRespVO> selectPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO);
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
|
||||
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetActualRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetReqVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CRM 业绩目标完成情况 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface CrmStatisticsPerformanceTargetMapper {
|
||||
|
||||
/**
|
||||
* 按月统计合同金额
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 按月统计合同金额
|
||||
*/
|
||||
List<CrmStatisticsPerformanceTargetActualRespVO> selectContractActualByMonth(CrmStatisticsPerformanceTargetReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 按月统计回款金额
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 按月统计回款金额
|
||||
*/
|
||||
List<CrmStatisticsPerformanceTargetActualRespVO> selectReceivableActualByMonth(CrmStatisticsPerformanceTargetReqVO reqVO);
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
|
||||
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CRM 产品分析 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface CrmStatisticsProductMapper {
|
||||
|
||||
/**
|
||||
* 获取产品销售情况统计
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 产品销售情况
|
||||
*/
|
||||
List<CrmStatisticsProductSalesRespVO> selectProductSalesList(CrmStatisticsProductReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获取产品分类销售分析
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 产品分类销售分析
|
||||
*/
|
||||
List<CrmStatisticsProductCategoryRespVO> selectProductCategorySummary(CrmStatisticsProductReqVO reqVO);
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.crm.enums.performance;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* CRM 业绩目标业务类型枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CrmPerformanceConfigBizTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
CRM_CONTRACT(CrmBizTypeEnum.CRM_CONTRACT.getType(), "销售目标"),
|
||||
CRM_RECEIVABLE(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), "回款目标");
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values())
|
||||
.map(CrmPerformanceConfigBizTypeEnum::getBizType).toArray(Integer[]::new);
|
||||
|
||||
/**
|
||||
* 业务类型
|
||||
*/
|
||||
private final Integer bizType;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.yudao.module.crm.enums.performance;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* CRM 业绩目标对象类型枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CrmPerformanceConfigObjectTypeEnum implements ArrayValuable<Integer> {
|
||||
|
||||
DEPT(2, "部门"),
|
||||
USER(3, "员工");
|
||||
|
||||
public static final Integer[] ARRAYS = Arrays.stream(values())
|
||||
.map(CrmPerformanceConfigObjectTypeEnum::getObjectType).toArray(Integer[]::new);
|
||||
|
||||
/**
|
||||
* 对象类型
|
||||
*/
|
||||
private final Integer objectType;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public Integer[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package cn.iocoder.yudao.module.crm.service.performance;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigPageReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigSaveReqVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CRM 业绩目标设置 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface CrmPerformanceConfigService {
|
||||
|
||||
/**
|
||||
* 创建业绩目标
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createPerformanceConfig(@Valid CrmPerformanceConfigSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新业绩目标
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updatePerformanceConfig(@Valid CrmPerformanceConfigSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除业绩目标
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deletePerformanceConfig(Long id);
|
||||
|
||||
/**
|
||||
* 获得业绩目标
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 业绩目标
|
||||
*/
|
||||
CrmPerformanceConfigDO getPerformanceConfig(Long id);
|
||||
|
||||
/**
|
||||
* 获得业绩目标分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 业绩目标分页
|
||||
*/
|
||||
PageResult<CrmPerformanceConfigDO> getPerformanceConfigPage(CrmPerformanceConfigPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 根据对象、年份、目标类型获得业绩目标配置
|
||||
*
|
||||
* @param objectId 对象编号
|
||||
* @param objectType 对象类型
|
||||
* @param year 年份
|
||||
* @param bizType 业务类型
|
||||
* @return 业绩目标配置
|
||||
*/
|
||||
CrmPerformanceConfigDO getPerformanceConfig(Long objectId, Integer objectType, Integer year, Integer bizType);
|
||||
|
||||
/**
|
||||
* 根据对象数组、年份、目标类型获得业绩目标配置列表
|
||||
*
|
||||
* @param objectIds 对象编号数组
|
||||
* @param objectType 对象类型
|
||||
* @param year 年份
|
||||
* @param bizType 业务类型
|
||||
* @return 业绩目标配置列表
|
||||
*/
|
||||
List<CrmPerformanceConfigDO> getPerformanceConfigList(Collection<Long> objectIds, Integer objectType, Integer year,
|
||||
Integer bizType);
|
||||
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
package cn.iocoder.yudao.module.crm.service.performance;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigPageReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.performance.vo.config.CrmPerformanceConfigSaveReqVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.mysql.performance.CrmPerformanceConfigMapper;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.hutool.core.collection.CollUtil.isEmpty;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
|
||||
import static java.util.Collections.singleton;
|
||||
|
||||
/**
|
||||
* CRM 业绩目标设置 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class CrmPerformanceConfigServiceImpl implements CrmPerformanceConfigService {
|
||||
|
||||
@Resource
|
||||
private CrmPerformanceConfigMapper performanceConfigMapper;
|
||||
|
||||
@Resource
|
||||
private DeptApi deptApi;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@Override
|
||||
public Long createPerformanceConfig(CrmPerformanceConfigSaveReqVO createReqVO) {
|
||||
// 1.1 校验目标对象
|
||||
validatePerformanceConfigObject(createReqVO.getObjectType(), createReqVO.getObjectId());
|
||||
// 1.2 校验唯一
|
||||
validatePerformanceConfigNotExists(null, createReqVO);
|
||||
|
||||
// 2. 插入
|
||||
CrmPerformanceConfigDO performanceConfig = BeanUtils.toBean(createReqVO, CrmPerformanceConfigDO.class);
|
||||
fillTargetPrice(performanceConfig);
|
||||
performanceConfigMapper.insert(performanceConfig);
|
||||
return performanceConfig.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePerformanceConfig(CrmPerformanceConfigSaveReqVO updateReqVO) {
|
||||
// 1.1 校验存在
|
||||
validatePerformanceConfigExists(updateReqVO.getId());
|
||||
// 1.2 校验目标对象
|
||||
validatePerformanceConfigObject(updateReqVO.getObjectType(), updateReqVO.getObjectId());
|
||||
// 1.3 校验唯一
|
||||
validatePerformanceConfigNotExists(updateReqVO.getId(), updateReqVO);
|
||||
|
||||
// 2. 更新
|
||||
CrmPerformanceConfigDO updateObj = BeanUtils.toBean(updateReqVO, CrmPerformanceConfigDO.class);
|
||||
fillTargetPrice(updateObj);
|
||||
performanceConfigMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePerformanceConfig(Long id) {
|
||||
// 1. 校验存在
|
||||
validatePerformanceConfigExists(id);
|
||||
// 2. 删除
|
||||
performanceConfigMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CrmPerformanceConfigDO getPerformanceConfig(Long id) {
|
||||
return validatePerformanceConfigExists(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<CrmPerformanceConfigDO> getPerformanceConfigPage(CrmPerformanceConfigPageReqVO pageReqVO) {
|
||||
return performanceConfigMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
private CrmPerformanceConfigDO validatePerformanceConfigExists(Long id) {
|
||||
CrmPerformanceConfigDO performanceConfig = performanceConfigMapper.selectById(id);
|
||||
if (performanceConfig == null) {
|
||||
throw exception(PERFORMANCE_CONFIG_NOT_EXISTS);
|
||||
}
|
||||
return performanceConfig;
|
||||
}
|
||||
|
||||
private void validatePerformanceConfigNotExists(Long id, CrmPerformanceConfigSaveReqVO reqVO) {
|
||||
CrmPerformanceConfigDO performanceConfig = performanceConfigMapper.selectByObjectIdAndObjectTypeAndYearAndBizType(
|
||||
reqVO.getObjectId(), reqVO.getObjectType(), reqVO.getYear(), reqVO.getBizType());
|
||||
if (performanceConfig == null || Objects.equals(performanceConfig.getId(), id)) {
|
||||
return;
|
||||
}
|
||||
throw exception(PERFORMANCE_CONFIG_EXISTS);
|
||||
}
|
||||
|
||||
private void validatePerformanceConfigObject(Integer objectType, Long objectId) {
|
||||
// 情况一:部门
|
||||
if (Objects.equals(objectType, CrmPerformanceConfigObjectTypeEnum.DEPT.getObjectType())) {
|
||||
deptApi.validateDeptList(singleton(objectId));
|
||||
return;
|
||||
}
|
||||
// 情况二:员工
|
||||
if (Objects.equals(objectType, CrmPerformanceConfigObjectTypeEnum.USER.getObjectType())) {
|
||||
adminUserApi.validateUserList(singleton(objectId));
|
||||
return;
|
||||
}
|
||||
throw exception(PERFORMANCE_CONFIG_TYPE_INVALID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CrmPerformanceConfigDO getPerformanceConfig(Long objectId, Integer objectType, Integer year, Integer bizType) {
|
||||
return performanceConfigMapper.selectByObjectIdAndObjectTypeAndYearAndBizType(objectId, objectType, year, bizType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CrmPerformanceConfigDO> getPerformanceConfigList(Collection<Long> objectIds, Integer objectType,
|
||||
Integer year, Integer bizType) {
|
||||
if (isEmpty(objectIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return performanceConfigMapper.selectListByObjectIdsAndObjectTypeAndYearAndBizType(objectIds, objectType,
|
||||
year, bizType);
|
||||
}
|
||||
|
||||
private void fillTargetPrice(CrmPerformanceConfigDO performanceConfig) {
|
||||
// 1. 空值设置为 0
|
||||
performanceConfig.setJanuaryTargetPrice(defaultIfNull(performanceConfig.getJanuaryTargetPrice()));
|
||||
performanceConfig.setFebruaryTargetPrice(defaultIfNull(performanceConfig.getFebruaryTargetPrice()));
|
||||
performanceConfig.setMarchTargetPrice(defaultIfNull(performanceConfig.getMarchTargetPrice()));
|
||||
performanceConfig.setAprilTargetPrice(defaultIfNull(performanceConfig.getAprilTargetPrice()));
|
||||
performanceConfig.setMayTargetPrice(defaultIfNull(performanceConfig.getMayTargetPrice()));
|
||||
performanceConfig.setJuneTargetPrice(defaultIfNull(performanceConfig.getJuneTargetPrice()));
|
||||
performanceConfig.setJulyTargetPrice(defaultIfNull(performanceConfig.getJulyTargetPrice()));
|
||||
performanceConfig.setAugustTargetPrice(defaultIfNull(performanceConfig.getAugustTargetPrice()));
|
||||
performanceConfig.setSeptemberTargetPrice(defaultIfNull(performanceConfig.getSeptemberTargetPrice()));
|
||||
performanceConfig.setOctoberTargetPrice(defaultIfNull(performanceConfig.getOctoberTargetPrice()));
|
||||
performanceConfig.setNovemberTargetPrice(defaultIfNull(performanceConfig.getNovemberTargetPrice()));
|
||||
performanceConfig.setDecemberTargetPrice(defaultIfNull(performanceConfig.getDecemberTargetPrice()));
|
||||
// 2. 计算年度目标
|
||||
performanceConfig.setYearTargetPrice(performanceConfig.getJanuaryTargetPrice()
|
||||
.add(performanceConfig.getFebruaryTargetPrice())
|
||||
.add(performanceConfig.getMarchTargetPrice())
|
||||
.add(performanceConfig.getAprilTargetPrice())
|
||||
.add(performanceConfig.getMayTargetPrice())
|
||||
.add(performanceConfig.getJuneTargetPrice())
|
||||
.add(performanceConfig.getJulyTargetPrice())
|
||||
.add(performanceConfig.getAugustTargetPrice())
|
||||
.add(performanceConfig.getSeptemberTargetPrice())
|
||||
.add(performanceConfig.getOctoberTargetPrice())
|
||||
.add(performanceConfig.getNovemberTargetPrice())
|
||||
.add(performanceConfig.getDecemberTargetPrice()));
|
||||
}
|
||||
|
||||
private BigDecimal defaultIfNull(BigDecimal value) {
|
||||
return value == null ? BigDecimal.ZERO : value;
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package cn.iocoder.yudao.module.crm.service.permission;
|
||||
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO;
|
||||
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmOwnerRecordCreateReqBO;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CRM 负责人变更记录 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface CrmOwnerRecordService {
|
||||
|
||||
/**
|
||||
* 创建负责人变更记录
|
||||
*
|
||||
* @param createReqBO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createOwnerRecord(@Valid CrmOwnerRecordCreateReqBO createReqBO);
|
||||
|
||||
/**
|
||||
* 批量创建负责人变更记录
|
||||
*
|
||||
* @param createReqBOs 创建信息
|
||||
*/
|
||||
void createOwnerRecordList(@Valid List<CrmOwnerRecordCreateReqBO> createReqBOs);
|
||||
|
||||
/**
|
||||
* 进入公海客户数(按日期)
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 统计数据
|
||||
*/
|
||||
List<CrmStatisticsPoolSummaryByDateRespVO> getPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 公海领取客户数(按日期)
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 统计数据
|
||||
*/
|
||||
List<CrmStatisticsPoolSummaryByDateRespVO> getPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 进入公海客户数(按用户)
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 统计数据
|
||||
*/
|
||||
List<CrmStatisticsPoolSummaryByUserRespVO> getPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 公海领取客户数(按用户)
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 统计数据
|
||||
*/
|
||||
List<CrmStatisticsPoolSummaryByUserRespVO> getPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO);
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package cn.iocoder.yudao.module.crm.service.permission;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmOwnerRecordDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmOwnerRecordMapper;
|
||||
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmOwnerRecordCreateReqBO;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CRM 负责人变更记录 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class CrmOwnerRecordServiceImpl implements CrmOwnerRecordService {
|
||||
|
||||
@Resource
|
||||
private CrmOwnerRecordMapper ownerRecordMapper;
|
||||
|
||||
@Override
|
||||
public Long createOwnerRecord(CrmOwnerRecordCreateReqBO createReqBO) {
|
||||
CrmOwnerRecordDO ownerRecord = BeanUtils.toBean(createReqBO, CrmOwnerRecordDO.class);
|
||||
ownerRecordMapper.insert(ownerRecord);
|
||||
return ownerRecord.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createOwnerRecordList(List<CrmOwnerRecordCreateReqBO> createReqBOs) {
|
||||
if (CollUtil.isEmpty(createReqBOs)) {
|
||||
return;
|
||||
}
|
||||
List<CrmOwnerRecordDO> list = BeanUtils.toBean(createReqBOs, CrmOwnerRecordDO.class);
|
||||
ownerRecordMapper.insertBatch(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CrmStatisticsPoolSummaryByDateRespVO> getPoolCustomerPutCountByDate(CrmStatisticsCustomerReqVO reqVO) {
|
||||
return ownerRecordMapper.selectPoolCustomerPutCountByDate(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CrmStatisticsPoolSummaryByDateRespVO> getPoolCustomerTakeCountByDate(CrmStatisticsCustomerReqVO reqVO) {
|
||||
return ownerRecordMapper.selectPoolCustomerTakeCountByDate(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CrmStatisticsPoolSummaryByUserRespVO> getPoolCustomerPutCountByUser(CrmStatisticsCustomerReqVO reqVO) {
|
||||
return ownerRecordMapper.selectPoolCustomerPutCountByUser(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CrmStatisticsPoolSummaryByUserRespVO> getPoolCustomerTakeCountByUser(CrmStatisticsCustomerReqVO reqVO) {
|
||||
return ownerRecordMapper.selectPoolCustomerTakeCountByUser(reqVO);
|
||||
}
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package cn.iocoder.yudao.module.crm.service.permission.bo;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.validation.InEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* CRM 负责人变更记录 Create Req BO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class CrmOwnerRecordCreateReqBO {
|
||||
|
||||
/**
|
||||
* CRM 业务类型
|
||||
*/
|
||||
@NotNull(message = "CRM 业务类型不能为空")
|
||||
@InEnum(CrmBizTypeEnum.class)
|
||||
private Integer bizType;
|
||||
|
||||
/**
|
||||
* CRM 业务编号
|
||||
*/
|
||||
@NotNull(message = "CRM 业务编号不能为空")
|
||||
private Long bizId;
|
||||
|
||||
/**
|
||||
* 变更前负责人
|
||||
*/
|
||||
private Long preOwnerUserId;
|
||||
|
||||
/**
|
||||
* 变更后负责人
|
||||
*/
|
||||
private Long postOwnerUserId;
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package cn.iocoder.yudao.module.crm.service.statistics;
|
||||
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetRespVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CRM 业绩目标统计 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface CrmStatisticsPerformanceTargetService {
|
||||
|
||||
/**
|
||||
* 获取业绩目标完成情况
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 业绩目标完成情况
|
||||
*/
|
||||
List<CrmStatisticsPerformanceTargetRespVO> getPerformanceTargetSummary(CrmStatisticsPerformanceTargetReqVO reqVO);
|
||||
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package cn.iocoder.yudao.module.crm.service.statistics;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetActualRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetRespVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.performance.CrmPerformanceConfigDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPerformanceTargetMapper;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigBizTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.enums.performance.CrmPerformanceConfigObjectTypeEnum;
|
||||
import cn.iocoder.yudao.module.crm.service.performance.CrmPerformanceConfigService;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
|
||||
/**
|
||||
* CRM 业绩目标统计 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class CrmStatisticsPerformanceTargetServiceImpl implements CrmStatisticsPerformanceTargetService {
|
||||
|
||||
@Resource
|
||||
private CrmStatisticsPerformanceTargetMapper performanceTargetMapper;
|
||||
@Resource
|
||||
private CrmPerformanceConfigService performanceConfigService;
|
||||
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@Resource
|
||||
private DeptApi deptApi;
|
||||
|
||||
@Override
|
||||
public List<CrmStatisticsPerformanceTargetRespVO> getPerformanceTargetSummary(CrmStatisticsPerformanceTargetReqVO reqVO) {
|
||||
// 1. 获得用户编号数组
|
||||
reqVO.setUserIds(getUserIds(reqVO));
|
||||
if (CollUtil.isEmpty(reqVO.getUserIds())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 2.1 获得目标配置
|
||||
List<CrmPerformanceConfigDO> performanceConfigs = getPerformanceConfigs(reqVO);
|
||||
// 2.2 获得实际完成金额
|
||||
List<CrmStatisticsPerformanceTargetActualRespVO> actualList = ObjUtil.equal(reqVO.getBizType(),
|
||||
CrmPerformanceConfigBizTypeEnum.CRM_RECEIVABLE.getBizType())
|
||||
? performanceTargetMapper.selectReceivableActualByMonth(reqVO)
|
||||
: performanceTargetMapper.selectContractActualByMonth(reqVO);
|
||||
Map<Integer, BigDecimal> actualMap = convertMap(actualList,
|
||||
CrmStatisticsPerformanceTargetActualRespVO::getMonth,
|
||||
CrmStatisticsPerformanceTargetActualRespVO::getCurrentPrice);
|
||||
|
||||
// 3. 组装数据返回
|
||||
List<CrmStatisticsPerformanceTargetRespVO> result = new ArrayList<>();
|
||||
for (int month = 1; month <= 12; month++) {
|
||||
BigDecimal targetPrice = getMonthTargetPrice(performanceConfigs, month);
|
||||
BigDecimal currentPrice = actualMap.getOrDefault(month, BigDecimal.ZERO);
|
||||
result.add(new CrmStatisticsPerformanceTargetRespVO().setMonth(month).setTargetPrice(targetPrice)
|
||||
.setCurrentPrice(currentPrice)
|
||||
.setCompletionRate(calculateCompletionRate(currentPrice, targetPrice)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<CrmPerformanceConfigDO> getPerformanceConfigs(CrmStatisticsPerformanceTargetReqVO reqVO) {
|
||||
// 情况一:选中某个用户
|
||||
if (ObjUtil.isNotNull(reqVO.getUserId())) {
|
||||
CrmPerformanceConfigDO performanceConfig = performanceConfigService.getPerformanceConfig(reqVO.getUserId(),
|
||||
CrmPerformanceConfigObjectTypeEnum.USER.getObjectType(), reqVO.getYear(), reqVO.getBizType());
|
||||
return performanceConfig == null ? Collections.emptyList() : ListUtil.of(performanceConfig);
|
||||
}
|
||||
// 情况二:选中某个部门
|
||||
return performanceConfigService.getPerformanceConfigList(getDeptIds(reqVO.getDeptId()),
|
||||
CrmPerformanceConfigObjectTypeEnum.DEPT.getObjectType(), reqVO.getYear(), reqVO.getBizType());
|
||||
}
|
||||
|
||||
private BigDecimal getMonthTargetPrice(List<CrmPerformanceConfigDO> performanceConfigs, int month) {
|
||||
return performanceConfigs.stream()
|
||||
.map(performanceConfig -> getMonthTargetPrice(performanceConfig, month))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
|
||||
@SuppressWarnings("EnhancedSwitchMigration")
|
||||
private BigDecimal getMonthTargetPrice(CrmPerformanceConfigDO performanceConfig, int month) {
|
||||
// 1. 无目标配置,返回 0
|
||||
if (performanceConfig == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
// 2. 获得月份目标
|
||||
BigDecimal targetPrice;
|
||||
switch (month) {
|
||||
case 1:
|
||||
targetPrice = performanceConfig.getJanuaryTargetPrice();
|
||||
break;
|
||||
case 2:
|
||||
targetPrice = performanceConfig.getFebruaryTargetPrice();
|
||||
break;
|
||||
case 3:
|
||||
targetPrice = performanceConfig.getMarchTargetPrice();
|
||||
break;
|
||||
case 4:
|
||||
targetPrice = performanceConfig.getAprilTargetPrice();
|
||||
break;
|
||||
case 5:
|
||||
targetPrice = performanceConfig.getMayTargetPrice();
|
||||
break;
|
||||
case 6:
|
||||
targetPrice = performanceConfig.getJuneTargetPrice();
|
||||
break;
|
||||
case 7:
|
||||
targetPrice = performanceConfig.getJulyTargetPrice();
|
||||
break;
|
||||
case 8:
|
||||
targetPrice = performanceConfig.getAugustTargetPrice();
|
||||
break;
|
||||
case 9:
|
||||
targetPrice = performanceConfig.getSeptemberTargetPrice();
|
||||
break;
|
||||
case 10:
|
||||
targetPrice = performanceConfig.getOctoberTargetPrice();
|
||||
break;
|
||||
case 11:
|
||||
targetPrice = performanceConfig.getNovemberTargetPrice();
|
||||
break;
|
||||
case 12:
|
||||
targetPrice = performanceConfig.getDecemberTargetPrice();
|
||||
break;
|
||||
default:
|
||||
targetPrice = BigDecimal.ZERO;
|
||||
}
|
||||
return targetPrice != null ? targetPrice : BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
private BigDecimal calculateCompletionRate(BigDecimal currentPrice, BigDecimal targetPrice) {
|
||||
if (targetPrice == null || targetPrice.compareTo(BigDecimal.ZERO) == 0) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
return currentPrice.multiply(BigDecimal.valueOf(100)).divide(targetPrice, 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户编号数组
|
||||
*
|
||||
* 如果用户编号为空,则获得部门下的用户编号数组,包括子部门的所有用户编号
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 用户编号数组
|
||||
*/
|
||||
private List<Long> getUserIds(CrmStatisticsPerformanceTargetReqVO reqVO) {
|
||||
// 情况一:选中某个用户
|
||||
if (ObjUtil.isNotNull(reqVO.getUserId())) {
|
||||
return ListUtil.of(reqVO.getUserId());
|
||||
}
|
||||
// 情况二:选中某个部门
|
||||
// 2.1 获得部门列表
|
||||
List<Long> deptIds = getDeptIds(reqVO.getDeptId());
|
||||
// 2.2 获得用户编号
|
||||
return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得部门编号数组,包括子部门
|
||||
*
|
||||
* @param deptId 部门编号
|
||||
* @return 部门编号数组
|
||||
*/
|
||||
private List<Long> getDeptIds(Long deptId) {
|
||||
List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId);
|
||||
deptIds.add(deptId);
|
||||
return deptIds;
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.crm.service.statistics;
|
||||
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* CRM 产品分析 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface CrmStatisticsProductService {
|
||||
|
||||
/**
|
||||
* 获取产品销售情况统计
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 产品销售情况
|
||||
*/
|
||||
List<CrmStatisticsProductSalesRespVO> getProductSalesList(CrmStatisticsProductReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获取产品分类销售分析
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 产品分类销售分析
|
||||
*/
|
||||
List<CrmStatisticsProductCategoryRespVO> getProductCategorySummary(CrmStatisticsProductReqVO reqVO);
|
||||
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package cn.iocoder.yudao.module.crm.service.statistics;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductReqVO;
|
||||
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO;
|
||||
import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
|
||||
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsProductMapper;
|
||||
import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService;
|
||||
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
||||
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import javax.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
/**
|
||||
* CRM 产品分析 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class CrmStatisticsProductServiceImpl implements CrmStatisticsProductService {
|
||||
|
||||
@Resource
|
||||
private CrmStatisticsProductMapper productMapper;
|
||||
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@Resource
|
||||
private DeptApi deptApi;
|
||||
@Resource
|
||||
private CrmProductCategoryService productCategoryService;
|
||||
|
||||
@Override
|
||||
public List<CrmStatisticsProductSalesRespVO> getProductSalesList(CrmStatisticsProductReqVO reqVO) {
|
||||
reqVO.setUserIds(getUserIds(reqVO));
|
||||
if (CollUtil.isEmpty(reqVO.getUserIds())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
setCategoryIds(reqVO);
|
||||
return productMapper.selectProductSalesList(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CrmStatisticsProductCategoryRespVO> getProductCategorySummary(CrmStatisticsProductReqVO reqVO) {
|
||||
reqVO.setUserIds(getUserIds(reqVO));
|
||||
if (CollUtil.isEmpty(reqVO.getUserIds())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
setCategoryIds(reqVO);
|
||||
return productMapper.selectProductCategorySummary(reqVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置产品分类编号数组,选择父分类时,包含子分类
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
*/
|
||||
private void setCategoryIds(CrmStatisticsProductReqVO reqVO) {
|
||||
if (ObjUtil.isNull(reqVO.getCategoryId())) {
|
||||
return;
|
||||
}
|
||||
List<Long> categoryIds = ListUtil.of(reqVO.getCategoryId());
|
||||
List<CrmProductCategoryDO> categoryList = productCategoryService.getProductCategoryList(
|
||||
new CrmProductCategoryListReqVO());
|
||||
categoryIds.addAll(convertList(categoryList, CrmProductCategoryDO::getId,
|
||||
category -> Objects.equals(category.getParentId(), reqVO.getCategoryId())));
|
||||
reqVO.setCategoryIds(categoryIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户编号数组
|
||||
*
|
||||
* 如果用户编号为空,则获得部门下的用户编号数组,包括子部门的所有用户编号
|
||||
*
|
||||
* @param reqVO 请求参数
|
||||
* @return 用户编号数组
|
||||
*/
|
||||
private List<Long> getUserIds(CrmStatisticsProductReqVO reqVO) {
|
||||
// 情况一:选中某个用户
|
||||
if (ObjUtil.isNotNull(reqVO.getUserId())) {
|
||||
return ListUtil.of(reqVO.getUserId());
|
||||
}
|
||||
// 情况二:选中某个部门
|
||||
// 2.1 获得部门列表
|
||||
List<Long> deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()), DeptRespDTO::getId);
|
||||
deptIds.add(reqVO.getDeptId());
|
||||
// 2.2 获得用户编号
|
||||
return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmOwnerRecordMapper">
|
||||
|
||||
<select id="selectPoolCustomerPutCountByDate"
|
||||
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO">
|
||||
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
|
||||
COUNT(*) AS customer_put_count
|
||||
FROM crm_owner_record
|
||||
WHERE deleted = 0
|
||||
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
|
||||
AND pre_owner_user_id IN
|
||||
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
|
||||
#{userId}
|
||||
</foreach>
|
||||
AND post_owner_user_id IS NULL
|
||||
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
|
||||
GROUP BY time
|
||||
</select>
|
||||
|
||||
<select id="selectPoolCustomerTakeCountByDate"
|
||||
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByDateRespVO">
|
||||
SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
|
||||
COUNT(*) AS customer_take_count
|
||||
FROM crm_owner_record
|
||||
WHERE deleted = 0
|
||||
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
|
||||
AND pre_owner_user_id IS NULL
|
||||
AND post_owner_user_id IN
|
||||
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
|
||||
#{userId}
|
||||
</foreach>
|
||||
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
|
||||
GROUP BY time
|
||||
</select>
|
||||
|
||||
<select id="selectPoolCustomerPutCountByUser"
|
||||
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO">
|
||||
SELECT pre_owner_user_id AS owner_user_id,
|
||||
COUNT(*) AS customer_put_count
|
||||
FROM crm_owner_record
|
||||
WHERE deleted = 0
|
||||
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
|
||||
AND pre_owner_user_id IN
|
||||
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
|
||||
#{userId}
|
||||
</foreach>
|
||||
AND post_owner_user_id IS NULL
|
||||
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
|
||||
GROUP BY pre_owner_user_id
|
||||
</select>
|
||||
|
||||
<select id="selectPoolCustomerTakeCountByUser"
|
||||
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsPoolSummaryByUserRespVO">
|
||||
SELECT post_owner_user_id AS owner_user_id,
|
||||
COUNT(*) AS customer_take_count
|
||||
FROM crm_owner_record
|
||||
WHERE deleted = 0
|
||||
AND biz_type = ${@cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum@CRM_CUSTOMER.type}
|
||||
AND pre_owner_user_id IS NULL
|
||||
AND post_owner_user_id IN
|
||||
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
|
||||
#{userId}
|
||||
</foreach>
|
||||
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
|
||||
GROUP BY post_owner_user_id
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsPerformanceTargetMapper">
|
||||
|
||||
<resultMap id="CrmStatisticsPerformanceTargetActualResultMap"
|
||||
type="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceTargetActualRespVO">
|
||||
<result property="month" column="month"/>
|
||||
<result property="currentPrice" column="current_price"/>
|
||||
</resultMap>
|
||||
|
||||
<select id="selectContractActualByMonth"
|
||||
resultMap="CrmStatisticsPerformanceTargetActualResultMap">
|
||||
SELECT MONTH(order_date) AS month,
|
||||
IFNULL(SUM(total_price), 0) AS current_price
|
||||
FROM crm_contract
|
||||
WHERE deleted = 0
|
||||
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
|
||||
AND owner_user_id IN
|
||||
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
|
||||
#{userId}
|
||||
</foreach>
|
||||
AND YEAR(order_date) = #{year}
|
||||
GROUP BY MONTH(order_date)
|
||||
</select>
|
||||
|
||||
<select id="selectReceivableActualByMonth"
|
||||
resultMap="CrmStatisticsPerformanceTargetActualResultMap">
|
||||
SELECT MONTH(return_time) AS month,
|
||||
IFNULL(SUM(price), 0) AS current_price
|
||||
FROM crm_receivable
|
||||
WHERE deleted = 0
|
||||
AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
|
||||
AND owner_user_id IN
|
||||
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
|
||||
#{userId}
|
||||
</foreach>
|
||||
AND YEAR(return_time) = #{year}
|
||||
GROUP BY MONTH(return_time)
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsProductMapper">
|
||||
|
||||
<sql id="ProductStatisticsWhere">
|
||||
contract.deleted = 0
|
||||
AND contract_product.deleted = 0
|
||||
AND product.deleted = 0
|
||||
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
|
||||
AND contract.owner_user_id IN
|
||||
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
|
||||
#{userId}
|
||||
</foreach>
|
||||
AND contract.order_date BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
|
||||
<if test="categoryIds != null and categoryIds.size() > 0">
|
||||
AND product.category_id IN
|
||||
<foreach collection="categoryIds" item="categoryId" open="(" close=")" separator=",">
|
||||
#{categoryId}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="productId != null">
|
||||
AND product.id = #{productId}
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<select id="selectProductSalesList"
|
||||
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductSalesRespVO">
|
||||
SELECT category.id AS category_id,
|
||||
IFNULL(category.name, '未分类') AS category_name,
|
||||
product.id AS product_id,
|
||||
product.name AS product_name,
|
||||
contract.id AS contract_id,
|
||||
contract.no AS contract_no,
|
||||
contract.name AS contract_name,
|
||||
contract.owner_user_id,
|
||||
owner.nickname AS owner_user_name,
|
||||
customer.id AS customer_id,
|
||||
customer.name AS customer_name,
|
||||
contract_product.product_price,
|
||||
contract_product.count AS product_count,
|
||||
contract_product.total_price AS product_total_price
|
||||
FROM crm_contract_product AS contract_product
|
||||
INNER JOIN crm_contract AS contract ON contract_product.contract_id = contract.id
|
||||
INNER JOIN crm_product AS product ON contract_product.product_id = product.id
|
||||
LEFT JOIN crm_product_category AS category ON product.category_id = category.id AND category.deleted = 0
|
||||
LEFT JOIN crm_customer AS customer ON contract.customer_id = customer.id AND customer.deleted = 0
|
||||
LEFT JOIN system_users AS owner ON contract.owner_user_id = owner.id AND owner.deleted = 0
|
||||
WHERE <include refid="ProductStatisticsWhere"/>
|
||||
ORDER BY category.id ASC, product.id ASC, contract.order_date DESC
|
||||
</select>
|
||||
|
||||
<select id="selectProductCategorySummary"
|
||||
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.product.CrmStatisticsProductCategoryRespVO">
|
||||
SELECT category.id AS category_id,
|
||||
IFNULL(category.name, '未分类') AS category_name,
|
||||
COUNT(DISTINCT contract.id) AS contract_count,
|
||||
IFNULL(SUM(contract_product.count), 0) AS product_count,
|
||||
IFNULL(SUM(contract_product.total_price), 0) AS product_total_price
|
||||
FROM crm_contract_product AS contract_product
|
||||
INNER JOIN crm_contract AS contract ON contract_product.contract_id = contract.id
|
||||
INNER JOIN crm_product AS product ON contract_product.product_id = product.id
|
||||
LEFT JOIN crm_product_category AS category ON product.category_id = category.id AND category.deleted = 0
|
||||
WHERE <include refid="ProductStatisticsWhere"/>
|
||||
GROUP BY category.id, category.name
|
||||
ORDER BY product_count DESC, product_total_price DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
+4
@@ -48,6 +48,10 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_010, "主表(id={})定义不存在,请检查");
|
||||
ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_011, "子表的字段(id={})不存在,请检查");
|
||||
ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_001_004_012, "主表生成代码失败,原因:它没有子表");
|
||||
ErrorCode CODEGEN_MASTER_TABLE_NAME_DUPLICATE = new ErrorCode(1_001_004_013,
|
||||
"主子表规范化类名({})重复,请调整主表或子表类名");
|
||||
ErrorCode CODEGEN_MASTER_TABLE_FIELD_DUPLICATE = new ErrorCode(1_001_004_014,
|
||||
"主子表属性名({})重复,请调整主表字段、子表类名或关联关系");
|
||||
|
||||
// ========== 文件配置 1-001-006-000 ==========
|
||||
ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在");
|
||||
|
||||
+92
-10
@@ -48,6 +48,9 @@ import java.util.*;
|
||||
|
||||
import static cn.hutool.core.map.MapUtil.getStr;
|
||||
import static cn.hutool.core.text.CharSequenceUtil.*;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.CODEGEN_MASTER_TABLE_FIELD_DUPLICATE;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.CODEGEN_MASTER_TABLE_NAME_DUPLICATE;
|
||||
|
||||
/**
|
||||
* 代码生成的引擎,用于具体生成代码
|
||||
@@ -152,6 +155,16 @@ public class CodegenEngine {
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/form/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail/index.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/index_tree.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("components/breadcrumb_tree.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/components/breadcrumb.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail_master.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/detail_erp.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/detail/index.vue"))
|
||||
.put(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType(), vue3AdminUniappTemplatePath("views/form_sub_erp.vue"),
|
||||
vue3UniappFilePath("pages-${table.moduleName}/${table.businessName}/${subSimpleClassName_strikeCase}/form/index.vue"))
|
||||
// VUE3_VBEN2_ANTD_SCHEMA
|
||||
.put(CodegenFrontTypeEnum.VUE3_VBEN2_ANTD_SCHEMA.getType(), vue3VbenTemplatePath("views/data.ts"),
|
||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
|
||||
@@ -380,12 +393,17 @@ public class CodegenEngine {
|
||||
|
||||
// 2. 执行生成
|
||||
Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序
|
||||
Map<String, String> generatedSources = Maps.newHashMapWithExpectedSize(templates.size());
|
||||
templates.forEach((vmPath, filePath) -> {
|
||||
// 2.1 特殊:主子表专属逻辑
|
||||
if (isSubTemplate(vmPath)) {
|
||||
generateSubCode(table, subTables, result, vmPath, filePath, bindingMap);
|
||||
// 2.1 特殊:主子表、Uniapp 树表使用独立页面模版
|
||||
if (isVue3AdminUniappTemplate(vmPath) && !matchVue3AdminUniappTemplate(table, vmPath)) {
|
||||
return;
|
||||
// 2.2 特殊:树表专属逻辑
|
||||
}
|
||||
// 2.2 特殊:主子表专属逻辑
|
||||
if (isSubTemplate(vmPath)) {
|
||||
generateSubCode(table, subTables, result, generatedSources, vmPath, filePath, bindingMap);
|
||||
return;
|
||||
// 2.3 特殊:树表专属逻辑
|
||||
} else if (isPageReqVOTemplate(vmPath)) {
|
||||
// 减少多余的类生成,例如说 PageVO.java 类
|
||||
if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
|
||||
@@ -402,23 +420,36 @@ public class CodegenEngine {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 2.3 默认生成
|
||||
generateCode(result, vmPath, filePath, bindingMap);
|
||||
// 2.4 默认生成
|
||||
generateCode(result, generatedSources, vmPath, filePath, bindingMap);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private void generateCode(Map<String, String> result, String vmPath,
|
||||
private void generateCode(Map<String, String> result, Map<String, String> generatedSources, String vmPath,
|
||||
String filePath, Map<String, Object> bindingMap) {
|
||||
filePath = formatFilePath(filePath, bindingMap);
|
||||
registerGeneratedSource(generatedSources, filePath, vmPath, bindingMap);
|
||||
String content = templateEngine.getTemplate(vmPath).render(bindingMap);
|
||||
// 格式化代码
|
||||
content = prettyCode(content, vmPath);
|
||||
result.put(filePath, content);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static void registerGeneratedSource(Map<String, String> generatedSources, String filePath, String vmPath,
|
||||
Map<String, Object> bindingMap) {
|
||||
String source = vmPath
|
||||
+ (bindingMap.containsKey("subIndex") ? "[subIndex=" + bindingMap.get("subIndex") + "]" : "");
|
||||
String previousSource = generatedSources.putIfAbsent(filePath, source);
|
||||
if (previousSource != null) {
|
||||
throw new IllegalStateException(String.format("生成文件路径重复:%s,来源模板:%s、%s",
|
||||
filePath, previousSource, source));
|
||||
}
|
||||
}
|
||||
|
||||
private void generateSubCode(CodegenTableDO table, List<CodegenTableDO> subTables,
|
||||
Map<String, String> result, String vmPath,
|
||||
Map<String, String> result, Map<String, String> generatedSources, String vmPath,
|
||||
String filePath, Map<String, Object> bindingMap) {
|
||||
// 没有子表,所以不生成
|
||||
if (CollUtil.isEmpty(subTables)) {
|
||||
@@ -441,7 +472,7 @@ public class CodegenEngine {
|
||||
// 逐个生成
|
||||
for (int i = 0; i < subTables.size(); i++) {
|
||||
bindingMap.put("subIndex", i);
|
||||
generateCode(result, vmPath, filePath, bindingMap);
|
||||
generateCode(result, generatedSources, vmPath, filePath, bindingMap);
|
||||
}
|
||||
bindingMap.remove("subIndex");
|
||||
}
|
||||
@@ -482,6 +513,13 @@ public class CodegenEngine {
|
||||
if (StrUtil.count(content, "DICT_TYPE.") == 0) {
|
||||
content = StrUtils.removeLineContains(content, "DICT_TYPE");
|
||||
}
|
||||
// Vue3 Admin UniApp:清理空白行,并补充文件末尾换行,满足 ESLint 格式要求
|
||||
if (vmPath.contains("vue3_admin_uniapp")) {
|
||||
content = content.replaceAll("(?m)^[\\t ]+$", "");
|
||||
if (!content.endsWith("\n")) {
|
||||
content += "\n";
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -533,6 +571,14 @@ public class CodegenEngine {
|
||||
List<String> subClassNameVars = new ArrayList<>();
|
||||
List<String> simpleClassNameUnderlineCases = new ArrayList<>();
|
||||
List<String> subSimpleClassNameStrikeCases = new ArrayList<>();
|
||||
Set<String> masterSymbols = new HashSet<>();
|
||||
masterSymbols.add(simpleClassName);
|
||||
boolean mergeSubTableFields = ObjectUtils.equalsAny(table.getTemplateType(),
|
||||
CodegenTemplateTypeEnum.MASTER_NORMAL.getType(), CodegenTemplateTypeEnum.MASTER_INNER.getType());
|
||||
Set<String> masterFieldSymbols = new HashSet<>();
|
||||
if (mergeSubTableFields) {
|
||||
columns.forEach(column -> masterFieldSymbols.add(column.getJavaField()));
|
||||
}
|
||||
for (int i = 0; i < subTables.size(); i++) {
|
||||
CodegenTableDO subTable = subTables.get(i);
|
||||
List<CodegenColumnDO> subColumns = subColumnsList.get(i);
|
||||
@@ -543,9 +589,18 @@ public class CodegenEngine {
|
||||
subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type
|
||||
// className 相关
|
||||
String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));
|
||||
if (!masterSymbols.add(subSimpleClassName)) {
|
||||
throw exception(CODEGEN_MASTER_TABLE_NAME_DUPLICATE, subSimpleClassName);
|
||||
}
|
||||
String subClassNameVar = lowerFirst(subSimpleClassName);
|
||||
String subFieldName = subClassNameVar
|
||||
+ (Boolean.TRUE.equals(subTable.getSubJoinMany()) ? "s" : "");
|
||||
if (mergeSubTableFields && !masterFieldSymbols.add(subFieldName)) {
|
||||
throw exception(CODEGEN_MASTER_TABLE_FIELD_DUPLICATE, subFieldName);
|
||||
}
|
||||
subSimpleClassNames.add(subSimpleClassName);
|
||||
simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type
|
||||
subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量
|
||||
subClassNameVars.add(subClassNameVar); // 将 DictType 转换成 dictType,用于变量
|
||||
subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type
|
||||
}
|
||||
bindingMap.put("subPrimaryColumns", subPrimaryColumns);
|
||||
@@ -734,6 +789,33 @@ public class CodegenEngine {
|
||||
return path.contains("_sub");
|
||||
}
|
||||
|
||||
private static boolean isVue3AdminUniappTemplate(String path) {
|
||||
return path.contains("codegen/vue3_admin_uniapp/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配 Uniapp 不同表类型的专用页面模版
|
||||
*/
|
||||
private static boolean matchVue3AdminUniappTemplate(CodegenTableDO table, String path) {
|
||||
boolean tree = CodegenTemplateTypeEnum.isTree(table.getTemplateType());
|
||||
boolean masterSubmit = ObjectUtil.equals(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_NORMAL.getType())
|
||||
|| ObjectUtil.equals(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_INNER.getType());
|
||||
boolean masterErp = ObjectUtil.equals(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_ERP.getType());
|
||||
if (path.contains("_tree")) {
|
||||
return tree;
|
||||
}
|
||||
if (path.contains("detail_master")) {
|
||||
return masterSubmit;
|
||||
}
|
||||
if (path.contains("_erp")) {
|
||||
return masterErp;
|
||||
}
|
||||
if (tree && path.endsWith("views/index.vue.vm")) {
|
||||
return false;
|
||||
}
|
||||
return !(masterSubmit || masterErp) || !path.endsWith("views/detail/index.vue.vm");
|
||||
}
|
||||
|
||||
private static boolean isPageReqVOTemplate(String path) {
|
||||
return path.contains("pageReqVO");
|
||||
}
|
||||
|
||||
@@ -1,36 +1,80 @@
|
||||
#if ($table.templateType != 2)
|
||||
import type { PageParam, PageResult } from '@/http/types'
|
||||
#end
|
||||
import { http } from '@/http/http'
|
||||
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
/** ${table.classComment}信息 */
|
||||
export interface ${simpleClassName} {
|
||||
#foreach ($column in $columns)
|
||||
#if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime")
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
/** ${subTable.classComment}信息 */
|
||||
export interface ${subSimpleClassName} {
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey || ($table.templateType != 11 && $column.id == $subJoinColumn.id))
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
${column.javaField}#if($optional)?#end: number
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
${column.javaField}#if($optional)?#end: Date
|
||||
#elseif(${javaType} == "boolean")
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
${column.javaField}#if($optional)?#end: number
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}#if($optional)?#end: boolean
|
||||
#else
|
||||
#else
|
||||
${column.javaField}#if($optional)?#end: string
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
#end
|
||||
/** ${table.classComment}信息 */
|
||||
export interface ${simpleClassName} {
|
||||
#foreach ($column in $columns)
|
||||
#if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey)
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
${column.javaField}#if($optional)?#end: number
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
${column.javaField}#if($optional)?#end: number
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}#if($optional)?#end: boolean
|
||||
#else
|
||||
${column.javaField}#if($optional)?#end: string
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#if ($table.templateType == 2)
|
||||
children?: ${simpleClassName}[]
|
||||
#end
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
${subClassNameVar}#if($subTable.subJoinMany)s#end?: ${subSimpleClassName}#if($subTable.subJoinMany)[]#else | null#end
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
#if ($table.templateType == 2)
|
||||
/** 获取${table.classComment}列表 */
|
||||
export function get${simpleClassName}List(params?: Record<string, any>) {
|
||||
return http.get<${simpleClassName}[]>('/${table.moduleName}/${simpleClassName_strikeCase}/list', params)
|
||||
}
|
||||
#else
|
||||
/** 获取${table.classComment}分页列表 */
|
||||
export function get${simpleClassName}Page(params: PageParam) {
|
||||
return http.get<PageResult<${simpleClassName}>>('/${table.moduleName}/${simpleClassName_strikeCase}/page', params)
|
||||
}
|
||||
#end
|
||||
|
||||
/** 获取${table.classComment}详情 */
|
||||
export function get${simpleClassName}(id: ${primaryTsType}) {
|
||||
@@ -39,7 +83,7 @@ export function get${simpleClassName}(id: ${primaryTsType}) {
|
||||
|
||||
/** 创建${table.classComment} */
|
||||
export function create${simpleClassName}(data: ${simpleClassName}) {
|
||||
return http.post<number>('/${table.moduleName}/${simpleClassName_strikeCase}/create', data)
|
||||
return http.post<${primaryTsType}>('/${table.moduleName}/${simpleClassName_strikeCase}/create', data)
|
||||
}
|
||||
|
||||
/** 更新${table.classComment} */
|
||||
@@ -51,3 +95,65 @@ export function update${simpleClassName}(data: ${simpleClassName}) {
|
||||
export function delete${simpleClassName}(id: ${primaryTsType}) {
|
||||
return http.delete<boolean>(`/${table.moduleName}/${simpleClassName_strikeCase}/delete?id=${id}`)
|
||||
}
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($subPrimaryJavaType = $subPrimaryColumn.javaType.toLowerCase())
|
||||
#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "float" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte")
|
||||
#set ($subPrimaryTsType = "number")
|
||||
#else
|
||||
#set ($subPrimaryTsType = "string")
|
||||
#end
|
||||
#set ($subJoinJavaType = $subJoinColumn.javaType.toLowerCase())
|
||||
#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "float" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte")
|
||||
#set ($subJoinTsType = "number")
|
||||
#else
|
||||
#set ($subJoinTsType = "string")
|
||||
#end
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
|
||||
#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
|
||||
|
||||
// ==================== 子表:${subTable.classComment} ====================
|
||||
#if ($table.templateType == 11)
|
||||
|
||||
/** 获取${subTable.classComment}分页列表 */
|
||||
export function get${subSimpleClassName}Page(params: PageParam & { ${subJoinColumn.javaField}: ${subJoinTsType} }) {
|
||||
return http.get<PageResult<${subSimpleClassName}>>('/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/page', params)
|
||||
}
|
||||
|
||||
/** 获取${subTable.classComment}详情 */
|
||||
export function get${subSimpleClassName}(id: ${subPrimaryTsType}) {
|
||||
return http.get<${subSimpleClassName}>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/get?id=${id}`)
|
||||
}
|
||||
|
||||
/** 创建${subTable.classComment} */
|
||||
export function create${subSimpleClassName}(data: ${subSimpleClassName}) {
|
||||
return http.post<${subPrimaryTsType}>('/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/create', data)
|
||||
}
|
||||
|
||||
/** 更新${subTable.classComment} */
|
||||
export function update${subSimpleClassName}(data: ${subSimpleClassName}) {
|
||||
return http.put<boolean>('/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/update', data)
|
||||
}
|
||||
|
||||
/** 删除${subTable.classComment} */
|
||||
export function delete${subSimpleClassName}(id: ${subPrimaryTsType}) {
|
||||
return http.delete<boolean>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/delete?id=${id}`)
|
||||
}
|
||||
#elseif ($subTable.subJoinMany)
|
||||
|
||||
/** 获取${subTable.classComment}列表 */
|
||||
export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: ${subJoinTsType}) {
|
||||
return http.get<${subSimpleClassName}[]>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=#[[${]]#${subJoinColumn.javaField}#[[}]]#`)
|
||||
}
|
||||
#else
|
||||
|
||||
/** 获取${subTable.classComment} */
|
||||
export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: ${subJoinTsType}) {
|
||||
return http.get<${subSimpleClassName} | null>(`/${table.moduleName}/${simpleClassName_strikeCase}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=#[[${]]#${subJoinColumn.javaField}#[[}]]#`)
|
||||
}
|
||||
#end
|
||||
#end
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<view class="bg-white px-24rpx py-16rpx">
|
||||
<scroll-view scroll-x class="whitespace-nowrap">
|
||||
<view class="inline-flex items-center">
|
||||
<view
|
||||
class="flex items-center text-28rpx"
|
||||
:class="items.length > 0 ? 'text-[#1890ff]' : 'text-[#333]'"
|
||||
@click="handleClick(-1)"
|
||||
>
|
||||
<text>全部${table.classComment}</text>
|
||||
</view>
|
||||
<template v-for="(item, index) in items" :key="item.id">
|
||||
<wd-icon name="arrow-right" size="12px" color="#999" custom-class="mx-8rpx" />
|
||||
<view
|
||||
class="flex items-center text-28rpx"
|
||||
:class="index < items.length - 1 ? 'text-[#1890ff]' : 'text-[#333]'"
|
||||
@click="handleClick(index)"
|
||||
>
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
interface BreadcrumbItem {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number]
|
||||
}>()
|
||||
|
||||
const items = ref<BreadcrumbItem[]>([]) // 面包屑数据
|
||||
|
||||
/** 监听外部值变化 */
|
||||
watch(() => props.modelValue, (value) => {
|
||||
if (value === 0) {
|
||||
items.value = []
|
||||
}
|
||||
})
|
||||
|
||||
/** 点击面包屑 */
|
||||
function handleClick(index: number) {
|
||||
if (index === -1) {
|
||||
reset()
|
||||
return
|
||||
}
|
||||
if (index < items.value.length - 1) {
|
||||
items.value = items.value.slice(0, index + 1)
|
||||
emit('update:modelValue', items.value[index].id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入子层级 */
|
||||
function enter(item: BreadcrumbItem) {
|
||||
items.value.push(item)
|
||||
emit('update:modelValue', item.id)
|
||||
}
|
||||
|
||||
/** 返回上一层级 */
|
||||
function back(): boolean {
|
||||
if (items.value.length === 0) {
|
||||
return false
|
||||
}
|
||||
items.value.pop()
|
||||
emit('update:modelValue', items.value.at(-1)?.id ?? 0)
|
||||
return true
|
||||
}
|
||||
|
||||
/** 重置面包屑 */
|
||||
function reset() {
|
||||
items.value = []
|
||||
emit('update:modelValue', 0)
|
||||
}
|
||||
|
||||
defineExpose({ back, enter, reset })
|
||||
</script>
|
||||
+14
-27
@@ -42,23 +42,12 @@
|
||||
#elseif ($column.htmlType == "datetime" && $listOperationCondition == "BETWEEN")
|
||||
<yd-search-date-range v-model="formData.${javaField}" label="${comment}" />
|
||||
#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $dictType && "" != $dictType)
|
||||
<view class="yd-search-form-item">
|
||||
<view class="yd-search-form-label">
|
||||
${comment}
|
||||
</view>
|
||||
<wd-radio-group v-model="formData.${javaField}" type="button">
|
||||
<wd-radio :value="-1">
|
||||
全部
|
||||
</wd-radio>
|
||||
<wd-radio
|
||||
v-for="dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</view>
|
||||
<yd-search-picker
|
||||
v-model="formData.${javaField}"
|
||||
label="${comment}"
|
||||
:columns="$dictMethod(DICT_TYPE.$dictType.toUpperCase())"
|
||||
all-option
|
||||
/>
|
||||
#else
|
||||
<view class="yd-search-form-item">
|
||||
<view class="yd-search-form-label">
|
||||
@@ -150,13 +139,13 @@ const formData = reactive({
|
||||
${column.javaField}: [undefined, undefined] as [number | undefined, number | undefined],
|
||||
#elseif ($column.dictType && "" != $column.dictType)
|
||||
#if(${javaType} == "string")
|
||||
${column.javaField}: -1 as -1 | string, // -1 表示全部
|
||||
${column.javaField}: undefined as string | undefined,
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}: -1 as -1 | boolean, // -1 表示全部
|
||||
${column.javaField}: undefined as boolean | undefined,
|
||||
#else
|
||||
${column.javaField}: -1, // -1 表示全部
|
||||
${column.javaField}: undefined as number | undefined,
|
||||
#end
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
${column.javaField}: undefined as number | undefined,
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}: undefined as boolean | undefined,
|
||||
@@ -181,11 +170,11 @@ const placeholder = computed(() => {
|
||||
conditions.push(`${comment}:#[[${]]#formatDate(formData.${javaField}[0])#[[}]]#~#[[${]]#formatDate(formData.${javaField}[1])#[[}]]#`)
|
||||
}
|
||||
#elseif ($dictType && "" != $dictType)
|
||||
if (formData.${javaField} !== -1) {
|
||||
if (formData.${javaField} !== undefined) {
|
||||
conditions.push(`${comment}:#[[${]]#getDictLabel(DICT_TYPE.${dictType.toUpperCase()}, formData.${javaField})#[[}]]#`)
|
||||
}
|
||||
#else
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte" || ${javaType} == "boolean")
|
||||
#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte" || ${javaType} == "boolean")
|
||||
if (formData.${javaField} !== undefined) {
|
||||
conditions.push(`${comment}:#[[${]]#formData.${javaField}#[[}]]#`)
|
||||
}
|
||||
@@ -207,9 +196,7 @@ function handleSearch() {
|
||||
...formData,
|
||||
#foreach($column in $columns)
|
||||
#if ($column.listOperation)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
${column.javaField}: formData.${column.javaField} === -1 ? undefined : formData.${column.javaField},
|
||||
#elseif ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
#if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
${column.javaField}: formatDateRange(formData.${column.javaField}),
|
||||
#end
|
||||
#end
|
||||
@@ -224,7 +211,7 @@ function handleReset() {
|
||||
#if ($column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
formData.${column.javaField} = [undefined, undefined]
|
||||
#elseif ($column.dictType && "" != $column.dictType)
|
||||
formData.${column.javaField} = -1
|
||||
formData.${column.javaField} = undefined
|
||||
#else
|
||||
formData.${column.javaField} = undefined
|
||||
#end
|
||||
|
||||
+28
-21
@@ -39,7 +39,7 @@
|
||||
</wd-button>
|
||||
<wd-button
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:delete'])"
|
||||
class="flex-1" type="error" :loading="deleting" @click="handleDelete"
|
||||
class="flex-1" type="danger" :loading="deleting" @click="handleDelete"
|
||||
>
|
||||
删除
|
||||
</wd-button>
|
||||
@@ -49,6 +49,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
#set ($hasDict = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($hasDict == 0 && $column.dictType && "" != $column.dictType)
|
||||
@@ -64,6 +70,7 @@
|
||||
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { onUnload } from '@dcloudio/uni-app'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useDialog } from '@wot-ui/ui/components/wd-dialog'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import { delete${simpleClassName}, get${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
@@ -76,7 +83,7 @@ import { formatDateTime } from '@/utils/date'
|
||||
#end
|
||||
|
||||
const props = defineProps<{
|
||||
id?: number | any
|
||||
id?: ${primaryTsType}
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
@@ -87,6 +94,7 @@ definePage({
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const formData = ref<${simpleClassName}>() // 详情数据
|
||||
const deleting = ref(false) // 删除状态
|
||||
@@ -117,28 +125,27 @@ function handleEdit() {
|
||||
}
|
||||
|
||||
/** 删除${table.classComment} */
|
||||
function handleDelete() {
|
||||
async function handleDelete() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该${table.classComment}吗?',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await delete${simpleClassName}(props.id)
|
||||
toast.success('删除成功')
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
},
|
||||
})
|
||||
try {
|
||||
await dialog.confirm({
|
||||
title: '提示',
|
||||
msg: '确定要删除该${table.classComment}吗?',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await delete${simpleClassName}(props.id)
|
||||
toast.success('删除成功')
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar title="${table.classComment}详情" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" />
|
||||
|
||||
<view class="pb-160rpx">
|
||||
<!-- 主表详情 -->
|
||||
<wd-cell-group border title="${table.classComment}信息">
|
||||
#foreach($column in $columns)
|
||||
#if ($column.primaryKey || $column.listOperationResult || $column.createOperation || $column.updateOperation)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
<wd-cell title="${column.columnComment}">
|
||||
<dict-tag :type="DICT_TYPE.${column.dictType.toUpperCase()}" :value="formData?.${column.javaField}" />
|
||||
</wd-cell>
|
||||
#elseif ($column.javaType == "LocalDateTime")
|
||||
<wd-cell title="${column.columnComment}" :value="formatDateTime(formData?.${column.javaField}) || '-'" />
|
||||
#else
|
||||
<wd-cell title="${column.columnComment}" :value="formData?.${column.javaField} ?? '-'" />
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($index))
|
||||
#set ($pageSize = 10)
|
||||
#if (!$subTable.subJoinMany)
|
||||
#set ($pageSize = 1)
|
||||
#end
|
||||
<!-- 子表:${subTable.classComment} -->
|
||||
<view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx">
|
||||
<text class="text-28rpx text-[#333] font-semibold">${subTable.classComment}({{ ${subClassNameVar}Total }})</text>
|
||||
<wd-button
|
||||
#if (!$subTable.subJoinMany)
|
||||
v-if="${subClassNameVar}Total === 0 && hasAccessByCodes(['${permissionPrefix}:create'])"
|
||||
#else
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:create'])"
|
||||
#end
|
||||
size="small" type="primary" variant="plain" @click="open${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}()"
|
||||
>
|
||||
添加
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="px-24rpx">
|
||||
<z-paging
|
||||
ref="${subClassNameVar}PagingRef"
|
||||
v-model="${subClassNameVar}List"
|
||||
:fixed="false"
|
||||
:auto="false"
|
||||
height="560rpx"
|
||||
:default-page-size="${pageSize}"
|
||||
:refresher-enabled="false"
|
||||
:inside-more="true"
|
||||
:to-bottom-loading-more-enabled="false"
|
||||
loading-more-default-text="点击加载更多"
|
||||
empty-view-text="暂无${subTable.classComment}"
|
||||
@query="query${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}List"
|
||||
>
|
||||
<view
|
||||
v-for="item in ${subClassNameVar}List"
|
||||
:key="item.${subPrimaryColumn.javaField}"
|
||||
class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm"
|
||||
>
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.listOperationResult && !$column.primaryKey)
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
${column.columnComment}:{{ item.${column.javaField} ?? '-' }}
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
<view class="mt-16rpx flex justify-end gap-16rpx">
|
||||
<wd-button size="small" variant="plain" @click="open${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item)">
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button size="small" type="danger" variant="plain" @click="remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item)">
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
#end
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<view class="yd-detail-footer-actions">
|
||||
<wd-button v-if="hasAccessByCodes(['${permissionPrefix}:update'])" class="flex-1" type="warning" @click="handleEdit">
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button v-if="hasAccessByCodes(['${permissionPrefix}:delete'])" class="flex-1" type="danger" :loading="deleting" @click="handleDelete">
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasDateTime = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#end
|
||||
#if ($column.javaType == "LocalDateTime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
import type {
|
||||
${simpleClassName},
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
${subSimpleClassName},
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { useDialog } from '@wot-ui/ui/components/wd-dialog'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import {
|
||||
delete${simpleClassName},
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
delete${subSimpleClassName},
|
||||
#end
|
||||
get${simpleClassName},
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
get${subSimpleClassName}Page,
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
#if ($hasDict == 1)
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
#end
|
||||
|
||||
const props = defineProps<{ id?: ${primaryTsType} }>()
|
||||
|
||||
definePage({
|
||||
style: { navigationBarTitleText: '', navigationStyle: 'custom' },
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const formData = ref<${simpleClassName}>() // 主表数据
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
const ${subClassNameVar}List = ref<${subSimpleClassName}[]>([]) // ${subTable.classComment}列表
|
||||
const ${subClassNameVar}Total = ref(0) // ${subTable.classComment}总数
|
||||
const ${subClassNameVar}PagingRef = ref<any>() // ${subTable.classComment}分页组件引用
|
||||
#end
|
||||
const deleting = ref(false) // 主表删除状态
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index')
|
||||
}
|
||||
|
||||
/** 加载主表 */
|
||||
async function loadMain() {
|
||||
if (props.id) {
|
||||
formData.value = await get${simpleClassName}(props.id)
|
||||
}
|
||||
}
|
||||
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($index))
|
||||
/** 查询${subTable.classComment}列表 */
|
||||
async function query${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}List(pageNo: number, pageSize: number) {
|
||||
if (!props.id) {
|
||||
${subClassNameVar}PagingRef.value?.completeByTotal([], 0)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = await get${subSimpleClassName}Page({ ${subJoinColumn.javaField}: props.id, pageNo, pageSize })
|
||||
${subClassNameVar}Total.value = data.total
|
||||
${subClassNameVar}PagingRef.value?.completeByTotal(data.list, data.total)
|
||||
} catch {
|
||||
${subClassNameVar}PagingRef.value?.complete(false)
|
||||
}
|
||||
}
|
||||
|
||||
/** 打开${subTable.classComment}表单 */
|
||||
function open${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item?: ${subSimpleClassName}) {
|
||||
const query = [
|
||||
`${subJoinColumn.javaField}=#[[${]]#props.id#[[}]]#`,
|
||||
item?.${subPrimaryColumn.javaField} ? `id=#[[${]]#item.${subPrimaryColumn.javaField}#[[}]]#` : '',
|
||||
].filter(Boolean).join('&')
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/${subSimpleClassName_strikeCases.get($index)}/form/index?#[[${]]#query#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 删除${subTable.classComment} */
|
||||
async function remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(item: ${subSimpleClassName}) {
|
||||
try {
|
||||
await dialog.confirm({ title: '提示', msg: '确定要删除该${subTable.classComment}吗?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await delete${subSimpleClassName}(item.${subPrimaryColumn.javaField}!)
|
||||
toast.success('删除成功')
|
||||
${subClassNameVar}PagingRef.value?.reload()
|
||||
}
|
||||
|
||||
#end
|
||||
/** 编辑主表 */
|
||||
function handleEdit() {
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/form/index?id=#[[${]]#props.id#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 删除主表 */
|
||||
async function handleDelete() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await dialog.confirm({ title: '提示', msg: '确定要删除该${table.classComment}吗?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await delete${simpleClassName}(props.id)
|
||||
toast.success('删除成功')
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入页面加载(含编辑和子表返回后刷新) */
|
||||
onShow(() => {
|
||||
loadMain()
|
||||
nextTick(() => {
|
||||
#foreach ($subClassNameVar in $subClassNameVars)
|
||||
${subClassNameVar}PagingRef.value?.reload()
|
||||
#end
|
||||
})
|
||||
})
|
||||
</script>
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
title="${table.classComment}详情"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
/>
|
||||
|
||||
<view class="pb-160rpx">
|
||||
<!-- 主表详情 -->
|
||||
<wd-cell-group border title="${table.classComment}信息">
|
||||
#foreach($column in $columns)
|
||||
#if ($column.primaryKey || $column.listOperationResult || $column.createOperation || $column.updateOperation)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
<wd-cell title="${column.columnComment}">
|
||||
<dict-tag :type="DICT_TYPE.${column.dictType.toUpperCase()}" :value="formData?.${column.javaField}" />
|
||||
</wd-cell>
|
||||
#elseif ($column.javaType == "LocalDateTime")
|
||||
<wd-cell title="${column.columnComment}" :value="formatDateTime(formData?.${column.javaField}) || '-'" />
|
||||
#else
|
||||
<wd-cell title="${column.columnComment}" :value="formData?.${column.javaField} ?? '-'" />
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($index))
|
||||
<!-- 子表:${subTable.classComment} -->
|
||||
<view class="mt-20rpx px-24rpx py-16rpx text-28rpx text-[#999]">
|
||||
${subTable.classComment}#if($subTable.subJoinMany)({{ ${subClassNameVar}s.length }})#end
|
||||
</view>
|
||||
<view class="px-24rpx">
|
||||
#if ($subTable.subJoinMany)
|
||||
<view v-if="!${subClassNameVar}s.length" class="rounded-12rpx bg-white py-40rpx text-center text-26rpx text-[#999]">
|
||||
暂无${subTable.classComment}
|
||||
</view>
|
||||
<view
|
||||
v-for="item in ${subClassNameVar}s"
|
||||
:key="item.${subPrimaryColumn.javaField}"
|
||||
class="mb-16rpx rounded-12rpx bg-white p-24rpx shadow-sm"
|
||||
>
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.listOperationResult && !$column.primaryKey)
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
${column.columnComment}:{{ item.${column.javaField} ?? '-' }}
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
</view>
|
||||
#else
|
||||
<view class="rounded-12rpx bg-white p-24rpx shadow-sm">
|
||||
<template v-if="${subClassNameVar}">
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.listOperationResult && !$column.primaryKey)
|
||||
<view class="mb-8rpx text-28rpx text-[#666]">
|
||||
${column.columnComment}:{{ ${subClassNameVar}.${column.javaField} ?? '-' }}
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
</template>
|
||||
<view v-else class="text-center text-26rpx text-[#999]">
|
||||
暂无${subTable.classComment}
|
||||
</view>
|
||||
</view>
|
||||
#end
|
||||
</view>
|
||||
#end
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<view class="yd-detail-footer-actions">
|
||||
<wd-button
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:update'])"
|
||||
class="flex-1" type="warning" @click="handleEdit"
|
||||
>
|
||||
编辑
|
||||
</wd-button>
|
||||
<wd-button
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:delete'])"
|
||||
class="flex-1" type="danger" :loading="deleting" @click="handleDelete"
|
||||
>
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasDateTime = 0)
|
||||
#foreach($column in $columns)
|
||||
#if ($column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#end
|
||||
#if ($column.javaType == "LocalDateTime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
import type {
|
||||
${simpleClassName},
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
${subSimpleClassName},
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
import { useDialog } from '@wot-ui/ui/components/wd-dialog'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import {
|
||||
delete${simpleClassName},
|
||||
get${simpleClassName},
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#if ($subTable.subJoinMany)
|
||||
get${subSimpleClassName}ListBy${SubJoinColumnName},
|
||||
#else
|
||||
get${subSimpleClassName}By${SubJoinColumnName},
|
||||
#end
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
#if ($hasDict == 1)
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
import { formatDateTime } from '@/utils/date'
|
||||
#end
|
||||
|
||||
const props = defineProps<{
|
||||
id?: ${primaryTsType}
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const dialog = useDialog()
|
||||
const toast = useToast()
|
||||
const formData = ref<${simpleClassName}>() // 主表数据
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
const ${subClassNameVar}s = ref<${subSimpleClassName}[]>([]) // ${subTable.classComment}数据
|
||||
#else
|
||||
const ${subClassNameVar} = ref<${subSimpleClassName} | null>(null) // ${subTable.classComment}数据
|
||||
#end
|
||||
#end
|
||||
const deleting = ref(false) // 删除状态
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index')
|
||||
}
|
||||
|
||||
/** 加载主表和子表详情 */
|
||||
async function getDetail() {
|
||||
if (!props.id || deleting.value) {
|
||||
return
|
||||
}
|
||||
const [data#foreach ($subTable in $subTables)#set ($index = $foreach.count - 1), ${subClassNameVars.get($index)}Data#end] = await Promise.all([
|
||||
get${simpleClassName}(props.id),
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#if ($subTable.subJoinMany)
|
||||
get${subSimpleClassName}ListBy${SubJoinColumnName}(props.id),
|
||||
#else
|
||||
get${subSimpleClassName}By${SubJoinColumnName}(props.id),
|
||||
#end
|
||||
#end
|
||||
])
|
||||
formData.value = data
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
${subClassNameVar}s.value = ${subClassNameVar}Data || []
|
||||
#else
|
||||
${subClassNameVar}.value = ${subClassNameVar}Data || null
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
/** 编辑${table.classComment} */
|
||||
function handleEdit() {
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/form/index?id=#[[${]]#props.id#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 删除${table.classComment} */
|
||||
async function handleDelete() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await dialog.confirm({ title: '提示', msg: '确定要删除该${table.classComment}吗?' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
deleting.value = true
|
||||
try {
|
||||
await delete${simpleClassName}(props.id)
|
||||
toast.success('删除成功')
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入页面加载(含编辑返回后刷新) */
|
||||
onShow(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
+638
-109
@@ -12,34 +12,48 @@
|
||||
<wd-form ref="formRef" :model="formData" :schema="formSchema">
|
||||
<wd-cell-group border>
|
||||
#foreach($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey)
|
||||
#set ($dictType = $column.dictType)
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($javaType = $column.javaType)
|
||||
#set ($comment = $column.columnComment)
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
## 下拉选择(字典)→ yd-form-picker,内部按 dictType 解析选项,适合选项较多
|
||||
#if ($column.htmlType == "select" && $dictType && "" != $dictType)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey)
|
||||
#set ($dictType = $column.dictType)
|
||||
#set ($javaField = $column.javaField)
|
||||
#set ($javaType = $column.javaType)
|
||||
#set ($comment = $column.columnComment)
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
## 树表的父级选择
|
||||
#if ($table.templateType == 2 && $column.id == $treeParentColumn.id)
|
||||
<yd-tree-select
|
||||
v-model="formData.${javaField}"
|
||||
:data="treeData"
|
||||
label="${comment}"
|
||||
prop="${javaField}"
|
||||
:props="{ label: '${treeNameColumn.javaField}', value: '${primaryColumn.javaField}' }"
|
||||
placeholder="请选择${comment}"
|
||||
/>
|
||||
## 下拉选择(字典)→ yd-form-picker,内部按 dictType 解析选项,适合选项较多
|
||||
#elseif ($column.htmlType == "select" && $dictType && "" != $dictType)
|
||||
<yd-form-picker
|
||||
v-model="formData.${javaField}"
|
||||
label="${comment}"
|
||||
label-width="180rpx"
|
||||
prop="${javaField}"
|
||||
#if ($javaType == "Boolean")
|
||||
:columns="getBoolDictOptions(DICT_TYPE.${dictType.toUpperCase()})"
|
||||
#else
|
||||
:dict-type="DICT_TYPE.${dictType.toUpperCase()}"
|
||||
#if ($javaType == "String")
|
||||
dict-kind="str"
|
||||
#end
|
||||
#end
|
||||
placeholder="请选择${comment}"
|
||||
/>
|
||||
## 单选(字典)→ radio-group,适合选项较少
|
||||
#elseif ($column.htmlType == "radio" && $dictType && "" != $dictType)
|
||||
## 单选(字典)→ radio-group,适合选项较少
|
||||
#elseif ($column.htmlType == "radio" && $dictType && "" != $dictType)
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center>
|
||||
<wd-radio-group v-model="formData.${javaField}" type="button">
|
||||
<wd-radio
|
||||
@@ -51,70 +65,78 @@
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</wd-form-item>
|
||||
## 数字类型(无字典)
|
||||
#elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte")
|
||||
## 数字类型(无字典)
|
||||
#elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "float" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<wd-input-number
|
||||
v-model="formData.${javaField}"
|
||||
#if (${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal")
|
||||
#if ($column.nullable)
|
||||
allow-null
|
||||
#end
|
||||
#if (${javaType.toLowerCase()} == "float" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal")
|
||||
:precision="2"
|
||||
input-type="number"
|
||||
#end
|
||||
:min="0"
|
||||
/>
|
||||
</wd-form-item>
|
||||
## 布尔类型
|
||||
#elseif (${javaType.toLowerCase()} == "boolean")
|
||||
## 布尔类型
|
||||
#elseif (${javaType.toLowerCase()} == "boolean")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}" center>
|
||||
<wd-switch v-model="formData.${javaField}" />
|
||||
</wd-form-item>
|
||||
## 日期时间类型
|
||||
#elseif (${javaType.toLowerCase()} == "date" || ${javaType.toLowerCase()} == "localdate" || ${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "date")
|
||||
#if (${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "datetime")
|
||||
#end
|
||||
## 日期时间类型
|
||||
#elseif (${javaType.toLowerCase()} == "date" || ${javaType.toLowerCase()} == "localdate" || ${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "date")
|
||||
#if (${javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($pickerType = "datetime")
|
||||
#end
|
||||
<wd-form-item
|
||||
title="${comment}"
|
||||
title-width="180rpx"
|
||||
prop="${javaField}"
|
||||
is-link
|
||||
#if ($pickerType == "datetime")
|
||||
:value="formatDateTime(formData.${javaField})"
|
||||
:value="formData.${javaField} ? formatDateTime(formData.${javaField}) : ''"
|
||||
#else
|
||||
:value="formatDate(formData.${javaField})"
|
||||
:value="formData.${javaField} ? formatDate(formData.${javaField}) : ''"
|
||||
#end
|
||||
placeholder="请选择${comment}"
|
||||
@click="${javaField}Visible = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model="formData.${javaField}"
|
||||
v-model:visible="${javaField}Visible"
|
||||
:model-value="formData.${javaField} || Date.now()"
|
||||
type="${pickerType}"
|
||||
@update:model-value="value => formData.${javaField} = value"
|
||||
/>
|
||||
## 文本域
|
||||
#elseif ($column.htmlType == "textarea")
|
||||
## 文本域
|
||||
#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<wd-textarea
|
||||
v-model="formData.${javaField}"
|
||||
clearable
|
||||
placeholder="请输入${comment}"
|
||||
#if ($column.htmlType == "editor")
|
||||
:maxlength="2000"
|
||||
#else
|
||||
:maxlength="200"
|
||||
#end
|
||||
show-word-limit
|
||||
/>
|
||||
</wd-form-item>
|
||||
## 图片上传 → yd-upload-img(包 wd-form-item 才能注册到 wd-form 做校验/错误展示)
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
## 图片上传 → yd-upload-img(包 wd-form-item 才能注册到 wd-form 做校验/错误展示)
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<yd-upload-img v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
## 文件上传 → yd-upload-file
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
## 文件上传 → yd-upload-file
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<yd-upload-file v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
<yd-upload-file v-model="formData.${javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
## 默认:文本输入
|
||||
#else
|
||||
## 默认:文本输入
|
||||
#else
|
||||
<wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
|
||||
<wd-input
|
||||
v-model="formData.${javaField}"
|
||||
@@ -122,11 +144,277 @@
|
||||
placeholder="请输入${comment}"
|
||||
/>
|
||||
</wd-form-item>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
|
||||
<!-- 子表:${subTable.classComment} -->
|
||||
<view class="mt-20rpx flex items-center justify-between px-24rpx py-16rpx">
|
||||
<text class="text-28rpx text-[#333] font-semibold">${subTable.classComment}</text>
|
||||
#if ($subTable.subJoinMany)
|
||||
<wd-button size="small" type="primary" variant="plain" @click="add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}">
|
||||
添加
|
||||
</wd-button>
|
||||
#else
|
||||
<wd-button
|
||||
v-if="!${subClassNameVar}"
|
||||
size="small"
|
||||
type="primary"
|
||||
variant="plain"
|
||||
@click="add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}()"
|
||||
>
|
||||
添加
|
||||
</wd-button>
|
||||
#end
|
||||
</view>
|
||||
<view class="px-24rpx">
|
||||
#if ($subTable.subJoinMany)
|
||||
<view v-if="!${subClassNameVar}s.length" class="rounded-12rpx bg-white py-40rpx text-center text-26rpx text-[#999]">
|
||||
暂无${subTable.classComment}
|
||||
</view>
|
||||
<view
|
||||
v-for="(item, index) in ${subClassNameVar}s"
|
||||
:key="index"
|
||||
class="mb-20rpx rounded-12rpx bg-white p-24rpx shadow-sm"
|
||||
>
|
||||
<view class="mb-16rpx flex justify-end">
|
||||
<wd-button size="small" type="danger" variant="plain" @click="remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(index)">
|
||||
删除
|
||||
</wd-button>
|
||||
</view>
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($column.javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($column.javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
#if ($column.htmlType == "select" && $column.dictType && "" != $column.dictType)
|
||||
<yd-form-picker
|
||||
v-model="item.${column.javaField}"
|
||||
label="${column.columnComment}"
|
||||
label-width="180rpx"
|
||||
#if ($column.javaType == "Boolean")
|
||||
:columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
#else
|
||||
:dict-type="DICT_TYPE.${column.dictType.toUpperCase()}"
|
||||
#if ($column.javaType == "String")
|
||||
dict-kind="str"
|
||||
#end
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
/>
|
||||
#elseif ($column.htmlType == "radio" && $column.dictType && "" != $column.dictType)
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" center>
|
||||
<wd-radio-group v-model="item.${column.javaField}" type="button">
|
||||
<wd-radio
|
||||
v-for="dict in $dictMethod(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "boolean")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" center>
|
||||
<wd-switch v-model="item.${column.javaField}" />
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-input-number
|
||||
v-model="item.${column.javaField}"
|
||||
#if($column.nullable)
|
||||
allow-null
|
||||
#end
|
||||
#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal")
|
||||
:precision="2"
|
||||
input-type="number"
|
||||
#end
|
||||
:min="0"
|
||||
/>
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
#set ($subPickerType = "date")
|
||||
#if ($javaType == "localdatetime")
|
||||
#set ($subPickerType = "datetime")
|
||||
#end
|
||||
<wd-form-item
|
||||
title="${column.columnComment}"
|
||||
title-width="180rpx"
|
||||
is-link
|
||||
#if ($javaType == "localdatetime")
|
||||
:value="item.${column.javaField} ? formatDateTime(item.${column.javaField}) : ''"
|
||||
#else
|
||||
:value="item.${column.javaField} ? formatDate(item.${column.javaField}) : ''"
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
@click="subDateVisible[`${subClassNameVar}s.#[[${]]#index#[[}]]#.${column.javaField}`] = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model:visible="subDateVisible[`${subClassNameVar}s.#[[${]]#index#[[}]]#.${column.javaField}`]"
|
||||
:model-value="item.${column.javaField} || Date.now()"
|
||||
type="${subPickerType}"
|
||||
@update:model-value="value => item.${column.javaField} = value"
|
||||
/>
|
||||
#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-textarea
|
||||
v-model="item.${column.javaField}"
|
||||
clearable
|
||||
placeholder="请输入${column.columnComment}"
|
||||
#if ($column.htmlType == "editor")
|
||||
:maxlength="2000"
|
||||
#else
|
||||
:maxlength="200"
|
||||
#end
|
||||
/>
|
||||
</wd-form-item>
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<yd-upload-img v-model="item.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<yd-upload-file v-model="item.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
#else
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-input v-model="item.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
|
||||
</wd-form-item>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</view>
|
||||
#else
|
||||
<view v-if="!${subClassNameVar}" class="rounded-12rpx bg-white py-40rpx text-center text-26rpx text-[#999]">
|
||||
暂无${subTable.classComment}
|
||||
</view>
|
||||
<view v-else class="rounded-12rpx bg-white p-24rpx shadow-sm">
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($dictMethod = "getDictOptions")
|
||||
#if ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short")
|
||||
#set ($dictMethod = "getIntDictOptions")
|
||||
#elseif ($column.javaType == "String")
|
||||
#set ($dictMethod = "getStrDictOptions")
|
||||
#elseif ($column.javaType == "Boolean")
|
||||
#set ($dictMethod = "getBoolDictOptions")
|
||||
#end
|
||||
#if ($column.htmlType == "select" && $column.dictType && "" != $column.dictType)
|
||||
<yd-form-picker
|
||||
v-model="${subClassNameVar}.${column.javaField}"
|
||||
label="${column.columnComment}"
|
||||
label-width="180rpx"
|
||||
#if ($column.javaType == "Boolean")
|
||||
:columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
#else
|
||||
:dict-type="DICT_TYPE.${column.dictType.toUpperCase()}"
|
||||
#if ($column.javaType == "String")
|
||||
dict-kind="str"
|
||||
#end
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
/>
|
||||
#elseif ($column.htmlType == "radio" && $column.dictType && "" != $column.dictType)
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" center>
|
||||
<wd-radio-group v-model="${subClassNameVar}.${column.javaField}" type="button">
|
||||
<wd-radio
|
||||
v-for="dict in $dictMethod(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</wd-radio>
|
||||
</wd-radio-group>
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "boolean")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" center>
|
||||
<wd-switch v-model="${subClassNameVar}.${column.javaField}" />
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-input-number
|
||||
v-model="${subClassNameVar}.${column.javaField}"
|
||||
#if($column.nullable)
|
||||
allow-null
|
||||
#end
|
||||
#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal")
|
||||
:precision="2"
|
||||
input-type="number"
|
||||
#end
|
||||
:min="0"
|
||||
/>
|
||||
</wd-form-item>
|
||||
#elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
#set ($subPickerType = "date")
|
||||
#if ($javaType == "localdatetime")
|
||||
#set ($subPickerType = "datetime")
|
||||
#end
|
||||
<wd-form-item
|
||||
title="${column.columnComment}"
|
||||
title-width="180rpx"
|
||||
is-link
|
||||
#if ($javaType == "localdatetime")
|
||||
:value="${subClassNameVar}.${column.javaField} ? formatDateTime(${subClassNameVar}.${column.javaField}) : ''"
|
||||
#else
|
||||
:value="${subClassNameVar}.${column.javaField} ? formatDate(${subClassNameVar}.${column.javaField}) : ''"
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
@click="subDateVisible['${subClassNameVar}.${column.javaField}'] = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model:visible="subDateVisible['${subClassNameVar}.${column.javaField}']"
|
||||
:model-value="${subClassNameVar}.${column.javaField} || Date.now()"
|
||||
type="${subPickerType}"
|
||||
@update:model-value="value => ${subClassNameVar}.${column.javaField} = value"
|
||||
/>
|
||||
#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-textarea
|
||||
v-model="${subClassNameVar}.${column.javaField}"
|
||||
clearable
|
||||
placeholder="请输入${column.columnComment}"
|
||||
#if ($column.htmlType == "editor")
|
||||
:maxlength="2000"
|
||||
#else
|
||||
:maxlength="200"
|
||||
#end
|
||||
/>
|
||||
</wd-form-item>
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<yd-upload-img v-model="${subClassNameVar}.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<yd-upload-file v-model="${subClassNameVar}.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
|
||||
</wd-form-item>
|
||||
#else
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx">
|
||||
<wd-input v-model="${subClassNameVar}.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
|
||||
</wd-form-item>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</view>
|
||||
#end
|
||||
</view>
|
||||
#end
|
||||
#end
|
||||
</view>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
@@ -146,7 +434,7 @@
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
@@ -154,54 +442,113 @@ import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasDate = 0)
|
||||
#set ($hasDateTime = 0)
|
||||
#set ($hasSubDate = 0)
|
||||
#set ($hasGetDictOptions = 0)
|
||||
#set ($hasGetIntDictOptions = 0)
|
||||
#set ($hasGetStrDictOptions = 0)
|
||||
#set ($hasGetBoolDictOptions = 0)
|
||||
#foreach ($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ($column.htmlType == "select" || $column.htmlType == "radio")
|
||||
&& $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short"))
|
||||
#set ($hasGetIntDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio" && $column.javaType == "String")
|
||||
#set ($hasGetStrDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio" && $column.javaType == "Boolean")
|
||||
#set ($hasGetBoolDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio")
|
||||
#set ($hasGetDictOptions = 1)
|
||||
#end
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate"))
|
||||
#set ($hasDate = 1)
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short"))
|
||||
#set ($hasGetIntDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio" && $column.javaType == "String")
|
||||
#set ($hasGetStrDictOptions = 1)
|
||||
#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $column.javaType == "Boolean")
|
||||
#set ($hasGetBoolDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio")
|
||||
#set ($hasGetDictOptions = 1)
|
||||
#end
|
||||
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate"))
|
||||
#set ($hasDate = 1)
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subColumns in $subColumnsList)
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ($column.htmlType == "select" || $column.htmlType == "radio")
|
||||
&& $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.htmlType == "radio" && ($column.javaType == "Integer" || $column.javaType == "Long" || $column.javaType == "Byte" || $column.javaType == "Short"))
|
||||
#set ($hasGetIntDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio" && $column.javaType == "String")
|
||||
#set ($hasGetStrDictOptions = 1)
|
||||
#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $column.javaType == "Boolean")
|
||||
#set ($hasGetBoolDictOptions = 1)
|
||||
#elseif ($column.htmlType == "radio")
|
||||
#set ($hasGetDictOptions = 1)
|
||||
#end
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate"))
|
||||
#set ($hasDate = 1)
|
||||
#set ($hasSubDate = 1)
|
||||
#end
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey
|
||||
&& ${column.javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($hasDateTime = 1)
|
||||
#set ($hasSubDate = 1)
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
import type {
|
||||
${simpleClassName},
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subSimpleClassName in $subSimpleClassNames)
|
||||
${subSimpleClassName},
|
||||
#end
|
||||
#end
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
import {
|
||||
create${simpleClassName},
|
||||
get${simpleClassName},
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#if ($subTable.subJoinMany)
|
||||
get${subSimpleClassName}ListBy${SubJoinColumnName},
|
||||
#else
|
||||
get${subSimpleClassName}By${SubJoinColumnName},
|
||||
#end
|
||||
#end
|
||||
update${simpleClassName},
|
||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
||||
#elseif ($table.templateType == 2)
|
||||
import { create${simpleClassName}, get${simpleClassName}, get${simpleClassName}List, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
#else
|
||||
import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
#end
|
||||
#if ($hasGetDictOptions == 1 || $hasGetIntDictOptions == 1 || $hasGetStrDictOptions == 1 || $hasGetBoolDictOptions == 1)
|
||||
#set ($dictImportNames = "")
|
||||
#if ($hasGetDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetIntDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getIntDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetStrDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getStrDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetBoolDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getBoolDictOptions, ")
|
||||
#end
|
||||
#set ($dictImportNames = $dictImportNames.trim())
|
||||
#set ($dictImportNames = $dictImportNames.substring(0, $dictImportNames.length() - 1))
|
||||
#set ($dictImportNames = "")
|
||||
#if ($hasGetDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetIntDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getIntDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetStrDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getStrDictOptions, ")
|
||||
#end
|
||||
#if ($hasGetBoolDictOptions == 1)
|
||||
#set ($dictImportNames = "${dictImportNames}getBoolDictOptions, ")
|
||||
#end
|
||||
#set ($dictImportNames = $dictImportNames.trim())
|
||||
#set ($dictImportNames = $dictImportNames.substring(0, $dictImportNames.length() - 1))
|
||||
import { $dictImportNames } from '@/hooks/useDict'
|
||||
#end
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
@@ -209,21 +556,27 @@ import { delay, navigateBackPlus } from '@/utils'
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDate == 1 || $hasDateTime == 1)
|
||||
#set ($dateImportNames = "")
|
||||
#if ($hasDate == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDate, ")
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDateTime, ")
|
||||
#end
|
||||
#set ($dateImportNames = $dateImportNames.trim())
|
||||
#set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1))
|
||||
#set ($dateImportNames = "")
|
||||
#if ($hasDate == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDate, ")
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDateTime, ")
|
||||
#end
|
||||
#set ($dateImportNames = $dateImportNames.trim())
|
||||
#set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1))
|
||||
import { $dateImportNames } from '@/utils/date'
|
||||
#end
|
||||
import { createFormSchema } from '@/utils/wot'
|
||||
#if ($table.templateType == 2)
|
||||
import { handleTree } from '@/utils/tree'
|
||||
#end
|
||||
|
||||
const props = defineProps<{
|
||||
id?: ${primaryTsType} | any
|
||||
id?: ${primaryTsType}
|
||||
#if ($table.templateType == 2)
|
||||
${treeParentColumn.javaField}?: ${primaryTsType}
|
||||
#end
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
@@ -236,42 +589,165 @@ definePage({
|
||||
const toast = useToast()
|
||||
const getTitle = computed(() => props.id ? '编辑${table.classComment}' : '新增${table.classComment}')
|
||||
const formLoading = ref(false) // 表单提交状态
|
||||
const formData = ref<${simpleClassName}>({
|
||||
const formData = ref<Partial<${simpleClassName}>>({
|
||||
#foreach($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) || $column.primaryKey)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime")
|
||||
#if ($column.primaryKey)
|
||||
#if (($column.createOperation || $column.updateOperation) || $column.primaryKey)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#set ($optional = $column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime")
|
||||
#if ($column.primaryKey)
|
||||
${column.javaField}: undefined,
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
#elseif($table.templateType == 2 && $column.id == $treeParentColumn.id)
|
||||
${column.javaField}: props.${column.javaField} ?? 0,
|
||||
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType)
|
||||
${column.javaField}: undefined,
|
||||
#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
|
||||
#if($column.nullable)
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
${column.javaField}: 0,
|
||||
#elseif(${javaType} == "boolean")
|
||||
#end
|
||||
#elseif(${javaType} == "boolean")
|
||||
${column.javaField}: false,
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
#elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
#else
|
||||
${column.javaField}: '',
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}) // 表单数据
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
const ${subClassNameVar}s = ref<Partial<${subSimpleClassName}>[]>([]) // ${subTable.classComment}数据
|
||||
#else
|
||||
const ${subClassNameVar} = ref<Partial<${subSimpleClassName}> | null>(null) // ${subTable.classComment}数据
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#foreach($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime"))
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime"))
|
||||
const ${column.javaField}Visible = ref(false) // ${column.columnComment}选择器状态
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#if ($hasSubDate == 1)
|
||||
const subDateVisible = ref<Record<string, boolean>>({}) // 子表日期选择器状态
|
||||
#end
|
||||
const formSchema = createFormSchema({
|
||||
#foreach($column in $columns)
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
|
||||
&& $javaFieldLower != "createtime" && $javaFieldLower != "updatetime")
|
||||
${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }],
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}) // 表单校验规则
|
||||
const formRef = ref<FormInstance>() // 表单组件引用
|
||||
#if ($table.templateType == 2)
|
||||
const treeData = ref<${simpleClassName}[]>([]) // 父级树形选项
|
||||
#end
|
||||
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
/** 添加${subTable.classComment} */
|
||||
function add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}() {
|
||||
${subClassNameVar}s.value.push({
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") 0#elseif($javaType == "boolean") false#else ''#end,
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除${subTable.classComment} */
|
||||
function remove${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}(index: number) {
|
||||
${subClassNameVar}s.value.splice(index, 1)
|
||||
}
|
||||
|
||||
#else
|
||||
/** 添加${subTable.classComment} */
|
||||
function add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.substring(1)}() {
|
||||
${subClassNameVar}.value = {
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") 0#elseif($javaType == "boolean") false#else ''#end,
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
#end
|
||||
#end
|
||||
/** 校验主子表数据 */
|
||||
function isEmptySubValue(value: unknown) {
|
||||
return value === undefined || value === null || value === ''
|
||||
}
|
||||
|
||||
function validateSubTables() {
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#set ($subColumns = $subColumnsList.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
for (let index = 0; index < ${subClassNameVar}s.value.length; index++) {
|
||||
const item = ${subClassNameVar}s.value[index]!
|
||||
#foreach ($column in $subColumns)
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
|
||||
&& $column.id != $subJoinColumn.id && $javaFieldLower != "createtime" && $javaFieldLower != "updatetime")
|
||||
if (isEmptySubValue(item.${column.javaField})) {
|
||||
toast.warning(`${subTable.classComment}第#[[${]]#index + 1#[[}]]#项的${column.columnComment}不能为空`)
|
||||
return false
|
||||
}
|
||||
#end
|
||||
#end
|
||||
}
|
||||
#else
|
||||
if (${subClassNameVar}.value) {
|
||||
#foreach ($column in $subColumns)
|
||||
#set ($javaFieldLower = $column.javaField.toLowerCase())
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey
|
||||
&& $column.id != $subJoinColumn.id && $javaFieldLower != "createtime" && $javaFieldLower != "updatetime")
|
||||
if (isEmptySubValue(${subClassNameVar}.value.${column.javaField})) {
|
||||
toast.warning('${subTable.classComment}的${column.columnComment}不能为空')
|
||||
return false
|
||||
}
|
||||
#end
|
||||
#end
|
||||
}
|
||||
#end
|
||||
#end
|
||||
return true
|
||||
}
|
||||
|
||||
#end
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus('/pages-${table.moduleName}/${table.businessName}/index')
|
||||
@@ -282,7 +758,34 @@ async function getDetail() {
|
||||
if (!props.id) {
|
||||
return
|
||||
}
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
const [data#foreach ($subTable in $subTables)#set ($index = $foreach.count - 1), ${subClassNameVars.get($index)}Data#end] = await Promise.all([
|
||||
get${simpleClassName}(props.id),
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($index))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + $subJoinColumn.javaField.substring(1))
|
||||
#if ($subTable.subJoinMany)
|
||||
get${subSimpleClassName}ListBy${SubJoinColumnName}(props.id),
|
||||
#else
|
||||
get${subSimpleClassName}By${SubJoinColumnName}(props.id),
|
||||
#end
|
||||
#end
|
||||
])
|
||||
formData.value = data
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
#if ($subTable.subJoinMany)
|
||||
${subClassNameVar}s.value = ${subClassNameVar}Data || []
|
||||
#else
|
||||
${subClassNameVar}.value = ${subClassNameVar}Data || null
|
||||
#end
|
||||
#end
|
||||
#else
|
||||
formData.value = await get${simpleClassName}(props.id)
|
||||
#end
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
@@ -291,14 +794,31 @@ async function handleSubmit() {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
if (!validateSubTables()) {
|
||||
return
|
||||
}
|
||||
#end
|
||||
|
||||
formLoading.value = true
|
||||
try {
|
||||
#if ($table.templateType == 10 || $table.templateType == 12)
|
||||
const data = {
|
||||
...formData.value,
|
||||
#foreach ($subTable in $subTables)
|
||||
#set ($index = $foreach.count - 1)
|
||||
#set ($subClassNameVar = $subClassNameVars.get($index))
|
||||
${subClassNameVar}#if($subTable.subJoinMany)s#end: ${subClassNameVar}#if($subTable.subJoinMany)s#end.value,
|
||||
#end
|
||||
} as ${simpleClassName}
|
||||
#else
|
||||
const data = formData.value as ${simpleClassName}
|
||||
#end
|
||||
if (props.id) {
|
||||
await update${simpleClassName}(formData.value)
|
||||
await update${simpleClassName}(data)
|
||||
toast.success('修改成功')
|
||||
} else {
|
||||
await create${simpleClassName}(formData.value)
|
||||
await create${simpleClassName}(data)
|
||||
toast.success('新增成功')
|
||||
}
|
||||
uni.$emit('${table.moduleName}:${table.businessName}:reload')
|
||||
@@ -309,7 +829,16 @@ async function handleSubmit() {
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
#if ($table.templateType == 2)
|
||||
const data = await get${simpleClassName}List()
|
||||
treeData.value = [{
|
||||
${primaryColumn.javaField}: 0,
|
||||
${treeNameColumn.javaField}: '顶级${table.classComment}',
|
||||
${treeParentColumn.javaField}: 0,
|
||||
children: handleTree(data, '${primaryColumn.javaField}', '${treeParentColumn.javaField}'),
|
||||
} as ${simpleClassName}]
|
||||
#end
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
#set ($subTable = $subTables.get($subIndex))
|
||||
#set ($subColumns = $subColumnsList.get($subIndex))
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
|
||||
#set ($subPrimaryColumn = $subPrimaryColumns.get($subIndex))
|
||||
#set ($subJoinColumn = $subJoinColumns.get($subIndex))
|
||||
#set ($subPrimaryJavaType = $subPrimaryColumn.javaType.toLowerCase())
|
||||
#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "float" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte")
|
||||
#set ($subPrimaryTsType = "number")
|
||||
#else
|
||||
#set ($subPrimaryTsType = "string")
|
||||
#end
|
||||
#set ($subJoinJavaType = $subJoinColumn.javaType.toLowerCase())
|
||||
#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "float" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte")
|
||||
#set ($subJoinTsType = "number")
|
||||
#else
|
||||
#set ($subJoinTsType = "string")
|
||||
#end
|
||||
#set ($hasDict = 0)
|
||||
#set ($hasBoolDict = 0)
|
||||
#set ($hasDate = 0)
|
||||
#set ($hasDateTime = 0)
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#if (($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType)
|
||||
#set ($hasDict = 1)
|
||||
#if ($column.javaType == "Boolean")
|
||||
#set ($hasBoolDict = 1)
|
||||
#end
|
||||
#end
|
||||
#if (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate")
|
||||
#set ($hasDate = 1)
|
||||
#end
|
||||
#if (${column.javaType.toLowerCase()} == "localdatetime")
|
||||
#set ($hasDateTime = 1)
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar :title="getTitle" left-arrow placeholder safe-area-inset-top fixed @click-left="handleBack" />
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<wd-form ref="formRef" :model="formData" :schema="formSchema">
|
||||
<wd-cell-group border>
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#if (($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType)
|
||||
<yd-form-picker
|
||||
v-model="formData.${column.javaField}"
|
||||
label="${column.columnComment}"
|
||||
label-width="180rpx"
|
||||
prop="${column.javaField}"
|
||||
#if ($column.javaType == "Boolean")
|
||||
:columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})"
|
||||
#else
|
||||
:dict-type="DICT_TYPE.${column.dictType.toUpperCase()}"
|
||||
#if ($column.javaType == "String")
|
||||
dict-kind="str"
|
||||
#end
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
/>
|
||||
#elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
#set ($pickerType = "date")
|
||||
#if ($javaType == "localdatetime")
|
||||
#set ($pickerType = "datetime")
|
||||
#end
|
||||
<wd-form-item
|
||||
title="${column.columnComment}"
|
||||
title-width="180rpx"
|
||||
prop="${column.javaField}"
|
||||
is-link
|
||||
#if ($javaType == "localdatetime")
|
||||
:value="formData.${column.javaField} ? formatDateTime(formData.${column.javaField}) : ''"
|
||||
#else
|
||||
:value="formData.${column.javaField} ? formatDate(formData.${column.javaField}) : ''"
|
||||
#end
|
||||
placeholder="请选择${column.columnComment}"
|
||||
@click="${column.javaField}Visible = true"
|
||||
/>
|
||||
<wd-datetime-picker
|
||||
v-model:visible="${column.javaField}Visible"
|
||||
:model-value="formData.${column.javaField} || Date.now()"
|
||||
type="${pickerType}"
|
||||
@update:model-value="value => formData.${column.javaField} = value"
|
||||
/>
|
||||
#else
|
||||
<wd-form-item title="${column.columnComment}" title-width="180rpx" prop="${column.javaField}">
|
||||
#if ($javaType == "boolean")
|
||||
<wd-switch v-model="formData.${column.javaField}" />
|
||||
#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
|
||||
<wd-input-number
|
||||
v-model="formData.${column.javaField}"
|
||||
#if($column.nullable)
|
||||
allow-null
|
||||
#end
|
||||
#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal")
|
||||
:precision="2"
|
||||
input-type="number"
|
||||
#end
|
||||
:min="0"
|
||||
/>
|
||||
#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
|
||||
<wd-textarea
|
||||
v-model="formData.${column.javaField}"
|
||||
clearable
|
||||
placeholder="请输入${column.columnComment}"
|
||||
#if ($column.htmlType == "editor")
|
||||
:maxlength="2000"
|
||||
#else
|
||||
:maxlength="200"
|
||||
#end
|
||||
/>
|
||||
#elseif ($column.htmlType == "imageUpload")
|
||||
<yd-upload-img v-model="formData.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
|
||||
#elseif ($column.htmlType == "fileUpload")
|
||||
<yd-upload-file v-model="formData.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
|
||||
#else
|
||||
<wd-input v-model="formData.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
|
||||
#end
|
||||
</wd-form-item>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
</wd-cell-group>
|
||||
</wd-form>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<view class="yd-detail-footer">
|
||||
<wd-button type="primary" block :loading="formLoading" @click="handleSubmit">
|
||||
保存
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
|
||||
import type { ${subSimpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from '@wot-ui/ui/components/wd-toast'
|
||||
import { create${subSimpleClassName}, get${subSimpleClassName}, update${subSimpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { delay, navigateBackPlus } from '@/utils'
|
||||
#if ($hasBoolDict == 1)
|
||||
import { getBoolDictOptions } from '@/hooks/useDict'
|
||||
#end
|
||||
#if ($hasDict == 1)
|
||||
import { DICT_TYPE } from '@/utils/constants'
|
||||
#end
|
||||
#if ($hasDate == 1 || $hasDateTime == 1)
|
||||
#set ($dateImportNames = "")
|
||||
#if ($hasDate == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDate, ")
|
||||
#end
|
||||
#if ($hasDateTime == 1)
|
||||
#set ($dateImportNames = "${dateImportNames}formatDateTime, ")
|
||||
#end
|
||||
#set ($dateImportNames = $dateImportNames.trim())
|
||||
#set ($dateImportNames = $dateImportNames.substring(0, $dateImportNames.length() - 1))
|
||||
import { $dateImportNames } from '@/utils/date'
|
||||
#end
|
||||
import { createFormSchema } from '@/utils/wot'
|
||||
|
||||
const props = defineProps<{
|
||||
id?: ${subPrimaryTsType}
|
||||
${subJoinColumn.javaField}: ${subJoinTsType}
|
||||
}>()
|
||||
|
||||
definePage({
|
||||
style: { navigationBarTitleText: '', navigationStyle: 'custom' },
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
const getTitle = computed(() => props.id ? '编辑${subTable.classComment}' : '新增${subTable.classComment}')
|
||||
const formLoading = ref(false) // 表单提交状态
|
||||
const formData = ref<Partial<${subSimpleClassName}>>({
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.primaryKey)
|
||||
${column.javaField}: undefined,
|
||||
#elseif ($column.id == $subJoinColumn.id)
|
||||
${column.javaField}: props.${column.javaField},
|
||||
#elseif ($column.createOperation || $column.updateOperation)
|
||||
#set ($javaType = $column.javaType.toLowerCase())
|
||||
#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
|
||||
${column.javaField}: undefined,
|
||||
#else
|
||||
${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") 0#elseif($javaType == "boolean") false#else ''#end,
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
}) // 表单数据
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id
|
||||
&& (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime"))
|
||||
const ${column.javaField}Visible = ref(false) // ${column.columnComment}选择器状态
|
||||
#end
|
||||
#end
|
||||
const formSchema = createFormSchema({
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !$column.primaryKey)
|
||||
${column.javaField}: [{ required: true, message: '${column.columnComment}不能为空' }],
|
||||
#end
|
||||
#end
|
||||
}) // 表单校验规则
|
||||
const formRef = ref<FormInstance>() // 表单组件引用
|
||||
|
||||
/** 返回上一页 */
|
||||
function handleBack() {
|
||||
navigateBackPlus()
|
||||
}
|
||||
|
||||
/** 加载${subTable.classComment}详情 */
|
||||
async function getDetail() {
|
||||
if (props.id) {
|
||||
formData.value = await get${subSimpleClassName}(props.id)
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
async function handleSubmit() {
|
||||
const { valid } = await formRef.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = {
|
||||
...formData.value,
|
||||
${subJoinColumn.javaField}: props.${subJoinColumn.javaField} ?? formData.value.${subJoinColumn.javaField},
|
||||
} as ${subSimpleClassName}
|
||||
if (props.id) {
|
||||
await update${subSimpleClassName}(data)
|
||||
toast.success('修改成功')
|
||||
} else {
|
||||
await create${subSimpleClassName}(data)
|
||||
toast.success('新增成功')
|
||||
}
|
||||
delay(handleBack)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<view class="yd-page-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<wd-navbar
|
||||
title="${table.classComment}管理"
|
||||
left-arrow placeholder safe-area-inset-top fixed
|
||||
@click-left="handleBack"
|
||||
/>
|
||||
|
||||
<!-- 搜索组件 -->
|
||||
<SearchForm @search="handleQuery" @reset="handleReset" />
|
||||
|
||||
<!-- 面包屑导航 -->
|
||||
<Breadcrumb ref="breadcrumbRef" v-model="currentParentId" />
|
||||
|
||||
<!-- ${table.classComment}列表 -->
|
||||
<view class="p-24rpx">
|
||||
<view
|
||||
v-for="item in currentList"
|
||||
:key="item.${primaryColumn.javaField}"
|
||||
class="mb-24rpx overflow-hidden rounded-12rpx bg-white shadow-sm"
|
||||
>
|
||||
<view class="p-24rpx" @click="handleDetail(item)">
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="flex min-w-0 items-center">
|
||||
<view class="mr-16rpx h-48rpx w-48rpx shrink-0 flex items-center justify-center rounded-8rpx bg-[#1890ff]">
|
||||
<wd-icon name="folder" size="20px" color="#fff" />
|
||||
</view>
|
||||
<view class="truncate text-32rpx text-[#333] font-semibold">
|
||||
{{ item.${treeNameColumn.javaField} }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="item.children?.length"
|
||||
class="ml-16rpx shrink-0 flex items-center"
|
||||
@click.stop="handleEnterChildren(item)"
|
||||
>
|
||||
<text class="text-24rpx text-[#1890ff]">子级 ({{ item.children.length }})</text>
|
||||
<wd-icon name="arrow-right" size="12px" color="#1890ff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="!loading && currentList.length === 0" class="py-100rpx text-center">
|
||||
<wd-empty icon="content" tip="暂无${table.classComment}数据" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新增按钮 -->
|
||||
<wd-fab
|
||||
v-if="hasAccessByCodes(['${permissionPrefix}:create'])"
|
||||
position="right-bottom"
|
||||
type="primary"
|
||||
:expandable="false"
|
||||
@click="handleAdd"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
#set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
|
||||
#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
|
||||
#set ($primaryTsType = "number")
|
||||
#else
|
||||
#set ($primaryTsType = "string")
|
||||
#end
|
||||
import type { ${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { onUnload } from '@dcloudio/uni-app'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { get${simpleClassName}List } from '@/api/${table.moduleName}/${table.businessName}'
|
||||
import { useAccess } from '@/hooks/useAccess'
|
||||
import { navigateBackPlus } from '@/utils'
|
||||
import { findChildren, handleTree } from '@/utils/tree'
|
||||
import Breadcrumb from './components/breadcrumb.vue'
|
||||
import SearchForm from './components/search-form.vue'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
})
|
||||
|
||||
const { hasAccessByCodes } = useAccess()
|
||||
const loading = ref(false) // 列表加载状态
|
||||
const list = ref<${simpleClassName}[]>([]) // 完整树形列表
|
||||
const currentParentId = ref<${primaryTsType}>(0) // 当前层级的父节点编号
|
||||
const currentList = computed(() => currentParentId.value === 0
|
||||
? list.value
|
||||
: findChildren(list.value, currentParentId.value, '${primaryColumn.javaField}')) // 当前层级列表
|
||||
const breadcrumbRef = ref<InstanceType<typeof Breadcrumb>>() // 面包屑组件引用
|
||||
const queryParams = ref<Record<string, any>>({}) // 查询参数
|
||||
|
||||
/** 返回上一页或上一层级 */
|
||||
function handleBack() {
|
||||
if (!breadcrumbRef.value?.back()) {
|
||||
navigateBackPlus()
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入子层级 */
|
||||
function handleEnterChildren(item: ${simpleClassName}) {
|
||||
breadcrumbRef.value?.enter({ id: item.${primaryColumn.javaField}!, name: item.${treeNameColumn.javaField} })
|
||||
}
|
||||
|
||||
/** 查询${table.classComment}列表 */
|
||||
async function getList() {
|
||||
loading.value = true
|
||||
try {
|
||||
list.value = handleTree(await get${simpleClassName}List(queryParams.value), '${primaryColumn.javaField}', '${treeParentColumn.javaField}')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery(data?: Record<string, any>) {
|
||||
queryParams.value = { ...data }
|
||||
currentParentId.value = 0
|
||||
breadcrumbRef.value?.reset()
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function handleReset() {
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 新增${table.classComment} */
|
||||
function handleAdd() {
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/form/index?${treeParentColumn.javaField}=#[[${]]#currentParentId.value#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 查看详情 */
|
||||
function handleDetail(item: ${simpleClassName}) {
|
||||
uni.navigateTo({ url: `/pages-${table.moduleName}/${table.businessName}/detail/index?id=#[[${]]#item.${primaryColumn.javaField}#[[}]]#` })
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
getList()
|
||||
uni.$on('${table.moduleName}:${table.businessName}:reload', getList)
|
||||
})
|
||||
|
||||
/** 卸载 */
|
||||
onUnload(() => {
|
||||
uni.$off('${table.moduleName}:${table.businessName}:reload', getList)
|
||||
})
|
||||
</script>
|
||||
+28
-22
@@ -1,5 +1,11 @@
|
||||
#set ($selectOptionEnable = false)
|
||||
#foreach ($column in $columns)
|
||||
#if (($column.createOperation || $column.updateOperation) && $column.htmlType == "select")
|
||||
#set ($selectOptionEnable = true)
|
||||
#end
|
||||
#end
|
||||
<script lang="ts" setup>
|
||||
import type { Rule } from 'antdv-next/es/form';
|
||||
import type { Rule } from 'antdv-next';
|
||||
import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
@@ -9,7 +15,7 @@ import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||
import { ImageUpload, FileUpload } from "#/components/upload";
|
||||
import { message, Tabs, Form, Input, TextArea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } from 'antdv-next';
|
||||
import { Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Input, message, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end#if ( $table.templateType == 10 || $table.templateType == 12 ), TabPane, Tabs#end, TextArea, TreeSelect } from 'antdv-next';
|
||||
#if($table.templateType == 2)## 树表需要导入这些
|
||||
import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
import { handleTree } from '@vben/utils'
|
||||
@@ -204,7 +210,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
#set ($dictMethod = "boolean")
|
||||
#end
|
||||
#if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<TreeSelect
|
||||
v-model:value="formData.${javaField}"
|
||||
:treeData="${classNameVar}Tree"
|
||||
@@ -225,25 +231,25 @@ const [Modal, modalApi] = useVbenModal({
|
||||
treeDefaultExpandAll
|
||||
placeholder="请选择${comment}"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "imageUpload")## 图片上传
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<ImageUpload v-model:value="formData.${javaField}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "fileUpload")## 文件上传
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<FileUpload v-model:value="formData.${javaField}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "editor")## 文本编辑器
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<RichTextarea v-model="formData.${javaField}" height="500px" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "select")## 下拉框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
<SelectOption
|
||||
@@ -257,9 +263,9 @@ const [Modal, modalApi] = useVbenModal({
|
||||
<SelectOption label="请选择字典生成" value="" />
|
||||
#end
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "checkbox")## 多选框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<CheckboxGroup v-model:value="formData.${javaField}">
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
<Checkbox
|
||||
@@ -273,9 +279,9 @@ const [Modal, modalApi] = useVbenModal({
|
||||
<Checkbox label="请选择字典生成" />
|
||||
#end
|
||||
</CheckboxGroup>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "radio")## 单选框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<RadioGroup v-model:value="formData.${javaField}">
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
<Radio
|
||||
@@ -289,19 +295,19 @@ const [Modal, modalApi] = useVbenModal({
|
||||
<Radio value="1">请选择字典生成</Radio>
|
||||
#end
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "datetime")## 时间框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<DatePicker
|
||||
v-model:value="formData.${javaField}"
|
||||
valueFormat="x"
|
||||
placeholder="选择${comment}"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "textarea")## 文本框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
|
||||
+2
-4
@@ -1,6 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import type { FileType } from 'antdv-next/es/upload/interface';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
@@ -43,8 +41,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||
});
|
||||
|
||||
/** 上传前:拦截 antd Upload 的自动上传,文件存到 ref */
|
||||
function beforeUpload(file: FileType) {
|
||||
fileRef.value = file as unknown as File;
|
||||
function beforeUpload(file: File) {
|
||||
fileRef.value = file;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+23
-11
@@ -1,3 +1,13 @@
|
||||
#set ($rangePickerEnable = false)
|
||||
#set ($selectOptionEnable = false)
|
||||
#foreach ($column in $columns)
|
||||
#if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
#set ($rangePickerEnable = true)
|
||||
#end
|
||||
#if ($column.listOperation && ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox"))
|
||||
#set ($selectOptionEnable = true)
|
||||
#end
|
||||
#end
|
||||
<script lang="ts" setup>
|
||||
import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
|
||||
@@ -8,12 +18,14 @@ import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
|
||||
import { cloneDeep, downloadFileFromBlobPart, formatDateTime, isEmpty } from '@vben/utils';
|
||||
import { Button, Card, message, Tabs, Pagination, Form, RangePicker, DatePicker, Select, Input } from 'antdv-next';
|
||||
import { Button, Card, DatePicker, Form, FormItem, Input, message, Pagination#if ($rangePickerEnable), DateRangePicker as RangePicker#end, Select#if ($selectOptionEnable), SelectOption#end#if ( $table.templateType == 11 || $table.templateType == 12 ), TabPane, Tabs#end } from 'antdv-next';
|
||||
import ${simpleClassName}Form from './modules/form.vue';
|
||||
import { Download, Plus, RefreshCw, Search, Trash2 } from '@vben/icons';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
|
||||
#if ($rangePickerEnable)
|
||||
import { getRangePickerDefaultProps } from '#/utils/rangePickerProps';
|
||||
#end
|
||||
|
||||
## 特殊:主子表专属逻辑
|
||||
#if ( $table.templateType == 11 || $table.templateType == 12 )
|
||||
@@ -249,7 +261,7 @@ onMounted(() => {
|
||||
#set ($dictMethod = "boolean")
|
||||
#end
|
||||
#if ($column.htmlType == "input")
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Input
|
||||
v-model:value="queryParams.${javaField}"
|
||||
placeholder="请输入${comment}"
|
||||
@@ -257,9 +269,9 @@ onMounted(() => {
|
||||
@pressEnter="handleQuery"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Select
|
||||
v-model:value="queryParams.${javaField}"
|
||||
placeholder="请选择${comment}"
|
||||
@@ -278,10 +290,10 @@ onMounted(() => {
|
||||
<SelectOption label="请选择字典生成" value="" />
|
||||
#end
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "datetime")
|
||||
#if ($column.listOperationCondition != "BETWEEN")## 非范围
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<DatePicker
|
||||
v-model:value="queryParams.${javaField}"
|
||||
valueFormat="YYYY-MM-DD"
|
||||
@@ -289,25 +301,25 @@ onMounted(() => {
|
||||
allowClear
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#else## 范围
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<RangePicker
|
||||
v-model:value="queryParams.${javaField}"
|
||||
v-bind="getRangePickerDefaultProps()"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
<Form.Item>
|
||||
<FormItem>
|
||||
<Button class="ml-2" @click="resetQuery"> 重置 </Button>
|
||||
<Button class="ml-2" @click="handleQuery" type="primary">
|
||||
搜索
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
|
||||
+26
-20
@@ -2,8 +2,14 @@
|
||||
#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
|
||||
#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
|
||||
#set ($selectOptionEnable = false)
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && $column.htmlType == "select")
|
||||
#set ($selectOptionEnable = true)
|
||||
#end
|
||||
#end
|
||||
<script lang="ts" setup>
|
||||
import type { Rule } from 'antdv-next/es/form';
|
||||
import type { Rule } from 'antdv-next';
|
||||
import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
@@ -13,7 +19,7 @@ import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||
import { ImageUpload, FileUpload } from "#/components/upload";
|
||||
import { message, Tabs, Form, Input, TextArea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } from 'antdv-next';
|
||||
import { Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Input, message, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end, TextArea, TreeSelect } from 'antdv-next';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
@@ -130,23 +136,23 @@ function resetForm(){
|
||||
#set ($dictMethod = "boolean")
|
||||
#end
|
||||
#if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "imageUpload")## 图片上传
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<ImageUpload v-model:value="formData.${javaField}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "fileUpload")## 文件上传
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<FileUpload v-model:value="formData.${javaField}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "editor")## 文本编辑器
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<RichTextarea v-model="formData.${javaField}" height="500px" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "select")## 下拉框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
<SelectOption
|
||||
@@ -160,9 +166,9 @@ function resetForm(){
|
||||
<SelectOption label="请选择字典生成" value="" />
|
||||
#end
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "checkbox")## 多选框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<CheckboxGroup v-model:value="formData.${javaField}">
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
<Checkbox
|
||||
@@ -176,9 +182,9 @@ function resetForm(){
|
||||
<Checkbox label="请选择字典生成" />
|
||||
#end
|
||||
</CheckboxGroup>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "radio")## 单选框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<RadioGroup v-model:value="formData.${javaField}">
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
<Radio
|
||||
@@ -192,19 +198,19 @@ function resetForm(){
|
||||
<Radio value="1">请选择字典生成</Radio>
|
||||
#end
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "datetime")## 时间框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<DatePicker
|
||||
v-model:value="formData.${javaField}"
|
||||
valueFormat="x"
|
||||
placeholder="选择${comment}"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "textarea")## 文本框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
## 主表的 normal 和 inner 使用相同的 form 表单
|
||||
#parse("codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm")
|
||||
#parse("codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_normal.vue.vm")
|
||||
|
||||
+26
-20
@@ -4,6 +4,12 @@
|
||||
#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
|
||||
#set ($subClassNameVar = $subClassNameVars.get($subIndex))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
|
||||
#set ($selectOptionEnable = false)
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && (!$subTable.subJoinMany || $column.id != $subJoinColumn.id) && $column.htmlType == "select")
|
||||
#set ($selectOptionEnable = true)
|
||||
#end
|
||||
#end
|
||||
<script lang="ts" setup>
|
||||
import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
|
||||
@@ -12,7 +18,7 @@ import { computed, ref, h, onMounted, watch, nextTick } from 'vue';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { message, Tabs, Form, Input, TextArea, Button, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker } from 'antdv-next';
|
||||
import { Button, Checkbox, CheckboxGroup, DatePicker#if (!$subTable.subJoinMany), Form, FormItem#end, Input, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end, TextArea } from 'antdv-next';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
#if ($subTable.subJoinMany) ## 一对多
|
||||
@@ -22,7 +28,7 @@ import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
|
||||
import { ImageUpload, FileUpload } from "#/components/upload";
|
||||
import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
#else
|
||||
import type { Rule } from 'antdv-next/es/form';
|
||||
import type { Rule } from 'antdv-next';
|
||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||
import { ImageUpload, FileUpload } from "#/components/upload";
|
||||
import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
@@ -264,23 +270,23 @@ watch(
|
||||
#set ($dictMethod = "boolean")
|
||||
#end
|
||||
#if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "imageUpload")## 图片上传
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<ImageUpload v-model:value="formData.${javaField}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "fileUpload")## 文件上传
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<FileUpload v-model:value="formData.${javaField}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "editor")## 文本编辑器
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<RichTextarea v-model="formData.${javaField}" height="500px" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "select")## 下拉框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
<SelectOption
|
||||
@@ -294,9 +300,9 @@ watch(
|
||||
<SelectOption label="请选择字典生成" value="" />
|
||||
#end
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "checkbox")## 多选框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<CheckboxGroup v-model:value="formData.${javaField}">
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
<Checkbox
|
||||
@@ -310,9 +316,9 @@ watch(
|
||||
<Checkbox label="请选择字典生成" />
|
||||
#end
|
||||
</CheckboxGroup>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "radio")## 单选框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<RadioGroup v-model:value="formData.${javaField}">
|
||||
#if ("" != $dictType)## 有数据字典
|
||||
<Radio
|
||||
@@ -326,19 +332,19 @@ watch(
|
||||
<Radio value="1">请选择字典生成</Radio>
|
||||
#end
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "datetime")## 时间框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<DatePicker
|
||||
v-model:value="formData.${javaField}"
|
||||
valueFormat="x"
|
||||
placeholder="选择${comment}"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "textarea")## 文本框
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
|
||||
+23
-11
@@ -5,6 +5,16 @@
|
||||
#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
|
||||
#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
|
||||
#set ($rangePickerEnable = false)
|
||||
#set ($selectOptionEnable = false)
|
||||
#foreach ($column in $subColumns)
|
||||
#if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
|
||||
#set ($rangePickerEnable = true)
|
||||
#end
|
||||
#if ($column.listOperation && ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox"))
|
||||
#set ($selectOptionEnable = true)
|
||||
#end
|
||||
#end
|
||||
<script lang="ts" setup>
|
||||
import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
import type { VxeTableInstance } from '#/adapter/vxe-table';
|
||||
@@ -15,7 +25,9 @@ import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
#if ($table.templateType == 11 && $rangePickerEnable)
|
||||
import { getRangePickerDefaultProps } from '#/utils/rangePickerProps';
|
||||
#end
|
||||
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
@@ -25,7 +37,7 @@ import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
|
||||
import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
|
||||
import { Tinymce as RichTextarea } from '#/components/tinymce';
|
||||
import { ImageUpload, FileUpload } from "#/components/upload";
|
||||
import { message, Button, Card, Tabs, Pagination, Form, Input, TextArea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, RangePicker, DatePicker, TreeSelect } from 'antdv-next';
|
||||
import { Button, Card, Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Input, message, Pagination, Radio, RadioGroup#if ($rangePickerEnable), DateRangePicker as RangePicker#end, Select#if ($selectOptionEnable), SelectOption#end, TextArea, TreeSelect } from 'antdv-next';
|
||||
import { Plus, Trash2 } from '@vben/icons';
|
||||
import { $t } from '#/locales';
|
||||
#end
|
||||
@@ -224,7 +236,7 @@ onMounted(() => {
|
||||
#set ($dictMethod = "boolean")
|
||||
#end
|
||||
#if ($column.htmlType == "input")
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Input
|
||||
v-model:value="queryParams.${javaField}"
|
||||
placeholder="请输入${comment}"
|
||||
@@ -232,9 +244,9 @@ onMounted(() => {
|
||||
@pressEnter="handleQuery"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<Select
|
||||
v-model:value="queryParams.${javaField}"
|
||||
placeholder="请选择${comment}"
|
||||
@@ -253,10 +265,10 @@ onMounted(() => {
|
||||
<SelectOption label="请选择字典生成" value="" />
|
||||
#end
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#elseif($column.htmlType == "datetime")
|
||||
#if ($column.listOperationCondition != "BETWEEN")## 非范围
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<DatePicker
|
||||
v-model:value="queryParams.${javaField}"
|
||||
valueFormat="YYYY-MM-DD"
|
||||
@@ -264,25 +276,25 @@ onMounted(() => {
|
||||
allowClear
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#else## 范围
|
||||
<Form.Item label="${comment}" name="${javaField}">
|
||||
<FormItem label="${comment}" name="${javaField}">
|
||||
<RangePicker
|
||||
v-model:value="queryParams.${javaField}"
|
||||
v-bind="getRangePickerDefaultProps()"
|
||||
class="w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
#end
|
||||
<Form.Item>
|
||||
<FormItem>
|
||||
<Button class="ml-2" @click="resetQuery"> 重置 </Button>
|
||||
<Button class="ml-2" @click="handleQuery" type="primary">
|
||||
搜索
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
|
||||
## 1)inner 使用 list 不分页,erp 使用 page 分页
|
||||
## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
|
||||
#parse("codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm")
|
||||
#parse("codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_erp.vue.vm")
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message#if ($table.templateType == 11), Tabs#end } from 'antdv-next';
|
||||
import { message#if ($table.templateType == 10 || $table.templateType == 12), TabPane, Tabs#end } from 'antdv-next';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
|
||||
+1
-3
@@ -1,6 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import type { FileType } from 'antdv-next/es/upload/interface';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
|
||||
@@ -46,7 +44,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
});
|
||||
|
||||
/** 上传前 */
|
||||
function beforeUpload(file: FileType) {
|
||||
function beforeUpload(file: File) {
|
||||
formApi.setFieldValue('file', file);
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import { ref } from 'vue';
|
||||
import {#if ($table.templateType != 2 && $deleteBatchEnable) confirm,#end Page, useVbenModal } from '@vben/common-ui';
|
||||
import { downloadFileFromBlobPart#if ($table.templateType != 2 && $deleteBatchEnable), isEmpty#end } from '@vben/utils';
|
||||
|
||||
import { message#if ($table.templateType == 11), Tabs#end } from 'antdv-next';
|
||||
import { message#if ($table.templateType == 11 || $table.templateType == 12), TabPane, Tabs#end } from 'antdv-next';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
#if (${table.templateType} == 2)## 树表接口
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
## 主表的 normal 和 inner 使用相同的 form 表单
|
||||
#parse("codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm")
|
||||
#parse("codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_normal.vue.vm")
|
||||
|
||||
+19
-1
@@ -5,6 +5,12 @@
|
||||
#set ($subClassNameVar = $subClassNameVars.get($subIndex))
|
||||
#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
|
||||
#set ($apiName = "${table.moduleName.substring(0,1).toUpperCase()}${table.moduleName.substring(1)}${simpleClassName}Api")
|
||||
#set ($selectOptionEnable = false)
|
||||
#foreach ($column in $subColumns)
|
||||
#if (($column.createOperation || $column.updateOperation) && $column.id != $subJoinColumn.id && $column.htmlType == "select")
|
||||
#set ($selectOptionEnable = true)
|
||||
#end
|
||||
#end
|
||||
<script lang="ts" setup>
|
||||
import type { ${apiName} } from '#/api/${table.moduleName}/${table.businessName}';
|
||||
|
||||
@@ -14,7 +20,19 @@
|
||||
|
||||
#if ($subTable.subJoinMany) ## 一对多
|
||||
import { Plus } from "@vben/icons";
|
||||
import { Button, Tabs, Checkbox, Input, TextArea, Select,RadioGroup,CheckboxGroup, DatePicker } from 'antdv-next';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
Input,
|
||||
RadioGroup,
|
||||
Select,
|
||||
#if ($selectOptionEnable)
|
||||
SelectOption,
|
||||
#end
|
||||
TextArea,
|
||||
} from 'antdv-next';
|
||||
import { ImageUpload, FileUpload } from "#/components/upload";
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { use${subSimpleClassName}GridEditColumns } from '../data';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
|
||||
## 1)inner 使用 list 不分页,erp 使用 page 分页
|
||||
## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
|
||||
#parse("codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm")
|
||||
#parse("codegen/vue3_vben5_antdv_next/schema/views/modules/list_sub_erp.vue.vm")
|
||||
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
package cn.iocoder.yudao.module.infra.service.codegen.inner;
|
||||
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
|
||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* {@link CodegenEngine} 的 Vue3 + Vben5 + Antdv Next 单元测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class CodegenEngineVben5AntdvNextTest extends CodegenEngineAbstractTest {
|
||||
|
||||
private static final Pattern ANTDV_NEXT_VALUE_IMPORT_PATTERN = Pattern.compile(
|
||||
"import\\s*\\{([^}]*)}\\s*from\\s*'antdv-next';?", Pattern.DOTALL);
|
||||
private static final Pattern RANGE_PICKER_COMPONENT_PATTERN = Pattern.compile("<RangePicker\\b");
|
||||
private static final Pattern RANGE_PICKER_HELPER_BINDING_PATTERN = Pattern.compile(
|
||||
"\\bv-bind\\s*=\\s*([\"'])\\s*getRangePickerDefaultProps\\s*\\(\\s*\\)\\s*\\1");
|
||||
private static final Pattern RANGE_PICKER_HELPER_CALL_PATTERN = Pattern.compile(
|
||||
"\\bgetRangePickerDefaultProps\\s*\\(\\s*\\)");
|
||||
private static final Pattern RANGE_PICKER_HELPER_IMPORT_PATTERN = Pattern.compile(
|
||||
"import\\s*\\{[^}]*\\bgetRangePickerDefaultProps\\b[^}]*}"
|
||||
+ "\\s*from\\s*'#/utils(?:/rangePickerProps)?';?", Pattern.DOTALL);
|
||||
private static final Pattern SCHEMA_RANGE_PICKER_COMPONENT_PATTERN = Pattern.compile(
|
||||
"\\bcomponent\\s*:\\s*([\"'])RangePicker\\1");
|
||||
private static final Pattern SCHEMA_RANGE_PICKER_HELPER_SPREAD_PATTERN = Pattern.compile(
|
||||
"\\.\\.\\.\\s*getRangePickerDefaultProps\\s*\\(\\s*\\)");
|
||||
|
||||
private static final String IMPORT_FORM_PATH =
|
||||
"yudao-ui-admin-vben/src/views/infra/demo/modules/import-form.vue";
|
||||
private static final String SCHEMA_DATA_PATH =
|
||||
"yudao-ui-admin-vben/src/views/infra/demo/data.ts";
|
||||
private static final String STUDENT_CONTACT_FORM_PATH =
|
||||
"yudao-ui-admin-vben/src/views/infra/demo/modules/student-contact-form.vue";
|
||||
private static final String STUDENT_TEACHER_FORM_PATH =
|
||||
"yudao-ui-admin-vben/src/views/infra/demo/modules/student-teacher-form.vue";
|
||||
|
||||
private static final List<String> INNER_TEMPLATE_PATHS = Arrays.asList(
|
||||
"codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_inner.vue.vm",
|
||||
"codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_inner.vue.vm",
|
||||
"codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_inner.vue.vm",
|
||||
"codegen/vue3_vben5_antdv_next/schema/views/modules/list_sub_inner.vue.vm");
|
||||
|
||||
@Test
|
||||
public void testExecute_schemaMasterNormal() {
|
||||
// 调用
|
||||
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(),
|
||||
CodegenTemplateTypeEnum.MASTER_NORMAL);
|
||||
|
||||
// 断言
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
assertSchemaRangePickerGenerated(result);
|
||||
assertTabsGenerated(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_schemaMasterErp() {
|
||||
// 调用
|
||||
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(),
|
||||
CodegenTemplateTypeEnum.MASTER_ERP);
|
||||
|
||||
// 断言
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
assertSchemaRangePickerGenerated(result);
|
||||
assertTabsGenerated(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_schemaMasterInner() {
|
||||
// 调用
|
||||
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(),
|
||||
CodegenTemplateTypeEnum.MASTER_INNER);
|
||||
|
||||
// 断言
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
assertSchemaRangePickerGenerated(result);
|
||||
assertTabsGenerated(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_generalTree() {
|
||||
// 调用
|
||||
Map<String, String> result = executeTree(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType());
|
||||
|
||||
// 断言
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
assertRuleGenerated(result);
|
||||
assertComponentGenerated(result, "FormItem");
|
||||
assertComponentGenerated(result, "TreeSelect");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_generalMasterNormal() {
|
||||
// 调用
|
||||
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(),
|
||||
CodegenTemplateTypeEnum.MASTER_NORMAL);
|
||||
|
||||
// 断言
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
assertRuleGenerated(result);
|
||||
assertGeneralCrudComponentsGenerated(result);
|
||||
assertTabsGenerated(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_generalMasterNormal_oneToOneJoinColumnOnlySelect() {
|
||||
// 调用
|
||||
Map<String, String> result = executeGeneralMasterNormalWithOneToOneJoinSelect();
|
||||
|
||||
// 断言
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
String content = result.get(STUDENT_TEACHER_FORM_PATH);
|
||||
assertNotNull(content, STUDENT_TEACHER_FORM_PATH + ":未生成一对一子表表单");
|
||||
assertTrue(content.contains("<Select v-model:value=\"formData.studentId\""),
|
||||
STUDENT_TEACHER_FORM_PATH + ":应渲染 join 列的 Select 组件");
|
||||
assertTrue(content.contains("<SelectOption"), STUDENT_TEACHER_FORM_PATH + ":应渲染 SelectOption 组件");
|
||||
assertRootValueImport(STUDENT_TEACHER_FORM_PATH, content, "\\bSelectOption\\b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_generalMasterNormal_oneToManyJoinColumnOnlySelect() {
|
||||
// 调用
|
||||
Map<String, String> result = executeGeneralMasterNormalWithOneToManyJoinSelect();
|
||||
|
||||
// 断言
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
String content = result.get(STUDENT_CONTACT_FORM_PATH);
|
||||
assertNotNull(content, STUDENT_CONTACT_FORM_PATH + ":未生成一对多子表表单");
|
||||
assertTrue(content.contains("<VxeTable"), STUDENT_CONTACT_FORM_PATH + ":应生成一对多子表表格");
|
||||
assertFalse(content.contains("<VxeColumn field=\"studentId\""),
|
||||
STUDENT_CONTACT_FORM_PATH + ":不应生成 join 列");
|
||||
assertFalse(content.contains("<Select v-model:value=\"row.studentId\""),
|
||||
STUDENT_CONTACT_FORM_PATH + ":不应渲染 join 列的 Select 组件");
|
||||
assertFalse(content.contains("<SelectOption"), STUDENT_CONTACT_FORM_PATH + ":不应渲染 SelectOption 组件");
|
||||
assertFalse(createRootImportPattern("import\\s*\\{", "\\bSelectOption\\b").matcher(content).find(),
|
||||
STUDENT_CONTACT_FORM_PATH + ":不应导入 SelectOption 组件");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_generalMasterInner() {
|
||||
// 调用
|
||||
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(),
|
||||
CodegenTemplateTypeEnum.MASTER_INNER);
|
||||
|
||||
// 断言
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
assertRuleGenerated(result);
|
||||
assertGeneralCrudComponentsGenerated(result);
|
||||
assertTabsGenerated(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_generalMasterErp() {
|
||||
// 调用
|
||||
Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(),
|
||||
CodegenTemplateTypeEnum.MASTER_ERP);
|
||||
|
||||
// 断言
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
assertRuleGenerated(result);
|
||||
assertGeneralCrudComponentsGenerated(result);
|
||||
assertTabsGenerated(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_oneImportEnable() {
|
||||
// 开启 import 开关
|
||||
codegenProperties.setImportEnable(true);
|
||||
codegenEngine.initGlobalBindingMap();
|
||||
|
||||
// 调用并断言
|
||||
assertImportFormCompatibleWithAntdvNext(
|
||||
executeOne(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType()));
|
||||
assertImportFormCompatibleWithAntdvNext(
|
||||
executeOne(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInnerTemplatesParseAntdvNext() {
|
||||
INNER_TEMPLATE_PATHS.forEach(path -> {
|
||||
String template = ResourceUtil.readUtf8Str(path);
|
||||
assertFalse(template.contains("codegen/vue3_vben5_antd/"), path + ":不应引用 Ant Design Vue 模板");
|
||||
assertTrue(template.contains("codegen/vue3_vben5_antdv_next/"), path + ":应引用 Antdv Next 模板");
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, String> executeOne(Integer frontType) {
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(frontType)
|
||||
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
|
||||
return codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"), null, null);
|
||||
}
|
||||
|
||||
private Map<String, String> executeTree(Integer frontType) {
|
||||
CodegenTableDO table = getTable("category")
|
||||
.setFrontType(frontType)
|
||||
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
|
||||
return codegenEngine.execute(DbType.MYSQL, table, getColumnList("category"), null, null);
|
||||
}
|
||||
|
||||
private Map<String, String> executeGeneralMasterNormalWithOneToOneJoinSelect() {
|
||||
return executeGeneralMasterNormalWithJoinSelect("teacher", 200L, false);
|
||||
}
|
||||
|
||||
private Map<String, String> executeGeneralMasterNormalWithOneToManyJoinSelect() {
|
||||
return executeGeneralMasterNormalWithJoinSelect("contact", 100L, true);
|
||||
}
|
||||
|
||||
private Map<String, String> executeGeneralMasterNormalWithJoinSelect(String subTableName,
|
||||
Long subJoinColumnId,
|
||||
boolean subJoinMany) {
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType())
|
||||
.setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType());
|
||||
CodegenTableDO subTable = getTable(subTableName)
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
||||
.setFrontType(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType())
|
||||
.setSubJoinColumnId(subJoinColumnId).setSubJoinMany(subJoinMany);
|
||||
List<CodegenColumnDO> subColumns = getColumnList(subTableName);
|
||||
// 稀疏场景:join 列是唯一的 select,避免其它 select 掩盖 join 列对导入判定的影响。
|
||||
subColumns.forEach(column -> {
|
||||
if (subJoinColumnId.equals(column.getId())) {
|
||||
column.setHtmlType("select");
|
||||
column.setDictType("");
|
||||
} else if ("select".equals(column.getHtmlType())) {
|
||||
column.setHtmlType("input");
|
||||
}
|
||||
});
|
||||
return codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"),
|
||||
Collections.singletonList(subTable), Collections.singletonList(subColumns));
|
||||
}
|
||||
|
||||
private Map<String, String> executeMaster(Integer frontType, CodegenTemplateTypeEnum templateType) {
|
||||
// 准备参数
|
||||
CodegenTableDO table = getTable("student")
|
||||
.setFrontType(frontType)
|
||||
.setTemplateType(templateType.getType());
|
||||
List<CodegenColumnDO> columns = getColumnList("student");
|
||||
// 准备参数(子表)
|
||||
CodegenTableDO contactTable = getTable("contact")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
||||
.setFrontType(frontType)
|
||||
.setSubJoinColumnId(100L).setSubJoinMany(true);
|
||||
List<CodegenColumnDO> contactColumns = getColumnList("contact");
|
||||
// 准备参数(班主任)
|
||||
CodegenTableDO teacherTable = getTable("teacher")
|
||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
||||
.setFrontType(frontType)
|
||||
.setSubJoinColumnId(200L).setSubJoinMany(false);
|
||||
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
|
||||
|
||||
// 调用
|
||||
return codegenEngine.execute(DbType.MYSQL, table, columns,
|
||||
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
|
||||
}
|
||||
|
||||
private static void assertGeneratedCodeCompatibleWithAntdvNext(Map<String, String> result) {
|
||||
assertTrue(result.values().stream().anyMatch(content -> content.contains("from 'antdv-next")),
|
||||
"生成结果应使用 antdv-next");
|
||||
result.forEach((filePath, content) -> {
|
||||
assertFalse(content.contains("ant-design-vue"), filePath + ":不应使用 ant-design-vue");
|
||||
assertFalse(content.contains("antdv-next/es/"), filePath + ":不应使用未导出的 antdv-next 深层路径");
|
||||
assertFalse(content.contains("<Form.Item"), filePath + ":不应使用 Form.Item");
|
||||
assertFalse(content.contains("</Form.Item>"), filePath + ":不应使用 Form.Item");
|
||||
assertRootValueImportsSorted(filePath, content);
|
||||
assertRootValueImportMatchesUsage(filePath, content,
|
||||
"\\bDateRangePicker\\s+as\\s+RangePicker\\b", "<RangePicker\\b", "RangePicker");
|
||||
assertRootValueImportMatchesUsage(filePath, content,
|
||||
"\\bFormItem\\b", "<FormItem\\b", "FormItem");
|
||||
assertRootValueImportMatchesUsage(filePath, content,
|
||||
"\\bSelectOption\\b", "<SelectOption\\b", "SelectOption");
|
||||
assertRootValueImportMatchesUsage(filePath, content,
|
||||
"\\bTabPane\\b", "<TabPane\\b", "TabPane");
|
||||
assertRootValueImportMatchesUsage(filePath, content,
|
||||
"\\bTabs\\b", "<Tabs\\b", "Tabs");
|
||||
assertRootTypeImportMatchesUsage(filePath, content,
|
||||
"\\bRule\\b", "\\bRule\\b", "Rule");
|
||||
assertRangePickerHelperImportMatchesUsage(filePath, content);
|
||||
if (filePath.endsWith(".vue")) {
|
||||
assertRangePickerHelperBindingMatchesComponent(filePath, content);
|
||||
} else if (filePath.endsWith(".ts")) {
|
||||
assertSchemaRangePickerHelperMatchesComponent(filePath, content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void assertImportFormCompatibleWithAntdvNext(Map<String, String> result) {
|
||||
assertGeneratedCodeCompatibleWithAntdvNext(result);
|
||||
String content = result.get(IMPORT_FORM_PATH);
|
||||
assertNotNull(content, IMPORT_FORM_PATH + ":未生成导入表单");
|
||||
assertTrue(content.contains("<Upload"), IMPORT_FORM_PATH + ":应生成 Upload 组件");
|
||||
assertRootValueImport(IMPORT_FORM_PATH, content, "\\bUpload\\b");
|
||||
assertTrue(content.contains("beforeUpload(file: File)"), IMPORT_FORM_PATH + ":上传参数应使用原生 File 类型");
|
||||
assertFalse(content.contains("UploadFile"), IMPORT_FORM_PATH + ":不应使用上传列表项类型 UploadFile");
|
||||
assertFalse(content.contains("as unknown as File"), IMPORT_FORM_PATH + ":不应通过双重断言转换上传文件");
|
||||
assertFalse(content.contains("FileType"), IMPORT_FORM_PATH + ":不应继续使用旧 FileType");
|
||||
}
|
||||
|
||||
private static void assertTabsGenerated(Map<String, String> result) {
|
||||
assertComponentGenerated(result, "Tabs");
|
||||
assertComponentGenerated(result, "TabPane");
|
||||
}
|
||||
|
||||
private static void assertGeneralCrudComponentsGenerated(Map<String, String> result) {
|
||||
assertComponentGenerated(result, "FormItem");
|
||||
assertComponentGenerated(result, "RangePicker");
|
||||
assertComponentGenerated(result, "SelectOption");
|
||||
}
|
||||
|
||||
private static void assertRuleGenerated(Map<String, String> result) {
|
||||
assertTrue(result.values().stream().map(CodegenEngineVben5AntdvNextTest::removeAntdvNextRootImports)
|
||||
.anyMatch(content -> Pattern.compile("\\bRule\\b").matcher(content).find()),
|
||||
"生成结果应包含 Rule 类型校验规则");
|
||||
}
|
||||
|
||||
private static void assertSchemaRangePickerGenerated(Map<String, String> result) {
|
||||
String content = result.get(SCHEMA_DATA_PATH);
|
||||
assertNotNull(content, SCHEMA_DATA_PATH + ":未生成 Schema 配置");
|
||||
long componentCount = countMatches(SCHEMA_RANGE_PICKER_COMPONENT_PATTERN, content);
|
||||
assertTrue(componentCount > 0, SCHEMA_DATA_PATH + ":应生成 RangePicker 组件配置");
|
||||
}
|
||||
|
||||
private static void assertComponentGenerated(Map<String, String> result, String component) {
|
||||
assertTrue(result.values().stream().anyMatch(content -> content.contains("<" + component)),
|
||||
"生成结果应包含 " + component + " 组件");
|
||||
}
|
||||
|
||||
private static void assertRootValueImportMatchesUsage(String filePath, String content,
|
||||
String expectedImportRegex, String usageRegex,
|
||||
String bindingName) {
|
||||
assertRootImportMatchesUsage(filePath, content, "import\\s*\\{", expectedImportRegex,
|
||||
usageRegex, bindingName);
|
||||
}
|
||||
|
||||
private static void assertRootTypeImportMatchesUsage(String filePath, String content,
|
||||
String expectedImportRegex, String usageRegex,
|
||||
String bindingName) {
|
||||
assertRootImportMatchesUsage(filePath, content, "import\\s+type\\s*\\{", expectedImportRegex,
|
||||
usageRegex, bindingName);
|
||||
}
|
||||
|
||||
private static void assertRootImportMatchesUsage(String filePath, String content, String importPrefix,
|
||||
String expectedImportRegex, String usageRegex,
|
||||
String bindingName) {
|
||||
boolean imported = createRootImportPattern(importPrefix, expectedImportRegex).matcher(content).find();
|
||||
boolean used = Pattern.compile(usageRegex).matcher(removeAntdvNextRootImports(content)).find();
|
||||
assertEquals(used, imported, filePath + ":" + bindingName + (used ? " 缺少合法根导入" : " 存在孤立根导入"));
|
||||
}
|
||||
|
||||
private static String removeAntdvNextRootImports(String content) {
|
||||
Pattern pattern = Pattern.compile("import\\s+(?:type\\s+)?\\{[^}]*}\\s*from\\s*'antdv-next';?",
|
||||
Pattern.DOTALL);
|
||||
return pattern.matcher(content).replaceAll("");
|
||||
}
|
||||
|
||||
private static void assertRootValueImport(String filePath, String content, String expectedRegex) {
|
||||
assertRootImport(filePath, content, "import\\s*\\{", expectedRegex);
|
||||
}
|
||||
|
||||
private static void assertRootValueImportsSorted(String filePath, String content) {
|
||||
Matcher matcher = ANTDV_NEXT_VALUE_IMPORT_PATTERN.matcher(content);
|
||||
while (matcher.find()) {
|
||||
List<String> actual = Arrays.stream(matcher.group(1).split(","))
|
||||
.map(String::trim)
|
||||
.filter(item -> !item.isEmpty())
|
||||
.map(item -> item.replaceFirst("^.*\\s+as\\s+", ""))
|
||||
.collect(Collectors.toList());
|
||||
List<String> expected = actual.stream().sorted(String.CASE_INSENSITIVE_ORDER)
|
||||
.collect(Collectors.toList());
|
||||
assertEquals(expected, actual, filePath + ":antdv-next 命名导入未按 natural 顺序排列");
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertRangePickerHelperImportMatchesUsage(String filePath, String content) {
|
||||
boolean imported = RANGE_PICKER_HELPER_IMPORT_PATTERN.matcher(content).find();
|
||||
boolean called = RANGE_PICKER_HELPER_CALL_PATTERN.matcher(content).find();
|
||||
assertEquals(called, imported,
|
||||
filePath + ":getRangePickerDefaultProps() 调用与导入不一致");
|
||||
}
|
||||
|
||||
private static void assertRangePickerHelperBindingMatchesComponent(String filePath, String content) {
|
||||
long componentCount = countMatches(RANGE_PICKER_COMPONENT_PATTERN, content);
|
||||
long bindingCount = countMatches(RANGE_PICKER_HELPER_BINDING_PATTERN, content);
|
||||
assertEquals(componentCount, bindingCount,
|
||||
filePath + ":RangePicker 与 getRangePickerDefaultProps() 绑定数量不一致");
|
||||
}
|
||||
|
||||
private static void assertSchemaRangePickerHelperMatchesComponent(String filePath, String content) {
|
||||
long componentCount = countMatches(SCHEMA_RANGE_PICKER_COMPONENT_PATTERN, content);
|
||||
long helperSpreadCount = countMatches(SCHEMA_RANGE_PICKER_HELPER_SPREAD_PATTERN, content);
|
||||
assertEquals(componentCount, helperSpreadCount,
|
||||
filePath + ":Schema RangePicker 与 getRangePickerDefaultProps() 展开数量不一致");
|
||||
}
|
||||
|
||||
private static long countMatches(Pattern pattern, String content) {
|
||||
Matcher matcher = pattern.matcher(content);
|
||||
long count = 0;
|
||||
while (matcher.find()) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static void assertRootImport(String filePath, String content, String importPrefix, String expectedRegex) {
|
||||
Pattern pattern = createRootImportPattern(importPrefix, expectedRegex);
|
||||
assertTrue(pattern.matcher(content).find(), filePath + ":缺少合法的 antdv-next 根导入 " + expectedRegex);
|
||||
}
|
||||
|
||||
private static Pattern createRootImportPattern(String importPrefix, String expectedRegex) {
|
||||
return Pattern.compile(importPrefix + "[^}]*" + expectedRegex
|
||||
+ "[^}]*}\\s*from\\s*'antdv-next';?", Pattern.DOTALL);
|
||||
}
|
||||
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
[ {
|
||||
"contentPath" : "java/InfraStudentPageReqVO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentPageReqVO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentRespVO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentRespVO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentSaveReqVO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/vo/InfraStudentSaveReqVO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentController",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/demo/InfraStudentController.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentDO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentDO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentContactDO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentContactDO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentTeacherDO",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/demo/InfraStudentTeacherDO.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentMapper.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentContactMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentContactMapper.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentTeacherMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/demo/InfraStudentTeacherMapper.java"
|
||||
}, {
|
||||
"contentPath" : "xml/InfraStudentMapper",
|
||||
"filePath" : "yudao-module-infra/src/main/resources/mapper/demo/InfraStudentMapper.xml"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentServiceImpl",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImpl.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentService",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentService.java"
|
||||
}, {
|
||||
"contentPath" : "java/InfraStudentServiceImplTest",
|
||||
"filePath" : "yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/demo/InfraStudentServiceImplTest.java"
|
||||
}, {
|
||||
"contentPath" : "java/ErrorCodeConstants_手动操作",
|
||||
"filePath" : "yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants_手动操作.java"
|
||||
}, {
|
||||
"contentPath" : "sql/sql",
|
||||
"filePath" : "sql/sql.sql"
|
||||
}, {
|
||||
"contentPath" : "sql/h2",
|
||||
"filePath" : "sql/h2.sql"
|
||||
}, {
|
||||
"contentPath" : "ts/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/api/infra/demo/index.ts"
|
||||
}, {
|
||||
"contentPath" : "vue/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/search-form",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/components/search-form.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/form/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/detail/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/student-contact/form/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/student-contact/form/index.vue"
|
||||
}, {
|
||||
"contentPath" : "vue/student-teacher/form/index",
|
||||
"filePath" : "yudao-ui-admin-uniapp/src/pages-infra/demo/student-teacher/form/index.vue"
|
||||
} ]
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// TODO 待办:请将下面的错误码复制到 yudao-module-infra 模块的 ErrorCodeConstants 类中。注意,请给“TODO 补充编号”设置一个错误码编号!!!
|
||||
// ========== 学生 TODO 补充编号 ==========
|
||||
ErrorCode STUDENT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生不存在");
|
||||
ErrorCode STUDENT_CONTACT_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生联系人不存在");
|
||||
ErrorCode STUDENT_TEACHER_NOT_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任不存在");
|
||||
ErrorCode STUDENT_TEACHER_EXISTS = new ErrorCode(TODO 补充编号, "学生班主任已存在");
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
||||
/**
|
||||
* 学生联系人 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_student_contact")
|
||||
@KeySequence("infra_student_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InfraStudentContactDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 学生编号
|
||||
*/
|
||||
private Long studentId;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 是否有效
|
||||
*
|
||||
* 枚举 {@link TODO infra_boolean_string 对应的类}
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String video;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String memo;
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 学生联系人 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface InfraStudentContactMapper extends BaseMapperX<InfraStudentContactDO> {
|
||||
|
||||
default PageResult<InfraStudentContactDO> selectPage(PageParam reqVO, Long studentId) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentContactDO>()
|
||||
.eq(InfraStudentContactDO::getStudentId, studentId)
|
||||
.orderByDesc(InfraStudentContactDO::getId));
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(InfraStudentContactDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(InfraStudentContactDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import jakarta.validation.*;
|
||||
import jakarta.servlet.http.*;
|
||||
import java.util.*;
|
||||
import java.io.IOException;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
|
||||
|
||||
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import cn.iocoder.yudao.module.infra.service.demo.InfraStudentService;
|
||||
|
||||
@Tag(name = "管理后台 - 学生")
|
||||
@RestController
|
||||
@RequestMapping("/infra/student")
|
||||
@Validated
|
||||
public class InfraStudentController {
|
||||
|
||||
@Resource
|
||||
private InfraStudentService studentService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:create')")
|
||||
public CommonResult<Long> createStudent(@Valid @RequestBody InfraStudentSaveReqVO createReqVO) {
|
||||
return success(studentService.createStudent(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:update')")
|
||||
public CommonResult<Boolean> updateStudent(@Valid @RequestBody InfraStudentSaveReqVO updateReqVO) {
|
||||
studentService.updateStudent(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除学生")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudent(@RequestParam("id") Long id) {
|
||||
studentService.deleteStudent(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentList(@RequestParam("ids") List<Long> ids) {
|
||||
studentService.deleteStudentListByIds(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得学生")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<InfraStudentRespVO> getStudent(@RequestParam("id") Long id) {
|
||||
InfraStudentDO student = studentService.getStudent(id);
|
||||
return success(BeanUtils.toBean(student, InfraStudentRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得学生分页")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<PageResult<InfraStudentRespVO>> getStudentPage(@Valid InfraStudentPageReqVO pageReqVO) {
|
||||
PageResult<InfraStudentDO> pageResult = studentService.getStudentPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, InfraStudentRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出学生 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportStudentExcel(@Valid InfraStudentPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<InfraStudentDO> list = studentService.getStudentPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "学生.xls", "数据", InfraStudentRespVO.class,
|
||||
BeanUtils.toBean(list, InfraStudentRespVO.class));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生联系人) ====================
|
||||
|
||||
@GetMapping("/student-contact/page")
|
||||
@Operation(summary = "获得学生联系人分页")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<PageResult<InfraStudentContactDO>> getStudentContactPage(PageParam pageReqVO,
|
||||
@RequestParam("studentId") Long studentId) {
|
||||
return success(studentService.getStudentContactPage(pageReqVO, studentId));
|
||||
}
|
||||
|
||||
@PostMapping("/student-contact/create")
|
||||
@Operation(summary = "创建学生联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:create')")
|
||||
public CommonResult<Long> createStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) {
|
||||
return success(studentService.createStudentContact(studentContact));
|
||||
}
|
||||
|
||||
@PutMapping("/student-contact/update")
|
||||
@Operation(summary = "更新学生联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:update')")
|
||||
public CommonResult<Boolean> updateStudentContact(@Valid @RequestBody InfraStudentContactDO studentContact) {
|
||||
studentService.updateStudentContact(studentContact);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/student-contact/delete")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@Operation(summary = "删除学生联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentContact(@RequestParam("id") Long id) {
|
||||
studentService.deleteStudentContact(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/student-contact/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentContactList(@RequestParam("ids") List<Long> ids) {
|
||||
studentService.deleteStudentContactListByIds(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/student-contact/get")
|
||||
@Operation(summary = "获得学生联系人")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<InfraStudentContactDO> getStudentContact(@RequestParam("id") Long id) {
|
||||
return success(studentService.getStudentContact(id));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生班主任) ====================
|
||||
|
||||
@GetMapping("/student-teacher/page")
|
||||
@Operation(summary = "获得学生班主任分页")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<PageResult<InfraStudentTeacherDO>> getStudentTeacherPage(PageParam pageReqVO,
|
||||
@RequestParam("studentId") Long studentId) {
|
||||
return success(studentService.getStudentTeacherPage(pageReqVO, studentId));
|
||||
}
|
||||
|
||||
@PostMapping("/student-teacher/create")
|
||||
@Operation(summary = "创建学生班主任")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:create')")
|
||||
public CommonResult<Long> createStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) {
|
||||
return success(studentService.createStudentTeacher(studentTeacher));
|
||||
}
|
||||
|
||||
@PutMapping("/student-teacher/update")
|
||||
@Operation(summary = "更新学生班主任")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:update')")
|
||||
public CommonResult<Boolean> updateStudentTeacher(@Valid @RequestBody InfraStudentTeacherDO studentTeacher) {
|
||||
studentService.updateStudentTeacher(studentTeacher);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/student-teacher/delete")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@Operation(summary = "删除学生班主任")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentTeacher(@RequestParam("id") Long id) {
|
||||
studentService.deleteStudentTeacher(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/student-teacher/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生班主任")
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:delete')")
|
||||
public CommonResult<Boolean> deleteStudentTeacherList(@RequestParam("ids") List<Long> ids) {
|
||||
studentService.deleteStudentTeacherListByIds(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/student-teacher/get")
|
||||
@Operation(summary = "获得学生班主任")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:student:query')")
|
||||
public CommonResult<InfraStudentTeacherDO> getStudentTeacher(@RequestParam("id") Long id) {
|
||||
return success(studentService.getStudentTeacher(id));
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
|
||||
/**
|
||||
* 学生 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("infra_student")
|
||||
@KeySequence("infra_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InfraStudentDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 是否有效
|
||||
*
|
||||
* 枚举 {@link TODO infra_boolean_string 对应的类}
|
||||
*/
|
||||
private Boolean enabled;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
/**
|
||||
* 附件
|
||||
*/
|
||||
private String video;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String memo;
|
||||
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.mysql.demo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
|
||||
/**
|
||||
* 学生 Mapper
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Mapper
|
||||
public interface InfraStudentMapper extends BaseMapperX<InfraStudentDO> {
|
||||
|
||||
default PageResult<InfraStudentDO> selectPage(InfraStudentPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<InfraStudentDO>()
|
||||
.likeIfPresent(InfraStudentDO::getName, reqVO.getName())
|
||||
.eqIfPresent(InfraStudentDO::getBirthday, reqVO.getBirthday())
|
||||
.eqIfPresent(InfraStudentDO::getSex, reqVO.getSex())
|
||||
.eqIfPresent(InfraStudentDO::getEnabled, reqVO.getEnabled())
|
||||
.betweenIfPresent(InfraStudentDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(InfraStudentDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
|
||||
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 学生分页 Request VO")
|
||||
@Data
|
||||
public class InfraStudentPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "名字", example = "芋头")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "出生日期")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "性别", example = "1")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否有效", example = "true")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import cn.idev.excel.annotation.*;
|
||||
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
|
||||
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
|
||||
|
||||
@Schema(description = "管理后台 - 学生 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class InfraStudentRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
|
||||
@ExcelProperty("名字")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
|
||||
@ExcelProperty("简介")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("出生日期")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty(value = "性别", converter = DictConvert.class)
|
||||
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
@ExcelProperty(value = "是否有效", converter = DictConvert.class)
|
||||
@DictFormat("infra_boolean_string") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
|
||||
@ExcelProperty("头像")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
|
||||
@ExcelProperty("附件")
|
||||
private String video;
|
||||
|
||||
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
|
||||
@ExcelProperty("备注")
|
||||
private String memo;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.demo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
|
||||
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
|
||||
@Data
|
||||
public class InfraStudentSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋头")
|
||||
@NotEmpty(message = "名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是介绍")
|
||||
@NotEmpty(message = "简介不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "出生日期不能为空")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "性别不能为空")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
|
||||
@NotNull(message = "是否有效不能为空")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
|
||||
@NotEmpty(message = "头像不能为空")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "附件", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.mp4")
|
||||
@NotEmpty(message = "附件不能为空")
|
||||
private String video;
|
||||
|
||||
@Schema(description = "备注", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是备注")
|
||||
@NotEmpty(message = "备注不能为空")
|
||||
private String memo;
|
||||
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package cn.iocoder.yudao.module.infra.service.demo;
|
||||
|
||||
import java.util.*;
|
||||
import jakarta.validation.*;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
|
||||
/**
|
||||
* 学生 Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface InfraStudentService {
|
||||
|
||||
/**
|
||||
* 创建学生
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createStudent(@Valid InfraStudentSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新学生
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateStudent(@Valid InfraStudentSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除学生
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteStudent(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除学生
|
||||
*
|
||||
* @param ids 编号
|
||||
*/
|
||||
void deleteStudentListByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得学生
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 学生
|
||||
*/
|
||||
InfraStudentDO getStudent(Long id);
|
||||
|
||||
/**
|
||||
* 获得学生分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 学生分页
|
||||
*/
|
||||
PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO);
|
||||
|
||||
// ==================== 子表(学生联系人) ====================
|
||||
|
||||
/**
|
||||
* 获得学生联系人分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @param studentId 学生编号
|
||||
* @return 学生联系人分页
|
||||
*/
|
||||
PageResult<InfraStudentContactDO> getStudentContactPage(PageParam pageReqVO, Long studentId);
|
||||
|
||||
/**
|
||||
* 创建学生联系人
|
||||
*
|
||||
* @param studentContact 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createStudentContact(@Valid InfraStudentContactDO studentContact);
|
||||
|
||||
/**
|
||||
* 更新学生联系人
|
||||
*
|
||||
* @param studentContact 更新信息
|
||||
*/
|
||||
void updateStudentContact(@Valid InfraStudentContactDO studentContact);
|
||||
|
||||
/**
|
||||
* 删除学生联系人
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteStudentContact(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除学生联系人
|
||||
*
|
||||
* @param ids 编号
|
||||
*/
|
||||
void deleteStudentContactListByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得学生联系人
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 学生联系人
|
||||
*/
|
||||
InfraStudentContactDO getStudentContact(Long id);
|
||||
|
||||
// ==================== 子表(学生班主任) ====================
|
||||
|
||||
/**
|
||||
* 获得学生班主任分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @param studentId 学生编号
|
||||
* @return 学生班主任分页
|
||||
*/
|
||||
PageResult<InfraStudentTeacherDO> getStudentTeacherPage(PageParam pageReqVO, Long studentId);
|
||||
|
||||
/**
|
||||
* 创建学生班主任
|
||||
*
|
||||
* @param studentTeacher 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher);
|
||||
|
||||
/**
|
||||
* 更新学生班主任
|
||||
*
|
||||
* @param studentTeacher 更新信息
|
||||
*/
|
||||
void updateStudentTeacher(@Valid InfraStudentTeacherDO studentTeacher);
|
||||
|
||||
/**
|
||||
* 删除学生班主任
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteStudentTeacher(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除学生班主任
|
||||
*
|
||||
* @param ids 编号
|
||||
*/
|
||||
void deleteStudentTeacherListByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得学生班主任
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 学生班主任
|
||||
*/
|
||||
InfraStudentTeacherDO getStudentTeacher(Long id);
|
||||
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package cn.iocoder.yudao.module.infra.service.demo;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.demo.vo.*;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentContactDO;
|
||||
import cn.iocoder.yudao.module.infra.dal.dataobject.demo.InfraStudentTeacherDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentMapper;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentContactMapper;
|
||||
import cn.iocoder.yudao.module.infra.dal.mysql.demo.InfraStudentTeacherMapper;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList;
|
||||
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 学生 Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
public class InfraStudentServiceImpl implements InfraStudentService {
|
||||
|
||||
@Resource
|
||||
private InfraStudentMapper studentMapper;
|
||||
@Resource
|
||||
private InfraStudentContactMapper studentContactMapper;
|
||||
@Resource
|
||||
private InfraStudentTeacherMapper studentTeacherMapper;
|
||||
|
||||
@Override
|
||||
public Long createStudent(InfraStudentSaveReqVO createReqVO) {
|
||||
// 插入
|
||||
InfraStudentDO student = BeanUtils.toBean(createReqVO, InfraStudentDO.class);
|
||||
studentMapper.insert(student);
|
||||
|
||||
// 返回
|
||||
return student.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStudent(InfraStudentSaveReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateStudentExists(updateReqVO.getId());
|
||||
// 更新
|
||||
InfraStudentDO updateObj = BeanUtils.toBean(updateReqVO, InfraStudentDO.class);
|
||||
studentMapper.updateById(updateObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteStudent(Long id) {
|
||||
// 校验存在
|
||||
validateStudentExists(id);
|
||||
// 删除
|
||||
studentMapper.deleteById(id);
|
||||
|
||||
// 删除子表
|
||||
deleteStudentContactByStudentId(id);
|
||||
deleteStudentTeacherByStudentId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteStudentListByIds(List<Long> ids) {
|
||||
// 删除
|
||||
studentMapper.deleteByIds(ids);
|
||||
|
||||
// 删除子表
|
||||
deleteStudentContactByStudentIds(ids);
|
||||
deleteStudentTeacherByStudentIds(ids);
|
||||
}
|
||||
|
||||
|
||||
private void validateStudentExists(Long id) {
|
||||
if (studentMapper.selectById(id) == null) {
|
||||
throw exception(STUDENT_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfraStudentDO getStudent(Long id) {
|
||||
return studentMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<InfraStudentDO> getStudentPage(InfraStudentPageReqVO pageReqVO) {
|
||||
return studentMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
// ==================== 子表(学生联系人) ====================
|
||||
|
||||
@Override
|
||||
public PageResult<InfraStudentContactDO> getStudentContactPage(PageParam pageReqVO, Long studentId) {
|
||||
return studentContactMapper.selectPage(pageReqVO, studentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createStudentContact(InfraStudentContactDO studentContact) {
|
||||
studentContact.clean(); // 清理掉创建、更新时间等相关属性值
|
||||
studentContactMapper.insert(studentContact);
|
||||
return studentContact.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStudentContact(InfraStudentContactDO studentContact) {
|
||||
// 校验存在
|
||||
validateStudentContactExists(studentContact.getId());
|
||||
// 更新
|
||||
studentContact.clean(); // 解决更新情况下:updateTime 不更新
|
||||
studentContactMapper.updateById(studentContact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStudentContact(Long id) {
|
||||
// 删除
|
||||
studentContactMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStudentContactListByIds(List<Long> ids) {
|
||||
// 删除
|
||||
studentContactMapper.deleteByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfraStudentContactDO getStudentContact(Long id) {
|
||||
return studentContactMapper.selectById(id);
|
||||
}
|
||||
|
||||
private void validateStudentContactExists(Long id) {
|
||||
if (studentContactMapper.selectById(id) == null) {
|
||||
throw exception(STUDENT_CONTACT_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteStudentContactByStudentId(Long studentId) {
|
||||
studentContactMapper.deleteByStudentId(studentId);
|
||||
}
|
||||
|
||||
private void deleteStudentContactByStudentIds(List<Long> studentIds) {
|
||||
studentContactMapper.deleteByStudentIds(studentIds);
|
||||
}
|
||||
|
||||
// ==================== 子表(学生班主任) ====================
|
||||
|
||||
@Override
|
||||
public PageResult<InfraStudentTeacherDO> getStudentTeacherPage(PageParam pageReqVO, Long studentId) {
|
||||
return studentTeacherMapper.selectPage(pageReqVO, studentId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createStudentTeacher(InfraStudentTeacherDO studentTeacher) {
|
||||
// 校验是否已经存在
|
||||
if (studentTeacherMapper.selectByStudentId(studentTeacher.getStudentId()) != null) {
|
||||
throw exception(STUDENT_TEACHER_EXISTS);
|
||||
}
|
||||
// 插入
|
||||
studentTeacher.clean(); // 清理掉创建、更新时间等相关属性值
|
||||
studentTeacherMapper.insert(studentTeacher);
|
||||
return studentTeacher.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStudentTeacher(InfraStudentTeacherDO studentTeacher) {
|
||||
// 校验存在
|
||||
validateStudentTeacherExists(studentTeacher.getId());
|
||||
// 更新
|
||||
studentTeacher.clean(); // 解决更新情况下:updateTime 不更新
|
||||
studentTeacherMapper.updateById(studentTeacher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStudentTeacher(Long id) {
|
||||
// 删除
|
||||
studentTeacherMapper.deleteById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteStudentTeacherListByIds(List<Long> ids) {
|
||||
// 删除
|
||||
studentTeacherMapper.deleteByIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfraStudentTeacherDO getStudentTeacher(Long id) {
|
||||
return studentTeacherMapper.selectById(id);
|
||||
}
|
||||
|
||||
private void validateStudentTeacherExists(Long id) {
|
||||
if (studentTeacherMapper.selectById(id) == null) {
|
||||
throw exception(STUDENT_TEACHER_NOT_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteStudentTeacherByStudentId(Long studentId) {
|
||||
studentTeacherMapper.deleteByStudentId(studentId);
|
||||
}
|
||||
|
||||
private void deleteStudentTeacherByStudentIds(List<Long> studentIds) {
|
||||
studentTeacherMapper.deleteByStudentIds(studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user