zxl
2025-12-11 ad7cbfd8fd19fcce4345a4907c1f059a34c2869b
工作台
20个文件已修改
7个文件已添加
1341 ■■■■ 已修改文件
business/src/main/java/com/ycl/controller/WorkStationScheduleController.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/entity/WorkStationSchedule.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/form/AuditHistoryForm.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/form/WorkStationScheduleForm.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/query/AuditHistoryQuery.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/query/WaitTodoQuery.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/query/WorkStationScheduleQuery.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/vo/AuditHistoryVO.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/vo/DailyStatVO.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/vo/TaskInfoVo.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/vo/WorkStationScheduleVO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/mapper/AuditHistoryMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/mapper/WorkStationScheduleMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/AuditHistoryService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/IndexHomeService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/WorkStationScheduleService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/impl/AuditHistoryServiceImpl.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/impl/FlowTaskServiceImpl.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/impl/IndexHomeServiceImpl.java 510 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/impl/ProjectProcessServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/impl/WorkStationScheduleServiceImpl.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/resources/mapper/AuditHistoryMapper.xml 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/resources/mapper/WorkStationScheduleMapper.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
common/src/main/java/com/ycl/common/enums/business/AuditHistoryEnum.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
common/src/main/java/com/ycl/common/enums/business/AuditTypeEnum.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
common/src/main/java/com/ycl/common/enums/business/WorkStationEnum.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
common/src/main/java/com/ycl/common/utils/DateUtils.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/controller/WorkStationScheduleController.java
@@ -2,10 +2,18 @@
import com.ycl.common.group.Update;
import com.ycl.common.group.Add;
import com.ycl.domain.form.ProjectInfoForm;
import com.ycl.domain.query.AuditHistoryQuery;
import com.ycl.domain.query.ProjectInfoQuery;
import com.ycl.domain.query.WaitTodoQuery;
import com.ycl.service.AuditHistoryService;
import com.ycl.service.IndexHomeService;
import com.ycl.system.domain.base.AbsQuery;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import lombok.RequiredArgsConstructor;
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotEmpty;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -29,6 +37,15 @@
public class WorkStationScheduleController {
    private final WorkStationScheduleService workStationScheduleService;
    private final IndexHomeService indexHomeService;
    private final AuditHistoryService auditHistoryService;
    @GetMapping("/auditHistoryPage")
    public Result page(AuditHistoryQuery query){
        return auditHistoryService.page(query);
    }
    @PostMapping
    @ApiOperation(value = "添加", notes = "添加")
@@ -54,10 +71,10 @@
        return workStationScheduleService.remove(ids);
    }
    @GetMapping("/page")
    @GetMapping("/listByDate")
    @ApiOperation(value = "分页", notes = "分页")
    public Result page(WorkStationScheduleQuery query) {
        return workStationScheduleService.page(query);
    public Result getListByDate(WorkStationScheduleQuery query) {
        return workStationScheduleService.listByDate(query);
    }
    @GetMapping("/{id}")
@@ -81,4 +98,23 @@
    public Result countAchievements(){
        return workStationScheduleService.countAchievements();
    }
    /**
     * 获得个人项目列表
     * @return
     */
    @GetMapping("/getProjectList")
    public Result projectList(){
        return indexHomeService.getProjectList();
    }
    @GetMapping("/getWaitTaskList")
    public Result getWaitTaskList(WaitTodoQuery query){
        return indexHomeService.getWaitTaskList(query);
    }
    @GetMapping("/countTodayTask")
    Result countTodayTask(){
        return workStationScheduleService.countTodayTask();
    }
}
business/src/main/java/com/ycl/domain/entity/WorkStationSchedule.java
@@ -36,4 +36,7 @@
    @TableField("completed_time")
    private Date completedTime;
    @TableField("project_id")
    private Integer projectId;
}
business/src/main/java/com/ycl/domain/form/AuditHistoryForm.java
New file
@@ -0,0 +1,65 @@
package com.ycl.domain.form;
import com.ycl.domain.entity.AuditHistory;
import com.ycl.domain.entity.WorkStationSchedule;
import com.ycl.system.domain.base.AbsForm;
import lombok.Data;
import org.springframework.beans.BeanUtils;
import org.springframework.lang.NonNull;
/**
 * nongtou-project-java
 *
 * @author : zxl
 * @date : 2025-12-11 10:19
 **/
@Data
public class AuditHistoryForm extends AbsForm {
    /**
     * 任务id
     */
    private String taskId;
    /**
     * 任务key
     */
    private String taskDefinitionKey;
    /**
     * 查看人id
     */
    private String viewer;
    /**
     * 提交人部门
     */
    private String commitDept;
    /**
     * 1:提交,2:审核,3:驳回,4:转交
     * @see com.ycl.common.enums.business.AuditTypeEnum
     */
    private String auditType;
    /**
     * 是否阅读(0:未读,1:已读)
     */
    private String isRead;
    /**
     * 业务id
     */
    private String businessKey;
    /**
     * 业务表
     */
    private String businessTable;
    /**
     * 内容(节点名称)
     */
    private String content;
    public static AuditHistory getEntityByForm(@NonNull AuditHistoryForm form, AuditHistory entity) {
        if(entity == null) {
            entity = new AuditHistory();
        }
        BeanUtils.copyProperties(form, entity);
        return entity;
    }
}
business/src/main/java/com/ycl/domain/form/WorkStationScheduleForm.java
@@ -1,5 +1,6 @@
package com.ycl.domain.form;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ycl.common.group.Update;
import com.ycl.common.group.Add;
import com.ycl.system.domain.base.AbsForm;
@@ -23,25 +24,15 @@
@ApiModel(value = "WorkStationSchedule表单", description = "表单")
public class WorkStationScheduleForm extends AbsForm {
    @NotNull(message = "所属用户id不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("所属用户id")
    private Long userId;
    @NotBlank(message = "工作内容不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("工作内容")
    private String content;
    @NotBlank(message = "状态不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("状态")
    private String status;
    @NotNull(message = "不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("")
    private Date gmtcreate;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date completedTime;
    @NotNull(message = "不能为空", groups = {Add.class, Update.class})
    @ApiModelProperty("")
    private Date gmtupdate;
    private Integer projectId;
    public static WorkStationSchedule getEntityByForm(@NonNull WorkStationScheduleForm form, WorkStationSchedule entity) {
        if(entity == null) {
business/src/main/java/com/ycl/domain/query/AuditHistoryQuery.java
New file
@@ -0,0 +1,24 @@
package com.ycl.domain.query;
import com.ycl.system.domain.base.AbsQuery;
import lombok.Data;
import java.util.List;
/**
 * nongtou-project-java
 *
 * @author : zxl
 * @date : 2025-12-11 11:59
 **/
@Data
public class AuditHistoryQuery extends AbsQuery {
    private String projectId;
    private Long deptId;
    private List<Long> projectIds;
}
business/src/main/java/com/ycl/domain/query/WaitTodoQuery.java
New file
@@ -0,0 +1,16 @@
package com.ycl.domain.query;
import com.ycl.system.domain.base.AbsQuery;
import lombok.Data;
/**
 * nongtou-project-java
 *
 * @author : zxl
 * @date : 2025-12-09 10:15
 **/
@Data
public class WaitTodoQuery extends AbsQuery {
    private String id; //all查询全部
}
business/src/main/java/com/ycl/domain/query/WorkStationScheduleQuery.java
@@ -1,8 +1,12 @@
package com.ycl.domain.query;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ycl.system.domain.base.AbsQuery;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
/**
 * 查询
@@ -13,5 +17,10 @@
@Data
@ApiModel(value = "WorkStationSchedule查询参数", description = "查询参数")
public class WorkStationScheduleQuery extends AbsQuery {
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date completedTime;
    private String projectId; //查询 全部 传入的字符串 "all"
}
business/src/main/java/com/ycl/domain/vo/AuditHistoryVO.java
New file
@@ -0,0 +1,90 @@
package com.ycl.domain.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * nongtou-project-java
 *
 * @author : zxl
 * @date : 2025-12-11 14:00
 **/
@Data
public class AuditHistoryVO {
    /**
     * 流程任务ID
     */
    @ApiModelProperty(value = "流程任务ID")
    private String taskId;
    /**
     * 任务key
     */
    @ApiModelProperty(value = "任务key")
    private String taskDefinitionKey;
    /**
     * 查看人ID
     */
    @ApiModelProperty(value = "查看人ID")
    private String viewer;
    /**
     * 提交人部门
     */
    @ApiModelProperty(value = "提交人部门")
    private String commitDept;
    /**
     * 审核类型(1:提交,2:审核,3:驳回,4:转交)
     */
    @ApiModelProperty(value = "审核类型(1:提交,2:审核,3:驳回,4:转交)")
    private String auditType;
    /**
     * 是否阅读(0:未读,1:已读)
     */
    @ApiModelProperty(value = "是否阅读(0:未读,1:已读)")
    private String isRead;
    /**
     * 业务ID(关联项目ID)
     */
    @ApiModelProperty(value = "业务ID(关联项目ID)")
    private String businessKey;
    /**
     * 业务表名称
     */
    @ApiModelProperty(value = "业务表名称")
    private String businessTable;
    /**
     * 内容(节点名称)
     */
    @ApiModelProperty(value = "内容(节点名称)")
    private String content;
    /**
     * 关联的项目名称(从t_project_info表关联)
     */
    @ApiModelProperty(value = "项目名称")
    private String projectName;
    // ========== 可选扩展字段(根据业务需求添加) ==========
    /**
     * 审核类型中文描述(前端展示用,如:提交/审核/驳回/转交)
     */
    @ApiModelProperty(value = "审核类型中文描述")
    private String auditTypeDesc;
    /**
     * 是否阅读中文描述(前端展示用,如:未读/已读)
     */
    @ApiModelProperty(value = "是否阅读中文描述")
    private String isReadDesc;
}
business/src/main/java/com/ycl/domain/vo/DailyStatVO.java
New file
@@ -0,0 +1,27 @@
package com.ycl.domain.vo;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
 * nongtou-project-java
 * 每日统计结果封装类
 * @author : zxl
 * @date : 2025-12-10 13:57
 **/
@Data
public class DailyStatVO {
    // 当日数据列表
    private Long id;
    private String date;
    private String time;
    private String title;
    private String project;
    private Date completedTime;
    private Integer projectId;
}
business/src/main/java/com/ycl/domain/vo/TaskInfoVo.java
@@ -22,5 +22,7 @@
    private Long overTimeCount;//超时次数
    private CheckPointVO checkPointInfo;
    private String status;
    private CustomerTaskVO customerTaskInfo;
}
business/src/main/java/com/ycl/domain/vo/WorkStationScheduleVO.java
@@ -31,13 +31,11 @@
    @ApiModelProperty("状态")
    private String status;
    /**  */
    @ApiModelProperty("")
    private Date gmtcreate;
    private Date completedTime;
    /**  */
    @ApiModelProperty("")
    private Date gmtupdate;
    private Integer projectId;
    private String projectName;
    public static WorkStationScheduleVO getVoByEntity(@NonNull WorkStationSchedule entity, WorkStationScheduleVO vo) {
        if(vo == null) {
business/src/main/java/com/ycl/mapper/AuditHistoryMapper.java
@@ -1,9 +1,13 @@
package com.ycl.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ycl.domain.entity.AuditHistory;
import com.ycl.domain.entity.File;
import com.ycl.domain.query.AuditHistoryQuery;
import com.ycl.domain.query.WorkStationScheduleQuery;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * 审核记录 Mapper 接口
@@ -12,5 +16,9 @@
@Mapper
public interface AuditHistoryMapper extends BaseMapper<AuditHistory> {
    /**
     *  分页
     */
    IPage getPage(IPage page, @Param("query") AuditHistoryQuery query);
}
business/src/main/java/com/ycl/mapper/WorkStationScheduleMapper.java
@@ -5,8 +5,13 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ycl.domain.vo.WorkStationScheduleVO;
import com.ycl.domain.query.WorkStationScheduleQuery;
import lombok.Data;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
 *  Mapper 接口
@@ -29,4 +34,9 @@
    */
    IPage getPage(IPage page, @Param("query") WorkStationScheduleQuery query);
    List<WorkStationScheduleVO> groupByProjectAndDate(
            @Param("userId") Long userId,
            @Param("projectId") Integer projectId,
            @Param("startTime") Date startTime,
            @Param("endTime") Date endTime);
}
business/src/main/java/com/ycl/service/AuditHistoryService.java
@@ -1,12 +1,18 @@
package com.ycl.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ycl.common.base.Result;
import com.ycl.domain.entity.AuditHistory;
import com.ycl.domain.form.AuditHistoryForm;
import com.ycl.domain.query.AuditHistoryQuery;
/**
 * 审核记录 服务类
 */
public interface AuditHistoryService extends IService<AuditHistory> {
    public Result add(AuditHistoryForm auditHistory);
    public Result page(AuditHistoryQuery query);
}
business/src/main/java/com/ycl/service/IndexHomeService.java
@@ -2,7 +2,12 @@
import com.ycl.common.base.Result;
import com.ycl.domain.entity.ProjectInfo;
import com.ycl.domain.form.ProjectProgressStatisticsForm;
import com.ycl.domain.query.WaitTodoQuery;
import com.ycl.system.domain.base.AbsQuery;
import java.util.List;
/**
 * 首页 服务类
@@ -21,6 +26,8 @@
     */
    Result projectStageCount();
    List<ProjectInfo> getLoginUserOwnProjectInfo();
    /**
     * 项目进度统计
     * @return
@@ -33,6 +40,11 @@
     */
    Result projectFundingStatus();
    /**
     * 获得待处理任务
     * @return
     */
    int countWaitTask();
    /**
     * 项目推进卡点
@@ -40,5 +52,15 @@
     */
    Result projectAdvanceCheckPoint();
    Result getWaitTaskList(WaitTodoQuery query);
    /**
     * 获得项目列表
     * @return
     */
    Result getProjectList();
    Result getProjectSelectList();
}
business/src/main/java/com/ycl/service/WorkStationScheduleService.java
@@ -51,6 +51,12 @@
    Result page(WorkStationScheduleQuery query);
    /**
     * 查询传入时间月份内每天日程
     * @param query
     * @return
     */
    Result listByDate(WorkStationScheduleQuery query);
    /**
     * 根据id查找
     * @param id
     * @return
@@ -64,6 +70,15 @@
    Result all();
    Result completedAchievements(Integer id);
    /**
     * 获得今成就统计
     * @return
     */
    Result countAchievements();
    /**
     * 获得今日任务统计
     * @return
     */
    Result countTodayTask();
}
business/src/main/java/com/ycl/service/impl/AuditHistoryServiceImpl.java
@@ -1,15 +1,30 @@
package com.ycl.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ycl.common.base.Result;
import com.ycl.common.utils.SecurityUtils;
import com.ycl.domain.entity.AuditHistory;
import com.ycl.domain.entity.ProjectInfo;
import com.ycl.domain.form.AuditHistoryForm;
import com.ycl.domain.query.AuditHistoryQuery;
import com.ycl.domain.vo.AuditHistoryVO;
import com.ycl.framework.utils.PageUtil;
import com.ycl.mapper.AuditHistoryMapper;
import com.ycl.mapper.ProjectInfoMapper;
import com.ycl.service.AuditHistoryService;
import com.ycl.service.IndexHomeService;
import com.ycl.system.mapper.SysDeptMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
 * 审核记录 服务实现类
@@ -19,4 +34,61 @@
@RequiredArgsConstructor
public class AuditHistoryServiceImpl extends ServiceImpl<AuditHistoryMapper, AuditHistory> implements AuditHistoryService {
    private final AuditHistoryMapper auditHistoryMapper;
    private final ProjectInfoMapper projectInfoMapper;
    private final SysDeptMapper sysDeptMapper;
    @Override
    public Result add(AuditHistoryForm auditHistoryForm) {
        AuditHistory entity = AuditHistoryForm.getEntityByForm(auditHistoryForm, null);
        baseMapper.insert(entity);
        return Result.ok("添加成功");
    }
    public List<ProjectInfo> getLoginUserOwnProjectInfo(){
        //权限控制
        Long userId = SecurityUtils.getUserId();
        List<ProjectInfo> list;
        if (SecurityUtils.isAdmin(userId)){
            //查询全部
            list = new LambdaQueryChainWrapper<>(projectInfoMapper)
                    .eq(ProjectInfo::getDeleted, Boolean.FALSE)
                    .eq(ProjectInfo::getUsedStatus, 2)//审核通过
                    .orderBy(true, true, ProjectInfo::getId)
                    .list();
        }else{
            String ancestors = sysDeptMapper.selectAncestors(userId);
            String[] ancestorArr = ancestors.split(",");
            List<String> ancestorList = Arrays.stream(ancestorArr).collect(Collectors.toList());
            ancestorList.add(SecurityUtils.getDeptId() + "");
            //获得本单位以及其子单位deptId;
            list = new LambdaQueryChainWrapper<>(projectInfoMapper)
                    .eq(ProjectInfo::getDeleted, Boolean.FALSE)
                    .eq(ProjectInfo::getUsedStatus, 2) //审核通过
                    .in(ProjectInfo::getProjectOwnerUnit, ancestorList)
                    .orderBy(true, true, ProjectInfo::getId)
                    .list();
        }
        return list;
    }
    @Override
    public Result page(AuditHistoryQuery query) {
        //需要查询到指定项目的日志
        List<Long> ids = new ArrayList<>();
        if ("all".equals(query.getProjectId())){
            List<ProjectInfo> loginUserOwnProjectInfo = getLoginUserOwnProjectInfo();
            if (CollectionUtils.isEmpty(loginUserOwnProjectInfo)) {
                //返回默认值
                return Result.ok().data(null).total(0);
            }
            ids = loginUserOwnProjectInfo.stream().map(ProjectInfo::getId).collect(Collectors.toList());
        }else{
            ids.add(Long.valueOf(query.getProjectId()));
        }
        query.setProjectIds(ids);
        query.setDeptId(SecurityUtils.getDeptId());
        IPage<AuditHistoryVO> page = PageUtil.getPage(query, AuditHistoryVO.class);
        baseMapper.getPage(page, query);
        return Result.ok().data(page.getRecords()).total(page.getTotal());
    }
}
business/src/main/java/com/ycl/service/impl/FlowTaskServiceImpl.java
@@ -16,6 +16,8 @@
import com.ycl.common.core.domain.entity.SysRole;
import com.ycl.common.core.domain.entity.SysUser;
import com.ycl.common.enums.FlowComment;
import com.ycl.common.enums.business.AuditHistoryEnum;
import com.ycl.common.enums.business.AuditTypeEnum;
import com.ycl.common.enums.business.ProcessLogEventTypeEnum;
import com.ycl.common.enums.business.TaskStatusEnum;
import com.ycl.common.exception.CustomException;
@@ -29,6 +31,7 @@
import com.ycl.domain.entity.ProcessLog;
import com.ycl.domain.entity.ProjectProcess;
import com.ycl.domain.entity.SysForm;
import com.ycl.domain.form.AuditHistoryForm;
import com.ycl.domain.form.EditFinishedTaskForm;
import com.ycl.domain.json.RejectData;
import com.ycl.domain.query.ProcessLogQuery;
@@ -111,7 +114,7 @@
    private final ProcessLogService processLogService;
    private final ApplicationEventPublisher publisher;
    private final ProjectProcessMapper projectProcessMapper;
    private final AuditHistoryService auditHistoryService;
    @Value("${targetIp}")
    private String targetIp;
@@ -136,6 +139,21 @@
            taskService.setAssignee(taskVo.getTaskId(), userId.toString());
            taskService.complete(taskVo.getTaskId(), taskVo.getVariables());
        }
        ProjectProcess projectProcess = new LambdaQueryChainWrapper<>(projectProcessMapper)
                .eq(ProjectProcess::getProcessInsId, task.getProcessInstanceId())
                .eq(ProjectProcess::getProcessDefId, task.getProcessDefinitionId())
                .one();
        AuditHistoryForm auditHistoryForm =
                buildForm(task.getId(),
                        task.getTaskDefinitionKey(),
                        null,
                        SecurityUtils.getDeptId() + "",
                        AuditTypeEnum.Review.name(),
                        projectProcess.getProjectId(),
                        "t_project_info",
                        task.getName()
                );
        auditHistoryService.add(auditHistoryForm);
        return AjaxResult.success();
    }
@@ -195,9 +213,45 @@
                    ProcessLogEventTypeEnum.FINISHED,
                    null));
        }
        //保存日志
        AuditHistoryForm auditHistoryForm =
                buildForm(taskId,
                task.getTaskDefinitionKey(),
                null,
                SecurityUtils.getDeptId() + "",
                AuditTypeEnum.Submit.name(),
                projectProcess.getProjectId(),
                "t_project_info",
                task.getName()
        );
        auditHistoryService.add(auditHistoryForm);
        return AjaxResult.success("提交成功");
    }
    public AuditHistoryForm buildForm(
            String taskId,
            String taskDefinitionKey,
            String viewer,
            String commitDept,
            String auditType,
            String businessKey,
            String businessTable,
            String content) {
        AuditHistoryForm auditHistoryForm = new AuditHistoryForm();
        auditHistoryForm.setTaskId(taskId);
        auditHistoryForm.setTaskDefinitionKey(taskDefinitionKey);
        auditHistoryForm.setViewer(viewer);
        auditHistoryForm.setCommitDept(commitDept);
        auditHistoryForm.setAuditType(auditType);
        auditHistoryForm.setBusinessKey(businessKey);
        auditHistoryForm.setBusinessTable(businessTable);
        auditHistoryForm.setContent(content);
        // 5. 特殊字段默认值:isRead 默认为 (未读)
        auditHistoryForm.setIsRead(AuditHistoryEnum.UN_READ.name());
        return auditHistoryForm;
    }
    /**
     * 容缺补交
@@ -499,6 +553,21 @@
                task.getName(),
                ProcessLogEventTypeEnum.REJECT,
                new RejectData(flowTaskVo.getComment())));
        //保存日志
        AuditHistoryForm auditHistoryForm =
                buildForm(task.getId(),
                        task.getTaskDefinitionKey(),
                        null,
                        SecurityUtils.getDeptId() + "",
                        AuditTypeEnum.Submit.name(),
                        projectProcess.getProjectId(),
                        "t_project_info",
                        task.getName()
                );
        auditHistoryService.add(auditHistoryForm);
    }
    /**
@@ -688,6 +757,7 @@
    @Transactional(rollbackFor = Exception.class)
    public void assignTask(FlowTaskVo flowTaskVo) {
        // 直接转派就可以覆盖掉之前的
        log.info("flowTaskVo:{}",flowTaskVo);
        taskService.setAssignee(flowTaskVo.getTaskId(), flowTaskVo.getAssignee());
//        // 删除指派人重新指派
//        taskService.deleteCandidateUser(flowTaskVo.getTaskId(),flowTaskVo.getAssignee());
@@ -695,6 +765,17 @@
//        // 如果要查询转给他人处理的任务,可以同时将OWNER进行设置:
//        taskService.setOwner(flowTaskVo.getTaskId(), flowTaskVo.getAssignee());
//        AuditHistoryForm auditHistoryForm =
//                buildForm(flowTaskVo.getTaskId(),
//                        task.getTaskDefinitionKey(),
//                        null,
//                        SecurityUtils.getDeptId() + "",
//                        AuditTypeEnum.Submit.name(),
//                        projectProcess.getProjectId(),
//                        "t_project_info",
//                        task.getName()
//                );
    }
    /**
business/src/main/java/com/ycl/service/impl/IndexHomeServiceImpl.java
@@ -1,9 +1,7 @@
package com.ycl.service.impl;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.ycl.common.annotation.DataScope;
import com.ycl.common.base.Result;
import com.ycl.common.constant.ProcessOverTimeConstants;
import com.ycl.common.core.domain.BaseEntity;
@@ -17,14 +15,15 @@
import com.ycl.domain.entity.ProjectInfo;
import com.ycl.domain.entity.ProjectProcess;
import com.ycl.domain.form.ProjectProgressStatisticsForm;
import com.ycl.domain.query.WaitTodoQuery;
import com.ycl.domain.vo.*;
import com.ycl.factory.FlowServiceFactory;
import com.ycl.mapper.ProcessCodingMapper;
import com.ycl.mapper.ProcessLogMapper;
import com.ycl.mapper.ProjectInfoMapper;
import com.ycl.mapper.ProjectProcessMapper;
import com.ycl.service.IndexHomeService;
import com.ycl.service.ProjectProcessService;
import com.ycl.service.common.TaskCommonService;
import com.ycl.system.mapper.SysDeptMapper;
import com.ycl.system.service.ISysDeptService;
import com.ycl.system.service.ISysRoleService;
@@ -39,8 +38,8 @@
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.identitylink.api.IdentityLinkInfo;
import org.flowable.identitylink.api.IdentityLinkType;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@@ -78,13 +77,14 @@
    private final ISysDeptService sysDeptService;
    private final ISysRoleService sysRoleService;
    private final TaskCommonService taskCommonService;
    @Override
    public Result projectCodingStatusCount() {
        //权限控制
        List<ProjectInfo> loginUserOwnProjectInfo = getLoginUserOwnProjectInfo();
        Map<String,Integer> map = new HashMap<>();
        map.put(ProcessOverTimeConstants.GREEN,0);
@@ -151,6 +151,7 @@
        return Result.ok().data(map);
    }
    @Override
    public List<ProjectInfo> getLoginUserOwnProjectInfo(){
        //权限控制
        Long userId = SecurityUtils.getUserId();
@@ -389,6 +390,58 @@
        return Result.ok().data(projectInfoAndFunding);
    }
    @Override
    public int countWaitTask() {
        Long userId = SecurityUtils.getUserId();
        List<ProjectInfo> loginUserOwnProjectInfo = getLoginUserOwnProjectInfo();
        if (CollectionUtils.isEmpty(loginUserOwnProjectInfo)) {
            return 0;
        }
        List<Long> ids = loginUserOwnProjectInfo.stream()
                .filter(Objects::nonNull)
                .map(ProjectInfo::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(ids)) {
            return 0;
        }
        List<ProjectProcess> projectProcessList = new LambdaQueryChainWrapper<>(projectProcessService.getBaseMapper())
                .eq(ProjectProcess::getDeleted, Boolean.FALSE)
                .in(ProjectProcess::getProjectId, ids)
                .list();
        if (CollectionUtils.isEmpty(projectProcessList)) {
            return 0;
        }
        List<String> targetProcessInsIds = projectProcessList.stream()
                .filter(Objects::nonNull) // 过滤null的ProjectProcess对象
                .map(ProjectProcess::getProcessInsId)
                .filter(defId -> defId != null && !defId.trim().isEmpty()) // 先判null再trim,修复风险3
                .distinct()
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(targetProcessInsIds)) {
            return 0;
        }
        List<Task> allRunningTasks = taskService.createTaskQuery().active().list();
        if (CollectionUtils.isEmpty(allRunningTasks)) {
            return 0;
        }
        List<Task> targetRunningTasks = allRunningTasks.stream()
                .filter(Objects::nonNull)
                .filter(task -> {
                    // 先判null,再匹配,修复风险1
                    String processInsId = task.getProcessInstanceId();
                    return processInsId != null && targetProcessInsIds.contains(processInsId);
                })
                .filter(task -> {
                    String assignee = task.getAssignee();
                    return assignee == null || userId.toString().equals(assignee);//排除领取人
                })
                .collect(Collectors.toList());
        return targetRunningTasks.size();
    }
    @Override
    public Result projectAdvanceCheckPoint() {
@@ -401,7 +454,7 @@
        List<ProjectProcess> projectProcessList = new LambdaQueryChainWrapper<>(projectProcessService.getBaseMapper())
                .eq(ProjectProcess::getDeleted, Boolean.FALSE)
                .in(ProjectProcess::getProjectId, ids).list();
        Long userId = SecurityUtils.getUserId();
        //流程实例id集合
        List<String> targetProcessInsIds = projectProcessList.stream()
                .map(ProjectProcess::getProcessInsId)
@@ -454,11 +507,16 @@
        List<Task> allRunningTasks  = taskService.createTaskQuery().active().list();
        List<Task> targetRunningTasks = allRunningTasks.stream()
                // 关键匹配:任务的流程定义ID 存在于 projectProcessList 的流程定义ID列表中
                .filter(task -> targetProcessInsIds.contains(task.getProcessInstanceId()))
                .filter(task -> {
                    String assignee = task.getAssignee();
                    log.info("assignee:{}",assignee);
                    return assignee == null || userId.toString().equals(assignee);
                })
                .collect(Collectors.toList());
        List<TaskInfoVo> list = new ArrayList<>();
        // 按流程实例id分组
@@ -471,9 +529,11 @@
                    .eq(ProcessCoding::getDeleted, Boolean.FALSE)
                    .in(ProcessCoding::getTaskId, taskIds)
                    .list();
            log.info("processCodingList.size():{}", processCodingList.size()); // 看日志是否为0
            Map<String, ProcessCoding> processCodingMap = new HashMap<>();
            //按taskId转换为map方面下面获取
            if (!CollectionUtils.isEmpty(processCodingList)){
                Map<String, ProcessCoding> processCodingMap = processCodingList.stream()
                processCodingMap = processCodingList.stream()
                        .filter(Objects::nonNull) // 避免null元素导致NPE(可选保留)
                        .filter(vo -> vo.getTaskId() != null) // 过滤taskId为null的无效数据
                        .collect(Collectors.toMap(
@@ -493,128 +553,126 @@
                                    return newCreateTime.getTime() > oldCreateTime.getTime() ? newVal : oldVal;
                                }
                        ));
            }
            for (Task task : targetRunningTasks) {
                for (Task task : targetRunningTasks) {
                TaskInfoVo taskVo = new TaskInfoVo();
                    TaskInfoVo taskVo = new TaskInfoVo();
                    taskVo.setId(task.getId());
                    taskVo.setTaskName(task.getName());
                    taskVo.setStartTime(task.getCreateTime());
                    taskVo.setEndTime(task.getDueDate());
                taskVo.setId(task.getId());
                taskVo.setTaskName(task.getName());
                taskVo.setStartTime(task.getCreateTime());
                taskVo.setEndTime(task.getDueDate());
//                    taskVo.setTaskType();
                    List<Long> handlerIds = new ArrayList<>(2);
                    List<String> handlerNames = new ArrayList<>(2);
                    List<Long> handlerUnitIds = new ArrayList<>(2);
                    List<String> handlerUnitNames = new ArrayList<>(2);
                    List<String> promoterNames = new ArrayList<>(2);
                    List<String> promoterUnitNames = new ArrayList<>(2);
                    CustomerTaskVO customerTaskVO = new CustomerTaskVO();
                    customerTaskVO.setHandlerId(handlerIds);
                    customerTaskVO.setHandlerName(handlerNames);
                    customerTaskVO.setHandlerUnitId(handlerUnitIds);
                    customerTaskVO.setHandlerUnitName(handlerUnitNames);
                    customerTaskVO.setPromoterName(promoterNames);
                    customerTaskVO.setPromoterUnitName(promoterUnitNames);
                List<Long> handlerIds = new ArrayList<>(2);
                List<String> handlerNames = new ArrayList<>(2);
                List<Long> handlerUnitIds = new ArrayList<>(2);
                List<String> handlerUnitNames = new ArrayList<>(2);
                List<String> promoterNames = new ArrayList<>(2);
                List<String> promoterUnitNames = new ArrayList<>(2);
                CustomerTaskVO customerTaskVO = new CustomerTaskVO();
                customerTaskVO.setHandlerId(handlerIds);
                customerTaskVO.setHandlerName(handlerNames);
                customerTaskVO.setHandlerUnitId(handlerUnitIds);
                customerTaskVO.setHandlerUnitName(handlerUnitNames);
                customerTaskVO.setPromoterName(promoterNames);
                customerTaskVO.setPromoterUnitName(promoterUnitNames);
                    // 流程处理人信息
                    List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask(task.getId());
                    for (IdentityLinkInfo identityLink : identityLinksForTask) {
                        // 绑定的是用户,查出用户姓名、部门
                        if (StringUtils.isNotBlank(identityLink.getUserId())) {
                            // 处理变量表达式,运行中的任务无需再处理表达式了,flowable已经自动根据变量设置了
                            customerTaskVO.setHandlerType(HandlerTypeEnum.USER);
                            SysUser sysUser = sysUserService.selectUserById(Long.parseLong(identityLink.getUserId()));
                            if (Objects.nonNull(sysUser)) {
                                customerTaskVO.getHandlerId().add(sysUser.getUserId());
                                customerTaskVO.getHandlerName().add(this.getUserShowName(sysUser));
                                if (Objects.nonNull(sysUser.getDept())) {
                                    customerTaskVO.getHandlerUnitId().add(sysUser.getDept().getDeptId());
                                    customerTaskVO.getHandlerUnitName().add(sysUser.getDept().getDeptName());
                                    customerTaskVO.getPromoterName().add(this.getUserShowName(sysUser));
                                    String[] str = sysUser.getDept().getAncestors().split(",");
                                    if (str.length >= 4){
                                        customerTaskVO.getPromoterUnitName().add(sysUser.getDept().getParentName() +"-"+sysUser.getDept().getDeptName());
                                    }else {
                                        customerTaskVO.getPromoterUnitName().add(sysUser.getDept().getDeptName());
                                    }
                // 流程处理人信息
                List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask(task.getId());
                for (IdentityLinkInfo identityLink : identityLinksForTask) {
                    // 绑定的是用户,查出用户姓名、部门
                    if (StringUtils.isNotBlank(identityLink.getUserId())) {
                        // 处理变量表达式,运行中的任务无需再处理表达式了,flowable已经自动根据变量设置了
                        customerTaskVO.setHandlerType(HandlerTypeEnum.USER);
                        SysUser sysUser = sysUserService.selectUserById(Long.parseLong(identityLink.getUserId()));
                        if (Objects.nonNull(sysUser)) {
                            customerTaskVO.getHandlerId().add(sysUser.getUserId());
                            customerTaskVO.getHandlerName().add(this.getUserShowName(sysUser));
                            if (Objects.nonNull(sysUser.getDept())) {
                                customerTaskVO.getHandlerUnitId().add(sysUser.getDept().getDeptId());
                                customerTaskVO.getHandlerUnitName().add(sysUser.getDept().getDeptName());
                                customerTaskVO.getPromoterName().add(this.getUserShowName(sysUser));
                                String[] str = sysUser.getDept().getAncestors().split(",");
                                if (str.length >= 4){
                                    customerTaskVO.getPromoterUnitName().add(sysUser.getDept().getParentName() +"-"+sysUser.getDept().getDeptName());
                                }else {
                                    customerTaskVO.getPromoterUnitName().add(sysUser.getDept().getDeptName());
                                }
                            }
                            // 绑定的是角色或者部门
                        } else if (StringUtils.isNotBlank(identityLink.getGroupId())) {
                            if (identityLink.getGroupId().startsWith("dept")) {   // 部门的id是加了前缀的如:dept:1
                                customerTaskVO.setHandlerType(HandlerTypeEnum.DEPT);
                                String[] split = identityLink.getGroupId().split(":");
                                if (split.length > 1) {
                                    // 部门
                                    SysDept dept = sysDeptService.selectDeptById(Long.parseLong(split[1]));
                                    if (Objects.nonNull(dept)) {
                                        customerTaskVO.getHandlerUnitId().add(dept.getDeptId());
                                        customerTaskVO.getHandlerUnitName().add(dept.getDeptName());
                                        customerTaskVO.getPromoterName().add(this.getDeptLeaderShowName(dept));
                                        customerTaskVO.getPromoterUnitName().add(this.setDeptNameWithParentName(dept));
                                    }
                                }
                            } else {
                                customerTaskVO.setHandlerType(HandlerTypeEnum.ROLE);
                                SysRole role = sysRoleService.selectRoleById(Long.parseLong(identityLink.getGroupId()));
                                if (Objects.nonNull(role)) {
                                    customerTaskVO.getHandlerUnitId().add(Long.parseLong(identityLink.getGroupId()));
                                    customerTaskVO.getHandlerUnitName().add(role.getRoleName());
                                }
                            }
                        }
                        this.distinctVo(customerTaskVO);
                    }
                    taskVo.setCustomerTaskInfo(customerTaskVO);
                    //组装任务信息
                    CheckPointVO checkPointVO = processInfoMap.get(task.getProcessInstanceId());
                    if (checkPointVO != null) {
                       taskVo.setCheckPointInfo(checkPointVO);
                    }
                    //超时时间和次数在 processCodingList中  这里需要更具taskId来获得对应信息
                    ProcessCoding processCoding = processCodingMap.get(task.getId());
                    //设置超时信息
                    if (processCoding != null) {
                        String overTimeTotalStr = processCoding.getOverTimeTotal();
                        long overTimeTotal = 0L;
                        if (StringUtils.isNotBlank(overTimeTotalStr)) {
                            try {
                                overTimeTotal = Long.parseLong(overTimeTotalStr.trim());
                            } catch (NumberFormatException e) {
                            }
                        }
                        String overTimeDesc = convertHoursToDayHourStr(overTimeTotal);
                        taskVo.setTotalOverTime(overTimeDesc);
                        // 2. 提取红码阈值 如: 0-22 天-小时 并转换为小时
                        String redTimeStr = processCoding.getRedTime();
                        Long redTimeSec = getTime(redTimeStr);
                        Double redTimeHour = null;
                        if (redTimeSec != null && redTimeSec > 0) {
                            redTimeHour = redTimeSec / 3600.0;
                        // 绑定的是角色或者部门
                    } else if (StringUtils.isNotBlank(identityLink.getGroupId())) {
                        if (identityLink.getGroupId().startsWith("dept")) {   // 部门的id是加了前缀的如:dept:1
                            customerTaskVO.setHandlerType(HandlerTypeEnum.DEPT);
                            String[] split = identityLink.getGroupId().split(":");
                            if (split.length > 1) {
                                // 部门
                                SysDept dept = sysDeptService.selectDeptById(Long.parseLong(split[1]));
                                if (Objects.nonNull(dept)) {
                                    customerTaskVO.getHandlerUnitId().add(dept.getDeptId());
                                    customerTaskVO.getHandlerUnitName().add(dept.getDeptName());
                                    customerTaskVO.getPromoterName().add(this.getDeptLeaderShowName(dept));
                                    customerTaskVO.getPromoterUnitName().add(this.setDeptNameWithParentName(dept));
                                }
                            }
                        } else {
                            customerTaskVO.setHandlerType(HandlerTypeEnum.ROLE);
                            SysRole role = sysRoleService.selectRoleById(Long.parseLong(identityLink.getGroupId()));
                            if (Objects.nonNull(role)) {
                                customerTaskVO.getHandlerUnitId().add(Long.parseLong(identityLink.getGroupId()));
                                customerTaskVO.getHandlerUnitName().add(role.getRoleName());
                            }
                        }
                        long overTimeCount = 0L;
                        // 判断超时次数
                        if (overTimeTotal > 0 && redTimeHour != null && redTimeHour > 0) {
                            overTimeCount = (long) (overTimeTotal / redTimeHour);
                        }
                        taskVo.setOverTimeCount(overTimeCount);
                    }else {
                        //为null 说明未配置超时时间 默认未超时
                        taskVo.setOverTimeCount(0L);
                        taskVo.setTotalOverTime("0小时");
                    }
                    list.add(taskVo);
                    this.distinctVo(customerTaskVO);
                }
                taskVo.setCustomerTaskInfo(customerTaskVO);
                //组装任务信息
                CheckPointVO checkPointVO = processInfoMap.get(task.getProcessInstanceId());
                if (checkPointVO != null) {
                    taskVo.setCheckPointInfo(checkPointVO);
                }
                //超时时间和次数在 processCodingList中  这里需要更具taskId来获得对应信息
                ProcessCoding processCoding = processCodingMap.get(task.getId());
                //设置超时信息
                if (processCoding != null) {
                    String overTimeTotalStr = processCoding.getOverTimeTotal();
                    long overTimeTotal = 0L;
                    if (StringUtils.isNotBlank(overTimeTotalStr)) {
                        try {
                            overTimeTotal = Long.parseLong(overTimeTotalStr.trim());
                        } catch (NumberFormatException e) {
                        }
                    }
                    String overTimeDesc = convertHoursToDayHourStr(overTimeTotal);
                    taskVo.setTotalOverTime(overTimeDesc);
                    // 2. 提取红码阈值 如: 0-22 天-小时 并转换为小时
                    String redTimeStr = processCoding.getRedTime();
                    Long redTimeSec = getTime(redTimeStr);
                    Double redTimeHour = null;
                    if (redTimeSec != null && redTimeSec > 0) {
                        redTimeHour = redTimeSec / 3600.0;
                    }
                    long overTimeCount = 0L;
                    // 判断超时次数
                    if (overTimeTotal > 0 && redTimeHour != null && redTimeHour > 0) {
                        overTimeCount = (long) (overTimeTotal / redTimeHour);
                    }
                    taskVo.setOverTimeCount(overTimeCount);
                }else {
                    //为null 说明未配置超时时间 默认未超时
                    taskVo.setOverTimeCount(0L);
                    taskVo.setTotalOverTime("0小时");
                }
                list.add(taskVo);
            }
@@ -622,6 +680,211 @@
        return Result.ok().data(list);
    }
    public HashMap<String,Object> buildEmptyResultMap(){
        HashMap<String,Object> map = new HashMap<>();
        map.put("data", new ArrayList<>());
        map.put("total", 0L);
        return map;
    }
    @Override
    public Result getWaitTaskList(WaitTodoQuery baseQuery) {
        int pageNum = (int) baseQuery.getCurrentPage();
        int pageSize = (int) baseQuery.getPageSize();
        Long userId = SecurityUtils.getUserId(); // 当前用户ID
        boolean isAdmin = SecurityUtils.getLoginUser().getUser().isAdmin(); // 是否管理员
        List<String> userGroups = taskCommonService.getCurrentUserGroups(); // 当前用户所属组
        List<ProjectInfo> targetProjectList = new ArrayList<>();
        String queryProjectId = baseQuery.getId();
        if ("all".equals(queryProjectId) || StringUtils.isBlank(queryProjectId)){
            // 查询全部
            targetProjectList = getLoginUserOwnProjectInfo();
        }else{
            try {
                Long projectId = Long.parseLong(queryProjectId);
                // ① 查询该项目基础信息(替换为实际查询方法)
                ProjectInfo projectInfo = projectInfoMapper.selectById(projectId);
                if (projectInfo == null) {
                    // 项目不存在,返回空数据
                    return Result.ok().data(buildEmptyResultMap());
                }
                //
                targetProjectList.add(projectInfo);
            } catch (NumberFormatException e) {
                // 项目ID格式错误,返回空数据
                return Result.ok().data(buildEmptyResultMap());
            }
        }
        List<Long> projectIds = targetProjectList.stream().map(ProjectInfo::getId).collect(Collectors.toList());
        List<ProjectProcess> projectProcessList = new LambdaQueryChainWrapper<>(projectProcessService.getBaseMapper())
                .eq(ProjectProcess::getDeleted, Boolean.FALSE)
                .in(ProjectProcess::getProjectId, projectIds).list();
        // 4. 提取有效的流程实例ID(去重+非空过滤)
        List<String> targetProcessInsIds = projectProcessList.stream()
                .map(ProjectProcess::getProcessInsId)
                .distinct()
                .filter(defId -> defId != null && !defId.trim().isEmpty())
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(targetProcessInsIds)) {
            return Result.ok().data(new HashMap<String, Object>() {{
                put("data", new ArrayList<>());
                put("total", 0L);
            }});
        }
        // 5. 构建processInfoMap(核心:项目ID→CheckPointVO,再关联流程信息)
        Map<String, CheckPointVO> map = new HashMap<>();
        // 先填充项目基础信息
        for (ProjectInfo projectInfo : targetProjectList) {
            CheckPointVO checkPointVO = new CheckPointVO();
            checkPointVO.setId(projectInfo.getId());
            checkPointVO.setProjectName(projectInfo.getProjectName());
            map.put(String.valueOf(projectInfo.getId()), checkPointVO);
        }
        // 再填充流程信息(部署ID、流程名称等)
        for (ProjectProcess projectProcess : projectProcessList) {
            CheckPointVO checkPointVO = map.get(projectProcess.getProjectId());
            if (checkPointVO == null) {
                continue; // 无匹配项目,跳过
            }
            checkPointVO.setProcessDefinitionId(projectProcess.getProcessDefId());
            checkPointVO.setProcessInstanceId(projectProcess.getProcessInsId());
            // 获得流程部署id和流程名称(兼容运行中/已结束流程)
            ProcessInstance process = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(projectProcess.getProcessInsId()).singleResult();
            if (Objects.nonNull(process)) {
                checkPointVO.setDeployId(process.getDeploymentId());
                checkPointVO.setProcessName(process.getProcessDefinitionName());
            } else {
                HistoricProcessInstance hisProcess = historyService.createHistoricProcessInstanceQuery()
                        .processInstanceId(projectProcess.getProcessInsId()).singleResult();
                if (Objects.nonNull(hisProcess)) { // 避免历史流程为null的NPE
                    checkPointVO.setDeployId(hisProcess.getDeploymentId());
                    checkPointVO.setProcessName(hisProcess.getProcessDefinitionName());
                }
            }
            map.put(projectProcess.getProjectId(), checkPointVO);
        }
        Map<String, CheckPointVO> processInfoMap = map.values().stream()
                .filter(Objects::nonNull)
                .filter(vo -> vo.getProcessInstanceId() != null)
                .collect(Collectors.toMap(
                        CheckPointVO::getProcessInstanceId,
                        vo -> vo,
                        (oldVal, newVal) -> newVal // 重复流程实例ID保留后者
                ));
        TaskQuery query = getTaskService().createTaskQuery()
                .active() // 未完成、未挂起的活跃任务
                .processInstanceIdIn(targetProcessInsIds); // 仅查项目关联的流程实例任务
        // 权限过滤:非管理员仅查自己有权限的任务(已领取+候选)
        if (!isAdmin) {
            query.or()
                    .taskAssignee(userId+"") // 自己已领取的任务
                    .taskCandidateUser(userId+"") // 自己是候选用户的任务
                    .taskCandidateGroupIn(userGroups) // 自己在候选组的任务
                    .endOr();
        }
        query.orderByTaskCreateTime().desc();
        List<Task> allTaskList = query.list();
        // 排除领取人问题
        allTaskList = allTaskList.stream()
                .filter(Objects::nonNull) // 空任务防护
                .filter(task -> {
                    String assignee = task.getAssignee();
                    return assignee == null || userId.toString().equals(assignee);
                })
                .collect(Collectors.toList());
        Set<String> taskIdSet = new HashSet<>();
        List<Task> distinctAllTaskList = new ArrayList<>();
        for (Task task : allTaskList) {
            if (!taskIdSet.contains(task.getId())) {
                taskIdSet.add(task.getId());
                distinctAllTaskList.add(task);
                //
            }
        }
        long distinctTotal = distinctAllTaskList.size();
        System.out.println("去重后总数:" + distinctTotal);
        int startIndex = (pageNum - 1) * pageSize;
        List<Task> pageTaskList = new ArrayList<>();
        if (startIndex < distinctAllTaskList.size()) {
            int endIndex = Math.min(startIndex + pageSize, distinctAllTaskList.size());
            pageTaskList = distinctAllTaskList.subList(startIndex, endIndex);
        }
        List<TaskInfoVo> taskInfoVoList = new ArrayList<>();
        //查询任务状态
        List<String> taskIds = pageTaskList.stream().map(Task::getId).collect(Collectors.toList());
        HashMap<String,ProcessLog> taskInfoMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(taskIds)) {
            taskInfoMap = new LambdaQueryChainWrapper<>(processLogMapper)
                    .eq(ProcessLog::getDeleted, Boolean.FALSE)
                    .in(ProcessLog::getTaskId, taskIds).list()
                    .stream()
                    .filter(processLog -> processLog.getTaskId() != null && processLog.getGmtCreate() != null)
                    .collect(Collectors.toMap(
                            // Key:taskId转字符串
                            ProcessLog::getTaskId,
                            // Value:ProcessLog对象本身
                            processLog -> processLog,
                            // 冲突策略:保留gmtCreate更新的那条记录
                            (existing, replacement) -> {
                                return replacement.getGmtCreate().after(existing.getGmtCreate())
                                        ? replacement : existing;
                            },
                            HashMap::new
                    ));
        }
        for (Task task : pageTaskList) {
            TaskInfoVo taskVo = new TaskInfoVo();
            taskVo.setId(task.getId());
            taskVo.setTaskName(task.getName());
            taskVo.setStartTime(task.getCreateTime());
            taskVo.setEndTime(task.getDueDate());
            // 填充任务状态字段
            taskVo.setTaskType("待完成");
            ProcessLog processLog = taskInfoMap.get(task.getId());
            if (Objects.nonNull(processLog)) {
                taskVo.setTaskType(processLog.getEventType().getDesc());
            }
            CheckPointVO checkPointVO = processInfoMap.get(task.getProcessInstanceId());
            if (checkPointVO != null) {
                taskVo.setCheckPointInfo(checkPointVO);
            }
            taskInfoVoList.add(taskVo);
        }
        //排除 已完成的
        taskInfoVoList = taskInfoVoList.stream().filter(taskInfoVo -> {
            if (ProcessLogEventTypeEnum.FINISHED.getDesc().equals(taskInfoVo.getTaskType())){
                return false;
            }
            return true;
        }).collect(Collectors.toList());
        HashMap<String, Object> resultMap = new HashMap<>();
        resultMap.put("data", taskInfoVoList);
        resultMap.put("total", distinctTotal);
        return Result.ok().data(resultMap);
    }
    private String convertHoursToDayHourStr(long totalHours) {
        if (totalHours < 0) {
            return "0小时"; // 防御性处理负数场景
@@ -681,11 +944,18 @@
    }
    @Override
    public Result getProjectList(){
        List<ProjectInfo> loginUserOwnProjectInfo = getLoginUserOwnProjectInfo();
        return Result.ok().data(loginUserOwnProjectInfo);
    }
    @Override
    public Result getProjectSelectList() {
        List<ProjectInfo> loginUserOwnProjectInfo = getLoginUserOwnProjectInfo();
        if (CollectionUtils.isEmpty(loginUserOwnProjectInfo)) {
            return Result.ok().data(loginUserOwnProjectInfo);
        }
        Map<String, String> projectMap = loginUserOwnProjectInfo.stream()
                .collect(Collectors.toMap(
                        projectInfo -> String.valueOf(projectInfo.getId()),          // key: 项目ID
@@ -695,4 +965,6 @@
                ));
        return Result.ok().data(projectMap);
    }
}
business/src/main/java/com/ycl/service/impl/ProjectProcessServiceImpl.java
@@ -95,7 +95,7 @@
    private final ProcessLogService processLogService;
    private final ISysDictTypeService dictTypeService;
    private final ProcessConfigInfoService processConfigInfoService;
    private final AuditHistoryService auditHistoryService;
    /**
     * 分页查询
     *
@@ -815,9 +815,44 @@
        // 发布转办事件
        publisher.publishEvent(new TaskLogEvent(this, null, SecurityUtils.getUserId(), form.getProjectId(), form.getProcessInsId(), task.getId(), task.getTaskDefinitionKey(), task.getName(), ProcessLogEventTypeEnum.DELEGATE, jsonData));
        AuditHistoryForm auditHistoryForm =
                buildForm(task.getId(),
                        task.getTaskDefinitionKey(),
                        null,
                        SecurityUtils.getDeptId() + "",
                        AuditTypeEnum.Submit.name(),
                        form.getProjectId(),
                        "t_project_info",
                        task.getName()
                );
        auditHistoryService.add(auditHistoryForm);
        return Result.ok("转办成功");
    }
    public AuditHistoryForm buildForm(
            String taskId,
            String taskDefinitionKey,
            String viewer,
            String commitDept,
            String auditType,
            String businessKey,
            String businessTable,
            String content) {
        AuditHistoryForm auditHistoryForm = new AuditHistoryForm();
        auditHistoryForm.setTaskId(taskId);
        auditHistoryForm.setTaskDefinitionKey(taskDefinitionKey);
        auditHistoryForm.setViewer(viewer);
        auditHistoryForm.setCommitDept(commitDept);
        auditHistoryForm.setAuditType(auditType);
        auditHistoryForm.setBusinessKey(businessKey);
        auditHistoryForm.setBusinessTable(businessTable);
        auditHistoryForm.setContent(content);
        // 5. 特殊字段默认值:isRead 默认为 (未读)
        auditHistoryForm.setIsRead(AuditHistoryEnum.UN_READ.name());
        return auditHistoryForm;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result taskJump(TaskJumpForm form) {
business/src/main/java/com/ycl/service/impl/WorkStationScheduleServiceImpl.java
@@ -3,8 +3,12 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.ycl.common.enums.business.WorkStationEnum;
import com.ycl.common.utils.DateUtils;
import com.ycl.common.utils.SecurityUtils;
import com.ycl.common.utils.StringUtils;
import com.ycl.domain.entity.ProjectInfo;
import com.ycl.domain.entity.WorkStationSchedule;
import com.ycl.domain.vo.DailyStatVO;
import com.ycl.factory.FlowServiceFactory;
import com.ycl.mapper.WorkStationScheduleMapper;
import com.ycl.service.WorkStationScheduleService;
@@ -13,6 +17,9 @@
import com.ycl.domain.form.WorkStationScheduleForm;
import com.ycl.domain.vo.WorkStationScheduleVO;
import com.ycl.domain.query.WorkStationScheduleQuery;
import com.ycl.service.common.TaskCommonService;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.springframework.stereotype.Service;
@@ -22,8 +29,9 @@
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import java.util.HashMap;
import java.util.List;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -39,6 +47,10 @@
    private final WorkStationScheduleMapper workStationScheduleMapper;
    private final FlowServiceFactory flowServiceFactory;
    private final TaskCommonService taskCommonService;
    private final IndexHomeServiceImpl indexHomeServiceImpl;
    /**
     * 添加
     * @param form
@@ -47,6 +59,9 @@
    @Override
    public Result add(WorkStationScheduleForm form) {
        WorkStationSchedule entity = WorkStationScheduleForm.getEntityByForm(form, null);
        Long userId = SecurityUtils.getUserId();
        entity.setUserId(userId);
        entity.setStatus(WorkStationEnum.Incomplete.name());
        baseMapper.insert(entity);
        return Result.ok("添加成功");
    }
@@ -101,6 +116,39 @@
        return Result.ok().data(page.getRecords()).total(page.getTotal());
    }
    @Override
    public Result listByDate(WorkStationScheduleQuery query) {
        Integer projectId = null;
        if (!"all".equals(query.getProjectId())){
            projectId = Integer.valueOf(query.getProjectId());
        }
        Long userId = SecurityUtils.getUserId();
        //查询出指定时间段内的数据
        List<WorkStationScheduleVO> list = baseMapper.groupByProjectAndDate(userId,projectId, DateUtils.getMonthStartTime(query.getCompletedTime()), DateUtils.getMonthEndTime(query.getCompletedTime()));
        List<DailyStatVO> dailyStatVOList = new ArrayList<>();
        for (WorkStationScheduleVO vo : list) {
            if (vo == null) {
                continue;
            }
            DailyStatVO statVO = new DailyStatVO();
            statVO.setId(vo.getId() == null ? -1 : vo.getId());
            statVO.setTitle(StringUtils.isBlank(vo.getContent()) ? "无内容" : vo.getContent());
            Map<String, String> stringTimeMap = DateUtils.splitDateToDateAndTime(vo.getCompletedTime());
            String date = StringUtils.defaultIfBlank(stringTimeMap.get("date"), "");
            String time = StringUtils.defaultIfBlank(stringTimeMap.get("time"), "");
            statVO.setDate(date);
            statVO.setTime(time);
            statVO.setProject(StringUtils.isBlank(vo.getProjectName()) ? "未关联项目" : vo.getProjectName());
            statVO.setProjectId(vo.getProjectId());
            statVO.setCompletedTime(vo.getCompletedTime());
            dailyStatVOList.add(statVO);
        }
        return Result.ok().data(dailyStatVOList);
    }
    /**
     * 根据id查找
     * @param id
@@ -123,6 +171,7 @@
        List<WorkStationScheduleVO> vos = entities.stream()
                .map(entity -> WorkStationScheduleVO.getVoByEntity(entity, null))
                .collect(Collectors.toList());
        return Result.ok().data(vos);
    }
@@ -142,9 +191,18 @@
        HistoricTaskInstanceQuery query = flowServiceFactory.getHistoryService()
                .createHistoricTaskInstanceQuery()
                .finished()
                .taskAssignee(userId.toString())
                .taskAssignee(SecurityUtils.getUserId() + "")
                .orderByHistoricTaskInstanceEndTime()
                .desc();
        if (!SecurityUtils.getLoginUser().getUser().isAdmin()) {
            query
                    .or()
                    .taskCandidateGroupIn(taskCommonService.getCurrentUserGroups())
                    .taskCandidateUser(SecurityUtils.getUserId() + "")
                    .taskAssignee(SecurityUtils.getUserId() + "")
                    .endOr();
        }
        List<HistoricTaskInstance> taskList = query.list();
        long totalDuration = 0L;
        int taskCount = 0;
@@ -163,7 +221,8 @@
        double avgDurationMs = (double) totalDuration / taskCount; // 平均耗时(毫秒)
        double avgDurationSec = avgDurationMs / 1000; // 转换为秒
        double avgDurationMin = avgDurationSec / 60; // 转换为分钟
        double avgDurationHour = avgDurationMin / 60; // 转换为分钟
        BigDecimal  avgDurationHour = new BigDecimal(avgDurationMin / 60).setScale(2, RoundingMode.HALF_UP); // 转换为分钟
        System.out.println("用户完成任务总数:" + taskCount);
        System.out.println("平均耗时(秒):" + avgDurationSec);
        System.out.println("平均耗时(分钟):" + avgDurationMin);
@@ -183,10 +242,37 @@
        }
        HashMap<String,Object> map = new HashMap<>();
        map.put("totalDuration",totalDuration);
        map.put("avgDurationHour",avgDurationHour);
        map.put("scheduleCount",list.size());
        map.put("completedCount",count);
        map.put("totalSchedules",list.size());
        map.put("completedSchedules",count);
        map.put("avgDuration",avgDurationHour.doubleValue());
        map.put("completedTasks",taskCount);
        return Result.ok().data(map);
    }
    @Override
    public Result countTodayTask() {
        Long userId = SecurityUtils.getUserId();
        Date now = new Date();
        Date dayStart = DateUtils.getDayStart(now);
        Date dayEnd = DateUtils.getDayEnd(now);
        List<WorkStationSchedule> list = new LambdaQueryChainWrapper<>(baseMapper)
                .eq(WorkStationSchedule::getUserId, SecurityUtils.getUserId())
                .eq(WorkStationSchedule::getDeleted, Boolean.FALSE)
                .between(WorkStationSchedule::getCompletedTime, dayStart, dayEnd)
                .list();
        HashMap<String,Object> map = new HashMap<>();
        map.put("todaySchedules",list.size());
        //查询今日处理的 的任务
        long claimedAndCompletedCount = flowServiceFactory.getHistoryService().createHistoricTaskInstanceQuery()
                .finished()
                .taskAssignee(userId+"") // 任务由当前用户认领(处理人是该用户)
                .taskCompletedAfter(dayStart)
                .taskCompletedBefore(dayEnd)
                .count();
        map.put("totalActiveTasks",claimedAndCompletedCount);
        int distinctTotal = indexHomeServiceImpl.countWaitTask();
        map.put("todayTasks",distinctTotal);
        return Result.ok().data(map);
    }
}
business/src/main/resources/mapper/AuditHistoryMapper.xml
@@ -3,7 +3,7 @@
<mapper namespace="com.ycl.mapper.AuditHistoryMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ycl.domain.entity.AuditHistory">
    <resultMap id="BaseResultMap" type="com.ycl.domain.vo.AuditHistoryVO">
        <result column="task_id" property="taskId" />
        <result column="task_definition_key" property="taskDefinitionKey" />
        <result column="viewer" property="viewer" />
@@ -13,7 +13,36 @@
        <result column="business_key" property="businessKey" />
        <result column="business_table" property="businessTable" />
        <result column="content" property="content" />
        <result column="project_name" property="projectName" />
    </resultMap>
    <select id="getPage" resultMap="BaseResultMap">
        SELECT
        ah.task_id,
        ah.task_definition_key,
        ah.viewer,
        ah.commit_dept,
        ah.audit_type,
        ah.is_read,
        ah.business_key,
        ah.business_table,
        ah.content,
        pi.project_name
        FROM t_audit_history ah
        LEFT JOIN t_project_info pi ON ah.business_key = pi.id
        WHERE ah.deleted = 0
        AND pi.deleted = 0
        <if test="query.projectIds != null and query.projectIds.size() > 0">
            AND ah.business_key IN
            <foreach collection="query.projectIds" item="id" open="(" separator="," close=")">
                #{id}
            </foreach>
        </if>
        <if test="query.deptId != null">
            AND
            ah.commit_dept = #{query.deptId}
        </if>
        ORDER BY ah.gmt_create DESC
    </select>
business/src/main/resources/mapper/WorkStationScheduleMapper.xml
@@ -7,8 +7,8 @@
        <result column="user_id" property="userId" />
        <result column="content" property="content" />
        <result column="status" property="status" />
        <result column="gmtCreate" property="gmtcreate" />
        <result column="gmtUpdate" property="gmtupdate" />
        <result column="gmt_create" property="gmtCreate" />
        <result column="gmt_update" property="gmtUpdate" />
    </resultMap>
@@ -41,4 +41,24 @@
            TWSS.deleted = 0
    </select>
    <select id="groupByProjectAndDate" resultType="com.ycl.domain.vo.WorkStationScheduleVO">
        SELECT
        TWSS.id,
        TWSS.user_id,
        TWSS.content,
        TWSS.status,
        TWSS.project_id,
        TWSS.completed_time,
        TPI.project_name
        FROM t_work_station_schedule TWSS
        LEFT JOIN t_project_info TPI on TWSS.project_id = TPI.id
        WHERE TWSS.deleted = 0
        AND TWSS.user_id = #{userId}
        AND TWSS.completed_time BETWEEN #{startTime} AND #{endTime}
        <if test="projectId != null">
            AND TWSS.project_id = #{projectId}
        </if>;
    </select>
</mapper>
common/src/main/java/com/ycl/common/enums/business/AuditHistoryEnum.java
New file
@@ -0,0 +1,17 @@
package com.ycl.common.enums.business;
import lombok.Getter;
@Getter
public enum AuditHistoryEnum {
    UN_READ("未读"),
    READ("已读");
    private String desc;
    AuditHistoryEnum(String desc) {
        this.desc = desc;
    }
}
common/src/main/java/com/ycl/common/enums/business/AuditTypeEnum.java
New file
@@ -0,0 +1,15 @@
package com.ycl.common.enums.business;
import lombok.Getter;
@Getter
public enum AuditTypeEnum {
    Submit("提交"),
    Review("审核"),
    Reject("驳回"),
    Forward("转交");
    private String desc;
    AuditTypeEnum(String desc) {
        this.desc = desc;
    }
}
common/src/main/java/com/ycl/common/enums/business/WorkStationEnum.java
@@ -1,12 +1,15 @@
package com.ycl.common.enums.business;
import lombok.Getter;
/**
 * nongtou-project-java
 *
 * @author : zxl
 * @date : 2025-12-05 10:50
 **/
@Getter
public enum WorkStationEnum {
    COMPLETED("完成"),
common/src/main/java/com/ycl/common/utils/DateUtils.java
@@ -5,10 +5,8 @@
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.Date;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.time.format.DateTimeFormatter;
import java.util.*;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.lang.Nullable;
@@ -38,6 +36,10 @@
    private static String isHoliday = "1-1,1-28,1-29,1-30,1-31,2-1,2-2,2-3,2-4,"
            +"4-4,4-5,4-6,5-1,5-2,5-3,5-4,5-5,5-31,6-1,6-2,10-1,10-2,10-3,"
            +"10-4,10-5,10-6,10-7,10-8";
    // 线程安全的格式化器(全局静态)
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
    /**
     * 获取两个时间内,除去周末周日以及法定节假日的 小时总数。
@@ -309,4 +311,73 @@
        LocalDateTime of = LocalDateTime.of(localDateTime.getYear(), localDateTime.getMonth(), localDateTime.getDayOfMonth(), 23, 59, 59);
        return Timestamp.valueOf(of);
    }
    /**
     * 获取指定时间所在月份的开始时间(当月1日 00:00:00)
     * @param date 任意时间(可为null,默认取当前时间)
     * @return 当月开始时间
     */
    public static Date getMonthStartTime(Date date) {
        Calendar calendar = Calendar.getInstance();
        // 若传入时间为null,使用当前时间
        if (date != null) {
            calendar.setTime(date);
        }
        // 设置为当月1日
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        // 设置为00:00:00
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0); // 毫秒置0,避免时间偏差
        return calendar.getTime();
    }
    /**
     * 获取指定时间所在月份的结束时间(当月最后一日 23:59:59)
     * @param date 任意时间(可为null,默认取当前时间)
     * @return 当月结束时间
     */
    public static Date getMonthEndTime(Date date) {
        Calendar calendar = Calendar.getInstance();
        if (date != null) {
            calendar.setTime(date);
        }
        // 设置为当月最后一天
        calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        // 设置为23:59:59
        calendar.set(Calendar.HOUR_OF_DAY, 23);
        calendar.set(Calendar.MINUTE, 59);
        calendar.set(Calendar.SECOND, 59);
        calendar.set(Calendar.MILLISECOND, 999); // 毫秒置999,覆盖所有毫秒数
        return calendar.getTime();
    }
    /**
     * 拆分Date为年月日和时分字符串
     * @param date 待拆分的Date(可为null)
     * @return Map:key=date(年月日)、time(时分),值为对应字符串;null返回空字符串
     */
    public static Map<String, String> splitDateToDateAndTime(Date date) {
        Map<String, String> result = new HashMap<>();
        if (date == null) {
            result.put("date", "");
            result.put("time", "");
            return result;
        }
        // Date转LocalDateTime(处理时区)
        LocalDateTime localDateTime = LocalDateTime.ofInstant(
                date.toInstant(),
                ZoneId.systemDefault() // 用系统默认时区,避免时差
        );
        // 拆分年月日和时分
        String dateStr = DATE_FORMATTER.format(localDateTime);
        String timeStr = TIME_FORMATTER.format(localDateTime);
        result.put("date", dateStr);
        result.put("time", timeStr);
        return result;
    }
}