Ver código fonte

Merge branch 'pre'

dzx 9 meses atrás
pai
commit
64a70425a1
59 arquivos alterados com 3538 adições e 63 exclusões
  1. 38 0
      src/main/java/com/xjrsoft/common/enums/TerminalTypeEnum.java
  2. 1 0
      src/main/java/com/xjrsoft/common/utils/JdbcToJavaUtil.java
  3. 2 9
      src/main/java/com/xjrsoft/module/base/service/impl/BaseClassCourseServiceImpl.java
  4. 4 0
      src/main/java/com/xjrsoft/module/courseTable/service/ICourseTableService.java
  5. 274 4
      src/main/java/com/xjrsoft/module/courseTable/service/impl/CourseTableServiceImpl.java
  6. 112 0
      src/main/java/com/xjrsoft/module/feedback/controller/FeedbackController.java
  7. 47 0
      src/main/java/com/xjrsoft/module/feedback/dto/AddFeedbackItemDto.java
  8. 38 0
      src/main/java/com/xjrsoft/module/feedback/dto/FeedbackPageDto.java
  9. 19 0
      src/main/java/com/xjrsoft/module/feedback/dto/HistoryPageMobileDto.java
  10. 21 0
      src/main/java/com/xjrsoft/module/feedback/dto/ItemDetailListDto.java
  11. 22 0
      src/main/java/com/xjrsoft/module/feedback/dto/ReadFeedbackItemDto.java
  12. 33 0
      src/main/java/com/xjrsoft/module/feedback/dto/ReplyFeedbackItemDto.java
  13. 111 0
      src/main/java/com/xjrsoft/module/feedback/entity/Feedback.java
  14. 123 0
      src/main/java/com/xjrsoft/module/feedback/entity/FeedbackItem.java
  15. 17 0
      src/main/java/com/xjrsoft/module/feedback/mapper/FeedbackItemMapper.java
  16. 17 0
      src/main/java/com/xjrsoft/module/feedback/mapper/FeedbackMapper.java
  17. 33 0
      src/main/java/com/xjrsoft/module/feedback/service/IFeedbackService.java
  18. 326 0
      src/main/java/com/xjrsoft/module/feedback/service/impl/FeedbackServiceImpl.java
  19. 73 0
      src/main/java/com/xjrsoft/module/feedback/vo/FeedbackPageVo.java
  20. 54 0
      src/main/java/com/xjrsoft/module/feedback/vo/HistoryPageMobileVo.java
  21. 111 0
      src/main/java/com/xjrsoft/module/feedback/vo/ItemDetailListVo.java
  22. 5 5
      src/main/java/com/xjrsoft/module/generator/service/impl/ApiGeneratorServiceImpl.java
  23. 1 4
      src/main/java/com/xjrsoft/module/oa/controller/WfMeetingApplyController.java
  24. 19 3
      src/main/java/com/xjrsoft/module/oa/service/impl/WfMeetingApplyServiceImpl.java
  25. 1 1
      src/main/java/com/xjrsoft/module/oa/vo/TodayMeetingMobilePageVo.java
  26. 7 3
      src/main/java/com/xjrsoft/module/organization/controller/DepartmentController.java
  27. 10 0
      src/main/java/com/xjrsoft/module/schedule/controller/ScheduleController.java
  28. 5 0
      src/main/java/com/xjrsoft/module/schedule/dto/CourseTableDto.java
  29. 37 0
      src/main/java/com/xjrsoft/module/schedule/dto/CourseTableExportQueryDto.java
  30. 278 0
      src/main/java/com/xjrsoft/module/schedule/util/CourseTableExportQueryUtil.java
  31. 1 1
      src/main/java/com/xjrsoft/module/schedule/util/DataUtil.java
  32. 302 0
      src/main/java/com/xjrsoft/module/student/controller/LeagueMembersManageController.java
  33. 62 0
      src/main/java/com/xjrsoft/module/student/dto/AddLeagueMembersManageDto.java
  34. 58 0
      src/main/java/com/xjrsoft/module/student/dto/LeagueMembersExportQueryDto.java
  35. 63 0
      src/main/java/com/xjrsoft/module/student/dto/LeagueMembersImportDto.java
  36. 58 0
      src/main/java/com/xjrsoft/module/student/dto/LeagueMembersManagePageDto.java
  37. 32 0
      src/main/java/com/xjrsoft/module/student/dto/UpdateLeagueMembersManageDto.java
  38. 109 0
      src/main/java/com/xjrsoft/module/student/entity/LeagueMembersManage.java
  39. 17 0
      src/main/java/com/xjrsoft/module/student/mapper/LeagueMembersManageMapper.java
  40. 33 0
      src/main/java/com/xjrsoft/module/student/service/ILeagueMembersManageService.java
  41. 4 0
      src/main/java/com/xjrsoft/module/student/service/impl/BaseStudentSchoolRollServiceImpl.java
  42. 394 0
      src/main/java/com/xjrsoft/module/student/service/impl/LeagueMembersManageServiceImpl.java
  43. 1 2
      src/main/java/com/xjrsoft/module/student/service/impl/PbCseFeeobjupdateServiceImpl.java
  44. 30 0
      src/main/java/com/xjrsoft/module/student/vo/ClassWithLMNumTreeVo.java
  45. 97 0
      src/main/java/com/xjrsoft/module/student/vo/LeagueMembersExportQueryListVo.java
  46. 96 0
      src/main/java/com/xjrsoft/module/student/vo/LeagueMembersManagePageVo.java
  47. 59 0
      src/main/java/com/xjrsoft/module/student/vo/LeagueMembersManageVo.java
  48. 1 1
      src/main/java/com/xjrsoft/module/textbook/controller/TextbookController.java
  49. 1 1
      src/main/java/com/xjrsoft/module/textbook/service/impl/TextbookIssueRecordServiceImpl.java
  50. 11 4
      src/main/java/com/xjrsoft/module/textbook/service/impl/TextbookStudentClaimServiceImpl.java
  51. 1 1
      src/main/java/com/xjrsoft/module/textbook/service/impl/TextbookSubscriptionServiceImpl.java
  52. 68 9
      src/main/java/com/xjrsoft/module/veb/util/ImportExcelUtil.java
  53. 9 9
      src/main/resources/application-dev.yml
  54. 2 2
      src/main/resources/mapper/courseTable/CourseTable.xml
  55. 3 0
      src/main/resources/mapper/room/RoomBedMapper.xml
  56. 25 1
      src/main/resources/sqlScript/20250408_sql.sql
  57. 51 0
      src/main/resources/sqlScript/20250409_sql.sql
  58. 33 0
      src/test/java/com/xjrsoft/module/feedback/service/impl/FeedbackServiceImplTest.java
  59. 78 3
      src/test/java/com/xjrsoft/xjrsoftboot/FreeMarkerGeneratorTest.java

+ 38 - 0
src/main/java/com/xjrsoft/common/enums/TerminalTypeEnum.java

@@ -0,0 +1,38 @@
+package com.xjrsoft.common.enums;
+
+/**
+ * @author phoenix
+ * @date 2025/04/09
+ * 	终端类型
+ */
+public enum TerminalTypeEnum {
+
+    /**
+     * 电脑端
+     * */
+    TT0001("TT0001", "电脑端"),
+    /**
+     * 移动端
+     * */
+    TT0002("TT0002", "移动端"),
+    /**
+     * 其他
+     * */
+    TT0003("TT0003", "其他");
+
+    final String code;
+    final String value;
+
+    public String getCode() {
+        return this.code;
+    }
+
+    public String getValue() {
+        return this.value;
+    }
+
+    TerminalTypeEnum(final String code, final String message) {
+        this.code = code;
+        this.value = message;
+    }
+}

+ 1 - 0
src/main/java/com/xjrsoft/common/utils/JdbcToJavaUtil.java

@@ -100,6 +100,7 @@ public class JdbcToJavaUtil {
             case LONGVARCHAR:
                 return "String";
             case DATE:
+                return "LocalDate";
             case TIMESTAMP:
                 if (dateType == 0) {
                     return "LocalDateTime";

+ 2 - 9
src/main/java/com/xjrsoft/module/base/service/impl/BaseClassCourseServiceImpl.java

@@ -41,14 +41,7 @@ import com.xjrsoft.module.textbook.mapper.TextbookStudentClaimMapper;
 import lombok.AllArgsConstructor;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.poi.ss.usermodel.Cell;
-import org.apache.poi.ss.usermodel.CellStyle;
-import org.apache.poi.ss.usermodel.Font;
-import org.apache.poi.ss.usermodel.HorizontalAlignment;
-import org.apache.poi.ss.usermodel.Row;
-import org.apache.poi.ss.usermodel.Sheet;
-import org.apache.poi.ss.usermodel.VerticalAlignment;
-import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.springframework.beans.BeanUtils;
@@ -590,7 +583,7 @@ public class BaseClassCourseServiceImpl extends MPJBaseServiceImpl<BaseClassCour
         List<ImportConfig> importConfigs = allFields(new ClassCourseTextbookExportQueryVo());
 
         // 表头
-        createHead(workbook, sheet, importConfigs, 0);
+        createHead(workbook, sheet, importConfigs, IndexedColors.YELLOW.getIndex(), IndexedColors.RED.getIndex(), 0);
 
         // 内容
         int dataRowNumber = 1;

+ 4 - 0
src/main/java/com/xjrsoft/module/courseTable/service/ICourseTableService.java

@@ -7,6 +7,7 @@ import com.xjrsoft.module.courseTable.entity.CourseTable;
 import com.xjrsoft.module.courseTable.vo.ClassListVo;
 import com.xjrsoft.module.schedule.dto.ClassOptionDto;
 import com.xjrsoft.module.schedule.dto.CourseTableDto;
+import com.xjrsoft.module.schedule.dto.CourseTableExportQueryDto;
 import com.xjrsoft.module.schedule.dto.ScheduleWeekExportQueryDto;
 import com.xjrsoft.module.schedule.entity.WfCourseAdjust;
 import com.xjrsoft.module.schedule.vo.ClassOptionVo;
@@ -60,4 +61,7 @@ public interface ICourseTableService extends IService<CourseTable> {
     Integer getSubstituteTeacherCourseCountByParams(ClassTeacherDto dto);
 
 
+    ByteArrayOutputStream exportQuery(CourseTableExportQueryDto dto) throws IOException;
+
+
 }

+ 274 - 4
src/main/java/com/xjrsoft/module/courseTable/service/impl/CourseTableServiceImpl.java

@@ -22,6 +22,7 @@ import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.xjrsoft.common.enums.CourseAdjustTypeEnum;
+import com.xjrsoft.common.enums.DeleteMark;
 import com.xjrsoft.common.enums.TimeNumberEnum;
 import com.xjrsoft.common.enums.TimePeriodEnum;
 import com.xjrsoft.common.enums.WeekEnum;
@@ -31,7 +32,11 @@ import com.xjrsoft.common.utils.excel.ExcelFillCellMergePrevColUtil;
 import com.xjrsoft.common.utils.excel.ExcelMergeUtil;
 import com.xjrsoft.config.TimetableConfig;
 import com.xjrsoft.module.base.entity.BaseClass;
+import com.xjrsoft.module.base.entity.BaseClassroom;
+import com.xjrsoft.module.base.entity.BaseOfficeBuild;
 import com.xjrsoft.module.base.entity.BaseSemester;
+import com.xjrsoft.module.base.mapper.BaseClassroomMapper;
+import com.xjrsoft.module.base.mapper.BaseOfficeBuildMapper;
 import com.xjrsoft.module.base.mapper.BaseSemesterMapper;
 import com.xjrsoft.module.base.service.IBaseClassService;
 import com.xjrsoft.module.base.service.IBaseSemesterService;
@@ -49,10 +54,12 @@ import com.xjrsoft.module.organization.service.IUserService;
 import com.xjrsoft.module.schedule.dto.ClassOptionDto;
 import com.xjrsoft.module.schedule.dto.CourseTableAdjustDto;
 import com.xjrsoft.module.schedule.dto.CourseTableDto;
+import com.xjrsoft.module.schedule.dto.CourseTableExportQueryDto;
 import com.xjrsoft.module.schedule.dto.ScheduleWeekExportQueryDto;
 import com.xjrsoft.module.schedule.entity.CourseTableBak;
 import com.xjrsoft.module.schedule.entity.WfCourseAdjust;
 import com.xjrsoft.module.schedule.mapper.CourseTableBakMapper;
+import com.xjrsoft.module.schedule.util.CourseTableExportQueryUtil;
 import com.xjrsoft.module.schedule.util.ScheduleUtil;
 import com.xjrsoft.module.schedule.vo.*;
 import com.xjrsoft.module.student.entity.BaseStudentSchoolRoll;
@@ -60,6 +67,7 @@ import com.xjrsoft.module.student.service.IBaseStudentSchoolRollService;
 import com.xjrsoft.module.teacher.entity.BaseTeacher;
 import com.xjrsoft.module.teacher.entity.XjrUser;
 import com.xjrsoft.module.teacher.service.ITeacherbaseManagerService;
+import com.xjrsoft.module.veb.util.ImportExcelUtil;
 import lombok.AllArgsConstructor;
 import me.zhyd.oauth.log.Log;
 import org.apache.poi.ss.usermodel.*;
@@ -105,6 +113,8 @@ public class CourseTableServiceImpl extends ServiceImpl<CourseTableMapper, Cours
     private final IBaseStudentSchoolRollService baseStudentSchoolRollService;
     private final CourseTableBakMapper courseTableBakMapper;
     private final IUserService userService;
+    private final BaseClassroomMapper classroomMapper;
+    private final BaseOfficeBuildMapper officeBuildMapper;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -249,7 +259,7 @@ public class CourseTableServiceImpl extends ServiceImpl<CourseTableMapper, Cours
                 tableVo.setTeacherName(xjrUser.getName());
             }
         }
-        if (dto.getStartDate() != null && dto.getEndDate() != null) {
+        if (dto.getStartDate() != null && dto.getEndDate() != null && dto.getIsCustom() == null) {
             if (dto.getStudentId() == null && dto.getTeacherId() == null && dto.getClassId() == null) {
                 dto.setTeacherId(StpUtil.getLoginIdAsLong());
             } else {
@@ -383,6 +393,9 @@ public class CourseTableServiceImpl extends ServiceImpl<CourseTableMapper, Cours
                 for (int i = 0; i < courseIds.length; i++) {
                     String courseId = courseIds[i];
                     String exchangeCourseId = exchangeCourseIds[i];
+                    if (StrUtil.isEmpty(exchangeCourseId)) {
+                        continue;
+                    }
 
                     //调课,将双方课程的日期(schedule_date)、时段(time_period)、节次(time_number)、周(week)、星期几(1-7)(weeks)、星期中文(weeks_cn)对调
                     CourseTable courseTable = courseMap.get(bakKeyInfoMap.get(Long.parseLong(courseId)));
@@ -995,7 +1008,7 @@ public class CourseTableServiceImpl extends ServiceImpl<CourseTableMapper, Cours
 
             createFirstTitle(workbook, sheet, data, dto);
 
-            createSecondTitle(workbook, sheet);
+            createSecondTitle(workbook, sheet, 1);
 
             Map<Integer, ClassTime> timeNumberMap = data.getClassTimeList().stream().collect(Collectors.toMap(ClassTime::getNumber, x -> x));
             Map<String, List<CourseDetailVo>> timeNumberDataMap = data.getCourseList().stream().collect(Collectors.groupingBy(CourseDetailVo::getTimeNumber));
@@ -1122,7 +1135,7 @@ public class CourseTableServiceImpl extends ServiceImpl<CourseTableMapper, Cours
         }
     }
 
-    void createSecondTitle(Workbook workbook, Sheet sheet) {
+    void createSecondTitle(Workbook workbook, Sheet sheet, int rowNumber) {
         // 创建一个字体对象
         Font font = workbook.createFont();
         font.setBold(true);// 设置为粗体
@@ -1140,7 +1153,6 @@ public class CourseTableServiceImpl extends ServiceImpl<CourseTableMapper, Cours
         cellStyle.setBorderLeft(BorderStyle.THIN);
         cellStyle.setBorderRight(BorderStyle.THIN);
 
-        int rowNumber = 1;
         Row row = sheet.createRow(rowNumber);
 
         int cellNumber = 0;
@@ -1249,4 +1261,262 @@ public class CourseTableServiceImpl extends ServiceImpl<CourseTableMapper, Cours
         return this.baseMapper.getSubstituteTeacherCourseCountByParams(dto);
     }
 
+    /**
+     * 课表导出
+     *
+     * @param dto
+     * @return
+     */
+    @Override
+    public ByteArrayOutputStream exportQuery(CourseTableExportQueryDto dto) throws IOException {
+        BaseSemester baseSemester = baseSemesterMapper.selectById(dto.getSemesterId());
+        CourseTableExportQueryUtil exportQueryUtil = new CourseTableExportQueryUtil();
+        String schoolName = "重庆市铜梁职业教育中心";
+        ByteArrayOutputStream bot = new ByteArrayOutputStream();
+
+        //查询数据
+        CourseTableDto dataDto = new CourseTableDto();
+        dataDto.setSemesterId(dto.getSemesterId());
+        dataDto.setWeek(dto.getWeek());
+        dataDto.setIsCustom(1);
+        CourseTableVo tableVo = this.getList(dataDto);
+
+        if("class".equals(dto.getCourseType())){//班级课表
+            //根据年级、专业部,查询需要导出的班级
+            List<BaseClass> classList = baseClassService.list(
+                    new QueryWrapper<BaseClass>().lambda()
+                            .eq(BaseClass::getDeleteMark, DeleteMark.NODELETE.getCode())
+                            .eq(BaseClass::getIsGraduate, 1)
+                            .in(BaseClass::getGradeId, dto.getGradeIds())
+                            .in(BaseClass::getOrgId, dto.getDeptIds())
+            );
+            if(classList.isEmpty()){
+                throw new MyException("未查询到班级,无法导出");
+            }
+
+            Map<String, List<CourseDetailVo>> classDataMap = tableVo.getCourseList().stream().filter(x -> x.getClassName() != null).collect(Collectors.groupingBy(CourseDetailVo::getClassName));
+
+            List<Long> teacherIds = classList.stream().map(BaseClass::getTeacherId).collect(Collectors.toList());
+            List<User> userList = userService.listByIds(teacherIds);
+            Map<Long, String> teacherMap = userList.stream().collect(Collectors.toMap(User::getId, User::getName));
+
+            Workbook workbook = new XSSFWorkbook();
+            String sheetName = "班级课表";
+            Sheet sheet = workbook.createSheet(sheetName);
+            String bigHead = "课程表";
+
+            int classTableRowCount = 16;//每个班的课表占用的总行数
+            int i = 0;
+            for (BaseClass baseClass : classList) {
+                CourseTableVo classData = new CourseTableVo();
+                classData.setClassTimeList(tableVo.getClassTimeList());
+                if(!classDataMap.containsKey(baseClass.getName())){
+                    continue;
+                }
+                classData.setCourseList(classDataMap.get(baseClass.getName()));
+
+                String rightName = "班主任:" + teacherMap.get(baseClass.getTeacherId()) + " 行政班:" + baseClass.getName();
+                int bigHeadRow = (classTableRowCount + 3) * i;
+                //生成第一行title
+                ImportExcelUtil.createBigHead(workbook, sheet, bigHead, bigHeadRow, 8);
+                //生成副标题,展示所属学期
+                ImportExcelUtil.createSubtitle(workbook, sheet, baseSemester.getName(), bigHeadRow + 1, 8);
+                //生成第三行,显示学校名字、班主任和班级名字
+                exportQueryUtil.createThirdTitle(workbook, sheet, bigHeadRow + 2, schoolName, rightName);
+                //生成表头
+                createSecondTitle(workbook, sheet, bigHeadRow + 3);
+                //生成内容
+                exportQueryUtil.createContent(workbook, sheet, bigHeadRow + 4, classData, dto.getCourseType());
+                i ++;
+            }
+            workbook.write(bot);
+        }else if("teacher".equals(dto.getCourseType())){//教师课表
+            //根据学期和周次,查询当前所有有课表的教师id
+            LocalDateTime startDateTime = LocalDateTime.ofInstant(baseSemester.getTeachingStart().toInstant(), ZoneId.systemDefault());
+
+            LocalDate startDate = startDateTime.plusDays((dto.getWeek() - 1) * 7L).toLocalDate();
+            LocalDate endDate = startDate.plusDays(6);
+
+            List<CourseTable> list = this.list(
+                    new QueryWrapper<CourseTable>().lambda()
+                            .eq(CourseTable::getBaseSemesterId, dto.getSemesterId())
+                            .ge(CourseTable::getScheduleDate, startDate)
+                            .le(CourseTable::getScheduleDate, endDate)
+            );
+            Set<String> teacherIds = list.stream().map(CourseTable::getTeacherId).collect(Collectors.toSet());
+            Set<Long> teacherIdList = new HashSet<>();
+            for (String teacherId : teacherIds) {
+                String[] split = teacherId.split(", ");
+                for (String s : split) {
+                    teacherIdList.add(Long.parseLong(s));
+                }
+            }
+            List<User> userList = userService.listByIds(teacherIdList);
+
+            Workbook workbook = new XSSFWorkbook();
+            String sheetName = "教师课表";
+            Sheet sheet = workbook.createSheet(sheetName);
+            String bigHead = "课程表";
+            int classTableRowCount = 16;//每个班的课表占用的总行数
+
+            for(int i = 0; i < userList.size(); i ++){
+                User user = userList.get(i);
+                List<CourseDetailVo> courseList = tableVo.getCourseList().stream().filter(x -> x.getTeacherName().contains(user.getName())).collect(Collectors.toList());
+
+                CourseTableVo classData = new CourseTableVo();
+                classData.setClassTimeList(tableVo.getClassTimeList());
+                classData.setCourseList(courseList);
+
+                String rightName = "教师:" + user.getName();
+                int bigHeadRow = (classTableRowCount + 3) * i;
+                //生成第一行title
+                ImportExcelUtil.createBigHead(workbook, sheet, bigHead, bigHeadRow, 8);
+                //生成副标题,展示所属学期
+                ImportExcelUtil.createSubtitle(workbook, sheet, baseSemester.getName(), bigHeadRow + 1, 8);
+                //生成第三行,显示学校名字、班主任和班级名字
+                exportQueryUtil.createThirdTitle(workbook, sheet, bigHeadRow + 2, schoolName, rightName);
+                //生成表头
+                createSecondTitle(workbook, sheet, bigHeadRow + 3);
+                //生成内容
+                exportQueryUtil.createContent(workbook, sheet, bigHeadRow + 4, classData, dto.getCourseType());
+            }
+            workbook.write(bot);
+        }else if("classroom".equals(dto.getCourseType())){//教室课表
+            Map<String, List<CourseDetailVo>> classroomDataMap = tableVo.getCourseList().stream().filter(x -> x.getClassroomName() != null).collect(Collectors.groupingBy(CourseDetailVo::getClassroomName));
+            List<BaseClassroom> classroomList = classroomMapper.selectList(
+                    new QueryWrapper<BaseClassroom>().lambda()
+                            .eq(BaseClassroom::getDeleteMark, DeleteMark.NODELETE.getCode())
+            );
+            List<BaseOfficeBuild> buildList = officeBuildMapper.selectList(
+                    new QueryWrapper<BaseOfficeBuild>().lambda()
+                            .eq(BaseOfficeBuild::getDeleteMark, DeleteMark.NODELETE.getCode())
+            );
+            Map<Long, String> buildMap = buildList.stream().collect(Collectors.toMap(BaseOfficeBuild::getId, BaseOfficeBuild::getName));
+
+            Workbook workbook = new XSSFWorkbook();
+            String sheetName = "教室课表";
+            Sheet sheet = workbook.createSheet(sheetName);
+            String bigHead = "课程表";
+            int classTableRowCount = 16;//每个班的课表占用的总行数
+            int i = 0;
+            for (BaseClassroom baseClassroom : classroomList) {
+                String classroomName = buildMap.get(baseClassroom.getOfficeBuildId()) + baseClassroom.getName();
+                if(!classroomDataMap.containsKey(classroomName)){
+                    continue;
+                }
+
+                List<CourseDetailVo> courseList = classroomDataMap.get(classroomName);
+
+                CourseTableVo classData = new CourseTableVo();
+                classData.setClassTimeList(tableVo.getClassTimeList());
+                classData.setCourseList(courseList);
+
+                int bigHeadRow = (classTableRowCount + 3) * i;
+                //生成第一行title
+                ImportExcelUtil.createBigHead(workbook, sheet, bigHead, bigHeadRow, 8);
+                //生成副标题,展示所属学期
+                ImportExcelUtil.createSubtitle(workbook, sheet, baseSemester.getName(), bigHeadRow + 1, 8);
+                //生成第三行,显示学校名字、班主任和班级名字
+                exportQueryUtil.createThirdTitle(workbook, sheet, bigHeadRow + 2, schoolName, classroomName);
+                //生成表头
+                createSecondTitle(workbook, sheet, bigHeadRow + 3);
+                //生成内容
+                exportQueryUtil.createContent(workbook, sheet, bigHeadRow + 4, classData, dto.getCourseType());
+
+                i ++;
+            }
+            workbook.write(bot);
+        }else if("all".equals(dto.getCourseType())){//总课表
+            //根据年级、专业部,查询需要导出的班级
+            List<BaseClass> classList = baseClassService.list(
+                    new QueryWrapper<BaseClass>().lambda()
+                            .eq(BaseClass::getDeleteMark, DeleteMark.NODELETE.getCode())
+                            .eq(BaseClass::getIsGraduate, 1)
+                            .in(BaseClass::getGradeId, dto.getGradeIds())
+                            .in(BaseClass::getOrgId, dto.getDeptIds())
+                            .orderByAsc(BaseClass::getName)
+            );
+            if(classList.isEmpty()){
+                throw new MyException("未查询到班级,无法导出");
+            }
+            Map<String, List<CourseDetailVo>> classDataMap = tableVo.getCourseList().stream().filter(x -> x.getClassName() != null).collect(Collectors.groupingBy(CourseDetailVo::getClassName));
+            ArrayList<ArrayList<String>> dataList = new ArrayList<>();
+            for (BaseClass baseClass : classList) {
+                List<CourseDetailVo> detailVoList = classDataMap.get(baseClass.getName());
+                if(detailVoList == null){
+                    continue;
+                }
+                Map<Integer, List<CourseDetailVo>> weeksMap = detailVoList.stream().collect(Collectors.groupingBy(CourseDetailVo::getWeeks));
+
+                List<Integer> weekList = new ArrayList<>();
+                weekList.add(1);
+                weekList.add(2);
+                weekList.add(3);
+                weekList.add(4);
+                weekList.add(5);
+                weekList.add(6);
+                weekList.add(7);
+
+                ArrayList<String> classData = new ArrayList<>();
+                classData.add(baseClass.getName());
+                for (Integer weeks : weekList) {
+                    if(!weeksMap.containsKey(weeks)){
+                        for (ClassTime classTime : tableVo.getClassTimeList()) {
+                            classData.add("");
+                        }
+                        continue;
+                    }
+                    Map<String, CourseDetailVo> timeNumberMap = weeksMap.get(weeks).stream().filter(x -> x.getTimeNumber() != null).collect(Collectors.toMap(CourseDetailVo::getTimeNumber, x -> x));
+                    for (ClassTime classTime : tableVo.getClassTimeList()) {
+                        CourseDetailVo courseDetailVo = timeNumberMap.get(classTime.getNumber().toString());
+                        if(courseDetailVo == null){
+                            classData.add("");
+                            continue;
+                        }
+                        String content = courseDetailVo.getCourseName() + "\r\n" + courseDetailVo.getTeacherName();
+                        if(courseDetailVo.getClassroomName() != null){
+                            content += "\r\n" + courseDetailVo.getClassroomName();
+                        }
+                        classData.add(content);
+                    }
+                }
+                dataList.add(classData);
+            }
+
+            Workbook workbook = new XSSFWorkbook();
+            String sheetName = "总课表";
+            Sheet sheet = workbook.createSheet(sheetName);
+
+            exportQueryUtil.createAllCourseTableTitle(workbook, sheet, tableVo.getClassTimeList());
+
+            Font font = workbook.createFont();
+            font.setFontName("宋体");
+            font.setFontHeightInPoints((short) 12);
+
+            CellStyle cellStyle = workbook.createCellStyle();
+            cellStyle.setFont(font); // 将字体应用到样式
+            cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            cellStyle.setAlignment(HorizontalAlignment.CENTER);
+            cellStyle.setBorderTop(BorderStyle.THIN);
+            cellStyle.setBorderBottom(BorderStyle.THIN);
+            cellStyle.setBorderLeft(BorderStyle.THIN);
+            cellStyle.setBorderRight(BorderStyle.THIN);
+            cellStyle.setWrapText(true);
+            int rowNumber = 2;
+            for (ArrayList<String> rowData : dataList) {
+                Row row = sheet.createRow(rowNumber);
+                for (int i = 0; i < rowData.size(); i++) {
+                    Cell cell = row.createCell(i);
+                    cell.setCellValue(rowData.get(i));
+                    cell.setCellStyle(cellStyle);
+                }
+                rowNumber++;
+                row.setHeightInPoints(48);
+            }
+
+            workbook.write(bot);
+        }
+        return bot;
+    }
+
 }

+ 112 - 0
src/main/java/com/xjrsoft/module/feedback/controller/FeedbackController.java

@@ -0,0 +1,112 @@
+package com.xjrsoft.module.feedback.controller;
+
+import cn.dev33.satoken.stp.StpUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.xjrsoft.common.page.ConventPage;
+import com.xjrsoft.common.page.PageOutput;
+import com.xjrsoft.common.model.result.RT;
+import com.xjrsoft.module.feedback.dto.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.xjrsoft.common.annotation.XjrLog;
+
+import com.xjrsoft.module.feedback.entity.Feedback;
+import com.xjrsoft.module.feedback.service.IFeedbackService;
+import com.xjrsoft.module.feedback.vo.FeedbackPageVo;
+
+import com.xjrsoft.module.feedback.vo.HistoryPageMobileVo;
+import com.xjrsoft.module.feedback.vo.ItemDetailListVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+* @title: 意见反馈
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@RestController
+@RequestMapping("/feedback" + "/feedback")
+@Api(value = "/feedback"  + "/feedback",tags = "意见反馈代码")
+@AllArgsConstructor
+public class FeedbackController {
+
+
+    private final IFeedbackService feedbackService;
+
+    @GetMapping(value = "/page")
+    @ApiOperation(value="意见反馈列表(分页)")
+    @SaCheckPermission("feedback:detail")
+    @XjrLog(value = "意见反馈列表(分页)")
+    public RT<PageOutput<FeedbackPageVo>> page(@Valid FeedbackPageDto dto){
+        IPage<FeedbackPageVo> page = feedbackService.pageRubAndHand(dto);
+        PageOutput<FeedbackPageVo> pageOutput = ConventPage.getPageOutput(page, FeedbackPageVo.class);
+        return RT.ok(pageOutput);
+    }
+
+    @GetMapping(value = "/history-page-mobile")
+    @ApiOperation(value="移动端-历史反馈列表(分页)")
+    @SaCheckPermission("feedback:detail")
+    @XjrLog(value = "移动端-历史反馈列表(分页)")
+    public RT<PageOutput<HistoryPageMobileVo>> historyPageMobile(@Valid HistoryPageMobileDto dto){
+        IPage<HistoryPageMobileVo> page = feedbackService.historyPageMobile(dto);
+        PageOutput<HistoryPageMobileVo> pageOutput = ConventPage.getPageOutput(page, HistoryPageMobileVo.class);
+        return RT.ok(pageOutput);
+    }
+
+    @GetMapping(value = "/item-detail-list")
+    @ApiOperation(value="反馈详情列表(不分页)")
+    @SaCheckPermission("feedback:detail")
+    @XjrLog(value = "反馈详情列表(不分页)")
+    public RT<List<ItemDetailListVo>> itemDetailList(@Valid ItemDetailListDto dto){
+        List<ItemDetailListVo> List = feedbackService.itemDetailList(dto);
+        return RT.ok(List);
+    }
+
+    @GetMapping(value = "isRead")
+    @ApiOperation(value="当前用户的反馈的回复是否已经全部阅读")
+    @SaCheckPermission("feedback:detail")
+    @XjrLog(value = "当前用户的反馈的回复是否已经全部阅读")
+    public RT<Boolean> isRead(){
+        LambdaQueryWrapper<Feedback> feedbackLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        feedbackLambdaQueryWrapper
+                .eq(Feedback::getUserId, StpUtil.getLoginIdAsLong())
+                .eq(Feedback::getHandleReadStatus, 1)
+                ;
+        List<Feedback> feedbacks = feedbackService.list(feedbackLambdaQueryWrapper);
+        if(CollectionUtils.isNotEmpty(feedbacks)){
+            return RT.ok(false);
+        }
+        return RT.ok(true);
+    }
+
+    @PostMapping("/add-feedback-item")
+    @ApiOperation(value = "新增(补充)意见反馈-具体反馈单项")
+    @SaCheckPermission("feedback:add")
+    @XjrLog(value = "新增(补充)意见反馈-具体反馈单项")
+    public RT<Boolean> addFeedbackItem(@Valid @RequestBody AddFeedbackItemDto dto){
+        return RT.ok(feedbackService.addFeedbackItem(dto));
+    }
+
+    @PostMapping("/reply")
+    @ApiOperation(value = "回复反馈")
+    @SaCheckPermission("feedback:edit")
+    @XjrLog(value = "回复反馈")
+    public RT<Boolean> replyFeedbackItem(@Valid @RequestBody ReplyFeedbackItemDto dto){
+        return RT.ok(feedbackService.replyFeedbackItem(dto));
+    }
+
+    @PostMapping("/read")
+    @ApiOperation(value = "已读回复")
+    @SaCheckPermission("feedback:edit")
+    @XjrLog(value = "已读回复")
+    public RT<Boolean> readFeedbackItem(@Valid @RequestBody ReadFeedbackItemDto dto){
+        return RT.ok(feedbackService.readFeedbackItem(dto));
+    }
+}

+ 47 - 0
src/main/java/com/xjrsoft/module/feedback/dto/AddFeedbackItemDto.java

@@ -0,0 +1,47 @@
+package com.xjrsoft.module.feedback.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.io.Serializable;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Date;
+
+
+
+/**
+* @title: 意见反馈-具体反馈单项
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+public class AddFeedbackItemDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 终端类型(xjr_dictionary_item(terminal_type))
+     */
+    @ApiModelProperty("终端类型(xjr_dictionary_item(terminal_type))")
+    private String terminalType;
+    /**
+    * 意见反馈主键id(feedback)
+    */
+    @ApiModelProperty("意见反馈主键id(feedback)")
+    private Long feedbackId;
+    /**
+    * 反馈文本
+    */
+    @ApiModelProperty("反馈文本")
+    private String feedback;
+    /**
+    * 反馈附件主键id
+    */
+    @ApiModelProperty("反馈附件主键id")
+    private Long feedbackFileId;
+}

+ 38 - 0
src/main/java/com/xjrsoft/module/feedback/dto/FeedbackPageDto.java

@@ -0,0 +1,38 @@
+package com.xjrsoft.module.feedback.dto;
+
+import com.xjrsoft.common.page.PageInput;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import javax.validation.constraints.NotNull;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.Date;
+
+
+/**
+* @title: 意见反馈分页查询入参
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class FeedbackPageDto extends PageInput {
+
+    @ApiModelProperty("处理状态(0:未处理,1:已处理)")
+    @NotNull(message = "处理状态不能为空!")
+    private Integer handleStatus;
+
+    @ApiModelProperty("意见反馈反馈用户主键id(xjr_user)")
+    private String userIdCn;
+
+    @ApiModelProperty("用户类型(1:教师,2:学生,3:家长)")
+    private Integer userType;
+
+    @ApiModelProperty("终端类型(xjr_dictionary_item(terminal_type))")
+    private String terminalType;
+}

+ 19 - 0
src/main/java/com/xjrsoft/module/feedback/dto/HistoryPageMobileDto.java

@@ -0,0 +1,19 @@
+package com.xjrsoft.module.feedback.dto;
+
+import com.xjrsoft.common.page.PageInput;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+
+/**
+* @title: 移动端-历史反馈列表(分页)
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class HistoryPageMobileDto extends PageInput {
+
+
+}

+ 21 - 0
src/main/java/com/xjrsoft/module/feedback/dto/ItemDetailListDto.java

@@ -0,0 +1,21 @@
+package com.xjrsoft.module.feedback.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+
+/**
+* @title: 反馈详情列表(不分页)入参
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+public class ItemDetailListDto {
+
+    @ApiModelProperty("意见反馈主键id(feedback)")
+    private Long feedbackId;
+}

+ 22 - 0
src/main/java/com/xjrsoft/module/feedback/dto/ReadFeedbackItemDto.java

@@ -0,0 +1,22 @@
+package com.xjrsoft.module.feedback.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+
+/**
+* @title: 意见反馈-具体反馈单项
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+public class ReadFeedbackItemDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("主键编号")
+    private Long id;
+}

+ 33 - 0
src/main/java/com/xjrsoft/module/feedback/dto/ReplyFeedbackItemDto.java

@@ -0,0 +1,33 @@
+package com.xjrsoft.module.feedback.dto;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+
+/**
+* @title: 意见反馈-具体反馈单项
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+public class ReplyFeedbackItemDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("主键编号")
+    private Long id;
+    /**
+     * 回复文本
+     */
+    @ApiModelProperty("回复文本")
+    private String reply;
+    /**
+     * 回复附件主键id
+     */
+    @ApiModelProperty("回复附件主键id")
+    private Long replyFileId;
+}

+ 111 - 0
src/main/java/com/xjrsoft/module/feedback/entity/Feedback.java

@@ -0,0 +1,111 @@
+package com.xjrsoft.module.feedback.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.github.yulichang.annotation.EntityMapping;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.io.Serializable;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Date;
+
+
+/**
+* @title: 意见反馈
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+@TableName("feedback")
+@ApiModel(value = "feedback", description = "意见反馈")
+public class Feedback implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+    * 主键编号
+    */
+    @ApiModelProperty("主键编号")
+    @TableId
+    private Long id;
+    /**
+    * 创建人
+    */
+    @ApiModelProperty("创建人")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUserId;
+    /**
+    * 创建时间
+    */
+    @ApiModelProperty("创建时间")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createDate;
+    /**
+    * 修改人
+    */
+    @ApiModelProperty("修改人")
+    @TableField(fill = FieldFill.UPDATE)
+    private Long modifyUserId;
+    /**
+    * 修改时间
+    */
+    @ApiModelProperty("修改时间")
+    @TableField(fill = FieldFill.UPDATE)
+    private LocalDateTime modifyDate;
+    /**
+    * 删除标记
+    */
+    @ApiModelProperty("删除标记")
+    @TableField(fill = FieldFill.INSERT)
+    @TableLogic
+    private Integer deleteMark;
+    /**
+    * 有效标志
+    */
+    @ApiModelProperty("有效标志")
+    @TableField(fill = FieldFill.INSERT)
+    private Integer enabledMark;
+    /**
+    * 意见反馈反馈用户主键id(xjr_user)
+    */
+    @ApiModelProperty("意见反馈反馈用户主键id(xjr_user)")
+    private Long userId;
+    /**
+    * 用户类型(1:教师,2:学生,3:家长)
+    */
+    @ApiModelProperty("用户类型(1:教师,2:学生,3:家长,4:其他)")
+    private Integer userType;
+    /**
+    * 终端类型(xjr_dictionary_item(terminal_type))
+    */
+    @ApiModelProperty("终端类型(xjr_dictionary_item(terminal_type))")
+    private String terminalType;
+    /**
+    * 反馈时间
+    */
+    @ApiModelProperty("反馈时间")
+    private LocalDateTime feedbackTime;
+    /**
+    * 处理状态(0:未处理,1:已处理)
+    */
+    @ApiModelProperty("处理状态(0:未处理,1:已处理)")
+    private Integer handleStatus;
+    /**
+    * 处理时间
+    */
+    @ApiModelProperty("处理时间")
+    private LocalDateTime handleTime;
+    /**
+    * 处理知晓状态(0:没有未读,1:有未读)
+    */
+    @ApiModelProperty("处理知晓状态(0:没有未读,1:有未读)")
+    private Integer handleReadStatus;
+}

+ 123 - 0
src/main/java/com/xjrsoft/module/feedback/entity/FeedbackItem.java

@@ -0,0 +1,123 @@
+package com.xjrsoft.module.feedback.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.github.yulichang.annotation.EntityMapping;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.io.Serializable;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Date;
+
+
+/**
+* @title: 意见反馈-具体反馈单项
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+@TableName("feedback_item")
+@ApiModel(value = "feedback_item", description = "意见反馈-具体反馈单项")
+public class FeedbackItem implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+    * 主键编号
+    */
+    @ApiModelProperty("主键编号")
+    @TableId
+    private Long id;
+    /**
+    * 创建人
+    */
+    @ApiModelProperty("创建人")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUserId;
+    /**
+    * 创建时间
+    */
+    @ApiModelProperty("创建时间")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createDate;
+    /**
+    * 修改人
+    */
+    @ApiModelProperty("修改人")
+    @TableField(fill = FieldFill.UPDATE)
+    private Long modifyUserId;
+    /**
+    * 修改时间
+    */
+    @ApiModelProperty("修改时间")
+    @TableField(fill = FieldFill.UPDATE)
+    private LocalDateTime modifyDate;
+    /**
+    * 删除标记
+    */
+    @ApiModelProperty("删除标记")
+    @TableField(fill = FieldFill.INSERT)
+    @TableLogic
+    private Integer deleteMark;
+    /**
+    * 有效标志
+    */
+    @ApiModelProperty("有效标志")
+    @TableField(fill = FieldFill.INSERT)
+    private Integer enabledMark;
+    /**
+    * 意见反馈主键id(feedback)
+    */
+    @ApiModelProperty("意见反馈主键id(feedback)")
+    private Long feedbackId;
+    /**
+    * 反馈文本
+    */
+    @ApiModelProperty("反馈文本")
+    private String feedback;
+    /**
+    * 反馈附件主键id
+    */
+    @ApiModelProperty("反馈附件主键id")
+    private Long feedbackFileId;
+    /**
+    * 回复状态(0:未回复,1:已回复)
+    */
+    @ApiModelProperty("回复状态(0:未回复,1:已回复)")
+    private Integer replyStatus;
+    /**
+    * 回复反馈用户主键id(xjr_user)
+    */
+    @ApiModelProperty("回复反馈用户主键id(xjr_user)")
+    private Long replyUserId;
+    /**
+    * 回复文本
+    */
+    @ApiModelProperty("回复文本")
+    private String reply;
+    /**
+    * 回复附件主键id
+    */
+    @ApiModelProperty("回复附件主键id")
+    private Long replyFileId;
+    /**
+    * 回复时间
+    */
+    @ApiModelProperty("回复时间")
+    private LocalDateTime replyTime;
+    /**
+    * 回复阅读状态(0:未读,1:已读)
+    */
+    @ApiModelProperty("回复阅读状态(0:未读,1:已读)")
+    private Integer replyReadStatus;
+
+
+}

+ 17 - 0
src/main/java/com/xjrsoft/module/feedback/mapper/FeedbackItemMapper.java

@@ -0,0 +1,17 @@
+package com.xjrsoft.module.feedback.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.github.yulichang.base.MPJBaseMapper;
+import com.xjrsoft.module.feedback.entity.FeedbackItem;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @title: 意见反馈-具体反馈单项
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Mapper
+public interface FeedbackItemMapper extends MPJBaseMapper<FeedbackItem> {
+
+}

+ 17 - 0
src/main/java/com/xjrsoft/module/feedback/mapper/FeedbackMapper.java

@@ -0,0 +1,17 @@
+package com.xjrsoft.module.feedback.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.github.yulichang.base.MPJBaseMapper;
+import com.xjrsoft.module.feedback.entity.Feedback;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @title: 意见反馈
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Mapper
+public interface FeedbackMapper extends MPJBaseMapper<Feedback> {
+
+}

+ 33 - 0
src/main/java/com/xjrsoft/module/feedback/service/IFeedbackService.java

@@ -0,0 +1,33 @@
+package com.xjrsoft.module.feedback.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.github.yulichang.base.MPJBaseService;
+import com.xjrsoft.module.feedback.dto.*;
+import com.xjrsoft.module.feedback.entity.Feedback;
+import com.xjrsoft.module.feedback.vo.FeedbackPageVo;
+import com.xjrsoft.module.feedback.vo.HistoryPageMobileVo;
+import com.xjrsoft.module.feedback.vo.ItemDetailListVo;
+import lombok.Data;
+import java.util.List;
+
+/**
+* @title: 意见反馈
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+
+public interface IFeedbackService extends MPJBaseService<Feedback> {
+    IPage<FeedbackPageVo> pageRubAndHand(FeedbackPageDto dto);
+
+    IPage<HistoryPageMobileVo> historyPageMobile(HistoryPageMobileDto dto);
+
+    List<ItemDetailListVo> itemDetailList(ItemDetailListDto dto);
+
+    Boolean addFeedbackItem(AddFeedbackItemDto dto);
+
+    Boolean replyFeedbackItem(ReplyFeedbackItemDto dto);
+
+    Boolean readFeedbackItem(ReadFeedbackItemDto dto);
+}

+ 326 - 0
src/main/java/com/xjrsoft/module/feedback/service/impl/FeedbackServiceImpl.java

@@ -0,0 +1,326 @@
+package com.xjrsoft.module.feedback.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.yulichang.base.MPJBaseServiceImpl;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import com.xjrsoft.common.exception.MyException;
+import com.xjrsoft.common.page.ConventPage;
+import com.xjrsoft.common.utils.VoToColumnUtil;
+import com.xjrsoft.module.feedback.dto.*;
+import com.xjrsoft.module.feedback.entity.Feedback;
+import com.xjrsoft.module.feedback.entity.FeedbackItem;
+import com.xjrsoft.module.feedback.mapper.FeedbackItemMapper;
+import com.xjrsoft.module.feedback.mapper.FeedbackMapper;
+import com.xjrsoft.module.feedback.service.IFeedbackService;
+import com.xjrsoft.module.feedback.vo.FeedbackPageVo;
+import com.xjrsoft.module.feedback.vo.HistoryPageMobileVo;
+import com.xjrsoft.module.feedback.vo.ItemDetailListVo;
+import com.xjrsoft.module.system.entity.DictionaryDetail;
+import com.xjrsoft.module.system.entity.File;
+import com.xjrsoft.module.system.service.IFileService;
+import com.xjrsoft.module.teacher.entity.XjrUser;
+import lombok.AllArgsConstructor;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+
+/**
+* @title: 意见反馈
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Service
+@AllArgsConstructor
+public class FeedbackServiceImpl extends MPJBaseServiceImpl<FeedbackMapper, Feedback> implements IFeedbackService {
+
+    private final FeedbackItemMapper feedbackItemMapper;
+
+    private final IFileService fileService;
+
+    @Override
+    public IPage<FeedbackPageVo> pageRubAndHand(FeedbackPageDto dto) {
+        MPJLambdaWrapper<Feedback> feedbackMPJLambdaWrapper = new MPJLambdaWrapper<>();
+        feedbackMPJLambdaWrapper
+                .disableSubLogicDel()
+                .select(Feedback::getId)
+                .select(Feedback.class,x -> VoToColumnUtil.fieldsToColumns(FeedbackPageVo.class).contains(x.getProperty()))
+                .leftJoin(XjrUser.class, XjrUser::getId, Feedback::getUserId,
+                        wrapper -> wrapper
+                                .selectAs(XjrUser::getName, FeedbackPageVo::getUserIdCn)
+                                .like(StringUtils.isNotEmpty(dto.getUserIdCn()), XjrUser::getName, dto.getUserIdCn())
+                )
+                .leftJoin(DictionaryDetail.class, DictionaryDetail::getCode, Feedback::getTerminalType,
+                        wrapper -> wrapper
+                                .selectAs(DictionaryDetail::getName, FeedbackPageVo::getTerminalTypeCn)
+                )
+                .eq(Feedback::getHandleStatus, dto.getHandleStatus())
+                .eq(ObjectUtils.isNotEmpty(dto.getUserType()), Feedback::getUserType, dto.getUserType())
+                .eq(StringUtils.isNotEmpty(dto.getTerminalType()), Feedback::getTerminalType, dto.getTerminalType())
+                .orderByDesc(Feedback::getCreateDate)
+                ;
+        IPage<FeedbackPageVo> page = this.selectJoinListPage(ConventPage.getPage(dto), FeedbackPageVo.class, feedbackMPJLambdaWrapper);
+
+        // 处理最后一条反馈内容
+        List<FeedbackPageVo> dataList = page.getRecords();
+        if(CollectionUtils.isNotEmpty(dataList)){
+            List<String> ids = dataList.stream()
+                    .map(FeedbackPageVo::getId)
+                    .collect(Collectors.toList());
+            if(CollectionUtils.isNotEmpty(ids)){
+                List<FeedbackItem> feedbackItems = feedbackItemMapper.selectList(
+                        Wrappers.lambdaQuery(FeedbackItem.class)
+                                .in(FeedbackItem::getFeedbackId, ids)
+                );
+
+                Map<Long, List<FeedbackItem>> groupByFeedbackId = feedbackItems.stream()
+                        .collect(Collectors.groupingBy(FeedbackItem::getFeedbackId));
+
+                for (FeedbackPageVo vo : page.getRecords()){
+                    List<FeedbackItem> feedbackItemList = groupByFeedbackId.get(Long.parseLong(vo.getId()));
+                    if(CollectionUtils.isNotEmpty(feedbackItemList)){
+                        feedbackItemList.sort(Comparator.comparing(FeedbackItem::getCreateDate).reversed());
+                        FeedbackItem lastOne = feedbackItemList.get(0);
+                        if(ObjectUtils.isNotEmpty(lastOne)){
+                            vo.setFeedbackTime(lastOne.getCreateDate());
+                        }
+                    }
+                }
+            }
+        }
+
+        return page;
+    }
+
+    @Override
+    public IPage<HistoryPageMobileVo> historyPageMobile(HistoryPageMobileDto dto) {
+        Long loginId = StpUtil.getLoginIdAsLong();
+
+        MPJLambdaWrapper<Feedback> feedbackMPJLambdaWrapper = new MPJLambdaWrapper<>();
+        feedbackMPJLambdaWrapper
+                .disableSubLogicDel()
+                .select(Feedback::getId)
+                .select(Feedback.class,x -> VoToColumnUtil.fieldsToColumns(HistoryPageMobileVo.class).contains(x.getProperty()))
+                .leftJoin(DictionaryDetail.class, DictionaryDetail::getCode, Feedback::getTerminalType,
+                        wrapper -> wrapper
+                                .selectAs(DictionaryDetail::getName, HistoryPageMobileVo::getTerminalTypeCn)
+                        )
+                .eq(Feedback::getUserId, loginId)
+                .orderByDesc(Feedback::getHandleReadStatus)
+                ;
+        IPage<HistoryPageMobileVo> page = this.selectJoinListPage(ConventPage.getPage(dto), HistoryPageMobileVo.class, feedbackMPJLambdaWrapper);
+
+        // 处理最后一条反馈内容
+        List<HistoryPageMobileVo> dataList = page.getRecords();
+        if(CollectionUtils.isNotEmpty(dataList)){
+            List<String> ids = dataList.stream()
+                    .map(HistoryPageMobileVo::getId)
+                    .collect(Collectors.toList());
+            if(CollectionUtils.isNotEmpty(ids)){
+                List<FeedbackItem> feedbackItems = feedbackItemMapper.selectList(
+                        Wrappers.lambdaQuery(FeedbackItem.class)
+                                .in(FeedbackItem::getFeedbackId, ids)
+                );
+
+                Map<Long, List<FeedbackItem>> groupByFeedbackId = feedbackItems.stream()
+                        .collect(Collectors.groupingBy(FeedbackItem::getFeedbackId));
+
+                for (HistoryPageMobileVo vo : page.getRecords()){
+                    List<FeedbackItem> feedbackItemList = groupByFeedbackId.get(Long.parseLong(vo.getId()));
+                    if(CollectionUtils.isNotEmpty(feedbackItemList)){
+                        feedbackItemList.sort(Comparator.comparing(FeedbackItem::getCreateDate).reversed());
+                        FeedbackItem lastOne = feedbackItemList.get(0);
+                        if(ObjectUtils.isNotEmpty(lastOne)){
+                            vo.setFeedbackTime(lastOne.getCreateDate());
+                            vo.setFeedback(lastOne.getFeedback());
+                            vo.setReply(lastOne.getReply());
+                        }
+                    }
+                }
+            }
+        }
+
+        return page;
+    }
+
+    @Override
+    public List<ItemDetailListVo> itemDetailList(ItemDetailListDto dto) {
+        MPJLambdaWrapper<FeedbackItem> feedbackItemMPJLambdaWrapper = new MPJLambdaWrapper<>();
+        feedbackItemMPJLambdaWrapper
+                .disableSubLogicDel()
+                .select(FeedbackItem::getId)
+                .select(FeedbackItem.class,x -> VoToColumnUtil.fieldsToColumns(ItemDetailListVo.class).contains(x.getProperty()))
+                .innerJoin(Feedback.class, Feedback::getId, FeedbackItem::getFeedbackId,
+                        wrapper -> wrapper
+                                .selectAs(Feedback::getUserType, ItemDetailListVo::getUserType)
+                                .selectAs(Feedback::getTerminalType, ItemDetailListVo::getTerminalType)
+                                .leftJoin(DictionaryDetail.class, DictionaryDetail::getCode, Feedback::getTerminalType,
+                                        wrap -> wrap
+                                                .selectAs(DictionaryDetail::getName, ItemDetailListVo::getTerminalTypeCn)
+                                )
+                        )
+                .leftJoin(XjrUser.class, XjrUser::getId, FeedbackItem::getCreateUserId,
+                        wrapper -> wrapper
+                                .selectAs(XjrUser::getName, ItemDetailListVo::getCreateUserIdCn)
+                )
+                .leftJoin(XjrUser.class, XjrUser::getId, FeedbackItem::getReplyUserId,
+                        wrapper -> wrapper
+                                .selectAs(XjrUser::getName, ItemDetailListVo::getReplyUserIdCn)
+                )
+                .eq(FeedbackItem::getFeedbackId, dto.getFeedbackId())
+        ;
+        List<ItemDetailListVo> itemDetailListVos = feedbackItemMapper.selectJoinList(ItemDetailListVo.class, feedbackItemMPJLambdaWrapper);
+
+        for (ItemDetailListVo vo : itemDetailListVos){
+            if(ObjectUtils.isNotEmpty(vo.getFeedbackFileId())){
+                List<File> feedbackFile = fileService.list(Wrappers.<File>query().lambda().eq(File::getFolderId, vo.getFeedbackFileId()));
+                vo.setFeedbackFiles(feedbackFile);
+            }
+            if(ObjectUtils.isNotEmpty(vo.getReplyFileId())){
+                List<File> replyFile = fileService.list(Wrappers.<File>query().lambda().eq(File::getFolderId, vo.getReplyFileId()));
+                vo.setReplyFiles(replyFile);
+            }
+        }
+
+        return itemDetailListVos;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean addFeedbackItem(AddFeedbackItemDto dto) {
+        LocalDateTime nowLocalDateTime = LocalDateTime.now();
+        Long loginId = StpUtil.getLoginIdAsLong();
+
+        Feedback feedback = new Feedback();
+        // 如果没有传入意见反馈主键id表示为新增反馈,应该添加主表记录
+        if(ObjectUtils.isEmpty(dto.getFeedbackId())){
+            feedback.setCreateDate(nowLocalDateTime);
+            feedback.setCreateUserId(loginId);
+            feedback.setUserId(loginId);
+
+            //  处理用户类别
+            if(StpUtil.hasRole("TEACHER")){
+                feedback.setUserType(1);
+            } else if(StpUtil.hasRole("STUDENT")){
+                feedback.setUserType(2);
+            } else if(StpUtil.hasRole("PARENT")){
+                feedback.setUserType(3);
+            } else {
+                feedback.setUserType(4);
+            }
+
+            if(ObjectUtils.isNotEmpty(dto.getTerminalType())){
+                feedback.setTerminalType(dto.getTerminalType());
+            } else {
+                throw new MyException("请选择终端类型");
+            }
+
+            feedback.setFeedbackTime(nowLocalDateTime);
+            feedback.setHandleStatus(0);
+            feedback.setHandleReadStatus(0);
+
+            this.save(feedback);
+
+            dto.setFeedbackId(feedback.getId());
+        } else {
+            feedback.setId(dto.getFeedbackId());
+            feedback.setFeedbackTime(nowLocalDateTime);
+            feedback.setHandleStatus(0);
+            feedback.setHandleTime(null);
+            feedback.setModifyUserId(loginId);
+            feedback.setModifyDate(nowLocalDateTime);
+            this.updateById(feedback);
+        }
+
+        FeedbackItem feedbackItem = BeanUtil.toBean(dto, FeedbackItem.class);
+        feedbackItem.setCreateDate(nowLocalDateTime);
+        feedbackItem.setCreateUserId(loginId);
+        feedbackItemMapper.insert(feedbackItem);
+
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean replyFeedbackItem(ReplyFeedbackItemDto dto) {
+        FeedbackItem old = feedbackItemMapper.selectById(dto.getId());
+
+        if(ObjectUtils.isEmpty(old)){
+            throw new MyException("当前反馈已被修改,请刷新重试");
+        }
+
+        LocalDateTime nowLocalDateTime = LocalDateTime.now();
+        Long loginId = StpUtil.getLoginIdAsLong();
+
+        FeedbackItem feedbackItem = BeanUtil.toBean(dto, FeedbackItem.class);
+        feedbackItem.setId(dto.getId());
+        feedbackItem.setModifyDate(nowLocalDateTime);
+        feedbackItem.setModifyUserId(loginId);
+
+        feedbackItem.setReplyStatus(1);
+        feedbackItem.setReplyUserId(loginId);
+        feedbackItem.setReplyTime(nowLocalDateTime);
+        feedbackItem.setReplyReadStatus(0);
+
+        feedbackItemMapper.updateById(feedbackItem);
+        
+        // 处理主反馈的状态
+        Feedback updateFeedback = new Feedback();
+        updateFeedback.setId(old.getFeedbackId());
+        updateFeedback.setModifyDate(nowLocalDateTime);
+        updateFeedback.setModifyUserId(loginId);
+
+        // 查询当前主反馈是否还有没有回复的
+        LambdaQueryWrapper<FeedbackItem> replyLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        replyLambdaQueryWrapper
+                .eq(FeedbackItem::getFeedbackId, old.getFeedbackId())
+                .eq(FeedbackItem::getReplyStatus, 0)
+                ;
+        List<FeedbackItem> replyLFeedbackItems = feedbackItemMapper.selectList(replyLambdaQueryWrapper);
+        if(CollectionUtils.isEmpty(replyLFeedbackItems)){
+            updateFeedback.setHandleStatus(1);
+            updateFeedback.setHandleTime(nowLocalDateTime);
+        }
+
+        updateFeedback.setHandleReadStatus(1);
+
+        this.updateById(updateFeedback);
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean readFeedbackItem(ReadFeedbackItemDto dto) {
+        Feedback old = this.getById(dto.getId());
+
+        if(ObjectUtils.isEmpty(old)){
+            throw new MyException("当前反馈已被修改,请刷新重试");
+        }
+
+        // 将所有已经处理的反馈项阅读
+        LambdaUpdateWrapper<FeedbackItem> feedbackItemLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+        feedbackItemLambdaUpdateWrapper
+                .set(FeedbackItem::getReplyReadStatus, 1)
+                .eq(FeedbackItem::getFeedbackId, dto.getId())
+                .eq(FeedbackItem::getReplyStatus, 1)
+                ;
+        feedbackItemMapper.update(new FeedbackItem(), feedbackItemLambdaUpdateWrapper);
+
+        this.updateById(new Feedback(){{
+            setId(dto.getId());
+            setHandleReadStatus(0);
+        }});
+        return true;
+    }
+}

+ 73 - 0
src/main/java/com/xjrsoft/module/feedback/vo/FeedbackPageVo.java

@@ -0,0 +1,73 @@
+package com.xjrsoft.module.feedback.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import com.xjrsoft.common.annotation.Trans;
+import com.xjrsoft.common.enums.TransType;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+* @title: 意见反馈分页列表出参
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+public class FeedbackPageVo {
+
+    /**
+    * 主键编号
+    */
+    @ApiModelProperty("主键编号")
+    private String id;
+
+    /**
+    * 意见反馈反馈用户主键id(xjr_user)
+    */
+    @ApiModelProperty("意见反馈反馈用户主键id(xjr_user)")
+    private Long userId;
+
+    @ApiModelProperty("意见反馈反馈用户主键id(xjr_user)")
+    private String userIdCn;
+
+    /**
+    * 用户类型(1:教师,2:学生,3:家长)
+    */
+    @ApiModelProperty("用户类型(1:教师,2:学生,3:家长)")
+    private Integer userType;
+    /**
+    * 终端类型(xjr_dictionary_item(terminal_type))
+    */
+    @ApiModelProperty("终端类型(xjr_dictionary_item(terminal_type))")
+    private String terminalType;
+
+    @ApiModelProperty("终端类型(xjr_dictionary_item(terminal_type))")
+    private String terminalTypeCn;
+
+    /**
+    * 反馈时间
+    */
+    @ApiModelProperty("反馈时间")
+    private LocalDateTime feedbackTime;
+    /**
+    * 处理状态(0:未处理,1:已处理)
+    */
+    @ApiModelProperty("处理状态(0:未处理,1:已处理)")
+    private Integer handleStatus;
+    /**
+    * 处理时间
+    */
+    @ApiModelProperty("处理时间")
+    private LocalDateTime handleTime;
+    /**
+    * 处理知晓状态(0:未读,1:已读)
+    */
+    @ApiModelProperty("处理知晓状态(0:没有未读,1:有未读)")
+    private Integer handleReadStatus;
+
+}

+ 54 - 0
src/main/java/com/xjrsoft/module/feedback/vo/HistoryPageMobileVo.java

@@ -0,0 +1,54 @@
+package com.xjrsoft.module.feedback.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+* @title: 移动端-历史反馈列表(分页)
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+public class HistoryPageMobileVo {
+
+    /**
+    * 主键编号
+    */
+    @ApiModelProperty("主键编号")
+    private String id;
+    /**
+    * 终端类型(xjr_dictionary_item(terminal_type))
+    */
+    @ApiModelProperty("终端类型(xjr_dictionary_item(terminal_type))")
+    private String terminalType;
+
+    @ApiModelProperty("终端类型(xjr_dictionary_item(terminal_type))")
+    private String terminalTypeCn;
+
+    /**
+    * 反馈时间
+    */
+    @ApiModelProperty("反馈时间")
+    private LocalDateTime feedbackTime;
+    /**
+    * 处理状态(0:未处理,1:已处理)
+    */
+    @ApiModelProperty("处理状态(0:未处理,1:已处理)")
+    private Integer handleStatus;
+
+    /**
+    * 处理知晓状态(0:未读,1:已读)
+    */
+    @ApiModelProperty("处理知晓状态(0:没有未读,1:有未读)")
+    private Integer handleReadStatus;
+
+    @ApiModelProperty("最后一条反馈文本")
+    private String feedback;
+
+    @ApiModelProperty("最后一条回复文本")
+    private String reply;
+}

+ 111 - 0
src/main/java/com/xjrsoft/module/feedback/vo/ItemDetailListVo.java

@@ -0,0 +1,111 @@
+package com.xjrsoft.module.feedback.vo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.xjrsoft.module.system.entity.File;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.List;
+
+/**
+* @title: 反馈详情列表(不分页)出参
+* @Author phoenix
+* @Date: 2025-04-09
+* @Version 1.0
+*/
+@Data
+public class ItemDetailListVo {
+
+    @ApiModelProperty("主键编号")
+    private Long id;
+    /**
+     * 创建人
+     */
+    @ApiModelProperty("创建人")
+    private Long createUserId;
+
+    @ApiModelProperty("创建人")
+    private String createUserIdCn;
+
+    /**
+     * 用户类型(1:教师,2:学生,3:家长)
+     */
+    @ApiModelProperty("用户类型(1:教师,2:学生,3:家长,4:其他)")
+    private Integer userType;
+    /**
+     * 终端类型(xjr_dictionary_item(terminal_type))
+     */
+    @ApiModelProperty("终端类型(xjr_dictionary_item(terminal_type))")
+    private String terminalType;
+
+    @ApiModelProperty("终端类型(xjr_dictionary_item(terminal_type))")
+    private String terminalTypeCn;
+
+    /**
+     * 创建时间
+     */
+    @ApiModelProperty("创建时间")
+    private LocalDateTime createDate;
+    /**
+     * 意见反馈主键id(feedback)
+     */
+    @ApiModelProperty("意见反馈主键id(feedback)")
+    private Long feedbackId;
+    /**
+     * 反馈文本
+     */
+    @ApiModelProperty("反馈文本")
+    private String feedback;
+    /**
+     * 反馈附件主键id
+     */
+    @ApiModelProperty("反馈附件主键id")
+    private Long feedbackFileId;
+
+    @ApiModelProperty("反馈附件主键id")
+    private List<File> feedbackFiles;
+
+    /**
+     * 回复状态(0:未回复,1:已回复)
+     */
+    @ApiModelProperty("回复状态(0:未回复,1:已回复)")
+    private Integer replyStatus;
+    /**
+     * 回复反馈用户主键id(xjr_user)
+     */
+    @ApiModelProperty("回复反馈用户主键id(xjr_user)")
+    private Long replyUserId;
+
+    @ApiModelProperty("回复反馈用户主键id(xjr_user)")
+    private String replyUserIdCn;
+
+    /**
+     * 回复文本
+     */
+    @ApiModelProperty("回复文本")
+    private String reply;
+    /**
+     * 回复附件主键id
+     */
+    @ApiModelProperty("回复附件主键id")
+    private Long replyFileId;
+
+    @ApiModelProperty("回复附件主键id")
+    private List<File> replyFiles;
+
+    /**
+     * 回复时间
+     */
+    @ApiModelProperty("回复时间")
+    private LocalDateTime replyTime;
+    /**
+     * 回复阅读状态(0:未读,1:已读)
+     */
+    @ApiModelProperty("回复阅读状态(0:未读,1:已读)")
+    private Integer replyReadStatus;
+}

+ 5 - 5
src/main/java/com/xjrsoft/module/generator/service/impl/ApiGeneratorServiceImpl.java

@@ -174,7 +174,7 @@ public class ApiGeneratorServiceImpl implements IApiGeneratorService {
                 }
                 fieldConfig.setFieldComment(column.getComment());
                 fieldConfig.setPk(column.isPk());
-                fieldConfig.setFieldType(JdbcToJavaUtil.getClassName(column.getTypeEnum(), 1));
+                fieldConfig.setFieldType(JdbcToJavaUtil.getClassName(column.getTypeEnum(), 0));
                 fieldConfig.setFieldName(StrUtil.toCamelCase(column.getName()));
 
                 fieldConfigList.add(fieldConfig);
@@ -433,7 +433,7 @@ public class ApiGeneratorServiceImpl implements IApiGeneratorService {
                                 !StrUtil.equalsIgnoreCase(column.getName(), GlobalConstant.DELETE_MARK) &&
                                 !StrUtil.equalsIgnoreCase(column.getName(), GlobalConstant.DELETE_MARK)
                 ) {
-                    String fieldType = JdbcToJavaUtil.getClassName(column.getTypeEnum(), 1);
+                    String fieldType = JdbcToJavaUtil.getClassName(column.getTypeEnum(), 0);
                     fieldConfig.setFieldComment(column.getComment());
                     fieldConfig.setPk(false);
                     fieldConfig.setFieldType(fieldType);
@@ -529,7 +529,7 @@ public class ApiGeneratorServiceImpl implements IApiGeneratorService {
                                 !StrUtil.equalsIgnoreCase(column.getName(), GlobalConstant.DELETE_MARK) &&
                                 !StrUtil.equalsIgnoreCase(column.getName(), GlobalConstant.DELETE_MARK)
                 ) {
-                    String fieldType = JdbcToJavaUtil.getClassName(column.getTypeEnum(), 1);
+                    String fieldType = JdbcToJavaUtil.getClassName(column.getTypeEnum(), 0);
                     fieldConfig.setFieldComment(column.getComment());
                     fieldConfig.setPk(false);
                     fieldConfig.setFieldType(fieldType);
@@ -622,7 +622,7 @@ public class ApiGeneratorServiceImpl implements IApiGeneratorService {
 
                     fieldConfig.setFieldComment(column.getComment());
                     fieldConfig.setPk(false);
-                    fieldConfig.setFieldType(JdbcToJavaUtil.getClassName(column.getTypeEnum(), 1));
+                    fieldConfig.setFieldType(JdbcToJavaUtil.getClassName(column.getTypeEnum(), 0));
                     fieldConfig.setFieldName(StrUtil.toCamelCase(column.getName()));
 
                     fieldConfigList.add(fieldConfig);
@@ -751,7 +751,7 @@ public class ApiGeneratorServiceImpl implements IApiGeneratorService {
             fieldConfig.setLabel(column.getComment());
             fieldConfig.setPk(false);
             //如果是主键 就默认使用字符串类型 前端无法识别long类型的精度
-            fieldConfig.setFieldType(column.isPk() ? "String" : JdbcToJavaUtil.getClassName(column.getTypeEnum(), 1));
+            fieldConfig.setFieldType(column.isPk() ? "String" : JdbcToJavaUtil.getClassName(column.getTypeEnum(), 0));
             fieldConfig.setFieldName(StrUtil.toCamelCase(column.getName()));
             fieldConfigList.add(fieldConfig);
         }

+ 1 - 4
src/main/java/com/xjrsoft/module/oa/controller/WfMeetingApplyController.java

@@ -98,9 +98,6 @@ public class WfMeetingApplyController {
     @XjrLog(value = "移动端会议信息会议的详情")
     public RT<MeetingMobileInfoVo> mobileInfo(@RequestParam Long id){
         MeetingMobileInfoVo meetingMobileInfoVo = wfMeetingApplyService.mobileInfo(id);
-        if (meetingMobileInfoVo == null) {
-           return RT.error("找不到此数据!");
-        }
         return RT.ok(meetingMobileInfoVo);
     }
 
@@ -197,7 +194,7 @@ public class WfMeetingApplyController {
     @SaCheckPermission("wfmeetingapply:detail")
     @XjrLog(value = "会议签到二维码-生成二维码")
     public RT<String> qrcode(@RequestParam Long id) throws Exception {
-        String url = commonPropertiesConfig.getDomainApp() + "/pages/meeting/permissions?id=" + id + "&type=0";
+        String url = commonPropertiesConfig.getDomainApp() + "/pages/meeting/detail?id=" + id + "&type=0";
         int width = 400;
         int height = 400;
         int margin = 1;

+ 19 - 3
src/main/java/com/xjrsoft/module/oa/service/impl/WfMeetingApplyServiceImpl.java

@@ -43,6 +43,7 @@ import com.xjrsoft.module.workflow.mapper.WorkflowFormRelationMapper;
 import com.xjrsoft.module.workflow.mapper.WorkflowRecordMapper;
 import com.xjrsoft.module.workflow.service.IWorkflowExtraService;
 import lombok.AllArgsConstructor;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.camunda.bpm.engine.history.HistoricProcessInstance;
@@ -110,6 +111,8 @@ public class WfMeetingApplyServiceImpl extends MPJBaseServiceImpl<WfMeetingApply
                 .selectAs(MeetingRoom::getCapacity, MeetingRoomListVo::getCapacity)
                 .selectAs(BaseOfficeBuild::getName, MeetingRoomListVo::getOfficeBuildIdCn)
                 .leftJoin(BaseOfficeBuild.class, BaseOfficeBuild::getId, MeetingRoom::getOfficeBuildId)
+                .like(StringUtils.isNotEmpty(dto.getName()), MeetingRoom::getName, dto.getName())
+                .like(StringUtils.isNotEmpty(dto.getOfficeBuildIdCn()), BaseOfficeBuild::getName, dto.getOfficeBuildIdCn())
                 .eq(MeetingRoom::getStatus, 1)
         ;
         List<MeetingRoomListVo> result = meetingRoomMapper.selectJoinList(MeetingRoomListVo.class, meetingRoomMPJLambdaWrapper);
@@ -137,7 +140,7 @@ public class WfMeetingApplyServiceImpl extends MPJBaseServiceImpl<WfMeetingApply
             meetingRoomListVo = new MeetingRoomListVo();
             for (WfMeetingApply wfMeetingApply : value) {
                 // 会议时间是否重叠
-                if (!(startTime.isBefore(wfMeetingApply.getMeetingApplyS()) || endTime.isAfter(wfMeetingApply.getMeetingApplyE()))) {
+                if (startTime.isBefore(wfMeetingApply.getMeetingApplyE()) && wfMeetingApply.getMeetingApplyS().isBefore(endTime)) {
                     isReservation = true;
                 }
                 reservationDetail.append(wfMeetingApply.getMeetingApplyS()).append("-").append(wfMeetingApply.getMeetingApplyE()).append("\r\n");
@@ -308,8 +311,7 @@ public class WfMeetingApplyServiceImpl extends MPJBaseServiceImpl<WfMeetingApply
                 }
                 LocalTime startTime = startTimeMap.get(id);
                 LocalTime endTime = endTimeMap.get(id);
-                if((oneStartTime.isAfter(startTime) && oneStartTime.isBefore(endTime))
-                        || (oneEndTime.isAfter(startTime) && oneEndTime.isBefore(endTime))){
+                if (oneStartTime.isBefore(endTime) && oneEndTime.isAfter(startTime)) {
                     adjustType = 0;
                 }
             }
@@ -439,6 +441,8 @@ public class WfMeetingApplyServiceImpl extends MPJBaseServiceImpl<WfMeetingApply
 
     @Override
     public MeetingMobileInfoVo mobileInfo(Long id) {
+        Long loginId = StpUtil.getLoginIdAsLong();
+
         MPJLambdaWrapper<WfMeetingApply> wfMeetingApplyPageVoMPJLambdaWrapper = new MPJLambdaWrapper<>();
         wfMeetingApplyPageVoMPJLambdaWrapper
                 .disableSubLogicDel()
@@ -469,6 +473,18 @@ public class WfMeetingApplyServiceImpl extends MPJBaseServiceImpl<WfMeetingApply
         MeetingMobileInfoVo info = this.selectJoinOne(MeetingMobileInfoVo.class, wfMeetingApplyPageVoMPJLambdaWrapper);
 
         if (ObjectUtils.isNotEmpty(info)) {
+            // 判断当前用户是否是当前会议的参与人
+            LambdaQueryWrapper<MeetingConferee> isMeetingConferee = new LambdaQueryWrapper<>();
+            isMeetingConferee
+                    .eq(MeetingConferee::getWfMeetingApplyId, id)
+                    .eq(MeetingConferee::getUserId, loginId)
+                    ;
+            List<MeetingConferee> meetingConfereeList = meetingConfereeMapper.selectList(isMeetingConferee);
+            // 既不是发起人也不是参与人
+            if(CollectionUtils.isEmpty(meetingConfereeList) && !info.getSponsorId().equals(loginId)){
+                return null;
+            }
+
             List<File> fileList = fileMapper.selectList(Wrappers.<File>query().lambda().eq(File::getFolderId, info.getPreMeetingInfoFileId()));
             info.setFileInfos(fileList);
 

+ 1 - 1
src/main/java/com/xjrsoft/module/oa/vo/TodayMeetingMobilePageVo.java

@@ -72,7 +72,7 @@ public class TodayMeetingMobilePageVo {
     private Integer checkInStatus;
 
     /**
-     * 签到状态(0:未签到,1:已签到
+     * 冲突状态(0:冲突,1:不冲突
      */
     @ApiModelProperty("冲突状态(0:冲突,1:不冲突)")
     private Integer conflictStatus;

+ 7 - 3
src/main/java/com/xjrsoft/module/organization/controller/DepartmentController.java

@@ -66,9 +66,13 @@ public class DepartmentController {
     @GetMapping(value = "/list")
     @ApiOperation(value = "机构列表(不分页)")
     @XjrLog(value = "获取不分页机构列表")
-    public R list() {
-        List<Department> list = departmentService.list(Wrappers.lambdaQuery(Department.class)
-                .select(Department.class, x -> VoToColumnUtil.fieldsToColumns(DepartmentListVo.class).contains(x.getProperty())));
+    public R list(DepartmentPageDto dto) {
+        List<Department> list = departmentService.list(
+                Wrappers.lambdaQuery(Department.class)
+                .select(Department.class, x -> VoToColumnUtil.fieldsToColumns(DepartmentListVo.class).contains(x.getProperty()))
+                        .eq(dto.getIsMajor() != null, Department::getIsMajor, dto.getIsMajor())
+                        .orderByAsc(Department::getId)
+        );
 
         List<DepartmentListVo> departmentListVos = BeanUtil.copyToList(list, DepartmentListVo.class);
         return R.ok(departmentListVos);

+ 10 - 0
src/main/java/com/xjrsoft/module/schedule/controller/ScheduleController.java

@@ -43,6 +43,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.time.DayOfWeek;
 import java.time.Duration;
 import java.time.LocalDateTime;
@@ -495,4 +496,13 @@ public class ScheduleController {
         return RT.ok(false);
     }
 
+    @PostMapping("/export-query")
+    @ApiOperation(value = "导出课表")
+    @SaCheckPermission("schedule:detail")
+    @XjrLog(value = "导出课表")
+    public ResponseEntity<byte[]> exportQuery(@Valid @RequestBody CourseTableExportQueryDto dto) throws IOException {
+        ByteArrayOutputStream bot = courseTableService.exportQuery(dto);
+        return RT.fileStream(bot.toByteArray(), "scheduleWeek" + ExcelTypeEnum.XLSX.getValue());
+    }
+
 }

+ 5 - 0
src/main/java/com/xjrsoft/module/schedule/dto/CourseTableDto.java

@@ -1,5 +1,6 @@
 package com.xjrsoft.module.schedule.dto;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
@@ -60,4 +61,8 @@ public class CourseTableDto implements Serializable {
     @ApiModelProperty("当前日期")
     private LocalDateTime scheduleDate;
 
+    @JsonIgnore
+    @ApiModelProperty("教师id")
+    private Integer isCustom;
+
 }

+ 37 - 0
src/main/java/com/xjrsoft/module/schedule/dto/CourseTableExportQueryDto.java

@@ -0,0 +1,37 @@
+package com.xjrsoft.module.schedule.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+
+/**
+ * @title: 寝室
+ * @Author dzx
+ * @Date: 2023-12-27
+ * @Version 1.0
+ */
+@Data
+public class CourseTableExportQueryDto {
+
+
+    @ApiModelProperty("学期id")
+    private Long semesterId;
+
+    @ApiModelProperty("课表类型(teacher:教师,class:班级,classroom:教室, all:总课表)")
+    private String courseType;
+
+    @ApiModelProperty("周次")
+    private Integer week;
+
+    @ApiModelProperty("年级id")
+    private List<Long> gradeIds;
+
+    @ApiModelProperty("专业部id")
+    private List<Long> deptIds;
+
+}

+ 278 - 0
src/main/java/com/xjrsoft/module/schedule/util/CourseTableExportQueryUtil.java

@@ -0,0 +1,278 @@
+package com.xjrsoft.module.schedule.util;
+
+import com.xjrsoft.module.courseTable.entity.ClassTime;
+import com.xjrsoft.module.schedule.vo.CourseDetailVo;
+import com.xjrsoft.module.schedule.vo.CourseTableVo;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author dzx
+ * @date 2025/4/9
+ */
+public class CourseTableExportQueryUtil {
+
+    /**
+     * 合并9列
+     * @param rowNumber 写入的行号
+     */
+    public void createThirdTitle(Workbook workbook, Sheet sheet, int rowNumber, String leftTitle, String rightTitle){
+        Font font = workbook.createFont();
+        font.setFontName("宋体");
+        //font.setColor(IndexedColors.RED.getIndex()); // 设置字体颜色为红色
+        font.setFontHeightInPoints((short) 14);
+
+        //设置样式
+        CellStyle cellStyle = workbook.createCellStyle();
+        cellStyle.setFont(font); // 将字体应用到样式
+        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        cellStyle.setAlignment(HorizontalAlignment.LEFT);
+        cellStyle.setBorderTop(BorderStyle.THIN);
+        cellStyle.setBorderBottom(BorderStyle.THIN);
+        cellStyle.setBorderLeft(BorderStyle.THIN);
+        cellStyle.setBorderRight(BorderStyle.THIN);
+
+        //创建一行
+        Row row = sheet.createRow(rowNumber);
+        Cell cell = row.createCell(0);
+        cell.setCellValue(leftTitle);
+        cell.setCellStyle(cellStyle);
+        for (int i = 1; i < 4; i ++){
+            cell = row.createCell(i);
+            cell.setCellStyle(cellStyle);
+        }
+        sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber, 0, 3));
+
+        cellStyle = workbook.createCellStyle();
+        cellStyle.setFont(font); // 将字体应用到样式
+        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        cellStyle.setAlignment(HorizontalAlignment.RIGHT);
+        cellStyle.setBorderTop(BorderStyle.THIN);
+        cellStyle.setBorderBottom(BorderStyle.THIN);
+        cellStyle.setBorderLeft(BorderStyle.THIN);
+        cellStyle.setBorderRight(BorderStyle.THIN);
+
+        cell = row.createCell(4);
+        cell.setCellValue(rightTitle);
+        cell.setCellStyle(cellStyle);
+        for (int i = 5; i < 9; i ++){
+            cell = row.createCell(i);
+            cell.setCellStyle(cellStyle);
+        }
+        sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber, 4, 8));
+    }
+
+
+    public void createContent(Workbook workbook, Sheet sheet, int rowNumber, CourseTableVo data, String courseType){
+        int firstRow = rowNumber;
+        Map<Integer, ClassTime> timeNumberMap = data.getClassTimeList().stream().collect(Collectors.toMap(ClassTime::getNumber, x -> x));
+        Map<String, List<CourseDetailVo>> timeNumberDataMap = data.getCourseList().stream().filter(x -> x.getTimeNumber() != null).collect(Collectors.groupingBy(CourseDetailVo::getTimeNumber));
+
+        List<Integer> weekList = new ArrayList<>();
+        weekList.add(1);
+        weekList.add(2);
+        weekList.add(3);
+        weekList.add(4);
+        weekList.add(5);
+        weekList.add(6);
+        weekList.add(7);
+        Collections.sort(weekList);
+
+        List<Integer> timeNumberList = data.getClassTimeList().stream().map(ClassTime::getNumber).collect(Collectors.toList());
+        Collections.sort(timeNumberList);
+
+        Map<Integer, Integer> timePeriodMap = data.getClassTimeList().stream().collect(Collectors.toMap(ClassTime::getNumber, ClassTime::getTimePeriod));
+
+        ArrayList<ArrayList<String>> dataList = new ArrayList<>();
+
+        for (Integer timeNumber : timeNumberList) {
+            ArrayList<String> rowData = new ArrayList<>();
+            Integer timePeriod = timePeriodMap.get(timeNumber);
+            String timePeriodStr = "";
+            if (timePeriod == 1) {
+                timePeriodStr = "上午";
+            } else if (timePeriod == 2) {
+                timePeriodStr = "下午";
+            } else if (timePeriod == 3) {
+                timePeriodStr = "晚上";
+            }
+            rowData.add(timePeriodStr);
+            ClassTime classTime = timeNumberMap.get(timeNumber);
+            String timeNumberStr = classTime.getShortName() + "\r\n" + classTime.getSummerStartTime() + "-" + classTime.getSummerEndTime();
+            rowData.add(timeNumberStr);
+            List<CourseDetailVo> list1 = timeNumberDataMap.get(timeNumber.toString());
+            if (list1 == null) {
+                for (Integer week : weekList) {
+                    rowData.add("");
+                }
+                dataList.add(rowData);
+                continue;
+            }
+            Map<Integer, List<CourseDetailVo>> weeksMap = list1.stream().collect(Collectors.groupingBy(CourseDetailVo::getWeeks));
+            for (Integer week : weekList) {
+                String content = "";
+                List<CourseDetailVo> list = weeksMap.get(week);
+                if (list != null && !list.isEmpty()) {
+                    if (list.size() == 1) {
+                        CourseDetailVo courseDetailVo = list.get(0);
+                        content += courseDetailVo.getCourseName() + "\r\n";
+                        if ("teacher".equals(courseType)) {
+                            if(courseDetailVo.getClassroomName() != null){
+                                content += courseDetailVo.getClassroomName() + "\r\n";
+                            }
+                            content += courseDetailVo.getClassName();
+                        } else if ("class".equals(courseType)) {
+                            if(courseDetailVo.getClassroomName() != null){
+                                content += courseDetailVo.getClassroomName() + "\r\n";
+                            }
+
+                            content += courseDetailVo.getTeacherName();
+                        } else if("classroom".equals(courseType)){
+                            content += courseDetailVo.getClassName() + "\r\n";
+                            content += courseDetailVo.getTeacherName();
+                        }
+                    } else {
+                        CourseDetailVo courseDetailVo = list.get(0);
+                        content += courseDetailVo.getCourseName() + "\r\n";
+                        if ("teacher".equals(courseType)) {
+                            if(courseDetailVo.getClassroomName() != null){
+                                content += courseDetailVo.getClassroomName() + "\r\n";
+                            }
+                            content += courseDetailVo.getClassName();
+                        } else if ("class".equals(courseType)) {
+                            if(courseDetailVo.getClassroomName() != null){
+                                content += courseDetailVo.getClassroomName() + "\r\n";
+                            }
+                            Set<String> classNames = list.stream().map(CourseDetailVo::getTeacherName).collect(Collectors.toSet());
+                            content += classNames.toString().replace("]", "").replace("[", "").replace(" ", "");
+                        } else if("classroom".equals(courseType)){
+                            content += courseDetailVo.getClassName() + "\r\n";
+                            content += courseDetailVo.getTeacherName();
+                        }
+                    }
+                }
+                rowData.add(content);
+            }
+            dataList.add(rowData);
+        }
+
+        Font font = workbook.createFont();
+        font.setFontName("宋体");
+        font.setFontHeightInPoints((short) 9);
+
+        // 创建一个单元格样式对象
+        CellStyle cellStyle = workbook.createCellStyle();
+        cellStyle.setFont(font); // 将字体应用到样式
+        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        cellStyle.setAlignment(HorizontalAlignment.CENTER);
+        cellStyle.setWrapText(true);
+        cellStyle.setBorderTop(BorderStyle.THIN);
+        cellStyle.setBorderBottom(BorderStyle.THIN);
+        cellStyle.setBorderLeft(BorderStyle.THIN);
+        cellStyle.setBorderRight(BorderStyle.THIN);
+
+        for (ArrayList<String> strings : dataList) {
+            Row row = sheet.createRow(rowNumber);
+            int cellNumber = 0;
+            for (String string : strings) {
+                Cell row1cell1 = row.createCell(cellNumber);
+                row1cell1.setCellValue(string);
+                row1cell1.setCellStyle(cellStyle);
+                cellNumber++;
+            }
+            rowNumber++;
+            row.setHeightInPoints(48);
+        }
+        //合并
+        Map<Integer, List<Integer>> collect = data.getClassTimeList().stream()
+                .collect(Collectors.groupingBy(ClassTime::getTimePeriod, Collectors.mapping(ClassTime::getNumber, Collectors.toList())));
+        for (Integer i : collect.keySet()) {
+            int lastRow = firstRow + collect.get(i).size() - 1;
+            if (lastRow == firstRow) {
+                continue;
+            }
+            sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, 0, 0));
+
+            firstRow = firstRow + collect.get(i).size();
+        }
+    }
+
+    public void createAllCourseTableTitle(Workbook workbook, Sheet sheet, List<ClassTime> classTimeList){
+        // 创建一个字体对象
+        Font font = workbook.createFont();
+        font.setBold(true);// 设置为粗体
+        font.setFontName("宋体");
+        //font.setColor(IndexedColors.RED.getIndex()); // 设置字体颜色为红色
+        font.setFontHeightInPoints((short) 12);
+
+        // 创建一个单元格样式对象
+        CellStyle cellStyle = workbook.createCellStyle();
+        cellStyle.setFont(font); // 将字体应用到样式
+        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        cellStyle.setAlignment(HorizontalAlignment.CENTER);
+        cellStyle.setBorderTop(BorderStyle.THIN);
+        cellStyle.setBorderBottom(BorderStyle.THIN);
+        cellStyle.setBorderLeft(BorderStyle.THIN);
+        cellStyle.setBorderRight(BorderStyle.THIN);
+        cellStyle.setWrapText(true);
+
+        int rowNumber = 0;
+        Row row = sheet.createRow(rowNumber);
+        row.setHeightInPoints(48);
+
+        List<String> weekList = new ArrayList<>();
+        weekList.add("周一");
+        weekList.add("周二");
+        weekList.add("周三");
+        weekList.add("周四");
+        weekList.add("周五");
+        weekList.add("周六");
+        weekList.add("周日");
+
+        int cellNumber = 0;
+        Cell row1cell1 = row.createCell(cellNumber);
+        row1cell1.setCellValue("班级");
+        row1cell1.setCellStyle(cellStyle);
+        sheet.setColumnWidth(0, 24 * 256);
+        sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber + 1, cellNumber, cellNumber));
+        cellNumber ++;
+
+        int size = classTimeList.size();
+
+        Row row2 = sheet.createRow(rowNumber + 1);
+        int cellNumber2 = 1;
+
+        for (String week : weekList) {
+            row1cell1 = row.createCell(cellNumber);
+            row1cell1.setCellValue(week);
+            row1cell1.setCellStyle(cellStyle);
+            int firstCol = cellNumber;
+            int lastCol = firstCol + size - 1;
+            sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber, firstCol, lastCol));
+            cellNumber = cellNumber + size;
+
+            for (ClassTime classTime : classTimeList) {
+                row1cell1 = row2.createCell(cellNumber2);
+                row1cell1.setCellValue(classTime.getShortName() + "\r\n" + classTime.getSummerStartTime() + "-" + classTime.getSummerEndTime());
+                row1cell1.setCellStyle(cellStyle);
+                sheet.setColumnWidth(cellNumber2, 24 * 256);
+                cellNumber2 ++;
+            }
+        }
+    }
+}

+ 1 - 1
src/main/java/com/xjrsoft/module/schedule/util/DataUtil.java

@@ -350,7 +350,7 @@ public class DataUtil {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         String spring = "春";
         String autumn = "秋";
-
+        //  秋1春2
         for (Map<String, Object> semesterMap : list) {
             BaseSemester semester = SqlRunnerAdapterUtil.convertMapToEntity(semesterMap, BaseSemester.class);
             String url = ScheduleUtil.apiUrl + "semester/Create";

+ 302 - 0
src/main/java/com/xjrsoft/module/student/controller/LeagueMembersManageController.java

@@ -0,0 +1,302 @@
+package com.xjrsoft.module.student.controller;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.support.ExcelTypeEnum;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import com.xjrsoft.common.constant.GlobalConstant;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.xjrsoft.common.enums.DeleteMark;
+import com.xjrsoft.common.exception.MyException;
+import com.xjrsoft.common.model.result.R;
+import com.xjrsoft.common.page.ConventPage;
+import com.xjrsoft.common.page.PageOutput;
+import com.xjrsoft.common.model.result.RT;
+import com.xjrsoft.common.utils.TreeUtil;
+import com.xjrsoft.common.utils.VoToColumnUtil;
+import com.xjrsoft.module.base.entity.BaseClass;
+import com.xjrsoft.module.base.entity.BaseGrade;
+import com.xjrsoft.module.databoard.vo.DistributionVo;
+import com.xjrsoft.module.oa.vo.MeetingRoomListVo;
+import com.xjrsoft.module.student.dto.AddLeagueMembersManageDto;
+import com.xjrsoft.module.student.dto.LeagueMembersExportQueryDto;
+import com.xjrsoft.module.student.dto.UpdateLeagueMembersManageDto;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import com.xjrsoft.common.annotation.XjrLog;
+
+import com.xjrsoft.module.student.dto.LeagueMembersManagePageDto;
+import com.xjrsoft.module.student.entity.BaseStudent;
+import com.xjrsoft.module.student.entity.BaseStudentSchoolRoll;
+import com.xjrsoft.module.student.entity.LeagueMembersManage;
+import com.xjrsoft.module.student.service.ILeagueMembersManageService;
+import com.xjrsoft.module.student.vo.ClassWithLMNumTreeVo;
+import com.xjrsoft.module.student.vo.LeagueMembersManagePageVo;
+
+import com.xjrsoft.module.student.vo.LeagueMembersManageVo;
+import com.xjrsoft.module.student.vo.StudentHonorsTreeVo;
+import com.xjrsoft.module.system.entity.DictionaryDetail;
+import com.xjrsoft.module.teacher.entity.XjrUser;
+import com.xjrsoft.module.textbook.dto.TextbookSubscriptionExportQueryListDto;
+import com.xjrsoft.module.textbook.vo.TextbookSubscriptionExportQueryListVo;
+import com.xjrsoft.module.veb.util.ImportExcelUtil;
+import com.xjrsoft.module.veb.vo.BaseCourseSubjectImportVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+/**
+ * @title: 团员管理
+ * @Author phoenix
+ * @Date: 2025-04-08
+ * @Version 1.0
+ */
+@RestController
+@RequestMapping("/student" + "/leagueMembersManage")
+@Api(value = "/student" + "/leagueMembersManage", tags = "团员管理代码")
+@AllArgsConstructor
+public class LeagueMembersManageController {
+
+    private final ILeagueMembersManageService leagueMembersManageService;
+
+    @GetMapping(value = "/tree")
+    @ApiOperation(value = "团员管理班级附带统计数量列表(树)")
+    @SaCheckPermission("leaguemembersmanage:detail")
+    @XjrLog(value = "团员管理班级附带统计数量列表(树)")
+    public RT<List<ClassWithLMNumTreeVo>> tree() {
+        MPJLambdaWrapper<LeagueMembersManage> leagueMembersManageMPJLambdaWrapper = new MPJLambdaWrapper<>();
+        leagueMembersManageMPJLambdaWrapper
+                .disableSubLogicDel()
+                .select(LeagueMembersManage::getId)
+                .leftJoin(BaseStudentSchoolRoll.class, BaseStudentSchoolRoll::getUserId, LeagueMembersManage::getUserId,
+                        wrappers -> wrappers
+                                .selectAs(BaseStudentSchoolRoll::getClassId, LeagueMembersManage::getCreateUserId) // 暂时将班级id放到createUserId字段
+                                .leftJoin(BaseClass.class, BaseClass::getId, BaseStudentSchoolRoll::getClassId,
+                                        wrap -> wrap
+                                                .selectAs(BaseClass::getName, LeagueMembersManage::getLeagueMembersNum) // 暂时将班级名称放到leagueMembersNum字段
+                                                .selectAs(BaseClass::getGradeId, LeagueMembersManage::getModifyUserId) // 暂时将年级id放到modifyUserId字段
+                                                .leftJoin(BaseGrade.class, BaseGrade::getId, BaseClass::getGradeId,
+                                                        wr -> wr
+                                                                .selectAs(BaseGrade::getName, LeagueMembersManage::getAddress) // 暂时将年级名称放到address字段
+                                                )
+                                )
+                                .eq(BaseStudentSchoolRoll::getDeleteMark, DeleteMark.NODELETE.getCode())
+                )
+                ;
+        List<LeagueMembersManage> leagueMembersManages = leagueMembersManageService.selectJoinList(LeagueMembersManage.class, leagueMembersManageMPJLambdaWrapper);
+
+        // 开始构建树并处理总数
+        List<ClassWithLMNumTreeVo> dataList = new ArrayList<>();
+        Map<Long, Integer> numMap = new LinkedHashMap<>();
+        int total = 0;
+        for (LeagueMembersManage l : leagueMembersManages){
+            // 当前班级已经处理
+            if(numMap.containsKey(l.getCreateUserId())){
+                numMap.put(l.getCreateUserId(), numMap.get(l.getCreateUserId()) + 1);
+                numMap.put(l.getModifyUserId(), numMap.get(l.getModifyUserId()) + 1);
+            } else {
+                numMap.put(l.getCreateUserId(), 1);
+                dataList.add(new ClassWithLMNumTreeVo(){{
+                    setId(Long.toString(l.getCreateUserId()));
+                    setName(l.getLeagueMembersNum());
+                    setParentId(Long.toString(l.getModifyUserId()));
+                    setLevel(3);
+                    setChildren(new ArrayList<>());
+                }});
+                // 当前年级已经处理
+                if(numMap.containsKey(l.getModifyUserId())){
+                    numMap.put(l.getModifyUserId(), numMap.get(l.getModifyUserId()) + 1);
+                }else {
+                    numMap.put(l.getModifyUserId(), 1);
+                    dataList.add(new ClassWithLMNumTreeVo(){{
+                        setId(Long.toString(l.getModifyUserId()));
+                        setName(l.getAddress());
+                        setParentId("8888");
+                        setLevel(2);
+                        setChildren(new ArrayList<>());
+                    }});
+                }
+            }
+            total++;
+        }
+
+        // 为每一个节点匹配数字
+        for (ClassWithLMNumTreeVo vo : dataList){
+            Integer num = numMap.get(Long.parseLong(vo.getId()));
+            if(ObjectUtils.isEmpty(num)){
+                num = 0;
+            }
+            vo.setName(vo.getName() + "(" + num + "人)" );
+            vo.setNum(num);
+        }
+
+        dataList.sort(Comparator.comparing(ClassWithLMNumTreeVo::getName).reversed());
+
+        int finalTotal = total;
+        dataList.add(new ClassWithLMNumTreeVo(){{
+            setId("8888");
+            setName("总览" + "(" + finalTotal + "人)" );
+            setParentId("0");
+            setNum(finalTotal);
+            setLevel(1);
+            setChildren(new ArrayList<>());
+        }});
+
+        List<ClassWithLMNumTreeVo> treeVoList = TreeUtil.build(dataList);
+
+        return RT.ok(treeVoList);
+    }
+
+    @GetMapping(value = "/page")
+    @ApiOperation(value = "团员管理列表(分页)")
+    @SaCheckPermission("leaguemembersmanage:detail")
+    @XjrLog(value = "团员管理列表(分页)")
+    public RT<PageOutput<LeagueMembersManagePageVo>> page(@Valid LeagueMembersManagePageDto dto) {
+        MPJLambdaWrapper<LeagueMembersManage> queryWrapper = new MPJLambdaWrapper<>();
+        queryWrapper
+                .disableSubLogicDel()
+                .select(LeagueMembersManage::getId)
+                .select(LeagueMembersManage.class, x -> VoToColumnUtil.fieldsToColumns(LeagueMembersManagePageVo.class).contains(x.getProperty()))
+                .innerJoin(XjrUser.class, XjrUser::getId, LeagueMembersManage::getUserId,
+                        wrappers -> wrappers
+                                .selectAs(XjrUser::getName, LeagueMembersManagePageVo::getStudentName)
+                                .selectAs(XjrUser::getCredentialNumber, LeagueMembersManagePageVo::getIdCard)
+                                .selectAs(XjrUser::getMobile, LeagueMembersManagePageVo::getPhone)
+                                .leftJoin(DictionaryDetail.class, DictionaryDetail::getCode, XjrUser::getGender,
+                                        wrap -> wrap
+                                                .selectAs(DictionaryDetail::getName, LeagueMembersManagePageVo::getGenderCn)
+                                )
+                                .like(StringUtils.isNotEmpty(dto.getStudentName()), XjrUser::getName, dto.getStudentName())
+                                .like(StringUtils.isNotEmpty(dto.getIdCard()), XjrUser::getCredentialNumber, dto.getIdCard())
+                                .eq(XjrUser::getDeleteMark, DeleteMark.NODELETE.getCode())
+                )
+                .leftJoin(BaseStudentSchoolRoll.class, BaseStudentSchoolRoll::getUserId, LeagueMembersManage::getUserId,
+                        wrappers -> wrappers
+                                .selectAs(BaseStudentSchoolRoll::getArchivesStatus, LeagueMembersManagePageVo::getArchivesStatus)
+                                .selectAs(BaseStudentSchoolRoll::getClassId, LeagueMembersManagePageVo::getClassId)
+                                .leftJoin(DictionaryDetail.class, DictionaryDetail::getCode, BaseStudentSchoolRoll::getArchivesStatus,
+                                        wrap -> wrap
+                                                .selectAs(DictionaryDetail::getName, LeagueMembersManagePageVo::getStatusCn)
+                                )
+                                .leftJoin(BaseClass.class, BaseClass::getId, BaseStudentSchoolRoll::getClassId,
+                                        wrap -> wrap
+                                                .selectAs(BaseClass::getName, LeagueMembersManagePageVo::getClassName)
+                                                .selectAs(BaseClass::getGradeId, LeagueMembersManagePageVo::getGradeId)
+                                                .leftJoin(XjrUser.class, XjrUser::getId, BaseClass::getTeacherId,
+                                                        wr -> wr
+                                                                .selectAs(XjrUser::getName, LeagueMembersManagePageVo::getTeacherName)
+                                                )
+                                                .leftJoin(BaseGrade.class, BaseGrade::getId, BaseClass::getGradeId,
+                                                        wr -> wr
+                                                                .selectAs(BaseGrade::getName, LeagueMembersManagePageVo::getGradeName)
+                                                )
+                                                .eq(ObjectUtils.isNotEmpty(dto.getNodeId()) && ObjectUtils.isNotEmpty(dto.getLevel()) && dto.getLevel() == 2, BaseClass::getGradeId, dto.getNodeId())
+                                )
+                                .like(StringUtils.isNotEmpty(dto.getArchivesStatus()), BaseStudentSchoolRoll::getArchivesStatus, dto.getArchivesStatus())
+                                .eq(ObjectUtils.isNotEmpty(dto.getNodeId()) && ObjectUtils.isNotEmpty(dto.getLevel()) && dto.getLevel() == 3, BaseStudentSchoolRoll::getClassId, dto.getNodeId())
+                                .eq(BaseStudentSchoolRoll::getDeleteMark, DeleteMark.NODELETE.getCode())
+                )
+                .leftJoin(BaseStudent.class, BaseStudent::getUserId, LeagueMembersManage::getUserId,
+                        wrappers -> wrappers
+                                .leftJoin(DictionaryDetail.class, DictionaryDetail::getCode, BaseStudent::getNation,
+                                        wrap -> wrap
+                                                .selectAs(DictionaryDetail::getName, LeagueMembersManagePageVo::getNation)
+                                )
+                                .eq(BaseStudent::getDeleteMark, DeleteMark.NODELETE.getCode())
+                )
+                .like(StringUtils.isNotEmpty(dto.getLeagueMembersNum()), LeagueMembersManage::getLeagueMembersNum, dto.getLeagueMembersNum())
+                .ge(ObjectUtils.isNotEmpty(dto.getJoinTimeStart()), LeagueMembersManage::getJoinTime, dto.getJoinTimeStart())
+                .le(ObjectUtils.isNotEmpty(dto.getJoinTimeEnd()), LeagueMembersManage::getJoinTime, dto.getJoinTimeEnd())
+                .ge(ObjectUtils.isNotEmpty(dto.getTransferTimeStart()), LeagueMembersManage::getTransferTime, dto.getTransferTimeStart())
+                .le(ObjectUtils.isNotEmpty(dto.getTransferTimeEnd()), LeagueMembersManage::getTransferTime, dto.getTransferTimeEnd())
+                .orderByAsc(BaseStudentSchoolRoll::getArchivesStatus)
+        ;
+
+        IPage<LeagueMembersManagePageVo> page = leagueMembersManageService.selectJoinListPage(ConventPage.getPage(dto), LeagueMembersManagePageVo.class, queryWrapper);
+        PageOutput<LeagueMembersManagePageVo> pageOutput = ConventPage.getPageOutput(page, LeagueMembersManagePageVo.class);
+        return RT.ok(pageOutput);
+    }
+
+    @PostMapping
+    @ApiOperation(value = "新增团员管理")
+    @SaCheckPermission("leaguemembersmanage:add")
+    @XjrLog(value = "新增团员管理")
+    public RT<Boolean> add(@Valid @RequestBody AddLeagueMembersManageDto dto) {
+        return RT.ok(leagueMembersManageService.addRubAndHand(dto));
+    }
+
+    @PutMapping
+    @ApiOperation(value = "修改团员管理")
+    @SaCheckPermission("leaguemembersmanage:edit")
+    @XjrLog(value = "修改团员管理")
+    public RT<Boolean> update(@Valid @RequestBody UpdateLeagueMembersManageDto dto) {
+        LeagueMembersManage leagueMembersManage = BeanUtil.toBean(dto, LeagueMembersManage.class);
+        return RT.ok(leagueMembersManageService.updateById(leagueMembersManage));
+    }
+
+    @DeleteMapping
+    @ApiOperation(value = "删除团员管理")
+    @SaCheckPermission("leaguemembersmanage:delete")
+    @XjrLog(value = "删除团员管理")
+    public RT<Boolean> delete(@Valid @RequestBody List<Long> ids) {
+        return RT.ok(leagueMembersManageService.deleteRubAndHand(ids));
+    }
+
+    @PostMapping("/league-members-template-download")
+    @ApiOperation(value = "团员信息导入模板下载")
+    @XjrLog(value = "团员信息导入模板下载")
+    public ResponseEntity<byte[]> leagueMembersTemplateDownload() throws IOException {
+//    @GetMapping("/league-members-import-template")
+//    @ApiOperation(value = "团员信息导入模板下载")
+//    @XjrLog(value = "团员信息导入模板下载")
+//    public ResponseEntity<byte[]> leagueMembersImportTemplate() throws IOException {
+        ByteArrayOutputStream bot = leagueMembersManageService.leagueMembersTemplateDownload();
+
+        String fileName = "团员信息导入模板";
+        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
+        return R.fileStream(bot.toByteArray(), fileName + System.currentTimeMillis() + ExcelTypeEnum.XLSX.getValue());
+    }
+
+    @PostMapping("/import")
+    @ApiOperation(value = "团员信息导入")
+    @XjrLog(value = "团员信息导入", saveRequestData = false, saveResponseData = true)
+    public RT<Boolean> leagueMembersImport(@Valid @RequestParam MultipartFile file) throws IOException, IllegalAccessException {
+        String result = leagueMembersManageService.leagueMembersImport(file);
+        if (!result.isEmpty()) {
+            throw new MyException(result);
+        }
+        return RT.ok("全部成功", true);
+    }
+
+    @PostMapping("/league-members-export-query")
+    @ApiOperation(value = "团员信息条件导出")
+    @XjrLog(value = "团员信息条件导出")
+    public ResponseEntity<byte[]> leagueMembersExportQuery(@Valid @RequestBody LeagueMembersExportQueryDto dto) throws IOException {
+//    @GetMapping("/league-members-export-query")
+//    @ApiOperation(value = "团员信息条件导出")
+//    @XjrLog(value = "团员信息条件导出")
+//    public ResponseEntity<byte[]> leagueMembersExportQuery(@Valid LeagueMembersExportQueryDto dto) throws IOException {
+        ByteArrayOutputStream bot = leagueMembersManageService.textbookSubscriptionExportQuery(dto);
+
+        String fileName = "团员信息列表";
+        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
+        return RT.fileStream(bot.toByteArray(), fileName +  System.currentTimeMillis() + ExcelTypeEnum.XLSX.getValue());
+    }
+}

+ 62 - 0
src/main/java/com/xjrsoft/module/student/dto/AddLeagueMembersManageDto.java

@@ -0,0 +1,62 @@
+package com.xjrsoft.module.student.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.io.Serializable;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Date;
+
+
+
+/**
+* @title: 团员管理
+* @Author phoenix
+* @Date: 2025-04-08
+* @Version 1.0
+*/
+@Data
+public class AddLeagueMembersManageDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+    * 学生主键id(xjr_user)
+    */
+    @ApiModelProperty("学生主键id(xjr_user)")
+    private Long userId;
+    /**
+    * 团员编号
+    */
+    @ApiModelProperty("团员编号")
+    private String leagueMembersNum;
+    /**
+    * 常住地址
+    */
+    @ApiModelProperty("常住地址")
+    private String address;
+    /**
+    * 加入时间
+    */
+    @ApiModelProperty("加入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate joinTime;
+    /**
+    * 转入时间
+    */
+    @ApiModelProperty("转入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate transferTime;
+    /**
+    * 备注
+    */
+    @ApiModelProperty("备注")
+    private String remark;
+
+}

+ 58 - 0
src/main/java/com/xjrsoft/module/student/dto/LeagueMembersExportQueryDto.java

@@ -0,0 +1,58 @@
+package com.xjrsoft.module.student.dto;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ContentStyle;
+import com.xjrsoft.common.annotation.Required;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+
+/**
+ * @title: 团员信息有参导出入参
+ * @Author phoenix
+ * @Date: 2025年04月08日
+ * @Version 1.0
+ */
+@Data
+public class LeagueMembersExportQueryDto implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("班级年级树节点主键id")
+    private Long nodeId;
+
+    @ApiModelProperty("节点层级,1:总览,2:年级,3:班级")
+    private Integer level;
+
+    @ApiModelProperty("学生姓名")
+    private String studentName;
+
+    @ApiModelProperty("身份证号")
+    private String idCard;
+
+    @ApiModelProperty("团员编号")
+    private String leagueMembersNum;
+
+    @ApiModelProperty("学籍状态(xjr_dictionary_item[archives_status])")
+    private String archivesStatus;
+
+    @ApiModelProperty("加入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate joinTimeStart;
+
+    @ApiModelProperty("加入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate joinTimeEnd;
+
+    @ApiModelProperty("转入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate transferTimeStart;
+
+    @ApiModelProperty("转入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate transferTimeEnd;
+}

+ 63 - 0
src/main/java/com/xjrsoft/module/student/dto/LeagueMembersImportDto.java

@@ -0,0 +1,63 @@
+package com.xjrsoft.module.student.dto;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ContentStyle;
+import com.xjrsoft.common.annotation.Required;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDate;
+
+
+/**
+ * @title: 团员信息导入映射实体对象
+ * @Author phoenix
+ * @Date: 2025年04月08日
+ * @Version 1.0
+ */
+@Data
+public class LeagueMembersImportDto {
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("姓名")
+    @ApiModelProperty("姓名")
+    @Required
+    private String studentName;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("身份证号")
+    @ApiModelProperty("身份证号")
+    @Required
+    private String idCard;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("常住地址")
+    @ApiModelProperty("常住地址")
+    @Required(value = false)
+    private String address;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("团员编号")
+    @ApiModelProperty("团员编号")
+    @Required(value = false)
+    private String leagueMembersNum;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("入团时间")
+    @ApiModelProperty("入团时间")
+    @Required(value = false)
+    private String joinTime;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("转入时间")
+    @ApiModelProperty("转入时间")
+    @Required(value = false)
+    private String transferTime;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("备注")
+    @ApiModelProperty("备注")
+    @Required(value = false)
+    private String remark;
+}

+ 58 - 0
src/main/java/com/xjrsoft/module/student/dto/LeagueMembersManagePageDto.java

@@ -0,0 +1,58 @@
+package com.xjrsoft.module.student.dto;
+
+import com.xjrsoft.common.page.PageInput;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.Date;
+
+
+/**
+* @title: 团员管理分页查询入参
+* @Author phoenix
+* @Date: 2025-04-08
+* @Version 1.0
+*/
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class LeagueMembersManagePageDto extends PageInput {
+    @ApiModelProperty("班级年级树节点主键id")
+    private Long nodeId;
+
+    @ApiModelProperty("节点层级,1:总览,2:年级,3:班级")
+    private Integer level;
+
+    @ApiModelProperty("学生姓名")
+    private String studentName;
+
+    @ApiModelProperty("身份证号")
+    private String idCard;
+
+    @ApiModelProperty("团员编号")
+    private String leagueMembersNum;
+
+    @ApiModelProperty("学籍状态(xjr_dictionary_item[archives_status])")
+    private String archivesStatus;
+
+    @ApiModelProperty("加入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate joinTimeStart;
+
+    @ApiModelProperty("加入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate joinTimeEnd;
+
+    @ApiModelProperty("转入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate transferTimeStart;
+
+    @ApiModelProperty("转入时间")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private LocalDate transferTimeEnd;
+}

+ 32 - 0
src/main/java/com/xjrsoft/module/student/dto/UpdateLeagueMembersManageDto.java

@@ -0,0 +1,32 @@
+package com.xjrsoft.module.student.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.io.Serializable;
+
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import java.util.List;
+import java.util.Date;
+
+
+
+/**
+* @title: 团员管理
+* @Author phoenix
+* @Date: 2025-04-08
+* @Version 1.0
+*/
+@Data
+public class UpdateLeagueMembersManageDto extends AddLeagueMembersManageDto {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+    * 主键编号
+    */
+    @ApiModelProperty("主键编号")
+    private Long id;
+}

+ 109 - 0
src/main/java/com/xjrsoft/module/student/entity/LeagueMembersManage.java

@@ -0,0 +1,109 @@
+package com.xjrsoft.module.student.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.github.yulichang.annotation.EntityMapping;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import java.io.Serializable;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Date;
+
+
+/**
+* @title: 团员管理
+* @Author phoenix
+* @Date: 2025-04-08
+* @Version 1.0
+*/
+@Data
+@TableName("league_members_manage")
+@ApiModel(value = "league_members_manage", description = "团员管理")
+public class LeagueMembersManage implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+    * 主键编号
+    */
+    @ApiModelProperty("主键编号")
+    @TableId
+    private Long id;
+    /**
+    * 创建人
+    */
+    @ApiModelProperty("创建人")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUserId;
+    /**
+    * 创建时间
+    */
+    @ApiModelProperty("创建时间")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createDate;
+    /**
+    * 修改人
+    */
+    @ApiModelProperty("修改人")
+    @TableField(fill = FieldFill.UPDATE)
+    private Long modifyUserId;
+    /**
+    * 修改时间
+    */
+    @ApiModelProperty("修改时间")
+    @TableField(fill = FieldFill.UPDATE)
+    private Date modifyDate;
+    /**
+    * 删除标记
+    */
+    @ApiModelProperty("删除标记")
+    @TableField(fill = FieldFill.INSERT)
+    @TableLogic
+    private Integer deleteMark;
+    /**
+    * 有效标志
+    */
+    @ApiModelProperty("有效标志")
+    @TableField(fill = FieldFill.INSERT)
+    private Integer enabledMark;
+    /**
+    * 学生主键id(xjr_user)
+    */
+    @ApiModelProperty("学生主键id(xjr_user)")
+    private Long userId;
+    /**
+    * 团员编号
+    */
+    @ApiModelProperty("团员编号")
+    private String leagueMembersNum;
+    /**
+    * 常住地址
+    */
+    @ApiModelProperty("常住地址")
+    private String address;
+    /**
+    * 加入时间
+    */
+    @ApiModelProperty("加入时间")
+    private LocalDate joinTime;
+    /**
+    * 转入时间
+    */
+    @ApiModelProperty("转入时间")
+    private LocalDate transferTime;
+    /**
+    * 备注
+    */
+    @ApiModelProperty("备注")
+    private String remark;
+
+
+}

+ 17 - 0
src/main/java/com/xjrsoft/module/student/mapper/LeagueMembersManageMapper.java

@@ -0,0 +1,17 @@
+package com.xjrsoft.module.student.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.github.yulichang.base.MPJBaseMapper;
+import com.xjrsoft.module.student.entity.LeagueMembersManage;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+* @title: 团员管理
+* @Author phoenix
+* @Date: 2025-04-08
+* @Version 1.0
+*/
+@Mapper
+public interface LeagueMembersManageMapper extends MPJBaseMapper<LeagueMembersManage> {
+
+}

+ 33 - 0
src/main/java/com/xjrsoft/module/student/service/ILeagueMembersManageService.java

@@ -0,0 +1,33 @@
+package com.xjrsoft.module.student.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.github.yulichang.base.MPJBaseService;
+import com.xjrsoft.module.student.dto.AddLeagueMembersManageDto;
+import com.xjrsoft.module.student.dto.LeagueMembersExportQueryDto;
+import com.xjrsoft.module.student.entity.LeagueMembersManage;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+/**
+* @title: 团员管理
+* @Author phoenix
+* @Date: 2025-04-08
+* @Version 1.0
+*/
+
+public interface ILeagueMembersManageService extends MPJBaseService<LeagueMembersManage> {
+
+    Boolean addRubAndHand(AddLeagueMembersManageDto dto);
+
+    Boolean deleteRubAndHand(List<Long> ids);
+
+    ByteArrayOutputStream leagueMembersTemplateDownload() throws IOException;
+
+    String leagueMembersImport(MultipartFile file) throws IOException, IllegalAccessException;
+
+    ByteArrayOutputStream textbookSubscriptionExportQuery(LeagueMembersExportQueryDto dto) throws IOException;
+}

+ 4 - 0
src/main/java/com/xjrsoft/module/student/service/impl/BaseStudentSchoolRollServiceImpl.java

@@ -138,6 +138,10 @@ public class BaseStudentSchoolRollServiceImpl extends MPJBaseServiceImpl<BaseStu
                         2
                 );
             }
+            if((dto.getArchivesStatus() != null && !ArchivesStatusEnum.FB2901.getCode().equals(dto.getArchivesStatus())
+                    || (dto.getStduyStatus() != null && !StudyStatusEnum.InResidence.getCode().equals(dto.getStduyStatus())))){
+                roomBedMapper.clearBedInfoByStudentUserId(id, StpUtil.getLoginIdAsLong());
+            }
 
             baseStudentSchoolRollMapper.updateInfoByUserId(dto, id);
         }

+ 394 - 0
src/main/java/com/xjrsoft/module/student/service/impl/LeagueMembersManageServiceImpl.java

@@ -0,0 +1,394 @@
+package com.xjrsoft.module.student.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.bean.BeanUtil;
+import com.alibaba.excel.EasyExcel;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.github.yulichang.base.MPJBaseServiceImpl;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import com.xjrsoft.common.enums.DeleteMark;
+import com.xjrsoft.common.utils.VoToColumnUtil;
+import com.xjrsoft.module.base.entity.BaseClass;
+import com.xjrsoft.module.generator.entity.ImportConfig;
+import com.xjrsoft.module.organization.entity.User;
+import com.xjrsoft.module.organization.mapper.UserMapper;
+import com.xjrsoft.module.student.dto.AddLeagueMembersManageDto;
+import com.xjrsoft.module.student.dto.LeagueMembersExportQueryDto;
+import com.xjrsoft.module.student.dto.LeagueMembersImportDto;
+import com.xjrsoft.module.student.entity.BaseStudent;
+import com.xjrsoft.module.student.entity.BaseStudentSchoolRoll;
+import com.xjrsoft.module.student.entity.LeagueMembersManage;
+import com.xjrsoft.module.student.mapper.BaseStudentMapper;
+import com.xjrsoft.module.student.mapper.LeagueMembersManageMapper;
+import com.xjrsoft.module.student.service.ILeagueMembersManageService;
+import com.xjrsoft.module.student.vo.LeagueMembersExportQueryListVo;
+import com.xjrsoft.module.student.vo.LeagueMembersManagePageVo;
+import com.xjrsoft.module.system.entity.DictionaryDetail;
+import com.xjrsoft.module.teacher.entity.XjrUser;
+import com.xjrsoft.module.veb.util.ImportExcelUtil;
+import com.xjrsoft.module.veb.vo.InternshipPlanImportVo;
+import lombok.AllArgsConstructor;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import org.springframework.web.multipart.MultipartFile;
+
+import static com.xjrsoft.module.veb.util.ImportExcelUtil.isRequiredFieldsFilled;
+
+/**
+* @title: 团员管理
+* @Author phoenix
+* @Date: 2025-04-08
+* @Version 1.0
+*/
+@Service
+@AllArgsConstructor
+public class LeagueMembersManageServiceImpl extends MPJBaseServiceImpl<LeagueMembersManageMapper, LeagueMembersManage> implements ILeagueMembersManageService {
+
+    private final BaseStudentMapper baseStudentMapper;
+
+    private final UserMapper userMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean addRubAndHand(AddLeagueMembersManageDto dto) {
+        LeagueMembersManage leagueMembersManage = BeanUtil.toBean(dto, LeagueMembersManage.class);
+
+        // 更改原有的团员状态
+        LambdaUpdateWrapper<BaseStudent> baseStudentLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+        baseStudentLambdaUpdateWrapper
+                .eq(BaseStudent::getUserId, dto.getUserId())
+                ;
+        BaseStudent baseStudent = new BaseStudent();
+        baseStudent.setPoliticalState("FB1204");
+        baseStudentMapper.update(baseStudent, baseStudentLambdaUpdateWrapper);
+
+        return this.save(leagueMembersManage);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean deleteRubAndHand(List<Long> ids) {
+        // 更改原有的团员状态
+        List<LeagueMembersManage> leagueMembersManages = this.listByIds(ids);
+        BaseStudent baseStudent = new BaseStudent();
+        baseStudent.setPoliticalState("FB1214");
+        LambdaUpdateWrapper<BaseStudent> baseStudentLambdaUpdateWrapper;
+        for(LeagueMembersManage leagueMembersManage : leagueMembersManages){
+            baseStudentLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+            baseStudentLambdaUpdateWrapper
+                    .eq(BaseStudent::getUserId, leagueMembersManage.getUserId())
+            ;
+            baseStudentMapper.update(baseStudent, baseStudentLambdaUpdateWrapper);
+        }
+        return this.removeBatchByIds(ids);
+    }
+
+    @Override
+    public ByteArrayOutputStream leagueMembersTemplateDownload() throws IOException {
+        // 开始写入
+        Workbook workbook = new XSSFWorkbook();
+        // 创建一个工作表(sheet)
+        String sheetName = "sheet1";
+        Sheet sheet = workbook.createSheet(sheetName);
+
+        LeagueMembersImportDto obj = new LeagueMembersImportDto();
+        List<ImportConfig> importConfigs = ImportExcelUtil.allFields(obj);
+
+        // 大标题
+        ImportExcelUtil.createBigHead(workbook, sheet, "团员导入模板", 0, importConfigs.size() - 1);
+
+        // 提示必填
+        String content = "说明:黄底红色背景为必填项,白底黑字为非必填项,导入时请将实例数据删除,避免导入失败!";
+        ImportExcelUtil.createCautionHead(workbook, sheet, 1, content, importConfigs.size() - 1, 12, IndexedColors.RED.getIndex());
+
+        // 表头
+        ImportExcelUtil.createHead(workbook, sheet, importConfigs, IndexedColors.YELLOW.getIndex(), IndexedColors.RED.getIndex(), 2);
+
+        //写入文件
+        ByteArrayOutputStream bot = new ByteArrayOutputStream();
+        workbook.write(bot);
+        return bot;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String leagueMembersImport(MultipartFile file) throws IOException, IllegalAccessException {
+        List<LeagueMembersImportDto> excelDataList = EasyExcel.read(file.getInputStream()).headRowNumber(3).head(LeagueMembersImportDto.class).sheet().doReadSync();
+        StringBuilder sb = new StringBuilder();
+
+        // 用户根据身份证号映射
+        List<User> users = userMapper.selectList(
+                Wrappers
+                        .lambdaQuery(User.class)
+                        .eq(User::getDeleteMark, DeleteMark.NODELETE.getCode())
+        );
+        Map<String, User> userByCredentialNumberMap = users.stream()
+                .filter(u -> StringUtils.isNotEmpty(u.getCredentialNumber()))
+                .collect(Collectors.toMap(User::getCredentialNumber, u -> u,  (o1, o2) -> o1));
+
+        // 已经维护的团员信息身份证号映射
+        MPJLambdaWrapper<LeagueMembersManage> leagueMembersManageMPJLambdaWrapper = new MPJLambdaWrapper<>();
+        leagueMembersManageMPJLambdaWrapper
+                .select(LeagueMembersManage::getId)
+                .selectAs(XjrUser::getCredentialNumber, LeagueMembersManage::getLeagueMembersNum) // 暂时将身份证号存在团员编号字段中
+                .innerJoin(XjrUser.class, XjrUser::getId, LeagueMembersManage::getUserId)
+                ;
+        List<LeagueMembersManage> olds = this.selectJoinList(LeagueMembersManage.class, leagueMembersManageMPJLambdaWrapper);
+        Map<String, Long> idByCredentialNumberMap = olds.stream()
+                .filter(u -> StringUtils.isNotEmpty(u.getLeagueMembersNum()))
+                .collect(Collectors.toMap(LeagueMembersManage::getLeagueMembersNum, LeagueMembersManage::getId,  (o1, o2) -> o1));
+
+        List<LeagueMembersManage> leagueMembersManages = new ArrayList<>();
+        LeagueMembersManage leagueMembersManage;
+        Date nowDate = new Date();
+        // 定义输入格式 (YYYYMM)
+        DateTimeFormatter joinTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM");
+        // 定义输入格式 (YYYY.MM)
+        DateTimeFormatter transferFormatter = DateTimeFormatter.ofPattern("yyyy.MM");
+        for (int i = 0; i < excelDataList.size(); i++) {
+            LeagueMembersImportDto dto = excelDataList.get(i);
+            leagueMembersManage = new LeagueMembersManage();
+
+            // 判断必填字段
+            if (ImportExcelUtil.isRequiredFieldsFilled(dto,
+                    sb,
+                    i + 4)) {
+                return sb.toString();
+            }
+
+            // 判断身份证对应的用户是否存在
+            User user = userByCredentialNumberMap.get(dto.getIdCard());
+            if(ObjectUtils.isEmpty(user)){
+                sb.append("第").append(i + 4).append("行数据的用户不存在!");
+                return sb.toString();
+            }
+
+            // 判断团员编号值是否为整数
+            if(StringUtils.isNotEmpty(dto.getLeagueMembersNum()) && !StringUtils.isNumeric(dto.getLeagueMembersNum())){
+                sb.append("第").append(i + 4).append("行数据的团员编号包含非数字字符!");
+                return sb.toString();
+            }
+
+            // 处理入团时间
+            if(StringUtils.isNotEmpty(dto.getJoinTime())){
+                // 将字符串解析为 YearMonth
+                YearMonth yearMonth = YearMonth.parse(dto.getJoinTime(), joinTimeFormatter);
+
+                // 设置为当月的第一天并转换为 LocalDate
+                LocalDate localDate = yearMonth.atDay(1);
+
+                leagueMembersManage.setJoinTime(localDate);
+            }
+
+            // 处理转入时间
+            if(StringUtils.isNotEmpty(dto.getTransferTime())){
+                // 将字符串解析为 YearMonth
+                YearMonth yearMonth = YearMonth.parse(dto.getTransferTime(), transferFormatter);
+
+                // 设置为当月的第一天并转换为 LocalDate
+                LocalDate localDate = yearMonth.atDay(1);
+
+                leagueMembersManage.setTransferTime(localDate);
+            }
+
+            leagueMembersManage.setUserId(user.getId());
+            leagueMembersManage.setLeagueMembersNum(dto.getLeagueMembersNum());
+            leagueMembersManage.setAddress(dto.getAddress());
+            leagueMembersManage.setRemark(dto.getRemark());
+
+            leagueMembersManage.setCreateDate(nowDate);
+            leagueMembersManage.setCreateUserId(StpUtil.getLoginIdAsLong());
+
+            Long leagueMembersManageId = idByCredentialNumberMap.get(dto.getIdCard());
+            if(ObjectUtils.isNotEmpty(leagueMembersManageId)){
+                leagueMembersManage.setId(leagueMembersManageId);
+            }
+
+            leagueMembersManages.add(leagueMembersManage);
+        }
+
+        if(CollectionUtils.isNotEmpty(leagueMembersManages)){
+            this.saveOrUpdateBatch(leagueMembersManages);
+        }
+
+        return sb.toString();
+    }
+
+    @Override
+    public ByteArrayOutputStream textbookSubscriptionExportQuery(LeagueMembersExportQueryDto dto) throws IOException {
+        MPJLambdaWrapper<LeagueMembersManage> leagueMembersManageMPJLambdaWrapper = new MPJLambdaWrapper<>();
+        leagueMembersManageMPJLambdaWrapper
+                .disableSubLogicDel()
+                .select(LeagueMembersManage::getId)
+                .select(LeagueMembersManage.class, x -> VoToColumnUtil.fieldsToColumns(LeagueMembersExportQueryListVo.class).contains(x.getProperty()))
+                .innerJoin(XjrUser.class, XjrUser::getId, LeagueMembersManage::getUserId,
+                        wrappers -> wrappers
+                                .selectAs(XjrUser::getName, LeagueMembersExportQueryListVo::getStudentName)
+                                .selectAs(XjrUser::getCredentialNumber, LeagueMembersExportQueryListVo::getIdCard)
+                                .selectAs(XjrUser::getMobile, LeagueMembersExportQueryListVo::getPhone)
+                                .leftJoin(DictionaryDetail.class, DictionaryDetail::getCode, XjrUser::getGender,
+                                        wrap -> wrap
+                                                .selectAs(DictionaryDetail::getName, LeagueMembersExportQueryListVo::getGenderCn)
+                                )
+                                .like(StringUtils.isNotEmpty(dto.getStudentName()), XjrUser::getName, dto.getStudentName())
+                                .like(StringUtils.isNotEmpty(dto.getIdCard()), XjrUser::getCredentialNumber, dto.getIdCard())
+                                .eq(XjrUser::getDeleteMark, DeleteMark.NODELETE.getCode())
+                )
+                .leftJoin(BaseStudentSchoolRoll.class, BaseStudentSchoolRoll::getUserId, LeagueMembersManage::getUserId,
+                        wrappers -> wrappers
+                                .leftJoin(DictionaryDetail.class, DictionaryDetail::getCode, BaseStudentSchoolRoll::getArchivesStatus,
+                                        wrap -> wrap
+                                                .selectAs(DictionaryDetail::getName, LeagueMembersExportQueryListVo::getStatusCn)
+                                )
+                                .like(StringUtils.isNotEmpty(dto.getArchivesStatus()), BaseStudentSchoolRoll::getArchivesStatus, dto.getArchivesStatus())
+                                .eq(ObjectUtils.isNotEmpty(dto.getNodeId()) && ObjectUtils.isNotEmpty(dto.getLevel()) && dto.getLevel() == 3, BaseStudentSchoolRoll::getClassId, dto.getNodeId())
+                                .eq(BaseStudentSchoolRoll::getDeleteMark, DeleteMark.NODELETE.getCode())
+                )
+                .leftJoin(BaseClass.class, BaseClass::getId, BaseStudentSchoolRoll::getClassId,
+                        wrappers -> wrappers
+                                .selectAs(BaseClass::getName, LeagueMembersExportQueryListVo::getClassName)
+                                .leftJoin(XjrUser.class, XjrUser::getId, BaseClass::getTeacherId,
+                                        wrap -> wrap
+                                                .selectAs(XjrUser::getName, LeagueMembersExportQueryListVo::getTeacherName)
+                                )
+                                .eq(ObjectUtils.isNotEmpty(dto.getNodeId()) && ObjectUtils.isNotEmpty(dto.getLevel()) && dto.getLevel() == 2, BaseClass::getGradeId, dto.getNodeId())
+                )
+                .leftJoin(BaseStudent.class, BaseStudent::getUserId, LeagueMembersManage::getUserId,
+                        wrappers -> wrappers
+                                .leftJoin(DictionaryDetail.class, DictionaryDetail::getCode, BaseStudent::getNation,
+                                        wrap -> wrap
+                                                .selectAs(DictionaryDetail::getName, LeagueMembersExportQueryListVo::getNation)
+                                )
+                                .eq(BaseStudent::getDeleteMark, DeleteMark.NODELETE.getCode())
+                )
+                .like(StringUtils.isNotEmpty(dto.getLeagueMembersNum()), LeagueMembersManage::getLeagueMembersNum, dto.getLeagueMembersNum())
+                .ge(ObjectUtils.isNotEmpty(dto.getJoinTimeStart()), LeagueMembersManage::getJoinTime, dto.getJoinTimeStart())
+                .le(ObjectUtils.isNotEmpty(dto.getJoinTimeEnd()), LeagueMembersManage::getJoinTime, dto.getJoinTimeEnd())
+                .ge(ObjectUtils.isNotEmpty(dto.getTransferTimeStart()), LeagueMembersManage::getTransferTime, dto.getTransferTimeStart())
+                .le(ObjectUtils.isNotEmpty(dto.getTransferTimeEnd()), LeagueMembersManage::getTransferTime, dto.getTransferTimeEnd())
+                .orderByAsc(BaseClass::getName)
+        ;
+        List<LeagueMembersExportQueryListVo> lmeqList = this.selectJoinList(LeagueMembersExportQueryListVo.class, leagueMembersManageMPJLambdaWrapper);
+
+        // 开始写入
+        Workbook workbook = new XSSFWorkbook();
+        // 创建一个工作表(sheet)
+        String sheetName = "sheet1";
+        Sheet sheet = workbook.createSheet(sheetName);
+
+        // 出参vo字段数量
+        LeagueMembersExportQueryListVo obj = new LeagueMembersExportQueryListVo();
+        List<ImportConfig> importConfigs = ImportExcelUtil.allFields(obj);
+
+        // 写大标题
+        int rowNumber = 0;
+        ImportExcelUtil.createBigHead(workbook, sheet, "团员信息列表", rowNumber++, importConfigs.size());
+
+        // 表头
+        ImportExcelUtil.createHead(workbook, sheet, importConfigs, IndexedColors.YELLOW.getIndex(), IndexedColors.RED.getIndex(), rowNumber++);
+
+        // 字体内容格式
+        Font font = workbook.createFont();
+        font.setBold(false);// 设置为粗体
+        font.setFontName("宋体");
+        font.setFontHeightInPoints((short) 12);
+
+        // 单元格样式
+        CellStyle cellStyle = workbook.createCellStyle();
+        cellStyle.setFont(font); // 将字体应用到样式
+        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        cellStyle.setAlignment(HorizontalAlignment.CENTER);
+
+        // 设置边框样式为细线
+        cellStyle.setBorderTop(BorderStyle.THIN);
+        cellStyle.setTopBorderColor(IndexedColors.BLACK.getIndex()); // 设置顶部边框颜色
+
+        cellStyle.setBorderBottom(BorderStyle.THIN);
+        cellStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex()); // 设置底部边框颜色
+
+        cellStyle.setBorderLeft(BorderStyle.THIN);
+        cellStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex()); // 设置左边框颜色
+
+        cellStyle.setBorderRight(BorderStyle.THIN);
+        cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex()); // 设置右边框颜色
+
+        // 构建数据
+        List<List<String>> resultList = new ArrayList<>();
+        List<String> oneResult;
+        int sortCode = 1;
+        // 定义输出格式 (YYYY.MM)
+        DateTimeFormatter transferFormatter = DateTimeFormatter.ofPattern("yyyy.MM");
+        DateTimeFormatter joinFormatter = DateTimeFormatter.ofPattern("yyyyMM");
+        for (LeagueMembersExportQueryListVo vo : lmeqList){
+            oneResult = new ArrayList<>();
+            oneResult.add("" + sortCode++);
+            oneResult.add(vo.getClassName());
+            oneResult.add(vo.getTeacherName());
+            oneResult.add(vo.getStudentName());
+            oneResult.add(vo.getGenderCn());
+            oneResult.add(vo.getNation());
+            oneResult.add(vo.getIdCard());
+            oneResult.add(vo.getPhone());
+            oneResult.add(vo.getLeagueMembersNum());
+            oneResult.add(vo.getAddress());
+
+            // 处理加入时间
+            if(ObjectUtils.isNotEmpty(vo.getJoinTime())){
+                // 将 LocalDate 转换为字符串
+                String joinTime = vo.getJoinTime().format(joinFormatter);
+                oneResult.add(joinTime);
+            } else {
+                oneResult.add("");
+            }
+
+            // 处理转入时间
+            if(ObjectUtils.isNotEmpty(vo.getTransferTime())){
+                // 将 LocalDate 转换为字符串
+                String transferTime = vo.getTransferTime().format(transferFormatter);
+                oneResult.add(transferTime);
+            } else {
+                oneResult.add("");
+            }
+
+            oneResult.add(vo.getStatusCn());
+            oneResult.add(vo.getRemark());
+            resultList.add(oneResult);
+        }
+
+        // 写入数据
+        for (List<String> rowData : resultList) {
+            Row dataRow = sheet.createRow(rowNumber);
+            for (int j = 0; j < rowData.size(); j++) {
+                String content = rowData.get(j);
+                Cell cell = dataRow.createCell(j);
+                cell.setCellValue(content);
+                cell.setCellStyle(cellStyle);
+            }
+            rowNumber++;
+        }
+
+        // 自动列宽
+        for (int i = 0; i < importConfigs.size(); i++) {
+            sheet.autoSizeColumn(i);
+        }
+
+        ByteArrayOutputStream bot = new ByteArrayOutputStream();
+        workbook.write(bot);
+        return bot;
+    }
+}

+ 1 - 2
src/main/java/com/xjrsoft/module/student/service/impl/PbCseFeeobjupdateServiceImpl.java

@@ -122,9 +122,8 @@ public class PbCseFeeobjupdateServiceImpl extends MPJBaseServiceImpl<PbCseFeeobj
                 setValue(entry.getValue());
                 if (pbCseSpecplanSumMap.get(entry.getKey()) != null) {
                     setPlannedNumber(pbCseSpecplanSumMap.get(entry.getKey()));
-                    setPaymentCount(specnameCountMap.get(entry.getKey()));
                     setRemainder(pbCseSpecplanSumMap.get(entry.getKey()) - specnameCountMap.get(entry.getKey()));
-
+                    setPaymentCount(specnameCountMap.get(entry.getKey()));
                 }
                 if (specnameCountMap.get(entry.getKey()) != null) {
                     setPaymentCount(specnameCountMap.get(entry.getKey()));

+ 30 - 0
src/main/java/com/xjrsoft/module/student/vo/ClassWithLMNumTreeVo.java

@@ -0,0 +1,30 @@
+package com.xjrsoft.module.student.vo;
+
+import com.xjrsoft.common.model.tree.ITreeNode;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class ClassWithLMNumTreeVo implements ITreeNode<ClassWithLMNumTreeVo, String>, Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String id;
+
+    @ApiModelProperty("名字")
+    private String name;
+
+    @ApiModelProperty("父级id")
+    private String parentId;
+
+    @ApiModelProperty("节点层级,1:总览,2:年级,3:班级")
+    private Integer level;
+
+    @ApiModelProperty("当前级别的数量")
+    private Integer num;
+
+    private List<ClassWithLMNumTreeVo> children;
+}

+ 97 - 0
src/main/java/com/xjrsoft/module/student/vo/LeagueMembersExportQueryListVo.java

@@ -0,0 +1,97 @@
+package com.xjrsoft.module.student.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.ContentStyle;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+/**
+ * @title: 团员信息有参导出列表出参
+ * @Author dzx
+ * @Date: 2024-06-27
+ * @Version 1.0
+ */
+@Data
+public class LeagueMembersExportQueryListVo {
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("序号")
+    @ApiModelProperty("序号")
+    private String sortCode;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("班级")
+    @ApiModelProperty("班级")
+    private String className;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("班主任")
+    @ApiModelProperty("班主任")
+    private String teacherName;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("学生姓名")
+    @ApiModelProperty("学生姓名")
+    private String studentName;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("性别")
+    @ApiModelProperty("性别")
+    private String genderCn;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("民族")
+    @ApiModelProperty("民族")
+    private String nation;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("身份证号")
+    @ApiModelProperty("身份证号")
+    private String idCard;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("手机号")
+    @ApiModelProperty("手机号")
+    private String phone;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("团员编号")
+    @ApiModelProperty("团员编号")
+    private String leagueMembersNum;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("常住地址")
+    @ApiModelProperty("常住地址")
+    private String address;
+
+    @ExcelIgnore
+    @ApiModelProperty("加入时间")
+    private LocalDate joinTime;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("加入时间")
+    @ApiModelProperty("加入时间")
+    private String joinTimeStr;
+
+    @ExcelIgnore
+    @ApiModelProperty("转入时间")
+    private LocalDate transferTime;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("转入时间")
+    @ApiModelProperty("转入时间")
+    private String transferTimeStr;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("在读状态")
+    @ApiModelProperty("学籍状态(xjr_dictionary_item[archives_status])")
+    private String statusCn;
+
+    @ContentStyle(dataFormat = 49)
+    @ExcelProperty("备注")
+    @ApiModelProperty("备注")
+    private String remark;
+}

+ 96 - 0
src/main/java/com/xjrsoft/module/student/vo/LeagueMembersManagePageVo.java

@@ -0,0 +1,96 @@
+package com.xjrsoft.module.student.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import com.xjrsoft.common.annotation.Trans;
+import com.xjrsoft.common.enums.TransType;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+* @title: 团员管理分页列表出参
+* @Author phoenix
+* @Date: 2025-04-08
+* @Version 1.0
+*/
+@Data
+public class LeagueMembersManagePageVo {
+    /**
+     * 主键编号
+     */
+    @ApiModelProperty("主键编号")
+    private String id;
+
+    @ApiModelProperty("年级主键")
+    private String gradeId;
+
+    @ApiModelProperty("年级")
+    private String gradeName;
+
+    @ApiModelProperty("班级主键")
+    private String classId;
+
+    @ApiModelProperty("班级")
+    private String className;
+
+    @ApiModelProperty("班主任")
+    private String teacherName;
+
+    /**
+     * 学生主键id(xjr_user)
+     */
+    @ApiModelProperty("学生主键id(xjr_user)")
+    private String userId;
+
+    @ApiModelProperty("学生姓名")
+    private String studentName;
+
+    @ApiModelProperty("性别")
+    private String genderCn;
+
+    @ApiModelProperty("民族")
+    private String nation;
+
+    @ApiModelProperty("身份证号")
+    private String idCard;
+
+    @ApiModelProperty("手机号")
+    private String phone;
+
+    @ApiModelProperty("常住地址")
+    private String address;
+
+    /**
+     * 团员编号
+     */
+    @ApiModelProperty("团员编号")
+    private String leagueMembersNum;
+    /**
+     * 加入时间
+     */
+    @ApiModelProperty("加入时间")
+    private LocalDate joinTime;
+    /**
+     * 转入时间
+     */
+    @ApiModelProperty("转入时间")
+    private LocalDate transferTime;
+
+    @ApiModelProperty("学籍状态(xjr_dictionary_item[archives_status])")
+    private String archivesStatus;
+
+    @ApiModelProperty("学籍状态(xjr_dictionary_item[archives_status])")
+    private String statusCn;
+
+    /**
+     * 备注
+     */
+    @ApiModelProperty("备注")
+    private String remark;
+}

+ 59 - 0
src/main/java/com/xjrsoft/module/student/vo/LeagueMembersManageVo.java

@@ -0,0 +1,59 @@
+package com.xjrsoft.module.student.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalTime;
+import java.time.LocalDateTime;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Date;
+
+/**
+* @title: 团员管理表单出参
+* @Author phoenix
+* @Date: 2025-04-08
+* @Version 1.0
+*/
+@Data
+public class LeagueMembersManageVo {
+
+    /**
+    * 主键编号
+    */
+    @ApiModelProperty("主键编号")
+    private Long id;
+    /**
+    * 学生主键id(xjr_user)
+    */
+    @ApiModelProperty("学生主键id(xjr_user)")
+    private Long userId;
+    /**
+    * 团员编号
+    */
+    @ApiModelProperty("团员编号")
+    private String leagueMembersNum;
+    /**
+    * 常住地址
+    */
+    @ApiModelProperty("常住地址")
+    private String address;
+    /**
+    * 加入时间
+    */
+    @ApiModelProperty("加入时间")
+    private Date joinTime;
+    /**
+    * 转入时间
+    */
+    @ApiModelProperty("转入时间")
+    private Date transferTime;
+    /**
+    * 备注
+    */
+    @ApiModelProperty("备注")
+    private String remark;
+
+
+
+}

+ 1 - 1
src/main/java/com/xjrsoft/module/textbook/controller/TextbookController.java

@@ -292,7 +292,7 @@ public class TextbookController {
         ImportExcelUtil.createCautionHead(workbook, sheet, 1, content, importConfigs.size() - 1, 12, IndexedColors.RED.getIndex());
 
         // 表头
-        ImportExcelUtil.createHead(workbook, sheet, importConfigs, 2);
+        ImportExcelUtil.createHead(workbook, sheet, importConfigs, IndexedColors.YELLOW.getIndex(), IndexedColors.RED.getIndex(), 2);
 
         //写入文件
         ByteArrayOutputStream bot = new ByteArrayOutputStream();

+ 1 - 1
src/main/java/com/xjrsoft/module/textbook/service/impl/TextbookIssueRecordServiceImpl.java

@@ -455,7 +455,7 @@ public class TextbookIssueRecordServiceImpl extends MPJBaseServiceImpl<TextbookI
 
 
             // 表头
-            ImportExcelUtil.createHead(workbook, sheet, eachImportConfigs, rowNumber++);
+            ImportExcelUtil.createHead(workbook, sheet, eachImportConfigs, IndexedColors.YELLOW.getIndex(), IndexedColors.RED.getIndex(), rowNumber++);
 
             // 处理每个单元格的内容
             resultList = new ArrayList<>();

+ 11 - 4
src/main/java/com/xjrsoft/module/textbook/service/impl/TextbookStudentClaimServiceImpl.java

@@ -247,7 +247,7 @@ public class TextbookStudentClaimServiceImpl extends MPJBaseServiceImpl<Textbook
         ;
         List<HeadTeaLookClassBookCategoryDetailVo> classList = wfTextbookClaimItemMapper.selectJoinList(HeadTeaLookClassBookCategoryDetailVo.class, classMPJLambdaWrapper);
 
-        // 班级申领教材
+        // 个人申领教材
         MPJLambdaWrapper<WfTextbookClaimItem> personalMPJLambdaWrapper = new MPJLambdaWrapper<>();
         personalMPJLambdaWrapper
                 .disableSubLogicDel()
@@ -274,9 +274,16 @@ public class TextbookStudentClaimServiceImpl extends MPJBaseServiceImpl<Textbook
         ;
         List<HeadTeaLookClassBookCategoryDetailVo> personalList = wfTextbookClaimItemMapper.selectJoinList(HeadTeaLookClassBookCategoryDetailVo.class, personalMPJLambdaWrapper);
 
-        // 合并两个列表
-        List<HeadTeaLookClassBookCategoryDetailVo> mergedList = Stream.concat(classList.stream(), personalList.stream())
-                .collect(Collectors.toList());
+        // 合并两个列表,如果有个人领取记录,应该保留个人的,移除班级的
+        List<HeadTeaLookClassBookCategoryDetailVo> mergedList = new ArrayList<>(Stream.concat(classList.stream(), personalList.stream())
+                .collect(Collectors.toMap(
+                        // 使用唯一标识作为 key
+                        HeadTeaLookClassBookCategoryDetailVo::getTextbookId,
+                        // 如果有重复,保留后面的值(即优先保留 personalList 的记录)
+                        vo -> vo,
+                        (classVo, personalVo) -> personalVo // 解决冲突时,优先选择 personalList 的记录
+                ))
+                .values()); // 转换回 List
 
         // 根据 textbookId 去重
         List<HeadTeaLookClassBookCategoryDetailVo> distinctList = new ArrayList<>(mergedList.stream()

+ 1 - 1
src/main/java/com/xjrsoft/module/textbook/service/impl/TextbookSubscriptionServiceImpl.java

@@ -1090,7 +1090,7 @@ public class TextbookSubscriptionServiceImpl extends MPJBaseServiceImpl<Textbook
         ImportExcelUtil.createCautionHead(workbook, sheet, 1, cautionHead, importConfigs.size() - 1, 12, IndexedColors.RED.getIndex());
 
         // 表头
-        ImportExcelUtil.createHead(workbook, sheet, importConfigs, 2);
+        ImportExcelUtil.createHead(workbook, sheet, importConfigs, IndexedColors.YELLOW.getIndex(), IndexedColors.RED.getIndex(), 2);
 
         //生成数据
         int dataRowNumber = 3;

+ 68 - 9
src/main/java/com/xjrsoft/module/veb/util/ImportExcelUtil.java

@@ -53,7 +53,7 @@ public class ImportExcelUtil {
         List<ImportConfig> importConfigs = allFields(obj);
 
         // 表头
-        createHead(workbook, sheet, importConfigs, 0);
+        createHead(workbook, sheet, importConfigs, IndexedColors.YELLOW.getIndex(), IndexedColors.RED.getIndex(), 0);
 
         // 提示必填
         String content = "红色背景为必填项,导入时请删除本行。";
@@ -147,6 +147,12 @@ public class ImportExcelUtil {
         Cell cell = row.createCell(0);
         cell.setCellValue(bigHead);
         cell.setCellStyle(normalCellStyle);
+
+        for (int i = 1; i < lastCol + 1; i ++){
+            cell = row.createCell(i);
+            cell.setCellStyle(normalCellStyle);
+        }
+
         sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber, 0, lastCol));
     }
 
@@ -158,14 +164,14 @@ public class ImportExcelUtil {
      * @param importConfigs
      * @param rowNumber
      */
-    public static void createHead(Workbook workbook, Sheet sheet, List<ImportConfig> importConfigs, int rowNumber) {
-        Font font = workbook.createFont();
-        font.setFontName("宋体");
-        font.setFontHeightInPoints((short) 12);
+    public static void createHead(Workbook workbook, Sheet sheet, List<ImportConfig> importConfigs, short requiredFieldForegroundColor, short requiredFieldFontColor, int rowNumber) {
+        Font normalFont = workbook.createFont();
+        normalFont.setFontName("宋体");
+        normalFont.setFontHeightInPoints((short) 12);
 
         // 正常样式
         CellStyle normalCellStyle = workbook.createCellStyle();
-        normalCellStyle.setFont(font); // 将字体应用到样式
+        normalCellStyle.setFont(normalFont); // 将字体应用到样式
         normalCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
         normalCellStyle.setAlignment(HorizontalAlignment.CENTER);
 
@@ -183,11 +189,16 @@ public class ImportExcelUtil {
         normalCellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex()); // 设置右边框颜色
 
         // 必填样式
+        Font requiredFont = workbook.createFont();
+        requiredFont.setFontName("宋体");
+        requiredFont.setFontHeightInPoints((short) 12);
+
         CellStyle requiredCellStyle = workbook.createCellStyle();
-        requiredCellStyle.setFont(font); // 将字体应用到样式
+        requiredCellStyle.setFont(requiredFont); // 将字体应用到样式
         requiredCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
         requiredCellStyle.setAlignment(HorizontalAlignment.CENTER);
-        requiredCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());//设置背景颜色
+//        requiredCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());//设置背景颜色
+        requiredCellStyle.setFillForegroundColor(requiredFieldForegroundColor);//设置背景颜色s
         requiredCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);//设置填充模式
 
         // 设置边框样式为细线
@@ -220,6 +231,51 @@ public class ImportExcelUtil {
         }
     }
 
+    /**
+     * 写入副标题,字号14,合并为一个单元格
+     * @param workbook
+     * @param sheet
+     * @param rowNumber
+     * @param lastCol
+     */
+    public static void createSubtitle(Workbook workbook, Sheet sheet, String bigHead, int rowNumber, int lastCol) {
+        Font font = workbook.createFont();
+        font.setFontName("宋体");
+        font.setFontHeightInPoints((short) 14);
+
+        // 正常样式
+        CellStyle normalCellStyle = workbook.createCellStyle();
+        normalCellStyle.setFont(font); // 将字体应用到样式
+        normalCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+        normalCellStyle.setAlignment(HorizontalAlignment.CENTER);
+        // 设置边框样式为细线
+        normalCellStyle.setBorderTop(BorderStyle.THIN);
+        normalCellStyle.setTopBorderColor(IndexedColors.BLACK.getIndex()); // 设置顶部边框颜色
+
+        normalCellStyle.setBorderBottom(BorderStyle.THIN);
+        normalCellStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex()); // 设置底部边框颜色
+
+        normalCellStyle.setBorderLeft(BorderStyle.THIN);
+        normalCellStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex()); // 设置左边框颜色
+
+        normalCellStyle.setBorderRight(BorderStyle.THIN);
+        normalCellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex()); // 设置右边框颜色
+
+        // 所在行
+        Row row = sheet.createRow(rowNumber);
+
+        Cell cell = row.createCell(0);
+        cell.setCellValue(bigHead);
+        cell.setCellStyle(normalCellStyle);
+
+        for (int i = 1; i < lastCol + 1; i ++){
+            cell = row.createCell(i);
+            cell.setCellStyle(normalCellStyle);
+        }
+
+        sheet.addMergedRegion(new CellRangeAddress(rowNumber, rowNumber, 0, lastCol));
+    }
+
     /**
      * 写单格
      *
@@ -268,6 +324,9 @@ public class ImportExcelUtil {
         sheet.addMergedRegion(new CellRangeAddress(starRow, endRow, startcol, lastCol));
     }
 
+
+
+
     /**
      * 写提示行
      *
@@ -492,7 +551,7 @@ public class ImportExcelUtil {
 
         for (Field field : instance.getClass().getDeclaredFields()) {
             Required required = field.getAnnotation(Required.class);
-            if (required != null) { // 如果字段被 @Required 标记
+            if (ObjectUtils.isNotEmpty(required) && required.value()) { // 如果字段被 @Required 标记
                 field.setAccessible(true); // 允许访问私有字段
                 Object value = field.get(instance); // 获取字段的值
                 if (value == null || (value instanceof String && ((String) value).trim().isEmpty())) {

+ 9 - 9
src/main/resources/application-dev.yml

@@ -5,9 +5,9 @@ spring:
   datasource:
     type: com.alibaba.druid.pool.DruidDataSource
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://10.150.10.139:3306/tl?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&autoReconnect=true&failOverReadOnly=false&testWhileIdle=true
-    username: root
-    password: Zwr~-f6H,u6QE^]C-AD_
+    url: jdbc:mysql://10.150.10.136:3308/tl?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&autoReconnect=true&failOverReadOnly=false&testWhileIdle=true
+    username: data_tl
+    password: qwe123QWE
     druid:
       # 验证连接的有效性
       test-while-idle: true
@@ -23,15 +23,15 @@ spring:
         datasource:
           master:
             driver-class-name: com.mysql.cj.jdbc.Driver
-            url: jdbc:mysql://10.150.10.139:3306/tl?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&autoReconnect=true&failOverReadOnly=false
-            username: root
-            password: Zwr~-f6H,u6QE^]C-AD_
+            url: jdbc:mysql://10.150.10.136:3308/tl?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&autoReconnect=true&failOverReadOnly=false&testWhileIdle=true
+            username: data_tl
+            password: qwe123QWE
 
   redis:
-    database: 10
-    host: 10.150.10.139
+    database: 11
+    host: 10.150.10.136
     port: 6379
-    password: wa3re~86Hu&ifniyM   # 密码(默认为空)
+    password: 9mwar,BNq}oMa63cUd?R   # 密码(默认为空)
     timeout: 36000ms  # 连接超时时长(毫秒)
     jedis:
       pool:

+ 2 - 2
src/main/resources/mapper/courseTable/CourseTable.xml

@@ -4,7 +4,7 @@
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.xjrsoft.module.courseTable.mapper.CourseTableMapper">
     <select id="getList" parameterType="com.xjrsoft.module.schedule.dto.CourseTableDto" resultType="com.xjrsoft.module.schedule.vo.CourseDetailVo">
-        SELECT t1.course_name, t1.teacher_name,CONCAT(t4.name,t3.name) AS classroom_name,t1.weeks,t1.time_period,t1.time_number,t5.name as class_name,t1.schedule_date,t1.adjust_type FROM course_table t1
+        SELECT distinct  t1.course_name, t1.teacher_name,CONCAT(t4.name,t3.name) AS classroom_name,t1.weeks,t1.time_period,t1.time_number,t5.name as class_name,t1.schedule_date,t1.adjust_type FROM course_table t1
         LEFT JOIN base_classroom t3 ON t1.site_id = t3.id
         LEFT JOIN base_office_build t4 ON t3.office_build_id = t4.id
         LEFT JOIN base_class t5 ON t1.class_id = t5.id
@@ -44,7 +44,7 @@
         <if test="dto.classId != null">
             and t5.id = #{dto.classId}
         </if>
-
+        ORDER BY t1.weeks,t1.time_period,t1.time_number
     </select>
     <select id="getAdjustList" parameterType="com.xjrsoft.module.schedule.dto.CourseTableAdjustDto" resultType="com.xjrsoft.module.schedule.vo.CourseListVo">
         SELECT t1.time_period,t4.short_name as time_number,t1.course_name,t2.name AS class_name,t3.name AS classroom_name,t1.id,t2.id as class_id FROM course_table t1

+ 3 - 0
src/main/resources/mapper/room/RoomBedMapper.xml

@@ -362,6 +362,9 @@
             and t2.floor_number = #{dto.floorNumber}
         </if>
         <if test="dto.classId != null and dto.isTeacher != null and dto.isTeacher == 1">
+            AND t8.id = #{dto.classId}
+        </if>
+        <if test="dto.classId != null and dto.isTeacher == null">
             AND (t2.id IN (
             SELECT DISTINCT c1.room_id FROM room_bed c1
             INNER JOIN base_student_school_roll c2 ON c1.student_user_id = c2.user_id

+ 25 - 1
src/main/resources/sqlScript/20250408_sql.sql

@@ -59,4 +59,28 @@ CREATE TABLE `base_class_merge_student` (
   `class_id` BIGINT DEFAULT NULL COMMENT '原有班级',
   `user_id` BIGINT DEFAULT NULL COMMENT '学生id',
   PRIMARY KEY (`id`) USING BTREE
-) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='班级合并涉及学生';
+) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='班级合并涉及学生';
+
+-- ---------------------------------------------------------------
+-- 团员管理
+-- ---------------------------------------------------------------
+DROP TABLE IF EXISTS `league_members_manage`;
+CREATE TABLE `league_members_manage`
+(
+    id                   bigint   not null comment '主键编号'
+        primary key,
+    create_user_id       BIGINT   NULL COMMENT '创建人',
+    create_date          DATETIME NULL COMMENT '创建时间',
+    modify_user_id       BIGINT   NULL COMMENT '修改人',
+    modify_date          DATETIME NULL COMMENT '修改时间',
+    delete_mark          INT      NOT NULL COMMENT '删除标记',
+    enabled_mark         INT      NOT NULL COMMENT '有效标志',
+
+    `user_id`            BIGINT   NOT NULL COMMENT '学生主键id(xjr_user)',
+    `league_members_num` varchar(64)   DEFAULT NULL COMMENT '团员编号',
+    `address`            varchar(256)  DEFAULT NULL COMMENT '常住地址',
+    `join_time`          DATE          DEFAULT NULL COMMENT '加入时间',
+    `transfer_time`      DATE          DEFAULT NULL COMMENT '转入时间',
+    remark               varchar(1024) DEFAULT NULL COMMENT '备注'
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4 COMMENT = '团员管理';

+ 51 - 0
src/main/resources/sqlScript/20250409_sql.sql

@@ -0,0 +1,51 @@
+-- ---------------------------------------------------------------
+-- 意见反馈
+-- ---------------------------------------------------------------
+drop table if exists `feedback`;
+create table `feedback`
+(
+    id                   bigint      not null comment '主键编号'
+        primary key,
+    create_user_id       bigint      null comment '创建人',
+    create_date          datetime    null comment '创建时间',
+    modify_user_id       bigint      null comment '修改人',
+    modify_date          datetime    null comment '修改时间',
+    delete_mark          int         not null comment '删除标记',
+    enabled_mark         int         not null comment '有效标志',
+
+    `user_id`            bigint      not null comment '意见反馈反馈用户主键id(xjr_user)',
+    `user_type`          int         not null comment '用户类型(1:教师,2:学生,3:家长)',
+    `terminal_type`      varchar(64) not null comment '终端类型(xjr_dictionary_item(terminal_type))',
+    `feedback_time`      datetime    not null comment '反馈时间',
+    `handle_status`      int      default 0 comment '处理状态(0:未处理,1:已处理)',
+    `handle_time`        datetime default null comment '处理时间',
+    `handle_read_status` int      default 0 comment '处理知晓状态(0:没有未读,1:有未读)'
+) engine = innodb
+  default charset = utf8mb4 comment = '意见反馈';
+
+-- ---------------------------------------------------------------
+-- 意见反馈-具体反馈单项
+-- ---------------------------------------------------------------
+drop table if exists `feedback_item`;
+create table `feedback_item`
+(
+    id                  bigint       not null comment '主键编号'
+        primary key,
+    create_user_id      bigint       null comment '创建人',
+    create_date         datetime     null comment '创建时间',
+    modify_user_id      bigint       null comment '修改人',
+    modify_date         datetime     null comment '修改时间',
+    delete_mark         int          not null comment '删除标记',
+    enabled_mark        int          not null comment '有效标志',
+
+    `feedback_id`       bigint       not null comment '意见反馈主键id(feedback)',
+    `feedback`          varchar(512) not null comment '反馈文本',
+    `feedback_file_id`  bigint       default null comment '反馈附件主键id',
+    `reply_status`      int          default 0 comment '回复状态(0:未回复,1:已回复)',
+    `reply_user_id`     bigint       default null comment '回复反馈用户主键id(xjr_user)',
+    `reply`             varchar(512) default null comment '回复文本',
+    `reply_file_id`     bigint       default null comment '回复附件主键id',
+    `reply_time`        datetime     default null comment '回复时间',
+    `reply_read_status` int          default 0 comment '回复阅读状态(0:未读,1:已读)'
+) engine = innodb
+  default charset = utf8mb4 comment = '意见反馈-具体反馈单项';

+ 33 - 0
src/test/java/com/xjrsoft/module/feedback/service/impl/FeedbackServiceImplTest.java

@@ -0,0 +1,33 @@
+package com.xjrsoft.module.feedback.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+class FeedbackServiceImplTest {
+
+    @BeforeEach
+    void setUp() {
+        // 模拟用户登录
+        StpUtil.login(15331004147782L); // 假设用户ID为1
+    }
+
+    @AfterEach
+    void tearDown() {
+        // 清理会话
+        StpUtil.logout();
+    }
+
+    @Test
+    void addFeedbackItem() {
+        List<String> roleList = StpUtil.getRoleList();
+        System.err.println(roleList);
+    }
+}

+ 78 - 3
src/test/java/com/xjrsoft/xjrsoftboot/FreeMarkerGeneratorTest.java

@@ -15,14 +15,14 @@ import java.util.List;
 
 public class FreeMarkerGeneratorTest {
 
-    private static final DataSource ds = new SimpleDataSource("jdbc:mysql://10.150.10.139:3306/tl?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true",
-            "root" , "Zwr~-f6H,u6QE^]C-AD_" );
+    private static final DataSource ds = new SimpleDataSource("jdbc:mysql://10.150.10.136:3308/tl?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&autoReconnect=true&failOverReadOnly=false&testWhileIdle=true",
+            "data_tl" , "qwe123QWE" );
 
     @Test
     public void generateCodesTest() throws IOException {
         List<TableConfig> tableConfigs = new ArrayList<>();
         TableConfig mainTable = new TableConfig();
-        mainTable.setTableName("xjr_workflow_form_relation");
+        mainTable.setTableName("league_members_manage");
         mainTable.setIsMain(true);
         mainTable.setPkField(GlobalConstant.DEFAULT_PK);
         mainTable.setPkType(GlobalConstant.DEFAULT_PK_TYPE);
@@ -4628,4 +4628,79 @@ public class FreeMarkerGeneratorTest {
 
         apiGeneratorService.generateCodes(params);
     }
+
+    @Test
+    public void gcLeagueMembersManage() throws IOException {
+        List<TableConfig> tableConfigs = new ArrayList<>();
+        TableConfig mainTable = new TableConfig();
+        mainTable.setTableName("league_members_manage");//init_sql中的表名
+        mainTable.setIsMain(true);//是否是主表,一般默认为true
+        mainTable.setPkField(GlobalConstant.DEFAULT_PK);//设置主键
+        mainTable.setPkType(GlobalConstant.DEFAULT_PK_TYPE);//设置主键类型
+        tableConfigs.add(mainTable);
+
+        ApiGenerateCodesDto params = new ApiGenerateCodesDto();
+        params.setAuthor("phoenix");//作者名称
+        params.setPackageName("student");//包名
+        params.setTableConfigs(tableConfigs);
+        params.setPage(true);//是否生成分页接口
+        params.setImport(false);//是否生成导入接口
+        params.setExport(false);//是否生成导出接口
+        params.setOutMainDir(false);//是否生成在主目录,前期测试可设置成false
+        params.setDs(ds);
+
+        IApiGeneratorService apiGeneratorService = new ApiGeneratorServiceImpl();
+
+        apiGeneratorService.generateCodes(params);
+    }
+
+    @Test
+    public void gcFeedback() throws IOException {
+        List<TableConfig> tableConfigs = new ArrayList<>();
+        TableConfig mainTable = new TableConfig();
+        mainTable.setTableName("feedback");//init_sql中的表名
+        mainTable.setIsMain(true);//是否是主表,一般默认为true
+        mainTable.setPkField(GlobalConstant.DEFAULT_PK);//设置主键
+        mainTable.setPkType(GlobalConstant.DEFAULT_PK_TYPE);//设置主键类型
+        tableConfigs.add(mainTable);
+
+        ApiGenerateCodesDto params = new ApiGenerateCodesDto();
+        params.setAuthor("phoenix");//作者名称
+        params.setPackageName("feedback");//包名
+        params.setTableConfigs(tableConfigs);
+        params.setPage(true);//是否生成分页接口
+        params.setImport(false);//是否生成导入接口
+        params.setExport(false);//是否生成导出接口
+        params.setOutMainDir(false);//是否生成在主目录,前期测试可设置成false
+        params.setDs(ds);
+
+        IApiGeneratorService apiGeneratorService = new ApiGeneratorServiceImpl();
+
+        apiGeneratorService.generateCodes(params);
+    }
+
+    @Test
+    public void gcFeedbackItem() throws IOException {
+        List<TableConfig> tableConfigs = new ArrayList<>();
+        TableConfig mainTable = new TableConfig();
+        mainTable.setTableName("feedback_item");//init_sql中的表名
+        mainTable.setIsMain(true);//是否是主表,一般默认为true
+        mainTable.setPkField(GlobalConstant.DEFAULT_PK);//设置主键
+        mainTable.setPkType(GlobalConstant.DEFAULT_PK_TYPE);//设置主键类型
+        tableConfigs.add(mainTable);
+
+        ApiGenerateCodesDto params = new ApiGenerateCodesDto();
+        params.setAuthor("phoenix");//作者名称
+        params.setPackageName("feedback");//包名
+        params.setTableConfigs(tableConfigs);
+        params.setPage(true);//是否生成分页接口
+        params.setImport(false);//是否生成导入接口
+        params.setExport(false);//是否生成导出接口
+        params.setOutMainDir(false);//是否生成在主目录,前期测试可设置成false
+        params.setDs(ds);
+
+        IApiGeneratorService apiGeneratorService = new ApiGeneratorServiceImpl();
+
+        apiGeneratorService.generateCodes(params);
+    }
 }