From 6a0f1206faa6ef9871ebd2f24b855d6e66868f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cwuzhen=E2=80=9D?= Date: Thu, 20 Aug 2026 15:17:14 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BA=8A=E4=BD=8D?= =?UTF-8?q?=E5=88=86=E9=85=8D=E5=AF=B9=E8=B1=A1=E5=B1=95=E7=A4=BA=E4=B8=8E?= =?UTF-8?q?=E5=8D=AB=E7=94=9F=E6=A3=80=E6=9F=A5=E6=8F=90=E4=BA=A4=E8=AF=AF?= =?UTF-8?q?=E6=8A=A5=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 恢复房间床位详情完整关联查询以显示学院/班级/学生;修复卫生检查 Web/移动端表单校验误报,并将房间学生 ID 以字符串返回避免精度丢失。 Co-authored-by: Cursor --- .../dromara/xg/mapper/XgDormBedMapper.java | 2 +- .../xg/service/impl/XgDormBedServiceImpl.java | 99 +++++----------- .../impl/XgDormBedStudentServiceImpl.java | 64 +++++++--- .../xgss/wsjc/components/SingleRoomCheck.vue | 111 ++++++++++++------ .../DormHygieneCheckEditDialog.vue | 24 +++- .../DormHygieneCheckSingleDialog.vue | 24 +++- 6 files changed, 193 insertions(+), 131 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedMapper.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedMapper.java index f85c59ca..e4824d4a 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedMapper.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedMapper.java @@ -49,7 +49,7 @@ public interface XgDormBedMapper extends BaseMapperPlus major_dept.dept_name as major_name, bs.class_id, - class_dept.dept_name as class_name, + COALESCE(c.class_name, class_dept.dept_name) as class_name, c.class_code, c.grade, diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedServiceImpl.java index 88c0db56..32316a43 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormBedServiceImpl.java @@ -32,7 +32,6 @@ import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; import java.util.ArrayList; -import java.util.HashSet; import java.util.Objects; import java.time.LocalDateTime; @@ -163,84 +162,42 @@ public class XgDormBedServiceImpl implements IXgDormBedService { */ @Override public List queryBedDetailsByRoomId(Long roomId) { - List result = new ArrayList<>(); + // 使用完整 LEFT JOIN:已分学院/班级/学生的床位均能带出分配对象 + List result = baseMapper.selectBedDetailsByRoomId(roomId); + if (result == null || result.isEmpty()) { + return result == null ? new ArrayList<>() : result; + } - List> studentBeds = xgDormBedStudentMapper.selectRoomStudents(roomId); - - List studentIds = studentBeds.stream() - .map(row -> { - Object studentId = row.get("student_id"); - if (studentId instanceof Number) { - return ((Number) studentId).longValue(); - } - return null; - }) + List studentIds = result.stream() + .map(XgDormBedDetailVo::getStudentId) .filter(Objects::nonNull) + .distinct() .collect(Collectors.toList()); - - Map leaveMap = Map.of(); - if (!studentIds.isEmpty()) { - leaveMap = xgStudentLeaveService.queryStudentLeavesByCheckDate( - studentIds, LocalDateTime.now()); + if (studentIds.isEmpty()) { + return result; } - Set occupiedBedIds = new HashSet<>(); - for (Map row : studentBeds) { - XgDormBedDetailVo vo = new XgDormBedDetailVo(); - Object bedIdObj = row.get("bed_id"); - Long bedId = bedIdObj != null ? ((Number) bedIdObj).longValue() : null; - if (bedId != null) { - occupiedBedIds.add(bedId); + Map leaveMap = xgStudentLeaveService.queryStudentLeavesByCheckDate( + studentIds, LocalDateTime.now()); + if (leaveMap == null || leaveMap.isEmpty()) { + return result; + } + + for (XgDormBedDetailVo vo : result) { + if (vo.getStudentId() == null) { + continue; } - vo.setId(bedId); - vo.setBedCode(row.get("bed_code") != null ? row.get("bed_code").toString() : null); - vo.setBedName(row.get("bed_name") != null ? row.get("bed_name").toString() : null); - vo.setRoomId(roomId); - vo.setStudentId(row.get("student_id") != null ? ((Number) row.get("student_id")).longValue() : null); - vo.setStudentNo(row.get("student_no") != null ? row.get("student_no").toString() : null); - vo.setStudentName(row.get("student_name") != null ? row.get("student_name").toString() : null); - vo.setStudentGender(row.get("gender") != null ? row.get("gender").toString() : null); - vo.setClassName(row.get("class_name") != null ? row.get("class_name").toString() : null); - vo.setCollegeName(row.get("college_name") != null ? row.get("college_name").toString() : null); - vo.setMajorName(row.get("major_name") != null ? row.get("major_name").toString() : null); - vo.setAssignmentStatus("0"); - vo.setBedStatus("1"); - - Object studentIdObj = row.get("student_id"); - if (studentIdObj instanceof Number) { - Long studentIdValue = ((Number) studentIdObj).longValue(); - XgStudentLeaveVo leaveVo = leaveMap.get(studentIdValue); - if (leaveVo != null) { - XgDormBedDetailVo.LeaveInfo leaveInfo = new XgDormBedDetailVo.LeaveInfo(); - leaveInfo.setLeaveId(leaveVo.getId()); - leaveInfo.setLeaveTypeName(leaveVo.getLeaveTypeName()); - leaveInfo.setLeaveStartTime(leaveVo.getStartTime()); - leaveInfo.setLeaveEndTime(leaveVo.getEndTime()); - vo.setLeaveInfo(leaveInfo); - } + XgStudentLeaveVo leaveVo = leaveMap.get(vo.getStudentId()); + if (leaveVo == null) { + continue; } - - result.add(vo); + XgDormBedDetailVo.LeaveInfo leaveInfo = new XgDormBedDetailVo.LeaveInfo(); + leaveInfo.setLeaveId(leaveVo.getId()); + leaveInfo.setLeaveTypeName(leaveVo.getLeaveTypeName()); + leaveInfo.setLeaveStartTime(leaveVo.getStartTime()); + leaveInfo.setLeaveEndTime(leaveVo.getEndTime()); + vo.setLeaveInfo(leaveInfo); } - - List emptyBeds = baseMapper.selectList( - Wrappers.lambdaQuery() - .eq(XgDormBed::getRoomId, roomId) - .notIn(!occupiedBedIds.isEmpty(), XgDormBed::getId, occupiedBedIds) - ); - - for (XgDormBed bed : emptyBeds) { - XgDormBedDetailVo vo = new XgDormBedDetailVo(); - vo.setId(bed.getId()); - vo.setBedCode(bed.getBedCode()); - vo.setBedName(bed.getBedName()); - vo.setRoomId(bed.getRoomId()); - vo.setBedType(bed.getBedType()); - vo.setBedStatus(bed.getBedStatus()); - vo.setBedRemark(bed.getRemark()); - result.add(vo); - } - return result; } 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 3ed883ad..ec004ac0 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 @@ -2473,10 +2473,17 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { List studentIds = studentDetails.stream() .map(detail -> { - Object studentId = detail.get("student_id"); + Object studentId = mapVal(detail, "studentId", "student_id"); if (studentId instanceof Number) { return ((Number) studentId).longValue(); } + if (studentId != null) { + try { + return Long.valueOf(studentId.toString()); + } catch (Exception ignored) { + return null; + } + } return null; }) .filter(Objects::nonNull) @@ -2491,29 +2498,29 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { List result = new ArrayList<>(); for (Map detailMap : studentDetails) { - // 组装结果数据 + // 组装结果数据(兼容 Map 键为下划线或驼峰:map-underscore-to-camel-case=true) Map studentInfo = new LinkedHashMap<>(); - // 学生基本信息 - studentInfo.put("studentId", detailMap.get("student_id")); - studentInfo.put("studentName", detailMap.get("student_name")); - studentInfo.put("studentNo", detailMap.get("student_no")); - studentInfo.put("gender", detailMap.get("gender")); + Object studentIdObj = mapVal(detailMap, "studentId", "student_id"); + // 学生基本信息(雪花 ID 统一转字符串,避免前端精度丢失) + studentInfo.put("studentId", toIdString(studentIdObj)); + studentInfo.put("studentName", mapVal(detailMap, "studentName", "student_name")); + studentInfo.put("studentNo", mapVal(detailMap, "studentNo", "student_no")); + studentInfo.put("gender", mapVal(detailMap, "gender", "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")); + studentInfo.put("collegeId", toIdString(mapVal(detailMap, "collegeId", "college_id"))); + studentInfo.put("collegeName", mapVal(detailMap, "collegeName", "college_name")); + studentInfo.put("majorId", toIdString(mapVal(detailMap, "majorId", "major_id"))); + studentInfo.put("majorName", mapVal(detailMap, "majorName", "major_name")); + studentInfo.put("classId", toIdString(mapVal(detailMap, "classId", "class_id"))); + studentInfo.put("className", mapVal(detailMap, "className", "class_name")); // 床位信息 - studentInfo.put("bedName", detailMap.get("bed_name")); - studentInfo.put("bedCode", detailMap.get("bed_code")); - studentInfo.put("bedId", detailMap.get("bed_id")); + studentInfo.put("bedName", mapVal(detailMap, "bedName", "bed_name")); + studentInfo.put("bedCode", mapVal(detailMap, "bedCode", "bed_code")); + studentInfo.put("bedId", toIdString(mapVal(detailMap, "bedId", "bed_id"))); - Object studentIdObj = detailMap.get("student_id"); if (studentIdObj instanceof Number) { Long studentIdValue = ((Number) studentIdObj).longValue(); XgStudentLeaveVo leaveInfo = leaveMap.get(studentIdValue); @@ -2525,6 +2532,21 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { leaveInfoMap.put("leaveEndTime", leaveInfo.getEndTime()); studentInfo.put("leaveInfo", leaveInfoMap); } + } else if (studentIdObj != null) { + try { + Long studentIdValue = Long.valueOf(studentIdObj.toString()); + XgStudentLeaveVo leaveInfo = leaveMap.get(studentIdValue); + if (leaveInfo != null) { + Map leaveInfoMap = new LinkedHashMap<>(); + leaveInfoMap.put("leaveId", leaveInfo.getId()); + leaveInfoMap.put("leaveTypeName", leaveInfo.getLeaveTypeName()); + leaveInfoMap.put("leaveStartTime", leaveInfo.getStartTime()); + leaveInfoMap.put("leaveEndTime", leaveInfo.getEndTime()); + studentInfo.put("leaveInfo", leaveInfoMap); + } + } catch (Exception ignored) { + // ignore invalid student id + } } result.add(studentInfo); @@ -2620,6 +2642,14 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { return val; } + /** 雪花 ID 等长整型转字符串,避免前端 Number 精度丢失 */ + private static String toIdString(Object val) { + if (val == null) { + return null; + } + return val.toString(); + } + /** * 根据床位ID获取该床位的学生信息 * 包含学生基本信息、学院、专业、班级等信息 diff --git a/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue index 0c8452e1..438d94a3 100644 --- a/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue +++ b/mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue @@ -110,7 +110,7 @@ - + { - if ( - student.classId == null || - student.classId === '' || - student.classId === 0 || - student.classId === '0' - ) { + const classId = student.classId != null ? student.classId : student.class_id; + const className = student.className || student.class_name; + if (classId == null || classId === '' || classId === 0 || classId === '0') { return; } - const key = String(student.classId); + const key = String(classId); if (!map.has(key)) { map.set(key, { - classId: student.classId, - className: student.className || key, + classId: key, + className: className || key, }); } }); @@ -2466,17 +2463,12 @@ export default { // 提交表单 async handleSubmit() { try { - // 表单验证 - await this.$refs.formRef.validate(); - - if ((this.formData.totalScore || 0) > 100) { - uni.showToast({ title: '宿舍合计扣分不能超过100分', icon: 'none' }); - return; - } - - if (!this.formData.classId) { + // 归属班级:无选项时给出明确提示(避免被统一文案掩盖) + if (!this.classOptions.length || !this.formData.classId) { uni.showToast({ - title: '请选择归属班级(取自当前寝室在住学生)', + title: this.classOptions.length + ? '请选择归属班级' + : '当前寝室无在住学生班级,无法提交宿舍扣分', icon: 'none', }); return; @@ -2487,6 +2479,44 @@ export default { if (matchedClass) { this.formData.className = matchedClass.className; } + if (!this.formData.className) { + uni.showToast({ title: '归属班级名称缺失,请重新选择归属班级', icon: 'none' }); + return; + } + if (!this.formData.hygieneGrade) { + uni.showToast({ title: '请选择卫生等级', icon: 'none' }); + return; + } + + // 只校验有 rules 的字段。uView 全量 validate() 会把无 prop 的 form-item + // 当成 undefined 一并校验,内部 child.prop.split 抛错,误报「表单不完整」 + const formRef = this.$refs.formRef; + if (formRef && typeof formRef.validateField === 'function') { + const propsToValidate = Object.keys(this.rules || {}); + try { + await new Promise((resolve, reject) => { + formRef.validateField(propsToValidate, (errors) => { + if (errors && errors.length) { + reject(errors); + } else { + resolve(true); + } + }); + }); + } catch (validateError) { + const firstMsg = + Array.isArray(validateError) && validateError[0] && validateError[0].message + ? validateError[0].message + : '请检查表单填写是否完整'; + uni.showToast({ title: firstMsg, icon: 'none' }); + return; + } + } + + if ((this.formData.totalScore || 0) > 100) { + uni.showToast({ title: '宿舍合计扣分不能超过100分', icon: 'none' }); + return; + } this.submitLoading = true; @@ -2498,20 +2528,23 @@ export default { .map((file) => file.ossId); this.formData.attachmentIds = attachmentOssIds.join(','); + // 雪花 ID 统一转字符串,避免 JS 精度丢失 + const toId = (v) => (v == null || v === '' ? undefined : String(v)); + // 处理学生评分数据 const studentScores = this.roomStudents.map((student) => ({ - studentId: student.studentId, + studentId: toId(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, - bedName: student.bedName, + classId: toId(student.classId || student.class_id), + className: student.className || student.class_name, + majorId: toId(student.majorId || student.major_id), + majorName: student.majorName || student.major_name, + collegeId: toId(student.collegeId || student.college_id), + collegeName: student.collegeName || student.college_name, + bedId: toId(student.bedId || student.bed_id), + bedCode: student.bedCode || student.bed_code, + bedName: student.bedName || student.bed_name, studentScore: student.student_score || 0, attendanceStatus: student.attendance_status || '1', studentGrade: student.student_grade || 'A', @@ -2526,7 +2559,7 @@ export default { violations.forEach((violation) => { violationData.push({ ...violation, - studentId, + studentId: toId(studentId), violationSource: '1', // 宿舍卫生检查来源 }); }); @@ -2537,22 +2570,24 @@ export default { // 基础检查信息 checkTitle: this.checkInfo.checkTitle, checkDate: this.checkInfo.checkDate, - checkerId: this.checkInfo.checkerId, + checkerId: toId(this.checkInfo.checkerId), checkerName: this.checkInfo.checkerName, checkType: this.checkInfo.checkType, checkLevel: this.checkInfo.checkLevel, // 房间信息 - roomId: this.roomInfo.id, + roomId: toId(this.roomInfo.id), roomCode: this.roomInfo.roomCode || this.roomInfo.roomName, roomName: this.roomInfo.roomName, - buildingId: this.checkInfo.buildingId, + buildingId: toId(this.checkInfo.buildingId), buildingName: this.checkInfo.buildingName, - campusId: this.checkInfo.campusId, + campusId: toId(this.checkInfo.campusId), campusName: this.checkInfo.campusName, // 宿舍整体评分 ...this.formData, + classId: toId(this.formData.classId), + className: this.formData.className, deductionItems: this.deductionItems, // 学生评分数据 @@ -2590,7 +2625,11 @@ export default { } } catch (error) { console.error('提交表单失败:', error); - uni.showToast({ title: '请检查表单填写是否完整', icon: 'none' }); + const msg = + (error && (error.msg || error.message)) || + (typeof error === 'string' ? error : '') || + '保存失败,请重试'; + uni.showToast({ title: msg, icon: 'none' }); } finally { this.submitLoading = false; } diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue index f5b0c2ce..ba61216c 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckEditDialog.vue @@ -522,7 +522,7 @@ const syncClassOptionsFromStudents = (students: any[], preferClassId?: string | const key = String(student.classId); if (!map.has(key)) { map.set(key, { - classId: student.classId, + classId: key, className: student.className || key }); } @@ -1255,7 +1255,25 @@ const handleDownloadAttachment = async (file: any) => { // 提交表单 const submitForm = async () => { try { - await hygieneFormRef.value?.validate(); + if (!classOptions.value.length) { + ElMessage.error('当前寝室无在住学生班级,无法提交宿舍扣分'); + return; + } + + try { + await hygieneFormRef.value?.validate(); + } catch (validateError: any) { + console.error('表单校验失败:', validateError); + let firstMsg = '请检查表单填写是否完整'; + if (validateError && typeof validateError === 'object') { + const firstField = Object.values(validateError)[0] as any; + if (Array.isArray(firstField) && firstField[0]?.message) { + firstMsg = firstField[0].message; + } + } + ElMessage.error(firstMsg); + return; + } if ((hygieneForm.totalScore || 0) > 100) { ElMessage.error('宿舍合计扣分不能超过100分'); @@ -1342,7 +1360,7 @@ const submitForm = async () => { } catch (error) { console.error('提交表单失败:', error); if (window.ElMessage) { - window.ElMessage.error('请检查表单填写是否完整'); + window.ElMessage.error('保存失败,请重试'); } } finally { buttonLoading.value = false; diff --git a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue index 7682d0fa..57cce4c5 100644 --- a/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue +++ b/web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckSingleDialog.vue @@ -480,7 +480,7 @@ const syncClassOptionsFromStudents = (students: any[], preferClassId?: string | const key = String(classId); if (!map.has(key)) { map.set(key, { - classId, + classId: key, className: (student.className || '').toString().trim() || key }); } @@ -936,7 +936,25 @@ const handleDownloadAttachment = async (file: any) => { // 提交表单 const submitForm = async () => { try { - await hygieneFormRef.value?.validate(); + if (!classOptions.value.length) { + ElMessage.error('当前寝室无在住学生班级,无法提交宿舍扣分'); + return; + } + + try { + await hygieneFormRef.value?.validate(); + } catch (validateError: any) { + console.error('表单校验失败:', validateError); + let firstMsg = '请检查表单填写是否完整'; + if (validateError && typeof validateError === 'object') { + const firstField = Object.values(validateError)[0] as any; + if (Array.isArray(firstField) && firstField[0]?.message) { + firstMsg = firstField[0].message; + } + } + ElMessage.error(firstMsg); + return; + } if ((hygieneForm.totalScore || 0) > 100) { ElMessage.error('宿舍合计扣分不能超过100分'); @@ -1056,7 +1074,7 @@ const submitForm = async () => { } } catch (error) { console.error('提交表单失败:', error); - ElMessage.error('请检查表单填写是否完整'); + ElMessage.error('保存失败,请重试'); } finally { buttonLoading.value = false; }