Parcourir la source

fix: 修复床位分配对象展示与卫生检查提交误报校验

恢复房间床位详情完整关联查询以显示学院/班级/学生;修复卫生检查 Web/移动端表单校验误报,并将房间学生 ID 以字符串返回避免精度丢失。

Co-authored-by: Cursor <cursoragent@cursor.com>
“wuzhen” il y a 2 mois
Parent
commit
6a0f1206fa

+ 1 - 1
backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgDormBedMapper.java

@@ -49,7 +49,7 @@ public interface XgDormBedMapper extends BaseMapperPlus<XgDormBed, XgDormBedVo>
                 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,
 

+ 27 - 70
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<XgDormBedDetailVo> queryBedDetailsByRoomId(Long roomId) {
-        List<XgDormBedDetailVo> result = new ArrayList<>();
-
-        List<Map<String, Object>> studentBeds = xgDormBedStudentMapper.selectRoomStudents(roomId);
+        // 使用完整 LEFT JOIN:已分学院/班级/学生的床位均能带出分配对象
+        List<XgDormBedDetailVo> result = baseMapper.selectBedDetailsByRoomId(roomId);
+        if (result == null || result.isEmpty()) {
+            return result == null ? new ArrayList<>() : result;
+        }
 
-        List<Long> studentIds = studentBeds.stream()
-            .map(row -> {
-                Object studentId = row.get("student_id");
-                if (studentId instanceof Number) {
-                    return ((Number) studentId).longValue();
-                }
-                return null;
-            })
+        List<Long> studentIds = result.stream()
+            .map(XgDormBedDetailVo::getStudentId)
             .filter(Objects::nonNull)
+            .distinct()
             .collect(Collectors.toList());
+        if (studentIds.isEmpty()) {
+            return result;
+        }
 
-        Map<Long, XgStudentLeaveVo> leaveMap = Map.of();
-        if (!studentIds.isEmpty()) {
-            leaveMap = xgStudentLeaveService.queryStudentLeavesByCheckDate(
-                studentIds, LocalDateTime.now());
+        Map<Long, XgStudentLeaveVo> leaveMap = xgStudentLeaveService.queryStudentLeavesByCheckDate(
+            studentIds, LocalDateTime.now());
+        if (leaveMap == null || leaveMap.isEmpty()) {
+            return result;
         }
 
-        Set<Long> occupiedBedIds = new HashSet<>();
-        for (Map<String, Object> 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);
+        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);
-        }
-
-        List<XgDormBed> emptyBeds = baseMapper.selectList(
-            Wrappers.<XgDormBed>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);
+            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);
         }
-
         return result;
     }
 

+ 47 - 17
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<Long> 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<Object> result = new ArrayList<>();
 
             for (Map<String, Object> detailMap : studentDetails) {
-                // 组装结果数据
+                // 组装结果数据(兼容 Map 键为下划线或驼峰:map-underscore-to-camel-case=true)
                 Map<String, Object> 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<String, Object> 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获取该床位的学生信息
      * 包含学生基本信息、学院、专业、班级等信息

+ 75 - 36
mobile/src/pages/xgss/wsjc/components/SingleRoomCheck.vue

@@ -110,7 +110,7 @@
                 </u-form-item>
 
                 <!-- 宿舍扣分 -->
-                <u-form-item label="宿舍扣分" prop="totalScore">
+                <u-form-item label="宿舍扣分">
                     <u-input
                         v-model="formData.totalScore"
                         type="digit"
@@ -1013,19 +1013,16 @@ export default {
         syncClassOptionsFromStudents(students, preferClassId) {
             const map = new Map();
             (students || []).forEach((student) => {
-                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;
             }

+ 21 - 3
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;

+ 21 - 3
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;
   }