diff --git a/pom.xml b/pom.xml index 8456059d8..2d5746c3a 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ 8.7.2-20250101 - 1.7.3 + 1.7.4 3.2.2 diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java index 4903c3860..271c46bcb 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/UserService.java @@ -52,6 +52,14 @@ public interface UserService { */ String selectEmailById(Long userId); + /** + * 通过用户ID查询用户详细信息 + * + * @param userId 用户id + * @return 用户详细信息 + */ + UserDTO selectUserDtoById(Long userId); + /** * 通过用户ID查询用户列表 * diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java index cc2cc88a9..6b41b8e4f 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java @@ -623,6 +623,23 @@ public class SysUserServiceImpl implements ISysUserService, UserService { return ObjectUtils.notNullGetter(sysUser, SysUser::getEmail); } + /** + * 通过用户ID查询用户详细信息 + * + * @param userId 用户id + * @return 用户详细信息 + */ + @Override + public UserDTO selectUserDtoById(Long userId) { + SysUser sysUser = baseMapper.selectOne(new LambdaQueryWrapper() + .select(SysUser::getUserId, SysUser::getDeptId, SysUser::getUserName, + SysUser::getNickName, SysUser::getUserType, SysUser::getEmail, + SysUser::getPhonenumber, SysUser::getSex, SysUser::getStatus, + SysUser::getCreateTime) + .eq(SysUser::getUserId, userId)); + return BeanUtil.toBean(sysUser, UserDTO.class); + } + /** * 通过用户ID查询用户列表 * @@ -635,7 +652,10 @@ public class SysUserServiceImpl implements ISysUserService, UserService { return List.of(); } List list = baseMapper.selectVoList(new LambdaQueryWrapper() - .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getEmail, SysUser::getPhonenumber) + .select(SysUser::getUserId, SysUser::getDeptId, SysUser::getUserName, + SysUser::getNickName, SysUser::getUserType, SysUser::getEmail, + SysUser::getPhonenumber, SysUser::getSex, SysUser::getStatus, + SysUser::getCreateTime) .eq(SysUser::getStatus, SystemConstants.NORMAL) .in(SysUser::getUserId, userIds)); return BeanUtil.copyToList(list, UserDTO.class); @@ -676,7 +696,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService { // 获取用户ID列表 Set userIds = StreamUtils.toSet(userRoles, SysUserRole::getUserId); - return selectListByIds(new ArrayList<>(userIds)); + return this.selectListByIds(new ArrayList<>(userIds)); } /** @@ -716,7 +736,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService { // 获取用户ID列表 Set userIds = StreamUtils.toSet(userPosts, SysUserPost::getUserId); - return selectListByIds(new ArrayList<>(userIds)); + return this.selectListByIds(new ArrayList<>(userIds)); } /** diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java new file mode 100644 index 000000000..e1f8f11a7 --- /dev/null +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwChartExtServiceImpl.java @@ -0,0 +1,194 @@ +package org.dromara.workflow.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.domain.dto.UserDTO; +import org.dromara.common.core.service.DeptService; +import org.dromara.common.core.service.UserService; +import org.dromara.common.core.utils.DateUtils; +import org.dromara.warm.flow.core.dto.DefJson; +import org.dromara.warm.flow.core.dto.NodeJson; +import org.dromara.warm.flow.core.dto.PromptContent; +import org.dromara.warm.flow.core.enums.NodeType; +import org.dromara.warm.flow.orm.entity.FlowHisTask; +import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper; +import org.dromara.warm.flow.ui.service.ChartExtService; +import org.dromara.workflow.common.ConditionalOnEnable; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 流程图提示信息 + * + * @author AprilWind + */ +@ConditionalOnEnable +@Slf4j +@RequiredArgsConstructor +@Service +public class FlwChartExtServiceImpl implements ChartExtService { + + /** + * 悬浮窗整体样式(支持滚动) + */ + public static final Map DIALOG_STYLE = Map.ofEntries( + Map.entry("position", "absolute"), + Map.entry("backgroundColor", "#fff"), + Map.entry("border", "1px solid #ccc"), + Map.entry("borderRadius", "4px"), + Map.entry("boxShadow", "0 2px 8px rgba(0, 0, 0, 0.15)"), + Map.entry("padding", "8px 12px"), + Map.entry("fontSize", "14px"), + Map.entry("zIndex", 1000), + Map.entry("maxWidth", "500px"), + // 取消 maxHeight,让高度自适应 + Map.entry("overflowY", "visible"), + Map.entry("overflowX", "hidden"), + Map.entry("color", "#333"), + Map.entry("pointerEvents", "auto"), + Map.entry("scrollbarWidth", "thin") + ); + public static final Map PREFIX_STYLE = Map.of( + "textAlign", "right", + "color", "#444", + "userSelect", "none", + "display", "inline-block", + "width", "100px", + "paddingRight", "8px", + "fontWeight", "500", + "fontSize", "14px", + "lineHeight", "24px", + "verticalAlign", "middle" + ); + public static final Map CONTENT_STYLE = Map.of( + "backgroundColor", "#f7faff", + "color", "#005cbf", + "padding", "4px 8px", + "fontSize", "14px", + "borderRadius", "4px", + "whiteSpace", "normal", + "border", "1px solid #d0e5ff", + "userSelect", "text", + "lineHeight", "20px" + ); + public static final Map ROW_STYLE = Map.of( + "color", "#222", + "alignItems", "center", + "display", "flex", + "marginBottom", "6px", + "fontWeight", "400", + "fontSize", "14px" + ); + + private final UserService userService; + private final DeptService deptService; + private final FlowHisTaskMapper flowHisTaskMapper; + + /** + * 设置流程图提示信息 + * + * @param defJson 流程定义json对象 + */ + @Override + public void execute(DefJson defJson) { + //TODO 等待下一版本更新传递 流程实例id + Long instanceId = 1935591874325151746L; + // 按 nodeCode 分组的历史任务列表 + Map> groupedByNode = this.getHisTaskGroupedByNode(instanceId); + + // 遍历每个节点,处理扩展提示内容 + for (NodeJson nodeJson : defJson.getNodeList()) { + List taskList = groupedByNode.getOrDefault(nodeJson.getNodeCode(), Collections.emptyList()); + this.processNodeExtInfo(nodeJson, taskList); + } + } + + /** + * 初始化流程图提示信息 + * + * @param defJson 流程定义json对象 + */ + @Override + public void initPromptContent(DefJson defJson) { + ChartExtService.super.initPromptContent(defJson); + // 为每个节点设置统一的提示框样式 + defJson.getNodeList().forEach(nodeJson -> nodeJson.getPromptContent().setDialogStyle(DIALOG_STYLE)); + } + + /** + * 处理每个节点的扩展信息,生成提示内容 + * + * @param nodeJson 当前节点 + */ + private void processNodeExtInfo(NodeJson nodeJson, List taskList) { + if (CollUtil.isEmpty(taskList)) { + return; + } + List info = nodeJson.getPromptContent().getInfo(); + for (FlowHisTask task : taskList) { + UserDTO userDTO = userService.selectUserDtoById(Long.valueOf(task.getApprover())); + if (ObjectUtil.isEmpty(userDTO)) { + return; + } + String deptName = deptService.selectDeptNameByIds(String.valueOf(userDTO.getDeptId())); + String displayName = String.format("👤 %s(%s)", userDTO.getNickName(), deptName); + + info.add(new PromptContent.InfoItem() + .setPrefix(displayName) + .setPrefixStyle(Map.of( + "fontWeight", "bold", + "fontSize", "15px", + "color", "#333" + )) + .setContent("") + .setContentStyle(Collections.emptyMap()) + .setRowStyle(Map.of("margin", "8px 0", "borderBottom", "1px dashed #ccc")) + ); + info.add(buildInfoItem("用户账号", userDTO.getUserName())); + info.add(buildInfoItem("审批耗时", DateUtils.getTimeDifference(task.getUpdateTime(), task.getCreateTime()))); + info.add(buildInfoItem("办理时间", DateUtils.formatDateTime(task.getUpdateTime()))); + } + } + + /** + * 构建单条提示内容对象 InfoItem,用于悬浮窗显示(key: value) + * + * @param key 字段名(作为前缀) + * @param value 字段值 + * @return 提示项对象 + */ + private PromptContent.InfoItem buildInfoItem(String key, String value) { + return new PromptContent.InfoItem() + .setPrefix(key + ": ") + .setPrefixStyle(PREFIX_STYLE) + .setContent(value) + .setContentStyle(CONTENT_STYLE) + .setRowStyle(ROW_STYLE); + } + + /** + * 根据流程实例ID获取按节点编号分组的历史任务列表 + * + * @param instanceId 流程实例ID + * @return Map<节点编码, 对应的历史任务列表> + */ + public Map> getHisTaskGroupedByNode(Long instanceId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(FlowHisTask::getInstanceId, instanceId); + wrapper.eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey()); + wrapper.orderByDesc(FlowHisTask::getCreateTime).orderByDesc(FlowHisTask::getUpdateTime); + List flowHisTasks = flowHisTaskMapper.selectList(wrapper); + + return flowHisTasks.stream() + .collect(Collectors.groupingBy(FlowHisTask::getNodeCode)); + } + +} diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java index 8a2f72380..71ce177aa 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java @@ -530,7 +530,10 @@ public class FlwTaskServiceImpl implements IFlwTaskService { // 构建以下节点数据 List buildNextTaskList = StreamUtils.toList(nextNodeList, node -> taskService.addTask(node, instance, definition, FlowParams.build())); // 办理人变量替换 - ExpressionUtil.evalVariable(buildNextTaskList, mergeVariable); + ExpressionUtil.evalVariable(buildNextTaskList, + FlowParams.build() + .variable(mergeVariable) + ); for (FlowNode flowNode : nextFlowNodes) { buildNextTaskList.stream().filter(t -> t.getNodeCode().equals(flowNode.getNodeCode())).findFirst().ifPresent(t -> { if (CollUtil.isNotEmpty(t.getPermissionList())) { diff --git a/script/sql/ry_workflow.sql b/script/sql/ry_workflow.sql index 5e055d2ed..20b96dfa6 100644 --- a/script/sql/ry_workflow.sql +++ b/script/sql/ry_workflow.sql @@ -29,7 +29,7 @@ CREATE TABLE `flow_node` `definition_id` bigint NOT NULL COMMENT '流程定义id', `node_code` varchar(100) NOT NULL COMMENT '流程节点编码', `node_name` varchar(100) DEFAULT NULL COMMENT '流程节点名称', - `permission_flag` varchar(200) DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用逗号隔开)', + `permission_flag` varchar(200) DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用@@隔开)', `node_ratio` decimal(6, 3) DEFAULT NULL COMMENT '流程签署比例值', `coordinate` varchar(100) DEFAULT NULL COMMENT '坐标', `any_node_skip` varchar(100) DEFAULT NULL COMMENT '任意结点跳转', @@ -42,7 +42,7 @@ CREATE TABLE `flow_node` `version` varchar(20) NOT NULL COMMENT '版本', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `ext` text COMMENT '扩展属性', + `ext` text COMMENT '节点扩展属性', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', `tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id', PRIMARY KEY (`id`) USING BTREE @@ -96,7 +96,7 @@ CREATE TABLE `flow_task` `node_code` varchar(100) NOT NULL COMMENT '节点编码', `node_name` varchar(100) DEFAULT NULL COMMENT '节点名称', `node_type` tinyint(1) NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', - `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', + `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', `form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)', `form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径', `create_time` datetime DEFAULT NULL COMMENT '创建时间', @@ -108,25 +108,25 @@ CREATE TABLE `flow_task` CREATE TABLE `flow_his_task` ( - `id` bigint(20) NOT NULL COMMENT '主键id', - `definition_id` bigint(20) NOT NULL COMMENT '对应flow_definition表的id', - `instance_id` bigint(20) NOT NULL COMMENT '对应flow_instance表的id', - `task_id` bigint(20) NOT NULL COMMENT '对应flow_task表的id', + `id` bigint(20) NOT NULL COMMENT '主键id', + `definition_id` bigint(20) NOT NULL COMMENT '对应flow_definition表的id', + `instance_id` bigint(20) NOT NULL COMMENT '对应flow_instance表的id', + `task_id` bigint(20) NOT NULL COMMENT '对应flow_task表的id', `node_code` varchar(100) DEFAULT NULL COMMENT '开始节点编码', `node_name` varchar(100) DEFAULT NULL COMMENT '开始节点名称', `node_type` tinyint(1) DEFAULT NULL COMMENT '开始节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', `target_node_code` varchar(200) DEFAULT NULL COMMENT '目标节点编码', `target_node_name` varchar(200) DEFAULT NULL COMMENT '结束节点名称', `approver` varchar(40) DEFAULT NULL COMMENT '审批者', - `cooperate_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)', + `cooperate_type` tinyint(1) NOT NULL DEFAULT '0' COMMENT '协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)', `collaborator` varchar(40) DEFAULT NULL COMMENT '协作人', - `skip_type` varchar(10) NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)', - `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', + `skip_type` varchar(10) NOT NULL COMMENT '流转类型(PASS通过 REJECT退回 NONE无动作)', + `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', `form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)', `form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径', `message` varchar(500) DEFAULT NULL COMMENT '审批意见', `variable` TEXT DEFAULT NULL COMMENT '任务变量', - `ext` varchar(500) DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串', + `ext` TEXT DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串', `create_time` datetime DEFAULT NULL COMMENT '任务开始时间', `update_time` datetime DEFAULT NULL COMMENT '审批完成时间', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志',