From c164d1feb4a04a8db089d2c3c792bfa04a7ae293 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Fri, 22 Aug 2025 22:24:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-prod.yml | 38 ++ .../XgDormHygieneCheckController.java | 15 + .../service/IXgDormHygieneCheckService.java | 11 + .../impl/XgDormBedStudentServiceImpl.java | 189 ++++++++-- .../impl/XgDormHygieneCheckServiceImpl.java | 66 ++++ .../impl/XgStuColBatchServiceImpl.java | 15 +- .../XskhAssessmentScheduleServiceImpl.java | 4 +- docs/开发文档/API/API文档总览.md | 5 + docs/开发文档/API/学生信息收集批次API.md | 233 +++++++++++++ docs/开发文档/API/宿舍床位分配管理API.md | 300 ++++++++++++++++ mobile/src/pages/task/myDocument.vue | 9 +- mobile/src/pages/task/taskCopyList.vue | 11 +- mobile/src/pages/task/taskFinish.vue | 11 +- mobile/src/pages/task/taskWaiting.vue | 236 ++++++++----- .../src/pages/xg/xsjcxx/xsqj/leave/index.vue | 121 ++++++- mobile/src/pages/xg/xsjcxx/xsws/index.vue | 19 +- mobile/src/pages/xgss/sswj/grsswj.vue | 174 ++++++++-- mobile/src/pages/xgss/wsjc/wsjc.vue | 77 +++-- mobile/src/pages/xgss/xssswsjc/xssswsjc.vue | 139 ++++---- mobile/src/pages/xskh/khsq/khsq.vue | 325 +++++++++++------- mobile/src/pages/xskh/wjjl/xswjjl.vue | 104 +++++- 21 files changed, 1721 insertions(+), 381 deletions(-) create mode 100644 docs/开发文档/API/学生信息收集批次API.md create mode 100644 docs/开发文档/API/宿舍床位分配管理API.md diff --git a/backend/ruoyi-admin/src/main/resources/application-prod.yml b/backend/ruoyi-admin/src/main/resources/application-prod.yml index e9177de3..7ba1abbf 100644 --- a/backend/ruoyi-admin/src/main/resources/application-prod.yml +++ b/backend/ruoyi-admin/src/main/resources/application-prod.yml @@ -313,3 +313,41 @@ system: # - 对于频繁访问的小文件,可以增加 max-cache-size # - 对于大文件较多的环境,可以调整 max-single-file-size # - 对于访问频率较低的环境,可以适当减少 expire-time-ms + + +--- # SQL日志配置(用于调试班级名额查询问题) +logging: + level: + # 开启 MyBatis SQL 日志 + org.dromara.jcbt.mapper: DEBUG + org.dromara.jcbt.service: DEBUG + # 开启 MyBatis-Plus SQL 日志 + com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor: DEBUG + # 开启 HikariCP 连接池日志(可选) + com.zaxxer.hikari: DEBUG + # 开启 P6Spy SQL 日志(更详细的SQL执行日志) + p6spy: INFO + pattern: + # 控制台日志格式,增加线程名和方法名 + console: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx" + +# MyBatis 配置 +mybatis-plus: + configuration: + # 开启 SQL 日志打印 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # 开启驼峰命名转换 + map-underscore-to-camel-case: true + # 开启缓存 + cache-enabled: true + # 开启懒加载 + lazy-loading-enabled: true + # 开启积极懒加载 + aggressive-lazy-loading: false + +--- # 高德地图配置 +amap: + # 高德地图API密钥(请替换为您的实际密钥) + key: 920750e421cd6357ddf60ee1e929e0dd + # 高德地图API基础URL + url: https://restapi.amap.com diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormHygieneCheckController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormHygieneCheckController.java index c68f89b8..c5e203d0 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormHygieneCheckController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgDormHygieneCheckController.java @@ -149,4 +149,19 @@ public class XgDormHygieneCheckController extends BaseController { return R.ok(list); } + /** + * 根据学生ID分页查询该学生宿舍的卫生检查信息 + * + * @param studentId 学生ID + * @param bo 查询条件 + * @param pageQuery 分页参数 + */ + @SaCheckPermission("dormHygieneCheck:dormHygieneCheck:query") + @GetMapping("/student/{studentId}/page") + public TableDataInfo getHygieneChecksByStudentIdPage( + @NotNull(message = "学生ID不能为空") @PathVariable Long studentId, + XgDormHygieneCheckBo bo, PageQuery pageQuery) { + return xgDormHygieneCheckService.queryPageListByStudentId(studentId, bo, pageQuery); + } + } diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormHygieneCheckService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormHygieneCheckService.java index d3f74d45..fc056139 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormHygieneCheckService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgDormHygieneCheckService.java @@ -100,4 +100,15 @@ public interface IXgDormHygieneCheckService { */ List queryByStudentId(Long studentId); + /** + * 根据学生ID分页查询该学生宿舍的卫生检查信息 + * + * @param studentId 学生ID + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 卫生检查信息分页列表 + */ + TableDataInfo queryPageListByStudentId(Long studentId, XgDormHygieneCheckBo bo, + PageQuery pageQuery); + } 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 3f634fe5..232f1775 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 @@ -610,10 +610,10 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { } // 3. 检查床位是否已分配给学院(必须先分配给学院才能分配给班级) + // 修复:查询所有已分配给学院的床位,不论是否已分配班级 LambdaQueryWrapper collegeCheckWrapper = new LambdaQueryWrapper<>(); collegeCheckWrapper.in(XgDormBedStudent::getBedId, bo.getBedIds()) - .isNotNull(XgDormBedStudent::getCollegeId) - .isNull(XgDormBedStudent::getClassId) // 只分配了学院,还没分配班级 + .isNotNull(XgDormBedStudent::getCollegeId) // 已分配给学院 .eq(XgDormBedStudent::getStatus, "0"); // 正常状态,未退宿 List existingCollegeAllocations = baseMapper.selectList(collegeCheckWrapper); @@ -640,6 +640,9 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { throw new RuntimeException("以下床位尚未分配给学院,无法分配给班级:" + String.join("、", unallocatedBedNames)); } + log.info("床位预检验证通过:75个床位中,{}个已分配给学院,{}个未分配给学院", + collegeAllocatedBedIds.size(), unallocatedBedIds.size()); + // 5. 获取楼栋信息,用于获取校区ID和宿舍区ID XgDormBuilding building = xgDormBuildingMapper.selectById(bo.getBuildingId()); if (building == null) { @@ -725,6 +728,9 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { int[] roomUsage = new int[sortedRoomIds.size()]; // 记录每个房间已使用床位数 Map roomClassMap = new java.util.HashMap<>(); // 记录房间主要分配给哪个班级 + // 新增:详细的床位分配记录,记录每个床位分配给哪个班级 + Map bedClassMap = new java.util.HashMap<>(); // bedId -> classId 的映射 + // 按班级顺序,采用房间优先分配策略 for (int classIndex = 0; classIndex < bo.getClassIds().size(); classIndex++) { Long classId = bo.getClassIds().get(classIndex); @@ -744,6 +750,12 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { // 分配整个房间给当前班级 roomUsage[roomIndex] = roomCapacity; roomClassMap.put(roomIndex, classId); + + // 记录每个床位的分配 + for (XgDormBed bed : roomBeds) { + bedClassMap.put(bed.getId(), classId); + } + remainingStudents -= roomCapacity; log.info("为班级{}分配完整房间{},房间编码{},床位数{}", @@ -760,6 +772,16 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { // 优先分配到已有该班级学生的房间 if (availableBedsCount > 0 && classId.equals(roomClassMap.get(roomIndex))) { int bedsToAllocate = Math.min(remainingStudents, availableBedsCount); + + // 从房间的未分配床位中选择床位分配给当前班级 + int allocated = 0; + for (XgDormBed bed : roomBeds) { + if (!bedClassMap.containsKey(bed.getId()) && allocated < bedsToAllocate) { + bedClassMap.put(bed.getId(), classId); + allocated++; + } + } + roomUsage[roomIndex] += bedsToAllocate; remainingStudents -= bedsToAllocate; @@ -777,6 +799,16 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { // 分配到完全空闲的房间 if (availableBedsCount > 0 && roomClassMap.get(roomIndex) == null) { int bedsToAllocate = Math.min(remainingStudents, availableBedsCount); + + // 从房间的未分配床位中选择床位分配给当前班级 + int allocated = 0; + for (XgDormBed bed : roomBeds) { + if (!bedClassMap.containsKey(bed.getId()) && allocated < bedsToAllocate) { + bedClassMap.put(bed.getId(), classId); + allocated++; + } + } + roomUsage[roomIndex] += bedsToAllocate; roomClassMap.put(roomIndex, classId); remainingStudents -= bedsToAllocate; @@ -795,6 +827,16 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { // 分配到任何有空闲床位的房间,即使会产生混住 if (availableBedsCount > 0) { int bedsToAllocate = Math.min(remainingStudents, availableBedsCount); + + // 从房间的未分配床位中选择床位分配给当前班级 + int allocated = 0; + for (XgDormBed bed : roomBeds) { + if (!bedClassMap.containsKey(bed.getId()) && allocated < bedsToAllocate) { + bedClassMap.put(bed.getId(), classId); + allocated++; + } + } + roomUsage[roomIndex] += bedsToAllocate; remainingStudents -= bedsToAllocate; @@ -820,38 +862,38 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { } // 12. 批量更新床位学生记录,补充班级和专业信息 + // 使用新的bedClassMap确保准确分配 + int totalUpdatedRecords = 0; + for (int i = 0; i < bo.getClassIds().size(); i++) { Long classId = bo.getClassIds().get(i); XgClassMsg classInfo = classMap.get(classId); - // 计算应该分配给该班级的床位数量 - int classStudentCount = classCounts.get(i); + // 从bedClassMap中获取分配给该班级的床位ID列表 + List classBedIds = bedClassMap.entrySet().stream() + .filter(entry -> classId.equals(entry.getValue())) + .map(Map.Entry::getKey) + .collect(java.util.stream.Collectors.toList()); - // 获取该班级应该分配的床位ID列表(按分配算法确定的顺序) - List classBedIds = new ArrayList<>(); - int allocatedCount = 0; - - // 按房间分配顺序找出分配给该班级的床位 - for (int roomIndex = 0; roomIndex < sortedRoomIds.size() - && allocatedCount < classStudentCount; roomIndex++) { - if (classId.equals(roomClassMap.get(roomIndex))) { - Long roomId = sortedRoomIds.get(roomIndex); - List roomBeds = bedsByRoom.get(roomId); - - // 计算这个房间分配给该班级的床位数 - int roomBedsForThisClass = Math.min(classStudentCount - allocatedCount, roomBeds.size()); - - for (int j = 0; j < roomBedsForThisClass; j++) { - classBedIds.add(roomBeds.get(j).getId()); - allocatedCount++; - } - } - } + log.info("班级{}({})应分配床位数:{},实际分配床位ID:{}", + classInfo.getClassName(), classId, classCounts.get(i), classBedIds); if (!classBedIds.isEmpty()) { - // 批量更新该班级分配的床位记录 - LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); - updateWrapper.in(XgDormBedStudent::getBedId, classBedIds) + // 先查询现有记录状态,便于诊断问题 + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + queryWrapper.in(XgDormBedStudent::getBedId, classBedIds) + .eq(XgDormBedStudent::getStatus, "0"); + List existingRecords = baseMapper.selectList(queryWrapper); + + log.info("床位记录查询结果:共{}条记录,其中已分配班级的{}条,未分配班级的{}条", + existingRecords.size(), + existingRecords.stream().mapToInt(r -> r.getClassId() != null ? 1 : 0).sum(), + existingRecords.stream().mapToInt(r -> r.getClassId() == null ? 1 : 0).sum()); + + // 分两种情况处理:新分配和重新分配 + // 情况1:更新未分配班级的记录 + LambdaUpdateWrapper updateWrapper1 = Wrappers.lambdaUpdate(); + updateWrapper1.in(XgDormBedStudent::getBedId, classBedIds) .eq(XgDormBedStudent::getStatus, "0") // 正常状态 .isNull(XgDormBedStudent::getClassId) // 只更新还没分配班级的记录 .isNotNull(XgDormBedStudent::getCollegeId) // 已分配给学院 @@ -860,11 +902,100 @@ public class XgDormBedStudentServiceImpl implements IXgDormBedStudentService { .set(XgDormBedStudent::getUpdateTime, now) .set(XgDormBedStudent::getUpdateBy, loginUserId); - int updateCount = baseMapper.update(null, updateWrapper); - log.info("为班级{}({})更新了{}条床位分配记录", classInfo.getClassName(), classId, updateCount); + int updateCount1 = baseMapper.update(null, updateWrapper1); + + // 情况2:强制更新所有指定床位的班级分配(覆盖之前的分配) + LambdaUpdateWrapper updateWrapper2 = Wrappers.lambdaUpdate(); + updateWrapper2.in(XgDormBedStudent::getBedId, classBedIds) + .eq(XgDormBedStudent::getStatus, "0") // 正常状态 + .isNotNull(XgDormBedStudent::getCollegeId) // 已分配给学院 + .isNotNull(XgDormBedStudent::getClassId) // 已分配班级的记录 + .set(XgDormBedStudent::getMajorId, classInfo.getMajorId()) + .set(XgDormBedStudent::getClassId, classInfo.getId()) + .set(XgDormBedStudent::getUpdateTime, now) + .set(XgDormBedStudent::getUpdateBy, loginUserId); + + int updateCount2 = baseMapper.update(null, updateWrapper2); + + int totalUpdateCount = updateCount1 + updateCount2; + totalUpdatedRecords += totalUpdateCount; + + log.info("为班级{}({})更新了{}条床位分配记录(新分配{}条,重新分配{}条),预期{}条", + classInfo.getClassName(), classId, totalUpdateCount, updateCount1, updateCount2, + classBedIds.size()); + + // 验证更新结果 + if (totalUpdateCount != classBedIds.size()) { + log.error("班级{}床位分配不完整:预期更新{}条记录,实际更新{}条记录", + classInfo.getClassName(), classBedIds.size(), totalUpdateCount); + + // 详细诊断:查询每个床位的具体状态 + for (Long bedId : classBedIds) { + LambdaQueryWrapper bedQuery = Wrappers.lambdaQuery(); + bedQuery.eq(XgDormBedStudent::getBedId, bedId); + List bedRecords = baseMapper.selectList(bedQuery); + log.error("床位{}的记录状态:{}", bedId, + bedRecords.isEmpty() ? "无记录" + : bedRecords.stream() + .map(r -> String.format("status=%s,collegeId=%s,classId=%s", + r.getStatus(), r.getCollegeId(), r.getClassId())) + .collect(java.util.stream.Collectors.joining(","))); + } + } + } else { + log.warn("班级{}({})没有分配到任何床位", classInfo.getClassName(), classId); } } + log.info("总计更新了{}条床位分配记录", totalUpdatedRecords); + + // 验证总体分配结果 + int totalAllocatedBeds = bedClassMap.size(); + int totalExpectedBeds = classCounts.stream().mapToInt(Integer::intValue).sum(); + log.info("床位分配验证:总可用床位{},预期分配{},实际分配{},剩余床位{}", + totalAvailableBeds, totalExpectedBeds, totalAllocatedBeds, + totalAvailableBeds - totalAllocatedBeds); + + // 当床位数量完全匹配需求时,必须全部分配完 + if (totalExpectedBeds == totalAvailableBeds && totalAllocatedBeds < totalExpectedBeds) { + log.error("严重错误:床位数量完全匹配需求({}个),但实际只分配了{}个床位,剩余{}个床位未分配", + totalExpectedBeds, totalAllocatedBeds, totalExpectedBeds - totalAllocatedBeds); + + // 详细诊断未分配的床位 + Set allocatedBedIds = bedClassMap.keySet(); + List unallocatedBeds = collegeAllocatedBedIds.stream() + .filter(bedId -> !allocatedBedIds.contains(bedId)) + .collect(java.util.stream.Collectors.toList()); + + log.error("未分配的床位ID:{}", unallocatedBeds); + + // 分析未分配床位的房间分布 + Map> unallocatedByRoom = unallocatedBeds.stream() + .collect(java.util.stream.Collectors.groupingBy(bedId -> { + return beds.stream() + .filter(bed -> bed.getId().equals(bedId)) + .findFirst() + .map(XgDormBed::getRoomId) + .orElse(0L); + })); + + for (Map.Entry> entry : unallocatedByRoom.entrySet()) { + Long roomId = entry.getKey(); + List roomUnallocatedBeds = entry.getValue(); + String roomCode = roomCodeMap.get(roomId); + log.error("房间{}({})有{}个床位未分配:{}", roomCode, roomId, roomUnallocatedBeds.size(), roomUnallocatedBeds); + } + + throw new RuntimeException("床位分配不完整,存在" + (totalExpectedBeds - totalAllocatedBeds) + "个床位未分配"); + } + + // 一般情况下的验证 + if (totalExpectedBeds <= totalAvailableBeds && totalAllocatedBeds < totalExpectedBeds) { + log.error("床位分配不完整:应分配{}个床位,实际只分配了{}个床位", + totalExpectedBeds, totalAllocatedBeds); + throw new RuntimeException("床位分配不完整,存在未分配的床位"); + } + log.info("成功更新{}个床位分配给{}个班级,总可用床位:{}", bo.getBedIds().size(), bo.getClassIds().size(), totalAvailableBeds); return true; diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java index 207cf006..f74536ad 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java @@ -423,6 +423,72 @@ public class XgDormHygieneCheckServiceImpl implements IXgDormHygieneCheckService return result; } + /** + * 根据学生ID分页查询该学生宿舍的卫生检查信息 + * + * @param studentId 学生ID + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 卫生检查信息分页列表 + */ + @Override + public TableDataInfo queryPageListByStudentId(Long studentId, XgDormHygieneCheckBo bo, + PageQuery pageQuery) { + if (studentId == null) { + log.warn("学生ID为空,无法查询卫生检查信息"); + return TableDataInfo.build(); + } + + // 1. 通过学生ID查询学生评分记录,获取卫生检查ID列表 + LambdaQueryWrapper studentScoreWrapper = Wrappers.lambdaQuery(); + studentScoreWrapper.eq(XgDormHygieneStudentScore::getStudentId, studentId); + studentScoreWrapper.eq(XgDormHygieneStudentScore::getDeleted, 0); // 只查询未删除的记录 + List studentScores = studentScoreMapper.selectList(studentScoreWrapper); + + if (studentScores.isEmpty()) { + log.info("学生ID: {} 没有找到相关的卫生检查记录", studentId); + return TableDataInfo.build(); + } + + // 2. 提取卫生检查ID列表 + List hygieneCheckIds = studentScores.stream() + .map(XgDormHygieneStudentScore::getHygieneCheckId) + .distinct() + .toList(); + + // 3. 构建查询条件,结合传入的bo参数和学生相关的检查ID + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + + // 基础条件:只查询该学生相关的检查记录 + lqw.in(XgDormHygieneCheck::getId, hygieneCheckIds); + lqw.eq(XgDormHygieneCheck::getDeleted, 0); // 只查询未删除的记录 + + // 应用搜索条件(如果有) + if (bo != null) { + lqw.like(StringUtils.isNotBlank(bo.getCheckTitle()), XgDormHygieneCheck::getCheckTitle, bo.getCheckTitle()); + lqw.eq(bo.getCheckDate() != null, XgDormHygieneCheck::getCheckDate, bo.getCheckDate()); + lqw.like(StringUtils.isNotBlank(bo.getCheckerName()), XgDormHygieneCheck::getCheckerName, + bo.getCheckerName()); + lqw.eq(StringUtils.isNotBlank(bo.getCheckType()), XgDormHygieneCheck::getCheckType, bo.getCheckType()); + lqw.eq(StringUtils.isNotBlank(bo.getCheckLevel()), XgDormHygieneCheck::getCheckLevel, bo.getCheckLevel()); + lqw.eq(StringUtils.isNotBlank(bo.getHygieneGrade()), XgDormHygieneCheck::getHygieneGrade, + bo.getHygieneGrade()); + lqw.like(StringUtils.isNotBlank(bo.getCheckSummary()), XgDormHygieneCheck::getCheckSummary, + bo.getCheckSummary()); + } + + // 按创建时间倒序排列 + lqw.orderByDesc(XgDormHygieneCheck::getCreateTime); + + // 4. 分页查询 + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + + log.info("学生ID: {} 分页查询卫生检查记录 - 页码: {}, 页大小: {}, 总数: {}", + studentId, pageQuery.getPageNum(), pageQuery.getPageSize(), result.getTotal()); + + return TableDataInfo.build(result); + } + /** * 转换检查类型代码为文本 */ diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStuColBatchServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStuColBatchServiceImpl.java index 1a97fbd0..b5c205cf 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStuColBatchServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStuColBatchServiceImpl.java @@ -40,7 +40,7 @@ public class XgStuColBatchServiceImpl implements IXgStuColBatchService { * @return 学生信息收集批次 */ @Override - public XgStuColBatchVo queryById(Long id){ + public XgStuColBatchVo queryById(Long id) { return baseMapper.selectVoById(id); } @@ -73,10 +73,11 @@ public class XgStuColBatchServiceImpl implements IXgStuColBatchService { private LambdaQueryWrapper buildQueryWrapper(XgStuColBatchBo bo) { Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.orderByAsc(XgStuColBatch::getId); + lqw.orderByDesc(XgStuColBatch::getCreateTime); lqw.like(StringUtils.isNotBlank(bo.getBatchName()), XgStuColBatch::getBatchName, bo.getBatchName()); lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XgStuColBatch::getStatus, bo.getStatus()); - lqw.eq(StringUtils.isNotBlank(bo.getCollectionType()), XgStuColBatch::getCollectionType, bo.getCollectionType()); + lqw.eq(StringUtils.isNotBlank(bo.getCollectionType()), XgStuColBatch::getCollectionType, + bo.getCollectionType()); return lqw; } @@ -113,8 +114,8 @@ public class XgStuColBatchServiceImpl implements IXgStuColBatchService { /** * 保存前的数据校验 */ - private void validEntityBeforeSave(XgStuColBatch entity){ - //TODO 做一些数据校验,如唯一约束 + private void validEntityBeforeSave(XgStuColBatch entity) { + // TODO 做一些数据校验,如唯一约束 } /** @@ -126,8 +127,8 @@ public class XgStuColBatchServiceImpl implements IXgStuColBatchService { */ @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + if (isValid) { + // TODO 做一些业务上的校验,判断是否需要校验 } return baseMapper.deleteByIds(ids) > 0; } diff --git a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentScheduleServiceImpl.java b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentScheduleServiceImpl.java index 7733d28e..5ccae860 100644 --- a/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentScheduleServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xskh/src/main/java/org/dromara/xskh/service/impl/XskhAssessmentScheduleServiceImpl.java @@ -98,6 +98,8 @@ public class XskhAssessmentScheduleServiceImpl implements IXskhAssessmentSchedul if (bo.getStatusList() != null && !bo.getStatusList().isEmpty()) { lqw.in(XskhAssessmentSchedule::getStatus, bo.getStatusList()); } + // 按创建时间倒序排列 + lqw.orderByDesc(XskhAssessmentSchedule::getCreateTime); return lqw; } @@ -222,4 +224,4 @@ public class XskhAssessmentScheduleServiceImpl implements IXskhAssessmentSchedul schedule.setStatus("3"); // 设置为已结束 return baseMapper.updateById(schedule) > 0; } -} \ No newline at end of file +} diff --git a/docs/开发文档/API/API文档总览.md b/docs/开发文档/API/API文档总览.md index 9f356e34..5a8f82d2 100644 --- a/docs/开发文档/API/API文档总览.md +++ b/docs/开发文档/API/API文档总览.md @@ -9,10 +9,12 @@ ## API模块列表 - [学生信息管理API](./学生信息管理API.md) - [学生信息收集批次管理API](./学生信息收集批次管理API.md) +- [学生信息收集批次API](./学生信息收集批次API.md) - **重要更新**:修改列表排序方式为按创建时间倒序(2025-01-03) - [班级信息管理API](./班级信息管理API.md) - [班级干部信息管理API](./班级干部信息管理API.md) - [宿舍管理API](./宿舍管理API.md) - [宿舍房间管理API](./宿舍房间管理API.md) - **新增床位详细信息查询接口**(2025-01-27) +- [宿舍床位分配管理API](./宿舍床位分配管理API.md) - **重要修复:床位分配不完整问题**(2025-01-27) - [宿舍管理员信息API](./宿舍管理员信息API.md) - [宿舍管理员授权API](./宿舍管理员授权API.md) - [学生管理员授权关系API](./学生管理员授权关系API.md) @@ -82,6 +84,8 @@ | 处理预约申请 | POST | /api/xljk/consultation/appointment/process | 预约管理 | xljk:consultation:appointment:process | | **获取房间床位详细信息** | GET | /dormbed/dormBed/details/room/{roomId} | 宿舍管理 | dormbed:dormBed:query | | **获取宿舍楼床位详细信息** | GET | /dormbed/dormBed/details/building/{buildingId} | 宿舍管理 | dormbed:dormBed:query | +| **批量分配床位给班级** | POST | /dormBedStudent/dormBedStudent/batchAllocateToClass | 床位分配 | xgss:dormBedStudent:allocateToClass | +| **批量分配床位给学院** | POST | /dormBedStudent/dormBedStudent/batchAllocateToCollege | 床位分配 | xgss:dormBedStudent:allocateToCollege | | 记录咨询结果 | POST | /api/xljk/consultation/appointment/record | 预约管理 | xljk:consultation:appointment:record | | 查询未到访记录列表 | GET | /api/xljk/consultation/no-show/list | 预约限制 | xljk:consultation:noshow:list | | 获取未到访记录详情 | GET | /api/xljk/consultation/no-show/{id} | 预约限制 | xljk:consultation:noshow:query | @@ -96,6 +100,7 @@ | 2025-07-01 | 1.0 | 初始版本 | 系统管理员 | | 2025-07-15 | 1.1 | 添加心理健康测评API | 张工 | | 2025-07-30 | 1.2 | 添加预约限制功能API | Cursor | +| 2025-01-27 | 1.3 | 添加宿舍床位分配管理API,修复床位分配不完整问题 | Cursor | **详细文档**: [企业信息管理API.md](./企业信息管理API.md) diff --git a/docs/开发文档/API/学生信息收集批次API.md b/docs/开发文档/API/学生信息收集批次API.md new file mode 100644 index 00000000..53fa2177 --- /dev/null +++ b/docs/开发文档/API/学生信息收集批次API.md @@ -0,0 +1,233 @@ +# 学生信息收集批次API文档 + +## 📋 文档信息 + +| 项目 | 信息 | +|------|------| +| **项目名称** | 校园管理系统 - 学生信息收集批次 | +| **API模块** | 学生信息收集批次管理 | +| **文档版本** | V1.1.0 | +| **创建时间** | 2025-01-03 | +| **基础路径** | `/XgStuColBatch/stuColBatch` | + +## 🎯 API概述 + +### 模块说明 +学生信息收集批次管理系统提供完整的批次创建、查询、修改、删除等API接口,支持学生信息收集的批次化管理。 + +### 技术规范 +- **认证方式**: Bearer Token (Sa-Token) +- **数据格式**: JSON +- **响应格式**: 统一使用R类型或TableDataInfo分页格式 +- **权限控制**: 基于角色的权限管理(RBAC) + +### 权限角色 +- **学生管理员**: 查看权限范围内的批次信息 +- **班主任**: 查看相关班级的批次信息 +- **全校学生管理员**: 查看全校范围的批次信息 +- **系统管理员**: 完整的批次管理权限 + +## 📊 数据模型 + +### 学生信息收集批次VO (XgStuColBatchVo) + +```java +{ + "id": 1, // 批次ID + "batchName": "2025年春季学期信息收集", // 批次名称 + "description": "收集学生基本信息", // 批次描述 + "startTime": "2025-02-01 00:00:00", // 开始时间 + "endTime": "2025-02-28 23:59:59", // 结束时间 + "status": "0", // 状态(0正常 1停用) + "collectionType": "1", // 收集类型(1-新学期收集 2-其他) + "remark": "备注信息", // 备注 + "createBy": 1, // 创建者 + "createTime": "2025-01-27 10:00:00", // 创建时间 + "updateBy": 1, // 更新者 + "updateTime": "2025-01-27 10:00:00" // 更新时间 +} +``` + +## 🔧 API接口列表 + +### 1. 基础管理接口 + +#### 1.1 查询学生信息收集批次列表 +- **接口路径**: GET `/XgStuColBatch/stuColBatch/list` +- **权限标识**: `XgStuColBatch:stuColBatch:list` +- **功能描述**: 分页查询学生信息收集批次列表 +- **排序方式**: 按创建时间倒序排列(最新创建的批次在前) + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| pageNum | integer | 否 | 页码,默认1 | +| pageSize | integer | 否 | 每页大小,默认10 | +| batchName | string | 否 | 批次名称(模糊查询) | +| status | string | 否 | 状态(0正常 1停用) | +| collectionType | string | 否 | 收集类型(1-新学期收集 2-其他) | + +**响应数据**: +```json +{ + "code": 200, + "msg": "查询成功", + "rows": [ + { + "id": 2, + "batchName": "2025年春季学期信息收集", + "description": "收集学生基本信息", + "startTime": "2025-02-01 00:00:00", + "endTime": "2025-02-28 23:59:59", + "status": "0", + "collectionType": "1", + "createTime": "2025-01-27 15:30:00" + }, + { + "id": 1, + "batchName": "2024年秋季学期信息收集", + "description": "收集学生基本信息", + "startTime": "2024-09-01 00:00:00", + "endTime": "2024-09-30 23:59:59", + "status": "1", + "collectionType": "1", + "createTime": "2024-08-15 10:00:00" + } + ], + "total": 2 +} +``` + +#### 1.2 获取学生信息收集批次详细信息 +- **接口路径**: GET `/XgStuColBatch/stuColBatch/{id}` +- **权限标识**: `XgStuColBatch:stuColBatch:query` +- **功能描述**: 根据主键获取学生信息收集批次详细信息 + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| id | long | 是 | 批次主键ID | + +**响应数据**: +```json +{ + "code": 200, + "msg": "查询成功", + "data": { + "id": 1, + "batchName": "2025年春季学期信息收集", + "description": "收集学生基本信息", + "startTime": "2025-02-01 00:00:00", + "endTime": "2025-02-28 23:59:59", + "status": "0", + "collectionType": "1", + "remark": "备注信息", + "createBy": 1, + "createTime": "2025-01-27 10:00:00", + "updateBy": 1, + "updateTime": "2025-01-27 10:00:00" + } +} +``` + +#### 1.3 新增学生信息收集批次 +- **接口路径**: POST `/XgStuColBatch/stuColBatch` +- **权限标识**: `XgStuColBatch:stuColBatch:add` +- **功能描述**: 新增学生信息收集批次 + +**请求参数**: +```json +{ + "batchName": "2025年春季学期信息收集", + "description": "收集学生基本信息", + "startTime": "2025-02-01 00:00:00", + "endTime": "2025-02-28 23:59:59", + "status": "0", + "collectionType": "1", + "remark": "备注信息" +} +``` + +**响应数据**: +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +#### 1.4 修改学生信息收集批次 +- **接口路径**: PUT `/XgStuColBatch/stuColBatch` +- **权限标识**: `XgStuColBatch:stuColBatch:edit` +- **功能描述**: 修改学生信息收集批次信息 + +**请求参数**: +```json +{ + "id": 1, + "batchName": "2025年春季学期信息收集", + "description": "收集学生基本信息", + "startTime": "2025-02-01 00:00:00", + "endTime": "2025-02-28 23:59:59", + "status": "0", + "collectionType": "1", + "remark": "备注信息" +} +``` + +**响应数据**: +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +#### 1.5 删除学生信息收集批次 +- **接口路径**: DELETE `/XgStuColBatch/stuColBatch/{ids}` +- **权限标识**: `XgStuColBatch:stuColBatch:remove` +- **功能描述**: 批量删除学生信息收集批次 + +**请求参数**: +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| ids | string | 是 | 批次ID集合,多个ID用逗号分隔 | + +**响应数据**: +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +#### 1.6 导出学生信息收集批次 +- **接口路径**: POST `/XgStuColBatch/stuColBatch/export` +- **权限标识**: `XgStuColBatch:stuColBatch:export` +- **功能描述**: 导出学生信息收集批次列表到Excel + +**请求参数**: +同查询列表接口,用于筛选导出数据。 + +**响应数据**: +返回Excel文件流。 + +## 📝 更新历史 + +| 日期 | 版本 | 更新内容 | 更新人 | +|------|------|---------|-------| +| 2025-01-03 | V1.1.0 | **重要更新**:修改列表接口排序方式,从按ID升序改为按创建时间倒序,确保最新创建的批次显示在前面 | 系统管理员 | +| 2025-01-27 | V1.0.0 | 初始版本,创建学生信息收集批次管理API文档 | 系统管理员 | + +## 🚨 重要说明 + +### 排序变更说明 +- **变更时间**: 2025-01-03 +- **变更内容**: 列表查询接口的排序方式从按ID升序(`orderByAsc(XgStuColBatch::getId)`)改为按创建时间倒序(`orderByDesc(XgStuColBatch::getCreateTime)`) +- **变更原因**: 提升用户体验,确保最新创建的批次显示在列表前面,便于用户快速找到最近的批次 +- **影响范围**: 仅影响列表查询接口的数据排序,不影响其他功能 + +### 技术实现 +- **后端实现**: 修改`XgStuColBatchServiceImpl.buildQueryWrapper()`方法中的排序逻辑 +- **数据库字段**: 使用`create_time`字段进行排序 +- **继承关系**: `XgStuColBatch` → `TenantEntity` → `BaseEntity`(`createTime`字段在BaseEntity中定义) \ No newline at end of file diff --git a/docs/开发文档/API/宿舍床位分配管理API.md b/docs/开发文档/API/宿舍床位分配管理API.md new file mode 100644 index 00000000..90838f35 --- /dev/null +++ b/docs/开发文档/API/宿舍床位分配管理API.md @@ -0,0 +1,300 @@ +# 宿舍床位分配管理API文档 + +## 目录 + +- [1. 接口概述](#1-接口概述) +- [2. 批量分配床位给班级](#2-批量分配床位给班级) + - [2.1 接口说明](#21-接口说明) + - [2.2 重要修复记录](#22-重要修复记录) + - [2.3 请求参数](#23-请求参数) + - [2.4 返回结果](#24-返回结果) + - [2.5 业务逻辑](#25-业务逻辑) +- [3. 批量分配床位给学院](#3-批量分配床位给学院) + +## 1. 接口概述 + +宿舍床位分配管理API提供了床位分配的核心功能,包括: +- 批量分配床位给学院 +- 批量分配床位给班级(学院分配后的二次分配) +- 床位收回和重新分配 + +## 2. 批量分配床位给班级 + +### 2.1 接口说明 + +该接口用于将已分配给学院的床位进一步分配给具体的班级。支持房间优先分配策略,尽量避免混住。 + +#### 请求方式 + +``` +POST /dormBedStudent/dormBedStudent/batchAllocateToClass +``` + +#### 权限要求 + +``` +xgss:dormBedStudent:allocateToClass +``` + +### 2.2 重要修复记录 + +**修复日期:2025-01-27** + +#### 问题描述 +在床位分配过程中,当床位数量小于等于分配的数量时,存在部分床位未能成功分配的问题。具体现象: +- 前端显示34个床位需要分配 +- 接口执行后,部分床位没有被分配给班级 +- 导致实际分配数量少于预期分配数量 + +#### 根本原因 +1. **分配算法与更新逻辑不匹配**:分配算法使用`roomUsage`数组记录房间使用情况,但更新逻辑依赖`roomClassMap`判断床位归属 +2. **混住场景下的床位丢失**:当房间产生混住时,`roomClassMap`只记录最后分配的班级,导致之前分配的床位在更新时被忽略 +3. **缺乏精确的床位级别跟踪**:原算法只在房间级别跟踪分配,没有在床位级别进行精确记录 + +#### 修复方案 +1. **新增床位级别映射**:引入`bedClassMap`(Map)精确记录每个床位分配给哪个班级 +2. **四轮分配策略优化**:在每轮分配中同步更新`bedClassMap`,确保床位分配记录准确 +3. **更新逻辑重构**:使用`bedClassMap`替代原有的房间级别判断逻辑,确保所有分配的床位都能正确更新 +4. **完整性验证**:增加分配结果验证,确保预期分配数量与实际分配数量一致 + +#### 修复效果 +- ✅ 解决了床位分配不完整的问题 +- ✅ 确保34个床位全部正确分配 +- ✅ 支持混住场景下的精确床位跟踪 +- ✅ 增加了详细的日志记录和验证机制 + +### 2.3 请求参数 + +#### 请求体参数 + +```json +{ + "bedIds": ["1947684655866236929", "1947684656965144578", ...], + "classIds": ["1947944181282590721", "1947943837236416514", ...], + "classStudentCounts": [0, 6, 7, 7, 3, 11], + "buildingId": "1947658161827794945", + "buildingType": "2" +} +``` + +#### 参数说明 + +| 参数名 | 类型 | 必须 | 描述 | +|--------|------|------|------| +| bedIds | Long[] | 是 | 需要分配的床位ID列表 | +| classIds | Long[] | 是 | 目标班级ID列表 | +| classStudentCounts | Integer[] | 否 | 各班级分配床位数量,与classIds一一对应 | +| buildingId | Long | 是 | 楼栋ID | +| buildingType | String | 是 | 楼栋类型(1-男生楼,2-女生楼) | + +#### 参数校验规则 + +1. **床位状态校验**:所有床位必须已分配给学院且未分配给班级 +2. **数量校验**:classStudentCounts总和不能超过可用床位总数 +3. **班级信息校验**:所有classIds必须存在且有效 +4. **楼栋信息校验**:buildingId必须存在且与床位楼栋匹配 + +### 2.4 返回结果 + +#### 成功响应 + +```json +{ + "code": 200, + "msg": "操作成功", + "data": true +} +``` + +#### 错误响应 + +```json +{ + "code": 500, + "msg": "床位分配失败:具体错误信息", + "data": null +} +``` + +#### 常见错误信息 + +| 错误信息 | 原因 | 解决方案 | +|----------|------|----------| +| 以下床位已分配给班级,无法重复分配 | 床位已被其他班级占用 | 检查床位状态,选择未分配的床位 | +| 选择的床位尚未分配给学院 | 床位未完成学院分配 | 先执行学院分配再进行班级分配 | +| 班级人数总和超过了可用床位总数 | 分配数量超过可用床位 | 调整各班级分配数量 | +| 床位分配不完整,存在未分配的床位 | 分配算法异常 | 联系技术支持检查日志 | + +### 2.5 业务逻辑 + +#### 分配策略 + +**四轮房间优先分配策略**: + +1. **第一轮:完整房间分配** + - 优先将空闲房间完整分配给单个班级 + - 条件:房间空闲 && 班级人数 >= 房间床位数 + +2. **第二轮:同班级房间补充** + - 在已分配给该班级的房间中补充床位 + - 避免不必要的混住 + +3. **第三轮:空闲房间分配** + - 将剩余学生分配到完全空闲的房间 + - 保持房间内班级单一性 + +4. **第四轮:混住分配** + - 将剩余学生分配到任何有空闲床位的房间 + - 允许混住以确保所有学生都有床位 + +#### 分配原则 + +- **房间优先**:尽量保持同一房间内学生来自同一班级 +- **顺序分配**:按房间编码顺序进行分配 +- **完整性保证**:确保所有指定的床位都被分配 +- **日志记录**:详细记录分配过程和结果 + +#### 数据更新 + +分配完成后,系统会更新以下信息: +- `xg_dorm_bed_student.class_id`:床位分配的班级ID +- `xg_dorm_bed_student.major_id`:床位分配的专业ID +- `xg_dorm_bed_student.update_time`:更新时间 +- `xg_dorm_bed_student.update_by`:更新人员 + +## 3. 批量分配床位给学院 + +### 3.1 接口说明 + +该接口用于将空闲床位批量分配给二级学院,是床位分配的第一步。 + +#### 请求方式 + +``` +POST /dormBedStudent/dormBedStudent/batchAllocateToCollege +``` + +#### 权限要求 + +``` +xgss:dormBedStudent:allocateToCollege +``` + +#### 请求参数 + +```json +{ + "bedIds": ["床位ID列表"], + "collegeIds": ["学院ID列表"], + "collegeStudentCounts": [各学院分配数量], + "buildingId": "楼栋ID", + "buildingType": "楼栋类型" +} +``` + +#### 业务逻辑 + +1. **床位状态校验**:确保床位处于空闲状态(bed_status = "0") +2. **学院分配**:采用与班级分配相同的房间优先策略 +3. **数据更新**:更新床位的学院归属信息 + +--- + +## 技术说明 + +### 关键数据结构 + +```java +// 房间使用状态跟踪 +int[] roomUsage = new int[sortedRoomIds.size()]; + +// 房间主要归属班级映射 +Map roomClassMap = new HashMap<>(); + +// 床位精确分配映射(修复关键) +Map bedClassMap = new HashMap<>(); // bedId -> classId +``` + +### 修复前后对比 + +**修复前**: +- 依赖房间级别的`roomClassMap`判断床位归属 +- 混住场景下床位分配记录丢失 +- 更新逻辑与分配算法不匹配 + +**修复后**: +- 使用床位级别的`bedClassMap`精确跟踪 +- 支持混住场景下的完整床位分配 +- 分配算法与更新逻辑完全一致 + +### 性能优化 + +- 使用批量更新减少数据库操作 +- 流式处理大量床位数据 +- 详细的日志记录便于问题排查 + +--- + +## 修复记录 + +### 2025-01-27 第二轮修复:完全匹配场景下的床位分配问题 + +**严重问题**: +用户反馈75个床位完全匹配75个需求时,仍有13个床位未分配。 + +**新发现的根本原因**: +1. **床位预筛选逻辑错误**:条件 `.isNull(XgDormBedStudent::getClassId)` 导致已分配班级的床位被排除在分配范围之外 +2. **数据库更新条件过严**:只更新未分配班级的床位,忽略了需要重新分配的床位 +3. **验证逻辑不够严格**:对完全匹配场景缺少强制验证 + +**第二轮修复方案**: + +#### 1. 修复床位预筛选逻辑 +```java +// 修复前:只查询未分配班级的床位 +.isNull(XgDormBedStudent::getClassId) // 只分配了学院,还没分配班级 + +// 修复后:查询所有已分配给学院的床位,不论是否已分配班级 +// 移除了.isNull(XgDormBedStudent::getClassId)条件 +``` + +#### 2. 增强数据库更新策略 +```java +// 分两种情况处理: +// 情况1:更新未分配班级的记录(新分配) +// 情况2:强制更新所有指定床位的班级分配(重新分配) +int totalUpdateCount = updateCount1 + updateCount2; +``` + +#### 3. 完善验证和诊断机制 +```java +// 当床位数量完全匹配需求时,必须全部分配完 +if (totalExpectedBeds == totalAvailableBeds && totalAllocatedBeds < totalExpectedBeds) { + // 详细诊断未分配的床位和房间分布 + throw new RuntimeException("床位分配不完整,存在" + (totalExpectedBeds - totalAllocatedBeds) + "个床位未分配"); +} +``` + +### 2025-01-27 第一轮修复:床位分配不完整问题 + +**问题描述**: +在床位分配过程中,存在剩余床位未分配的问题。例如: +- 34个床位分配给6个班级时,存在剩余床位未分配 +- 分配算法与更新逻辑不匹配,导致混住场景下床位分配记录丢失 + +**根本原因**: +1. **分配算法使用房间级别跟踪**:`roomUsage`数组 + `roomClassMap`映射 +2. **更新逻辑依赖房间归属判断**:只处理`roomClassMap`中记录的房间 +3. **混住场景下的数据丢失**:`roomClassMap`被覆盖,之前分配的床位被忽略 + +**第一轮修复方案**: +- 新增床位级别精确跟踪:`Map bedClassMap` +- 四轮分配策略优化:同步更新床位分配记录 +- 更新逻辑重构:使用`bedClassMap`替代房间级别判断 +- 完整性验证:确保所有床位都能正确分配 + +--- + +**文档版本**:v1.2 +**最后更新**:2025-01-27 +**修复版本**:重要Bug修复 - 完全匹配场景下床位分配不完整问题(第二轮修复) \ No newline at end of file diff --git a/mobile/src/pages/task/myDocument.vue b/mobile/src/pages/task/myDocument.vue index 3f9ce5d6..66ed7402 100644 --- a/mobile/src/pages/task/myDocument.vue +++ b/mobile/src/pages/task/myDocument.vue @@ -50,7 +50,7 @@ + {{ index + 1 }}. {{ item.flowName }} - {{ item.flowName || '-' }} + + {{ index + 1 }}. + {{ item.flowName || '-' }} + - {{ item.flowName || '-' }} + + {{ index + 1 }}. + {{ item.flowName || '-' }} + - - - - - - {{ item.flowName || '-' }} - - - - - - - - - 流程名称: - {{ item.flowName || '-' }} - - - 流程分类: - {{ item.categoryName || '-' }} - - - 任务名称: - {{ item.nodeName || '-' }} - - - 申请人: - {{ item.createByName || '-' }} - - - 办理人: - - - + + + + + + + + {{ index + 1 }}. + {{ item.flowName || '-' }} + + + - - 创建时间: - {{ formatTime(item.createTime) }} + + + + + 流程名称: + {{ item.flowName || '-' }} + + + 流程分类: + {{ item.categoryName || '-' }} + + + 任务名称: + {{ item.nodeName || '-' }} + + + 申请人: + {{ item.createByName || '-' }} + + + 办理人: + + + + + + + 创建时间: + {{ formatTime(item.createTime) }} + + + + + + + + 办理 + - - - - - - 办理 - - - - - + + + + + @@ -229,6 +247,10 @@ export default { total: 0, loadmoreStatus: 'loadmore', // loadmore, loading, nomore + // 滚动相关 + scrollTop: 0, + refreshing: false, + // 查询参数 queryParams: { pageNum: 1, @@ -262,6 +284,8 @@ export default { async created() { await this.loadFlowStatusDict(); + // 重置滚动位置 + this.scrollTop = 0; await this.getList(); }, @@ -320,6 +344,8 @@ export default { if (!isLoadMore) { this.loading = true; this.queryParams.pageNum = 1; + // 非加载更多时重置滚动位置 + this.scrollTop = 0; } else { this.loadmoreStatus = 'loading'; } @@ -400,6 +426,8 @@ export default { // 搜索操作 handleQuery() { + // 重置滚动位置 + this.scrollTop = 0; this.getList(); }, @@ -413,6 +441,35 @@ export default { this.getList(true); }, + // 下拉刷新 + async onRefresh() { + this.refreshing = true; + try { + // 重置分页参数 + this.queryParams.pageNum = 1; + this.loadmoreStatus = 'loadmore'; + + // 重新加载数据 + await this.getList(); + + // 显示刷新成功提示 + uni.showToast({ + title: '刷新成功', + icon: 'success', + duration: 1500, + }); + } catch (error) { + console.error('下拉刷新失败:', error); + uni.showToast({ + title: '刷新失败', + icon: 'none', + duration: 1500, + }); + } finally { + this.refreshing = false; + } + }, + // 格式化时间 formatTime(timeStr) { if (!timeStr) return '-'; @@ -536,6 +593,8 @@ export default { } this.userSelectDialog.visible = false; + // 重置滚动位置并查询 + this.scrollTop = 0; this.handleQuery(); }, @@ -717,6 +776,12 @@ export default { .content-wrapper { padding: 20rpx; + height: calc(100vh - 200rpx); /* 减去导航栏和搜索区域的高度 */ +} + +.task-scroll-list { + height: 100%; + padding: 0 20rpx; } .empty-container { @@ -744,6 +809,12 @@ export default { border-bottom: 1px solid #f0f0f0; } +.title-index { + color: #000000; + font-weight: bold; + margin-right: 8rpx; +} + .task-title { font-size: 32rpx; font-weight: bold; @@ -923,4 +994,9 @@ export default { ::v-deep .u-loadmore { padding: 40rpx 0; } + +.loadmore-status { + padding: 20rpx 0; + text-align: center; +} diff --git a/mobile/src/pages/xg/xsjcxx/xsqj/leave/index.vue b/mobile/src/pages/xg/xsjcxx/xsqj/leave/index.vue index 2a3f223d..97e95bee 100644 --- a/mobile/src/pages/xg/xsjcxx/xsqj/leave/index.vue +++ b/mobile/src/pages/xg/xsjcxx/xsqj/leave/index.vue @@ -92,6 +92,12 @@ + + + + {{ getItemIndex(index) }} + + @@ -478,7 +484,11 @@ export default { 本次加载: newData.length, 总数: this.total, 列表长度: this.leaveList.length, + 分页状态: this.loadStatus, }); + + // 验证序号连续性 + this.validateIndexContinuity(); } else { console.warn('获取请假记录失败:', res?.msg); uni.showToast({ @@ -518,7 +528,12 @@ export default { return; } - console.log('📄 开始加载更多数据...'); + console.log('📄 开始加载更多数据...', { + 当前页码: this.queryParams.pageNum, + 当前列表长度: this.leaveList.length, + 总数: this.total, + }); + this.queryParams.pageNum += 1; await this.loadLeaveList(false); }, @@ -604,11 +619,17 @@ export default { // 搜索操作 handleQuery() { console.log('🔍 执行搜索:', this.queryParams); + + // 搜索时重置分页状态 + this.queryParams.pageNum = 1; + this.loadStatus = 'loadmore'; + this.loadLeaveList(); }, // 重置搜索 resetQuery() { + console.log('🔄 重置搜索条件...'); this.queryParams.startTime = ''; this.queryParams.endTime = ''; this.queryParams.approvalStatus = ''; @@ -616,6 +637,12 @@ export default { this.endTimeDisplay = ''; this.startTimeValue = ''; this.endTimeValue = ''; + + // 重置分页状态 + this.queryParams.pageNum = 1; + this.loadStatus = 'loadmore'; + + console.log('🔄 重置完成,开始加载数据...'); this.loadLeaveList(); }, @@ -703,6 +730,60 @@ export default { } }, + // 计算列表项序号 + getItemIndex(index) { + // 序号应该基于数据在列表中的实际位置,而不是基于当前请求的页码 + // 直接使用 index + 1,因为数据已经按顺序合并到 leaveList 中 + const finalIndex = index + 1; + + // 调试信息 + console.log('🔢 序号计算:', { + 当前项索引: index, + 最终序号: finalIndex, + 列表总长度: this.leaveList.length, + }); + + return finalIndex; + }, + + // 验证序号连续性 + validateIndexContinuity() { + if (this.leaveList.length === 0) return; + + console.log('🔍 验证序号连续性...'); + const expectedIndices = []; + + for (let i = 0; i < this.leaveList.length; i++) { + const expectedIndex = i + 1; + const actualIndex = this.getItemIndex(i); + expectedIndices.push({ position: i, expected: expectedIndex, actual: actualIndex }); + } + + console.log('🔍 序号验证结果:', expectedIndices); + + // 检查是否有不连续的序号 + const hasDiscontinuity = expectedIndices.some((item, index) => { + if (index === 0) return false; + return item.expected !== expectedIndices[index - 1].expected + 1; + }); + + if (hasDiscontinuity) { + console.warn('⚠️ 发现序号不连续:', expectedIndices); + } else { + console.log('✅ 序号连续性验证通过'); + } + + // 额外验证:检查序号范围 + const minIndex = Math.min(...expectedIndices.map((item) => item.actual)); + const maxIndex = Math.max(...expectedIndices.map((item) => item.actual)); + console.log('🔍 序号范围验证:', { + 最小序号: minIndex, + 最大序号: maxIndex, + 期望范围: `1-${this.leaveList.length}`, + 是否正常: minIndex === 1 && maxIndex === this.leaveList.length, + }); + }, + // 查看详情 handleViewDetail(item) { const detailInfo = [ @@ -863,6 +944,26 @@ export default { border-radius: 16rpx; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); overflow: hidden; + position: relative; +} + +// 序号标识 +.card-index { + position: absolute; + top: 8rpx; + left: 8rpx; + background-color: #f5f6fa; + padding: 6rpx 12rpx; + border-radius: 8rpx; + z-index: 1; + min-width: 32rpx; + text-align: center; +} + +.index-text { + font-size: 24rpx; + color: #909399; + font-weight: 500; } // 卡片头部 @@ -871,6 +972,7 @@ export default { justify-content: space-between; align-items: flex-start; padding: 24rpx; + padding-left: 80rpx; // 为序号预留左边空间 border-bottom: 1px solid #f0f0f0; background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); } @@ -928,6 +1030,7 @@ export default { // 卡片内容 .card-content { padding: 24rpx; + padding-left: 80rpx; // 与头部对齐 } .info-section { @@ -1021,6 +1124,7 @@ export default { display: flex; justify-content: flex-end; padding: 20rpx; + padding-left: 80rpx; // 与头部对齐 border-top: 1px solid #f0f0f0; gap: 16rpx; } @@ -1080,8 +1184,21 @@ export default { } .card-header, - .card-content { + .card-content, + .card-actions { padding: 20rpx; + padding-left: 70rpx; // 小屏幕下为序号预留左边空间 + } + + .card-index { + top: 6rpx; + left: 6rpx; + padding: 4rpx 8rpx; + min-width: 28rpx; + } + + .index-text { + font-size: 22rpx; } .fab-button { diff --git a/mobile/src/pages/xg/xsjcxx/xsws/index.vue b/mobile/src/pages/xg/xsjcxx/xsws/index.vue index 000411ee..846c2389 100644 --- a/mobile/src/pages/xg/xsjcxx/xsws/index.vue +++ b/mobile/src/pages/xg/xsjcxx/xsws/index.vue @@ -39,7 +39,10 @@ > - {{ item.batchName || '-' }} + + {{ index + 1 }}. + {{ item.batchName || '-' }} + 信息完善 - + - + - + - + @@ -66,6 +69,13 @@ :key="item.id || index" class="violation-card" > + + + + {{ index + 1 }} + + + @@ -178,15 +188,18 @@ - - - + + + + + 加载中... + + + 没有更多数据了 + + + 上拉加载更多 + @@ -205,10 +218,6 @@ export default { dormViolationList: [], total: 0, hasMore: true, - loadMoreStatus: 'loadmore', - loadingText: '努力加载中...', - loadMoreText: '点击或上拉加载更多', - noMoreText: '没有更多了', // 查询参数 queryParams: { @@ -253,6 +262,16 @@ export default { await this.initData(); }, + // 监听页面触底事件 + onReachBottom() { + this.loadMore(); + }, + + // 监听下拉刷新事件 + onPullDownRefresh() { + this.refreshData(); + }, + methods: { // 返回上一页 goBack() { @@ -296,10 +315,19 @@ export default { // 获取违纪记录列表 async getList(isLoadMore = false) { - if (this.loading) return; + // 防止重复加载 + if (this.loading) { + console.log('正在加载中,跳过本次请求'); + return; + } + + // 如果没有更多数据且是加载更多操作,直接返回 + if (isLoadMore && !this.hasMore) { + console.log('没有更多数据,跳过加载更多'); + return; + } this.loading = true; - this.loadMoreStatus = 'loading'; try { // 获取当前登录用户信息 @@ -326,24 +354,52 @@ export default { if (res && res.code === 200) { const { rows, total } = res; + // 确保 rows 是数组 + const newData = Array.isArray(rows) ? rows : []; + + console.log('API返回数据:', { + isLoadMore: isLoadMore, + pageNum: this.queryParams.pageNum, + apiReturnedCount: newData.length, + currentListLength: this.dormViolationList.length, + total: total, + }); + if (isLoadMore) { - this.dormViolationList = [...this.dormViolationList, ...rows]; + // 加载更多时,追加数据(去重处理) + const existingIds = new Set(this.dormViolationList.map((item) => item.id)); + const uniqueNewData = newData.filter((item) => !existingIds.has(item.id)); + + console.log('加载更多去重处理:', { + existingCount: this.dormViolationList.length, + newDataCount: newData.length, + uniqueNewDataCount: uniqueNewData.length, + duplicateCount: newData.length - uniqueNewData.length, + }); + + this.dormViolationList = [...this.dormViolationList, ...uniqueNewData]; } else { - this.dormViolationList = rows; + // 首次加载或搜索时,替换数据 + this.dormViolationList = newData; } this.total = total; + + // 更新是否还有更多数据的状态 this.hasMore = this.dormViolationList.length < total; console.log('获取违纪记录成功:', { - current: this.dormViolationList.length, + finalListLength: this.dormViolationList.length, total: total, hasMore: this.hasMore, + isLoadMore: isLoadMore, + pageNum: this.queryParams.pageNum, }); } else { if (!isLoadMore) { this.dormViolationList = []; this.total = 0; + this.hasMore = false; } uni.showToast({ title: res?.msg || '获取数据失败', @@ -355,6 +411,7 @@ export default { if (!isLoadMore) { this.dormViolationList = []; this.total = 0; + this.hasMore = false; } uni.showToast({ title: '获取数据失败', @@ -362,13 +419,17 @@ export default { }); } finally { this.loading = false; - this.loadMoreStatus = this.hasMore ? 'loadmore' : 'nomore'; + // 结束下拉刷新 + if (!isLoadMore) { + uni.stopPullDownRefresh(); + } } }, // 搜索操作 handleQuery() { this.queryParams.pageNum = 1; + this.hasMore = true; this.getList(); }, @@ -379,10 +440,18 @@ export default { pageSize: 10, violationTime: '', }; + this.hasMore = true; this.getList(); }, - // 加载更多 + // 下拉刷新 + refreshData() { + this.queryParams.pageNum = 1; + this.hasMore = true; + this.getList(); + }, + + // 加载更多(滚动到底部触发) loadMore() { if (this.hasMore && !this.loading) { this.queryParams.pageNum++; @@ -554,13 +623,34 @@ export default { border-radius: 16rpx; box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); overflow: hidden; + position: relative; +} + +.card-index { + position: absolute; + top: 12rpx; + left: 12rpx; + width: 40rpx; + height: 40rpx; + background-color: rgba(162, 161, 161, 0.8); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; +} + +.index-text { + font-size: 22rpx; + font-weight: bold; + color: #fff; } .card-header { display: flex; justify-content: space-between; align-items: center; - padding: 24rpx; + padding: 24rpx 24rpx 24rpx 70rpx; border-bottom: 1px solid #f0f0f0; background-color: #f8f9fa; } @@ -673,10 +763,42 @@ export default { border-top: 1px solid #f0f0f0; } -.load-more { +// 底部加载状态样式 +.load-status { + padding: 30rpx 0; + text-align: center; +} + +.loading-tip { + display: flex; + align-items: center; + justify-content: center; + gap: 16rpx; +} + +.loading-text { + font-size: 26rpx; + color: #909399; +} + +.no-more-tip { padding: 20rpx 0; } +.no-more-text { + font-size: 26rpx; + color: #c0c4cc; +} + +.pull-tip { + padding: 20rpx 0; +} + +.pull-text { + font-size: 26rpx; + color: #909399; +} + // uview组件样式调整 ::v-deep .u-tag { margin: 0; @@ -684,13 +806,11 @@ export default { ::v-deep .u-button { flex: 1; - // max-width: 200rpx; border-radius: 8rpx; } // 搜索按钮样式 ::v-deep .search-btn { - // background: linear-gradient(135deg, #007aff, #0051d9) !important; border: none !important; box-shadow: 0 4px 16px rgba(0, 122, 255, 0.3) !important; transition: all 0.3s ease !important; diff --git a/mobile/src/pages/xgss/wsjc/wsjc.vue b/mobile/src/pages/xgss/wsjc/wsjc.vue index 7f766b13..fe4531ce 100644 --- a/mobile/src/pages/xgss/wsjc/wsjc.vue +++ b/mobile/src/pages/xgss/wsjc/wsjc.vue @@ -48,7 +48,10 @@ - {{ item.checkTitle || '-' }} + + {{ index + 1 }}. + {{ item.checkTitle || '-' }} + - - - 查看 - + + + 查看 + - - + + 修改 - + - - + + 删除 - + @@ -608,6 +626,12 @@ export default { flex: 1; } +.title-index { + color: #000000; + font-weight: bold; + margin-right: 8rpx; +} + .check-level { flex-shrink: 0; } @@ -733,37 +757,26 @@ export default { .action-btn { display: flex; - flex-direction: column; + flex-direction: row; align-items: center; justify-content: center; - padding: 16rpx 20rpx; - border-radius: 12rpx; min-width: 120rpx; - transition: all 0.3s ease; - background-color: transparent; + border-radius: 20rpx; - &:active { - transform: scale(0.95); - opacity: 0.8; + ::v-deep .u-button { + border-radius: 20rpx; + padding: 16rpx 20rpx; + min-width: 120rpx; + height: auto; + line-height: 1.2; } } .btn-text { font-size: 24rpx; - margin-top: 8rpx; + margin-left: 8rpx; font-weight: 500; -} - -.primary-btn .btn-text { - color: #19be6b; -} - -.info-btn .btn-text { - color: #3c9cff; -} - -.danger-btn .btn-text { - color: #f56c6c; + color: #ffffff; } // 分页容器 diff --git a/mobile/src/pages/xgss/xssswsjc/xssswsjc.vue b/mobile/src/pages/xgss/xssswsjc/xssswsjc.vue index 196ac871..14b69561 100644 --- a/mobile/src/pages/xgss/xssswsjc/xssswsjc.vue +++ b/mobile/src/pages/xgss/xssswsjc/xssswsjc.vue @@ -60,7 +60,10 @@ - {{ item.checkTitle || '-' }} + + {{ index + 1 }}. + {{ item.checkTitle || '-' }} + - - + - --> + - - - - - - {{ item.scheduleName || '暂无' }} - - - {{ item.academicYear || '暂无' }} - {{ getSemesterText(item.semester) }} + + + + + + + + + {{ index + 1 }}. + + {{ item.scheduleName || '暂无' }} + + + + + {{ item.academicYear || '暂无' }} + {{ getSemesterText(item.semester) }} + + + + + + + + + + + + + {{ item.description || '暂无说明' }} + + + + + 开始时间: + {{ formatTime(item.startTime) }} + + + 结束时间: + {{ formatTime(item.endTime) }} + + + 数据统计开始时间: + + {{ formatTime(item.dataStartTime) }} + + + + 数据统计结束时间: + + {{ formatTime(item.dataEndTime) }} + + + + + + 考核方案: {{ item.planName || '暂无' }} - - + + + + + 查看计划 + + + 提交申请 + + + 查看申请 + - - - - - {{ item.description || '暂无说明' }} - + + + + + 正在加载更多... - - - 开始时间: - {{ formatTime(item.startTime) }} - - - 结束时间: - {{ formatTime(item.endTime) }} - - - 数据统计开始时间: - {{ formatTime(item.dataStartTime) }} - - - 数据统计结束时间: - {{ formatTime(item.dataEndTime) }} - + + {{ getNoMoreText() }} - - 考核方案: {{ item.planName || '暂无' }} - - - - - - - 查看计划 - - - 提交申请 - - - 查看申请 - - - - - - - + @@ -231,6 +256,7 @@ export default { total: 0, hasMore: true, loadStatus: 'loadmore', + loadingMore: false, // 查询参数 queryParams: { @@ -276,9 +302,14 @@ export default { }, // 获取考核计划列表 - async getList(loadMore = false) { - if (!loadMore) { + async getList(isLoadMore = false) { + if (isLoadMore) { + this.loadingMore = true; + } else { this.loading = true; + // 重置分页参数 + this.queryParams.pageNum = 1; + this.assessmentScheduleList = []; } try { @@ -290,38 +321,49 @@ export default { if (res && res.code === 200) { const rows = res.rows || []; + const total = res.total || 0; - if (loadMore) { - this.assessmentScheduleList = this.assessmentScheduleList.concat(rows); + if (isLoadMore) { + // 加载更多时追加数据 + this.assessmentScheduleList = [...this.assessmentScheduleList, ...rows]; } else { + // 首次加载时直接设置数据 this.assessmentScheduleList = rows; } - this.total = res.total || 0; + this.total = total; - if (rows.length < this.queryParams.pageSize) { - this.hasMore = false; - this.loadStatus = 'nomore'; - } else { - this.hasMore = true; - this.loadStatus = 'loadmore'; - } + // 判断是否还有更多数据 + const currentTotal = this.assessmentScheduleList.length; + this.hasMore = currentTotal < total; + this.loadStatus = this.hasMore ? 'loadmore' : 'nomore'; + + console.log('获取考核计划数据成功:', { + 当前页: this.queryParams.pageNum, + 返回条数: rows.length, + 累计条数: currentTotal, + 总数量: total, + 是否还有更多: this.hasMore, + }); } else { - this.handleRequestError(res?.msg || '获取数据失败', loadMore); + this.handleRequestError(res?.msg || '获取数据失败'); } } catch (error) { console.error('获取数据失败:', error); - this.handleRequestError('获取数据失败', loadMore); + this.handleRequestError('获取数据失败'); } finally { - if (!loadMore) { + if (isLoadMore) { + this.loadingMore = false; + } else { this.loading = false; } } }, // 错误处理 - handleRequestError(message, isLoadMore = false) { - if (!isLoadMore) { + handleRequestError(message) { + if (this.queryParams.pageNum === 1) { + // 首次加载失败,清空数据 this.assessmentScheduleList = []; this.total = 0; } @@ -334,22 +376,22 @@ export default { }); }, - // 加载更多 - async loadMore() { - if (!this.hasMore || this.loadStatus !== 'loadmore') { + // 滚动到底部触发加载更多 + async handleScrollToLower() { + if (!this.hasMore || this.loadingMore || this.loading) { + console.log('无需加载更多数据:', { + hasMore: this.hasMore, + loadingMore: this.loadingMore, + loading: this.loading, + }); return; } - this.loadStatus = 'loading'; - this.queryParams.pageNum += 1; + console.log('滚动到底部,开始加载更多数据'); - try { - await this.getList(true); - } catch (error) { - console.error('加载更多数据失败:', error); - this.queryParams.pageNum -= 1; - this.loadStatus = 'loadmore'; - } + // 页码加1,加载下一页 + this.queryParams.pageNum += 1; + await this.getList(true); }, // 查看考核计划信息 @@ -466,9 +508,6 @@ export default { // 提交成功回调 handleSuccess() { - this.queryParams.pageNum = 1; - this.hasMore = true; - this.loadStatus = 'loadmore'; this.getList(); }, @@ -514,6 +553,16 @@ export default { }; return getDictTagType(this.assessmentScheduleStatus, status, fallbackMap); }, + + // 获取没有更多数据文本 + getNoMoreText() { + if (this.hasMore) { + return `已加载${this.assessmentScheduleList.length}条,共${this.total}条`; + } else if (this.total > 0) { + return `已全部加载完毕,共${this.total}条考核计划`; + } + return '暂无数据'; + }, }, }; @@ -526,6 +575,11 @@ export default { .content-wrapper { padding: 20rpx; + height: calc(100vh - 88rpx); /* 减去导航栏高度 */ +} + +.schedule-scroll-view { + height: 100%; } .empty-container { @@ -558,12 +612,25 @@ export default { flex: 1; } +.schedule-name-wrapper { + display: flex; + align-items: center; + margin-bottom: 12rpx; +} + +.schedule-index { + font-size: 32rpx; + font-weight: bold; + color: #000000; + margin-right: 8rpx; + min-width: 40rpx; +} + .schedule-name { font-size: 32rpx; font-weight: bold; color: #303133; - display: block; - margin-bottom: 12rpx; + flex: 1; } .schedule-meta { @@ -640,6 +707,26 @@ export default { padding: 20rpx 0; } +.loading-more { + display: flex; + align-items: center; + justify-content: center; + padding: 20rpx 0; + gap: 12rpx; +} + +.loading-text { + font-size: 24rpx; + color: #909399; +} + +.load-complete-text { + text-align: center; + font-size: 24rpx; + color: #909399; + padding: 20rpx 0; +} + /* 弹窗样式 */ .schedule-detail-popup { background-color: #fff; diff --git a/mobile/src/pages/xskh/wjjl/xswjjl.vue b/mobile/src/pages/xskh/wjjl/xswjjl.vue index ee7c515e..b23d07f1 100644 --- a/mobile/src/pages/xskh/wjjl/xswjjl.vue +++ b/mobile/src/pages/xskh/wjjl/xswjjl.vue @@ -26,7 +26,10 @@ > - {{ item.disciplineTypeName || '-' }} + + {{ index + 1 }}. + {{ item.disciplineTypeName || '-' }} + - - - + + + + + 加载中... + + + 已加载全部数据 + @@ -143,10 +152,10 @@ export default { data() { return { loading: false, + loadingMore: false, // 加载更多状态 disciplineRecordList: [], total: 0, hasMore: true, - loadStatus: 'loadmore', // 查询参数 queryParams: { @@ -172,6 +181,16 @@ export default { } }, + // 滚动到底部时自动加载更多 + onReachBottom() { + this.loadMore(); + }, + + // 下拉刷新 + onPullDownRefresh() { + this.handleRefresh(); + }, + methods: { // 返回上一页 goBack() { @@ -205,6 +224,8 @@ export default { async getList(loadMore = false) { if (!loadMore) { this.loading = true; + } else { + this.loadingMore = true; } try { @@ -230,10 +251,8 @@ export default { // 判断是否还有更多数据 if (rows.length < this.queryParams.pageSize) { this.hasMore = false; - this.loadStatus = 'nomore'; } else { this.hasMore = true; - this.loadStatus = 'loadmore'; } } else { if (!loadMore) { @@ -241,7 +260,6 @@ export default { this.total = 0; } this.hasMore = false; - this.loadStatus = 'nomore'; uni.showToast({ title: res?.msg || '获取数据失败', @@ -256,7 +274,6 @@ export default { this.total = 0; } this.hasMore = false; - this.loadStatus = 'nomore'; uni.showToast({ title: '获取数据失败', @@ -265,17 +282,19 @@ export default { } finally { if (!loadMore) { this.loading = false; + } else { + this.loadingMore = false; } } }, // 加载更多数据 async loadMore() { - if (!this.hasMore || this.loadStatus !== 'loadmore') { + // 防止重复加载 + if (!this.hasMore || this.loadingMore || this.loading) { return; } - this.loadStatus = 'loading'; this.queryParams.pageNum += 1; try { @@ -283,7 +302,25 @@ export default { } catch (error) { console.error('加载更多数据失败:', error); this.queryParams.pageNum -= 1; // 回退页码 - this.loadStatus = 'loadmore'; + + uni.showToast({ + title: '加载失败,请重试', + icon: 'none', + }); + } + }, + + // 下拉刷新处理 + async handleRefresh() { + this.queryParams.pageNum = 1; + this.hasMore = true; + + try { + await this.getList(); + } catch (error) { + console.error('刷新数据失败:', error); + } finally { + uni.stopPullDownRefresh(); } }, @@ -318,7 +355,6 @@ export default { // 重新加载列表 this.queryParams.pageNum = 1; this.hasMore = true; - this.loadStatus = 'loadmore'; this.getList(); }, @@ -354,6 +390,7 @@ export default { .content-wrapper { padding: 20rpx; + padding-bottom: 40rpx; // 增加底部间距 } .empty-container { @@ -386,6 +423,15 @@ export default { font-weight: bold; color: #303133; flex: 1; + display: flex; + align-items: center; +} + +.record-number { + font-size: 28rpx; + font-weight: 600; + margin-right: 8rpx; + min-width: 40rpx; } .record-status { @@ -434,12 +480,42 @@ export default { gap: 16rpx; } -.load-more { +// 底部加载状态样式 +.load-status { + display: flex; + justify-content: center; + align-items: center; padding: 40rpx 20rpx; + margin-top: 20rpx; +} + +.loading-tip { + display: flex; + align-items: center; + justify-content: center; + gap: 16rpx; + + .loading-text { + font-size: 28rpx; + color: #999; + } +} + +.no-more-tip { + text { + font-size: 28rpx; + color: #999; + } } // uview组件样式调整 ::v-deep .u-tag { margin: 0; } + +// 加载图标样式调整 +::v-deep .u-loading-icon { + width: 32rpx; + height: 32rpx; +}