1课表模板管理;
2、课表生成; 3、课表的网格视图; 4、导出与打印
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package com.roomroot.jwgl.dto.kcb;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 课表网格查询参数(手册一第12章 课表生成)。
|
||||
* <p>dim 维度:semester 班次学期 / team 学员队 / teacher 教员 / room 教室。
|
||||
* id 为对应维度主键(xydxqbh / xydbh / jybh / jsbh)。</p>
|
||||
*/
|
||||
@Data
|
||||
public class TimetableGridQuery {
|
||||
|
||||
/** 维度:semester | team | teacher | room */
|
||||
private String dim;
|
||||
|
||||
/** 维度目标编号 */
|
||||
private String id;
|
||||
|
||||
/** 年度(校历/备注取数用;semester 维度自动从班次学期带出) */
|
||||
private Integer nd;
|
||||
|
||||
/** 起始日期 yyyy-MM-dd;空时 semester 取学期范围,其它维度取本周 */
|
||||
private String start;
|
||||
|
||||
/** 结束日期 yyyy-MM-dd */
|
||||
private String end;
|
||||
|
||||
/** 课表模板编号(可选;套用模板显示开关:78节/晚上/夜间/选修/天数) */
|
||||
private String mbbh;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.roomroot.jwgl.service;
|
||||
|
||||
import com.roomroot.jwgl.entity.JCSJB;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -18,4 +20,10 @@ public interface ClassPeriodService {
|
||||
* 上午时段的双节次标签,用于「正课」默认判定。
|
||||
*/
|
||||
Set<String> mainSlotLabels();
|
||||
|
||||
/**
|
||||
* 单节次时间行(双节次=0),按节次序号升序;表内无数据时返回空,
|
||||
* 调用方回退默认 1–12 节。
|
||||
*/
|
||||
List<JCSJB> singleRows();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.roomroot.jwgl.service;
|
||||
|
||||
import com.roomroot.jwgl.entity.KCBMBB;
|
||||
import com.roomroot.jwgl.unit.PageQuery;
|
||||
import com.roomroot.jwgl.unit.PageResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 课表模版管理(手册一第12章:只做 CRUD + 套用,不建新表)。
|
||||
*/
|
||||
public interface KCBMBService {
|
||||
|
||||
PageResult<KCBMBB> page(PageQuery query, String mbmc);
|
||||
|
||||
KCBMBB get(String bh);
|
||||
|
||||
void add(KCBMBB tpl);
|
||||
|
||||
void update(KCBMBB tpl);
|
||||
|
||||
void delete(String bh);
|
||||
|
||||
/** 启用/停用(模版可用开关) */
|
||||
void toggle(String bh, boolean enabled);
|
||||
|
||||
/** 可用模板下拉(只回编号+名称) */
|
||||
List<KCBMBB> options();
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.roomroot.jwgl.service;
|
||||
|
||||
import com.roomroot.jwgl.dto.kcb.TimetableGridQuery;
|
||||
import com.roomroot.jwgl.vo.kcb.TimetableGridVO;
|
||||
|
||||
/**
|
||||
* 课表网格生成(手册一第12章)。
|
||||
* <p>按 班次学期/学员队/教员/教室 四个维度生成「周次 × 星期 × 节次」横版网格。</p>
|
||||
*/
|
||||
public interface TimetableGridService {
|
||||
|
||||
/**
|
||||
* 生成课表网格数据。
|
||||
*/
|
||||
TimetableGridVO grid(TimetableGridQuery query);
|
||||
|
||||
/**
|
||||
* 导出网格课表为矩阵 Excel(每周一个 sheet)。
|
||||
*/
|
||||
byte[] exportGrid(TimetableGridQuery query);
|
||||
}
|
||||
+8
@@ -51,6 +51,14 @@ public class ClassPeriodServiceImpl implements ClassPeriodService {
|
||||
return main.isEmpty() ? DEFAULT_MAIN_SLOTS : main;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JCSJB> singleRows() {
|
||||
List<JCSJB> rows = jcsjbMapper.selectList(new LambdaQueryWrapper<JCSJB>()
|
||||
.eq(JCSJB::getSjc, false));
|
||||
rows.sort(Comparator.comparingInt(r -> PeriodUtil.firstPeriod(r.getJcsy())));
|
||||
return rows;
|
||||
}
|
||||
|
||||
private List<JCSJB> loadSlots() {
|
||||
List<JCSJB> rows = jcsjbMapper.selectList(new LambdaQueryWrapper<JCSJB>()
|
||||
.eq(JCSJB::getSjc, true));
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package com.roomroot.jwgl.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.roomroot.jwgl.entity.KCBMBB;
|
||||
import com.roomroot.jwgl.mapper.KCBMBBMapper;
|
||||
import com.roomroot.jwgl.service.KCBMBService;
|
||||
import com.roomroot.jwgl.unit.BusinessException;
|
||||
import com.roomroot.jwgl.unit.PageQuery;
|
||||
import com.roomroot.jwgl.unit.PageResult;
|
||||
import com.roomroot.jwgl.utils.UuidUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class KCBMBServiceImpl implements KCBMBService {
|
||||
|
||||
@Resource
|
||||
private KCBMBBMapper kcbmbbMapper;
|
||||
|
||||
@Override
|
||||
public PageResult<KCBMBB> page(PageQuery query, String mbmc) {
|
||||
Page<KCBMBB> page = new Page<>(query.getPageNum(), query.getPageSize());
|
||||
LambdaQueryWrapper<KCBMBB> qw = new LambdaQueryWrapper<KCBMBB>()
|
||||
.like(mbmc != null && !mbmc.isEmpty(), KCBMBB::getMBMC, mbmc)
|
||||
.orderByDesc(KCBMBB::getGXSJ);
|
||||
Page<KCBMBB> result = kcbmbbMapper.selectPage(page, qw);
|
||||
return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public KCBMBB get(String bh) {
|
||||
return kcbmbbMapper.selectById(bh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(KCBMBB tpl) {
|
||||
if (tpl.getMBMC() == null || tpl.getMBMC().isEmpty()) {
|
||||
throw new BusinessException("模版名称不能为空");
|
||||
}
|
||||
tpl.setBH(UuidUtil.getUUID());
|
||||
tpl.setGXSJ(LocalDateTime.now());
|
||||
if (tpl.getMBKY() == null) {
|
||||
tpl.setMBKY(true);
|
||||
}
|
||||
tpl.setMBCZ(tpl.getMB() != null && !tpl.getMB().isEmpty());
|
||||
kcbmbbMapper.insert(tpl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(KCBMBB tpl) {
|
||||
if (tpl.getBH() == null || tpl.getBH().isEmpty()) {
|
||||
throw new BusinessException("模版编号不能为空");
|
||||
}
|
||||
tpl.setGXSJ(LocalDateTime.now());
|
||||
tpl.setMBCZ(tpl.getMB() != null && !tpl.getMB().isEmpty());
|
||||
kcbmbbMapper.updateById(tpl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String bh) {
|
||||
kcbmbbMapper.deleteById(bh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggle(String bh, boolean enabled) {
|
||||
KCBMBB tpl = kcbmbbMapper.selectById(bh);
|
||||
if (tpl == null) {
|
||||
throw new BusinessException("模版不存在:" + bh);
|
||||
}
|
||||
tpl.setMBKY(enabled);
|
||||
tpl.setGXSJ(LocalDateTime.now());
|
||||
kcbmbbMapper.updateById(tpl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KCBMBB> options() {
|
||||
return kcbmbbMapper.selectList(new LambdaQueryWrapper<KCBMBB>()
|
||||
.eq(KCBMBB::getMBKY, true)
|
||||
.orderByDesc(KCBMBB::getGXSJ))
|
||||
.stream()
|
||||
.map(t -> {
|
||||
KCBMBB o = new KCBMBB();
|
||||
o.setBH(t.getBH());
|
||||
o.setMBMC(t.getMBMC());
|
||||
return o;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+617
@@ -0,0 +1,617 @@
|
||||
package com.roomroot.jwgl.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.roomroot.jwgl.dto.kcb.TimetableGridQuery;
|
||||
import com.roomroot.jwgl.entity.JCSJB;
|
||||
import com.roomroot.jwgl.entity.JQB;
|
||||
import com.roomroot.jwgl.entity.JSB;
|
||||
import com.roomroot.jwgl.entity.JYB;
|
||||
import com.roomroot.jwgl.entity.KB;
|
||||
import com.roomroot.jwgl.entity.KCBMBB;
|
||||
import com.roomroot.jwgl.entity.SSKC;
|
||||
import com.roomroot.jwgl.entity.SSKCB;
|
||||
import com.roomroot.jwgl.entity.SSKCBJS;
|
||||
import com.roomroot.jwgl.entity.SSKCBFZJY;
|
||||
import com.roomroot.jwgl.entity.SSKCBXYD;
|
||||
import com.roomroot.jwgl.entity.XQXLB;
|
||||
import com.roomroot.jwgl.entity.XYDB;
|
||||
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
|
||||
import com.roomroot.jwgl.mapper.ClassRoomMapper;
|
||||
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
|
||||
import com.roomroot.jwgl.mapper.JQBMapper;
|
||||
import com.roomroot.jwgl.mapper.JYBMapper;
|
||||
import com.roomroot.jwgl.mapper.KBMapper;
|
||||
import com.roomroot.jwgl.mapper.KCBMBBMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCBJSMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCBFZJYMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCBMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCBXYDMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCMapper;
|
||||
import com.roomroot.jwgl.mapper.SemesterCalendarMapper;
|
||||
import com.roomroot.jwgl.mapper.XYDBMapper;
|
||||
import com.roomroot.jwgl.service.ClassPeriodService;
|
||||
import com.roomroot.jwgl.service.TimetableGridService;
|
||||
import com.roomroot.jwgl.unit.BusinessException;
|
||||
import com.roomroot.jwgl.utils.PeriodUtil;
|
||||
import com.roomroot.jwgl.vo.kcb.TimetableGridVO;
|
||||
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.VerticalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 课表网格生成实现。
|
||||
* <p>课次一律从 实施_课程表(SSKCB) 按日期范围取;维度过滤经三张关联子表
|
||||
* (学员队 / 教室 / 辅助教员)在内存归并,避免 N+1 与达梦 GUID 双形态问题。</p>
|
||||
*/
|
||||
@Service
|
||||
public class TimetableGridServiceImpl implements TimetableGridService {
|
||||
|
||||
private static final DateTimeFormatter DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter COMPACT = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
private static final String[] WEEKDAY_NAMES = {"", "一", "二", "三", "四", "五", "六", "日"};
|
||||
|
||||
@Resource
|
||||
private SSKCBMapper sskcbMapper;
|
||||
@Resource
|
||||
private SSKCBXYDMapper sskcbxydMapper;
|
||||
@Resource
|
||||
private SSKCBJSMapper sskcbjsMapper;
|
||||
@Resource
|
||||
private SSKCBFZJYMapper sskcbfzjyMapper;
|
||||
@Resource
|
||||
private SSKCMapper sskcMapper;
|
||||
@Resource
|
||||
private KBMapper kbMapper;
|
||||
@Resource
|
||||
private XYDBMapper xydbMapper;
|
||||
@Resource
|
||||
private JYBMapper jybMapper;
|
||||
@Resource
|
||||
private ClassRoomMapper classRoomMapper;
|
||||
@Resource
|
||||
private ClassSemesterMapper classSemesterMapper;
|
||||
@Resource
|
||||
private SemesterCalendarMapper semesterCalendarMapper;
|
||||
@Resource
|
||||
private JQBMapper jqbMapper;
|
||||
@Resource
|
||||
private KCBMBBMapper kcbmbbMapper;
|
||||
@Resource
|
||||
private ClassPeriodService classPeriodService;
|
||||
|
||||
@Override
|
||||
public TimetableGridVO grid(TimetableGridQuery query) {
|
||||
if (query == null || query.getDim() == null || query.getId() == null || query.getId().isEmpty()) {
|
||||
throw new BusinessException("课表网格需要维度(dim)与目标编号(id)");
|
||||
}
|
||||
|
||||
// ---- 维度目标与日期范围 ----
|
||||
String dim = query.getDim();
|
||||
String targetName = query.getId();
|
||||
String teamFilter = null; // 学员队编号(semester/team 维度)
|
||||
String teacherFilter = null;
|
||||
String roomFilter = null;
|
||||
String semesterBh = null; // 班历取数用(semester 维度)
|
||||
LocalDate start = parseDay(query.getStart());
|
||||
LocalDate end = parseDay(query.getEnd());
|
||||
Integer nd = query.getNd();
|
||||
|
||||
switch (dim) {
|
||||
case "semester": {
|
||||
XYDNDXQJBXXB sem = classSemesterMapper.selectById(query.getId());
|
||||
if (sem == null) {
|
||||
throw new BusinessException("班次学期不存在:" + query.getId());
|
||||
}
|
||||
semesterBh = sem.getBh();
|
||||
teamFilter = sem.getXydbh();
|
||||
nd = sem.getNd();
|
||||
if (start == null) start = sem.getKxrq();
|
||||
if (end == null) end = sem.getJsrq();
|
||||
String xydmc = resolveTeamName(teamFilter);
|
||||
targetName = xydmc + "(" + sem.getNd() + "年度 第" + sem.getXqdc() + "学期)";
|
||||
break;
|
||||
}
|
||||
case "team": {
|
||||
XYDB team = xydbMapper.selectOne(new LambdaQueryWrapper<XYDB>().eq(XYDB::getXydbh, query.getId()));
|
||||
if (team == null) {
|
||||
throw new BusinessException("学员队不存在:" + query.getId());
|
||||
}
|
||||
teamFilter = team.getXydbh();
|
||||
targetName = team.getXydmc() != null ? team.getXydmc() : team.getXydbh();
|
||||
break;
|
||||
}
|
||||
case "teacher": {
|
||||
JYB teacher = jybMapper.selectOne(new LambdaQueryWrapper<JYB>().eq(JYB::getJybh, query.getId()));
|
||||
if (teacher == null) {
|
||||
throw new BusinessException("教员不存在:" + query.getId());
|
||||
}
|
||||
teacherFilter = teacher.getJybh();
|
||||
targetName = (teacher.getJyxm() != null ? teacher.getJyxm() : teacher.getJybh());
|
||||
break;
|
||||
}
|
||||
case "room": {
|
||||
JSB room = classRoomMapper.selectOne(new LambdaQueryWrapper<JSB>()
|
||||
.eq(JSB::getJsbh, query.getId()).eq(JSB::getDelFlag, 0));
|
||||
if (room == null) {
|
||||
throw new BusinessException("教室不存在:" + query.getId());
|
||||
}
|
||||
roomFilter = room.getJsbh();
|
||||
targetName = room.getJsmc() != null ? room.getJsmc() : room.getJsbh();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new BusinessException("不支持的课表维度:" + dim + "(可选 semester/team/teacher/room)");
|
||||
}
|
||||
if (start == null || end == null) {
|
||||
LocalDate monday = LocalDate.now().with(DayOfWeek.MONDAY);
|
||||
if (start == null) start = monday;
|
||||
if (end == null) end = monday.plusDays(6);
|
||||
}
|
||||
if (end.isBefore(start)) {
|
||||
throw new BusinessException("结束日期不能早于开始日期");
|
||||
}
|
||||
if (nd == null) {
|
||||
nd = start.getYear();
|
||||
}
|
||||
|
||||
// ---- 模板显示开关 ----
|
||||
KCBMBB tpl = null;
|
||||
if (query.getMbbh() != null && !query.getMbbh().isEmpty()) {
|
||||
tpl = kcbmbbMapper.selectById(query.getMbbh());
|
||||
}
|
||||
|
||||
// ---- 课次与关联 ----
|
||||
List<SSKCB> lessons = sskcbMapper.selectList(new LambdaQueryWrapper<SSKCB>()
|
||||
.ge(SSKCB::getRq, start.atStartOfDay())
|
||||
.lt(SSKCB::getRq, end.plusDays(1).atStartOfDay())
|
||||
.eq(SSKCB::getSczt, 0)
|
||||
.isNotNull(SSKCB::getRq));
|
||||
List<String> lessonBhs = lessons.stream().map(SSKCB::getBh).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
List<String> courseBhs = lessons.stream().map(SSKCB::getSskcbh).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
|
||||
Map<String, List<String>> teamsOfLesson = new HashMap<>();
|
||||
Map<String, String> teamShortName = new HashMap<>(); // sskcbbh -> 简称(SSKCBXYD.jc)
|
||||
Map<String, List<String>> roomsOfLesson = new HashMap<>();
|
||||
Map<String, List<String>> teachersOfLesson = new HashMap<>();
|
||||
Map<String, String> mainTeacherOfLesson = new HashMap<>();
|
||||
if (!lessonBhs.isEmpty()) {
|
||||
for (SSKCBXYD l : sskcbxydMapper.selectList(new LambdaQueryWrapper<SSKCBXYD>()
|
||||
.in(SSKCBXYD::getSskcbbh, lessonBhs))) {
|
||||
teamsOfLesson.computeIfAbsent(l.getSskcbbh(), k -> new ArrayList<>()).add(l.getXydbh());
|
||||
if (l.getJc() != null && !l.getJc().isEmpty()) {
|
||||
teamShortName.putIfAbsent(l.getSskcbbh(), l.getJc());
|
||||
}
|
||||
}
|
||||
for (SSKCBJS l : sskcbjsMapper.selectList(new LambdaQueryWrapper<SSKCBJS>()
|
||||
.in(SSKCBJS::getSskcbbh, lessonBhs))) {
|
||||
roomsOfLesson.computeIfAbsent(l.getSskcbbh(), k -> new ArrayList<>()).add(l.getJsbh());
|
||||
}
|
||||
for (SSKCBFZJY l : sskcbfzjyMapper.selectList(new LambdaQueryWrapper<SSKCBFZJY>()
|
||||
.in(SSKCBFZJY::getSskcbbh, lessonBhs))) {
|
||||
if (l.getFzjybh() == null) continue;
|
||||
teachersOfLesson.computeIfAbsent(l.getSskcbbh(), k -> new ArrayList<>()).add(l.getFzjybh());
|
||||
if (l.getZjy() != null && l.getZjy() == 1) {
|
||||
mainTeacherOfLesson.put(l.getSskcbbh(), l.getFzjybh());
|
||||
} else {
|
||||
mainTeacherOfLesson.putIfAbsent(l.getSskcbbh(), l.getFzjybh());
|
||||
}
|
||||
}
|
||||
}
|
||||
Map<String, SSKC> courses = courseBhs.isEmpty() ? Map.of()
|
||||
: sskcMapper.selectByBhs(courseBhs).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(SSKC::getBh, c -> c, (a, b) -> a));
|
||||
Set<String> kbhs = courses.values().stream().map(SSKC::getKmbh).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
Map<String, KB> kbByKbh = kbhs.isEmpty() ? Map.of()
|
||||
: kbMapper.selectBatchIds(kbhs).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(KB::getKbh, k -> k, (a, b) -> a));
|
||||
Set<String> jybhSet = new HashSet<>();
|
||||
teachersOfLesson.values().forEach(jybhSet::addAll);
|
||||
courses.values().forEach(c -> { if (c.getJybh() != null) jybhSet.add(c.getJybh()); });
|
||||
Map<String, String> teacherNames = jybhSet.isEmpty() ? Map.of()
|
||||
: jybMapper.selectList(new LambdaQueryWrapper<JYB>().in(JYB::getJybh, jybhSet)).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(JYB::getJybh, j -> j.getJyxm() == null ? j.getJybh() : j.getJyxm(), (a, b) -> a));
|
||||
Set<String> jsbhSet = new HashSet<>();
|
||||
roomsOfLesson.values().forEach(jsbhSet::addAll);
|
||||
Map<String, String> roomNames = jsbhSet.isEmpty() ? Map.of()
|
||||
: classRoomMapper.selectList(new LambdaQueryWrapper<JSB>()
|
||||
.in(JSB::getJsbh, jsbhSet).eq(JSB::getDelFlag, 0)).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(JSB::getJsbh, j -> j.getJsmc() == null ? j.getJsbh() : j.getJsmc(), (a, b) -> a));
|
||||
Set<String> xydbhSet = new HashSet<>();
|
||||
teamsOfLesson.values().forEach(xydbhSet::addAll);
|
||||
Map<String, String> teamNames = xydbhSet.isEmpty() ? Map.of()
|
||||
: xydbMapper.selectList(new LambdaQueryWrapper<XYDB>().in(XYDB::getXydbh, xydbhSet)).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(XYDB::getXydbh, t -> t.getXydmc() == null ? t.getXydbh() : t.getXydmc(), (a, b) -> a));
|
||||
|
||||
// ---- 节次行 ----
|
||||
List<TimetableGridVO.Period> periods = buildPeriods(tpl);
|
||||
Set<Integer> periodJcs = periods.stream().map(TimetableGridVO.Period::getJc).collect(Collectors.toSet());
|
||||
Map<Integer, String> periodSd = periods.stream()
|
||||
.collect(Collectors.toMap(TimetableGridVO.Period::getJc, TimetableGridVO.Period::getSd, (a, b) -> a));
|
||||
|
||||
// ---- 历表:不可排格 + 备注栏 ----
|
||||
// 备注栏规则:晚上/夜间/周末 且 备注显示=1 的事件写入备注而非格子。
|
||||
Map<String, String> blockByCell = new HashMap<>(); // date#jc -> 事件名
|
||||
List<TimetableGridVO.Remark> remarks = new ArrayList<>();
|
||||
if (nd != null) {
|
||||
for (XQXLB g : semesterCalendarMapper.selectList(new LambdaQueryWrapper<XQXLB>().eq(XQXLB::getNd, nd))) {
|
||||
applyCalendarRow(g.getJqsj(), g.getCourseClass(), g.getKpk() != null && g.getKpk(),
|
||||
Boolean.TRUE.equals(g.getBzxs()), joinName(g.getJqmc(), g.getBz()),
|
||||
start, end, periodSd, blockByCell, remarks, "校历");
|
||||
}
|
||||
}
|
||||
if (semesterBh != null) {
|
||||
for (JQB g : jqbMapper.selectList(new LambdaQueryWrapper<JQB>().eq(JQB::getXydxqbh, semesterBh))) {
|
||||
String dateStr = g.getJqsj() == null ? null : intToDay(g.getJqsj());
|
||||
applyCalendarRow(dateStr, g.getCourseClass(), g.getKpk() != null && g.getKpk() == 1,
|
||||
g.getBzxs() != null && g.getBzxs() == 1, joinName(g.getJqmc(), g.getBz()),
|
||||
start, end, periodSd, blockByCell, remarks, "班历");
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 组装网格 ----
|
||||
TimetableGridVO vo = new TimetableGridVO();
|
||||
vo.setDim(dim);
|
||||
vo.setTargetName(targetName);
|
||||
vo.setNd(nd);
|
||||
vo.setStartDate(start.format(DAY));
|
||||
vo.setEndDate(end.format(DAY));
|
||||
vo.setPeriods(periods);
|
||||
vo.setRemarks(remarks);
|
||||
|
||||
// 课次按 date#jc 归并(先按维度过滤)
|
||||
Map<String, List<SSKCB>> lessonByCell = new HashMap<>();
|
||||
for (SSKCB l : lessons) {
|
||||
if (!matchDimension(l, dim, teamFilter, teacherFilter, roomFilter, teamsOfLesson, teachersOfLesson, roomsOfLesson, courses)) {
|
||||
continue;
|
||||
}
|
||||
SSKC course = courses.get(l.getSskcbh());
|
||||
KB kb = course == null ? null : kbByKbh.get(course.getKmbh());
|
||||
if (tpl != null && Boolean.FALSE.equals(tpl.getKBBHXX())
|
||||
&& kb != null && kb.getKclx() != null && kb.getKclx().contains("选修")) {
|
||||
continue;
|
||||
}
|
||||
if (l.getJc() == null || !periodJcs.contains(l.getJc())) {
|
||||
continue;
|
||||
}
|
||||
lessonByCell.computeIfAbsent(l.getRq().toLocalDate().format(DAY) + "#" + l.getJc(), k -> new ArrayList<>()).add(l);
|
||||
}
|
||||
|
||||
LocalDate weekStart = start.with(DayOfWeek.MONDAY);
|
||||
int weekNo = 1;
|
||||
while (!weekStart.isAfter(end)) {
|
||||
TimetableGridVO.Week week = new TimetableGridVO.Week();
|
||||
week.setWeekNo(weekNo++);
|
||||
week.setStartDate(weekStart.format(DAY));
|
||||
week.setEndDate(weekStart.plusDays(6).format(DAY));
|
||||
for (int wd = 1; wd <= 7; wd++) {
|
||||
LocalDate date = weekStart.plusDays(wd - 1);
|
||||
TimetableGridVO.Day day = new TimetableGridVO.Day();
|
||||
day.setDate(date.format(DAY));
|
||||
day.setWeekday(wd);
|
||||
if (tpl != null && tpl.getZXXSTS() != null && tpl.getZXXSTS() <= 5 && wd > tpl.getZXXSTS()) {
|
||||
// 模板「纵向显示天数」截断周末列
|
||||
continue;
|
||||
}
|
||||
if (date.isBefore(start) || date.isAfter(end)) {
|
||||
day.setBlocked(true);
|
||||
day.setBlockReason("范围外");
|
||||
week.getDays().add(day);
|
||||
continue;
|
||||
}
|
||||
boolean dayBlocked = false;
|
||||
String reason = null;
|
||||
for (Integer jc : periodJcs) {
|
||||
String key = date.format(DAY) + "#" + jc;
|
||||
String block = blockByCell.get(key);
|
||||
if (block != null) {
|
||||
dayBlocked = true;
|
||||
reason = block;
|
||||
}
|
||||
List<SSKCB> cellLessons = lessonByCell.get(key);
|
||||
if (cellLessons == null) continue;
|
||||
for (SSKCB l : cellLessons) {
|
||||
day.getCells().computeIfAbsent(String.valueOf(jc), k -> new ArrayList<>())
|
||||
.add(buildCell(l, dim, courses, kbByKbh, teamShortName, teacherNames,
|
||||
roomNames, teamNames, mainTeacherOfLesson, roomsOfLesson, teamsOfLesson));
|
||||
}
|
||||
}
|
||||
day.setBlocked(dayBlocked);
|
||||
day.setBlockReason(reason);
|
||||
week.getDays().add(day);
|
||||
}
|
||||
vo.getWeeks().add(week);
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportGrid(TimetableGridQuery query) {
|
||||
TimetableGridVO vo = grid(query);
|
||||
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
CellStyle head = wb.createCellStyle();
|
||||
Font hf = wb.createFont();
|
||||
hf.setBold(true);
|
||||
head.setFont(hf);
|
||||
head.setAlignment(HorizontalAlignment.CENTER);
|
||||
head.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
CellStyle center = wb.createCellStyle();
|
||||
center.setAlignment(HorizontalAlignment.CENTER);
|
||||
center.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
center.setWrapText(true);
|
||||
CellStyle title = wb.createCellStyle();
|
||||
Font tf = wb.createFont();
|
||||
tf.setBold(true);
|
||||
tf.setFontHeightInPoints((short) 14);
|
||||
title.setFont(tf);
|
||||
title.setAlignment(HorizontalAlignment.CENTER);
|
||||
|
||||
for (TimetableGridVO.Week week : vo.getWeeks()) {
|
||||
org.apache.poi.ss.usermodel.Sheet sheet = wb.createSheet("第" + week.getWeekNo() + "周");
|
||||
int r = 0;
|
||||
org.apache.poi.ss.usermodel.Row titleRow = sheet.createRow(r++);
|
||||
org.apache.poi.ss.usermodel.Cell tc = titleRow.createCell(0);
|
||||
tc.setCellValue(vo.getTargetName() + " 课表(" + week.getStartDate() + " ~ " + week.getEndDate() + ")");
|
||||
tc.setCellStyle(title);
|
||||
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, 7));
|
||||
|
||||
org.apache.poi.ss.usermodel.Row headRow = sheet.createRow(r++);
|
||||
String[] heads = {"节次", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
|
||||
for (int i = 0; i < heads.length; i++) {
|
||||
org.apache.poi.ss.usermodel.Cell c = headRow.createCell(i);
|
||||
c.setCellValue(heads[i]);
|
||||
c.setCellStyle(head);
|
||||
}
|
||||
for (TimetableGridVO.Period p : vo.getPeriods()) {
|
||||
org.apache.poi.ss.usermodel.Row row = sheet.createRow(r++);
|
||||
org.apache.poi.ss.usermodel.Cell pc = row.createCell(0);
|
||||
pc.setCellValue(p.getMc() + (p.getKssj() != null ? "\n" + p.getKssj() + "-" + p.getJssj() : ""));
|
||||
pc.setCellStyle(head);
|
||||
for (TimetableGridVO.Day day : week.getDays()) {
|
||||
org.apache.poi.ss.usermodel.Cell c = row.createCell(day.getWeekday());
|
||||
List<TimetableGridVO.Cell> cells = day.getCells().get(String.valueOf(p.getJc()));
|
||||
if (cells != null && !cells.isEmpty()) {
|
||||
c.setCellValue(cells.stream().map(this::cellText).collect(Collectors.joining("\n")));
|
||||
}
|
||||
c.setCellStyle(center);
|
||||
}
|
||||
}
|
||||
if (!vo.getRemarks().isEmpty()) {
|
||||
r++;
|
||||
org.apache.poi.ss.usermodel.Row rh = sheet.createRow(r++);
|
||||
org.apache.poi.ss.usermodel.Cell rc = rh.createCell(0);
|
||||
rc.setCellValue("备注栏");
|
||||
rc.setCellStyle(head);
|
||||
for (TimetableGridVO.Remark rm : vo.getRemarks()) {
|
||||
org.apache.poi.ss.usermodel.Row rr = sheet.createRow(r++);
|
||||
org.apache.poi.ss.usermodel.Cell cc = rr.createCell(0);
|
||||
cc.setCellValue(rm.getDate() + " " + rm.getScope() + ":" + rm.getText());
|
||||
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(rr.getRowNum(), rr.getRowNum(), 0, 7));
|
||||
}
|
||||
}
|
||||
sheet.setColumnWidth(0, 14 * 256);
|
||||
for (int i = 1; i <= 7; i++) {
|
||||
sheet.setColumnWidth(i, 22 * 256);
|
||||
}
|
||||
}
|
||||
if (vo.getWeeks().isEmpty()) {
|
||||
wb.createSheet("课表").createRow(0).createCell(0).setCellValue("范围内无课表数据");
|
||||
}
|
||||
wb.write(out);
|
||||
return out.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new BusinessException("课表导出失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 内部方法 ====================
|
||||
|
||||
private String cellText(TimetableGridVO.Cell c) {
|
||||
StringBuilder sb = new StringBuilder(c.getKcjc() != null && !c.getKcjc().isEmpty() ? c.getKcjc() : c.getKcmc());
|
||||
List<String> extra = new ArrayList<>();
|
||||
if (c.getJyxm() != null && !c.getJyxm().isEmpty()) extra.add(c.getJyxm());
|
||||
if (c.getJsmc() != null && !c.getJsmc().isEmpty()) extra.add(c.getJsmc());
|
||||
if (c.getXydmc() != null && !c.getXydmc().isEmpty()) extra.add(c.getXydmc());
|
||||
if (!extra.isEmpty()) sb.append("\n(").append(String.join(" ", extra)).append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private TimetableGridVO.Cell buildCell(SSKCB l, String dim,
|
||||
Map<String, SSKC> courses, Map<String, KB> kbByKbh,
|
||||
Map<String, String> teamShortName,
|
||||
Map<String, String> teacherNames, Map<String, String> roomNames,
|
||||
Map<String, String> teamNames,
|
||||
Map<String, String> mainTeacherOfLesson,
|
||||
Map<String, List<String>> roomsOfLesson,
|
||||
Map<String, List<String>> teamsOfLesson) {
|
||||
TimetableGridVO.Cell cell = new TimetableGridVO.Cell();
|
||||
cell.setSskcbbh(l.getBh());
|
||||
cell.setSskcbh(l.getSskcbh());
|
||||
SSKC course = courses.get(l.getSskcbh());
|
||||
KB kb = course == null ? null : kbByKbh.get(course.getKmbh());
|
||||
cell.setKcmc(kb != null && kb.getKmc() != null ? kb.getKmc() : l.getJxnr());
|
||||
cell.setKclx(kb == null ? null : kb.getKclx());
|
||||
// 简称优先:课次-学员队关联上的简称 > 课表.简称 > 课程名称
|
||||
String shortName = teamShortName.get(l.getBh());
|
||||
if (shortName == null || shortName.isEmpty()) {
|
||||
shortName = kb != null && kb.getJc() != null && !kb.getJc().isEmpty() ? kb.getJc() : cell.getKcmc();
|
||||
}
|
||||
cell.setKcjc(shortName);
|
||||
String mainJybh = mainTeacherOfLesson.get(l.getBh());
|
||||
if (mainJybh == null && course != null) {
|
||||
mainJybh = course.getJybh();
|
||||
}
|
||||
cell.setJyxm(mainJybh == null ? null : teacherNames.getOrDefault(mainJybh, mainJybh));
|
||||
cell.setJsmc(roomsOfLesson.getOrDefault(l.getBh(), List.of()).stream()
|
||||
.map(b -> roomNames.getOrDefault(b, b)).distinct().collect(Collectors.joining("、")));
|
||||
cell.setXydmc(teamsOfLesson.getOrDefault(l.getBh(), List.of()).stream()
|
||||
.map(b -> teamNames.getOrDefault(b, b)).distinct().collect(Collectors.joining("、")));
|
||||
return cell;
|
||||
}
|
||||
|
||||
private boolean matchDimension(SSKCB l, String dim, String teamFilter, String teacherFilter, String roomFilter,
|
||||
Map<String, List<String>> teamsOfLesson,
|
||||
Map<String, List<String>> teachersOfLesson,
|
||||
Map<String, List<String>> roomsOfLesson,
|
||||
Map<String, SSKC> courses) {
|
||||
switch (dim) {
|
||||
case "semester":
|
||||
case "team":
|
||||
return teamsOfLesson.getOrDefault(l.getBh(), List.of()).contains(teamFilter);
|
||||
case "teacher": {
|
||||
if (teachersOfLesson.getOrDefault(l.getBh(), List.of()).contains(teacherFilter)) {
|
||||
return true;
|
||||
}
|
||||
SSKC c = courses.get(l.getSskcbh());
|
||||
return c != null && (teacherFilter.equals(c.getJybh()) || teacherFilter.equals(c.getZrjybh()));
|
||||
}
|
||||
case "room":
|
||||
return roomsOfLesson.getOrDefault(l.getBh(), List.of()).contains(roomFilter);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 历表一行 → 不可排格标记或备注栏条目。
|
||||
* 备注栏规则:晚上/夜间时段或周末 且 备注显示 → remarks;可排课=0 → blockByCell。
|
||||
*/
|
||||
private void applyCalendarRow(String dateStr, String courseClass, boolean schedulable, boolean bzxs,
|
||||
String text, LocalDate start, LocalDate end,
|
||||
Map<Integer, String> periodSd,
|
||||
Map<String, String> blockByCell,
|
||||
List<TimetableGridVO.Remark> remarks, String scope) {
|
||||
if (dateStr == null) {
|
||||
return;
|
||||
}
|
||||
LocalDate day;
|
||||
try {
|
||||
day = LocalDate.parse(dateStr.substring(0, 10), DAY);
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
if (day.isBefore(start) || day.isAfter(end)) {
|
||||
return;
|
||||
}
|
||||
boolean weekend = day.getDayOfWeek() == DayOfWeek.SATURDAY || day.getDayOfWeek() == DayOfWeek.SUNDAY;
|
||||
List<Integer> jcs = PeriodUtil.parsePeriods(courseClass);
|
||||
boolean evening = jcs.stream().anyMatch(jc -> {
|
||||
String sd = periodSd.getOrDefault(jc, "");
|
||||
return "晚上".equals(sd) || "夜间".equals(sd);
|
||||
});
|
||||
if (bzxs && (weekend || evening) && text != null && !text.isEmpty()) {
|
||||
TimetableGridVO.Remark rm = new TimetableGridVO.Remark();
|
||||
rm.setDate(day.format(DAY));
|
||||
rm.setScope(scope);
|
||||
rm.setText(text);
|
||||
remarks.add(rm);
|
||||
return;
|
||||
}
|
||||
if (!schedulable) {
|
||||
for (Integer jc : jcs) {
|
||||
blockByCell.put(day.format(DAY) + "#" + jc, text == null ? "不可排课" : text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 节次行:节次时间表单节次行优先,空表回退 1-12;再套模板显示开关 */
|
||||
private List<TimetableGridVO.Period> buildPeriods(KCBMBB tpl) {
|
||||
List<TimetableGridVO.Period> periods = new ArrayList<>();
|
||||
List<JCSJB> rows = classPeriodService.singleRows();
|
||||
if (rows.isEmpty()) {
|
||||
for (int jc = 1; jc <= 12; jc++) {
|
||||
TimetableGridVO.Period p = new TimetableGridVO.Period();
|
||||
p.setJc(jc);
|
||||
p.setMc("第" + jc + "节");
|
||||
p.setSd(defaultSd(jc));
|
||||
periods.add(p);
|
||||
}
|
||||
} else {
|
||||
for (JCSJB row : rows) {
|
||||
for (Integer jc : PeriodUtil.parsePeriods(row.getJcsy())) {
|
||||
TimetableGridVO.Period p = new TimetableGridVO.Period();
|
||||
p.setJc(jc);
|
||||
p.setMc(row.getMc() != null ? row.getMc() : "第" + jc + "节");
|
||||
p.setSd(row.getSd() != null ? row.getSd() : defaultSd(jc));
|
||||
p.setKssj(trimTime(row.getKssj()));
|
||||
p.setJssj(trimTime(row.getJssj()));
|
||||
periods.add(p);
|
||||
}
|
||||
}
|
||||
periods.sort(Comparator.comparingInt(TimetableGridVO.Period::getJc));
|
||||
}
|
||||
if (tpl != null) {
|
||||
periods = periods.stream().filter(p -> {
|
||||
if (Boolean.FALSE.equals(tpl.getZXXS78J()) && (p.getJc() == 7 || p.getJc() == 8)) return false;
|
||||
if (Boolean.FALSE.equals(tpl.getZXXSWS()) && "晚上".equals(p.getSd())) return false;
|
||||
if (Boolean.FALSE.equals(tpl.getXSYJ()) && "夜间".equals(p.getSd())) return false;
|
||||
return true;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
return periods;
|
||||
}
|
||||
|
||||
private static String defaultSd(int jc) {
|
||||
if (jc <= 4) return "上午";
|
||||
if (jc <= 8) return "下午";
|
||||
if (jc <= 10) return "晚上";
|
||||
return "夜间";
|
||||
}
|
||||
|
||||
private static String trimTime(String t) {
|
||||
if (t == null) return null;
|
||||
String s = t.trim();
|
||||
return s.length() > 5 ? s.substring(0, 5) : s;
|
||||
}
|
||||
|
||||
private static String joinName(String mc, String bz) {
|
||||
if (mc == null || mc.isEmpty()) return bz;
|
||||
if (bz == null || bz.isEmpty()) return mc;
|
||||
return mc + "(" + bz + ")";
|
||||
}
|
||||
|
||||
private static String intToDay(Integer yyyymmdd) {
|
||||
String s = String.valueOf(yyyymmdd);
|
||||
if (s.length() != 8) return null;
|
||||
return s.substring(0, 4) + "-" + s.substring(4, 6) + "-" + s.substring(6);
|
||||
}
|
||||
|
||||
private static LocalDate parseDay(String s) {
|
||||
if (s == null || s.trim().isEmpty()) return null;
|
||||
try {
|
||||
return LocalDate.parse(s.trim().substring(0, 10), DAY);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveTeamName(String xydbh) {
|
||||
if (xydbh == null) return "";
|
||||
XYDB team = xydbMapper.selectOne(new LambdaQueryWrapper<XYDB>().eq(XYDB::getXydbh, xydbh));
|
||||
return team != null && team.getXydmc() != null ? team.getXydmc() : xydbh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.roomroot.jwgl.vo.kcb;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 周次横版课表网格。
|
||||
* <p>weeks 按周纵向排列;每周内 行=节次、列=星期一~日;
|
||||
* 晚上/夜间/周末且「备注显示」的历表事件进 remarks,不占格子。</p>
|
||||
*/
|
||||
@Data
|
||||
public class TimetableGridVO {
|
||||
|
||||
/** 维度 */
|
||||
private String dim;
|
||||
|
||||
/** 维度目标显示名(标题用) */
|
||||
private String targetName;
|
||||
|
||||
/** 年度 */
|
||||
private Integer nd;
|
||||
|
||||
private String startDate;
|
||||
private String endDate;
|
||||
|
||||
/** 节次行定义(升序) */
|
||||
private List<Period> periods = new ArrayList<>();
|
||||
|
||||
/** 周块 */
|
||||
private List<Week> weeks = new ArrayList<>();
|
||||
|
||||
/** 备注栏条目(历表「备注显示」且落在晚上/夜间/周末的事件) */
|
||||
private List<Remark> remarks = new ArrayList<>();
|
||||
|
||||
@Data
|
||||
public static class Period {
|
||||
/** 节次序号 */
|
||||
private Integer jc;
|
||||
/** 显示名(如 第1节 / 第1-2节) */
|
||||
private String mc;
|
||||
/** 时段:上午/下午/晚上/夜间 */
|
||||
private String sd;
|
||||
/** 开始时间 HH:mm */
|
||||
private String kssj;
|
||||
/** 结束时间 HH:mm */
|
||||
private String jssj;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Week {
|
||||
private Integer weekNo;
|
||||
private String startDate;
|
||||
private String endDate;
|
||||
/** 固定 7 天(周一~周日) */
|
||||
private List<Day> days = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Day {
|
||||
private String date;
|
||||
/** 1=周一 … 7=周日 */
|
||||
private Integer weekday;
|
||||
/** 校历/班历标记不可排课(该日所有节次或部分节次不可排时为 true) */
|
||||
private Boolean blocked = false;
|
||||
/** 不可排说明(如假期名称) */
|
||||
private String blockReason;
|
||||
/** key=节次序号字符串 */
|
||||
private Map<String, List<Cell>> cells = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Cell {
|
||||
private String sskcbbh;
|
||||
private String sskcbh;
|
||||
/** 课程名称 */
|
||||
private String kcmc;
|
||||
/** 课程简称(格子主显示) */
|
||||
private String kcjc;
|
||||
/** 主讲教员姓名 */
|
||||
private String jyxm;
|
||||
/** 场地名称 */
|
||||
private String jsmc;
|
||||
/** 班次/学员队名(教员、教室维度下展示) */
|
||||
private String xydmc;
|
||||
/** 课程类型(选修过滤用) */
|
||||
private String kclx;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Remark {
|
||||
private String date;
|
||||
/** 来源:校历 / 班历 */
|
||||
private String scope;
|
||||
/** 备注文本(事件名 + 备注) */
|
||||
private String text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user