diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgHolidayStayController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgHolidayStayController.java index ef1b3666..69ac754d 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgHolidayStayController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgHolidayStayController.java @@ -5,7 +5,6 @@ import java.util.List; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.*; -import cn.dev33.satoken.annotation.SaCheckPermission; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; import org.dromara.common.idempotent.annotation.RepeatSubmit; @@ -19,6 +18,7 @@ import org.dromara.common.log.enums.BusinessType; import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.xg.domain.vo.XgHolidayStayVo; import org.dromara.xg.domain.bo.XgHolidayStayBo; +import org.dromara.xg.domain.bo.XgHolidayStayAuditBo; import org.dromara.xg.domain.vo.StudentOptionVo; import org.dromara.xg.domain.bo.StudentOptionBo; import org.dromara.xg.service.IXgHolidayStayService; @@ -48,7 +48,7 @@ public class XgHolidayStayController extends BaseController { } /** - * 导出假期留宿管理列表 + * 导出假期留宿管理列表(可按 applyStatus 筛选) */ // @SaCheckPermission("holidayStay:holidayStay:export") @Log(title = "假期留宿管理", businessType = BusinessType.EXPORT) @@ -58,10 +58,88 @@ public class XgHolidayStayController extends BaseController { ExcelUtil.exportExcel(list, "假期留宿管理", XgHolidayStayVo.class, response); } + /** + * 获取学生选择器选项列表(支持按学号、姓名检索) + */ + // @SaCheckPermission("holidayStay:holidayStay:list") + @GetMapping("/studentOptions") + public TableDataInfo getStudentOptions(StudentOptionBo bo, PageQuery pageQuery) { + return xgHolidayStayService.getStudentOptions(bo, pageQuery); + } + + /** + * 查询单人留宿有效数据 + */ + // @SaCheckPermission("holidayStay:holidayStay:list") + @GetMapping("/singlePersonValidStay") + public R> getSinglePersonValidStay() { + return R.ok(xgHolidayStayService.querySinglePersonValidStay()); + } + + /** + * 学生本人提交留宿申请 + */ + // @SaCheckPermission("holidayStay:holidayStay:apply") + @Log(title = "假期留宿申请", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/apply") + public R apply(@RequestBody XgHolidayStayBo bo) { + return toAjax(xgHolidayStayService.studentApply(bo)); + } + + /** + * 当前学生的申请列表 + */ + // @SaCheckPermission("holidayStay:holidayStay:apply") + @GetMapping("/myList") + public TableDataInfo myList(XgHolidayStayBo bo, PageQuery pageQuery) { + return xgHolidayStayService.queryMyApplyPage(bo, pageQuery); + } + + /** + * 班主任本班申请列表 + */ + // @SaCheckPermission("holidayStay:holidayStay:list1") + @GetMapping("/teacherList") + public TableDataInfo teacherList(XgHolidayStayBo bo, PageQuery pageQuery) { + return xgHolidayStayService.queryTeacherPage(bo, pageQuery); + } + + /** + * 班主任审批通过 + */ + // @SaCheckPermission("holidayStay:holidayStay:audit") + @Log(title = "假期留宿审批通过", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PostMapping("/approve") + public R approve(@Validated @RequestBody XgHolidayStayAuditBo auditBo) { + return toAjax(xgHolidayStayService.approve(auditBo)); + } + + /** + * 班主任驳回 + */ + // @SaCheckPermission("holidayStay:holidayStay:audit") + @Log(title = "假期留宿驳回", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PostMapping("/reject") + public R reject(@Validated @RequestBody XgHolidayStayAuditBo auditBo) { + return toAjax(xgHolidayStayService.reject(auditBo)); + } + + /** + * 班主任撤销已通过申请 + */ + // @SaCheckPermission("holidayStay:holidayStay:revoke") + @Log(title = "假期留宿撤销", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PostMapping("/revoke") + public R revoke(@Validated(XgHolidayStayAuditBo.Revoke.class) @RequestBody XgHolidayStayAuditBo auditBo) { + return toAjax(xgHolidayStayService.revoke(auditBo)); + } + /** * 获取假期留宿管理详细信息 - * - * @param id 主键 */ // @SaCheckPermission("holidayStay:holidayStay:query") @GetMapping("/{id}") @@ -70,7 +148,7 @@ public class XgHolidayStayController extends BaseController { } /** - * 新增假期留宿管理 + * 新增假期留宿管理(宿管登记,默认已通过) */ // @SaCheckPermission("holidayStay:holidayStay:add") @Log(title = "假期留宿管理", businessType = BusinessType.INSERT) @@ -93,8 +171,6 @@ public class XgHolidayStayController extends BaseController { /** * 删除假期留宿管理 - * - * @param ids 主键串 */ // @SaCheckPermission("holidayStay:holidayStay:remove") @Log(title = "假期留宿管理", businessType = BusinessType.DELETE) @@ -102,24 +178,4 @@ public class XgHolidayStayController extends BaseController { public R remove(@NotEmpty(message = "主键不能为空") @PathVariable("ids") Long[] ids) { return toAjax(xgHolidayStayService.deleteWithValidByIds(List.of(ids), true)); } - - /** - * 获取学生选择器选项列表(支持按学号、姓名检索) - */ - // @SaCheckPermission("holidayStay:holidayStay:list") - @GetMapping("/studentOptions") - public TableDataInfo getStudentOptions(StudentOptionBo bo, PageQuery pageQuery) { - return xgHolidayStayService.getStudentOptions(bo, pageQuery); - } - - /** - * 查询单人留宿有效数据 - * 获取所有有效的假期留宿数据,筛选出在同一时间段内该宿舍只有一个学生留宿的记录 - */ - // @SaCheckPermission("holidayStay:holidayStay:list") - @GetMapping("/singlePersonValidStay") - public R> getSinglePersonValidStay() { - List result = xgHolidayStayService.querySinglePersonValidStay(); - return R.ok(result); - } } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java index c183d0dd..0bba8a6f 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgDormHygieneCheck.java @@ -110,6 +110,16 @@ public class XgDormHygieneCheck extends TenantEntity { */ private String campusName; + /** + * 归属班级ID(宿舍扣分归属,取自当前寝室在住学生班级) + */ + private Long classId; + + /** + * 归属班级名称 + */ + private String className; + /** * 宿舍扣分(0-100分) */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgHolidayStay.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgHolidayStay.java index 88c2263e..0dcacbee 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgHolidayStay.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/XgHolidayStay.java @@ -199,5 +199,55 @@ public class XgHolidayStay extends TenantEntity { */ private String remark; + /** + * 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销) + */ + private String applyStatus; + + /** + * 提交申请时间 + */ + private Date applyTime; + + /** + * 审批人用户ID + */ + private Long auditorId; + + /** + * 审批人姓名 + */ + private String auditorName; + + /** + * 审批时间 + */ + private Date auditTime; + + /** + * 审批意见 + */ + private String auditOpinion; + + /** + * 撤销人用户ID + */ + private Long revokeBy; + + /** + * 撤销人姓名 + */ + private String revokeByName; + + /** + * 撤销时间 + */ + private Date revokeTime; + + /** + * 撤销原因 + */ + private String revokeReason; + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckBo.java index 5912f3f9..ac187428 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckBo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckBo.java @@ -117,6 +117,18 @@ public class XgDormHygieneCheckBo extends BaseEntity { */ private String campusName; + /** + * 归属班级ID(宿舍扣分归属,取自当前寝室在住学生班级) + */ + @NotNull(message = "归属班级不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long classId; + + /** + * 归属班级名称 + */ + @NotBlank(message = "归属班级名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String className; + /** * 宿舍总分(0-100分) */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckCompleteBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckCompleteBo.java index 5597e609..2ac4ade1 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckCompleteBo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgDormHygieneCheckCompleteBo.java @@ -118,6 +118,18 @@ public class XgDormHygieneCheckCompleteBo { */ private String campusName; + /** + * 归属班级ID(宿舍扣分归属,取自当前寝室在住学生班级) + */ + @NotNull(message = "归属班级不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long classId; + + /** + * 归属班级名称 + */ + @NotBlank(message = "归属班级名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String className; + /** * 宿舍总分(0-100分) */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHolidayStayAuditBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHolidayStayAuditBo.java new file mode 100644 index 00000000..31103862 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHolidayStayAuditBo.java @@ -0,0 +1,35 @@ +package org.dromara.xg.domain.bo; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 假期留宿审批/撤销参数 + */ +@Data +public class XgHolidayStayAuditBo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @NotNull(message = "申请ID不能为空") + private Long id; + + /** + * 审批意见(驳回建议填写) + */ + private String auditOpinion; + + /** + * 撤销原因(撤销必填) + */ + @NotBlank(message = "撤销原因不能为空", groups = { Revoke.class }) + private String revokeReason; + + public interface Revoke { + } +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHolidayStayBo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHolidayStayBo.java index a9b2e0ad..e30ff61d 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHolidayStayBo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgHolidayStayBo.java @@ -209,5 +209,23 @@ public class XgHolidayStayBo extends BaseEntity { */ private String remark; + /** + * 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销) + */ + private String applyStatus; + + /** + * 提交申请时间 + */ + private Date applyTime; + + private Long auditorId; + private String auditorName; + private Date auditTime; + private String auditOpinion; + private Long revokeBy; + private String revokeByName; + private Date revokeTime; + private String revokeReason; } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckSimplifiedVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckSimplifiedVo.java index 51b8d632..b5146dbf 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckSimplifiedVo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckSimplifiedVo.java @@ -123,6 +123,17 @@ public class XgDormHygieneCheckSimplifiedVo implements Serializable { @ExcelProperty(value = "校区名称") private String campusName; + /** + * 归属班级ID(宿舍扣分归属) + */ + private Long classId; + + /** + * 归属班级名称 + */ + @ExcelProperty(value = "归属班级") + private String className; + /** * 宿舍总分(0-100分) */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckVo.java index f970726b..1f99cc31 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckVo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgDormHygieneCheckVo.java @@ -135,6 +135,18 @@ public class XgDormHygieneCheckVo implements Serializable { @ExcelProperty(value = "校区名称") private String campusName; + /** + * 归属班级ID(宿舍扣分归属) + */ + @ExcelProperty(value = "归属班级ID") + private Long classId; + + /** + * 归属班级名称 + */ + @ExcelProperty(value = "归属班级") + private String className; + /** * 宿舍总分(0-100分) */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgHolidayStayVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgHolidayStayVo.java index c6bc2dc6..d425b5ed 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgHolidayStayVo.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/XgHolidayStayVo.java @@ -236,4 +236,39 @@ public class XgHolidayStayVo implements Serializable { @ExcelProperty(value = "备注信息") private String remark; + /** + * 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销) + */ + @ExcelProperty(value = "申请状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "xg_holiday_stay_apply_status") + private String applyStatus; + + @ExcelProperty(value = "提交申请时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date applyTime; + + private Long auditorId; + + @ExcelProperty(value = "审批人") + private String auditorName; + + @ExcelProperty(value = "审批时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date auditTime; + + @ExcelProperty(value = "审批意见") + private String auditOpinion; + + private Long revokeBy; + + @ExcelProperty(value = "撤销人") + private String revokeByName; + + @ExcelProperty(value = "撤销时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date revokeTime; + + @ExcelProperty(value = "撤销原因") + private String revokeReason; + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedStudentMapper.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedStudentMapper.java index d938c4fd..dc61f5e0 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedStudentMapper.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedStudentMapper.java @@ -165,14 +165,17 @@ public interface XgDormBedStudentMapper extends BaseMapperPlus querySinglePersonValidStay(); + /** + * 学生本人提交留宿申请(状态=待审) + */ + Boolean studentApply(XgHolidayStayBo bo); + + /** + * 当前学生的申请列表 + */ + TableDataInfo queryMyApplyPage(XgHolidayStayBo bo, PageQuery pageQuery); + + /** + * 班主任本班申请列表(含待审) + */ + TableDataInfo queryTeacherPage(XgHolidayStayBo bo, PageQuery pageQuery); + + /** + * 班主任审批通过 + */ + Boolean approve(XgHolidayStayAuditBo auditBo); + + /** + * 班主任驳回 + */ + Boolean reject(XgHolidayStayAuditBo auditBo); + + /** + * 班主任撤销已通过申请 + */ + Boolean revoke(XgHolidayStayAuditBo auditBo); + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java index c5ca775d..3ed883ad 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedStudentServiceImpl.java @@ -2500,9 +2500,12 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { studentInfo.put("studentNo", detailMap.get("student_no")); studentInfo.put("gender", detailMap.get("gender")); - // 学院、专业、班级信息 + // 学院、专业、班级信息(含 ID,供卫生检查归属班级等使用) + studentInfo.put("collegeId", detailMap.get("college_id")); studentInfo.put("collegeName", detailMap.get("college_name")); + studentInfo.put("majorId", detailMap.get("major_id")); studentInfo.put("majorName", detailMap.get("major_name")); + studentInfo.put("classId", detailMap.get("class_id")); studentInfo.put("className", detailMap.get("class_name")); // 床位信息 @@ -2561,33 +2564,35 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { return null; } - // 组装返回结果 + // 组装返回结果(兼容 Map 键为下划线或驼峰) Map result = new LinkedHashMap<>(); // 校区信息 - result.put("campusId", dormInfo.get("campus_id")); - result.put("campusName", dormInfo.get("campus_name")); + result.put("campusId", mapVal(dormInfo, "campusId", "campus_id")); + result.put("campusName", mapVal(dormInfo, "campusName", "campus_name")); // 宿舍区信息 - result.put("areaId", dormInfo.get("area_id")); - result.put("areaName", dormInfo.get("area_name")); + result.put("areaId", mapVal(dormInfo, "areaId", "area_id")); + result.put("areaName", mapVal(dormInfo, "areaName", "area_name")); // 楼栋信息 - result.put("buildingId", dormInfo.get("building_id")); - result.put("buildingName", dormInfo.get("building_name")); - result.put("buildingCode", dormInfo.get("building_code")); + result.put("buildingId", mapVal(dormInfo, "buildingId", "building_id")); + result.put("buildingName", mapVal(dormInfo, "buildingName", "building_name")); + result.put("buildingCode", mapVal(dormInfo, "buildingCode", "building_code")); // 房间信息 - result.put("roomId", dormInfo.get("room_id")); - result.put("roomName", dormInfo.get("room_name")); - result.put("roomNo", dormInfo.get("room_no")); - result.put("roomNumber", dormInfo.get("room_no")); // 兼容字段 + result.put("roomId", mapVal(dormInfo, "roomId", "room_id")); + result.put("roomName", mapVal(dormInfo, "roomName", "room_name")); + Object roomNo = mapVal(dormInfo, "roomNo", "room_no"); + result.put("roomNo", roomNo); + result.put("roomNumber", roomNo); // 兼容字段 // 床位信息 - result.put("bedId", dormInfo.get("bed_id")); - result.put("bedName", dormInfo.get("bed_name")); - result.put("bedNo", dormInfo.get("bed_no")); - result.put("bedNumber", dormInfo.get("bed_no")); // 兼容字段 + result.put("bedId", mapVal(dormInfo, "bedId", "bed_id")); + result.put("bedName", mapVal(dormInfo, "bedName", "bed_name")); + Object bedNo = mapVal(dormInfo, "bedNo", "bed_no"); + result.put("bedNo", bedNo); + result.put("bedNumber", bedNo); // 兼容字段 log.info("获取学生当前宿舍信息成功,学生ID: {}, 宿舍信息: {}校区-{}楼-{}房间-{}床位", studentId, @@ -2604,6 +2609,17 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { } } + private static Object mapVal(Map map, String camelKey, String snakeKey) { + if (map == null) { + return null; + } + Object val = map.get(camelKey); + if (val == null) { + val = map.get(snakeKey); + } + return val; + } + /** * 根据床位ID获取该床位的学生信息 * 包含学生基本信息、学院、专业、班级等信息 diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgHolidayStayServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgHolidayStayServiceImpl.java index fee36299..2ed2a49a 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgHolidayStayServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgHolidayStayServiceImpl.java @@ -11,12 +11,21 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.dromara.xg.domain.bo.XgHolidayStayBo; +import org.dromara.xg.domain.bo.XgHolidayStayAuditBo; import org.dromara.xg.domain.vo.XgHolidayStayVo; import org.dromara.xg.domain.XgHolidayStay; import org.dromara.xg.domain.bo.StudentOptionBo; import org.dromara.xg.domain.vo.StudentOptionVo; +import org.dromara.xg.domain.vo.XgStudentMsgVo; +import org.dromara.xg.domain.vo.XgClassMsgVo; +import org.dromara.xg.domain.bo.XgClassMsgBo; import org.dromara.xg.mapper.XgHolidayStayMapper; import org.dromara.xg.service.IXgHolidayStayService; +import org.dromara.xg.service.IXgStudentMsgService; +import org.dromara.xg.service.IXgClassMsgService; +import org.dromara.xg.service.IXgDormBedStudentService; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.satoken.utils.LoginHelper; import java.util.List; import java.util.Map; @@ -44,6 +53,9 @@ import org.springframework.beans.BeanUtils; public class XgHolidayStayServiceImpl implements IXgHolidayStayService { private final XgHolidayStayMapper baseMapper; + private final IXgStudentMsgService xgStudentMsgService; + private final IXgClassMsgService xgClassMsgService; + private final IXgDormBedStudentService xgDormBedStudentService; /** * 查询假期留宿管理 @@ -154,6 +166,9 @@ public class XgHolidayStayServiceImpl implements IXgHolidayStayService { // 添加创建人过滤条件 - 实现班主任权限控制,只能查看自己创建的数据 lqw.eq(bo.getCreateBy() != null, XgHolidayStay::getCreateBy, bo.getCreateBy()); + // 申请审批状态 + lqw.eq(StringUtils.isNotBlank(bo.getApplyStatus()), XgHolidayStay::getApplyStatus, bo.getApplyStatus()); + // 添加状态过滤逻辑 addStatusFilter(lqw, bo); @@ -201,6 +216,10 @@ public class XgHolidayStayServiceImpl implements IXgHolidayStayService { @Override public Boolean insertByBo(XgHolidayStayBo bo) { XgHolidayStay add = MapstructUtils.convert(bo, XgHolidayStay.class); + // 宿管/班主任直接登记:默认已通过;学生申请走 studentApply + if (StringUtils.isBlank(add.getApplyStatus())) { + add.setApplyStatus("2"); + } validEntityBeforeSave(add); boolean flag = baseMapper.insert(add) > 0; if (flag) { @@ -755,4 +774,209 @@ public class XgHolidayStayServiceImpl implements IXgHolidayStayService { return originalStay; } } + + @Override + public Boolean studentApply(XgHolidayStayBo bo) { + Long userId = LoginHelper.getUserId(); + XgStudentMsgVo student = xgStudentMsgService.queryByUserId(userId); + if (student == null || student.getId() == null) { + throw new ServiceException("未找到当前登录学生信息,无法申请留宿"); + } + fillStudentSnapshot(bo, student); + fillDormSnapshot(bo, student.getId()); + if (bo.getBedId() == null || bo.getRoomId() == null) { + throw new ServiceException("未查询到当前在住宿舍/床位信息,请先完成床位分配后再申请留宿"); + } + bo.setApplyStatus("1"); + bo.setApplyTime(new Date()); + bo.setAuditorId(null); + bo.setAuditorName(null); + bo.setAuditTime(null); + bo.setAuditOpinion(null); + bo.setRevokeBy(null); + bo.setRevokeByName(null); + bo.setRevokeTime(null); + bo.setRevokeReason(null); + return insertByBo(bo); + } + + @Override + public TableDataInfo queryMyApplyPage(XgHolidayStayBo bo, PageQuery pageQuery) { + Long userId = LoginHelper.getUserId(); + XgStudentMsgVo student = xgStudentMsgService.queryByUserId(userId); + if (student == null || student.getId() == null) { + return TableDataInfo.build(new Page<>()); + } + if (bo == null) { + bo = new XgHolidayStayBo(); + } + bo.setStudentId(student.getId()); + return queryPageList(bo, pageQuery); + } + + @Override + public TableDataInfo queryTeacherPage(XgHolidayStayBo bo, PageQuery pageQuery) { + List classIds = listMyClassIdsAsTeacher(); + if (classIds.isEmpty()) { + return TableDataInfo.build(new Page<>()); + } + if (bo == null) { + bo = new XgHolidayStayBo(); + } + // 指定班级时必须是本班 + if (bo.getClassId() != null && !classIds.contains(bo.getClassId())) { + return TableDataInfo.build(new Page<>()); + } + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + if (bo.getClassId() == null) { + lqw.in(XgHolidayStay::getClassId, classIds); + } + lqw.orderByDesc(XgHolidayStay::getApplyTime).orderByDesc(XgHolidayStay::getId); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + @Override + public Boolean approve(XgHolidayStayAuditBo auditBo) { + XgHolidayStay entity = requireTeacherPendingRecord(auditBo.getId()); + entity.setApplyStatus("2"); + entity.setAuditorId(LoginHelper.getUserId()); + entity.setAuditorName(LoginHelper.getNickname()); + entity.setAuditTime(new Date()); + entity.setAuditOpinion(auditBo.getAuditOpinion()); + return baseMapper.updateById(entity) > 0; + } + + @Override + public Boolean reject(XgHolidayStayAuditBo auditBo) { + XgHolidayStay entity = requireTeacherPendingRecord(auditBo.getId()); + entity.setApplyStatus("3"); + entity.setAuditorId(LoginHelper.getUserId()); + entity.setAuditorName(LoginHelper.getNickname()); + entity.setAuditTime(new Date()); + entity.setAuditOpinion(auditBo.getAuditOpinion()); + return baseMapper.updateById(entity) > 0; + } + + @Override + public Boolean revoke(XgHolidayStayAuditBo auditBo) { + if (StringUtils.isBlank(auditBo.getRevokeReason())) { + throw new ServiceException("撤销原因不能为空"); + } + XgHolidayStay entity = requireTeacherOwnedRecord(auditBo.getId()); + if (!"2".equals(entity.getApplyStatus())) { + throw new ServiceException("仅已通过的申请可撤销"); + } + entity.setApplyStatus("4"); + entity.setRevokeBy(LoginHelper.getUserId()); + entity.setRevokeByName(LoginHelper.getNickname()); + entity.setRevokeTime(new Date()); + entity.setRevokeReason(auditBo.getRevokeReason()); + return baseMapper.updateById(entity) > 0; + } + + private void fillStudentSnapshot(XgHolidayStayBo bo, XgStudentMsgVo student) { + bo.setStudentId(student.getId()); + bo.setStudentName(student.getName()); + bo.setStudentNo(student.getStudentNo()); + if (StringUtils.isNotBlank(student.getClassId())) { + try { + bo.setClassId(Long.valueOf(student.getClassId())); + } catch (NumberFormatException ignored) { + // ignore + } + } + bo.setClassName(student.getClassName()); + if (StringUtils.isNotBlank(student.getMajorId())) { + try { + bo.setMajorId(Long.valueOf(student.getMajorId())); + } catch (NumberFormatException ignored) { + // ignore + } + } + bo.setMajorName(student.getMajorName()); + if (StringUtils.isNotBlank(student.getCollegeId())) { + try { + bo.setCollegeId(Long.valueOf(student.getCollegeId())); + } catch (NumberFormatException ignored) { + // ignore + } + } + bo.setCollegeName(student.getCollegeName()); + if (StringUtils.isBlank(bo.getStudentPhone())) { + bo.setStudentPhone(student.getPhone()); + } + } + + @SuppressWarnings("unchecked") + private void fillDormSnapshot(XgHolidayStayBo bo, Long studentId) { + Object dormObj = xgDormBedStudentService.getCurrentDormInfo(studentId); + if (!(dormObj instanceof Map)) { + return; + } + Map dorm = (Map) dormObj; + bo.setCampusId(toLong(dorm.get("campusId"))); + bo.setCampusName(toStr(dorm.get("campusName"))); + bo.setAreaId(toLong(dorm.get("areaId"))); + bo.setAreaName(toStr(dorm.get("areaName"))); + bo.setBuildingId(toLong(dorm.get("buildingId"))); + bo.setBuildingName(toStr(dorm.get("buildingName"))); + bo.setRoomId(toLong(dorm.get("roomId"))); + bo.setRoomName(toStr(dorm.get("roomName"))); + bo.setBedId(toLong(dorm.get("bedId"))); + Object bedName = dorm.get("bedName"); + if (bedName == null) { + bedName = dorm.get("bedNo"); + } + bo.setBedName(toStr(bedName)); + } + + private List listMyClassIdsAsTeacher() { + XgClassMsgBo classBo = new XgClassMsgBo(); + classBo.setClassTeacherId(LoginHelper.getUserId()); + classBo.setClassStatus("1"); + List classes = xgClassMsgService.queryList(classBo); + if (classes == null || classes.isEmpty()) { + return List.of(); + } + return classes.stream().map(XgClassMsgVo::getId).filter(id -> id != null).collect(Collectors.toList()); + } + + private XgHolidayStay requireTeacherPendingRecord(Long id) { + XgHolidayStay entity = requireTeacherOwnedRecord(id); + if (!"1".equals(entity.getApplyStatus())) { + throw new ServiceException("仅待审申请可审批"); + } + return entity; + } + + private XgHolidayStay requireTeacherOwnedRecord(Long id) { + XgHolidayStay entity = baseMapper.selectById(id); + if (entity == null || (entity.getDeleted() != null && entity.getDeleted() != 0L)) { + throw new ServiceException("申请记录不存在"); + } + List classIds = listMyClassIdsAsTeacher(); + if (entity.getClassId() == null || !classIds.contains(entity.getClassId())) { + throw new ServiceException("无权操作非本班学生的留宿申请"); + } + return entity; + } + + private Long toLong(Object val) { + if (val == null) { + return null; + } + if (val instanceof Number) { + return ((Number) val).longValue(); + } + try { + return Long.valueOf(val.toString()); + } catch (Exception e) { + return null; + } + } + + private String toStr(Object val) { + return val == null ? null : val.toString(); + } } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormHygieneCheck/XgDormHygieneCheckMapper.xml b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormHygieneCheck/XgDormHygieneCheckMapper.xml index 71977c83..fdae0bc9 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormHygieneCheck/XgDormHygieneCheckMapper.xml +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/dormHygieneCheck/XgDormHygieneCheckMapper.xml @@ -107,6 +107,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND dhc.campus_name LIKE CONCAT('%', #{campusName}, '%') + + AND dhc.class_id = #{classId} + + + AND dhc.class_name LIKE CONCAT('%', #{className}, '%') + AND dhc.hygiene_grade = #{hygieneGrade} diff --git a/docs/开发文档/API/API文档总览.md b/docs/开发文档/API/API文档总览.md index 13bbff0b..42bfc7d6 100644 --- a/docs/开发文档/API/API文档总览.md +++ b/docs/开发文档/API/API文档总览.md @@ -20,7 +20,7 @@ - [宿舍管理员授权API](./宿舍管理员授权API.md) - [学生管理员授权关系API](./学生管理员授权关系API.md) - [学生销假管理API](./学生销假管理API.md) -- [假期留宿管理API](./假期留宿管理API.md) - **重要修复:单人留宿计算逻辑**(2025-01-03) +- [假期留宿管理API](./假期留宿管理API.md) - **申请/班主任一级审批/撤销/导出**(2026-08-04) - [均化处理最高分值API](./均化处理最高分值API.md) - **新增**:支持考核申请均化处理功能(2025-01-03) ### 1. 用户认证登录 diff --git a/docs/开发文档/API/宿舍卫生检查管理API.md b/docs/开发文档/API/宿舍卫生检查管理API.md index db90defe..c7eb0f30 100644 --- a/docs/开发文档/API/宿舍卫生检查管理API.md +++ b/docs/开发文档/API/宿舍卫生检查管理API.md @@ -34,6 +34,8 @@ - 单项分数必须在对应类型的 min-max 范围内 - 合计扣分上限 100 分 - 历史单条数据迁移:以主表 `total_score` 作为唯一明细的 score +- **归属班级(classId/className)必填**:选项仅来自当前寝室在住学生的班级(去重);默认选中第一项;用于宿舍扣分归属统计 +- **班级选项数据来源**:`GET /dormBedStudent/dormBedStudent/getRoomStudents` 须返回 `classId`、`className`(以及 collegeId/majorId);前端按 `classId` 去重生成下拉,缺 `classId` 时会出现「当前寝室无在住学生班级」 ## API接口规范 @@ -59,6 +61,8 @@ "areaName": "东区", "campusId": 1, "campusName": "主校区", + "classId": 10001, + "className": "计算机应用技术2301班", "totalScore": 85.5, "deductionItems": [ { @@ -315,6 +319,7 @@ public interface DormHygieneStudentScoreMapper extends BaseMapperPlus { + return request({ + url: '/holidayStay/holidayStay/apply', + method: 'POST', + data + }); +}; + +/** 当前学生申请列表 */ +export const listMyHolidayStay = (query) => { + return request({ + url: '/holidayStay/holidayStay/myList', + method: 'GET', + params: query + }); +}; + +/** 班主任本班申请列表 */ +export const listTeacherHolidayStay = (query) => { + return request({ + url: '/holidayStay/holidayStay/teacherList', + method: 'GET', + params: query + }); +}; + +/** 审批通过 */ +export const approveHolidayStay = (data) => { + return request({ + url: '/holidayStay/holidayStay/approve', + method: 'POST', + data + }); +}; + +/** 驳回 */ +export const rejectHolidayStay = (data) => { + return request({ + url: '/holidayStay/holidayStay/reject', + method: 'POST', + data + }); +}; + +/** 撤销已通过申请 */ +export const revokeHolidayStay = (data) => { + return request({ + url: '/holidayStay/holidayStay/revoke', + method: 'POST', + data + }); +}; + +/** 详情 */ +export const getHolidayStay = (id) => { + return request({ + url: '/holidayStay/holidayStay/' + id, + method: 'GET' + }); +}; + +/** 获取学生当前在住宿舍信息 */ +export const getCurrentDormInfo = (studentId) => { + return request({ + url: '/dormBedStudent/dormBedStudent/currentDormInfo/' + studentId, + method: 'GET' + }); +}; + +/** 当前登录学生信息 */ +export const getCurrentStudentInfo = () => { + return request({ + url: '/studentMsg/studentMsg/currentUserInfo', + method: 'GET' + }); +}; diff --git a/mobile/src/pages.json b/mobile/src/pages.json index c676b867..d2b6df29 100644 --- a/mobile/src/pages.json +++ b/mobile/src/pages.json @@ -207,6 +207,30 @@ "navigationStyle": "custom" } }, + { + "path": "pages/xgss/jqzs/myList", + "style": { + "navigationBarTitleText": "我的留宿申请", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + }, + { + "path": "pages/xgss/jqzs/apply", + "style": { + "navigationBarTitleText": "申请假期留宿", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + }, + { + "path": "pages/xgss/jqzs/teacherList", + "style": { + "navigationBarTitleText": "留宿审批", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + }, { "path": "pages/xgss/ssdm/ssdm", "style": { diff --git a/mobile/src/pages/index/index.vue b/mobile/src/pages/index/index.vue index 53c3003a..66c9f5ee 100644 --- a/mobile/src/pages/index/index.vue +++ b/mobile/src/pages/index/index.vue @@ -111,6 +111,26 @@ + + + + + + + + + + + + + + + item.disciplineTypeName).join('、'); }, + classColumns() { + return [ + this.classOptions.map((item) => ({ + text: item.className, + value: item.classId, + })), + ]; + }, + + classDisplay() { + return this.formData.className || ''; + }, + // 卫生等级列 hygieneGradeColumns() { return [ @@ -919,6 +963,7 @@ export default { async loadRoomStudents() { if (!this.roomInfo || !this.roomInfo.id) { this.roomStudents = []; + this.syncClassOptionsFromStudents([]); return; } @@ -946,21 +991,74 @@ export default { showAttendancePicker: false, showGradePicker: false, })); + this.syncClassOptionsFromStudents(this.roomStudents); // 加载学生请假信息 await this.loadStudentLeaves(); } else { this.roomStudents = []; + this.syncClassOptionsFromStudents([]); } } catch (error) { console.error('加载房间学生信息失败:', error); uni.showToast({ title: '加载学生信息失败', icon: 'none' }); this.roomStudents = []; + this.syncClassOptionsFromStudents([]); } finally { this.studentsLoading = false; } }, + /** 从本房学生提取班级选项,默认选中第一项 */ + syncClassOptionsFromStudents(students, preferClassId) { + const map = new Map(); + (students || []).forEach((student) => { + if ( + student.classId == null || + student.classId === '' || + student.classId === 0 || + student.classId === '0' + ) { + return; + } + const key = String(student.classId); + if (!map.has(key)) { + map.set(key, { + classId: student.classId, + className: student.className || key, + }); + } + }); + this.classOptions = Array.from(map.values()); + + if (!this.classOptions.length) { + this.formData.classId = null; + this.formData.className = ''; + return; + } + + const preferred = + preferClassId !== undefined && preferClassId !== null + ? preferClassId + : this.formData.classId; + const matched = + preferred != null + ? this.classOptions.find((item) => String(item.classId) === String(preferred)) + : null; + const selected = matched || this.classOptions[0]; + this.formData.classId = selected.classId; + this.formData.className = selected.className; + }, + + handleClassConfirm(e) { + const selected = e.value && e.value[0]; + if (selected) { + this.formData.classId = selected.value; + this.formData.className = selected.text; + } + this.showClassPicker = false; + }, + // 加载学生请假信息 async loadStudentLeaves() { if (!this.checkInfo || !this.checkInfo.checkDate || this.roomStudents.length === 0) @@ -1008,6 +1106,8 @@ export default { // 1️⃣ 回显表单基础数据 this.formData.disciplineTypeId = this.existingData.disciplineTypeId || null; this.formData.disciplineTypeName = this.existingData.disciplineTypeName || ''; + this.formData.classId = this.existingData.classId || null; + this.formData.className = this.existingData.className || ''; this.formData.totalScore = this.existingData.totalScore || 0; this.formData.hygieneGrade = this.existingData.hygieneGrade || 'A'; this.formData.checkRemarks = this.existingData.checkRemarks || ''; @@ -1060,6 +1160,7 @@ export default { studentId: normalizeStudentId(studentScore.studentId), studentName: studentScore.studentName, studentNo: studentScore.studentNo, + classId: studentScore.classId, className: studentScore.className, majorName: studentScore.majorName, collegeName: studentScore.collegeName, @@ -1091,6 +1192,7 @@ export default { } this.roomStudents = students; + this.syncClassOptionsFromStudents(students, this.formData.classId); console.log('✅ 学生评分数据映射完成'); @@ -2372,6 +2474,20 @@ export default { return; } + if (!this.formData.classId) { + uni.showToast({ + title: '请选择归属班级(取自当前寝室在住学生)', + icon: 'none', + }); + return; + } + const matchedClass = this.classOptions.find( + (item) => String(item.classId) === String(this.formData.classId), + ); + if (matchedClass) { + this.formData.className = matchedClass.className; + } + this.submitLoading = true; this.syncStudentScoreFromViolations(); @@ -2387,8 +2503,11 @@ export default { studentId: student.studentId, studentName: student.studentName, studentNo: student.studentNo, + classId: student.classId, className: student.className, + majorId: student.majorId, majorName: student.majorName, + collegeId: student.collegeId, collegeName: student.collegeName, bedId: student.bedId, bedCode: student.bedCode, diff --git a/web/src/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types.ts b/web/src/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types.ts index ea07e5ac..498aa8d4 100644 --- a/web/src/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types.ts +++ b/web/src/api/xg/xgss/dormHygieneCheck/dormHygieneCheck/types.ts @@ -84,6 +84,16 @@ export interface DormHygieneCheckVO { */ campusName: string; + /** + * 归属班级ID(宿舍扣分归属) + */ + classId?: string | number; + + /** + * 归属班级名称 + */ + className?: string; + /** * 宿舍总分(0-100分) */ @@ -227,6 +237,16 @@ export interface DormHygieneCheckForm extends BaseEntity { */ campusName?: string; + /** + * 归属班级ID(宿舍扣分归属) + */ + classId?: string | number; + + /** + * 归属班级名称 + */ + className?: string; + /** * 宿舍总分(0-100分) */ diff --git a/web/src/api/xg/xgss/holidayStay/holidayStay/index.ts b/web/src/api/xg/xgss/holidayStay/holidayStay/index.ts index 20b855b7..51af3223 100644 --- a/web/src/api/xg/xgss/holidayStay/holidayStay/index.ts +++ b/web/src/api/xg/xgss/holidayStay/holidayStay/index.ts @@ -142,3 +142,57 @@ export const getBedOptions = (roomId?: string | number): AxiosPromise => params: roomId ? { roomId } : undefined }); }; + +/** 学生提交留宿申请 */ +export const applyHolidayStay = (data: HolidayStayForm) => { + return request({ + url: '/holidayStay/holidayStay/apply', + method: 'post', + data + }); +}; + +/** 当前学生申请列表 */ +export const listMyHolidayStay = (query?: HolidayStayQuery): AxiosPromise => { + return request({ + url: '/holidayStay/holidayStay/myList', + method: 'get', + params: query + }); +}; + +/** 班主任本班申请列表 */ +export const listTeacherHolidayStay = (query?: HolidayStayQuery): AxiosPromise => { + return request({ + url: '/holidayStay/holidayStay/teacherList', + method: 'get', + params: query + }); +}; + +/** 班主任审批通过 */ +export const approveHolidayStay = (data: { id: string | number; auditOpinion?: string }) => { + return request({ + url: '/holidayStay/holidayStay/approve', + method: 'post', + data + }); +}; + +/** 班主任驳回 */ +export const rejectHolidayStay = (data: { id: string | number; auditOpinion?: string }) => { + return request({ + url: '/holidayStay/holidayStay/reject', + method: 'post', + data + }); +}; + +/** 班主任撤销已通过申请 */ +export const revokeHolidayStay = (data: { id: string | number; revokeReason: string }) => { + return request({ + url: '/holidayStay/holidayStay/revoke', + method: 'post', + data + }); +}; diff --git a/web/src/api/xg/xgss/holidayStay/holidayStay/types.ts b/web/src/api/xg/xgss/holidayStay/holidayStay/types.ts index 75c01781..c841e5c6 100644 --- a/web/src/api/xg/xgss/holidayStay/holidayStay/types.ts +++ b/web/src/api/xg/xgss/holidayStay/holidayStay/types.ts @@ -173,6 +173,56 @@ export interface HolidayStayVO { * 备注信息 */ remark: string; + + /** + * 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销) + */ + applyStatus?: string; + + /** + * 提交申请时间 + */ + applyTime?: string; + + /** + * 审批人用户ID + */ + auditorId?: string | number; + + /** + * 审批人姓名 + */ + auditorName?: string; + + /** + * 审批时间 + */ + auditTime?: string; + + /** + * 审批意见 + */ + auditOpinion?: string; + + /** + * 撤销人用户ID + */ + revokeBy?: string | number; + + /** + * 撤销人姓名 + */ + revokeByName?: string; + + /** + * 撤销时间 + */ + revokeTime?: string; + + /** + * 撤销原因 + */ + revokeReason?: string; } export interface HolidayStayForm extends BaseEntity { @@ -350,6 +400,11 @@ export interface HolidayStayForm extends BaseEntity { * 备注信息 */ remark?: string; + + /** + * 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销) + */ + applyStatus?: string; } export interface HolidayStayQuery extends PageQuery { @@ -523,6 +578,11 @@ export interface HolidayStayQuery extends PageQuery { */ status?: string; + /** + * 申请状态(0草稿 1待审 2已通过 3已驳回 4已撤销) + */ + applyStatus?: string; + /** * 日期范围参数 */ diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue index 523205a5..f5b0c2ce 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue @@ -57,6 +57,25 @@ + + + + + + + void) => { @@ -440,6 +462,8 @@ const hygieneRules = reactive({ // 学生评分数据 const studentScores = ref([]); +/** 本寝室在住学生班级(去重) */ +const classOptions = ref>([]); // 违纪记录数据(使用普通对象,避免 Map 变更无法触发 Vue 响应式更新) const violationRecords = ref>({}); @@ -488,6 +512,41 @@ const handleDeductionTotalChange = (total: number) => { hygieneForm.totalScore = total; }; +/** 从本房学生提取班级选项,并默认选中第一项(已选且仍有效则保留) */ +const syncClassOptionsFromStudents = (students: any[], preferClassId?: string | number | null) => { + const map = new Map(); + students.forEach((student) => { + if (student.classId == null || student.classId === '' || student.classId === 0 || student.classId === '0') { + return; + } + const key = String(student.classId); + if (!map.has(key)) { + map.set(key, { + classId: student.classId, + className: student.className || key + }); + } + }); + classOptions.value = Array.from(map.values()); + + if (!classOptions.value.length) { + hygieneForm.classId = undefined; + hygieneForm.className = ''; + return; + } + + const preferred = preferClassId ?? hygieneForm.classId; + const matched = preferred != null ? classOptions.value.find((item) => String(item.classId) === String(preferred)) : undefined; + const selected = matched || classOptions.value[0]; + hygieneForm.classId = selected.classId; + hygieneForm.className = selected.className; +}; + +const handleClassChange = (classId: string | number) => { + const matched = classOptions.value.find((item) => String(item.classId) === String(classId)); + hygieneForm.className = matched?.className || ''; +}; + // 上传配置 const uploadAction = computed(() => { return (import.meta as any).env.VITE_APP_BASE_API + '/resource/oss/upload'; @@ -623,14 +682,38 @@ const loadStudentScores = async () => { studentScores.value = scores; console.log('学生评分数据加载成功:', scores.length, '条'); + + // 班级选项优先用评分中的班级;若缺失则从当前寝室在住学生补全 + let classSource = scores; + const hasClass = scores.some( + (s: any) => s.classId != null && s.classId !== '' && s.classId !== 0 && s.classId !== '0' + ); + if (!hasClass && hygieneForm.roomId) { + try { + const roomRes = await request({ + url: '/dormBedStudent/dormBedStudent/getRoomStudents', + method: 'get', + params: { roomId: hygieneForm.roomId } + }); + if (roomRes.code === 200 && roomRes.data) { + classSource = roomRes.data; + } + } catch (error) { + console.warn('加载寝室班级选项失败', error); + } + } + syncClassOptionsFromStudents(classSource, hygieneForm.classId); + await loadStudentLeaves(); } else { studentScores.value = []; + syncClassOptionsFromStudents([], hygieneForm.classId); } } catch (error) { console.error('加载学生评分数据失败:', error); ElMessage.error('加载学生评分数据失败'); studentScores.value = []; + syncClassOptionsFromStudents([], hygieneForm.classId); } finally { studentsLoading.value = false; } @@ -1179,6 +1262,12 @@ const submitForm = async () => { return; } + if (!hygieneForm.classId) { + ElMessage.error('请选择归属班级(取自当前寝室在住学生)'); + return; + } + handleClassChange(hygieneForm.classId); + buttonLoading.value = true; // 提交前同步个人扣分为违纪合计 @@ -1194,8 +1283,11 @@ const submitForm = async () => { studentId: student.studentId, studentName: student.studentName, studentNo: student.studentNo, + classId: student.classId, className: student.className, + majorId: student.majorId, majorName: student.majorName, + collegeId: student.collegeId, collegeName: student.collegeName, bedId: student.bedId, bedCode: student.bedCode, diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue index 615d168b..7682d0fa 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue @@ -22,6 +22,25 @@ + + + + + + + ([]); const hygieneForm = reactive({ disciplineTypeId: null as number | null, disciplineTypeName: '', + classId: undefined as string | number | undefined, + className: '', totalScore: 0, hygieneGrade: 'A', checkRemarks: '', @@ -344,6 +365,7 @@ const hygieneForm = reactive({ // 表单验证规则 const hygieneRules = reactive({ + classId: [{ required: true, message: '请选择归属班级', trigger: 'change' }], totalScore: [ { validator: (rule: any, value: any, callback: (error?: string | Error) => void) => { @@ -366,6 +388,8 @@ const hygieneRules = reactive({ // 学生相关数据 const roomStudents = ref([]); +/** 本寝室在住学生班级(去重) */ +const classOptions = ref>([]); const violationRecords = ref>(new Map()); const studentLeaves = ref>(new Map()); @@ -444,10 +468,48 @@ const handleDeductionTotalChange = (total: number) => { hygieneForm.totalScore = total; }; +/** 从本房学生提取班级选项,并默认选中第一项(已选且仍有效则保留) */ +const syncClassOptionsFromStudents = (students: any[], preferClassId?: string | number | null) => { + const map = new Map(); + students.forEach((student) => { + const classId = student.classId; + const hasClassId = classId != null && classId !== '' && classId !== 0 && classId !== '0'; + if (!hasClassId) { + return; + } + const key = String(classId); + if (!map.has(key)) { + map.set(key, { + classId, + className: (student.className || '').toString().trim() || key + }); + } + }); + classOptions.value = Array.from(map.values()); + + if (!classOptions.value.length) { + hygieneForm.classId = undefined; + hygieneForm.className = ''; + return; + } + + const preferred = preferClassId ?? hygieneForm.classId; + const matched = preferred != null ? classOptions.value.find((item) => String(item.classId) === String(preferred)) : undefined; + const selected = matched || classOptions.value[0]; + hygieneForm.classId = selected.classId; + hygieneForm.className = selected.className; +}; + +const handleClassChange = (classId: string | number) => { + const matched = classOptions.value.find((item) => String(item.classId) === String(classId)); + hygieneForm.className = matched?.className || ''; +}; + // 加载房间学生信息 const loadRoomStudents = async () => { if (!props.roomInfo?.id) { roomStudents.value = []; + syncClassOptionsFromStudents([]); return; } @@ -467,16 +529,19 @@ const loadRoomStudents = async () => { attendance_status: '1', student_grade: 'A' })); + syncClassOptionsFromStudents(roomStudents.value); // 加载学生请假信息 await loadStudentLeaves(); } else { roomStudents.value = []; + syncClassOptionsFromStudents([]); } } catch (error) { console.error('加载房间学生信息失败:', error); ElMessage.error('加载学生信息失败'); roomStudents.value = []; + syncClassOptionsFromStudents([]); } finally { studentsLoading.value = false; } @@ -878,6 +943,12 @@ const submitForm = async () => { return; } + if (!hygieneForm.classId) { + ElMessage.error('请选择归属班级(取自当前寝室在住学生)'); + return; + } + handleClassChange(hygieneForm.classId); + buttonLoading.value = true; syncStudentScoreFromViolations(); @@ -891,8 +962,11 @@ const submitForm = async () => { studentId: student.studentId, studentName: student.studentName, studentNo: student.studentNo, + classId: student.classId, className: student.className, + majorId: student.majorId, majorName: student.majorName, + collegeId: student.collegeId, collegeName: student.collegeName, bedId: student.bedId, bedCode: student.bedCode, @@ -999,12 +1073,15 @@ const resetForm = () => { Object.assign(hygieneForm, { disciplineTypeId: null, disciplineTypeName: '', + classId: undefined, + className: '', totalScore: 0, hygieneGrade: 'A', checkRemarks: '', attachmentIds: '' }); deductionItems.value = []; + classOptions.value = []; roomStudents.value = []; violationRecords.value.clear(); diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue index 45356000..5b25d2fb 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/index.vue @@ -105,6 +105,7 @@ + diff --git a/web/src/views/xg/xgss/holidayStay/holidayStay/index-sg.vue b/web/src/views/xg/xgss/holidayStay/holidayStay/index-sg.vue index 2bf5469f..071ffed4 100644 --- a/web/src/views/xg/xgss/holidayStay/holidayStay/index-sg.vue +++ b/web/src/views/xg/xgss/holidayStay/holidayStay/index-sg.vue @@ -16,6 +16,11 @@ + + + + + @@ -223,6 +228,11 @@ + + + @@ -221,15 +228,47 @@ - + + + + - + + + + + + + + + + + @@ -565,9 +624,8 @@ import { UploadFilled, Document, Loading, Download, Delete } from '@element-plus import { getToken } from '@/utils/auth'; import ImagePreview from '@/components/ImagePreview/index.vue'; import request from '@/utils/request'; -import { useUserStore } from '@/store/modules/user'; import { - listHolidayStay, + listTeacherHolidayStay, getHolidayStay, delHolidayStay, addHolidayStay, @@ -577,7 +635,10 @@ import { getAreaOptions, getBuildingOptions, getRoomOptions, - getBedOptions + getBedOptions, + approveHolidayStay, + rejectHolidayStay, + revokeHolidayStay } from '@/api/xg/xgss/holidayStay/holidayStay'; import { HolidayStayVO, @@ -593,10 +654,9 @@ import { } from '@/api/xg/xgss/holidayStay/holidayStay/types'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; -const { xg_holiday_type, xg_stay_reason_type, sys_yesno } = toRefs(proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type', 'sys_yesno')); - -// 用户store - 用于获取当前登录用户信息,实现权限控制 -const userStore = useUserStore(); +const { xg_holiday_type, xg_stay_reason_type, sys_yesno, xg_holiday_stay_apply_status } = toRefs( + proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type', 'sys_yesno', 'xg_holiday_stay_apply_status') +); const holidayStayList = ref([]); const buttonLoading = ref(false); @@ -701,6 +761,7 @@ const data = reactive>({ extField1: undefined, extField2: undefined, extField3: undefined, + applyStatus: '1', // 班主任默认看待审 status: 'valid', // 默认显示有效状态的记录 params: {} }, @@ -734,34 +795,23 @@ const uploadData = computed(() => { }; }); -/** 查询假期留宿管理列表 */ +/** 查询假期留宿管理列表(班主任本班) */ const getList = async () => { loading.value = true; - // 确保用户信息已加载 - if (!userStore.userId) { - await userStore.getInfo(); - } - - // 处理状态过滤逻辑 const requestParams = { ...queryParams.value }; - // 添加创建人过滤条件 - 班主任只能看到自己创建的数据 - (requestParams as any).createBy = userStore.userId; - - // 根据状态设置时间过滤条件 + // 根据有效期设置时间过滤条件 if (requestParams.status) { const currentTime = new Date().toISOString().slice(0, 19).replace('T', ' '); if (requestParams.status === 'valid') { - // 查询有效状态:结束时间大于等于当前时间 requestParams.params = { ...requestParams.params, statusType: 'valid', currentTime: currentTime }; } else if (requestParams.status === 'invalid') { - // 查询失效状态:结束时间小于当前时间 requestParams.params = { ...requestParams.params, statusType: 'invalid', @@ -769,11 +819,10 @@ const getList = async () => { }; } - // 移除status参数,因为后端不需要这个字段 delete requestParams.status; } - const res = await listHolidayStay(requestParams); + const res = await listTeacherHolidayStay(requestParams); holidayStayList.value = res.rows; total.value = res.total; loading.value = false; @@ -911,15 +960,64 @@ const handleDelete = async (row?: HolidayStayVO) => { await getList(); }; -/** 导出按钮操作 */ +/** 导出按钮操作:默认导出已通过名单;若已选申请状态则按筛选导出 */ const handleExport = () => { - proxy?.download( - 'holidayStay/holidayStay/export', - { - ...queryParams.value - }, - `holidayStay_${new Date().getTime()}.xlsx` - ); + const exportParams: any = { ...queryParams.value }; + if (!exportParams.applyStatus) { + exportParams.applyStatus = '2'; + } + if (exportParams.status) { + const currentTime = new Date().toISOString().slice(0, 19).replace('T', ' '); + exportParams.params = { + ...exportParams.params, + statusType: exportParams.status, + currentTime + }; + delete exportParams.status; + } + proxy?.download('holidayStay/holidayStay/export', exportParams, `holidayStay_${new Date().getTime()}.xlsx`); +}; + +const auditDialog = reactive({ + visible: false, + title: '', + mode: 'approve' as 'approve' | 'reject' | 'revoke', + rowId: undefined as string | number | undefined, + opinion: '', + loading: false +}); + +const openAuditDialog = (row: HolidayStayVO, mode: 'approve' | 'reject' | 'revoke') => { + auditDialog.mode = mode; + auditDialog.rowId = row.id; + auditDialog.opinion = ''; + auditDialog.title = mode === 'approve' ? '审批通过' : mode === 'reject' ? '审批驳回' : '撤销留宿'; + auditDialog.visible = true; +}; + +const submitAudit = async () => { + if (!auditDialog.rowId) return; + if (auditDialog.mode === 'revoke' && !auditDialog.opinion?.trim()) { + proxy?.$modal.msgWarning('请填写撤销原因'); + return; + } + auditDialog.loading = true; + try { + if (auditDialog.mode === 'approve') { + await approveHolidayStay({ id: auditDialog.rowId, auditOpinion: auditDialog.opinion }); + proxy?.$modal.msgSuccess('已通过'); + } else if (auditDialog.mode === 'reject') { + await rejectHolidayStay({ id: auditDialog.rowId, auditOpinion: auditDialog.opinion }); + proxy?.$modal.msgSuccess('已驳回'); + } else { + await revokeHolidayStay({ id: auditDialog.rowId, revokeReason: auditDialog.opinion }); + proxy?.$modal.msgSuccess('已撤销'); + } + auditDialog.visible = false; + await getList(); + } finally { + auditDialog.loading = false; + } }; /** 远程搜索学生 */