From 03c80657ac709581f71e2a904960851714cc642f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=97=AD=E6=98=8E?= Date: Tue, 25 Nov 2025 15:16:56 +0800 Subject: [PATCH] lxm --- .../stgl/config/AnnualReviewConfig.java | 62 +++ .../StglActivityAuditController.java | 111 +++++ .../controller/StglActivityController.java | 4 + .../org/dromara/stgl/domain/StglActivity.java | 12 +- .../stgl/domain/StglActivityAudit.java | 91 ++++ .../domain/bo/StglActivityApprovalBo.java | 61 +++ .../stgl/domain/bo/StglActivityBo.java | 4 +- .../stgl/domain/vo/StglActivityAuditVo.java | 106 +++++ .../stgl/domain/vo/StglActivityVo.java | 4 +- .../stgl/mapper/StglActivityAuditMapper.java | 42 ++ .../service/IStglActivityAuditService.java | 94 +++++ .../stgl/service/IStglActivityService.java | 4 + .../impl/StglActivityAuditServiceImpl.java | 388 ++++++++++++++++++ .../service/impl/StglActivityServiceImpl.java | 114 ++++- .../impl/StglClubAnnualReviewServiceImpl.java | 2 +- .../impl/StglClubPermissionServiceImpl.java | 113 ++++- .../resources/application-stgl.properties | 7 + .../mapper/stgl/StglActivityAuditMapper.xml | 82 ++++ web/src/api/stgl/activity/audit.ts | 131 ++++++ web/src/api/stgl/activity/index.ts | 4 + .../activity/components/ApprovalDialog.vue | 310 ++++++++++++++ .../components/ApprovalProcessDialog.vue | 323 +++++++++++++++ web/src/views/stgl/activity/index.vue | 308 ++++++++------ 23 files changed, 2236 insertions(+), 141 deletions(-) create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/config/AnnualReviewConfig.java create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglActivityAuditController.java create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivityAudit.java create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityApprovalBo.java create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityAuditVo.java create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglActivityAuditMapper.java create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglActivityAuditService.java create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityAuditServiceImpl.java create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/resources/application-stgl.properties create mode 100644 backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityAuditMapper.xml create mode 100644 web/src/api/stgl/activity/audit.ts create mode 100644 web/src/views/stgl/activity/components/ApprovalDialog.vue create mode 100644 web/src/views/stgl/activity/components/ApprovalProcessDialog.vue diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/config/AnnualReviewConfig.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/config/AnnualReviewConfig.java new file mode 100644 index 00000000..1c8bdb84 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/config/AnnualReviewConfig.java @@ -0,0 +1,62 @@ +package org.dromara.stgl.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 年审配置类 + * + * @author Cursor AI + * @date 2025-01-15 + */ +@Data +@Component +@ConfigurationProperties(prefix = "stgl.club.annual-review") +@PropertySource("classpath:application-stgl.properties") +public class AnnualReviewConfig { + + /** + * 项目上线时间(字符串格式:yyyy-MM-dd HH:mm:ss) + */ + private String projectLaunchDate = "2025-01-01 00:00:00"; + + /** + * 历史数据宽限期(年) + */ + private int gracePeriodYears = 1; + + /** + * 年审窗口期天数 + */ + private int windowDays = 15; + + /** + * 获取项目上线时间的Date对象 + * + * @return 项目上线时间 + */ + public Date getProjectLaunchDateTime() { + try { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + return sdf.parse(projectLaunchDate); + } catch (ParseException e) { + throw new RuntimeException("项目上线时间格式错误: " + projectLaunchDate, e); + } + } + + /** + * 是否为历史社团 + * + * @param establishDate 社团成立时间 + * @return true-历史社团,false-新成立社团 + */ + public boolean isHistoricalClub(Date establishDate) { + return establishDate.before(getProjectLaunchDateTime()); + } +} diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglActivityAuditController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglActivityAuditController.java new file mode 100644 index 00000000..7163521e --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglActivityAuditController.java @@ -0,0 +1,111 @@ +package org.dromara.stgl.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.web.core.BaseController; +import org.dromara.stgl.domain.StglActivityAudit; +import org.dromara.stgl.domain.bo.StglActivityApprovalBo; +import org.dromara.stgl.domain.vo.StglActivityAuditVo; +import org.dromara.stgl.service.IStglActivityAuditService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +/** + * 活动审批记录 + * + * @author ruoyi + * @date 2025-01-09 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/stgl/activityAudit") +public class StglActivityAuditController extends BaseController { + + private final IStglActivityAuditService stglActivityAuditService; + + /** + * 查询活动审批记录详细 + */ + @SaCheckPermission("stgl:activity:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(stglActivityAuditService.queryById(id)); + } + + /** + * 根据活动ID查询审批记录 + */ + @SaCheckPermission("stgl:activity:query") + @GetMapping("/byActivity/{activityId}") + public R> getByActivityId(@NotNull(message = "活动ID不能为空") + @PathVariable Long activityId) { + return R.ok(stglActivityAuditService.queryByActivityId(activityId)); + } + + /** + * 根据审批人ID查询待审批记录 + */ + @SaCheckPermission("stgl:activity:query") + @GetMapping("/pending/{approverId}") + public R> getPendingByApproverId(@NotNull(message = "审批人ID不能为空") + @PathVariable Long approverId) { + return R.ok(stglActivityAuditService.queryPendingByApproverId(approverId)); + } + + /** + * 新增活动审批记录 + */ + @SaCheckPermission("stgl:activity:add") + @Log(title = "活动审批记录", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody StglActivityAudit stglActivityAudit) { + return toAjax(stglActivityAuditService.insertByBo(stglActivityAudit)); + } + + /** + * 修改活动审批记录 + */ + @SaCheckPermission("stgl:activity:edit") + @Log(title = "活动审批记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody StglActivityAudit stglActivityAudit) { + return toAjax(stglActivityAuditService.updateByBo(stglActivityAudit)); + } + + /** + * 删除活动审批记录 + */ + @SaCheckPermission("stgl:activity:remove") + @Log(title = "活动审批记录", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(stglActivityAuditService.deleteWithValidByIds(List.of(ids), true)); + } + + /** + * 审批活动 + */ + @SaCheckPermission("stgl:activity:approve") + @Log(title = "活动审批", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PostMapping("/approve") + public R approve(@Validated @RequestBody StglActivityApprovalBo approvalBo) { + return toAjax(stglActivityAuditService.approveActivity(approvalBo)); + } +} + diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglActivityController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglActivityController.java index abf49ded..2b69b665 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglActivityController.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglActivityController.java @@ -156,4 +156,8 @@ public class StglActivityController extends BaseController { return R.ok(stglActivityService.getActivityRatings(id)); } + // 注意:提交审批和撤回审批功能已改为通过修改接口(edit)更新活动状态实现 + // 前端调用 updateStglActivity 修改状态为8(已提交)或0(草稿) + // 后端在 updateByBo 方法中监听状态变化,自动创建或删除审批记录 + } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java index 2ed27089..84aa17c4 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java @@ -3,6 +3,7 @@ package org.dromara.stgl.domain; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; @@ -32,6 +33,13 @@ public class StglActivity extends TenantEntity { @TableId(value = "id", type = IdType.ASSIGN_ID) private Long id; + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + @TableField("deleted") + private Integer deleted; + /** * 活动名称 */ @@ -107,10 +115,10 @@ public class StglActivity extends TenantEntity { private String relatedDeptNames; /** - * 关联社团ID + * 关联社团ID(String类型以避免雪花ID精度丢失) */ @TableField("related_club_id") - private Long relatedClubId; + private String relatedClubId; /** * 关联类型(1-社团 2-志愿者队伍) diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivityAudit.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivityAudit.java new file mode 100644 index 00000000..468758ea --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivityAudit.java @@ -0,0 +1,91 @@ +package org.dromara.stgl.domain; + +import org.dromara.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 活动审批记录对象 stgl_activity_audit + * + * @author ruoyi + * @date 2025-01-09 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("stgl_activity_audit") +public class StglActivityAudit extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId + private Long id; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer deleted; + + /** + * 活动ID + */ + private Long activityId; + + /** + * 节点代码(advisor_approve/dept_approve/school_approve) + */ + private String nodeCode; + + /** + * 节点名称(指导老师审批/部门审批/校级审批) + */ + private String nodeName; + + /** + * 节点顺序(1.1,1.2,2.1,2.2等) + */ + private BigDecimal nodeOrder; + + /** + * 节点状态(pending/waiting/approved) + */ + @TableField("audit_status") + private String nodeStatus; + + /** + * 审批人ID + */ + private Long approverId; + + /** + * 审批人姓名 + */ + private String approverName; + + /** + * 审批结果(通过/不通过) + */ + private String approveResult; + + /** + * 审批意见 + */ + @TableField("audit_opinion") + private String approveOpinion; + + /** + * 审批时间 + */ + @TableField("audit_time") + private Date approveTime; +} + diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityApprovalBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityApprovalBo.java new file mode 100644 index 00000000..928b9c77 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityApprovalBo.java @@ -0,0 +1,61 @@ +package org.dromara.stgl.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 活动审批请求业务对象 + * + * @author ruoyi + * @date 2025-01-09 + */ +@Data +public class StglActivityApprovalBo { + + /** + * 审批记录ID + */ + @NotNull(message = "审批记录ID不能为空") + private Long auditId; + + /** + * 审批结果 + */ + @NotBlank(message = "审批结果不能为空") + private String approveResult; + + /** + * 审批意见 + */ + private String approveOpinion; + + /** + * 下一步审核人列表 + */ + private List nextAuditors; + + /** + * 下一步审核人信息 + */ + @Data + public static class NextAuditor { + /** + * 用户ID + */ + private Long userId; + + /** + * 用户名 + */ + private String userName; + + /** + * 昵称 + */ + private String nickName; + } +} + diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityBo.java index df878a3d..594b85db 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityBo.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglActivityBo.java @@ -97,9 +97,9 @@ public class StglActivityBo extends BaseEntity { private String relatedDeptNames; /** - * 关联社团ID + * 关联社团ID(String类型以避免雪花ID精度丢失) */ - private Long relatedClubId; + private String relatedClubId; /** * 关联类型 (1: 社团, 2: 志愿者队伍) diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityAuditVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityAuditVo.java new file mode 100644 index 00000000..a0798423 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityAuditVo.java @@ -0,0 +1,106 @@ +package org.dromara.stgl.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + * 活动审批记录视图对象 stgl_activity_audit + * + * @author ruoyi + * @date 2025-01-09 + */ +@Data +public class StglActivityAuditVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + private Long id; + + /** + * 活动ID + */ + private Long activityId; + + /** + * 活动名称 + */ + private String activityName; + + /** + * 节点代码(advisor_approve/dept_approve/school_approve) + */ + private String nodeCode; + + /** + * 节点名称(指导老师审批/部门审批/校级审批) + */ + private String nodeName; + + /** + * 节点顺序(1.1,1.2,2.1,2.2等) + */ + private BigDecimal nodeOrder; + + /** + * 节点状态(pending/waiting/approved) + */ + private String nodeStatus; + + /** + * 审批人ID + */ + private Long approverId; + + /** + * 审批人姓名 + */ + private String approverName; + + /** + * 审批结果(通过/不通过) + */ + private String approveResult; + + /** + * 审批意见 + */ + private String approveOpinion; + + /** + * 审批时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date approveTime; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; +} + diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java index a4e7ca65..221a73bb 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java @@ -110,9 +110,9 @@ public class StglActivityVo implements Serializable { private String relatedDeptNames; /** - * 关联社团ID + * 关联社团ID(String类型以避免雪花ID精度丢失) */ - private Long relatedClubId; + private String relatedClubId; /** * 关联类型(1-社团 2-志愿者队伍) diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglActivityAuditMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglActivityAuditMapper.java new file mode 100644 index 00000000..3eec2a65 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglActivityAuditMapper.java @@ -0,0 +1,42 @@ +package org.dromara.stgl.mapper; + +import org.dromara.stgl.domain.StglActivityAudit; +import org.dromara.stgl.domain.vo.StglActivityAuditVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +import java.util.List; + +/** + * 活动审批记录Mapper接口 + * + * @author ruoyi + * @date 2025-01-09 + */ +public interface StglActivityAuditMapper extends BaseMapperPlus { + + /** + * 根据活动ID查询审批记录 + * + * @param activityId 活动ID + * @return 审批记录列表 + */ + List selectByActivityId(Long activityId); + + /** + * 根据活动ID和节点代码查询审批记录 + * + * @param activityId 活动ID + * @param nodeCode 节点代码 + * @return 审批记录列表 + */ + List selectByActivityIdAndNodeCode(Long activityId, String nodeCode); + + /** + * 根据审批人ID查询待审批记录 + * + * @param approverId 审批人ID + * @return 审批记录列表 + */ + List selectPendingByApproverId(Long approverId); +} + diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglActivityAuditService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglActivityAuditService.java new file mode 100644 index 00000000..a48f631a --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglActivityAuditService.java @@ -0,0 +1,94 @@ +package org.dromara.stgl.service; + +import org.dromara.stgl.domain.StglActivityAudit; +import org.dromara.stgl.domain.bo.StglActivityApprovalBo; +import org.dromara.stgl.domain.vo.StglActivityAuditVo; + +import java.util.Collection; +import java.util.List; + +/** + * 活动审批记录Service接口 + * + * @author ruoyi + * @date 2025-01-09 + */ +public interface IStglActivityAuditService { + + /** + * 查询活动审批记录 + * + * @param id 主键ID + * @return 审批记录 + */ + StglActivityAudit queryById(Long id); + + /** + * 根据活动ID查询审批记录 + * + * @param activityId 活动ID + * @return 审批记录列表 + */ + List queryByActivityId(Long activityId); + + /** + * 根据活动ID和节点代码查询审批记录 + * + * @param activityId 活动ID + * @param nodeCode 节点代码 + * @return 审批记录列表 + */ + List queryByActivityIdAndNodeCode(Long activityId, String nodeCode); + + /** + * 根据审批人ID查询待审批记录 + * + * @param approverId 审批人ID + * @return 审批记录列表 + */ + List queryPendingByApproverId(Long approverId); + + /** + * 新增活动审批记录 + * + * @param bo 审批记录业务对象 + * @return 是否成功 + */ + Boolean insertByBo(StglActivityAudit bo); + + /** + * 修改活动审批记录 + * + * @param bo 审批记录业务对象 + * @return 是否成功 + */ + Boolean updateByBo(StglActivityAudit bo); + + /** + * 批量删除活动审批记录 + * + * @param ids 主键ID列表 + * @param isValid 是否进行有效性校验 + * @return 是否成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 创建指导老师审批记录 + * + * @param activityId 活动ID + * @param advisorIds 指导老师ID列表 + * @param advisorNames 指导老师姓名列表 + * @return 是否成功 + */ + Boolean createAdvisorApprovalRecords(Long activityId, List advisorIds, List advisorNames); + + /** + * 审批活动(支持动态审核人选择) + * + * @param approvalBo 审批业务对象 + * @return 是否成功 + */ + Boolean approveActivity(StglActivityApprovalBo approvalBo); +} + diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglActivityService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglActivityService.java index 1424d59d..8e0fd4f2 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglActivityService.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglActivityService.java @@ -123,4 +123,8 @@ public interface IStglActivityService extends IService { */ List getActivityRatings(Long activityId); + // 注意:提交审批和撤回审批功能已改为通过 updateByBo 方法监听状态变化实现 + // 前端调用 updateStglActivity 修改状态为8(已提交)或0(草稿) + // 后端在 updateByBo 方法中自动创建或删除审批记录 + } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityAuditServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityAuditServiceImpl.java new file mode 100644 index 00000000..c4a9747b --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityAuditServiceImpl.java @@ -0,0 +1,388 @@ +package org.dromara.stgl.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.stgl.domain.StglActivity; +import org.dromara.stgl.domain.StglActivityAudit; +import org.dromara.stgl.domain.bo.StglActivityApprovalBo; +import org.dromara.stgl.domain.vo.StglActivityAuditVo; +import org.dromara.stgl.mapper.StglActivityAuditMapper; +import org.dromara.stgl.mapper.StglActivityMapper; +import org.dromara.stgl.service.IStglActivityAuditService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * 活动审批记录Service业务层处理 + * + * @author ruoyi + * @date 2025-01-09 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class StglActivityAuditServiceImpl implements IStglActivityAuditService { + + private final StglActivityAuditMapper baseMapper; + private final StglActivityMapper activityMapper; + + /** + * 查询活动审批记录 + */ + @Override + public StglActivityAudit queryById(Long id) { + return baseMapper.selectById(id); + } + + /** + * 根据活动ID查询审批记录 + */ + @Override + public List queryByActivityId(Long activityId) { + return baseMapper.selectByActivityId(activityId); + } + + /** + * 根据活动ID和节点代码查询审批记录 + */ + @Override + public List queryByActivityIdAndNodeCode(Long activityId, String nodeCode) { + return baseMapper.selectByActivityIdAndNodeCode(activityId, nodeCode); + } + + /** + * 根据审批人ID查询待审批记录 + */ + @Override + public List queryPendingByApproverId(Long approverId) { + return baseMapper.selectPendingByApproverId(approverId); + } + + /** + * 新增活动审批记录 + */ + @Override + public Boolean insertByBo(StglActivityAudit bo) { + StglActivityAudit add = BeanUtil.toBean(bo, StglActivityAudit.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改活动审批记录 + */ + @Override + public Boolean updateByBo(StglActivityAudit bo) { + StglActivityAudit update = BeanUtil.toBean(bo, StglActivityAudit.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(StglActivityAudit entity) { + // TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除活动审批记录 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if (isValid) { + // TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } + + /** + * 创建指导老师审批记录 + */ + @Override + @Transactional + public Boolean createAdvisorApprovalRecords(Long activityId, List advisorIds, List advisorNames) { + if (ObjectUtil.isEmpty(advisorIds) || ObjectUtil.isEmpty(advisorNames) || advisorIds.size() != advisorNames.size()) { + return false; + } + + // 删除已存在的指导老师审批记录 + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(StglActivityAudit::getActivityId, activityId) + .eq(StglActivityAudit::getNodeCode, "advisor_approve"); + baseMapper.delete(wrapper); + + // 创建新的指导老师审批记录 + for (int i = 0; i < advisorIds.size(); i++) { + StglActivityAudit audit = new StglActivityAudit(); + audit.setActivityId(activityId); + audit.setNodeCode("advisor_approve"); + audit.setNodeName("指导老师审批"); + + // 设置节点顺序:单个指导老师为1.0,多个指导老师为1.1, 1.2, 1.3... + if (advisorIds.size() == 1) { + audit.setNodeOrder(new BigDecimal("1.0")); + } else { + audit.setNodeOrder(new BigDecimal("1." + (i + 1))); + } + + // 设置节点状态:第一个为pending,其他为waiting + audit.setNodeStatus(i == 0 ? "pending" : "waiting"); + audit.setApproverId(advisorIds.get(i)); + audit.setApproverName(advisorNames.get(i)); + + baseMapper.insert(audit); + } + + return true; + } + + /** + * 审批活动(支持动态审核人选择) + */ + @Override + @Transactional + public Boolean approveActivity(StglActivityApprovalBo approvalBo) { + // 1. 获取当前审批记录 + StglActivityAudit currentAudit = baseMapper.selectById(approvalBo.getAuditId()); + if (currentAudit == null || !"pending".equals(currentAudit.getNodeStatus())) { + return false; + } + + // 2. 更新当前审批记录 + currentAudit.setNodeStatus("approved"); + currentAudit.setApproveResult(approvalBo.getApproveResult()); + currentAudit.setApproveOpinion(approvalBo.getApproveOpinion()); + currentAudit.setApproveTime(new Date()); + baseMapper.updateById(currentAudit); + + // 3. 如果审批不通过,直接更新活动状态为7(已驳回) + if ("不通过".equals(approvalBo.getApproveResult())) { + StglActivity activity = activityMapper.selectById(currentAudit.getActivityId()); + if (activity != null) { + LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); + updateWrapper.eq(StglActivity::getId, currentAudit.getActivityId()) + .set(StglActivity::getActivityStatus, "7"); + activityMapper.update(null, updateWrapper); + } + return true; + } + + // 4. 如果审批通过,检查是否有下一个待审批的同环节审批人 + List samePhaseAudits = baseMapper.selectByActivityIdAndNodeCode( + currentAudit.getActivityId(), currentAudit.getNodeCode()); + + // 查找下一个waiting状态的审批记录 + StglActivityAudit nextAudit = null; + for (StglActivityAudit audit : samePhaseAudits) { + if ("waiting".equals(audit.getNodeStatus()) && + audit.getNodeOrder().compareTo(currentAudit.getNodeOrder()) > 0) { + if (nextAudit == null || audit.getNodeOrder().compareTo(nextAudit.getNodeOrder()) < 0) { + nextAudit = audit; + } + } + } + + // 5. 如果有下一个审批人,将其状态改为pending + if (nextAudit != null) { + nextAudit.setNodeStatus("pending"); + baseMapper.updateById(nextAudit); + } else { + // 6. 如果同环节所有审批人都审批通过,检查是否是第一个审批节点 + boolean allApproved = samePhaseAudits.stream() + .allMatch(audit -> "approved".equals(audit.getNodeStatus()) && "通过".equals(audit.getApproveResult())); + + if (allApproved) { + // 只有第一个审批节点(node_order=1.1 或 1.0)通过时才设置为9-审核中 + if (isFirstApprovalNode(currentAudit)) { + StglActivity activity = activityMapper.selectById(currentAudit.getActivityId()); + if (activity != null) { + LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); + updateWrapper.eq(StglActivity::getId, currentAudit.getActivityId()) + .set(StglActivity::getActivityStatus, "9"); + activityMapper.update(null, updateWrapper); + log.info("第一个审批节点通过,活动状态更新为9-审核中,活动ID: {}", activity.getId()); + } + } + + // 如果有指定下一步审核人,创建新的审批记录 + if (approvalBo.getNextAuditors() != null && !approvalBo.getNextAuditors().isEmpty()) { + createNextStepAuditRecords(currentAudit.getActivityId(), approvalBo.getNextAuditors()); + } else if (isLastApprovalNode(currentAudit, samePhaseAudits)) { + // 如果是最后一个节点且没有下一步审核人,根据活动时间判断最终状态 + StglActivity activity = activityMapper.selectById(currentAudit.getActivityId()); + String finalStatus = determineFinalStatus(activity); + + LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); + updateWrapper.eq(StglActivity::getId, currentAudit.getActivityId()) + .set(StglActivity::getActivityStatus, finalStatus); + activityMapper.update(null, updateWrapper); + log.info("最后审批节点通过,活动状态更新为: {}, 活动ID: {}", finalStatus, activity.getId()); + } + } + } + + return true; + } + + /** + * 创建下一步审批记录 + */ + private void createNextStepAuditRecords(Long activityId, List nextAuditors) { + // 查询已有的审批记录,确定下一个审批层级 + List existingAudits = baseMapper.selectByActivityId(activityId); + + // 确定下一个审批层级 + int nextLevel = determineNextApprovalLevel(existingAudits); + + for (int i = 0; i < nextAuditors.size(); i++) { + StglActivityApprovalBo.NextAuditor auditor = nextAuditors.get(i); + + StglActivityAudit nextAudit = new StglActivityAudit(); + nextAudit.setActivityId(activityId); + nextAudit.setNodeCode(getNodeCodeByLevel(nextLevel)); + nextAudit.setNodeName(getNodeNameByLevel(nextLevel)); + + // 根据层级和顺序设置node_order + if (nextAuditors.size() == 1) { + // 单个审核人为 X.0 + nextAudit.setNodeOrder(new BigDecimal(nextLevel + ".0")); + } else { + // 多个审核人为 X.1, X.2, X.3... + nextAudit.setNodeOrder(new BigDecimal(nextLevel + "." + (i + 1))); + } + + nextAudit.setApproverId(auditor.getUserId()); + nextAudit.setApproverName(auditor.getNickName() != null ? auditor.getNickName() : auditor.getUserName()); + nextAudit.setNodeStatus(i == 0 ? "pending" : "waiting"); // 第一个为pending,其余为waiting + nextAudit.setCreateTime(new Date()); + + baseMapper.insert(nextAudit); + } + } + + /** + * 确定下一个审批层级 + */ + private int determineNextApprovalLevel(List existingAudits) { + int maxLevel = 1; // 默认从指导老师审批层级开始 + + for (StglActivityAuditVo audit : existingAudits) { + BigDecimal nodeOrder = audit.getNodeOrder(); + if (nodeOrder != null) { + int level = nodeOrder.intValue(); // 获取整数部分作为层级 + if (level > maxLevel) { + maxLevel = level; + } + } + } + + return maxLevel + 1; // 下一个层级 + } + + /** + * 根据层级获取节点代码 + */ + private String getNodeCodeByLevel(int level) { + switch (level) { + case 1: + return "advisor_approve"; + case 2: + return "dept_approve"; + case 3: + return "school_approve"; + default: + return "dept_approve"; // 默认为部门审批 + } + } + + /** + * 根据层级获取节点名称 + */ + private String getNodeNameByLevel(int level) { + switch (level) { + case 1: + return "指导老师审批"; + case 2: + return "部门审批"; + case 3: + return "校级审批"; + default: + return "部门审批"; // 默认为部门审批 + } + } + + /** + * 判断是否为第一个审批节点(node_order=1.1 或 1.0) + */ + private boolean isFirstApprovalNode(StglActivityAudit currentAudit) { + if (currentAudit == null || currentAudit.getNodeOrder() == null) { + return false; + } + + BigDecimal nodeOrder = currentAudit.getNodeOrder(); + // 检查node_order是否为1.1或1.0 + return nodeOrder.compareTo(new BigDecimal("1.0")) == 0 || + nodeOrder.compareTo(new BigDecimal("1.1")) == 0; + } + + /** + * 判断是否为最后一个审批节点 + */ + private boolean isLastApprovalNode(StglActivityAudit currentAudit, List allAudits) { + if (currentAudit == null || currentAudit.getNodeOrder() == null || allAudits == null || allAudits.isEmpty()) { + return false; + } + + // 获取所有审批记录中最大的nodeOrder + BigDecimal maxNodeOrder = allAudits.stream() + .map(StglActivityAudit::getNodeOrder) + .filter(order -> order != null) + .max(BigDecimal::compareTo) + .orElse(BigDecimal.ZERO); + + // 当前节点的nodeOrder等于最大nodeOrder时,表示是最后一个节点 + boolean isLast = currentAudit.getNodeOrder().compareTo(maxNodeOrder) == 0; + + log.info("最后节点判断: 当前节点={}, nodeOrder={}, 最大nodeOrder={}, 是否最后节点={}", + currentAudit.getNodeName(), currentAudit.getNodeOrder(), maxNodeOrder, isLast); + + return isLast; + } + + /** + * 根据活动时间判断最终状态 + */ + private String determineFinalStatus(StglActivity activity) { + if (activity == null) { + return "1"; // 默认未开始 + } + + Date now = new Date(); + Date startTime = activity.getStartTime(); + Date endTime = activity.getEndTime(); + + if (startTime != null && now.before(startTime)) { + return "1"; // 未开始 + } else if (endTime != null && now.after(endTime)) { + return "5"; // 已结束 + } else { + return "4"; // 进行中 + } + } +} + diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java index c14459b9..48d231f0 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglActivityServiceImpl.java @@ -17,13 +17,17 @@ import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.stgl.domain.StglActivity; +import org.dromara.stgl.domain.StglActivityAudit; import org.dromara.stgl.domain.bo.StglActivityBo; import org.dromara.stgl.domain.vo.StglActivityVo; import org.dromara.stgl.domain.vo.ActivityRatingVo; import org.dromara.stgl.mapper.StglActivityMapper; +import org.dromara.stgl.mapper.StglActivityAuditMapper; import org.dromara.stgl.mapper.StglActivityRatingMapper; import org.dromara.stgl.mapper.StglMemberMapper; import org.dromara.stgl.service.IStglActivityService; +import org.dromara.stgl.service.IStglActivityAuditService; +import org.dromara.stgl.service.IStglClubService; import org.dromara.stgl.utils.AdvisorRoleAssigner; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -69,6 +73,9 @@ public class StglActivityServiceImpl extends ServiceImpl advisorIds = new ArrayList<>(); + List advisorNames = new ArrayList<>(); + + if (StringUtils.isNotBlank(clubVo.getAdvisorsJson())) { + // 解析社团指导老师JSON数据 + List> advisors = + JsonUtils.parseObject(clubVo.getAdvisorsJson(), + new TypeReference>>() {}); + + if (advisors != null) { + for (Map advisor : advisors) { + // 安全地转换userId,支持String和Number类型 + Long userId = null; + Object userIdObj = advisor.get("userId"); + if (userIdObj != null) { + if (userIdObj instanceof Number) { + userId = ((Number) userIdObj).longValue(); + } else if (userIdObj instanceof String) { + try { + userId = Long.valueOf((String) userIdObj); + } catch (NumberFormatException e) { + log.warn("无法解析指导老师userId: {}", userIdObj, e); + continue; + } + } + } + + String name = (String) advisor.get("name"); + if (userId != null && StringUtils.isNotBlank(name)) { + advisorIds.add(userId); + advisorNames.add(name); + } + } + } + } + + if (advisorIds.isEmpty()) { + throw new RuntimeException("活动关联的社团没有配置指导老师,无法提交审批"); + } + + // 创建指导老师审批记录 + activityAuditService.createAdvisorApprovalRecords( + update.getId(), advisorIds, advisorNames); + + log.info("自动创建审批记录成功 - activityId: {}, advisorCount: {}", + update.getId(), advisorIds.size()); + } + + // 监听状态变化:如果状态从8(已提交)变为0(草稿),自动删除审批记录 + if (oldActivity != null && + "8".equals(oldActivity.getActivityStatus()) && + "0".equals(update.getActivityStatus())) { + + log.info("检测到活动状态从已提交变为草稿,自动删除审批记录 - activityId: {}", update.getId()); + + // 删除所有审批记录 + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + queryWrapper.eq(StglActivityAudit::getActivityId, update.getId()); + List audits = activityAuditMapper.selectList(queryWrapper); + + if (!audits.isEmpty()) { + activityAuditService.deleteWithValidByIds( + audits.stream().map(StglActivityAudit::getId).collect(Collectors.toList()), + false); + log.info("自动删除审批记录成功 - activityId: {}, auditCount: {}", + update.getId(), audits.size()); + } + } } return updateResult; @@ -908,4 +1015,7 @@ public class StglActivityServiceImpl extends ServiceImpl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, tenant_id, create_dept, create_by, create_time, update_by, update_time, deleted, + activity_id, node_code, node_name, node_order, audit_status, + approver_id, approver_name, approve_result, audit_opinion, audit_time + from stgl_activity_audit + + + + + + + + + + + + + diff --git a/web/src/api/stgl/activity/audit.ts b/web/src/api/stgl/activity/audit.ts new file mode 100644 index 00000000..1d2e1bf5 --- /dev/null +++ b/web/src/api/stgl/activity/audit.ts @@ -0,0 +1,131 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; + +/** + * 活动审批记录VO + */ +export interface ActivityAuditVO { + id?: number; + activityId?: number; + activityName?: string; + nodeCode?: string; + nodeName?: string; + nodeOrder?: number; + nodeStatus?: string; + approverId?: number; + approverName?: string; + approveResult?: string; + approveOpinion?: string; + approveTime?: string; + createBy?: number; + createTime?: string; + updateBy?: number; + updateTime?: string; +} + +/** + * 活动审批请求参数 + */ +export interface ActivityApprovalForm { + auditId: number; + approveResult: string; + approveOpinion?: string; + nextAuditors?: NextAuditor[]; +} + +/** + * 下一步审核人信息 + */ +export interface NextAuditor { + userId: number; + userName: string; + nickName: string; +} + +/** + * 查询活动审批记录详细 + * @param id 审批记录主键 + * @returns 审批记录详情 + */ +export const getActivityAudit = (id: string | number): AxiosPromise => { + return request({ + url: '/stgl/activityAudit/' + id, + method: 'get' + }); +}; + +/** + * 根据活动ID查询审批记录 + * @param activityId 活动ID + * @returns 审批记录列表 + */ +export const getActivityAuditsByActivityId = (activityId: string | number): AxiosPromise => { + return request({ + url: '/stgl/activityAudit/byActivity/' + activityId, + method: 'get' + }); +}; + +/** + * 根据审批人ID查询待审批记录 + * @param approverId 审批人ID + * @returns 审批记录列表 + */ +export const getPendingActivityAudits = (approverId: string | number): AxiosPromise => { + return request({ + url: '/stgl/activityAudit/pending/' + approverId, + method: 'get' + }); +}; + +/** + * 审批活动 + * @param data 审批参数 + * @returns 操作结果 + */ +export const approveActivity = (data: ActivityApprovalForm) => { + return request({ + url: '/stgl/activityAudit/approve', + method: 'post', + data: data + }); +}; + +/** + * 新增活动审批记录 + * @param data 审批记录信息 + * @returns 操作结果 + */ +export const addActivityAudit = (data: ActivityAuditVO) => { + return request({ + url: '/stgl/activityAudit', + method: 'post', + data: data + }); +}; + +/** + * 修改活动审批记录 + * @param data 审批记录信息 + * @returns 操作结果 + */ +export const updateActivityAudit = (data: ActivityAuditVO) => { + return request({ + url: '/stgl/activityAudit', + method: 'put', + data: data + }); +}; + +/** + * 删除活动审批记录 + * @param id 审批记录主键 + * @returns 操作结果 + */ +export const delActivityAudit = (id: string | number | Array) => { + return request({ + url: '/stgl/activityAudit/' + id, + method: 'delete' + }); +}; + diff --git a/web/src/api/stgl/activity/index.ts b/web/src/api/stgl/activity/index.ts index 6831931f..e3f9d826 100644 --- a/web/src/api/stgl/activity/index.ts +++ b/web/src/api/stgl/activity/index.ts @@ -146,3 +146,7 @@ export const getActivityRatings = (activityId: string | number): AxiosPromise + + + + {{ activityInfo?.activityName || '加载中...' }} + + + + {{ activityInfo?.activityType || '加载中...' }} + + + + {{ auditRecord?.nodeName || '加载中...' }} + + + + + 通过 + 不通过 + + + + + + + + + + + + + + + + + + + diff --git a/web/src/views/stgl/activity/components/ApprovalProcessDialog.vue b/web/src/views/stgl/activity/components/ApprovalProcessDialog.vue new file mode 100644 index 00000000..12fa0956 --- /dev/null +++ b/web/src/views/stgl/activity/components/ApprovalProcessDialog.vue @@ -0,0 +1,323 @@ + + + + + + diff --git a/web/src/views/stgl/activity/index.vue b/web/src/views/stgl/activity/index.vue index 64ff9d04..fafb5858 100644 --- a/web/src/views/stgl/activity/index.vue +++ b/web/src/views/stgl/activity/index.vue @@ -117,12 +117,20 @@ !single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '0' && hasActivityOperationPermission(selectedActivity) " > - 提交 + 提交审批 + 撤回审批 + + 审批 @@ -283,7 +291,7 @@ > @@ -293,14 +301,29 @@ icon="Upload" size="default" style="font-size: 16px; margin-right: 8px" - @click="handleSubmit(scope.row)" + @click="handleSubmitApproval(scope.row)" + v-hasPermi="['stgl:activity:edit']" + > + + + - - - - - - - - - 通过 - 驳回 - - - - - - - - + + @@ -712,9 +714,11 @@ import ActivityRatingView from './components/ActivityRatingView.vue'; import ActivityDetailView from './components/ActivityDetailView.vue'; import ActivityMembersDialog from './components/ActivityMembersDialog.vue'; import ActivityRegistrationView from './components/ActivityRegistrationView.vue'; // Added import +import ApprovalDialog from './components/ApprovalDialog.vue'; import { AdvisorVO } from '@/components/AdvisorManager/index.vue'; import { useUserStore } from '@/store/modules/user'; import { checkClubActivityPermission, type ClubActivityPermissionResult } from '@/api/stgl/clubPermission'; +import { getPendingActivityAudits } from '@/api/stgl/activity/audit'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; const route = useRoute(); @@ -752,8 +756,8 @@ const queryFormRef = ref(); const activityFormRef = ref(); const copyFormRef = ref(); const summaryFormRef = ref(); -const approveFormRef = ref(); const membersDialogRef = ref(); +const approvalDialogRef = ref>(); const dialog = reactive({ visible: false, @@ -780,23 +784,11 @@ const membersDialog = reactive({ title: '' }); -// 审批对话框 -const approveDialog = reactive({ - visible: false, - title: '' -}); - -// 审批表单 -const approveForm = reactive({ - id: undefined as string | number | undefined, - activityName: '', - approvalResult: 'approve' as 'approve' | 'reject', - rejectReason: '' -}); +// 待审批活动缓存(用于权限判断) +const pendingAuditsCache = ref>(new Map()); const copyLoading = ref(false); const summaryLoading = ref(false); -const approveLoading = ref(false); // 关联社团禁用状态控制 const isRelatedClubDisabled = ref(false); @@ -1005,12 +997,6 @@ const reset = () => { // 重置关联社团禁用状态 isRelatedClubDisabled.value = false; - // 重置审批表单 - approveForm.id = undefined; - approveForm.activityName = ''; - approveForm.approvalResult = 'approve'; - approveForm.rejectReason = ''; - // 更新formKey以强制重新渲染组件 formKey.value++; @@ -1689,6 +1675,12 @@ const handleRelatedDeptsChange = (depts: DeptOption[] | null) => { } }; +/** 查询表单 - 关联部门变化处理 */ +const handleQueryRelatedDeptChange = (dept: DeptOption | null) => { + queryParams.value.relatedDept = dept; + queryParams.value.relatedDeptId = dept?.deptId; +}; + /** 关联社团变化处理 */ const handleRelatedClubChange = (club: ClubOption | null) => { form.value.relatedClub = club; @@ -1776,18 +1768,135 @@ const handleSubmit = async (row?: StglActivityVO) => { } }; -/** 审批按钮操作 */ +/** 提交活动审批 */ +const handleSubmitApproval = async (row?: StglActivityVO) => { + const activity = row || selectedActivity.value; + if (!activity) return; + + // 调试信息:打印关联社团ID + console.log('🔍 提交审批 - 活动信息:', { + activityId: activity.id, + activityName: activity.activityName, + relatedClubId: activity.relatedClubId, + relatedClubIdType: typeof activity.relatedClubId, + relatedClubName: activity.relatedClubName + }); + + // 前置检查:必须关联社团 + if (!activity.relatedClubId) { + proxy?.$modal.msgError('该活动未关联社团,无法提交审批。请先编辑活动并选择关联社团。'); + return; + } + + try { + await proxy?.$modal.confirm(`是否确认提交活动"${activity.activityName}"进行审批?`); + + // 发送必填字段 + 业务必需字段(满足后端验证和业务逻辑) + // 注意:relatedClubId 保持字符串类型,避免雪花ID精度丢失 + // 后端 Bo 已修改为接受 String 类型,在 Service 层转换为 Long + const requestData = { + id: activity.id, + activityName: activity.activityName, + activityType: activity.activityType, + startTime: activity.startTime, + endTime: activity.endTime, + relatedClubId: String(activity.relatedClubId), // 保持字符串类型 + activityStatus: '8' // 8-已提交审批 + }; + + console.log('📤 发送审批请求:', requestData); + + await updateStglActivity(requestData); + + proxy?.$modal.msgSuccess('提交审批成功'); + await getList(); + // 重新加载待审批权限 + await loadPendingAudits(); + } catch (error) { + if (error !== 'cancel') { + console.error('❌ 提交审批失败:', error); + proxy?.$modal.msgError('提交审批失败'); + } + } +}; + +/** 撤回活动审批 */ +const handleWithdrawApproval = async (row?: StglActivityVO) => { + const activity = row || selectedActivity.value; + if (!activity) return; + + try { + await proxy?.$modal.confirm(`是否确认撤回活动"${activity.activityName}"的审批?`); + + // 发送必填字段(满足后端EditGroup验证) + await updateStglActivity({ + id: activity.id, + activityName: activity.activityName, + activityType: activity.activityType, + startTime: activity.startTime, + endTime: activity.endTime, + activityStatus: '0' // 0-草稿 + }); + + proxy?.$modal.msgSuccess('撤回审批成功'); + await getList(); + // 重新加载待审批权限 + await loadPendingAudits(); + } catch (error) { + if (error !== 'cancel') { + console.error('撤回审批失败:', error); + proxy?.$modal.msgError('撤回审批失败'); + } + } +}; + +/** 审批活动 */ const handleApprove = async (row?: StglActivityVO) => { const activity = row || selectedActivity.value; if (!activity) return; - // 显示审批对话框 - approveDialog.visible = true; - approveDialog.title = `审批活动 - ${activity.activityName}`; - approveForm.id = activity.id; - approveForm.activityName = activity.activityName || ''; - approveForm.approvalResult = 'approve'; - approveForm.rejectReason = ''; + // 从缓存中获取审批记录 + const auditRecord = pendingAuditsCache.value.get(Number(activity.id)); + if (!auditRecord) { + proxy?.$modal.msgWarning('无法获取审批记录信息'); + return; + } + + // 打开新的审批对话框 + approvalDialogRef.value?.open(auditRecord.id, activity.id); +}; + +/** 审批成功回调 */ +const handleApprovalSuccess = async () => { + await getList(); + // 重新加载待审批权限 + await loadPendingAudits(); +}; + +/** 判断是否可以审批活动 */ +const canApproveActivity = (activity: StglActivityVO | null): boolean => { + if (!activity) return false; + return pendingAuditsCache.value.has(Number(activity.id)); +}; + +/** 加载待审批权限 */ +const loadPendingAudits = async () => { + try { + const userId = userStore.userId; + const res = await getPendingActivityAudits(userId); + const audits = res.data || []; + + // 构建待审批缓存 + const cache = new Map(); + audits.forEach((audit: any) => { + cache.set(Number(audit.activityId), audit); + }); + pendingAuditsCache.value = cache; + + console.log('待审批活动缓存:', pendingAuditsCache.value); + } catch (error) { + console.error('加载待审批权限失败:', error); + } }; /** 取消活动按钮操作 */ @@ -1815,63 +1924,6 @@ const handleCancel = async (row?: StglActivityVO) => { } }; -/** 确认审批 */ -const confirmApprove = async () => { - if (!approveFormRef.value) return; - - approveLoading.value = true; - try { - const activity = activityList.value.find((item) => item.id === approveForm.id); - if (!activity) { - proxy?.$modal.msgError('活动信息不存在'); - return; - } - - let newStatus: string; - let successMessage: string; - - if (approveForm.approvalResult === 'approve') { - // 审批通过,根据时间比较确定状态 - const now = new Date(); - const startTime = new Date(activity.startTime); - const endTime = new Date(activity.endTime); - - if (now < startTime) { - newStatus = '3'; // 已通过(活动还未开始) - } else if (now >= startTime && now <= endTime) { - newStatus = '4'; // 进行中(活动正在进行) - } else { - newStatus = '5'; // 已结束(活动已结束) - } - successMessage = '审批通过'; - } else { - // 审批驳回 - if (!approveForm.rejectReason.trim()) { - proxy?.$modal.msgWarning('请输入驳回原因'); - return; - } - newStatus = '7'; // 已驳回 - successMessage = '审批驳回'; - } - - // 调用后端接口更新活动状态 - // 传递完整的活动数据,避免后端验证失败 - await updateStglActivity({ - ...activity, - activityStatus: newStatus - }); - - proxy?.$modal.msgSuccess(successMessage); - approveDialog.visible = false; - await getList(); - } catch (error) { - console.error('审批失败:', error); - proxy?.$modal.msgError('审批失败'); - } finally { - approveLoading.value = false; - } -}; - /** 格式化日期 */ const formatDate = (dateStr: string): string => { if (!dateStr) return ''; @@ -1989,6 +2041,28 @@ const getOriginalActivityStatus = (activity: StglActivityVO | null): string => { return activity.originalActivityStatus || activity.activityStatus || ''; }; +// 监听对话框打开,在新增模式下自动设置报名开始时间为当前时间 +watch( + () => dialog.visible, + (isVisible, wasVisible) => { + // 当对话框从关闭变为打开,且是新增模式(无ID)时,自动设置报名开始时间 + if (isVisible && !wasVisible && !form.value.id) { + nextTick(() => { + // 获取当前时间并格式化为 YYYY-MM-DD HH:mm:ss + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + + form.value.registrationStart = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + }); + } + } +); + // 监听活动开始时间变化,自动更新报名结束时间 watch( () => form.value.startTime, @@ -2019,9 +2093,11 @@ watch( onMounted(() => { initUrlParams(); getList(); + // 加载待审批权限 + loadPendingAudits(); // 如果有关联社团,检查权限 if (queryParams.value.relatedClub?.relatedClubId && queryParams.value.relatedClub?.relatedType === 1) { - checkClubPermission(Number(queryParams.value.relatedClub.relatedClubId)); + checkClubPermission(queryParams.value.relatedClub.relatedClubId); } }); @@ -2030,7 +2106,7 @@ watch( () => queryParams.value.relatedClub, (newRelatedClub) => { if (newRelatedClub?.relatedClubId && newRelatedClub.relatedType === 1) { - checkClubPermission(Number(newRelatedClub.relatedClubId)); + checkClubPermission(newRelatedClub.relatedClubId); } else { clubPermissionInfo.value = null; }