diff --git a/3.教学实施计划编排系统操作手册.pdf b/3.教学实施计划编排系统操作手册.pdf new file mode 100755 index 000000000..d60ec43e7 Binary files /dev/null and b/3.教学实施计划编排系统操作手册.pdf differ diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java index 82328f135..dd0f2febf 100644 --- a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java @@ -1,17 +1,24 @@ package com.roomroot.web.controller.jwgl; +import com.roomroot.jwgl.dto.kcb.TimetableGridQuery; import com.roomroot.jwgl.dto.kcb.TimetableQuery; import com.roomroot.jwgl.entity.KCB; import com.roomroot.jwgl.service.KCBService; +import com.roomroot.jwgl.service.TimetableGridService; import com.roomroot.jwgl.unit.PageQuery; import com.roomroot.jwgl.unit.PageResult; import com.roomroot.jwgl.unit.Result; import com.roomroot.jwgl.vo.kcb.ClassTimetableVO; import com.roomroot.jwgl.vo.kcb.DailyWeeklyTimetableRangeVO; +import com.roomroot.jwgl.vo.kcb.TimetableGridVO; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import jakarta.annotation.Resource; +import java.nio.charset.StandardCharsets; import java.util.List; /** @@ -24,6 +31,9 @@ public class KCBController { @Resource private KCBService kcbService; + @Resource + private TimetableGridService timetableGridService; + /** * 新增 */ @@ -133,4 +143,27 @@ public class KCBController { public void exportClassCourses(TimetableQuery cond, HttpServletResponse response) { kcbService.exportClassCourses(cond, response); } + + /** + * 周次横版课表网格(手册一第12章)。 + *

dim=semester|team|teacher|room,id 为对应编号;可传 mbbh 套用课表模板。

+ */ + @GetMapping("/grid") + public Result grid(TimetableGridQuery query) { + return Result.success(timetableGridService.grid(query)); + } + + /** + * 网格课表矩阵 Excel 导出(每周一个 sheet,节次 × 星期)。 + */ + @GetMapping("/grid/export") + public ResponseEntity exportGrid(TimetableGridQuery query) { + byte[] bytes = timetableGridService.exportGrid(query); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + String name = "课表_" + query.getDim() + ".xlsx"; + headers.setContentDispositionFormData("attachment", + new String(name.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)); + return ResponseEntity.ok().headers(headers).body(bytes); + } } diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBMBController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBMBController.java new file mode 100644 index 000000000..6998600b2 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBMBController.java @@ -0,0 +1,63 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.KCBMBB; +import com.roomroot.jwgl.service.KCBMBService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 课表模版管理(手册一第12章;CRUD + 启停 + 下拉选项) + */ +@RestController +@RequestMapping("/kcbmb") +public class KCBMBController { + + @Resource + private KCBMBService kcbmbService; + + @GetMapping("/list") + public Result> list(PageQuery query, @RequestParam(required = false) String mbmc) { + return Result.success(kcbmbService.page(query, mbmc)); + } + + @GetMapping("/get") + public Result get(@RequestParam("bh") String bh) { + return Result.success(kcbmbService.get(bh)); + } + + /** 可用模板下拉(课表生成页套用选择) */ + @GetMapping("/options") + public Result> options() { + return Result.success(kcbmbService.options()); + } + + @PostMapping("/add") + public Result add(@RequestBody KCBMBB tpl) { + kcbmbService.add(tpl); + return Result.success(); + } + + @PostMapping("/update") + public Result update(@RequestBody KCBMBB tpl) { + kcbmbService.update(tpl); + return Result.success(); + } + + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + kcbmbService.delete(bh); + return Result.success(); + } + + /** 启用/停用模版 */ + @PostMapping("/toggle") + public Result toggle(@RequestParam("bh") String bh, @RequestParam("enabled") boolean enabled) { + kcbmbService.toggle(bh, enabled); + return Result.success(); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/kcb/TimetableGridQuery.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/kcb/TimetableGridQuery.java new file mode 100644 index 000000000..b20569598 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/kcb/TimetableGridQuery.java @@ -0,0 +1,30 @@ +package com.roomroot.jwgl.dto.kcb; + +import lombok.Data; + +/** + * 课表网格查询参数(手册一第12章 课表生成)。 + *

dim 维度:semester 班次学期 / team 学员队 / teacher 教员 / room 教室。 + * id 为对应维度主键(xydxqbh / xydbh / jybh / jsbh)。

+ */ +@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; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassPeriodService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassPeriodService.java index e5cf0e342..f49f34505 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassPeriodService.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassPeriodService.java @@ -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 mainSlotLabels(); + + /** + * 单节次时间行(双节次=0),按节次序号升序;表内无数据时返回空, + * 调用方回退默认 1–12 节。 + */ + List singleRows(); } diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/KCBMBService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/KCBMBService.java new file mode 100644 index 000000000..5905b2282 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/KCBMBService.java @@ -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 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 options(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TimetableGridService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TimetableGridService.java new file mode 100644 index 000000000..6a8597683 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TimetableGridService.java @@ -0,0 +1,21 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.kcb.TimetableGridQuery; +import com.roomroot.jwgl.vo.kcb.TimetableGridVO; + +/** + * 课表网格生成(手册一第12章)。 + *

按 班次学期/学员队/教员/教室 四个维度生成「周次 × 星期 × 节次」横版网格。

+ */ +public interface TimetableGridService { + + /** + * 生成课表网格数据。 + */ + TimetableGridVO grid(TimetableGridQuery query); + + /** + * 导出网格课表为矩阵 Excel(每周一个 sheet)。 + */ + byte[] exportGrid(TimetableGridQuery query); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassPeriodServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassPeriodServiceImpl.java index 8d7be1f65..d2cb1c0a5 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassPeriodServiceImpl.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassPeriodServiceImpl.java @@ -51,6 +51,14 @@ public class ClassPeriodServiceImpl implements ClassPeriodService { return main.isEmpty() ? DEFAULT_MAIN_SLOTS : main; } + @Override + public List singleRows() { + List rows = jcsjbMapper.selectList(new LambdaQueryWrapper() + .eq(JCSJB::getSjc, false)); + rows.sort(Comparator.comparingInt(r -> PeriodUtil.firstPeriod(r.getJcsy()))); + return rows; + } + private List loadSlots() { List rows = jcsjbMapper.selectList(new LambdaQueryWrapper() .eq(JCSJB::getSjc, true)); diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/KCBMBServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/KCBMBServiceImpl.java new file mode 100644 index 000000000..17d4260d5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/KCBMBServiceImpl.java @@ -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 page(PageQuery query, String mbmc) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper qw = new LambdaQueryWrapper() + .like(mbmc != null && !mbmc.isEmpty(), KCBMBB::getMBMC, mbmc) + .orderByDesc(KCBMBB::getGXSJ); + Page 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 options() { + return kcbmbbMapper.selectList(new LambdaQueryWrapper() + .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()); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TimetableGridServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TimetableGridServiceImpl.java new file mode 100644 index 000000000..3474e4c54 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TimetableGridServiceImpl.java @@ -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; + +/** + * 课表网格生成实现。 + *

课次一律从 实施_课程表(SSKCB) 按日期范围取;维度过滤经三张关联子表 + * (学员队 / 教室 / 辅助教员)在内存归并,避免 N+1 与达梦 GUID 双形态问题。

+ */ +@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().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().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() + .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 lessons = sskcbMapper.selectList(new LambdaQueryWrapper() + .ge(SSKCB::getRq, start.atStartOfDay()) + .lt(SSKCB::getRq, end.plusDays(1).atStartOfDay()) + .eq(SSKCB::getSczt, 0) + .isNotNull(SSKCB::getRq)); + List lessonBhs = lessons.stream().map(SSKCB::getBh).filter(Objects::nonNull).distinct().collect(Collectors.toList()); + List courseBhs = lessons.stream().map(SSKCB::getSskcbh).filter(Objects::nonNull).distinct().collect(Collectors.toList()); + + Map> teamsOfLesson = new HashMap<>(); + Map teamShortName = new HashMap<>(); // sskcbbh -> 简称(SSKCBXYD.jc) + Map> roomsOfLesson = new HashMap<>(); + Map> teachersOfLesson = new HashMap<>(); + Map mainTeacherOfLesson = new HashMap<>(); + if (!lessonBhs.isEmpty()) { + for (SSKCBXYD l : sskcbxydMapper.selectList(new LambdaQueryWrapper() + .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() + .in(SSKCBJS::getSskcbbh, lessonBhs))) { + roomsOfLesson.computeIfAbsent(l.getSskcbbh(), k -> new ArrayList<>()).add(l.getJsbh()); + } + for (SSKCBFZJY l : sskcbfzjyMapper.selectList(new LambdaQueryWrapper() + .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 courses = courseBhs.isEmpty() ? Map.of() + : sskcMapper.selectByBhs(courseBhs).stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(SSKC::getBh, c -> c, (a, b) -> a)); + Set kbhs = courses.values().stream().map(SSKC::getKmbh).filter(Objects::nonNull).collect(Collectors.toSet()); + Map kbByKbh = kbhs.isEmpty() ? Map.of() + : kbMapper.selectBatchIds(kbhs).stream() + .filter(Objects::nonNull) + .collect(Collectors.toMap(KB::getKbh, k -> k, (a, b) -> a)); + Set jybhSet = new HashSet<>(); + teachersOfLesson.values().forEach(jybhSet::addAll); + courses.values().forEach(c -> { if (c.getJybh() != null) jybhSet.add(c.getJybh()); }); + Map teacherNames = jybhSet.isEmpty() ? Map.of() + : jybMapper.selectList(new LambdaQueryWrapper().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 jsbhSet = new HashSet<>(); + roomsOfLesson.values().forEach(jsbhSet::addAll); + Map roomNames = jsbhSet.isEmpty() ? Map.of() + : classRoomMapper.selectList(new LambdaQueryWrapper() + .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 xydbhSet = new HashSet<>(); + teamsOfLesson.values().forEach(xydbhSet::addAll); + Map teamNames = xydbhSet.isEmpty() ? Map.of() + : xydbMapper.selectList(new LambdaQueryWrapper().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 periods = buildPeriods(tpl); + Set periodJcs = periods.stream().map(TimetableGridVO.Period::getJc).collect(Collectors.toSet()); + Map periodSd = periods.stream() + .collect(Collectors.toMap(TimetableGridVO.Period::getJc, TimetableGridVO.Period::getSd, (a, b) -> a)); + + // ---- 历表:不可排格 + 备注栏 ---- + // 备注栏规则:晚上/夜间/周末 且 备注显示=1 的事件写入备注而非格子。 + Map blockByCell = new HashMap<>(); // date#jc -> 事件名 + List remarks = new ArrayList<>(); + if (nd != null) { + for (XQXLB g : semesterCalendarMapper.selectList(new LambdaQueryWrapper().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().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> 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 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 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 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 courses, Map kbByKbh, + Map teamShortName, + Map teacherNames, Map roomNames, + Map teamNames, + Map mainTeacherOfLesson, + Map> roomsOfLesson, + Map> 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> teamsOfLesson, + Map> teachersOfLesson, + Map> roomsOfLesson, + Map 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 periodSd, + Map blockByCell, + List 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 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 buildPeriods(KCBMBB tpl) { + List periods = new ArrayList<>(); + List 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().eq(XYDB::getXydbh, xydbh)); + return team != null && team.getXydmc() != null ? team.getXydmc() : xydbh; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/kcb/TimetableGridVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/kcb/TimetableGridVO.java new file mode 100644 index 000000000..e99bbc917 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/kcb/TimetableGridVO.java @@ -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; + +/** + * 周次横版课表网格。 + *

weeks 按周纵向排列;每周内 行=节次、列=星期一~日; + * 晚上/夜间/周末且「备注显示」的历表事件进 remarks,不占格子。

+ */ +@Data +public class TimetableGridVO { + + /** 维度 */ + private String dim; + + /** 维度目标显示名(标题用) */ + private String targetName; + + /** 年度 */ + private Integer nd; + + private String startDate; + private String endDate; + + /** 节次行定义(升序) */ + private List periods = new ArrayList<>(); + + /** 周块 */ + private List weeks = new ArrayList<>(); + + /** 备注栏条目(历表「备注显示」且落在晚上/夜间/周末的事件) */ + private List 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 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> 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; + } +} diff --git a/frontend/src/api/teachBusiness/kcbmb.js b/frontend/src/api/teachBusiness/kcbmb.js new file mode 100644 index 000000000..2a527ed10 --- /dev/null +++ b/frontend/src/api/teachBusiness/kcbmb.js @@ -0,0 +1,65 @@ +import request from '@/utils/request' + +// 课表模版管理(KCBMBController /kcbmb,手册一第12章) + +/** 分页查询模版列表 GET /kcbmb/list(pageNum/pageSize/mbmc) */ +export function listKcbmb(params) { + return request({ + url: '/kcbmb/list', + method: 'get', + params + }) +} + +/** 模版详情 GET /kcbmb/get?bh= */ +export function getKcbmb(bh) { + return request({ + url: '/kcbmb/get', + method: 'get', + params: { bh } + }) +} + +/** 可用模版下拉 GET /kcbmb/options → [{bh, mbmc}] */ +export function listKcbmbOptions() { + return request({ + url: '/kcbmb/options', + method: 'get' + }) +} + +/** 新增模版 POST /kcbmb/add */ +export function addKcbmb(data) { + return request({ + url: '/kcbmb/add', + method: 'post', + data + }) +} + +/** 更新模版 POST /kcbmb/update */ +export function updateKcbmb(data) { + return request({ + url: '/kcbmb/update', + method: 'post', + data + }) +} + +/** 删除模版 POST /kcbmb/delete?bh= */ +export function deleteKcbmb(bh) { + return request({ + url: '/kcbmb/delete', + method: 'post', + params: { bh } + }) +} + +/** 启用/停用模版 POST /kcbmb/toggle?bh=&enabled= */ +export function toggleKcbmb(bh, enabled) { + return request({ + url: '/kcbmb/toggle', + method: 'post', + params: { bh, enabled } + }) +} diff --git a/frontend/src/api/teachBusiness/timetable.js b/frontend/src/api/teachBusiness/timetable.js index b0907b978..94bb915c7 100644 --- a/frontend/src/api/teachBusiness/timetable.js +++ b/frontend/src/api/teachBusiness/timetable.js @@ -87,3 +87,30 @@ export function exportClassCourses(data) { responseType: 'blob' }) } + +/** + * 周次横版课表网格(手册一第12章) + * GET /kcb/grid?dim=&id=&nd=&start=&end=&mbbh= + * dim: semester 班次学期 | team 学员队 | teacher 教员 | room 教室 + * 返回 TimetableGridVO:periods / weeks[].days[].cells / remarks + */ +export function getTimetableGrid(params) { + return request({ + url: '/kcb/grid', + method: 'get', + params + }) +} + +/** + * 网格课表矩阵 Excel 导出(每周一个 sheet) + * GET /kcb/grid/export(参数同 getTimetableGrid) + */ +export function exportTimetableGrid(params) { + return request({ + url: '/kcb/grid/export', + method: 'get', + params, + responseType: 'blob' + }) +} diff --git a/frontend/src/components/TimetableGridView/index.vue b/frontend/src/components/TimetableGridView/index.vue new file mode 100644 index 000000000..e6411c9b8 --- /dev/null +++ b/frontend/src/components/TimetableGridView/index.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/frontend/src/views/teachBusiness/timetableGrid/index.vue b/frontend/src/views/teachBusiness/timetableGrid/index.vue new file mode 100644 index 000000000..65ec40573 --- /dev/null +++ b/frontend/src/views/teachBusiness/timetableGrid/index.vue @@ -0,0 +1,259 @@ + + + + + diff --git a/frontend/src/views/teachBusiness/timetableTemplate/index.vue b/frontend/src/views/teachBusiness/timetableTemplate/index.vue new file mode 100644 index 000000000..157852ef5 --- /dev/null +++ b/frontend/src/views/teachBusiness/timetableTemplate/index.vue @@ -0,0 +1,292 @@ + + + + +