宿舍卫生检查管理模块提供完整的宿舍卫生检查功能,包括检查记录管理和学生个人评分管理。该模块支持同时操作两个数据表:xg_dorm_hygiene_check(主表)和 xg_dorm_hygiene_student_score(学生评分表)。
hygiene_check_id,每条记录对应一个违纪类型及扣分值total_score 为明细合计;discipline_type_name 为多项名称用「、」拼接(兼容旧列表/导出)deductionItems 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| disciplineTypeId | Long | 违纪类型ID |
| disciplineTypeName | String | 违纪类型名称 |
| score | Number | 该项扣分,默认取类型 minScore,可在 min-max 范围内手动调整 |
| sortOrder | Integer | 排序序号 |
业务规则:
minScoretotal_score 作为唯一明细的 scoreGET /dormBedStudent/dormBedStudent/getRoomStudents 须返回 classId、className(以及 collegeId/majorId);前端按 classId 去重生成下拉,缺 classId 时会出现「当前寝室无在住学生班级」接口地址: POST /dormHygieneCheck/dormHygieneCheck/addWithStudentScores
请求参数:
{
"checkTitle": "2025年1月宿舍卫生检查",
"checkDate": "2025-01-28 09:00:00",
"checkerId": 1,
"checkerName": "张检查员",
"checkType": "1",
"checkLevel": "1",
"roomId": 1,
"roomCode": "A101",
"roomName": "A栋101室",
"buildingId": 1,
"buildingName": "A栋宿舍楼",
"areaId": 1,
"areaName": "东区",
"campusId": 1,
"campusName": "主校区",
"classId": 10001,
"className": "计算机应用技术2301班",
"totalScore": 85.5,
"deductionItems": [
{
"disciplineTypeId": 1963793291531026434,
"disciplineTypeName": "地面未打扫",
"score": 5,
"sortOrder": 1
},
{
"disciplineTypeId": 1963793291531026435,
"disciplineTypeName": "物品摆放凌乱",
"score": 3,
"sortOrder": 2
}
],
"hygieneGrade": "B",
"attachmentIds": "oss1,oss2,oss3",
"checkSummary": "宿舍整体卫生良好",
"improvementSuggestions": "建议加强个人物品整理",
"checkRemarks": "检查顺利完成",
"studentData": [
{
"studentId": 1,
"studentName": "张三",
"studentNo": "2021001",
"className": "计算机科学1班",
"majorName": "计算机科学与技术",
"collegeName": "信息工程学院",
"bedId": 1,
"bedCode": "A101-1",
"studentScore": 85.5,
"attendanceStatus": "1",
"studentGrade": "B",
"isResponsiblePerson": 0,
"scoreRemarks": "个人卫生良好",
"improvementNotes": "建议整理桌面物品"
}
]
}
实现要求:
响应格式:
{
"code": 200,
"msg": "新增成功",
"data": {
"id": 1,
"checkTitle": "2025年1月宿舍卫生检查",
"studentCount": 4
}
}
接口地址: PUT /dormHygieneCheck/dormHygieneCheck/updateWithStudentScores
请求参数: 与新增接口相同,但必须包含 id 字段
实现要求:
deleted 字段设置为 1(逻辑删除)响应格式:
{
"code": 200,
"msg": "更新成功",
"data": {
"id": 1,
"checkTitle": "2025年1月宿舍卫生检查(更新)",
"studentCount": 4,
"updatedTime": "2025-01-28 10:30:00"
}
}
@RestController
@RequestMapping("/dormHygieneCheck/dormHygieneCheck")
public class DormHygieneCheckController {
@Autowired
private DormHygieneCheckService dormHygieneCheckService;
/**
* 新增宿舍卫生检查(包含学生评分)
*/
@PostMapping("/addWithStudentScores")
@SaCheckPermission("dormHygieneCheck:dormHygieneCheck:add")
public R<Void> addWithStudentScores(@RequestBody @Valid DormHygieneCheckCompleteForm form) {
dormHygieneCheckService.addWithStudentScores(form);
return R.ok();
}
/**
* 更新宿舍卫生检查(包含学生评分)
*/
@PutMapping("/updateWithStudentScores")
@SaCheckPermission("dormHygieneCheck:dormHygieneCheck:edit")
public R<Void> updateWithStudentScores(@RequestBody @Valid DormHygieneCheckCompleteForm form) {
dormHygieneCheckService.updateWithStudentScores(form);
return R.ok();
}
}
@Service
public class DormHygieneCheckServiceImpl implements DormHygieneCheckService {
@Autowired
private DormHygieneCheckMapper dormHygieneCheckMapper;
@Autowired
private DormHygieneStudentScoreMapper studentScoreMapper;
/**
* 新增宿舍卫生检查(包含学生评分)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void addWithStudentScores(DormHygieneCheckCompleteForm form) {
// 1. 验证数据
validateFormData(form);
// 2. 插入主表
DormHygieneCheck entity = convertToEntity(form);
dormHygieneCheckMapper.insert(entity);
// 3. 插入学生评分数据
if (form.getStudentData() != null && !form.getStudentData().isEmpty()) {
for (DormHygieneStudentScoreForm studentForm : form.getStudentData()) {
DormHygieneStudentScore studentEntity = convertToStudentEntity(studentForm);
studentEntity.setHygieneCheckId(entity.getId());
studentScoreMapper.insert(studentEntity);
}
}
}
/**
* 更新宿舍卫生检查(包含学生评分)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void updateWithStudentScores(DormHygieneCheckCompleteForm form) {
// 1. 验证数据
validateFormData(form);
validateRecordExists(form.getId());
// 2. 更新主表
DormHygieneCheck entity = convertToEntity(form);
dormHygieneCheckMapper.updateById(entity);
// 3. 逻辑删除原有学生评分记录
studentScoreMapper.logicDeleteByCheckId(entity.getId());
// 4. 插入新的学生评分数据
if (form.getStudentData() != null && !form.getStudentData().isEmpty()) {
for (DormHygieneStudentScoreForm studentForm : form.getStudentData()) {
DormHygieneStudentScore studentEntity = convertToStudentEntity(studentForm);
studentEntity.setHygieneCheckId(entity.getId());
studentScoreMapper.insert(studentEntity);
}
}
}
/**
* 验证表单数据
*/
private void validateFormData(DormHygieneCheckCompleteForm form) {
// 验证必填字段
if (StringUtils.isBlank(form.getCheckTitle())) {
throw new ServiceException("检查标题不能为空");
}
if (form.getRoomId() == null) {
throw new ServiceException("房间ID不能为空");
}
// 验证分数范围
if (form.getTotalScore() != null) {
if (form.getTotalScore() < 0 || form.getTotalScore() > 100) {
throw new ServiceException("宿舍总分必须在0-100分之间");
}
}
// 验证学生数据
if (form.getStudentData() != null) {
for (DormHygieneStudentScoreForm student : form.getStudentData()) {
if (student.getStudentScore() != null) {
if (student.getStudentScore() < 0 || student.getStudentScore() > 100) {
throw new ServiceException("学生个人得分必须在0-100分之间");
}
}
}
}
}
/**
* 验证记录是否存在
*/
private void validateRecordExists(Long id) {
DormHygieneCheck existing = dormHygieneCheckMapper.selectById(id);
if (existing == null) {
throw new ServiceException("记录不存在");
}
}
}
@Mapper
public interface DormHygieneStudentScoreMapper extends BaseMapperPlus<DormHygieneStudentScore, DormHygieneStudentScoreVO> {
/**
* 根据检查ID逻辑删除学生评分记录
*/
@Update("UPDATE xg_dorm_hygiene_student_score SET deleted = 1, update_time = NOW(), update_by = #{updateBy} WHERE hygiene_check_id = #{checkId} AND deleted = 0")
int logicDeleteByCheckId(@Param("checkId") Long checkId, @Param("updateBy") Long updateBy);
/**
* 根据检查ID查询学生评分列表
*/
@Select("SELECT * FROM xg_dorm_hygiene_student_score WHERE hygiene_check_id = #{checkId} AND deleted = 0")
List<DormHygieneStudentScore> selectByCheckId(@Param("checkId") Long checkId);
}
checkTitle: 必填,长度1-200字符checkDate: 必填,日期时间格式checkerId: 必填,关联用户表roomId: 必填,关联房间表classId / className: 必填,宿舍扣分归属班级;选项须为当前寝室在住学生班级totalScore: 可选,0-100分(由 deductionItems 合计自动计算,只读)deductionItems: 可选,扣分明细数组;每项 score 默认 minScore,可在类型 min-max 内调整;合计不超过100hygieneGrade: 可选,A/B/C/DstudentId: 必填,关联学生表studentScore: 可选,0-100分attendanceStatus: 必填,1/2/3/4studentGrade: 可选,A/B/C/D400: 参数验证失败401: 权限不足404: 记录不存在500: 服务器内部错误{
"code": 400,
"msg": "检查标题不能为空",
"data": null
}
接口地址: DELETE /dormHygieneCheck/dormHygieneCheck/{ids}
接口描述: 删除宿舍卫生检查记录,自动级联删除四个相关表的数据
涉及表结构:
xg_dorm_hygiene_check - 宿舍卫生检查主表xg_dorm_hygiene_student_score - 学生卫生评分表xg_hygiene_check_violation - 卫生检查违纪关联表xskh_discipline_record - 学生违纪处分记录表(统一违纪表)请求参数:
ids: 待删除的检查记录ID,支持单个或多个,用逗号分隔请求示例:
DELETE /dormHygieneCheck/dormHygieneCheck/1
DELETE /dormHygieneCheck/dormHygieneCheck/1,2,3
删除逻辑顺序:
xg_hygiene_check_violation 表xskh_discipline_record 表中的违纪处分记录xg_hygiene_check_violation 表中的关联记录xg_dorm_hygiene_student_score 表中的评分记录xg_dorm_hygiene_check 主表记录技术实现要点:
@Transactional 确保四表删除的原子性deleted = 1),不物理删除数据响应格式:
{
"code": 200,
"msg": "删除成功",
"data": null
}
错误响应:
{
"code": 500,
"msg": "删除宿舍卫生检查记录失败: 具体错误信息",
"data": null
}
权限要求:
dormHygieneCheck:dormHygieneCheck:remove 权限注意事项:
文档版本: v1.0
创建时间: 2025-01-28
维护人员: 系统开发团队
问题现象: 点击新增按钮时,弹窗中房间学生信息列表的个人扣分列显示默认0分placeholder,而不是直接填入0。
解决方案: 修改DormHygieneCheckDialog.vue中loadRoomStudents方法,将新增模式下学生的student_score默认值从null改为0。
// 修改前
student_score: student.student_score || null, // 个人得分,默认为空
// 修改后
student_score: 0, // 个人得分,新增时默认为0
问题现象: 当宿舍扣分从1改成0时,房间学生信息列表中个人扣分还是1,没有变成0。
解决方案: 修改handleTotalScoreChange方法,添加对0分的特别处理逻辑。
// 修改后的逻辑
const handleTotalScoreChange = () => {
// 验证宿舍总分范围
if (form.totalScore !== null && form.totalScore !== undefined && form.totalScore !== '') {
// 处理非0分情况
const score = parseFloat(form.totalScore.toString());
if (isNaN(score) || score < 0 || score > 100) {
ElMessage.warning('宿舍总分必须在0-100分之间');
form.totalScore = 0;
return;
}
// 自动为所有学生设置相同的个人得分(包括0分)
if (roomStudents.value.length > 0) {
roomStudents.value.forEach((student: any) => {
student.student_score = score;
});
ElMessage.success(`已自动为${roomStudents.value.length}名学生设置个人得分:${score}分`);
}
} else if (form.totalScore === 0 || form.totalScore === '0') {
// 特别处理0分的情况
if (roomStudents.value.length > 0) {
roomStudents.value.forEach((student: any) => {
student.student_score = 0;
});
ElMessage.success(`已自动为${roomStudents.value.length}名学生设置个人得分:0分`);
}
}
};
问题现象: 选择宿舍楼名称后,房间名称按照sort序号进行排序返回,但因为每层房间的sort都从0开始编码,导致返回数据把每个楼层的第一个房间先返回。
解决方案: 修改后端XgDormRoomServiceImpl.java中的buildQueryWrapper方法,调整排序逻辑为先按楼层再按sort排序。
// 修改前
lqw.orderByAsc(XgDormRoom::getSort); // 第一优先级:按排序号升序
lqw.orderByAsc(XgDormRoom::getFloorNum); // 第二优先级:按楼层升序
// 修改后
lqw.orderByAsc(XgDormRoom::getFloorNum); // 第一优先级:按楼层升序
lqw.orderByAsc(XgDormRoom::getSort); // 第二优先级:按排序号升序
web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckDialog.vuebackend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java在调用 updateCompleteData 接口更新宿舍卫生检查数据时,即使违纪记录入参中包含有效的 disciplineRecordId,系统仍然执行了"逻辑删除旧记录 + 新增新记录"的操作,而不是直接更新现有记录。
数据字段映射错误:
XgViolationDataBo 中的 id 字段被误认为是违纪记录ID (disciplineRecordId)id 字段是关联表 xg_hygiene_check_violation 的主键IDdisciplineRecordId 字段传递文件: backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgViolationDataBo.java
/**
* 违纪记录ID(用于更新操作,新增时为null)
*/
private Long id;
/**
* 违纪记录ID,关联xskh_discipline_record.id(用于更新违纪记录)
*/
private Long disciplineRecordId;
文件: backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java
修复前:
newDisciplineIds = newViolationData.stream()
.map(XgViolationDataBo::getId) // 错误:使用关联表ID
.filter(Objects::nonNull)
.collect(Collectors.toSet());
修复后:
newDisciplineIds = newViolationData.stream()
.map(violationBo -> {
// 优先使用disciplineRecordId字段,如果为空则使用id字段(向后兼容)
return violationBo.getDisciplineRecordId() != null
? violationBo.getDisciplineRecordId()
: violationBo.getId();
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
修复前:
if (violationBo.getId() != null && existingDisciplineIds.contains(violationBo.getId())) {
// 更新逻辑
}
修复后:
Long currentDisciplineRecordId = violationBo.getDisciplineRecordId() != null
? violationBo.getDisciplineRecordId()
: violationBo.getId();
if (currentDisciplineRecordId != null && existingDisciplineIds.contains(currentDisciplineRecordId)) {
// 更新逻辑
}
{
"violationData": [
{
"id": "1964301377652428802", // 关联表ID (可选)
"disciplineRecordId": "1964301377522405377", // 违纪记录ID (用于更新判断)
"studentId": "1949114038914949121",
"studentNo": "2025DQ21121",
"studentName": "梁文清",
"disciplineTypeId": "1963793291531026434",
"disciplineTypeName": "地面未打扫",
"disciplineReason": "未按要求打扫地面",
"disciplineContent": "地面有明显垃圾",
"scoreDeduction": "2.00",
// ... 其他字段
}
]
}
使用修复后的接口,当入参包含 disciplineRecordId 时: