1、校历的78、晚上、夜间等直接落库,
2、配当、任务书完善、排课窗完善; 3、任务书批量发布、删除等; 4、班历同步校历
This commit is contained in:
+5
-3
@@ -47,12 +47,14 @@ public class ClassEventCalendarController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新同步校历:confirm=false 返回缺失格预览,confirm=true 实际补齐(只补缺失,不覆盖人工改动)
|
||||
* 重新同步校历:confirm=false 返回差异预览,confirm=true 实际写入;
|
||||
* force=true 时额外覆盖与校历不一致的已有格。
|
||||
*/
|
||||
@PostMapping("/resync")
|
||||
public Result<ClassCalendarResyncVO> resync(@RequestParam("xydxqbh") String xydxqbh,
|
||||
@RequestParam(value = "confirm", defaultValue = "false") boolean confirm) {
|
||||
return Result.success(classEventCalendarService.resync(xydxqbh, confirm));
|
||||
@RequestParam(value = "confirm", defaultValue = "false") boolean confirm,
|
||||
@RequestParam(value = "force", defaultValue = "false") boolean force) {
|
||||
return Result.success(classEventCalendarService.resync(xydxqbh, confirm, force));
|
||||
}
|
||||
|
||||
@PostMapping("/apply")
|
||||
|
||||
+22
@@ -10,6 +10,8 @@ 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.DailyWeeklyTimetableVO;
|
||||
import com.roomroot.jwgl.vo.kcb.FreePeriodsVO;
|
||||
import com.roomroot.jwgl.vo.kcb.TimetableGridVO;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -166,4 +168,24 @@ public class KCBController {
|
||||
new String(name.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
|
||||
return ResponseEntity.ok().headers(headers).body(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有课/无课时段查询:按日期给出 busy(有课节次)与 free(无课节次)。
|
||||
* <p>dim=semester|team|teacher|room,id 为对应编号;start/end 缺省本周。</p>
|
||||
*/
|
||||
@GetMapping("/freeSlots")
|
||||
public Result<FreePeriodsVO> freeSlots(TimetableGridQuery query) {
|
||||
return Result.success(timetableGridService.freePeriods(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 课次综合查询:按 年度/学期/学员队/教员/教室/教研室/学员 组合过滤课次日历。
|
||||
* <p>学员账号只看本队;教员只看本人;教研室只看本室课程。</p>
|
||||
*/
|
||||
@GetMapping("/lessons")
|
||||
public Result<List<DailyWeeklyTimetableVO>> listLessons(TimetableQuery cond,
|
||||
@RequestParam(value = "start", required = false) String start,
|
||||
@RequestParam(value = "end", required = false) String end) {
|
||||
return Result.success(kcbService.listLessons(cond, start, end));
|
||||
}
|
||||
}
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package com.roomroot.web.controller.jwgl;
|
||||
|
||||
import com.roomroot.jwgl.service.MyBusinessService;
|
||||
import com.roomroot.jwgl.unit.Result;
|
||||
import com.roomroot.jwgl.vo.kcb.ClassTimetableVO;
|
||||
import com.roomroot.jwgl.vo.kcb.DailyWeeklyTimetableVO;
|
||||
import com.roomroot.jwgl.vo.kcb.TimetableGridVO;
|
||||
import com.roomroot.jwgl.vo.mybusiness.MyCourseTaskVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 「我的教学」角色视图接口(阶段 C)。
|
||||
*
|
||||
* <p>教员/教研室/学员维度全部由服务端按登录身份解析,
|
||||
* 不接受调用方传入他人编号。</p>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/my")
|
||||
public class MyBusinessController {
|
||||
|
||||
@Autowired
|
||||
private MyBusinessService myBusinessService;
|
||||
|
||||
// ==================== 教员 ====================
|
||||
|
||||
/** 本教员课程任务 */
|
||||
@GetMapping("/teacher/courses")
|
||||
public Result<List<MyCourseTaskVO>> teacherCourses(@RequestParam(value = "nd", required = false) Integer nd,
|
||||
@RequestParam(value = "xqdc", required = false) Integer xqdc) {
|
||||
return Result.success(myBusinessService.teacherCourses(nd, xqdc));
|
||||
}
|
||||
|
||||
/** 本教员实施计划(默认本周) */
|
||||
@GetMapping("/teacher/lessons")
|
||||
public Result<List<DailyWeeklyTimetableVO>> teacherLessons(@RequestParam(value = "start", required = false) String start,
|
||||
@RequestParam(value = "end", required = false) String end,
|
||||
@RequestParam(value = "nd", required = false) Integer nd) {
|
||||
return Result.success(myBusinessService.teacherLessons(start, end, nd));
|
||||
}
|
||||
|
||||
/** 本教员课表网格 */
|
||||
@GetMapping("/teacher/grid")
|
||||
public Result<TimetableGridVO> teacherGrid(@RequestParam(value = "mbbh", required = false) String mbbh,
|
||||
@RequestParam(value = "start", required = false) String start,
|
||||
@RequestParam(value = "end", required = false) String end,
|
||||
@RequestParam(value = "nd", required = false) Integer nd) {
|
||||
return Result.success(myBusinessService.teacherGrid(mbbh, start, end, nd));
|
||||
}
|
||||
|
||||
// ==================== 教研室 ====================
|
||||
|
||||
/** 今日本室计划 */
|
||||
@GetMapping("/office/today")
|
||||
public Result<List<DailyWeeklyTimetableVO>> officeToday(@RequestParam(value = "rq", required = false) String rq,
|
||||
@RequestParam(value = "offset", required = false) Integer offset) {
|
||||
return Result.success(myBusinessService.officeToday(rq, offset));
|
||||
}
|
||||
|
||||
/** 本室课程 */
|
||||
@GetMapping("/office/courses")
|
||||
public Result<List<ClassTimetableVO>> officeCourses(@RequestParam(value = "nd", required = false) Integer nd,
|
||||
@RequestParam(value = "xqdc", required = false) Integer xqdc) {
|
||||
return Result.success(myBusinessService.officeCourses(nd, xqdc));
|
||||
}
|
||||
|
||||
/** 本室教员清单 */
|
||||
@GetMapping("/office/teachers")
|
||||
public Result<List<Map<String, Object>>> officeTeachers() {
|
||||
return Result.success(myBusinessService.officeTeachers());
|
||||
}
|
||||
|
||||
// ==================== 学员 ====================
|
||||
|
||||
/** 本学员队课程列表 */
|
||||
@GetMapping("/student/courses")
|
||||
public Result<List<ClassTimetableVO>> studentCourses(@RequestParam(value = "nd", required = false) Integer nd,
|
||||
@RequestParam(value = "xqdc", required = false) Integer xqdc) {
|
||||
return Result.success(myBusinessService.studentCourses(nd, xqdc));
|
||||
}
|
||||
|
||||
/** 本学员队课次(默认本周) */
|
||||
@GetMapping("/student/lessons")
|
||||
public Result<List<DailyWeeklyTimetableVO>> studentLessons(@RequestParam(value = "start", required = false) String start,
|
||||
@RequestParam(value = "end", required = false) String end) {
|
||||
return Result.success(myBusinessService.studentLessons(start, end));
|
||||
}
|
||||
|
||||
/** 本学员队课表网格 */
|
||||
@GetMapping("/student/grid")
|
||||
public Result<TimetableGridVO> studentGrid(@RequestParam(value = "mbbh", required = false) String mbbh,
|
||||
@RequestParam(value = "start", required = false) String start,
|
||||
@RequestParam(value = "end", required = false) String end,
|
||||
@RequestParam(value = "nd", required = false) Integer nd) {
|
||||
return Result.success(myBusinessService.studentGrid(mbbh, start, end, nd));
|
||||
}
|
||||
|
||||
/** 本学员队干部清单 */
|
||||
@GetMapping("/team/cadres")
|
||||
public Result<List<Map<String, Object>>> teamCadres() {
|
||||
return Result.success(myBusinessService.teamCadres());
|
||||
}
|
||||
}
|
||||
+29
@@ -4,11 +4,15 @@ import com.roomroot.common.exception.ServiceException;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingArrangeRequest;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingCellOpRequest;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingCourseEditRequest;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingMoveRequest;
|
||||
import com.roomroot.jwgl.service.RunningCoursePublishService;
|
||||
import com.roomroot.jwgl.service.SchedulingWindowService;
|
||||
import com.roomroot.jwgl.unit.Result;
|
||||
import com.roomroot.jwgl.vo.scheduling.SchedulingViewVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -17,6 +21,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -38,6 +43,20 @@ public class SchedulingWindowController {
|
||||
@Autowired
|
||||
private RunningCoursePublishService runningCoursePublishService;
|
||||
|
||||
/**
|
||||
* 导出排课窗视图 Excel:每周一个 sheet(节次×星期网格,阶段 E4)。
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('teach:schedulingWindow:list')")
|
||||
@GetMapping("/export")
|
||||
public ResponseEntity<byte[]> export(@RequestParam("xydxqbh") String xydxqbh) {
|
||||
byte[] bytes = schedulingWindowService.exportView(xydxqbh);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
|
||||
headers.setContentDispositionFormData("attachment",
|
||||
new String("排课窗口.xlsx".getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
|
||||
return ResponseEntity.ok().headers(headers).body(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 排课窗组合视图:班次头 + 课程列表(排满标绿)+ 周次×星期×节次格子。
|
||||
*/
|
||||
@@ -56,6 +75,16 @@ public class SchedulingWindowController {
|
||||
return Result.success(schedulingWindowService.arrange(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 拖拽移动课次:把 from 格的课次平移到 to 格(阶段 E4)。
|
||||
* 目标格走与 arrange 相同的硬冲突校验;软提示照常移动并返回 warning。
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('teach:schedulingWindow:edit')")
|
||||
@PostMapping("/move")
|
||||
public Result<Map<String, Object>> move(@RequestBody SchedulingMoveRequest request) {
|
||||
return Result.success(schedulingWindowService.moveLesson(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除所选节次:仅删除该课程在这些格上的课次;已提交实施计划的课次拒绝。
|
||||
*/
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.roomroot.web.controller.jwgl;
|
||||
|
||||
import com.roomroot.jwgl.entity.DBVersion;
|
||||
import com.roomroot.jwgl.entity.JCSJB;
|
||||
import com.roomroot.jwgl.service.ClassPeriodService;
|
||||
import com.roomroot.jwgl.service.SystemSettingsService;
|
||||
import com.roomroot.jwgl.unit.Result;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 教务系统参数(DBVersion 单行,阶段 E1)。
|
||||
*
|
||||
* <p>当前开放字段:显示78节(xs78j)、显示晚上(xsws)、显示夜间(xsyj)、
|
||||
* 教学要点名称(jxydmc)。更新只写非空字段。</p>
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/systemSettings")
|
||||
public class SystemSettingsController {
|
||||
|
||||
@Autowired
|
||||
private SystemSettingsService systemSettingsService;
|
||||
|
||||
@Autowired
|
||||
private ClassPeriodService classPeriodService;
|
||||
|
||||
/** 读取系统参数 */
|
||||
@GetMapping("/get")
|
||||
public Result<DBVersion> get() {
|
||||
return Result.success(systemSettingsService.get());
|
||||
}
|
||||
|
||||
/** 节次段字典:节次时间表的双节次行,供日历编辑器渲染节次列 */
|
||||
@GetMapping("/periodSlots")
|
||||
public Result<List<JCSJB>> periodSlots() {
|
||||
return Result.success(classPeriodService.slotRows());
|
||||
}
|
||||
|
||||
/** 更新系统参数(只写非空字段) */
|
||||
@PostMapping("/update")
|
||||
public Result<DBVersion> update(@RequestBody DBVersion settings) {
|
||||
return Result.success(systemSettingsService.update(settings));
|
||||
}
|
||||
}
|
||||
+47
-3
@@ -1,5 +1,8 @@
|
||||
package com.roomroot.web.controller.jwgl;
|
||||
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBatchFillRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBatchRoomRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBatchTeacherRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBhListRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookFillRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookRoomRequest;
|
||||
@@ -34,8 +37,9 @@ public class TaskBookFillController {
|
||||
* 填报列表:某教学任务下全部课程任务行(排序 课程→责任教员→课次,含合班分组号)。
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<List<TaskBookRowVO>> list(@RequestParam("jxrwbh") String jxrwbh) {
|
||||
return Result.success(taskBookFillService.list(jxrwbh));
|
||||
public Result<List<TaskBookRowVO>> list(@RequestParam("jxrwbh") String jxrwbh,
|
||||
@RequestParam(value = "jybh", required = false) String jybh) {
|
||||
return Result.success(taskBookFillService.list(jxrwbh, jybh));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,10 +84,50 @@ public class TaskBookFillController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 填报字段:计划教员 / 场地(同合班同步)/ 排课建议。
|
||||
* 填报字段:计划教员 / 场地(同合班同步)/ 排课建议 / 课次序号。
|
||||
*/
|
||||
@PostMapping("/fill")
|
||||
public Result<Integer> fill(@RequestBody TaskBookFillRequest request) {
|
||||
return Result.success(taskBookFillService.fill(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量指定责任教员:对每行执行与 setTeacher 相同的校验与合班组同步。
|
||||
*/
|
||||
@PostMapping("/batchSetTeacher")
|
||||
public Result<Integer> batchSetTeacher(@RequestBody TaskBookBatchTeacherRequest request) {
|
||||
return Result.success(taskBookFillService.batchSetTeacher(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量指定场地:对每行执行与 setRoom 相同的校验与合班组同步。
|
||||
*/
|
||||
@PostMapping("/batchSetRoom")
|
||||
public Result<Integer> batchSetRoom(@RequestBody TaskBookBatchRoomRequest request) {
|
||||
return Result.success(taskBookFillService.batchSetRoom(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量填报:对所选行统一应用填报字段(仅写非空),单事务。
|
||||
*/
|
||||
@PostMapping("/batchFill")
|
||||
public Result<Integer> batchFill(@RequestBody TaskBookBatchFillRequest request) {
|
||||
return Result.success(taskBookFillService.batchFill(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 行级删除:所选课程任务行逻辑删除;已发布到运行课表的班次须先撤回。
|
||||
*/
|
||||
@PostMapping("/deleteRows")
|
||||
public Result<Integer> deleteRows(@RequestBody TaskBookBhListRequest request) {
|
||||
return Result.success(taskBookFillService.deleteRows(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 教研室跨任务汇总:该教研室全部任务书(附教学任务名称/年度/状态)。
|
||||
*/
|
||||
@GetMapping("/officeSummary")
|
||||
public Result<List<Map<String, Object>>> officeSummary(@RequestParam("jysdh") String jysdh) {
|
||||
return Result.success(taskBookFillService.officeSummary(jysdh));
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -72,6 +72,17 @@ public class TeachingAllocationController {
|
||||
return Result.success(teachingAllocationService.setGroup(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动排布:按铺学时建议把计算起始周回填到 pdqsz。
|
||||
* onlyMissing=true(辅助排布)只补未设起始周的任务;body 可选传任务编号列表限定范围。
|
||||
*/
|
||||
@PostMapping("/autoArrange")
|
||||
public Result<Integer> autoArrange(@RequestParam("xydxqbh") String xydxqbh,
|
||||
@RequestParam(value = "onlyMissing", defaultValue = "false") boolean onlyMissing,
|
||||
@RequestBody(required = false) List<String> bhList) {
|
||||
return Result.success(teachingAllocationService.autoArrange(xydxqbh, bhList, onlyMissing));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出配当 Excel(支持批量:xydxqbh 逗号分隔)。
|
||||
*/
|
||||
|
||||
+6
-6
@@ -81,15 +81,15 @@ public class TeachingTaskController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布教学任务及教研室任务书
|
||||
* 根据教学任务编号修改状态为"发布",并批量更新所有关联的教研室任务书状态为"发布"
|
||||
* 批量发布教学任务及教研室任务书
|
||||
* 逐个任务置"发布"并同步教研室任务书;任一校验失败(已结束/任务书未生成)整批回滚。
|
||||
*
|
||||
* @param bh 教学任务编号
|
||||
* @return 更新数量
|
||||
* @param bhList 教学任务编号列表
|
||||
* @return 更新的教研室任务书行数合计
|
||||
*/
|
||||
@PostMapping("/batchPublish")
|
||||
public Result<Integer> batchPublish(@RequestParam("bh") String bh) {
|
||||
int count = teachingTaskService.batchPublish(bh);
|
||||
public Result<Integer> batchPublish(@RequestBody List<String> bhList) {
|
||||
int count = teachingTaskService.batchPublish(bhList);
|
||||
return Result.success(count);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.roomroot.jwgl.dto.kcb;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 班次课表查询条件。
|
||||
* <p>未传年度、学期时按当前时间取本学期。</p>
|
||||
@@ -20,4 +22,19 @@ public class TimetableQuery {
|
||||
|
||||
/** 学员队名称(队别班次模糊) */
|
||||
private String xydmc;
|
||||
|
||||
/** 教员编号(主讲/辅讲/责任教员任一命中) */
|
||||
private String jybh;
|
||||
|
||||
/** 教室编号(课次场地或课程默认场地任一命中) */
|
||||
private String jsbh;
|
||||
|
||||
/** 教研室代号(按课程归属教研室过滤) */
|
||||
private String jysdh;
|
||||
|
||||
/** 学员编号(服务端解析为其所在学员队后按队过滤) */
|
||||
private String xybh;
|
||||
|
||||
/** 教员编号集合(教研室等批量范围过滤用) */
|
||||
private List<String> jybhs;
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package com.roomroot.jwgl.dto.scheduling;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 拖拽移动课次请求(阶段 E4):把课程在 from 格的课次平移到 to 格。
|
||||
* 目标格沿用 arrange 的硬冲突校验;软提示(配当周次不符)照常移动并返回 warning。
|
||||
*/
|
||||
@Data
|
||||
public class SchedulingMoveRequest {
|
||||
|
||||
/** 实施_课程编号 */
|
||||
private String sskcbh;
|
||||
|
||||
/** 源格(已有课次) */
|
||||
private SchedulingArrangeRequest.Cell from;
|
||||
|
||||
/** 目标格 */
|
||||
private SchedulingArrangeRequest.Cell to;
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.roomroot.jwgl.dto.taskbook;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量填报请求:对所选课程任务行统一应用填报字段(仅写非空字段)。
|
||||
*/
|
||||
@Data
|
||||
public class TaskBookBatchFillRequest {
|
||||
|
||||
/** 课程任务编号列表 */
|
||||
private List<String> bhList;
|
||||
|
||||
/** 教研室计划教员编号 */
|
||||
private String jysjhjybh;
|
||||
|
||||
/** 场地编号(useSpecial=true 时忽略,取班次专用教室) */
|
||||
private String jsbh;
|
||||
|
||||
/** 使用班次专用教室 */
|
||||
private Boolean useSpecial;
|
||||
|
||||
/** 排课建议(教研室计划备注) */
|
||||
private String jysjhbz;
|
||||
|
||||
/** 课次序号 */
|
||||
private Integer kcxh;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.roomroot.jwgl.dto.taskbook;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量指定场地请求。
|
||||
*/
|
||||
@Data
|
||||
public class TaskBookBatchRoomRequest {
|
||||
|
||||
/** 课程任务编号列表 */
|
||||
private List<String> bhList;
|
||||
|
||||
/** 教室编号(useSpecial=false 时必传) */
|
||||
private String jsbh;
|
||||
|
||||
/** true=使用各行班次专用教室(忽略 jsbh,取各班次学期.专用教室编号) */
|
||||
private Boolean useSpecial;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.roomroot.jwgl.dto.taskbook;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量指定责任教员请求。
|
||||
*/
|
||||
@Data
|
||||
public class TaskBookBatchTeacherRequest {
|
||||
|
||||
/** 课程任务编号列表 */
|
||||
private List<String> bhList;
|
||||
|
||||
/**
|
||||
* 指定方式:
|
||||
* unit — 责任单位教员(教员必须属于各行教研室 jysdh);
|
||||
* academy — 全院教员(任意在职教员);
|
||||
* plan — 应用各行教研室计划教员(jybh = 各行 jysjhjybh)。
|
||||
*/
|
||||
private String mode;
|
||||
|
||||
/** 责任教员编号(mode=unit / academy 时必传) */
|
||||
private String jybh;
|
||||
}
|
||||
+3
@@ -19,4 +19,7 @@ public class TaskBookFillRequest {
|
||||
|
||||
/** 排课建议(教研室计划备注) */
|
||||
private String jysjhbz;
|
||||
|
||||
/** 课次序号(课序设置;非空时写入) */
|
||||
private Integer kcxh;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,44 @@
|
||||
AND cxf."学期第次" = #{cond.xqdc}
|
||||
)
|
||||
</if>
|
||||
<if test="cond != null and cond.jybh != null and cond.jybh != ''">
|
||||
AND (
|
||||
c."教员编号" = #{cond.jybh}
|
||||
OR c."责任教员编号" = #{cond.jybh}
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM "实施_课程表_辅助教员" f2
|
||||
WHERE f2."实施_课程表编号" = s."编号"
|
||||
AND f2."辅助教员编号" = #{cond.jybh}
|
||||
)
|
||||
)
|
||||
</if>
|
||||
<if test="cond != null and cond.jybhs != null and cond.jybhs.size() > 0">
|
||||
AND (
|
||||
c."教员编号" IN
|
||||
<foreach collection="cond.jybhs" item="j" open="(" separator="," close=")">#{j}</foreach>
|
||||
OR c."责任教员编号" IN
|
||||
<foreach collection="cond.jybhs" item="j" open="(" separator="," close=")">#{j}</foreach>
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM "实施_课程表_辅助教员" f3
|
||||
WHERE f3."实施_课程表编号" = s."编号"
|
||||
AND f3."辅助教员编号" IN
|
||||
<foreach collection="cond.jybhs" item="j" open="(" separator="," close=")">#{j}</foreach>
|
||||
)
|
||||
)
|
||||
</if>
|
||||
<if test="cond != null and cond.jsbh != null and cond.jsbh != ''">
|
||||
AND (
|
||||
c."教室编号" = #{cond.jsbh}
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM "实施_课程表_教室" j2
|
||||
WHERE j2."实施_课程表编号" = s."编号"
|
||||
AND j2."教室编号" = #{cond.jsbh}
|
||||
)
|
||||
)
|
||||
</if>
|
||||
<if test="cond != null and cond.jysdh != null and cond.jysdh != ''">
|
||||
AND k."教研室代号" = #{cond.jysdh}
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<select id="selectLessonTimetable" resultType="com.roomroot.jwgl.vo.kcb.DailyWeeklyTimetableVO">
|
||||
@@ -89,6 +127,50 @@
|
||||
<if test="cond != null and cond.xydmc != null and cond.xydmc != ''">
|
||||
AND xyd."学员队名称" LIKE CONCAT('%', #{cond.xydmc}, '%')
|
||||
</if>
|
||||
<if test="cond != null and cond.jybh != null and cond.jybh != ''">
|
||||
AND (
|
||||
c."教员编号" = #{cond.jybh}
|
||||
OR c."责任教员编号" = #{cond.jybh}
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM "实施_课程表" t2
|
||||
INNER JOIN "实施_课程表_辅助教员" f2 ON f2."实施_课程表编号" = t2."编号"
|
||||
WHERE LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(t2."实施_课程编号"), '-', ''))
|
||||
AND COALESCE(t2."删除状态", 0) = 0
|
||||
AND f2."辅助教员编号" = #{cond.jybh}
|
||||
)
|
||||
)
|
||||
</if>
|
||||
<if test="cond != null and cond.jybhs != null and cond.jybhs.size() > 0">
|
||||
AND (
|
||||
c."教员编号" IN
|
||||
<foreach collection="cond.jybhs" item="j" open="(" separator="," close=")">#{j}</foreach>
|
||||
OR c."责任教员编号" IN
|
||||
<foreach collection="cond.jybhs" item="j" open="(" separator="," close=")">#{j}</foreach>
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM "实施_课程表" t3
|
||||
INNER JOIN "实施_课程表_辅助教员" f3 ON f3."实施_课程表编号" = t3."编号"
|
||||
WHERE LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(t3."实施_课程编号"), '-', ''))
|
||||
AND COALESCE(t3."删除状态", 0) = 0
|
||||
AND f3."辅助教员编号" IN
|
||||
<foreach collection="cond.jybhs" item="j" open="(" separator="," close=")">#{j}</foreach>
|
||||
)
|
||||
)
|
||||
</if>
|
||||
<if test="cond != null and cond.jsbh != null and cond.jsbh != ''">
|
||||
AND (
|
||||
c."教室编号" = #{cond.jsbh}
|
||||
OR EXISTS (
|
||||
SELECT 1 FROM "实施_课程表" t4
|
||||
INNER JOIN "实施_课程表_教室" j2 ON j2."实施_课程表编号" = t4."编号"
|
||||
WHERE LOWER(RAWTOHEX(c."编号")) = LOWER(REPLACE(TRIM(t4."实施_课程编号"), '-', ''))
|
||||
AND COALESCE(t4."删除状态", 0) = 0
|
||||
AND j2."教室编号" = #{cond.jsbh}
|
||||
)
|
||||
)
|
||||
</if>
|
||||
<if test="cond != null and cond.jysdh != null and cond.jysdh != ''">
|
||||
AND k."教研室代号" = #{cond.jysdh}
|
||||
</if>
|
||||
</sql>
|
||||
|
||||
<sql id="ClassTimetableSelect">
|
||||
|
||||
@@ -242,6 +242,7 @@
|
||||
d."学员队名称" AS xydmc,
|
||||
r."教员编号" AS jybh,
|
||||
jy."教员姓名" AS jyxm,
|
||||
jysx."主任" AS zr,
|
||||
r."教室编号" AS jsbh,
|
||||
js."教室名称" AS jsmc,
|
||||
r."课次序号" AS kcxh,
|
||||
@@ -256,6 +257,7 @@
|
||||
LEFT JOIN "课表" k ON k."课编号" = r."课编号"
|
||||
LEFT JOIN "学员队表" d ON d."学员队编号" = r."学员队编号"
|
||||
LEFT JOIN "教员表" jy ON jy."教员编号" = r."教员编号"
|
||||
LEFT JOIN "教员属性" jysx ON jysx."教员编号" = r."教员编号"
|
||||
LEFT JOIN "教员表" jy2 ON jy2."教员编号" = r."教研室计划教员编号"
|
||||
LEFT JOIN "教室表" js ON js."教室编号" = r."教室编号"
|
||||
LEFT JOIN "教研室表" jys ON jys."教研室代号" = r."教研室代号"
|
||||
|
||||
+3
-2
@@ -28,10 +28,11 @@ public interface ClassEventCalendarService {
|
||||
int batchSave(ClassCalendarBatchSaveDTO body);
|
||||
|
||||
/**
|
||||
* 重新同步校历:只补校历有而班历缺失的时间格,不覆盖人工改动。
|
||||
* 重新同步校历:默认只补校历有而班历缺失的时间格,不覆盖人工改动;
|
||||
* force=true 时额外把与校历不一致的已有格按校历覆盖。
|
||||
* confirm=false 仅返回差异预览;confirm=true 实际写入。
|
||||
*/
|
||||
ClassCalendarResyncVO resync(String xydxqbh, boolean confirm);
|
||||
ClassCalendarResyncVO resync(String xydxqbh, boolean confirm, boolean force);
|
||||
|
||||
/**
|
||||
* 把选定或整个班历覆盖到其它班次的对应时间格。不改课程任务。
|
||||
|
||||
@@ -16,6 +16,12 @@ public interface ClassPeriodService {
|
||||
*/
|
||||
List<String> slotLabels();
|
||||
|
||||
/**
|
||||
* 双节次时间格行(双节次=1),按起始节次排序;表内无数据时返回空。
|
||||
* 供前端按「节次时间表」渲染日历节次列。
|
||||
*/
|
||||
List<JCSJB> slotRows();
|
||||
|
||||
/**
|
||||
* 上午时段的双节次标签,用于「正课」默认判定。
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.roomroot.jwgl.unit.PageQuery;
|
||||
import com.roomroot.jwgl.unit.PageResult;
|
||||
import com.roomroot.jwgl.vo.kcb.ClassTimetableVO;
|
||||
import com.roomroot.jwgl.vo.kcb.DailyWeeklyTimetableRangeVO;
|
||||
import com.roomroot.jwgl.vo.kcb.DailyWeeklyTimetableVO;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.util.List;
|
||||
@@ -87,4 +88,10 @@ public interface KCBService {
|
||||
* 班次课程列表另存 Excel。
|
||||
*/
|
||||
void exportClassCourses(TimetableQuery cond, HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 课次综合查询:多维条件(年度/学期/学员队/教员/教室/教研室/学员)+ 日期范围。
|
||||
* start/end 缺省时取本周;身份范围在服务端强制(学员本队、教员本人、教研室本室)。
|
||||
*/
|
||||
List<DailyWeeklyTimetableVO> listLessons(TimetableQuery cond, String start, String end);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.roomroot.jwgl.service;
|
||||
|
||||
import com.roomroot.jwgl.vo.kcb.ClassTimetableVO;
|
||||
import com.roomroot.jwgl.vo.kcb.DailyWeeklyTimetableVO;
|
||||
import com.roomroot.jwgl.vo.kcb.TimetableGridVO;
|
||||
import com.roomroot.jwgl.vo.mybusiness.MyCourseTaskVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 「我的教学」角色视图服务(阶段 C)。
|
||||
*
|
||||
* <p>所有接口由服务端按登录身份解析业务主体(教员/教研室/学员队),
|
||||
* 不接受调用方传入他人编号,防止越权。</p>
|
||||
*/
|
||||
public interface MyBusinessService {
|
||||
|
||||
// ==================== 教员 ====================
|
||||
|
||||
/** 本教员课程任务(学员队任务表视角,含教学任务归属)。 */
|
||||
List<MyCourseTaskVO> teacherCourses(Integer nd, Integer xqdc);
|
||||
|
||||
/** 本教员实施计划(课次日历列表,默认本周)。 */
|
||||
List<DailyWeeklyTimetableVO> teacherLessons(String start, String end, Integer nd);
|
||||
|
||||
/** 本教员课表网格(dim=teacher)。 */
|
||||
TimetableGridVO teacherGrid(String mbbh, String start, String end, Integer nd);
|
||||
|
||||
// ==================== 教研室 ====================
|
||||
|
||||
/** 今日本室计划:本室全部教员(含辅讲)当日课次。 */
|
||||
List<DailyWeeklyTimetableVO> officeToday(String rq, Integer offset);
|
||||
|
||||
/** 本室课程(班次课程列表按教研室过滤)。 */
|
||||
List<ClassTimetableVO> officeCourses(Integer nd, Integer xqdc);
|
||||
|
||||
/** 本室教员清单(jybh/jyxm/zr)。 */
|
||||
List<Map<String, Object>> officeTeachers();
|
||||
|
||||
// ==================== 学员 ====================
|
||||
|
||||
/** 本学员队课程列表。 */
|
||||
List<ClassTimetableVO> studentCourses(Integer nd, Integer xqdc);
|
||||
|
||||
/** 本学员队课次(日实施计划,默认本周)。 */
|
||||
List<DailyWeeklyTimetableVO> studentLessons(String start, String end);
|
||||
|
||||
/** 本学员队课表网格(dim=team)。 */
|
||||
TimetableGridVO studentGrid(String mbbh, String start, String end, Integer nd);
|
||||
|
||||
/** 本学员队干部清单(骨干任职非空的同期学员)。 */
|
||||
List<Map<String, Object>> teamCadres();
|
||||
}
|
||||
+12
@@ -3,6 +3,7 @@ package com.roomroot.jwgl.service;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingArrangeRequest;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingCellOpRequest;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingCourseEditRequest;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingMoveRequest;
|
||||
import com.roomroot.jwgl.vo.scheduling.SchedulingViewVO;
|
||||
|
||||
import java.util.List;
|
||||
@@ -26,6 +27,14 @@ public interface SchedulingWindowService {
|
||||
*/
|
||||
Map<String, Object> arrange(SchedulingArrangeRequest request);
|
||||
|
||||
/**
|
||||
* 拖拽移动课次:把 from 格的课次平移到 to 格(阶段 E4)。
|
||||
* 目标格走与 arrange 相同的硬冲突校验;软提示照常移动并返回 warning。
|
||||
*
|
||||
* @return {moved: 1, warning: 软提示原因(可空)}
|
||||
*/
|
||||
Map<String, Object> moveLesson(SchedulingMoveRequest request);
|
||||
|
||||
/** 删除所选节次:仅删除该课程在这些格上的课次 */
|
||||
int deleteCells(SchedulingCellOpRequest request);
|
||||
|
||||
@@ -38,6 +47,9 @@ public interface SchedulingWindowService {
|
||||
/** 排课日志查询:按课程(可叠加操作类型) */
|
||||
List<Map<String, Object>> logs(String sskcbh, String czlx);
|
||||
|
||||
/** 导出排课窗视图 Excel:每周一个 sheet(节次×星期网格,E4) */
|
||||
byte[] exportView(String xydxqbh);
|
||||
|
||||
/** 编辑课程教学任务信息:学时/周课时/课程简称/责任教员/默认场地(手册 12.3.1) */
|
||||
Map<String, Object> updateCourse(SchedulingCourseEditRequest request);
|
||||
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.roomroot.jwgl.service;
|
||||
|
||||
import com.roomroot.jwgl.entity.DBVersion;
|
||||
|
||||
/**
|
||||
* 教务系统参数(DBVersion 单行)。
|
||||
*
|
||||
* <p>阶段 E1:校历/编辑器显示配置(显示78节、显示晚上、显示夜间)与
|
||||
* 教学要点名称等参数的读写。</p>
|
||||
*/
|
||||
public interface SystemSettingsService {
|
||||
|
||||
/**
|
||||
* 读取系统参数行;库中无记录时返回默认行(不落库)。
|
||||
*/
|
||||
DBVersion get();
|
||||
|
||||
/**
|
||||
* 更新系统参数;只写非空字段。库中无记录时先插入一行。
|
||||
*/
|
||||
DBVersion update(DBVersion settings);
|
||||
}
|
||||
+46
-1
@@ -1,5 +1,8 @@
|
||||
package com.roomroot.jwgl.service;
|
||||
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBatchFillRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBatchRoomRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBatchTeacherRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBhListRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookFillRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookRoomRequest;
|
||||
@@ -27,9 +30,15 @@ public interface TaskBookFillService {
|
||||
|
||||
/**
|
||||
* 任务书填报列表:某教学任务下全部课程任务行(排序 课程 → 责任教员 → 课次)。
|
||||
* 教员账号自动过滤为本人相关行;教研室账号过滤为本室行。
|
||||
*/
|
||||
List<TaskBookRowVO> list(String jxrwbh);
|
||||
|
||||
/**
|
||||
* 任务书填报列表(可按责任/计划教员编号过滤)。
|
||||
*/
|
||||
List<TaskBookRowVO> list(String jxrwbh, String jybh);
|
||||
|
||||
/**
|
||||
* 手动合班:所选行写入同一编组(新组号 = 现有最大编组 + 1)。
|
||||
* 校验 科目、学时、课类型、成绩分制 相同,任一不同则拒绝并说明。
|
||||
@@ -70,9 +79,45 @@ public interface TaskBookFillService {
|
||||
int setRoom(TaskBookRoomRequest request);
|
||||
|
||||
/**
|
||||
* 填报字段:计划教员(jysjhjybh)、场地(jsbh,同合班同步)、排课建议(jysjhbz)。
|
||||
* 填报字段:计划教员(jysjhjybh)、场地(jsbh,同合班同步)、排课建议(jysjhbz)、课次序号(kcxh)。
|
||||
*
|
||||
* @return 实际更新行数
|
||||
*/
|
||||
int fill(TaskBookFillRequest request);
|
||||
|
||||
/**
|
||||
* 批量指定责任教员:对每行执行与 {@link #setTeacher} 相同的校验与合班组同步。
|
||||
*
|
||||
* @return 实际更新行数合计
|
||||
*/
|
||||
int batchSetTeacher(TaskBookBatchTeacherRequest request);
|
||||
|
||||
/**
|
||||
* 批量指定场地:对每行执行与 {@link #setRoom} 相同的校验与合班组同步。
|
||||
*
|
||||
* @return 实际更新行数合计
|
||||
*/
|
||||
int batchSetRoom(TaskBookBatchRoomRequest request);
|
||||
|
||||
/**
|
||||
* 批量填报:对所选行统一应用填报字段(计划教员/场地/排课建议/课次序号,仅写非空)。
|
||||
* 每行执行与 {@link #fill} 相同的校验;整批单事务。
|
||||
*
|
||||
* @return 实际更新行数合计
|
||||
*/
|
||||
int batchFill(TaskBookBatchFillRequest request);
|
||||
|
||||
/**
|
||||
* 行级删除:所选课程任务行逻辑删除(delFlag=1)。
|
||||
* 与拆班同口径:仅机关/教研室可做;已发布到运行课表的班次须先撤回。
|
||||
*
|
||||
* @return 实际删除行数
|
||||
*/
|
||||
int deleteRows(TaskBookBhListRequest request);
|
||||
|
||||
/**
|
||||
* 教研室跨任务汇总:该教研室全部任务书(JYSRWS),附教学任务名称/年度/状态。
|
||||
* 教研室账号强制本室代号。
|
||||
*/
|
||||
List<Map<String, Object>> officeSummary(String jysdh);
|
||||
}
|
||||
|
||||
+9
@@ -49,6 +49,15 @@ public interface TeachingAllocationService {
|
||||
*/
|
||||
int setGroup(AllocationGroupRequest request);
|
||||
|
||||
/**
|
||||
* 自动排布:按铺学时建议把「配档起始周」回填到任务(pdqsz 落库)。
|
||||
* onlyMissing=true(辅助排布)只回填尚未设置起始周的任务,不动人工排布结果;
|
||||
* bhList 非空时只处理所选任务。
|
||||
*
|
||||
* @return 实际回填行数
|
||||
*/
|
||||
int autoArrange(String xydxqbh, List<String> bhList, boolean onlyMissing);
|
||||
|
||||
/**
|
||||
* 导出配当 Excel(批量:每行一个任务,含班次名称与学期代号列)。
|
||||
*/
|
||||
|
||||
+5
-5
@@ -64,13 +64,13 @@ public interface TeachingTaskService {
|
||||
List<JXRW> listAllValid();
|
||||
|
||||
/**
|
||||
* 发布教学任务及教研室任务书
|
||||
* 根据教学任务编号修改状态为"发布",并批量更新所有关联的教研室任务书状态为"发布"
|
||||
* 批量发布教学任务及教研室任务书
|
||||
* 逐个任务置"发布"并同步其教研室任务书;任一任务校验失败(已结束/任务书未生成)整批回滚。
|
||||
*
|
||||
* @param bh 教学任务编号
|
||||
* @return 更新数量
|
||||
* @param bhList 教学任务编号列表
|
||||
* @return 更新的教研室任务书行数合计
|
||||
*/
|
||||
int batchPublish(String bh);
|
||||
int batchPublish(List<String> bhList);
|
||||
|
||||
/**
|
||||
* 结束发布。结束后教研室不能再改任务书和课程任务。
|
||||
|
||||
+7
@@ -1,6 +1,7 @@
|
||||
package com.roomroot.jwgl.service;
|
||||
|
||||
import com.roomroot.jwgl.dto.kcb.TimetableGridQuery;
|
||||
import com.roomroot.jwgl.vo.kcb.FreePeriodsVO;
|
||||
import com.roomroot.jwgl.vo.kcb.TimetableGridVO;
|
||||
|
||||
/**
|
||||
@@ -18,4 +19,10 @@ public interface TimetableGridService {
|
||||
* 导出网格课表为矩阵 Excel(每周一个 sheet)。
|
||||
*/
|
||||
byte[] exportGrid(TimetableGridQuery query);
|
||||
|
||||
/**
|
||||
* 有课/无课时段查询:按日期给出 busy(有课节次)与 free(无课节次)。
|
||||
* 复用 {@link #grid} 的维度解析与历表不可排判定。
|
||||
*/
|
||||
FreePeriodsVO freePeriods(TimetableGridQuery query);
|
||||
}
|
||||
|
||||
+65
-18
@@ -138,54 +138,101 @@ public class ClassEventCalendarServiceImpl implements ClassEventCalendarService
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新同步校历(口径A:只补缺失格,不覆盖人工改动)。
|
||||
* confirm=false 返回差异预览;confirm=true 实际写入。
|
||||
* 重新同步校历。confirm=false 返回差异预览;confirm=true 实际写入。
|
||||
* 默认只补缺失格,不覆盖人工改动;force=true 时额外把与校历不一致的已有格按校历覆盖。
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ClassCalendarResyncVO resync(String xydxqbh, boolean confirm) {
|
||||
public ClassCalendarResyncVO resync(String xydxqbh, boolean confirm, boolean force) {
|
||||
XYDNDXQJBXXB semester = requireSemester(xydxqbh);
|
||||
LocalDate start = semester.getKxrq();
|
||||
LocalDate end = semester.getJsrq();
|
||||
if (start == null || end == null) {
|
||||
throw new ServiceException("班次学期缺少开学或结束日期,不能同步校历", BAD_REQUEST);
|
||||
}
|
||||
Set<String> existingKeys = new HashSet<>();
|
||||
Map<String, JQB> existingByKey = new LinkedHashMap<>();
|
||||
for (JQB row : listRows(xydxqbh)) {
|
||||
existingKeys.add(cellKey(row.getJqsj(), row.getCourseClass()));
|
||||
existingByKey.putIfAbsent(cellKey(row.getJqsj(), row.getCourseClass()), row);
|
||||
}
|
||||
List<ClassCalendarEventDTO> missing = new ArrayList<>();
|
||||
List<ClassCalendarEventDTO> changed = new ArrayList<>();
|
||||
for (XQXLB school : mergeByDayAndPeriod(loadSchoolRows(semester, start, end), start, end)) {
|
||||
LocalDate day = parseDay(school.getJqsj());
|
||||
if (!existingKeys.contains(cellKey(Integer.valueOf(day.format(COMPACT)), school.getCourseClass()))) {
|
||||
ClassCalendarEventDTO dto = new ClassCalendarEventDTO();
|
||||
dto.setJqsj(day.format(DAY));
|
||||
dto.setCourseClass(school.getCourseClass());
|
||||
dto.setJqmc(school.getJqmc());
|
||||
dto.setJc(Boolean.TRUE.equals(school.getJc()));
|
||||
dto.setKpk(Boolean.TRUE.equals(school.getKpk()));
|
||||
dto.setBzxs(Boolean.TRUE.equals(school.getBzxs()));
|
||||
dto.setZdpk(Boolean.TRUE.equals(school.getZdpk()));
|
||||
dto.setZk(Boolean.TRUE.equals(school.getZk()));
|
||||
dto.setBz(school.getBz());
|
||||
missing.add(dto);
|
||||
String key = cellKey(Integer.valueOf(day.format(COMPACT)), school.getCourseClass());
|
||||
JQB existing = existingByKey.get(key);
|
||||
if (existing == null) {
|
||||
missing.add(schoolDto(day, school));
|
||||
} else if (force && differs(existing, school)) {
|
||||
changed.add(schoolDto(day, school));
|
||||
}
|
||||
}
|
||||
ClassCalendarResyncVO vo = new ClassCalendarResyncVO();
|
||||
vo.setItems(missing);
|
||||
vo.setAddCount(missing.size());
|
||||
if (confirm && !missing.isEmpty()) {
|
||||
vo.setUpdateItems(changed);
|
||||
vo.setUpdateCount(changed.size());
|
||||
if (confirm && (!missing.isEmpty() || (force && !changed.isEmpty()))) {
|
||||
teachingTaskWriteGuard.assertCourseTaskWritable(xydxqbh);
|
||||
for (ClassCalendarEventDTO dto : missing) {
|
||||
insertCell(semester, parseDay(dto.getJqsj()), dto.getCourseClass(), dto.getJqmc(),
|
||||
flag(dto.getJc()), flag(dto.getKpk()), flag(dto.getBzxs()),
|
||||
flag(dto.getZdpk()), flag(dto.getZk()), dto.getBz());
|
||||
}
|
||||
if (force) {
|
||||
for (ClassCalendarEventDTO dto : changed) {
|
||||
LocalDate day = parseDay(dto.getJqsj());
|
||||
JQB existing = existingByKey.get(
|
||||
cellKey(Integer.valueOf(day.format(COMPACT)), dto.getCourseClass()));
|
||||
if (existing != null) {
|
||||
applyCell(semester, existing, day, dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
vo.setExecuted(true);
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校历格转时间格 DTO(同步预览/写入的统一口径)。
|
||||
*/
|
||||
private ClassCalendarEventDTO schoolDto(LocalDate day, XQXLB school) {
|
||||
ClassCalendarEventDTO dto = new ClassCalendarEventDTO();
|
||||
dto.setJqsj(day.format(DAY));
|
||||
dto.setCourseClass(school.getCourseClass());
|
||||
dto.setJqmc(school.getJqmc());
|
||||
dto.setJc(Boolean.TRUE.equals(school.getJc()));
|
||||
dto.setKpk(Boolean.TRUE.equals(school.getKpk()));
|
||||
dto.setBzxs(Boolean.TRUE.equals(school.getBzxs()));
|
||||
dto.setZdpk(Boolean.TRUE.equals(school.getZdpk()));
|
||||
dto.setZk(Boolean.TRUE.equals(school.getZk()));
|
||||
dto.setBz(school.getBz());
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 班历已有格与校历口径是否不一致(force 覆盖判定;null 与空串视为相同)。
|
||||
*/
|
||||
private boolean differs(JQB row, XQXLB school) {
|
||||
if (!norm(row.getJqmc()).equals(norm(school.getJqmc()))
|
||||
|| !norm(row.getBz()).equals(norm(school.getBz()))) {
|
||||
return true;
|
||||
}
|
||||
return flagDiffers(row.getJc(), school.getJc())
|
||||
|| flagDiffers(row.getKpk(), school.getKpk())
|
||||
|| flagDiffers(row.getBzxs(), school.getBzxs())
|
||||
|| flagDiffers(row.getZdpk(), school.getZdpk())
|
||||
|| flagDiffers(row.getZk(), school.getZk());
|
||||
}
|
||||
|
||||
private boolean flagDiffers(Integer rowFlag, Boolean schoolFlag) {
|
||||
return Integer.valueOf(1).equals(rowFlag) != Boolean.TRUE.equals(schoolFlag);
|
||||
}
|
||||
|
||||
private String norm(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 编号 / (学员队学期编号+日期+节次) 定位已有格。
|
||||
*/
|
||||
|
||||
+5
@@ -37,6 +37,11 @@ public class ClassPeriodServiceImpl implements ClassPeriodService {
|
||||
.filter(s -> s != null && !s.isEmpty()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<JCSJB> slotRows() {
|
||||
return loadSlots();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> mainSlotLabels() {
|
||||
List<JCSJB> slots = loadSlots();
|
||||
|
||||
+1
-1
@@ -269,7 +269,7 @@ public class ClassSemesterServiceImpl implements ClassSemesterService {
|
||||
.and(w -> w.lt(com.roomroot.jwgl.entity.JQB::getJqsj, start)
|
||||
.or().gt(com.roomroot.jwgl.entity.JQB::getJqsj, end)));
|
||||
// 只补缺失格、不覆盖人工改动(与班历弹窗「同步校历」同一口径)
|
||||
classEventCalendarService.resync(bh, true);
|
||||
classEventCalendarService.resync(bh, true, false);
|
||||
}
|
||||
|
||||
return count;
|
||||
|
||||
+8
@@ -782,6 +782,14 @@ public class CourseRunningImportServiceImpl implements CourseRunningImportServic
|
||||
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次已提交,不允许修改,如需修改请先撤回");
|
||||
}
|
||||
|
||||
// 步骤4.5:教学内容必填;教学方法缺省按「理论讲授」
|
||||
if (dto.getJxnr() == null || dto.getJxnr().trim().isEmpty()) {
|
||||
throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "教学内容不能为空");
|
||||
}
|
||||
if (dto.getJxff() == null || dto.getJxff().trim().isEmpty()) {
|
||||
dto.setJxff("理论讲授");
|
||||
}
|
||||
|
||||
// 步骤5:将DTO中的填写内容设置到课次实体
|
||||
lesson.setJxnr(dto.getJxnr()); // 教学内容
|
||||
lesson.setJxyd(dto.getJxyd()); // 教学要点
|
||||
|
||||
+61
@@ -5,9 +5,13 @@ import com.roomroot.common.utils.StringUtils;
|
||||
import com.roomroot.common.utils.poi.ExcelUtil;
|
||||
import com.roomroot.jwgl.dto.kcb.TimetableQuery;
|
||||
import com.roomroot.jwgl.entity.KCB;
|
||||
import com.roomroot.jwgl.entity.XYDQB;
|
||||
import com.roomroot.jwgl.entity.XYXX;
|
||||
import com.roomroot.jwgl.mapper.KCBMapper;
|
||||
import com.roomroot.jwgl.mapper.KcbTimetableMapper;
|
||||
import com.roomroot.jwgl.mapper.SystemInitializationMapper;
|
||||
import com.roomroot.jwgl.mapper.XYDQBMapper;
|
||||
import com.roomroot.jwgl.mapper.XYXXMapper;
|
||||
import com.roomroot.jwgl.service.KCBService;
|
||||
import com.roomroot.jwgl.unit.BusinessException;
|
||||
import com.roomroot.jwgl.unit.PageQuery;
|
||||
@@ -51,6 +55,12 @@ public class KCBServiceImpl implements KCBService {
|
||||
@Resource
|
||||
private JwglRoleHelper jwglRoleHelper;
|
||||
|
||||
@Resource
|
||||
private XYXXMapper xyxxMapper;
|
||||
|
||||
@Resource
|
||||
private XYDQBMapper xydqbMapper;
|
||||
|
||||
@Override
|
||||
public void add(KCB kcb) {
|
||||
if (kcb.getCjsj() == null) {
|
||||
@@ -139,6 +149,24 @@ public class KCBServiceImpl implements KCBService {
|
||||
util.exportExcel(response, list, "班次课程列表");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DailyWeeklyTimetableVO> listLessons(TimetableQuery cond, String start, String end) {
|
||||
TimetableQuery filter = prepareFilter(cond);
|
||||
LocalDate s = StringUtils.isEmpty(start) ? null : parseViewDate(start);
|
||||
LocalDate e = StringUtils.isEmpty(end) ? null : parseViewDate(end);
|
||||
if (s == null && e == null) {
|
||||
LocalDate monday = LocalDate.now().with(DayOfWeek.MONDAY);
|
||||
s = monday;
|
||||
e = monday.plusDays(6);
|
||||
} else if (s == null) {
|
||||
s = e;
|
||||
} else if (e == null) {
|
||||
e = s;
|
||||
}
|
||||
return kcbTimetableMapper.selectLessonTimetable(filter,
|
||||
s.format(DATE_FORMAT), e.format(DATE_FORMAT));
|
||||
}
|
||||
|
||||
private LocalDate parseViewDate(String rq) {
|
||||
if (StringUtils.isEmpty(rq)) {
|
||||
return LocalDate.now();
|
||||
@@ -169,13 +197,36 @@ public class KCBServiceImpl implements KCBService {
|
||||
if (jwglRoleHelper.isStudent()) {
|
||||
filter.setXydbh(jwglRoleHelper.requireStudentTeamId());
|
||||
filter.setXydmc(null);
|
||||
filter.setXybh(null);
|
||||
filter.setJybh(null);
|
||||
filter.setJybhs(null);
|
||||
} else {
|
||||
if (StringUtils.isNotEmpty(filter.getXybh())) {
|
||||
filter.setXydbh(resolveTeamByStudent(filter.getXybh().trim()));
|
||||
filter.setXydmc(null);
|
||||
}
|
||||
if (jwglRoleHelper.isTeacher()) {
|
||||
filter.setJybh(jwglRoleHelper.requireTeacherId());
|
||||
filter.setJybhs(null);
|
||||
}
|
||||
if (jwglRoleHelper.isResearchOffice() && StringUtils.isEmpty(filter.getJysdh())) {
|
||||
filter.setJysdh(jwglRoleHelper.requireResearchOfficeId());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(filter.getXydbh())) {
|
||||
filter.setXydbh(filter.getXydbh().trim());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(filter.getXydmc())) {
|
||||
filter.setXydmc(filter.getXydmc().trim());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(filter.getJybh())) {
|
||||
filter.setJybh(filter.getJybh().trim());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(filter.getJsbh())) {
|
||||
filter.setJsbh(filter.getJsbh().trim());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(filter.getJysdh())) {
|
||||
filter.setJysdh(filter.getJysdh().trim());
|
||||
}
|
||||
}
|
||||
if (filter.getNd() != null) {
|
||||
return filter;
|
||||
@@ -212,4 +263,14 @@ public class KCBServiceImpl implements KCBService {
|
||||
return new String[]{start.format(DATE_FORMAT), end.format(DATE_FORMAT)};
|
||||
}
|
||||
|
||||
/** 学员编号 → 其当前所属学员队编号(查不到返回原值使结果为空而非报错) */
|
||||
private String resolveTeamByStudent(String xybh) {
|
||||
XYXX student = xyxxMapper.selectById(xybh);
|
||||
if (student == null || StringUtils.isEmpty(student.getXydqbh())) {
|
||||
return xybh;
|
||||
}
|
||||
XYDQB period = xydqbMapper.selectById(student.getXydqbh());
|
||||
return period == null || StringUtils.isEmpty(period.getXydbh()) ? xybh : period.getXydbh();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
package com.roomroot.jwgl.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.roomroot.common.exception.ServiceException;
|
||||
import com.roomroot.common.utils.StringUtils;
|
||||
import com.roomroot.jwgl.dto.kcb.TimetableGridQuery;
|
||||
import com.roomroot.jwgl.dto.kcb.TimetableQuery;
|
||||
import com.roomroot.jwgl.entity.JXRW;
|
||||
import com.roomroot.jwgl.entity.JYB;
|
||||
import com.roomroot.jwgl.entity.JYSX;
|
||||
import com.roomroot.jwgl.entity.KB;
|
||||
import com.roomroot.jwgl.entity.XYDB;
|
||||
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
|
||||
import com.roomroot.jwgl.entity.XYDRWB;
|
||||
import com.roomroot.jwgl.entity.XYXX;
|
||||
import com.roomroot.jwgl.entity.JSB;
|
||||
import com.roomroot.jwgl.mapper.ClassRoomMapper;
|
||||
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
|
||||
import com.roomroot.jwgl.mapper.JYBMapper;
|
||||
import com.roomroot.jwgl.mapper.JYSXMapper;
|
||||
import com.roomroot.jwgl.mapper.KBMapper;
|
||||
import com.roomroot.jwgl.mapper.KcbTimetableMapper;
|
||||
import com.roomroot.jwgl.mapper.StudentTeamTaskMapper;
|
||||
import com.roomroot.jwgl.mapper.TeachingTaskMapper;
|
||||
import com.roomroot.jwgl.mapper.XYDBMapper;
|
||||
import com.roomroot.jwgl.mapper.XYXXMapper;
|
||||
import com.roomroot.jwgl.service.MyBusinessService;
|
||||
import com.roomroot.jwgl.service.TimetableGridService;
|
||||
import com.roomroot.jwgl.utils.JwglRoleHelper;
|
||||
import com.roomroot.jwgl.vo.kcb.ClassTimetableVO;
|
||||
import com.roomroot.jwgl.vo.kcb.DailyWeeklyTimetableVO;
|
||||
import com.roomroot.jwgl.vo.kcb.TimetableGridVO;
|
||||
import com.roomroot.jwgl.vo.mybusiness.MyCourseTaskVO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
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;
|
||||
|
||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
|
||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.FORBIDDEN;
|
||||
|
||||
/**
|
||||
* 「我的教学」角色视图实现(阶段 C)。
|
||||
*/
|
||||
@Service
|
||||
public class MyBusinessServiceImpl implements MyBusinessService {
|
||||
|
||||
private static final DateTimeFormatter DAY = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
@Resource
|
||||
private JwglRoleHelper roleHelper;
|
||||
|
||||
@Resource
|
||||
private KcbTimetableMapper kcbTimetableMapper;
|
||||
|
||||
@Resource
|
||||
private StudentTeamTaskMapper taskMapper;
|
||||
|
||||
@Resource
|
||||
private ClassSemesterMapper classSemesterMapper;
|
||||
|
||||
@Resource
|
||||
private TeachingTaskMapper jxrwMapper;
|
||||
|
||||
@Resource
|
||||
private KBMapper kbMapper;
|
||||
|
||||
@Resource
|
||||
private XYDBMapper xydbMapper;
|
||||
|
||||
@Resource
|
||||
private JYBMapper jybMapper;
|
||||
|
||||
@Resource
|
||||
private JYSXMapper jysxMapper;
|
||||
|
||||
@Resource
|
||||
private ClassRoomMapper classRoomMapper;
|
||||
|
||||
@Resource
|
||||
private XYXXMapper xyxxMapper;
|
||||
|
||||
@Resource
|
||||
private TimetableGridService timetableGridService;
|
||||
|
||||
// ==================== 教员 ====================
|
||||
|
||||
@Override
|
||||
public List<MyCourseTaskVO> teacherCourses(Integer nd, Integer xqdc) {
|
||||
String self = roleHelper.requireTeacherId();
|
||||
List<XYDRWB> rows = taskMapper.selectList(new LambdaQueryWrapper<XYDRWB>()
|
||||
.eq(XYDRWB::getDelFlag, 0)
|
||||
.and(w -> w.eq(XYDRWB::getJybh, self).or().eq(XYDRWB::getJysjhjybh, self))
|
||||
.eq(nd != null, XYDRWB::getNd, nd)
|
||||
.eq(xqdc != null, XYDRWB::getXqdc, xqdc)
|
||||
.orderByAsc(XYDRWB::getKbh).orderByAsc(XYDRWB::getKcxh));
|
||||
return decorate(rows);
|
||||
}
|
||||
|
||||
private List<MyCourseTaskVO> decorate(List<XYDRWB> rows) {
|
||||
Set<String> semBhs = rows.stream().map(XYDRWB::getXydxqbh).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
Map<String, XYDNDXQJBXXB> sems = semBhs.isEmpty() ? Map.of()
|
||||
: classSemesterMapper.selectBatchIds(semBhs).stream()
|
||||
.collect(Collectors.toMap(XYDNDXQJBXXB::getBh, s -> s, (a, b) -> a));
|
||||
Set<String> taskBhs = sems.values().stream().map(XYDNDXQJBXXB::getJxrwbh)
|
||||
.filter(StringUtils::isNotEmpty).collect(Collectors.toSet());
|
||||
Map<String, String> taskNames = taskBhs.isEmpty() ? Map.of()
|
||||
: jxrwMapper.selectBatchIds(taskBhs).stream()
|
||||
.collect(Collectors.toMap(JXRW::getBh, t -> t.getRwmc() == null ? "" : t.getRwmc(), (a, b) -> a));
|
||||
Set<String> kbhs = rows.stream().map(XYDRWB::getKbh).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
Map<String, KB> kbs = kbhs.isEmpty() ? Map.of()
|
||||
: kbMapper.selectBatchIds(kbhs).stream()
|
||||
.collect(Collectors.toMap(KB::getKbh, k -> k, (a, b) -> a));
|
||||
Set<String> teamBhs = rows.stream().map(XYDRWB::getXydbh).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
Map<String, String> teamNames = teamBhs.isEmpty() ? Map.of()
|
||||
: xydbMapper.selectList(new LambdaQueryWrapper<XYDB>().in(XYDB::getXydbh, teamBhs)).stream()
|
||||
.collect(Collectors.toMap(XYDB::getXydbh, t -> t.getXydmc() == null ? t.getXydbh() : t.getXydmc(), (a, b) -> a));
|
||||
Set<String> jybhSet = new java.util.HashSet<>();
|
||||
rows.forEach(r -> { if (r.getJybh() != null) jybhSet.add(r.getJybh()); if (r.getJysjhjybh() != null) jybhSet.add(r.getJysjhjybh()); });
|
||||
Map<String, String> teacherNames = jybhSet.isEmpty() ? Map.of()
|
||||
: jybMapper.selectList(new LambdaQueryWrapper<JYB>().in(JYB::getJybh, jybhSet)).stream()
|
||||
.collect(Collectors.toMap(JYB::getJybh, t -> t.getJyxm() == null ? t.getJybh() : t.getJyxm(), (a, b) -> a));
|
||||
Map<String, Integer> directorOf = jybhSet.isEmpty() ? Map.of()
|
||||
: jysxMapper.selectBatchIds(jybhSet).stream()
|
||||
.collect(Collectors.toMap(JYSX::getJybh, t -> t.getZr() == null ? 0 : t.getZr(), (a, b) -> a));
|
||||
Set<String> jsbhSet = rows.stream().map(XYDRWB::getJsbh).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
Map<String, String> roomNames = jsbhSet.isEmpty() ? Map.of()
|
||||
: classRoomMapper.selectList(new LambdaQueryWrapper<JSB>()
|
||||
.in(JSB::getJsbh, jsbhSet).eq(JSB::getDelFlag, 0)).stream()
|
||||
.collect(Collectors.toMap(JSB::getJsbh, j -> j.getJsmc() == null ? j.getJsbh() : j.getJsmc(), (a, b) -> a));
|
||||
List<MyCourseTaskVO> out = new ArrayList<>();
|
||||
for (XYDRWB r : rows) {
|
||||
MyCourseTaskVO vo = new MyCourseTaskVO();
|
||||
vo.setBh(r.getBh());
|
||||
vo.setXydxqbh(r.getXydxqbh());
|
||||
vo.setNd(r.getNd());
|
||||
vo.setXqdc(r.getXqdc());
|
||||
vo.setKbh(r.getKbh());
|
||||
KB kb = kbs.get(r.getKbh());
|
||||
vo.setKcmc(StringUtils.isNotEmpty(r.getJc()) ? r.getJc()
|
||||
: (kb != null && kb.getKmc() != null ? kb.getKmc() : r.getKbh()));
|
||||
vo.setKlx(r.getKlx());
|
||||
vo.setXs(r.getXs());
|
||||
vo.setZks(r.getZks());
|
||||
vo.setXydbh(r.getXydbh());
|
||||
vo.setXydmc(teamNames.get(r.getXydbh()));
|
||||
vo.setJybh(r.getJybh());
|
||||
vo.setJyxm(teacherNames.get(r.getJybh()));
|
||||
vo.setZr(directorOf.get(r.getJybh()));
|
||||
vo.setJysjhjybh(r.getJysjhjybh());
|
||||
vo.setJysjhjyxm(teacherNames.get(r.getJysjhjybh()));
|
||||
vo.setJysdh(r.getJysdh());
|
||||
vo.setJsmc(roomNames.get(r.getJsbh()));
|
||||
vo.setJsbh(r.getJsbh());
|
||||
vo.setKcxh(r.getKcxh());
|
||||
vo.setBz2(r.getBz2());
|
||||
XYDNDXQJBXXB sem = sems.get(r.getXydxqbh());
|
||||
if (sem != null) {
|
||||
vo.setJxrwbh(sem.getJxrwbh());
|
||||
vo.setRwmc(taskNames.get(sem.getJxrwbh()));
|
||||
}
|
||||
out.add(vo);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DailyWeeklyTimetableVO> teacherLessons(String start, String end, Integer nd) {
|
||||
String self = roleHelper.requireTeacherId();
|
||||
String[] range = weekRange(start, end);
|
||||
TimetableQuery cond = new TimetableQuery();
|
||||
cond.setJybh(self);
|
||||
cond.setNd(nd);
|
||||
return kcbTimetableMapper.selectLessonTimetable(cond, range[0], range[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimetableGridVO teacherGrid(String mbbh, String start, String end, Integer nd) {
|
||||
TimetableGridQuery q = new TimetableGridQuery();
|
||||
q.setDim("teacher");
|
||||
q.setId(roleHelper.requireTeacherId());
|
||||
q.setMbbh(mbbh);
|
||||
q.setStart(start);
|
||||
q.setEnd(end);
|
||||
q.setNd(nd);
|
||||
return timetableGridService.grid(q);
|
||||
}
|
||||
|
||||
// ==================== 教研室 ====================
|
||||
|
||||
@Override
|
||||
public List<DailyWeeklyTimetableVO> officeToday(String rq, Integer offset) {
|
||||
String office = roleHelper.requireResearchOfficeId();
|
||||
LocalDate base;
|
||||
if (StringUtils.isEmpty(rq)) {
|
||||
base = LocalDate.now();
|
||||
} else {
|
||||
try {
|
||||
base = LocalDate.parse(rq.trim(), DAY);
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("日期格式应为 yyyy-MM-dd", BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
LocalDate day = base.plusDays(offset == null ? 0 : offset);
|
||||
List<String> teachers = officeTeacherIds(office);
|
||||
if (teachers.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
TimetableQuery cond = new TimetableQuery();
|
||||
cond.setJybhs(teachers);
|
||||
String d = day.format(DAY);
|
||||
return kcbTimetableMapper.selectLessonTimetable(cond, d, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ClassTimetableVO> officeCourses(Integer nd, Integer xqdc) {
|
||||
String office = roleHelper.requireResearchOfficeId();
|
||||
TimetableQuery cond = new TimetableQuery();
|
||||
cond.setJysdh(office);
|
||||
cond.setNd(nd);
|
||||
cond.setXqdc(xqdc);
|
||||
return kcbTimetableMapper.selectClassTimetableList(cond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> officeTeachers() {
|
||||
String office = roleHelper.requireResearchOfficeId();
|
||||
List<Map<String, Object>> out = new ArrayList<>();
|
||||
List<JYB> teachers = jybMapper.selectList(new LambdaQueryWrapper<JYB>()
|
||||
.eq(JYB::getJysdh, office)
|
||||
.and(w -> w.isNull(JYB::getLzzt).or().ne(JYB::getLzzt, 1)));
|
||||
Set<String> ids = teachers.stream().map(JYB::getJybh).collect(Collectors.toSet());
|
||||
Map<String, Integer> directorOf = ids.isEmpty() ? Map.of()
|
||||
: jysxMapper.selectBatchIds(ids).stream()
|
||||
.collect(Collectors.toMap(JYSX::getJybh, t -> t.getZr() == null ? 0 : t.getZr(), (a, b) -> a));
|
||||
for (JYB t : teachers) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("jybh", t.getJybh());
|
||||
m.put("jyxm", t.getJyxm());
|
||||
m.put("zr", directorOf.getOrDefault(t.getJybh(), 0));
|
||||
out.add(m);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<String> officeTeacherIds(String office) {
|
||||
return jybMapper.selectList(new LambdaQueryWrapper<JYB>()
|
||||
.eq(JYB::getJysdh, office)
|
||||
.and(w -> w.isNull(JYB::getLzzt).or().ne(JYB::getLzzt, 1))).stream()
|
||||
.map(JYB::getJybh).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ==================== 学员 ====================
|
||||
|
||||
@Override
|
||||
public List<ClassTimetableVO> studentCourses(Integer nd, Integer xqdc) {
|
||||
String team = roleHelper.requireStudentTeamId();
|
||||
TimetableQuery cond = new TimetableQuery();
|
||||
cond.setXydbh(team);
|
||||
cond.setNd(nd);
|
||||
cond.setXqdc(xqdc);
|
||||
return kcbTimetableMapper.selectClassTimetableList(cond);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DailyWeeklyTimetableVO> studentLessons(String start, String end) {
|
||||
String team = roleHelper.requireStudentTeamId();
|
||||
String[] range = weekRange(start, end);
|
||||
TimetableQuery cond = new TimetableQuery();
|
||||
cond.setXydbh(team);
|
||||
return kcbTimetableMapper.selectLessonTimetable(cond, range[0], range[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimetableGridVO studentGrid(String mbbh, String start, String end, Integer nd) {
|
||||
TimetableGridQuery q = new TimetableGridQuery();
|
||||
q.setDim("team");
|
||||
q.setId(roleHelper.requireStudentTeamId());
|
||||
q.setMbbh(mbbh);
|
||||
q.setStart(start);
|
||||
q.setEnd(end);
|
||||
q.setNd(nd);
|
||||
return timetableGridService.grid(q);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> teamCadres() {
|
||||
String studentId = roleHelper.requireStudentId();
|
||||
XYXX me = xyxxMapper.selectById(studentId);
|
||||
if (me == null || StringUtils.isEmpty(me.getXydqbh())) {
|
||||
throw new ServiceException("当前学员未分班,无法查看学员队信息", FORBIDDEN);
|
||||
}
|
||||
List<XYXX> cadres = xyxxMapper.selectList(new LambdaQueryWrapper<XYXX>()
|
||||
.eq(XYXX::getXydqbh, me.getXydqbh())
|
||||
.isNotNull(XYXX::getGgrz)
|
||||
.ne(XYXX::getGgrz, ""));
|
||||
List<Map<String, Object>> out = new ArrayList<>();
|
||||
for (XYXX s : cadres) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("xh", s.getXh());
|
||||
m.put("xm", s.getXm());
|
||||
m.put("ggrz", s.getGgrz());
|
||||
out.add(m);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** start/end 缺省时取本周(周一~周日) */
|
||||
private String[] weekRange(String start, String end) {
|
||||
LocalDate s = StringUtils.isEmpty(start) ? null : LocalDate.parse(start.trim(), DAY);
|
||||
LocalDate e = StringUtils.isEmpty(end) ? null : LocalDate.parse(end.trim(), DAY);
|
||||
if (s == null && e == null) {
|
||||
LocalDate monday = LocalDate.now().with(DayOfWeek.MONDAY);
|
||||
s = monday;
|
||||
e = monday.plusDays(6);
|
||||
} else if (s == null) {
|
||||
s = e;
|
||||
} else if (e == null) {
|
||||
e = s;
|
||||
}
|
||||
return new String[]{s.format(DAY), e.format(DAY)};
|
||||
}
|
||||
}
|
||||
+249
@@ -6,6 +6,7 @@ import com.roomroot.common.utils.SecurityUtils;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingArrangeRequest;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingCellOpRequest;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingCourseEditRequest;
|
||||
import com.roomroot.jwgl.dto.scheduling.SchedulingMoveRequest;
|
||||
import com.roomroot.jwgl.entity.BZLB;
|
||||
import com.roomroot.jwgl.entity.BZTGMX;
|
||||
import com.roomroot.jwgl.entity.JCSJB;
|
||||
@@ -55,9 +56,18 @@ import com.roomroot.jwgl.vo.scheduling.SchedulingViewVO;
|
||||
import com.roomroot.common.core.domain.entity.SysUser;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -712,6 +722,166 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== 拖拽移动(E4) ====================
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Map<String, Object> moveLesson(SchedulingMoveRequest request) {
|
||||
if (request == null || request.getSskcbh() == null
|
||||
|| request.getFrom() == null || request.getFrom().getRq() == null || request.getFrom().getJc() == null
|
||||
|| request.getTo() == null || request.getTo().getRq() == null || request.getTo().getJc() == null) {
|
||||
throw new ServiceException("请指定课程与源/目标节次", BAD_REQUEST);
|
||||
}
|
||||
SSKC course = requireCourse(request.getSskcbh());
|
||||
Ctx ctx = loadCtxForCourse(course);
|
||||
if (!canEdit(course, ctx)) {
|
||||
throw new ServiceException("没有该课程的排课权限", FORBIDDEN);
|
||||
}
|
||||
String fromKey = request.getFrom().getRq() + "#" + request.getFrom().getJc();
|
||||
String toKey = request.getTo().getRq() + "#" + request.getTo().getJc();
|
||||
if (fromKey.equals(toKey)) {
|
||||
Map<String, Object> same = new HashMap<>();
|
||||
same.put("moved", 0);
|
||||
return same;
|
||||
}
|
||||
// 找源格课次
|
||||
SSKCB lesson = null;
|
||||
List<SSKCB> courseLessons = sskcbMapper.selectList(new LambdaQueryWrapper<SSKCB>()
|
||||
.eq(SSKCB::getSskcbh, course.getBh()));
|
||||
for (SSKCB l : courseLessons) {
|
||||
if (l.getRq() == null || l.getJc() == null) continue;
|
||||
if (fromKey.equals(l.getRq().toLocalDate().toString() + "#" + l.getJc())) {
|
||||
lesson = l;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lesson == null) {
|
||||
throw new ServiceException("源节次没有该课程的课次", BAD_REQUEST);
|
||||
}
|
||||
if (lesson.getJhsjap() != null && lesson.getJhsjap() == 1) {
|
||||
throw new ServiceException(request.getFrom().getRq() + " 第" + request.getFrom().getJc()
|
||||
+ "节已提交实施计划,请走调课申请", BAD_REQUEST);
|
||||
}
|
||||
// 课次关联资源
|
||||
List<String> lessonRooms = sskcbjsMapper.selectList(new LambdaQueryWrapper<SSKCBJS>()
|
||||
.eq(SSKCBJS::getSskcbbh, lesson.getBh())).stream()
|
||||
.map(SSKCBJS::getJsbh).filter(s -> s != null && !s.isEmpty()).distinct().toList();
|
||||
List<String> lessonTeachers = sskcbfzjyMapper.selectList(new LambdaQueryWrapper<SSKCBFZJY>()
|
||||
.eq(SSKCBFZJY::getSskcbbh, lesson.getBh())).stream()
|
||||
.map(SSKCBFZJY::getFzjybh).filter(s -> s != null && !s.isEmpty()).distinct().toList();
|
||||
List<String> lessonTeams = sskcbxydMapper.selectList(new LambdaQueryWrapper<SSKCBXYD>()
|
||||
.eq(SSKCBXYD::getSskcbbh, lesson.getBh())).stream()
|
||||
.map(SSKCBXYD::getXydbh).filter(s -> s != null && !s.isEmpty()).distinct().toList();
|
||||
|
||||
// 目标格硬冲突(与 arrange 同口径)
|
||||
Set<String> schoolHard = calendarHardKeys(ctx, true);
|
||||
Set<String> classHard = calendarHardKeys(ctx, false);
|
||||
if (schoolHard.contains(toKey)) {
|
||||
throw new ServiceException("目标节次校历不可排课", BAD_REQUEST);
|
||||
}
|
||||
if (classHard.contains(toKey)) {
|
||||
throw new ServiceException("目标节次班历不可排课", BAD_REQUEST);
|
||||
}
|
||||
Map<String, String> roomBlocked = new HashMap<>();
|
||||
for (JXCDL row : classRoomCalendarMapper.selectList(new LambdaQueryWrapper<JXCDL>()
|
||||
.eq(JXCDL::getKpk, false).eq(JXCDL::getDelFlag, 0))) {
|
||||
if (row.getRq() != null && row.getJsbh() != null) {
|
||||
roomBlocked.put(row.getRq().toLocalDate().toString() + "#" + row.getJc() + "#" + row.getJsbh(), "场地历不可用");
|
||||
}
|
||||
}
|
||||
for (String room : lessonRooms) {
|
||||
if (roomBlocked.containsKey(toKey + "#" + room)) {
|
||||
throw new ServiceException("目标节次场地历不可用(教室 " + resolveRoomName(room) + ")", BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
Map<String, String> teacherBlocked = new HashMap<>();
|
||||
for (JYL row : jylMapper.selectList(new LambdaQueryWrapper<JYL>().eq(JYL::getKpk, 0))) {
|
||||
if (row.getRq() != null && row.getJybh() != null) {
|
||||
teacherBlocked.put(row.getRq().toLocalDate().toString() + "#" + row.getJc() + "#" + row.getJybh(), "教员历不可用");
|
||||
}
|
||||
}
|
||||
for (String jybh : lessonTeachers) {
|
||||
if (teacherBlocked.containsKey(toKey + "#" + jybh)) {
|
||||
throw new ServiceException("目标节次教员历不可用(" + resolveTeacherName(jybh) + ")", BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
// 目标格双占:其它课程的课次与本课次共享学员队/教室/教员
|
||||
List<SSKCB> semesterLessons = ctx.lessons;
|
||||
List<String> occupantIds = semesterLessons.stream()
|
||||
.filter(l -> l.getRq() != null && l.getJc() != null
|
||||
&& toKey.equals(l.getRq().toLocalDate().toString() + "#" + l.getJc())
|
||||
&& !course.getBh().equals(l.getSskcbh()))
|
||||
.map(SSKCB::getBh).toList();
|
||||
if (!occupantIds.isEmpty()) {
|
||||
Set<String> occTeams = sskcbxydMapper.selectList(new LambdaQueryWrapper<SSKCBXYD>()
|
||||
.in(SSKCBXYD::getSskcbbh, occupantIds)).stream()
|
||||
.map(SSKCBXYD::getXydbh).collect(Collectors.toSet());
|
||||
Set<String> occRooms = sskcbjsMapper.selectList(new LambdaQueryWrapper<SSKCBJS>()
|
||||
.in(SSKCBJS::getSskcbbh, occupantIds)).stream()
|
||||
.map(SSKCBJS::getJsbh).collect(Collectors.toSet());
|
||||
Set<String> occTeachers = sskcbfzjyMapper.selectList(new LambdaQueryWrapper<SSKCBFZJY>()
|
||||
.in(SSKCBFZJY::getSskcbbh, occupantIds)).stream()
|
||||
.map(SSKCBFZJY::getFzjybh).collect(Collectors.toSet());
|
||||
if (lessonTeams.stream().anyMatch(occTeams::contains)) {
|
||||
throw new ServiceException("目标节次班次已有其它课程", BAD_REQUEST);
|
||||
}
|
||||
if (lessonRooms.stream().anyMatch(occRooms::contains)) {
|
||||
throw new ServiceException("目标节次教室已被占用", BAD_REQUEST);
|
||||
}
|
||||
if (lessonTeachers.stream().anyMatch(occTeachers::contains)) {
|
||||
throw new ServiceException("目标节次教员已有课", BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
// 软提示:配当周次不符照常移动
|
||||
String warning = null;
|
||||
Set<Integer> allocatedWeeks = allocatedWeeks(ctx, course);
|
||||
LocalDate toDate = LocalDate.parse(request.getTo().getRq());
|
||||
if (allocatedWeeks != null && !allocatedWeeks.isEmpty()) {
|
||||
int weekNo = weekNoOf(ctx, toDate);
|
||||
if (!allocatedWeeks.contains(weekNo)) {
|
||||
warning = "配当周次不符(第 " + weekNo + " 周不在该课配当范围)";
|
||||
}
|
||||
}
|
||||
|
||||
// 平移主表 + 子表日期节次
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
lesson.setRq(toDate.atStartOfDay());
|
||||
lesson.setJc(request.getTo().getJc());
|
||||
lesson.setBdsj(now);
|
||||
sskcbMapper.updateById(lesson);
|
||||
for (SSKCBXYD l : sskcbxydMapper.selectList(new LambdaQueryWrapper<SSKCBXYD>()
|
||||
.eq(SSKCBXYD::getSskcbbh, lesson.getBh()))) {
|
||||
l.setRq(lesson.getRq());
|
||||
l.setJc2(request.getTo().getJc());
|
||||
sskcbxydMapper.updateById(l);
|
||||
}
|
||||
for (SSKCBJS l : sskcbjsMapper.selectList(new LambdaQueryWrapper<SSKCBJS>()
|
||||
.eq(SSKCBJS::getSskcbbh, lesson.getBh()))) {
|
||||
l.setRq(lesson.getRq());
|
||||
l.setJc(request.getTo().getJc());
|
||||
sskcbjsMapper.updateById(l);
|
||||
}
|
||||
for (SSKCBFZJY l : sskcbfzjyMapper.selectList(new LambdaQueryWrapper<SSKCBFZJY>()
|
||||
.eq(SSKCBFZJY::getSskcbbh, lesson.getBh()))) {
|
||||
l.setRq(lesson.getRq());
|
||||
l.setJc(request.getTo().getJc());
|
||||
sskcbfzjyMapper.updateById(l);
|
||||
}
|
||||
for (KCBBZMX l : kcbbzMapper.selectList(new LambdaQueryWrapper<KCBBZMX>()
|
||||
.eq(KCBBZMX::getKcbbh, lesson.getBh()))) {
|
||||
l.setRq(lesson.getRq());
|
||||
l.setJc(request.getTo().getJc());
|
||||
kcbbzMapper.updateById(l);
|
||||
}
|
||||
logOperation(course, "移动", "课次 " + fromKey + " → " + toKey, null);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("moved", 1);
|
||||
if (warning != null) result.put("warning", warning);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== 删除 ====================
|
||||
|
||||
@Override
|
||||
@@ -815,6 +985,85 @@ public class SchedulingWindowServiceImpl implements SchedulingWindowService {
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ==================== 导出(E4) ====================
|
||||
|
||||
@Override
|
||||
public byte[] exportView(String xydxqbh) {
|
||||
SchedulingViewVO vo = view(xydxqbh);
|
||||
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);
|
||||
|
||||
String[] heads = {"节次", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
|
||||
for (SchedulingViewVO.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.getXydmc() == null ? "" : vo.getXydmc())
|
||||
+ " 排课窗口(" + week.getStartDate() + " ~ " + week.getEndDate() + ")");
|
||||
tc.setCellStyle(title);
|
||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 7));
|
||||
|
||||
org.apache.poi.ss.usermodel.Row headRow = sheet.createRow(r++);
|
||||
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 (SchedulingViewVO.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.getLabel() != null ? p.getLabel() : String.valueOf(p.getJc()));
|
||||
pc.setCellStyle(head);
|
||||
for (SchedulingViewVO.Day day : week.getDays()) {
|
||||
org.apache.poi.ss.usermodel.Cell c = row.createCell(day.getWeekday());
|
||||
SchedulingViewVO.Cell cell = day.getCells().stream()
|
||||
.filter(x -> Objects.equals(x.getJc(), p.getJc()))
|
||||
.findFirst().orElse(null);
|
||||
if (cell != null) {
|
||||
if (cell.getLessons() != null && !cell.getLessons().isEmpty()) {
|
||||
c.setCellValue(cell.getLessons().stream()
|
||||
.map(l -> {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(l.getKcmc() == null ? "" : l.getKcmc());
|
||||
if (l.getJyxm() != null) sb.append("\n").append(l.getJyxm());
|
||||
if (l.getJsmc() != null) sb.append("\n").append(l.getJsmc());
|
||||
return sb.toString();
|
||||
}).collect(Collectors.joining("\n----\n")));
|
||||
} else if (Boolean.TRUE.equals(cell.getUnavailable())) {
|
||||
c.setCellValue(cell.getReason() == null ? "不可排" : cell.getReason());
|
||||
}
|
||||
}
|
||||
c.setCellStyle(center);
|
||||
}
|
||||
}
|
||||
sheet.setColumnWidth(0, 10 * 256);
|
||||
for (int i = 1; i <= 7; i++) {
|
||||
sheet.setColumnWidth(i, 24 * 256);
|
||||
}
|
||||
}
|
||||
wb.write(out);
|
||||
return out.toByteArray();
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("导出排课窗失败:" + e.getMessage(), BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/** 操作人显示名:按登录名取用户昵称,取不到则回显登录名 */
|
||||
private String resolveOperatorName(String username) {
|
||||
if (username == null || username.isEmpty()) return "-";
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.roomroot.jwgl.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.roomroot.jwgl.entity.DBVersion;
|
||||
import com.roomroot.jwgl.mapper.DBVersionMapper;
|
||||
import com.roomroot.jwgl.service.SystemSettingsService;
|
||||
import com.roomroot.jwgl.utils.UuidUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 教务系统参数实现:DBVersion 单行 get-or-create,更新仅写非空字段。
|
||||
*/
|
||||
@Service
|
||||
public class SystemSettingsServiceImpl implements SystemSettingsService {
|
||||
|
||||
@Resource
|
||||
private DBVersionMapper dbVersionMapper;
|
||||
|
||||
@Override
|
||||
public DBVersion get() {
|
||||
DBVersion row = dbVersionMapper.selectOne(
|
||||
new LambdaQueryWrapper<DBVersion>().last("LIMIT 1"));
|
||||
return row == null ? new DBVersion() : row;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public DBVersion update(DBVersion settings) {
|
||||
DBVersion row = dbVersionMapper.selectOne(
|
||||
new LambdaQueryWrapper<DBVersion>().last("LIMIT 1"));
|
||||
if (row == null) {
|
||||
row = new DBVersion();
|
||||
row.setXYZKQJMBWJBH(UuidUtil.getUUID());
|
||||
apply(row, settings);
|
||||
dbVersionMapper.insert(row);
|
||||
return row;
|
||||
}
|
||||
apply(row, settings);
|
||||
dbVersionMapper.updateById(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
/** 只写非空字段,避免前端局部提交清空其它参数 */
|
||||
private void apply(DBVersion row, DBVersion src) {
|
||||
if (src.getXS78J() != null) {
|
||||
row.setXS78J(src.getXS78J());
|
||||
}
|
||||
if (src.getXSWS() != null) {
|
||||
row.setXSWS(src.getXSWS());
|
||||
}
|
||||
if (src.getXSYJ() != null) {
|
||||
row.setXSYJ(src.getXSYJ());
|
||||
}
|
||||
if (src.getJXYDMC() != null) {
|
||||
row.setJXYDMC(src.getJXYDMC());
|
||||
}
|
||||
}
|
||||
}
|
||||
+210
-6
@@ -2,22 +2,30 @@ package com.roomroot.jwgl.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.roomroot.common.exception.ServiceException;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBatchFillRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBatchRoomRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBatchTeacherRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookBhListRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookFillRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookRoomRequest;
|
||||
import com.roomroot.jwgl.dto.taskbook.TaskBookTeacherRequest;
|
||||
import com.roomroot.jwgl.entity.JYB;
|
||||
import com.roomroot.jwgl.entity.JXRW;
|
||||
import com.roomroot.jwgl.entity.JYSB;
|
||||
import com.roomroot.jwgl.entity.JYSRWS;
|
||||
import com.roomroot.jwgl.entity.SSKCXYD;
|
||||
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
|
||||
import com.roomroot.jwgl.entity.XYDRWB;
|
||||
import com.roomroot.jwgl.mapper.ClassSemesterMapper;
|
||||
import com.roomroot.jwgl.mapper.JYBMapper;
|
||||
import com.roomroot.jwgl.mapper.JYSBMapper;
|
||||
import com.roomroot.jwgl.mapper.OfficeTaskBookMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCXYDMapper;
|
||||
import com.roomroot.jwgl.mapper.StudentTeamTaskMapper;
|
||||
import com.roomroot.jwgl.mapper.TeachingTaskMapper;
|
||||
import com.roomroot.jwgl.service.TaskBookFillService;
|
||||
import com.roomroot.jwgl.service.TeachingTaskWriteGuard;
|
||||
import com.roomroot.jwgl.utils.JwglRoleHelper;
|
||||
import com.roomroot.jwgl.utils.SemesterCodeUtil;
|
||||
import com.roomroot.jwgl.utils.TeachingTaskStatus;
|
||||
import com.roomroot.jwgl.vo.taskbook.TaskBookRowVO;
|
||||
@@ -36,6 +44,7 @@ import java.util.Set;
|
||||
|
||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
|
||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.CONFLICT;
|
||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.FORBIDDEN;
|
||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND;
|
||||
|
||||
/**
|
||||
@@ -62,10 +71,24 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
@Resource
|
||||
private SSKCXYDMapper sskcxydMapper;
|
||||
|
||||
@Resource
|
||||
private OfficeTaskBookMapper officeTaskBookMapper;
|
||||
|
||||
@Resource
|
||||
private JYSBMapper jysbMapper;
|
||||
|
||||
@Resource
|
||||
private JwglRoleHelper roleHelper;
|
||||
|
||||
// ==================== 列表 ====================
|
||||
|
||||
@Override
|
||||
public List<TaskBookRowVO> list(String jxrwbh) {
|
||||
return list(jxrwbh, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TaskBookRowVO> list(String jxrwbh, String jybh) {
|
||||
if (jxrwbh == null || jxrwbh.isEmpty()) {
|
||||
throw new ServiceException("请指定教学任务", BAD_REQUEST);
|
||||
}
|
||||
@@ -74,6 +97,27 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
throw new ServiceException("教学任务不存在:" + jxrwbh, NOT_FOUND);
|
||||
}
|
||||
List<TaskBookRowVO> rows = jxrwMapper.selectTaskBookRows(jxrwbh);
|
||||
// 身份范围:学员无权看任务书;教员只看本人相关行;教研室只看本室行
|
||||
if (roleHelper.isStudent()) {
|
||||
throw new ServiceException("学员无权查看教学任务书", FORBIDDEN);
|
||||
}
|
||||
if (roleHelper.isTeacher()) {
|
||||
String self = roleHelper.requireTeacherId();
|
||||
rows = rows.stream()
|
||||
.filter(r -> self.equals(r.getJybh()) || self.equals(r.getJysjhjybh()))
|
||||
.toList();
|
||||
} else if (roleHelper.isResearchOffice()) {
|
||||
String office = roleHelper.requireResearchOfficeId();
|
||||
rows = rows.stream()
|
||||
.filter(r -> office.equals(r.getJysdh()))
|
||||
.toList();
|
||||
}
|
||||
if (jybh != null && !jybh.isEmpty()) {
|
||||
String t = jybh.trim();
|
||||
rows = rows.stream()
|
||||
.filter(r -> t.equals(r.getJybh()) || t.equals(r.getJysjhjybh()))
|
||||
.toList();
|
||||
}
|
||||
boolean frozen = isFrozen(jxrw);
|
||||
rows.forEach(r -> r.setFrozen(frozen));
|
||||
return rows;
|
||||
@@ -91,6 +135,7 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
public Integer merge(TaskBookBhListRequest request) {
|
||||
List<XYDRWB> rows = loadRows(request);
|
||||
assertAllFillable(rows);
|
||||
assertOfficeEditable(rows);
|
||||
assertSameXq(rows);
|
||||
assertMergeCompatible(rows);
|
||||
int groupNo = nextGroupNo();
|
||||
@@ -163,6 +208,7 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
public List<Map<String, Object>> mergeByPreset(TaskBookBhListRequest request) {
|
||||
List<XYDRWB> rows = loadRows(request);
|
||||
assertAllFillable(rows);
|
||||
assertOfficeEditable(rows);
|
||||
assertSameXq(rows);
|
||||
// 行按 课程科目(kbh) → 预设编班号(ysbbh,空则各班次学期独立) 分组
|
||||
Map<String, List<XYDRWB>> groups = new LinkedHashMap<>();
|
||||
@@ -198,6 +244,7 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
public int split(TaskBookBhListRequest request) {
|
||||
List<XYDRWB> rows = loadRows(request);
|
||||
assertAllFillable(rows);
|
||||
assertOfficeEditable(rows);
|
||||
// 阶段 5 已落地:行所属班次学期若已发布到运行课表,合班组已被固化成运行课程,必须先撤回。
|
||||
assertNotPublishedToRunning(rows);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
@@ -221,9 +268,13 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
if (request == null || request.getBh() == null || request.getBh().isEmpty()) {
|
||||
throw new ServiceException("请指定课程任务", BAD_REQUEST);
|
||||
}
|
||||
XYDRWB row = requireRow(request.getBh());
|
||||
return setTeacherForRow(requireRow(request.getBh()), request.getMode(), request.getJybh());
|
||||
}
|
||||
|
||||
private int setTeacherForRow(XYDRWB row, String reqMode, String reqJybh) {
|
||||
assertFillable(row);
|
||||
String mode = request.getMode() == null ? "" : request.getMode();
|
||||
assertCanEdit(row);
|
||||
String mode = reqMode == null ? "" : reqMode;
|
||||
String jybh;
|
||||
if ("plan".equalsIgnoreCase(mode)) {
|
||||
// 应用教研室计划教员:jybh ← 该行 jysjhjybh
|
||||
@@ -232,7 +283,7 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
throw new ServiceException("该行没有教研室计划教员,请先在填报字段中指定", BAD_REQUEST);
|
||||
}
|
||||
} else if ("unit".equalsIgnoreCase(mode) || "academy".equalsIgnoreCase(mode)) {
|
||||
jybh = request.getJybh();
|
||||
jybh = reqJybh;
|
||||
if (jybh == null || jybh.isEmpty()) {
|
||||
throw new ServiceException("请选择教员", BAD_REQUEST);
|
||||
}
|
||||
@@ -254,23 +305,40 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
return updateGroupField(row, "jybh", jybh);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public int batchSetTeacher(TaskBookBatchTeacherRequest request) {
|
||||
if (request == null || request.getBhList() == null || request.getBhList().isEmpty()) {
|
||||
throw new ServiceException("请先选择课程任务行", BAD_REQUEST);
|
||||
}
|
||||
int count = 0;
|
||||
for (String bh : request.getBhList()) {
|
||||
count += setTeacherForRow(requireRow(bh), request.getMode(), request.getJybh());
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public int setRoom(TaskBookRoomRequest request) {
|
||||
if (request == null || request.getBh() == null || request.getBh().isEmpty()) {
|
||||
throw new ServiceException("请指定课程任务", BAD_REQUEST);
|
||||
}
|
||||
XYDRWB row = requireRow(request.getBh());
|
||||
return setRoomForRow(requireRow(request.getBh()), request.getJsbh(), request.getUseSpecial());
|
||||
}
|
||||
|
||||
private int setRoomForRow(XYDRWB row, String reqJsbh, Boolean useSpecial) {
|
||||
assertFillable(row);
|
||||
assertCanEdit(row);
|
||||
String jsbh;
|
||||
if (Boolean.TRUE.equals(request.getUseSpecial())) {
|
||||
if (Boolean.TRUE.equals(useSpecial)) {
|
||||
XYDNDXQJBXXB semester = requireSemester(row.getXydxqbh());
|
||||
jsbh = semester.getZyjsbh();
|
||||
if (jsbh == null || jsbh.isEmpty()) {
|
||||
throw new ServiceException("该班次学期没有专用教室,请改选其它教室", BAD_REQUEST);
|
||||
}
|
||||
} else {
|
||||
jsbh = request.getJsbh();
|
||||
jsbh = reqJsbh;
|
||||
if (jsbh == null || jsbh.isEmpty()) {
|
||||
throw new ServiceException("请选择教室,或选择使用班次专用教室", BAD_REQUEST);
|
||||
}
|
||||
@@ -278,6 +346,98 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
return updateGroupField(row, "jsbh", jsbh);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public int batchSetRoom(TaskBookBatchRoomRequest request) {
|
||||
if (request == null || request.getBhList() == null || request.getBhList().isEmpty()) {
|
||||
throw new ServiceException("请先选择课程任务行", BAD_REQUEST);
|
||||
}
|
||||
int count = 0;
|
||||
for (String bh : request.getBhList()) {
|
||||
count += setRoomForRow(requireRow(bh), request.getJsbh(), request.getUseSpecial());
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public int batchFill(TaskBookBatchFillRequest request) {
|
||||
if (request == null || request.getBhList() == null || request.getBhList().isEmpty()) {
|
||||
throw new ServiceException("请先选择课程任务行", BAD_REQUEST);
|
||||
}
|
||||
int count = 0;
|
||||
for (String bh : request.getBhList()) {
|
||||
XYDRWB row = requireRow(bh);
|
||||
TaskBookFillRequest item = new TaskBookFillRequest();
|
||||
item.setBh(bh);
|
||||
item.setJysjhjybh(request.getJysjhjybh());
|
||||
item.setJysjhbz(request.getJysjhbz());
|
||||
item.setKcxh(request.getKcxh());
|
||||
if (!Boolean.TRUE.equals(request.getUseSpecial())) {
|
||||
item.setJsbh(request.getJsbh());
|
||||
}
|
||||
count += fill(item);
|
||||
if (Boolean.TRUE.equals(request.getUseSpecial())) {
|
||||
count += setRoomForRow(row, null, true);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public int deleteRows(TaskBookBhListRequest request) {
|
||||
List<XYDRWB> rows = loadRows(request);
|
||||
assertAllFillable(rows);
|
||||
assertOfficeEditable(rows);
|
||||
assertNotPublishedToRunning(rows);
|
||||
int count = 0;
|
||||
for (XYDRWB row : rows) {
|
||||
row.setDelFlag(1);
|
||||
row.setBdsj(LocalDateTime.now());
|
||||
taskMapper.updateById(row);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> officeSummary(String jysdh) {
|
||||
if (roleHelper.isResearchOffice()) {
|
||||
jysdh = roleHelper.requireResearchOfficeId();
|
||||
} else if (roleHelper.isStudent() || roleHelper.isTeacher()) {
|
||||
throw new ServiceException("当前角色无权查看教研室任务书汇总", FORBIDDEN);
|
||||
}
|
||||
if (jysdh == null || jysdh.isEmpty()) {
|
||||
throw new ServiceException("请指定教研室代号", BAD_REQUEST);
|
||||
}
|
||||
String office = jysdh;
|
||||
List<JYSRWS> books = officeTaskBookMapper.selectByJysdh(office);
|
||||
JYSB jysb = jysbMapper.selectOne(new LambdaQueryWrapper<JYSB>()
|
||||
.eq(JYSB::getJysdh, office)
|
||||
.last("LIMIT 1"));
|
||||
String jysmc = jysb != null && jysb.getJysmc() != null ? jysb.getJysmc() : office;
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (JYSRWS book : books) {
|
||||
JXRW task = book.getJxrwbh() == null ? null : jxrwMapper.selectById(book.getJxrwbh());
|
||||
if (task == null || Integer.valueOf(1).equals(task.getDelFlag())) {
|
||||
continue;
|
||||
}
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("bh", book.getBh());
|
||||
item.put("jysdh", office);
|
||||
item.put("jysmc", jysmc);
|
||||
item.put("jxrwbh", book.getJxrwbh());
|
||||
item.put("rwmc", task.getRwmc());
|
||||
item.put("nd", task.getNd());
|
||||
item.put("taskZt", task.getZt());
|
||||
item.put("zt", book.getZt());
|
||||
item.put("sbsj", book.getSbsj());
|
||||
result.add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public int fill(TaskBookFillRequest request) {
|
||||
@@ -286,6 +446,7 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
}
|
||||
XYDRWB row = requireRow(request.getBh());
|
||||
assertFillable(row);
|
||||
assertCanEdit(row);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int count;
|
||||
if (request.getJysjhjybh() != null) {
|
||||
@@ -294,6 +455,9 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
if (request.getJysjhbz() != null) {
|
||||
row.setJysjhbz(request.getJysjhbz());
|
||||
}
|
||||
if (request.getKcxh() != null) {
|
||||
row.setKcxh(request.getKcxh());
|
||||
}
|
||||
row.setBdsj(now);
|
||||
taskMapper.updateById(row);
|
||||
count = 1;
|
||||
@@ -384,6 +548,46 @@ public class TaskBookFillServiceImpl implements TaskBookFillService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拟制权限(与排课窗口 canEdit 同口径):
|
||||
* 机关/管理员全部可改;教研室账号可改本室行;教员只能改本人责任/计划行;其余禁止。
|
||||
*/
|
||||
private void assertCanEdit(XYDRWB row) {
|
||||
if (roleHelper.isDepartmentPersonnel()) {
|
||||
return;
|
||||
}
|
||||
if (roleHelper.isResearchOffice()) {
|
||||
if (roleHelper.requireResearchOfficeId().equals(row.getJysdh())) {
|
||||
return;
|
||||
}
|
||||
throw new ServiceException("只能修改本教研室的课程任务", FORBIDDEN);
|
||||
}
|
||||
if (roleHelper.isTeacher()) {
|
||||
String self = roleHelper.requireTeacherId();
|
||||
if (self.equals(row.getJybh()) || self.equals(row.getJysjhjybh())) {
|
||||
return;
|
||||
}
|
||||
throw new ServiceException("只能修改本人承担的课程任务", FORBIDDEN);
|
||||
}
|
||||
throw new ServiceException("当前角色无任务书拟制权限", FORBIDDEN);
|
||||
}
|
||||
|
||||
/** 合班/拆班属教研室级操作:仅机关/管理员与本室教研室账号可做 */
|
||||
private void assertOfficeEditable(List<XYDRWB> rows) {
|
||||
if (roleHelper.isDepartmentPersonnel()) {
|
||||
return;
|
||||
}
|
||||
if (!roleHelper.isResearchOffice()) {
|
||||
throw new ServiceException("合班/拆班仅教研室账号可执行", FORBIDDEN);
|
||||
}
|
||||
String office = roleHelper.requireResearchOfficeId();
|
||||
for (XYDRWB row : rows) {
|
||||
if (!office.equals(row.getJysdh())) {
|
||||
throw new ServiceException("只能合班/拆班本教研室的课程任务", FORBIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 阶段 5 反向门禁:拆班前确认行所在班次学期尚未发布到运行课表。
|
||||
*
|
||||
|
||||
+114
@@ -6,12 +6,18 @@ import com.roomroot.jwgl.dto.allocation.AllocationGroupRequest;
|
||||
import com.roomroot.jwgl.dto.allocation.AllocationSaveItem;
|
||||
import com.roomroot.jwgl.entity.JQB;
|
||||
import com.roomroot.jwgl.entity.KB;
|
||||
import com.roomroot.jwgl.entity.SSKC;
|
||||
import com.roomroot.jwgl.entity.SSKCB;
|
||||
import com.roomroot.jwgl.entity.SSKCXYD;
|
||||
import com.roomroot.jwgl.entity.XQXLB;
|
||||
import com.roomroot.jwgl.entity.XYDB;
|
||||
import com.roomroot.jwgl.entity.XYDNDXQJBXXB;
|
||||
import com.roomroot.jwgl.entity.XYDRWB;
|
||||
import com.roomroot.jwgl.mapper.JQBMapper;
|
||||
import com.roomroot.jwgl.mapper.KBMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCBMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCMapper;
|
||||
import com.roomroot.jwgl.mapper.SSKCXYDMapper;
|
||||
import com.roomroot.jwgl.mapper.SemesterCalendarMapper;
|
||||
import com.roomroot.jwgl.mapper.StudentTeamTaskMapper;
|
||||
import com.roomroot.jwgl.mapper.XYDBMapper;
|
||||
@@ -34,10 +40,12 @@ import java.time.LocalDateTime;
|
||||
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 static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST;
|
||||
import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND;
|
||||
@@ -89,6 +97,18 @@ public class TeachingAllocationServiceImpl implements TeachingAllocationService
|
||||
@Resource
|
||||
private TeachingTaskWriteGuard teachingTaskWriteGuard;
|
||||
|
||||
@Resource
|
||||
private SSKCXYDMapper sskcxydMapper;
|
||||
|
||||
@Resource
|
||||
private SSKCMapper sskcMapper;
|
||||
|
||||
@Resource
|
||||
private SSKCBMapper sskcbMapper;
|
||||
|
||||
/** 已排学时口径:每课次 2 学时 + 节次调节(与排课窗 scheduledHours 一致) */
|
||||
private static final int HOURS_PER_LESSON = 2;
|
||||
|
||||
// ==================== 视图 ====================
|
||||
|
||||
@Override
|
||||
@@ -115,10 +135,68 @@ public class TeachingAllocationServiceImpl implements TeachingAllocationService
|
||||
voMap.put(task.getBh(), toVO(task));
|
||||
}
|
||||
allocate(weeks, tasks, voMap);
|
||||
fillScheduledHours(semester, voMap);
|
||||
aggregateWeeks(weeks, voMap);
|
||||
vo.setTasks(new ArrayList<>(voMap.values()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 已排/剩余学时:按 任务.kbh → 运行课程(科目编号) → 课次 折算,
|
||||
* 课次只取与本班本学期关联(SSKCXYD xydbh+学期代号)且未删除的课程。
|
||||
*/
|
||||
private void fillScheduledHours(XYDNDXQJBXXB semester, Map<String, AllocationTaskVO> voMap) {
|
||||
Integer ndCode = SemesterCodeUtil.resolve(semester.getNd(), semester.getXqdc());
|
||||
List<SSKCXYD> links = sskcxydMapper.selectList(new LambdaQueryWrapper<SSKCXYD>()
|
||||
.eq(SSKCXYD::getXydbh, semester.getXydbh())
|
||||
.eq(SSKCXYD::getNd, ndCode));
|
||||
List<String> courseIds = links.stream().map(SSKCXYD::getSskcbh).distinct().toList();
|
||||
Map<String, Integer> hoursByKbh = new HashMap<>();
|
||||
if (!courseIds.isEmpty()) {
|
||||
Map<String, String> kbhByCourse = new HashMap<>();
|
||||
for (SSKC course : sskcMapper.selectByBhs(courseIds)) {
|
||||
if (course.getKmbh() != null) {
|
||||
kbhByCourse.put(course.getBh(), course.getKmbh());
|
||||
}
|
||||
}
|
||||
if (!kbhByCourse.isEmpty()) {
|
||||
for (SSKCB lesson : sskcbMapper.selectList(new LambdaQueryWrapper<SSKCB>()
|
||||
.in(SSKCB::getSskcbh, kbhByCourse.keySet())
|
||||
.eq(SSKCB::getSczt, 0))) {
|
||||
String kbh = kbhByCourse.get(lesson.getSskcbh());
|
||||
if (kbh == null) continue;
|
||||
int hours = HOURS_PER_LESSON + (lesson.getJcdj() == null ? 0 : lesson.getJcdj());
|
||||
hoursByKbh.merge(kbh, hours, Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (AllocationTaskVO vo : voMap.values()) {
|
||||
int scheduled = hoursByKbh.getOrDefault(vo.getKbh(), 0);
|
||||
vo.setScheduledHours(scheduled);
|
||||
int total = vo.getXs() == null ? 0 : vo.getXs();
|
||||
vo.setRemainingHours(Math.max(0, total - scheduled));
|
||||
}
|
||||
}
|
||||
|
||||
/** 周轴已铺/剩余:全部任务分布按周合计,剩余 = 可排正课 − 已铺(不为负) */
|
||||
private void aggregateWeeks(List<AllocationWeekVO> weeks, Map<String, AllocationTaskVO> voMap) {
|
||||
int[] used = new int[weeks.size()];
|
||||
for (AllocationTaskVO vo : voMap.values()) {
|
||||
for (AllocationTaskVO.WeekHours wh : vo.getDistribution()) {
|
||||
if (wh.getWeek() != null && wh.getWeek() >= 1 && wh.getWeek() <= weeks.size()
|
||||
&& wh.getHours() != null) {
|
||||
used[wh.getWeek() - 1] += wh.getHours();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < weeks.size(); i++) {
|
||||
AllocationWeekVO w = weeks.get(i);
|
||||
w.setScheduledHours(used[i]);
|
||||
int avail = w.getAvailableHours() == null ? 0 : w.getAvailableHours();
|
||||
w.setRemainingHours(Math.max(0, avail - used[i]));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFrozen(String xydxqbh) {
|
||||
try {
|
||||
teachingTaskWriteGuard.assertCourseTaskWritable(xydxqbh);
|
||||
@@ -485,6 +563,42 @@ public class TeachingAllocationServiceImpl implements TeachingAllocationService
|
||||
return tasks.stream().map(XYDRWB::getBh).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动排布:复用铺学时计算结果,把各任务的计算起始周回填到 pdqsz。
|
||||
* onlyMissing(辅助排布)只补未设起始周的任务,不覆盖人工排布。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public int autoArrange(String xydxqbh, List<String> bhList, boolean onlyMissing) {
|
||||
teachingTaskWriteGuard.assertCourseTaskWritable(xydxqbh);
|
||||
AllocationViewVO view = view(xydxqbh);
|
||||
Set<String> filter = bhList == null || bhList.isEmpty() ? null : new HashSet<>(bhList);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int count = 0;
|
||||
for (AllocationTaskVO vo : view.getTasks()) {
|
||||
if (filter != null && !filter.contains(vo.getBh())) {
|
||||
continue;
|
||||
}
|
||||
if (vo.getStartWeek() == null) {
|
||||
continue;
|
||||
}
|
||||
if (onlyMissing && vo.getPdqsz() != null && vo.getPdqsz() > 0) {
|
||||
continue;
|
||||
}
|
||||
XYDRWB task = xydrwbMapper.selectById(vo.getBh());
|
||||
if (task == null || Integer.valueOf(1).equals(task.getDelFlag())) {
|
||||
continue;
|
||||
}
|
||||
if (!Objects.equals(task.getPdqsz(), vo.getStartWeek())) {
|
||||
task.setPdqsz(vo.getStartWeek());
|
||||
task.setBdsj(now);
|
||||
xydrwbMapper.updateById(task);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> groups(String kbh) {
|
||||
if (kbh == null || kbh.isEmpty()) {
|
||||
|
||||
+17
-2
@@ -109,7 +109,18 @@ public class TeachingTaskServiceImpl implements TeachingTaskService {
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public int batchPublish(String bh) {
|
||||
public int batchPublish(List<String> bhList) {
|
||||
if (bhList == null || bhList.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (String bh : bhList) {
|
||||
count += publishOne(bh);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private int publishOne(String bh) {
|
||||
if (bh == null || bh.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
@@ -119,7 +130,11 @@ public class TeachingTaskServiceImpl implements TeachingTaskService {
|
||||
return 0;
|
||||
}
|
||||
if (TeachingTaskStatus.isEnded(jxrw.getZt())) {
|
||||
throw new ServiceException("教学任务已结束,不能再次发布", CONFLICT);
|
||||
throw new ServiceException("教学任务「" + jxrw.getRwmc() + "」已结束,不能再次发布", CONFLICT);
|
||||
}
|
||||
// 发布前校验:任务书须已生成课程任务行
|
||||
if (teachingTaskMapper.selectTaskBookRows(bh).isEmpty()) {
|
||||
throw new ServiceException("教学任务「" + jxrw.getRwmc() + "」尚未生成任务书课程任务,不能发布", BAD_REQUEST);
|
||||
}
|
||||
|
||||
// 更新教学任务状态为"发布"
|
||||
|
||||
+44
@@ -33,6 +33,7 @@ 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.FreePeriodsVO;
|
||||
import com.roomroot.jwgl.vo.kcb.TimetableGridVO;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
@@ -56,6 +57,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -349,6 +351,48 @@ public class TimetableGridServiceImpl implements TimetableGridService {
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FreePeriodsVO freePeriods(TimetableGridQuery query) {
|
||||
TimetableGridVO grid = grid(query);
|
||||
FreePeriodsVO vo = new FreePeriodsVO();
|
||||
vo.setDim(grid.getDim());
|
||||
vo.setTargetName(grid.getTargetName());
|
||||
vo.setStartDate(grid.getStartDate());
|
||||
vo.setEndDate(grid.getEndDate());
|
||||
vo.setPeriods(grid.getPeriods());
|
||||
List<Integer> allJcs = grid.getPeriods().stream()
|
||||
.map(TimetableGridVO.Period::getJc).sorted().collect(Collectors.toList());
|
||||
for (TimetableGridVO.Week week : grid.getWeeks()) {
|
||||
for (TimetableGridVO.Day day : week.getDays()) {
|
||||
FreePeriodsVO.DayFree df = new FreePeriodsVO.DayFree();
|
||||
df.setDate(day.getDate());
|
||||
df.setWeekday(day.getWeekday());
|
||||
df.setBlocked(day.getBlocked());
|
||||
Set<Integer> busy = new TreeSet<>();
|
||||
if (!Boolean.TRUE.equals(day.getBlocked())) {
|
||||
for (Map.Entry<String, List<TimetableGridVO.Cell>> e : day.getCells().entrySet()) {
|
||||
if (e.getValue() != null && !e.getValue().isEmpty()) {
|
||||
try {
|
||||
busy.add(Integer.parseInt(e.getKey()));
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
df.setBusy(new ArrayList<>(busy));
|
||||
if (!Boolean.TRUE.equals(day.getBlocked())) {
|
||||
for (Integer jc : allJcs) {
|
||||
if (!busy.contains(jc)) {
|
||||
df.getFree().add(jc);
|
||||
}
|
||||
}
|
||||
}
|
||||
vo.getDays().add(df);
|
||||
}
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportGrid(TimetableGridQuery query) {
|
||||
TimetableGridVO vo = grid(query);
|
||||
|
||||
+6
@@ -49,6 +49,12 @@ public class AllocationTaskVO {
|
||||
/** 教研室代号 */
|
||||
private String jysdh;
|
||||
|
||||
/** 已排学时(实施课程表已排课次折算,每课次 2 学时 + 节次调节) */
|
||||
private Integer scheduledHours;
|
||||
|
||||
/** 剩余学时(学时 − 已排学时,不为负) */
|
||||
private Integer remainingHours;
|
||||
|
||||
/** 计算起始周(铺学时结果) */
|
||||
private Integer startWeek;
|
||||
|
||||
|
||||
+6
@@ -26,6 +26,12 @@ public class AllocationWeekVO {
|
||||
/** 该周可排正课学时 */
|
||||
private Integer availableHours;
|
||||
|
||||
/** 该周已铺学时(全部课程任务分布合计,含超容量部分) */
|
||||
private Integer scheduledHours;
|
||||
|
||||
/** 该周剩余可排学时(可排正课 − 已铺,不为负) */
|
||||
private Integer remainingHours;
|
||||
|
||||
/** 容量来源:class-班历 / school-校历 / default-默认 */
|
||||
private String source;
|
||||
}
|
||||
|
||||
+10
@@ -21,6 +21,16 @@ public class ClassCalendarResyncVO {
|
||||
*/
|
||||
private List<ClassCalendarEventDTO> items;
|
||||
|
||||
/**
|
||||
* 与校历不一致、force 时将被覆盖的时间格数量
|
||||
*/
|
||||
private int updateCount;
|
||||
|
||||
/**
|
||||
* 将被覆盖的时间格明细(按校历口径)
|
||||
*/
|
||||
private List<ClassCalendarEventDTO> updateItems;
|
||||
|
||||
/**
|
||||
* 是否已实际写入(false = 仅预览)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.roomroot.jwgl.vo.kcb;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 有课/无课时段查询结果。
|
||||
* <p>按日期展开:每天给出 busy(有课节次)与 free(无课节次),
|
||||
* 不可排日(blocked)所有节次既不算 busy 也不算 free。</p>
|
||||
*/
|
||||
@Data
|
||||
public class FreePeriodsVO {
|
||||
|
||||
/** 维度(semester/team/teacher/room) */
|
||||
private String dim;
|
||||
|
||||
/** 维度目标显示名 */
|
||||
private String targetName;
|
||||
|
||||
private String startDate;
|
||||
private String endDate;
|
||||
|
||||
/** 全部节次行定义 */
|
||||
private List<TimetableGridVO.Period> periods = new ArrayList<>();
|
||||
|
||||
/** 每日占用情况 */
|
||||
private List<DayFree> days = new ArrayList<>();
|
||||
|
||||
@Data
|
||||
public static class DayFree {
|
||||
private String date;
|
||||
/** 1=周一 … 7=周日 */
|
||||
private Integer weekday;
|
||||
/** 历表标记不可排课 */
|
||||
private Boolean blocked = false;
|
||||
/** 有课节次序号(升序) */
|
||||
private List<Integer> busy = new ArrayList<>();
|
||||
/** 无课节次序号(升序) */
|
||||
private List<Integer> free = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package com.roomroot.jwgl.vo.mybusiness;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 「我的课程任务」行(学员队任务表视角,附教学任务归属)。
|
||||
*/
|
||||
@Data
|
||||
public class MyCourseTaskVO {
|
||||
|
||||
/** 课程任务编号(学员队任务表.编号) */
|
||||
private String bh;
|
||||
|
||||
/** 学员队学期编号 */
|
||||
private String xydxqbh;
|
||||
|
||||
/** 教学任务编号(经班次学期关联) */
|
||||
private String jxrwbh;
|
||||
|
||||
/** 教学任务名称 */
|
||||
private String rwmc;
|
||||
|
||||
/** 年度 */
|
||||
private Integer nd;
|
||||
|
||||
/** 学期第次 */
|
||||
private Integer xqdc;
|
||||
|
||||
/** 课编号 */
|
||||
private String kbh;
|
||||
|
||||
/** 课程名称(简称优先,缺省取课表课名称) */
|
||||
private String kcmc;
|
||||
|
||||
/** 课类型 */
|
||||
private String klx;
|
||||
|
||||
/** 学时 */
|
||||
private Integer xs;
|
||||
|
||||
/** 周课时 */
|
||||
private Integer zks;
|
||||
|
||||
/** 学员队编号 */
|
||||
private String xydbh;
|
||||
|
||||
/** 学员队名称 */
|
||||
private String xydmc;
|
||||
|
||||
/** 责任教员编号 */
|
||||
private String jybh;
|
||||
|
||||
/** 责任教员姓名 */
|
||||
private String jyxm;
|
||||
|
||||
/** 责任教员是否教研室主任 */
|
||||
private Integer zr;
|
||||
|
||||
/** 教研室计划教员编号 */
|
||||
private String jysjhjybh;
|
||||
|
||||
/** 教研室计划教员姓名 */
|
||||
private String jysjhjyxm;
|
||||
|
||||
/** 教研室代号 */
|
||||
private String jysdh;
|
||||
|
||||
/** 教研室名称 */
|
||||
private String jysmc;
|
||||
|
||||
/** 教室编号 */
|
||||
private String jsbh;
|
||||
|
||||
/** 教室名称 */
|
||||
private String jsmc;
|
||||
|
||||
/** 课次序号 */
|
||||
private Integer kcxh;
|
||||
|
||||
/** 合班分组号 */
|
||||
private Integer bz2;
|
||||
}
|
||||
@@ -47,6 +47,9 @@ public class TaskBookRowVO {
|
||||
/** 责任教员姓名 */
|
||||
private String jyxm;
|
||||
|
||||
/** 责任教员是否教研室主任(教员属性.主任,1=主任,前端标 *) */
|
||||
private Integer zr;
|
||||
|
||||
/** 场地(教室编号) */
|
||||
private String jsbh;
|
||||
|
||||
|
||||
@@ -57,3 +57,14 @@ export function allocationExport(xydxqbhList) {
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 自动排布:把铺学时建议的计算起始周回填到 pdqsz;
|
||||
// onlyMissing=true(辅助排布)只补未设起始周的任务;bhList 限定所选任务
|
||||
export function allocationAutoArrange(xydxqbh, onlyMissing, bhList) {
|
||||
return request({
|
||||
url: '/teachingAllocation/autoArrange',
|
||||
method: 'post',
|
||||
params: { xydxqbh, onlyMissing: !!onlyMissing },
|
||||
data: bhList && bhList.length ? bhList : null
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 「我的教学」角色视图接口(阶段 C,MyBusinessController /my)
|
||||
// 教员/教研室/学员身份全部由服务端按登录账号解析,不传他人编号
|
||||
|
||||
// ==================== 教员 ====================
|
||||
|
||||
/** 本教员课程任务 GET /my/teacher/courses?nd=&xqdc= */
|
||||
export function myTeacherCourses(params) {
|
||||
return request({
|
||||
url: '/my/teacher/courses',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/** 本教员实施计划 GET /my/teacher/lessons?start=&end=&nd= */
|
||||
export function myTeacherLessons(params) {
|
||||
return request({
|
||||
url: '/my/teacher/lessons',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/** 本教员课表网格 GET /my/teacher/grid?mbbh=&start=&end=&nd= */
|
||||
export function myTeacherGrid(params) {
|
||||
return request({
|
||||
url: '/my/teacher/grid',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 教研室 ====================
|
||||
|
||||
/** 今日本室计划 GET /my/office/today?rq=&offset= */
|
||||
export function myOfficeToday(params) {
|
||||
return request({
|
||||
url: '/my/office/today',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/** 本室课程 GET /my/office/courses?nd=&xqdc= */
|
||||
export function myOfficeCourses(params) {
|
||||
return request({
|
||||
url: '/my/office/courses',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/** 本室教员清单 GET /my/office/teachers */
|
||||
export function myOfficeTeachers() {
|
||||
return request({
|
||||
url: '/my/office/teachers',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 学员 ====================
|
||||
|
||||
/** 本学员队课程列表 GET /my/student/courses?nd=&xqdc= */
|
||||
export function myStudentCourses(params) {
|
||||
return request({
|
||||
url: '/my/student/courses',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/** 本学员队课次 GET /my/student/lessons?start=&end= */
|
||||
export function myStudentLessons(params) {
|
||||
return request({
|
||||
url: '/my/student/lessons',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/** 本学员队课表网格 GET /my/student/grid?mbbh=&start=&end=&nd= */
|
||||
export function myStudentGrid(params) {
|
||||
return request({
|
||||
url: '/my/student/grid',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/** 本学员队干部清单 GET /my/team/cadres */
|
||||
export function myTeamCadres() {
|
||||
return request({
|
||||
url: '/my/team/cadres',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -23,6 +23,25 @@ export function arrangeLessons(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 拖拽移动课次:{ sskcbh, from: {rq,jc}, to: {rq,jc} };返回 {moved, warning?}
|
||||
export function moveLesson(data) {
|
||||
return request({
|
||||
url: '/schedulingWindow/move',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 导出排课窗视图 Excel(每周一个 sheet)
|
||||
export function exportSchedulingView(xydxqbh) {
|
||||
return request({
|
||||
url: '/schedulingWindow/export',
|
||||
method: 'get',
|
||||
params: { xydxqbh },
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
// 删除所选节次(仅删除该课程在这些格上的课次;已提交实施计划的课次会被拒绝)
|
||||
export function deleteLessonCells(data) {
|
||||
return request({
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 读取教务系统参数(DBVersion 单行;显示开关 xs78J/xsws/xsyj 等)
|
||||
export function getSystemSettings() {
|
||||
return request({
|
||||
url: '/systemSettings/get',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 更新系统参数(后端只写非空字段,可局部提交)
|
||||
export function updateSystemSettings(data) {
|
||||
return request({
|
||||
url: '/systemSettings/update',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 节次段字典:节次时间表的双节次行(jcsy 节次索引、sd 时段),空表时前端回退默认六段
|
||||
export function getPeriodSlots() {
|
||||
return request({
|
||||
url: '/systemSettings/periodSlots',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -5,12 +5,12 @@ import request from '@/utils/request'
|
||||
* 接口依据:/taskBookFill/*(前端 baseURL 为 /api,此处不重复 /api 前缀)
|
||||
*/
|
||||
|
||||
/** 填报列表 GET /taskBookFill/list?jxrwbh= */
|
||||
export function taskBookList(jxrwbh) {
|
||||
/** 填报列表 GET /taskBookFill/list?jxrwbh=&jybh= */
|
||||
export function taskBookList(jxrwbh, jybh) {
|
||||
return request({
|
||||
url: '/taskBookFill/list',
|
||||
method: 'get',
|
||||
params: { jxrwbh }
|
||||
params: { jxrwbh, jybh }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export function taskBookSetRoom(data) {
|
||||
})
|
||||
}
|
||||
|
||||
/** 填报字段 POST /taskBookFill/fill body: { bh, jysjhjybh, jsbh, jysjhbz } */
|
||||
/** 填报字段 POST /taskBookFill/fill body: { bh, jysjhjybh, jsbh, jysjhbz, kcxh } */
|
||||
export function taskBookFill(data) {
|
||||
return request({
|
||||
url: '/taskBookFill/fill',
|
||||
@@ -67,3 +67,48 @@ export function taskBookFill(data) {
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 批量指定责任教员 POST /taskBookFill/batchSetTeacher body: { bhList, mode, jybh } */
|
||||
export function taskBookBatchSetTeacher(data) {
|
||||
return request({
|
||||
url: '/taskBookFill/batchSetTeacher',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 批量指定场地 POST /taskBookFill/batchSetRoom body: { bhList, jsbh, useSpecial } */
|
||||
export function taskBookBatchSetRoom(data) {
|
||||
return request({
|
||||
url: '/taskBookFill/batchSetRoom',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 批量填报 POST /taskBookFill/batchFill body: { bhList, jysjhjybh?, jsbh?, useSpecial?, jysjhbz?, kcxh? } */
|
||||
export function taskBookBatchFill(data) {
|
||||
return request({
|
||||
url: '/taskBookFill/batchFill',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 行级删除 POST /taskBookFill/deleteRows body: { bhList } */
|
||||
export function taskBookDeleteRows(bhList) {
|
||||
return request({
|
||||
url: '/taskBookFill/deleteRows',
|
||||
method: 'post',
|
||||
data: { bhList }
|
||||
})
|
||||
}
|
||||
|
||||
/** 教研室跨任务汇总 GET /taskBookFill/officeSummary?jysdh= */
|
||||
export function taskBookOfficeSummary(jysdh) {
|
||||
return request({
|
||||
url: '/taskBookFill/officeSummary',
|
||||
method: 'get',
|
||||
params: { jysdh }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -76,16 +76,28 @@ export function deleteTeachingTask(bh) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 教学任务发布(需要先填写教研室任务书)
|
||||
* POST /teachingTask/batchPublish?bh=
|
||||
* @param bh 教学任务编号
|
||||
* 返回:更新数量
|
||||
* 批量删除教学任务(级联删除教研室任务书)
|
||||
* POST /teachingTask/batchDelete body: [bh, ...]
|
||||
*/
|
||||
export function publishTeachingTask(bh) {
|
||||
export function batchDeleteTeachingTask(bhList) {
|
||||
return request({
|
||||
url: '/teachingTask/batchDelete',
|
||||
method: 'post',
|
||||
data: bhList
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 教学任务批量发布(需要先填写教研室任务书)
|
||||
* POST /teachingTask/batchPublish body: [bh, ...]
|
||||
* @param bhList 教学任务编号数组(单个任务传 [bh])
|
||||
* 返回:成功发布的任务数
|
||||
*/
|
||||
export function publishTeachingTask(bhList) {
|
||||
return request({
|
||||
url: '/teachingTask/batchPublish',
|
||||
method: 'post',
|
||||
params: { bh }
|
||||
data: bhList
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -114,3 +114,29 @@ export function exportTimetableGrid(params) {
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 有课/无课时段查询
|
||||
* GET /kcb/freeSlots?dim=&id=&start=&end=
|
||||
* 返回 FreePeriodsVO:periods / days[].busy / days[].free / days[].blocked
|
||||
*/
|
||||
export function getFreeSlots(params) {
|
||||
return request({
|
||||
url: '/kcb/freeSlots',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 课次综合查询
|
||||
* GET /kcb/lessons?nd=&xqdc=&xydbh=&xydmc=&jybh=&jsbh=&jysdh=&xybh=&start=&end=
|
||||
* 返回 List<DailyWeeklyTimetableVO>
|
||||
*/
|
||||
export function listLessons(params) {
|
||||
return request({
|
||||
url: '/kcb/lessons',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,12 +25,13 @@ export function batchSaveClassCalendarEvent(xydxqbh, cells) {
|
||||
})
|
||||
}
|
||||
|
||||
// 重新同步校历:confirm=false 返回缺失格预览,confirm=true 实际补齐
|
||||
export function resyncClassCalendar(xydxqbh, confirm) {
|
||||
// 重新同步校历:confirm=false 返回差异预览,confirm=true 实际写入;
|
||||
// force=true 时额外覆盖与校历不一致的已有格
|
||||
export function resyncClassCalendar(xydxqbh, confirm, force) {
|
||||
return request({
|
||||
url: '/class-calendar/resync',
|
||||
method: 'post',
|
||||
params: { xydxqbh, confirm }
|
||||
params: { xydxqbh, confirm, force: !!force }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-tabs v-model="tab" type="border-card">
|
||||
<!-- ==================== 我的课表 ==================== -->
|
||||
<el-tab-pane label="我的课表" name="grid">
|
||||
<el-form inline size="small" style="margin-bottom: 8px">
|
||||
<el-form-item label="日期范围">
|
||||
<el-date-picker
|
||||
v-model="gridRange"
|
||||
type="daterange"
|
||||
value-format="yyyy-MM-dd"
|
||||
range-separator="~"
|
||||
start-placeholder="开始"
|
||||
end-placeholder="结束"
|
||||
style="width: 260px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="loadGrid">生成</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<timetable-grid-view :grid="grid" :loading="gridLoading" :show-teacher="true" :show-room="true" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ==================== 我的课程 ==================== -->
|
||||
<el-tab-pane label="我的课程" name="courses">
|
||||
<el-form inline size="small" style="margin-bottom: 8px">
|
||||
<el-form-item label="年度">
|
||||
<el-input-number v-model="courseQuery.nd" :min="2000" :max="2100" style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学期第次">
|
||||
<el-input-number v-model="courseQuery.xqdc" :min="1" :max="4" style="width: 100px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="loadCourses">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="courseLoading" :data="courseRows" border stripe size="small" max-height="560">
|
||||
<el-table-column prop="kcmcksxs" label="课程名称/课时系数" min-width="180" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zrjykc" label="责任教员/课次" min-width="140" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jhxs" label="计划学时" width="80" align="center" />
|
||||
<el-table-column prop="yxxs" label="运行学时" width="80" align="center" />
|
||||
<el-table-column prop="khlxfs" label="考核类型/方式" width="120" align="center" />
|
||||
<el-table-column prop="ssjy" label="实施教员" width="110" align="center" />
|
||||
<el-table-column prop="rscd" label="人数/场地" min-width="140" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ==================== 日实施计划 ==================== -->
|
||||
<el-tab-pane label="日实施计划" name="lessons">
|
||||
<el-form inline size="small" style="margin-bottom: 8px">
|
||||
<el-form-item label="日期范围">
|
||||
<el-date-picker
|
||||
v-model="lessonRange"
|
||||
type="daterange"
|
||||
value-format="yyyy-MM-dd"
|
||||
range-separator="~"
|
||||
start-placeholder="开始"
|
||||
end-placeholder="结束"
|
||||
style="width: 260px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="loadLessons">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="lessonLoading" :data="lessonRows" border stripe size="small" max-height="560">
|
||||
<el-table-column prop="sksj" label="上课时间" width="170" align="center" />
|
||||
<el-table-column prop="kc" label="课程" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jy" label="教员" width="120" align="center" />
|
||||
<el-table-column prop="cd" label="场地" width="120" align="center" />
|
||||
<el-table-column prop="jxnrxff" label="教学内容 / 方法" min-width="160" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="bz" label="备注" min-width="100" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ==================== 本队干部 ==================== -->
|
||||
<el-tab-pane label="本队干部" name="cadres">
|
||||
<el-table v-loading="cadreLoading" :data="cadreRows" border stripe size="small" max-height="560">
|
||||
<el-table-column prop="xh" label="学号" width="140" align="center" />
|
||||
<el-table-column prop="xm" label="姓名" width="140" align="center" />
|
||||
<el-table-column prop="ggrz" label="骨干任职" min-width="160" align="center" />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { myStudentCourses, myStudentLessons, myStudentGrid, myTeamCadres } from '@/api/teachBusiness/my'
|
||||
import TimetableGridView from '@/components/TimetableGridView'
|
||||
|
||||
export default {
|
||||
name: 'MyClass',
|
||||
components: { TimetableGridView },
|
||||
data() {
|
||||
return {
|
||||
tab: 'grid',
|
||||
gridRange: null,
|
||||
grid: null,
|
||||
gridLoading: false,
|
||||
courseQuery: { nd: null, xqdc: null },
|
||||
courseRows: [],
|
||||
courseLoading: false,
|
||||
lessonRange: null,
|
||||
lessonRows: [],
|
||||
lessonLoading: false,
|
||||
cadreRows: [],
|
||||
cadreLoading: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadGrid()
|
||||
this.loadCourses()
|
||||
this.loadCadres()
|
||||
},
|
||||
methods: {
|
||||
loadGrid() {
|
||||
this.gridLoading = true
|
||||
const params = {}
|
||||
if (this.gridRange && this.gridRange.length === 2) {
|
||||
params.start = this.gridRange[0]
|
||||
params.end = this.gridRange[1]
|
||||
}
|
||||
myStudentGrid(params)
|
||||
.then(res => { this.grid = res.data })
|
||||
.finally(() => { this.gridLoading = false })
|
||||
},
|
||||
loadCourses() {
|
||||
this.courseLoading = true
|
||||
myStudentCourses(this.courseQuery)
|
||||
.then(res => { this.courseRows = res.data || [] })
|
||||
.finally(() => { this.courseLoading = false })
|
||||
},
|
||||
loadLessons() {
|
||||
this.lessonLoading = true
|
||||
const params = {}
|
||||
if (this.lessonRange && this.lessonRange.length === 2) {
|
||||
params.start = this.lessonRange[0]
|
||||
params.end = this.lessonRange[1]
|
||||
}
|
||||
myStudentLessons(params)
|
||||
.then(res => { this.lessonRows = res.data || [] })
|
||||
.finally(() => { this.lessonLoading = false })
|
||||
},
|
||||
loadCadres() {
|
||||
this.cadreLoading = true
|
||||
myTeamCadres()
|
||||
.then(res => { this.cadreRows = res.data || [] })
|
||||
.finally(() => { this.cadreLoading = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -20,6 +20,7 @@
|
||||
<el-input v-model="toolbar.eventName" size="small" class="sce-event-input" :placeholder="mode === 'class' ? '请输入班历事件名称' : (mode === 'venue' ? '请输入场地事件名称' : '请输入校历事件名称')" />
|
||||
<el-checkbox v-if="mode !== 'venue'" v-model="toolbar.bold" class="sce-cb">加粗显示</el-checkbox>
|
||||
<el-checkbox v-model="toolbar.schedulable" class="sce-cb">可排课</el-checkbox>
|
||||
<el-checkbox v-if="mode !== 'venue'" v-model="toolbar.autoSchedule" class="sce-cb">自动排课</el-checkbox>
|
||||
<el-checkbox v-if="mode !== 'venue'" v-model="toolbar.mainCourse" class="sce-cb">正课</el-checkbox>
|
||||
<el-checkbox v-if="mode !== 'venue'" v-model="toolbar.remarkShow" class="sce-cb">备注显示</el-checkbox>
|
||||
<span class="sce-label">备注:</span>
|
||||
@@ -148,25 +149,47 @@ import {
|
||||
listVenueLessons,
|
||||
listVenueDesignatedTeams
|
||||
} from '@/api/teachBusiness/venueCalendar'
|
||||
import { getSystemSettings, updateSystemSettings, getPeriodSlots } from '@/api/teachBusiness/systemSettings'
|
||||
|
||||
// 节次定义:12/34/56 常显,78/晚上/夜间由开关控制可见
|
||||
const SLOT_DEFS = [
|
||||
{ key: '12', label: '1-2', ctrl: null },
|
||||
{ key: '34', label: '3-4', ctrl: null },
|
||||
{ key: '56', label: '5-6', ctrl: null },
|
||||
{ key: '78', label: '7-8', ctrl: 'show78' },
|
||||
{ key: 'night', label: '晚上', ctrl: 'showNight' },
|
||||
{ key: 'late', label: '夜间', ctrl: 'showLateNight' }
|
||||
// 节次定义回退值:节次时间表为空时使用,12/34/56 常显,78/晚上/夜间由开关控制可见
|
||||
const DEFAULT_SLOT_DEFS = [
|
||||
{ key: '12', label: '1-2', courseClass: '1-2', ctrl: null },
|
||||
{ key: '34', label: '3-4', courseClass: '3-4', ctrl: null },
|
||||
{ key: '56', label: '5-6', courseClass: '5-6', ctrl: null },
|
||||
{ key: '78', label: '7-8', courseClass: '7-8', ctrl: 'show78' },
|
||||
{ key: 'night', label: '晚上', courseClass: '9-10', ctrl: 'showNight' },
|
||||
{ key: 'late', label: '夜间', courseClass: '11-12', ctrl: 'showLateNight' }
|
||||
]
|
||||
|
||||
// 前端节次 key -> 后端 xqxlb.courseClass 节次范围(如 12 节 -> "1-2"、夜间 -> "11-12")
|
||||
const SLOT_COURSE_MAP = {
|
||||
'12': '1-2',
|
||||
'34': '3-4',
|
||||
'56': '5-6',
|
||||
'78': '7-8',
|
||||
night: '9-10',
|
||||
late: '11-12'
|
||||
const SLOT_COURSE_MAP = {}
|
||||
DEFAULT_SLOT_DEFS.forEach(s => { SLOT_COURSE_MAP[s.key] = s.courseClass })
|
||||
|
||||
// 节次范围解析:"1-2"/"7~8" -> [1,2];单数字 -> [n,n];非数字返回 null
|
||||
function parseSlotRange(label) {
|
||||
if (label == null) return null
|
||||
const s = String(label).trim()
|
||||
const m = s.match(/(\d+)\s*[-~—–]\s*(\d+)/)
|
||||
if (m) return [+m[1], +m[2]]
|
||||
if (/^\d+$/.test(s)) return [+s, +s]
|
||||
return null
|
||||
}
|
||||
|
||||
// 节次段受哪个显示开关控制:前 6 节常显,7-8 -> show78,9-10 -> showNight,11+ -> showLateNight;
|
||||
// 非数字标签按名称/时段推断(晚->晚上,夜->夜间)
|
||||
function ctrlOfSlot(label, sd) {
|
||||
const range = parseSlotRange(label)
|
||||
if (range) {
|
||||
const first = range[0]
|
||||
if (first <= 6) return null
|
||||
if (first <= 8) return 'show78'
|
||||
if (first <= 10) return 'showNight'
|
||||
return 'showLateNight'
|
||||
}
|
||||
const s = String(label || '') + String(sd || '')
|
||||
if (s.indexOf('夜') >= 0) return 'showLateNight'
|
||||
if (s.indexOf('晚') >= 0) return 'showNight'
|
||||
return null
|
||||
}
|
||||
|
||||
const WEEK_DAYS = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
@@ -215,15 +238,20 @@ export default {
|
||||
totalWeeks: 0,
|
||||
dateRangeText: '',
|
||||
weeks: [],
|
||||
// 显示开关(默认不勾选,每天仅显示 1-2/3-4/5-6 三个节次)
|
||||
// 显示开关(默认不勾选,挂载时从系统参数 DBVersion 恢复)
|
||||
show78: false,
|
||||
showNight: false,
|
||||
showLateNight: false,
|
||||
// 系统参数是否已加载(加载完成前的开关赋值不回写)
|
||||
settingsReady: false,
|
||||
// 节次列定义:由节次时间表驱动,空表时回退默认六段
|
||||
slotDefs: DEFAULT_SLOT_DEFS.slice(),
|
||||
// 工具栏表单
|
||||
toolbar: {
|
||||
eventName: '',
|
||||
bold: false,
|
||||
schedulable: false,
|
||||
autoSchedule: false,
|
||||
mainCourse: false,
|
||||
remarkShow: false,
|
||||
remark: ''
|
||||
@@ -250,7 +278,6 @@ export default {
|
||||
// 指定本场地为专用教室的班次(顶部横幅)
|
||||
designatedTeams: [],
|
||||
weekDayOptions: WEEK_DAYS,
|
||||
slotOptions: SLOT_DEFS,
|
||||
ruleDialog: {
|
||||
visible: false,
|
||||
submitting: false,
|
||||
@@ -262,12 +289,16 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 规律生成弹窗的节次选项(同节次时间表口径)
|
||||
slotOptions() {
|
||||
return this.slotDefs
|
||||
},
|
||||
// 当前可见列(由开关过滤,默认每天三个节次)
|
||||
visibleColumns() {
|
||||
const cols = []
|
||||
let colIndex = 0
|
||||
WEEK_DAYS.forEach((dayLabel, dayIndex) => {
|
||||
SLOT_DEFS.forEach(slot => {
|
||||
this.slotDefs.forEach(slot => {
|
||||
if (slot.ctrl && !this[slot.ctrl]) return
|
||||
cols.push({ colIndex: colIndex, dayIndex: dayIndex, slotKey: slot.key, slotLabel: slot.label, dayLabel: dayLabel })
|
||||
colIndex++
|
||||
@@ -312,6 +343,10 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 显示开关变更即写回系统参数(防抖合并连续变更)
|
||||
show78() { this.persistDisplaySettings() },
|
||||
showNight() { this.persistDisplaySettings() },
|
||||
showLateNight() { this.persistDisplaySettings() },
|
||||
nd: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
@@ -371,10 +406,72 @@ export default {
|
||||
this.selectedKeys = []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDisplaySettings()
|
||||
this.loadPeriodSlots()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.removeGlobalListeners()
|
||||
if (this._settingsTimer) clearTimeout(this._settingsTimer)
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 系统参数 / 节次字典 ---------- */
|
||||
// 显示开关从 DBVersion 恢复,刷新不丢配置
|
||||
loadDisplaySettings() {
|
||||
getSystemSettings().then(res => {
|
||||
const d = (res && res.data) || {}
|
||||
const pick = (a, b) => (d[a] !== undefined ? d[a] : d[b])
|
||||
this.show78 = !!pick('xs78J', 'xs78j')
|
||||
this.showNight = !!pick('xsws', 'xsws')
|
||||
this.showLateNight = !!pick('xsyj', 'xsyj')
|
||||
this.$nextTick(() => { this.settingsReady = true })
|
||||
}).catch(() => {
|
||||
this.settingsReady = true
|
||||
})
|
||||
},
|
||||
// 开关变更回写 DBVersion(只提交三个字段,后端只写非空字段不影响其它参数)
|
||||
persistDisplaySettings() {
|
||||
if (!this.settingsReady) return
|
||||
if (this._settingsTimer) clearTimeout(this._settingsTimer)
|
||||
this._settingsTimer = setTimeout(() => {
|
||||
updateSystemSettings({
|
||||
xs78J: this.show78,
|
||||
xsws: this.showNight,
|
||||
xsyj: this.showLateNight
|
||||
}).catch(() => {})
|
||||
}, 400)
|
||||
},
|
||||
// 节次列由节次时间表驱动;空表或拉取失败保留默认六段
|
||||
loadPeriodSlots() {
|
||||
getPeriodSlots().then(res => {
|
||||
const rows = (res && res.data) || []
|
||||
const defs = rows
|
||||
.filter(r => r && r.jcsy)
|
||||
.map(r => ({
|
||||
key: String(r.jcsy),
|
||||
label: String(r.jcsy),
|
||||
courseClass: String(r.jcsy),
|
||||
ctrl: ctrlOfSlot(r.jcsy, r.sd)
|
||||
}))
|
||||
if (defs.length) this.slotDefs = defs
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 节次格 key -> 后端 courseClass 值(动态节次表优先)
|
||||
courseClassOfSlot(slotKey) {
|
||||
const s = this.slotDefs.find(x => x.key === slotKey)
|
||||
return s ? s.courseClass : (SLOT_COURSE_MAP[slotKey] || null)
|
||||
},
|
||||
// 单节次 -> 节次格 key(动态范围优先,回退固定 1-12 映射)
|
||||
jcToSlotKey(jc) {
|
||||
const n = Number(jc)
|
||||
if (!n) return null
|
||||
for (const s of this.slotDefs) {
|
||||
const r = parseSlotRange(s.courseClass)
|
||||
if (r && n >= r[0] && n <= r[1]) return s.key
|
||||
}
|
||||
const fallback = JC_SLOT_MAP[n]
|
||||
return this.slotDefs.some(x => x.key === fallback) ? fallback : null
|
||||
},
|
||||
/* ---------- 数据加载 ---------- */
|
||||
loadClassCalendar() {
|
||||
const start = (this.rangeStart || '').slice(0, 10)
|
||||
@@ -504,7 +601,7 @@ export default {
|
||||
const lessons = {}
|
||||
lsList.forEach(item => {
|
||||
const dateStr = String(item.rq || '').slice(0, 10)
|
||||
const slotKey = JC_SLOT_MAP[item.jc]
|
||||
const slotKey = this.jcToSlotKey(item.jc)
|
||||
if (!dateStr || !slotKey) return
|
||||
const key = dateStr + '#' + slotKey
|
||||
if (!lessons[key]) lessons[key] = []
|
||||
@@ -519,7 +616,7 @@ export default {
|
||||
const merged = {}
|
||||
list.forEach(row => {
|
||||
const dateStr = String(row.rq || '').slice(0, 10)
|
||||
const slotKey = JC_SLOT_MAP[row.jc]
|
||||
const slotKey = this.jcToSlotKey(row.jc)
|
||||
if (!dateStr || !slotKey) return
|
||||
const pos = this.locateDateSlot(dateStr, slotKey)
|
||||
if (!pos) return
|
||||
@@ -610,9 +707,11 @@ export default {
|
||||
return { ok, skipped }
|
||||
},
|
||||
periodsOfSlot(slotKey) {
|
||||
const seg = SLOT_COURSE_MAP[slotKey]
|
||||
if (!seg) return []
|
||||
return seg.split('-').map(Number).filter(n => !isNaN(n))
|
||||
const range = parseSlotRange(this.courseClassOfSlot(slotKey))
|
||||
if (!range) return []
|
||||
const list = []
|
||||
for (let n = range[0]; n <= range[1]; n++) list.push(n)
|
||||
return list
|
||||
},
|
||||
/* ---------- 规律生成 ---------- */
|
||||
openRuleDialog() {
|
||||
@@ -676,17 +775,18 @@ export default {
|
||||
if (slotKey === null) return null
|
||||
return { wIdx, dayIndex, slotKey }
|
||||
},
|
||||
// 后端 courseClass 节次范围 -> 前端节次 key
|
||||
// 后端 courseClass 节次范围 -> 前端节次 key(动态节次表 + 紧凑写法兼容)
|
||||
courseClassToSlotKey(courseClass) {
|
||||
if (!courseClass) return null
|
||||
const s = String(courseClass).trim()
|
||||
for (const key in SLOT_COURSE_MAP) {
|
||||
if (s === SLOT_COURSE_MAP[key]) return key
|
||||
const direct = this.slotDefs.find(x => x.courseClass === s)
|
||||
if (direct) return direct.key
|
||||
// 兼容 "910"/"1112"/"12" 等紧凑写法:按单节次归并到所属段
|
||||
if (/^\d+$/.test(s) && s.length > 1) {
|
||||
const first = this.jcToSlotKey(Number(s.slice(0, s.length - 1)))
|
||||
if (first) return first
|
||||
}
|
||||
// 兼容 "910"/"1112" 等紧凑写法
|
||||
if (s === '910') return 'night'
|
||||
if (s === '1112') return 'late'
|
||||
return null
|
||||
return this.jcToSlotKey(s)
|
||||
},
|
||||
// 获取日期所在周的周一(周一为一周起点)
|
||||
getMonday(d) {
|
||||
@@ -914,7 +1014,7 @@ export default {
|
||||
name: name,
|
||||
bold: this.toolbar.bold,
|
||||
schedulable: this.toolbar.schedulable,
|
||||
autoSchedule: !!(prev && prev.autoSchedule),
|
||||
autoSchedule: !!this.toolbar.autoSchedule,
|
||||
mainCourse: this.toolbar.mainCourse,
|
||||
remarkShow: this.toolbar.remarkShow,
|
||||
remark: this.toolbar.remark
|
||||
@@ -975,6 +1075,7 @@ export default {
|
||||
this.toolbar.eventName = target.name
|
||||
this.toolbar.bold = !!target.bold
|
||||
this.toolbar.schedulable = !!target.schedulable
|
||||
this.toolbar.autoSchedule = !!target.autoSchedule
|
||||
this.toolbar.mainCourse = !!target.mainCourse
|
||||
this.toolbar.remarkShow = !!target.remarkShow
|
||||
this.toolbar.remark = target.remark || ''
|
||||
@@ -997,6 +1098,7 @@ export default {
|
||||
this.toolbar.eventName = ev.name
|
||||
this.toolbar.bold = !!ev.bold
|
||||
this.toolbar.schedulable = !!ev.schedulable
|
||||
this.toolbar.autoSchedule = !!ev.autoSchedule
|
||||
this.toolbar.mainCourse = !!ev.mainCourse
|
||||
this.toolbar.remarkShow = !!ev.remarkShow
|
||||
this.toolbar.remark = ev.remark || ''
|
||||
@@ -1132,7 +1234,7 @@ export default {
|
||||
bzxs: !!ev.remarkShow,
|
||||
zdpk: !!ev.autoSchedule,
|
||||
zk: !!ev.mainCourse,
|
||||
courseClass: SLOT_COURSE_MAP[pos.slotKey] || null,
|
||||
courseClass: this.courseClassOfSlot(pos.slotKey),
|
||||
xydxqbh: this.mode === 'class' ? this.xydxqbh : undefined,
|
||||
xydbh: this.mode === 'class' ? this.xydbh : undefined
|
||||
}
|
||||
|
||||
@@ -106,8 +106,9 @@
|
||||
<el-radio-group v-model="viewMode" size="mini" style="margin-right: 12px">
|
||||
<el-radio-button label="week">单周视图</el-radio-button>
|
||||
<el-radio-button label="all">全学期视图</el-radio-button>
|
||||
<el-radio-button label="h">横版视图</el-radio-button>
|
||||
</el-radio-group>
|
||||
<template v-if="viewMode === 'week'">
|
||||
<template v-if="viewMode !== 'all'">
|
||||
<el-button size="mini" icon="el-icon-arrow-left" :disabled="weekIndex <= 0" @click="weekIndex--">上一周</el-button>
|
||||
<span class="week-text">
|
||||
第 <b>{{ currentWeek ? currentWeek.weekNo : '—' }}</b> 周
|
||||
@@ -138,6 +139,9 @@
|
||||
<el-checkbox-button label="night">夜间</el-checkbox-button>
|
||||
</el-checkbox-group>
|
||||
<el-button size="mini" type="text" @click="conflictDims = []; periodToggles = ['p78','eve','night']">重置</el-button>
|
||||
<el-checkbox v-model="moveMode" size="mini" style="margin-left: 10px">拖拽调课</el-checkbox>
|
||||
<el-button size="mini" icon="el-icon-download" style="margin-left: 8px" @click="handleExport">导出</el-button>
|
||||
<el-button size="mini" icon="el-icon-printer" @click="handlePrint">打印</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -178,13 +182,65 @@
|
||||
@mousedown.prevent="startDrag(d.fi, jc, d, $event)"
|
||||
@mouseenter="hoverCell(d.fi, jc)"
|
||||
@dblclick="dblclickCell(d, jc)"
|
||||
@dragover="onCellDragover(d, jc, $event)"
|
||||
@dragleave="onCellDragleave(d, jc)"
|
||||
@drop="onCellDrop(d, jc, $event)"
|
||||
>
|
||||
<template v-if="lessonsOf(d, jc).length">
|
||||
<div
|
||||
v-for="l in lessonsOf(d, jc)"
|
||||
:key="l.bh"
|
||||
class="lesson"
|
||||
:class="{ 'is-current': currentCourse && l.sskcbh === currentCourse.sskcbh }"
|
||||
:class="{ 'is-current': currentCourse && l.sskcbh === currentCourse.sskcbh, 'is-draggable': moveMode && lessonEditable(l) }"
|
||||
:draggable="moveMode && lessonEditable(l)"
|
||||
@dragstart="onLessonDragstart(l, d, jc, $event)"
|
||||
>
|
||||
<div class="lesson-name">{{ shortName(l) }}</div>
|
||||
<div class="lesson-meta">{{ [l.kcmc ? l.jxnr : '', l.jyxm, l.jsmc].filter(Boolean).join(' · ') || '—' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="isSelected(d.date, jc)" class="slot-picked">已选</div>
|
||||
<div v-else-if="cellOf(d, jc) && cellOf(d, jc).unavailable" class="slot-block">{{ cellOf(d, jc).reason || '不可排' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ==================== 横版:行=日期 列=节次(E4) ==================== -->
|
||||
<template v-if="viewMode === 'h' && currentWeek">
|
||||
<div class="tb-row tb-head">
|
||||
<div class="tb-cell tb-corner">日期</div>
|
||||
<div
|
||||
v-for="jc in jcList"
|
||||
:key="'hjc' + jc"
|
||||
class="tb-cell tb-day"
|
||||
:title="periodLabel(jc)"
|
||||
>{{ jc }}</div>
|
||||
</div>
|
||||
<div v-for="d in hDays" :key="'hd' + d.date" class="tb-row">
|
||||
<div class="tb-cell tb-jc" :class="{ 'day-off': !!d.unavailableReason }" :title="d.unavailableReason || ''">
|
||||
{{ weekdayName(d.weekday) }} {{ fmtDate(d.date) }}
|
||||
</div>
|
||||
<div
|
||||
v-for="jc in jcList"
|
||||
:key="d.date + '#h#' + jc"
|
||||
class="tb-cell tb-slot"
|
||||
:class="slotClass(d, jc)"
|
||||
:title="slotTitle(d, jc)"
|
||||
@mousedown.prevent="startDrag(d.fi, jc, d, $event)"
|
||||
@mouseenter="hoverCell(d.fi, jc)"
|
||||
@dblclick="dblclickCell(d, jc)"
|
||||
@dragover="onCellDragover(d, jc, $event)"
|
||||
@dragleave="onCellDragleave(d, jc)"
|
||||
@drop="onCellDrop(d, jc, $event)"
|
||||
>
|
||||
<template v-if="lessonsOf(d, jc).length">
|
||||
<div
|
||||
v-for="l in lessonsOf(d, jc)"
|
||||
:key="l.bh"
|
||||
class="lesson"
|
||||
:class="{ 'is-current': currentCourse && l.sskcbh === currentCourse.sskcbh, 'is-draggable': moveMode && lessonEditable(l) }"
|
||||
:draggable="moveMode && lessonEditable(l)"
|
||||
@dragstart="onLessonDragstart(l, d, jc, $event)"
|
||||
>
|
||||
<div class="lesson-name">{{ shortName(l) }}</div>
|
||||
<div class="lesson-meta">{{ [l.kcmc ? l.jxnr : '', l.jyxm, l.jsmc].filter(Boolean).join(' · ') || '—' }}</div>
|
||||
@@ -520,6 +576,8 @@
|
||||
import {
|
||||
getSchedulingView,
|
||||
arrangeLessons,
|
||||
moveLesson,
|
||||
exportSchedulingView,
|
||||
deleteLessonCells,
|
||||
clearCourseLessons,
|
||||
deleteRunningCourse,
|
||||
@@ -529,6 +587,7 @@ import {
|
||||
} from '@/api/teachBusiness/schedulingWindow'
|
||||
import { listSemester } from '@/api/studentRecords/semester'
|
||||
import { listAllClassroom } from '@/api/teachBusiness/classroom'
|
||||
import { saveAs } from 'file-saver'
|
||||
|
||||
export default {
|
||||
name: 'SchedulingWindow',
|
||||
@@ -551,8 +610,12 @@ export default {
|
||||
additiveDrag: false,
|
||||
/** 当前聚焦格(⑨ 当前格子编排信息) */
|
||||
focusedCell: null,
|
||||
/** 视图模式:week 单周 / all 全学期 */
|
||||
/** 视图模式:week 单周 / all 全学期 / h 横版 */
|
||||
viewMode: 'week',
|
||||
/** 拖拽调课模式(E4):开启后已排课次可拖到其它格 */
|
||||
moveMode: false,
|
||||
/** 当前拖放悬停目标格 key */
|
||||
dropTarget: null,
|
||||
/** 冲突显示选项(按维度着色) */
|
||||
conflictDims: [],
|
||||
conflictDimOptions: [
|
||||
@@ -624,8 +687,9 @@ export default {
|
||||
})
|
||||
return { list, index }
|
||||
},
|
||||
/** 网格行:单周一行 / 全学期每周一组 */
|
||||
/** 网格行:单周一行 / 全学期每周一组;横版走 hDays */
|
||||
gridRows() {
|
||||
if (this.viewMode === 'h') return []
|
||||
if (this.viewMode === 'all') {
|
||||
return this.weeks.map(w => ({
|
||||
key: 'w' + w.weekNo,
|
||||
@@ -641,6 +705,12 @@ export default {
|
||||
days: (w.days || []).map(d => Object.assign({}, d, { fi: this.fiOf(d.date) }))
|
||||
}]
|
||||
},
|
||||
/** 横版视图的当前周日列表(行=日期) */
|
||||
hDays() {
|
||||
const w = this.currentWeek
|
||||
if (!w) return []
|
||||
return (w.days || []).map(d => Object.assign({}, d, { fi: this.fiOf(d.date) }))
|
||||
},
|
||||
/** 节次字典(来自节次时间表),缺失时按数据推导 */
|
||||
periodDict() {
|
||||
const list = (this.view && this.view.periods) || []
|
||||
@@ -914,6 +984,7 @@ export default {
|
||||
'is-picked': !hasLesson && this.isSelected(day.date, jc),
|
||||
'has-lesson': hasLesson,
|
||||
'is-conflict': !!(cell && !cell.unavailable && this.conflictHit(day, jc)),
|
||||
'drop-hover': this.moveMode && this.dropTarget === this.cellKey(day.date, jc),
|
||||
'day-off': !!day.unavailableReason
|
||||
}
|
||||
},
|
||||
@@ -950,6 +1021,12 @@ export default {
|
||||
this.$message.warning(cell.reason || '该节次不可排课')
|
||||
return
|
||||
}
|
||||
// 拖拽调课模式下按住格子只做聚焦,课次块用 HTML5 拖拽移动
|
||||
if (this.moveMode) {
|
||||
this.focusCell(day.date, jc)
|
||||
if (this.$refs.gridWrap && this.$refs.gridWrap.focus) this.$refs.gridWrap.focus()
|
||||
return
|
||||
}
|
||||
this.dragging = true
|
||||
this.additiveDrag = !!(ev && (ev.ctrlKey || ev.metaKey))
|
||||
this.dragStart = { fi, jc }
|
||||
@@ -1006,6 +1083,78 @@ export default {
|
||||
clearSelection() {
|
||||
this.selectedCells = []
|
||||
},
|
||||
/* ==================== 拖拽调课(E4) ==================== */
|
||||
lessonEditable(l) {
|
||||
const c = this.courses.find(x => x.sskcbh === l.sskcbh)
|
||||
return !!(c && c.editable)
|
||||
},
|
||||
onLessonDragstart(l, day, jc, ev) {
|
||||
if (!this.moveMode || !this.lessonEditable(l)) {
|
||||
ev.preventDefault()
|
||||
return
|
||||
}
|
||||
ev.dataTransfer.setData('text/plain', JSON.stringify({
|
||||
sskcbh: l.sskcbh,
|
||||
rq: this.fmtDate(day.date),
|
||||
jc
|
||||
}))
|
||||
ev.dataTransfer.effectAllowed = 'move'
|
||||
ev.stopPropagation()
|
||||
},
|
||||
onCellDragover(day, jc, ev) {
|
||||
if (!this.moveMode) return
|
||||
const cell = this.cellOf(day, jc)
|
||||
if (cell && cell.unavailable) return
|
||||
ev.preventDefault()
|
||||
if (ev.dataTransfer) ev.dataTransfer.dropEffect = 'move'
|
||||
this.dropTarget = this.cellKey(day.date, jc)
|
||||
},
|
||||
onCellDragleave(day, jc) {
|
||||
if (this.dropTarget === this.cellKey(day.date, jc)) this.dropTarget = null
|
||||
},
|
||||
onCellDrop(day, jc, ev) {
|
||||
if (!this.moveMode) return
|
||||
ev.preventDefault()
|
||||
this.dropTarget = null
|
||||
let payload = null
|
||||
try {
|
||||
payload = JSON.parse(ev.dataTransfer.getData('text/plain'))
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
if (!payload || !payload.sskcbh) return
|
||||
const to = { rq: this.fmtDate(day.date), jc }
|
||||
if (payload.rq === to.rq && payload.jc === to.jc) return
|
||||
this.submitting = true
|
||||
moveLesson({ sskcbh: payload.sskcbh, from: { rq: payload.rq, jc: payload.jc }, to })
|
||||
.then(res => {
|
||||
const data = (res && res.data) || {}
|
||||
if (data.warning) {
|
||||
this.$message.warning('已移动,但 ' + data.warning)
|
||||
} else {
|
||||
this.$message.success('课次已移动')
|
||||
}
|
||||
this.loadView()
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error((err && err.message) || '移动失败')
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
},
|
||||
/* ==================== 导出 / 打印(E4) ==================== */
|
||||
handleExport() {
|
||||
if (!this.xydxqbh) return
|
||||
exportSchedulingView(this.xydxqbh)
|
||||
.then(blob => {
|
||||
saveAs(blob, `排课窗口_${this.xydxqbh}.xlsx`)
|
||||
})
|
||||
.catch(() => this.$message.error('导出失败'))
|
||||
},
|
||||
handlePrint() {
|
||||
window.print()
|
||||
},
|
||||
/** ⑨ 当前格子编排信息 */
|
||||
focusCell(rq, jc) {
|
||||
this.focusedCell = { rq: this.fmtDate(rq), jc }
|
||||
@@ -1889,3 +2038,36 @@ export default {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
/* E4:拖拽调课与打印(非 scoped,拖拽/打印需命中 body 级) */
|
||||
.tb-slot.drop-hover {
|
||||
outline: 2px dashed var(--edu-color-primary, #2f7d5c);
|
||||
outline-offset: -2px;
|
||||
background: var(--edu-color-primary-faint, #eef7f2);
|
||||
}
|
||||
|
||||
.lesson.is-draggable {
|
||||
cursor: grab;
|
||||
}
|
||||
.lesson.is-draggable:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.page-head,
|
||||
.op-bar,
|
||||
.cell-info-bar,
|
||||
.grid-controls,
|
||||
.week-nav .el-radio-group,
|
||||
.week-nav .el-button,
|
||||
.el-dialog__wrapper,
|
||||
.v-modal {
|
||||
display: none !important;
|
||||
}
|
||||
.timetable-wrap {
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-tabs v-model="tab" type="border-card">
|
||||
<!-- ==================== 今日本室 ==================== -->
|
||||
<el-tab-pane label="今日本室" name="today">
|
||||
<div class="day-bar">
|
||||
<el-button size="small" icon="el-icon-arrow-left" @click="shiftDay(-1)">上一天</el-button>
|
||||
<el-date-picker
|
||||
v-model="day"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
:clearable="false"
|
||||
style="width: 150px"
|
||||
size="small"
|
||||
@change="loadToday"
|
||||
/>
|
||||
<el-button size="small" @click="shiftDay(1)">下一天<i class="el-icon-arrow-right" /></el-button>
|
||||
</div>
|
||||
<el-table v-loading="todayLoading" :data="todayRows" border stripe size="small" max-height="560">
|
||||
<el-table-column prop="sksj" label="上课时间" width="170" align="center" />
|
||||
<el-table-column prop="kc" label="课程" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="bc" label="班次" min-width="130" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jy" label="教员" width="140" align="center" />
|
||||
<el-table-column prop="cd" label="场地" width="120" align="center" />
|
||||
<el-table-column prop="jxnrxff" label="教学内容 / 方法" min-width="160" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ==================== 本室课程 ==================== -->
|
||||
<el-tab-pane label="本室课程" name="courses">
|
||||
<el-form inline size="small" style="margin-bottom: 8px">
|
||||
<el-form-item label="年度">
|
||||
<el-input-number v-model="courseQuery.nd" :min="2000" :max="2100" style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学期第次">
|
||||
<el-input-number v-model="courseQuery.xqdc" :min="1" :max="4" style="width: 100px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="loadCourses">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="courseLoading" :data="courseRows" border stripe size="small" max-height="560">
|
||||
<el-table-column prop="kcmcksxs" label="课程名称/课时系数" min-width="180" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zrjykc" label="责任教员/课次" min-width="140" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jhxs" label="计划学时" width="80" align="center" />
|
||||
<el-table-column prop="yxxs" label="运行学时" width="80" align="center" />
|
||||
<el-table-column prop="khlxfs" label="考核类型/方式" width="120" align="center" />
|
||||
<el-table-column prop="ssjy" label="实施教员" width="110" align="center" />
|
||||
<el-table-column prop="rscd" label="人数/场地" min-width="140" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ==================== 本室教员 ==================== -->
|
||||
<el-tab-pane label="本室教员" name="teachers">
|
||||
<el-table v-loading="teacherLoading" :data="teacherRows" border stripe size="small" max-height="560">
|
||||
<el-table-column prop="jybh" label="教员编号" width="140" align="center" />
|
||||
<el-table-column prop="jyxm" label="姓名" min-width="140" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
{{ row.jyxm || '-' }}<span v-if="row.zr === 1" class="dir-star" title="教研室主任"> *</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { myOfficeToday, myOfficeCourses, myOfficeTeachers } from '@/api/teachBusiness/my'
|
||||
|
||||
export default {
|
||||
name: 'MyOffice',
|
||||
data() {
|
||||
return {
|
||||
tab: 'today',
|
||||
day: '',
|
||||
todayRows: [],
|
||||
todayLoading: false,
|
||||
courseQuery: { nd: null, xqdc: null },
|
||||
courseRows: [],
|
||||
courseLoading: false,
|
||||
teacherRows: [],
|
||||
teacherLoading: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.day = this.fmt(new Date())
|
||||
this.loadToday()
|
||||
this.loadCourses()
|
||||
this.loadTeachers()
|
||||
},
|
||||
methods: {
|
||||
fmt(d) {
|
||||
const m = `${d.getMonth() + 1}`.padStart(2, '0')
|
||||
const dd = `${d.getDate()}`.padStart(2, '0')
|
||||
return `${d.getFullYear()}-${m}-${dd}`
|
||||
},
|
||||
shiftDay(n) {
|
||||
const d = new Date(this.day)
|
||||
d.setDate(d.getDate() + n)
|
||||
this.day = this.fmt(d)
|
||||
this.loadToday()
|
||||
},
|
||||
loadToday() {
|
||||
this.todayLoading = true
|
||||
myOfficeToday({ rq: this.day })
|
||||
.then(res => { this.todayRows = res.data || [] })
|
||||
.finally(() => { this.todayLoading = false })
|
||||
},
|
||||
loadCourses() {
|
||||
this.courseLoading = true
|
||||
myOfficeCourses(this.courseQuery)
|
||||
.then(res => { this.courseRows = res.data || [] })
|
||||
.finally(() => { this.courseLoading = false })
|
||||
},
|
||||
loadTeachers() {
|
||||
this.teacherLoading = true
|
||||
myOfficeTeachers()
|
||||
.then(res => { this.teacherRows = res.data || [] })
|
||||
.finally(() => { this.teacherLoading = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.day-bar {
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.dir-star {
|
||||
color: #e6a23c;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-tabs v-model="tab" type="border-card">
|
||||
<!-- ==================== 我的课程任务 ==================== -->
|
||||
<el-tab-pane label="我的课程任务" name="courses">
|
||||
<el-form inline size="small" style="margin-bottom: 8px">
|
||||
<el-form-item label="年度">
|
||||
<el-input-number v-model="courseQuery.nd" :min="2000" :max="2100" style="width: 120px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学期第次">
|
||||
<el-input-number v-model="courseQuery.xqdc" :min="1" :max="4" style="width: 100px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="loadCourses">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="courseLoading" :data="courseRows" border stripe size="small" max-height="560">
|
||||
<el-table-column prop="rwmc" label="教学任务" min-width="140" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="kcmc" label="课程" min-width="140" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="klx" label="课类型" width="80" align="center" />
|
||||
<el-table-column prop="xs" label="学时" width="60" align="center" />
|
||||
<el-table-column prop="xydmc" label="学员队" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="kcxh" label="课次" width="60" align="center" />
|
||||
<el-table-column label="责任教员" width="110" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
{{ row.jyxm || '-' }}<span v-if="row.zr === 1" class="dir-star" title="教研室主任"> *</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jysjhjyxm" label="计划教员" width="100" align="center" />
|
||||
<el-table-column label="场地" width="120" align="center" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ row.jsmc || row.jsbh || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合班" width="80" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag v-if="row.bz2" size="small">组{{ row.bz2 }}</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ==================== 我的课表 ==================== -->
|
||||
<el-tab-pane label="我的课表" name="grid">
|
||||
<el-form inline size="small" style="margin-bottom: 8px">
|
||||
<el-form-item label="日期范围">
|
||||
<el-date-picker
|
||||
v-model="gridRange"
|
||||
type="daterange"
|
||||
value-format="yyyy-MM-dd"
|
||||
range-separator="~"
|
||||
start-placeholder="开始"
|
||||
end-placeholder="结束"
|
||||
style="width: 260px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="loadGrid">生成</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<timetable-grid-view :grid="grid" :loading="gridLoading" :show-team="true" :show-room="true" :show-teacher="false" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- ==================== 实施计划 ==================== -->
|
||||
<el-tab-pane label="我的实施计划" name="lessons">
|
||||
<el-form inline size="small" style="margin-bottom: 8px">
|
||||
<el-form-item label="日期范围">
|
||||
<el-date-picker
|
||||
v-model="lessonRange"
|
||||
type="daterange"
|
||||
value-format="yyyy-MM-dd"
|
||||
range-separator="~"
|
||||
start-placeholder="开始"
|
||||
end-placeholder="结束"
|
||||
style="width: 260px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="loadLessons">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="lessonLoading" :data="lessonRows" border stripe size="small" max-height="560">
|
||||
<el-table-column prop="sksj" label="上课时间" width="170" align="center" />
|
||||
<el-table-column prop="kc" label="课程" min-width="150" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="bc" label="班次" min-width="130" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jy" label="教员" width="120" align="center" />
|
||||
<el-table-column prop="cd" label="场地" width="120" align="center" />
|
||||
<el-table-column prop="jxnrxff" label="教学内容 / 方法" min-width="160" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="bz" label="备注" min-width="100" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { myTeacherCourses, myTeacherLessons, myTeacherGrid } from '@/api/teachBusiness/my'
|
||||
import TimetableGridView from '@/components/TimetableGridView'
|
||||
|
||||
export default {
|
||||
name: 'MyTeach',
|
||||
components: { TimetableGridView },
|
||||
data() {
|
||||
return {
|
||||
tab: 'courses',
|
||||
courseQuery: { nd: null, xqdc: null },
|
||||
courseRows: [],
|
||||
courseLoading: false,
|
||||
gridRange: null,
|
||||
grid: null,
|
||||
gridLoading: false,
|
||||
lessonRange: null,
|
||||
lessonRows: [],
|
||||
lessonLoading: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadCourses()
|
||||
this.loadGrid()
|
||||
},
|
||||
methods: {
|
||||
loadCourses() {
|
||||
this.courseLoading = true
|
||||
myTeacherCourses(this.courseQuery)
|
||||
.then(res => { this.courseRows = res.data || [] })
|
||||
.finally(() => { this.courseLoading = false })
|
||||
},
|
||||
loadGrid() {
|
||||
this.gridLoading = true
|
||||
const params = {}
|
||||
if (this.gridRange && this.gridRange.length === 2) {
|
||||
params.start = this.gridRange[0]
|
||||
params.end = this.gridRange[1]
|
||||
}
|
||||
myTeacherGrid(params)
|
||||
.then(res => { this.grid = res.data })
|
||||
.finally(() => { this.gridLoading = false })
|
||||
},
|
||||
loadLessons() {
|
||||
this.lessonLoading = true
|
||||
const params = {}
|
||||
if (this.lessonRange && this.lessonRange.length === 2) {
|
||||
params.start = this.lessonRange[0]
|
||||
params.end = this.lessonRange[1]
|
||||
}
|
||||
myTeacherLessons(params)
|
||||
.then(res => { this.lessonRows = res.data || [] })
|
||||
.finally(() => { this.lessonLoading = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dir-star {
|
||||
color: #e6a23c;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -12,7 +12,8 @@
|
||||
<el-button size="small" :disabled="!semesterBh" @click="handleApplyRegion">选定区域应用于其它班次</el-button>
|
||||
<el-button size="small" :disabled="!semesterBh" @click="handleApplyWhole">整个班历应用于其它班次</el-button>
|
||||
<el-button size="small" :disabled="!semesterBh" :loading="resyncLoading" @click="handleResync">同步校历</el-button>
|
||||
<span class="hint">应用到其它班次只覆盖班历时间格,不改课程任务;同步校历只补缺失格,不覆盖人工改动。</span>
|
||||
<el-checkbox v-model="resyncForce" size="small">覆盖已有格</el-checkbox>
|
||||
<span class="hint">应用到其它班次只覆盖班历时间格,不改课程任务;勾选「覆盖已有格」后,与校历不一致的班历格子将被校历内容覆盖。</span>
|
||||
</div>
|
||||
<div class="editor-wrap">
|
||||
<SchoolCalendarEditor
|
||||
@@ -57,7 +58,8 @@ export default {
|
||||
return {
|
||||
teamSelectVisible: false,
|
||||
applyWhole: false,
|
||||
resyncLoading: false
|
||||
resyncLoading: false,
|
||||
resyncForce: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -137,22 +139,34 @@ export default {
|
||||
},
|
||||
handleResync() {
|
||||
this.resyncLoading = true
|
||||
resyncClassCalendar(this.semesterBh, false).then(res => {
|
||||
const force = this.resyncForce
|
||||
resyncClassCalendar(this.semesterBh, false, force).then(res => {
|
||||
const data = res && res.data
|
||||
if (!data || !data.addCount) {
|
||||
const addCount = (data && data.addCount) || 0
|
||||
const updateCount = (data && data.updateCount) || 0
|
||||
if (!addCount && !updateCount) {
|
||||
this.$message.info('班历已与校历一致,无需同步')
|
||||
return
|
||||
}
|
||||
const lines = (data.items || []).slice(0, 20)
|
||||
.map(i => `${i.jqsj} ${i.courseClass}节 ${i.jqmc || ''}${i.kpk === false ? '(不可排课)' : ''}`)
|
||||
const more = data.addCount > lines.length ? `<br/>…共 ${data.addCount} 格` : ''
|
||||
this.$confirm(
|
||||
`校历中有 ${data.addCount} 个班历缺失的时间格:<br/>${lines.join('<br/>')}${more}<br/><br/>补齐这些格子?(不覆盖已有班历内容)`,
|
||||
'同步校历',
|
||||
{ dangerouslyUseHTMLString: true, confirmButtonText: '补齐', cancelButtonText: '取消' }
|
||||
).then(() => {
|
||||
return resyncClassCalendar(this.semesterBh, true).then(r => {
|
||||
this.$message.success(`已补齐 ${(r.data && r.data.addCount) || 0} 个时间格`)
|
||||
const more = addCount > lines.length ? `<br/>…共 ${addCount} 格` : ''
|
||||
let msg = ''
|
||||
if (addCount) {
|
||||
msg += `校历中有 ${addCount} 个班历缺失的时间格:<br/>${lines.join('<br/>')}${more}<br/>`
|
||||
}
|
||||
if (updateCount) {
|
||||
msg += `<br/>另有 ${updateCount} 个已有格与校历不一致,将被<span style="color:#f56c6c">覆盖为校历内容</span>(人工改动丢失)。<br/>`
|
||||
}
|
||||
msg += '<br/>确认执行同步?'
|
||||
this.$confirm(msg, '同步校历', {
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: '同步',
|
||||
cancelButtonText: '取消'
|
||||
}).then(() => {
|
||||
return resyncClassCalendar(this.semesterBh, true, force).then(r => {
|
||||
const d = r.data || {}
|
||||
this.$message.success(`已补齐 ${d.addCount || 0} 格${force ? `,覆盖 ${d.updateCount || 0} 格` : ''}`)
|
||||
const editor = this.$refs.editor
|
||||
if (editor && editor.reloadEvents) editor.reloadEvents()
|
||||
})
|
||||
|
||||
@@ -22,24 +22,42 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 每周可排正课:时间轴按周、月刻度 -->
|
||||
<!-- 每周可排正课:时间轴按周/月刻度切换 -->
|
||||
<div class="axis-toolbar">
|
||||
<el-radio-group v-model="axisMode" size="mini">
|
||||
<el-radio-button label="week">按周</el-radio-button>
|
||||
<el-radio-button label="month">按月</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="week-axis">
|
||||
<table class="axis-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sticky-col first">周次</th>
|
||||
<th v-for="w in view.weeks" :key="'wn' + w.weekNo" class="week-col"
|
||||
:class="{ 'month-start': isMonthStart(w) }">
|
||||
W{{ w.weekNo }}
|
||||
<div class="week-date">{{ shortDate(w.startDate) }}</div>
|
||||
<th class="sticky-col first">{{ axisMode === 'week' ? '周次' : '月份' }}</th>
|
||||
<th v-for="col in axisCols" :key="'wn' + col.key" class="week-col"
|
||||
:class="{ 'month-start': axisMode === 'week' && isMonthStart(col.weeks[0]) }">
|
||||
{{ col.label }}
|
||||
<div class="week-date">{{ col.sublabel }}</div>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="sticky-col first">可排正课</th>
|
||||
<th v-for="w in view.weeks" :key="'wh' + w.weekNo" class="week-col hours-cell"
|
||||
:class="{ 'month-start': isMonthStart(w) }"
|
||||
:title="sourceText(w.source)">
|
||||
{{ w.availableHours }}h
|
||||
<th v-for="col in axisCols" :key="'wh' + col.key" class="week-col hours-cell"
|
||||
:class="{ 'month-start': axisMode === 'week' && isMonthStart(col.weeks[0]) }"
|
||||
:title="col.weeks.map(w => sourceText(w.source)).join('、')">
|
||||
{{ sumOf(col.weeks, 'availableHours') }}h
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="sticky-col first">已铺</th>
|
||||
<th v-for="col in axisCols" :key="'ws' + col.key" class="week-col used-cell">
|
||||
{{ sumOf(col.weeks, 'scheduledHours') }}h
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="sticky-col first">剩余</th>
|
||||
<th v-for="col in axisCols" :key="'wr' + col.key" class="week-col remain-cell">
|
||||
{{ sumOf(col.weeks, 'remainingHours') }}h
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -53,7 +71,12 @@
|
||||
<div class="list-actions">
|
||||
<el-button size="small" type="primary" :disabled="!selection.length || view.frozen"
|
||||
@click="handleSaveSelected">保存所选</el-button>
|
||||
<el-button size="small" type="warning" plain :disabled="view.frozen"
|
||||
@click="handleAutoArrange(false)">自动排布</el-button>
|
||||
<el-button size="small" type="warning" plain :disabled="view.frozen"
|
||||
@click="handleAutoArrange(true)">辅助排布</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="loadData">刷新</el-button>
|
||||
<el-button size="small" icon="el-icon-printer" @click="handlePrint">打印</el-button>
|
||||
<el-button size="small" icon="el-icon-download" @click="handleExportImage">导出图像</el-button>
|
||||
<el-button size="small" icon="el-icon-download" @click="handleExportExcel">导出 Excel</el-button>
|
||||
</div>
|
||||
@@ -71,14 +94,16 @@
|
||||
<th class="sticky-col c-name">课程</th>
|
||||
<th class="sticky-col c-num">学时</th>
|
||||
<th class="sticky-col c-num">周课时</th>
|
||||
<th class="sticky-col c-num" title="实施课程表已排课次折算学时">已排</th>
|
||||
<th class="sticky-col c-num" title="学时 − 已排学时">剩余</th>
|
||||
<th class="sticky-col c-num" title="优选序数">序数</th>
|
||||
<th class="sticky-col c-num" title="配档起始周">起周</th>
|
||||
<th class="sticky-col c-mode">连排/按周</th>
|
||||
<th class="sticky-col c-num" title="配档编组(0=不编组)">编组</th>
|
||||
<th class="sticky-col c-ops">操作</th>
|
||||
<th v-for="w in view.weeks" :key="'tc' + w.weekNo"
|
||||
class="week-col" :class="{ 'month-start': isMonthStart(w) }">
|
||||
{{ w.weekNo }}
|
||||
<th v-for="col in axisCols" :key="'tc' + col.key"
|
||||
class="week-col" :class="{ 'month-start': axisMode === 'week' && isMonthStart(col.weeks[0]) }">
|
||||
{{ col.short }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -91,6 +116,8 @@
|
||||
<td class="sticky-col c-name" :title="row.kbh">{{ row.kcmc }}</td>
|
||||
<td class="sticky-col c-num">{{ row.xs }}</td>
|
||||
<td class="sticky-col c-num">{{ row.zks }}</td>
|
||||
<td class="sticky-col c-num used-cell">{{ row.scheduledHours == null ? '-' : row.scheduledHours }}</td>
|
||||
<td class="sticky-col c-num remain-cell">{{ row.remainingHours == null ? '-' : row.remainingHours }}</td>
|
||||
<td class="sticky-col c-num">
|
||||
<el-input-number v-model="row.pdxh" :min="1" :controls="false" size="mini"
|
||||
class="tiny-input" :disabled="view.frozen" />
|
||||
@@ -117,13 +144,13 @@
|
||||
<el-button type="text" size="mini" :disabled="view.frozen" @click="handleMove(row, 'down')">↓</el-button>
|
||||
<el-button type="text" size="mini" @click="openGroupPanel(row)">编组</el-button>
|
||||
</td>
|
||||
<td v-for="w in view.weeks" :key="'cell' + row.bh + w.weekNo"
|
||||
class="week-col cell" :class="cellClass(row, w)">
|
||||
{{ cellHours(row, w.weekNo) || '' }}
|
||||
<td v-for="col in axisCols" :key="'cell' + row.bh + col.key"
|
||||
class="week-col cell" :class="cellClassOf(row, col)">
|
||||
{{ cellHoursOf(row, col) || '' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!view.tasks.length">
|
||||
<td :colspan="10 + view.weeks.length" class="empty-row">
|
||||
<td :colspan="12 + axisCols.length" class="empty-row">
|
||||
该班次学期暂无课程任务,请先在「班次教学任务」中生成/添加课程。
|
||||
</td>
|
||||
</tr>
|
||||
@@ -172,7 +199,8 @@ import {
|
||||
allocationMove,
|
||||
allocationGroups,
|
||||
allocationSetGroup,
|
||||
allocationExport
|
||||
allocationExport,
|
||||
allocationAutoArrange
|
||||
} from '@/api/teachBusiness/allocation'
|
||||
|
||||
const SOURCE_TEXT = { class: '班历', school: '校历', default: '默认(30学时)' }
|
||||
@@ -187,6 +215,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
axisMode: 'week',
|
||||
view: { weeks: [], tasks: [], totalWeeks: 0, frozen: false, ndCode: '', kxrq: '', jsrq: '', xydmc: '' },
|
||||
selection: [],
|
||||
groupPanelVisible: false,
|
||||
@@ -201,6 +230,38 @@ export default {
|
||||
semesterName() {
|
||||
const s = this.semester || {}
|
||||
return s.xydmc || s.xydbh || ''
|
||||
},
|
||||
/**
|
||||
* 轴列:week=每周一列;month=按周起所在月份归并(label=月名,sublabel=覆盖周次)
|
||||
*/
|
||||
axisCols() {
|
||||
const weeks = this.view.weeks || []
|
||||
if (this.axisMode === 'week') {
|
||||
return weeks.map(w => ({
|
||||
key: 'w' + w.weekNo,
|
||||
label: 'W' + w.weekNo,
|
||||
short: w.weekNo,
|
||||
sublabel: this.shortDate(w.startDate),
|
||||
weeks: [w]
|
||||
}))
|
||||
}
|
||||
const groups = []
|
||||
const byMonth = {}
|
||||
weeks.forEach(w => {
|
||||
const d = w.startDate ? new Date(String(w.startDate).replace(/-/g, '/')) : null
|
||||
const key = d && !isNaN(d) ? d.getFullYear() + '-' + (d.getMonth() + 1) : '其它'
|
||||
if (!byMonth[key]) {
|
||||
byMonth[key] = { key: 'm' + key, label: key === '其它' ? '其它' : (d.getMonth() + 1) + '月', short: key === '其它' ? '?' : (d.getMonth() + 1) + '月', sublabel: '', weeks: [] }
|
||||
groups.push(byMonth[key])
|
||||
}
|
||||
byMonth[key].weeks.push(w)
|
||||
})
|
||||
groups.forEach(g => {
|
||||
const first = g.weeks[0].weekNo
|
||||
const last = g.weeks[g.weeks.length - 1].weekNo
|
||||
g.sublabel = 'W' + first + (last > first ? '-' + last : '')
|
||||
})
|
||||
return groups
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -303,16 +364,46 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
/* ---------- 自动排布 ---------- */
|
||||
handleAutoArrange(onlyMissing) {
|
||||
const s = this.semester || {}
|
||||
if (!s.bh) return
|
||||
const selected = this.selection.length ? this.selection.map(r => r.bh) : null
|
||||
const scopeText = selected ? `所选 ${selected.length} 条任务` : '全部任务'
|
||||
const modeText = onlyMissing
|
||||
? '只回填尚未设置起始周的任务(不动人工排布)'
|
||||
: '按铺学时建议重算并覆盖全部起始周'
|
||||
this.$confirm(`将对${scopeText}执行「${onlyMissing ? '辅助排布' : '自动排布'}」:${modeText},配档起始周写库。是否继续?`, '自动排布', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
return allocationAutoArrange(s.bh, onlyMissing, selected)
|
||||
}).then(res => {
|
||||
this.$message.success(`已回填 ${res && res.data || 0} 条任务的起始周`)
|
||||
this.loadData()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 打印 ---------- */
|
||||
handlePrint() {
|
||||
window.print()
|
||||
},
|
||||
|
||||
/* ---------- 单元格 ---------- */
|
||||
sumOf(weeks, field) {
|
||||
return (weeks || []).reduce((sum, w) => sum + (w[field] || 0), 0)
|
||||
},
|
||||
cellHours(row, weekNo) {
|
||||
const hit = (row.distribution || []).find(item => item.week === weekNo)
|
||||
return hit ? hit.hours : 0
|
||||
},
|
||||
cellClass(row, w) {
|
||||
const hours = this.cellHours(row, w.weekNo)
|
||||
cellHoursOf(row, col) {
|
||||
return col.weeks.reduce((sum, w) => sum + this.cellHours(row, w.weekNo), 0)
|
||||
},
|
||||
cellClassOf(row, col) {
|
||||
const hours = this.cellHoursOf(row, col)
|
||||
return {
|
||||
'cell-active': hours > 0,
|
||||
'cell-overflow-week': hours > w.availableHours
|
||||
'cell-overflow-week': hours > this.sumOf(col.weeks, 'availableHours')
|
||||
}
|
||||
},
|
||||
isMonthStart(w) {
|
||||
@@ -418,6 +509,9 @@ export default {
|
||||
.week-col { min-width: 34px; }
|
||||
.month-start { border-left: 2px solid #dcdfe6 !important; }
|
||||
.hours-cell { color: #409eff; font-weight: 600; }
|
||||
.used-cell { color: #67c23a; font-weight: 600; }
|
||||
.remain-cell { color: #e6a23c; font-weight: 600; }
|
||||
.axis-toolbar { margin: 4px 0; text-align: right; }
|
||||
|
||||
.alloc-table-wrap { overflow: auto; max-height: 52vh; border: 1px solid #ebeef5; }
|
||||
.alloc-table {
|
||||
@@ -427,7 +521,7 @@ export default {
|
||||
.c-name { left: 72px; min-width: 120px; max-width: 160px; text-align: left; }
|
||||
.c-num { min-width: 56px; }
|
||||
.c-mode { min-width: 74px; }
|
||||
.c-ops { left: 486px; min-width: 150px; white-space: nowrap; }
|
||||
.c-ops { left: 598px; min-width: 150px; white-space: nowrap; }
|
||||
.cell { min-width: 34px; color: #909399; }
|
||||
.cell-active { background: #d9ecff; color: #1f5faa; font-weight: 600; }
|
||||
.cell-overflow-week { background: #fde2e2; }
|
||||
@@ -450,3 +544,23 @@ export default {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 打印:只输出配当内容区 -->
|
||||
<style>
|
||||
@media print {
|
||||
body * { visibility: hidden; }
|
||||
.allocation-page, .allocation-page * { visibility: visible; }
|
||||
.allocation-page {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
.allocation-page .list-actions,
|
||||
.allocation-page .c-ops,
|
||||
.allocation-page .axis-toolbar,
|
||||
.allocation-page .el-checkbox { display: none !important; }
|
||||
.allocation-page .alloc-table-wrap { max-height: none !important; overflow: visible !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
<el-button type="success" plain icon="el-icon-date" :disabled="!currentRow" @click="handleEditEventCalendar">编辑班历</el-button>
|
||||
<el-button type="success" plain icon="el-icon-calendar" :disabled="!currentRow" @click="handleEditCalendar">编辑班次教学任务</el-button>
|
||||
<el-button type="success" plain icon="el-icon-data-line" :disabled="!currentRow" @click="handleAllocation">教学配当</el-button>
|
||||
<el-button type="success" plain icon="el-icon-download" :disabled="!selection.length" @click="handleExportAllocation">导出配当</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-upload2" :disabled="!selection.length" @click="handlePublishRunning">发布运行课表</el-button>
|
||||
<el-button type="danger" plain icon="el-icon-delete-solid" :disabled="!selection.length" @click="handleWithdrawRunning">删除运行课表</el-button>
|
||||
</div>
|
||||
@@ -335,6 +336,7 @@ import { batchAutoGenerateTasks } from '@/api/studentRecords/classCalendar'
|
||||
import { listSemester, allSemester, addSemester, updateSemester, delSemester, batchDeleteSemester, batchUpdateDateRange, batchUpdateJxrwbh, batchCreateSemester, batchUpdateXqdc, batchUpdateKfjypk, batchWaveMerge, batchWaveSplit, listCreatableClasses, presetSemesterDates } from '@/api/studentRecords/semester'
|
||||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||||
import { listTeachingTask } from '@/api/teachBusiness/teachingTask'
|
||||
import { allocationExport } from '@/api/teachBusiness/allocation'
|
||||
import { publishRunningCourses, withdrawRunningCourses } from '@/api/teachBusiness/schedulingWindow'
|
||||
|
||||
export default {
|
||||
@@ -668,6 +670,23 @@ export default {
|
||||
this.allocationVisible = true
|
||||
},
|
||||
|
||||
// 导出配当(多学期批量):勾选几个班次学期就导出几个
|
||||
handleExportAllocation() {
|
||||
const ids = this.selection.map(item => item.bh || item.xydxqbh).filter(Boolean)
|
||||
if (!ids.length) {
|
||||
this.$message.warning('请先勾选要导出的班次学期')
|
||||
return
|
||||
}
|
||||
allocationExport(ids).then(blob => {
|
||||
const url = window.URL.createObjectURL(new Blob([blob]))
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `教学配当_${ids.length}个班次学期.xlsx`
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
})
|
||||
},
|
||||
|
||||
// 批量发布【运行课表】(手册 12.1):把课程任务发布为运行课程,之后才能在排课窗口排课
|
||||
handlePublishRunning() {
|
||||
const rows = this.selection.slice()
|
||||
|
||||
@@ -31,7 +31,34 @@
|
||||
:disabled="frozen || !selection.length"
|
||||
@click="handleSplit"
|
||||
>拆班</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-user"
|
||||
:disabled="frozen || !selection.length"
|
||||
@click="openBatchTeacher"
|
||||
>批量指定教员({{ selection.length }})</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-office-building"
|
||||
:disabled="frozen || !selection.length"
|
||||
@click="openBatchRoom"
|
||||
>批量指定场地({{ selection.length }})</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-edit-outline"
|
||||
:disabled="frozen || !selection.length"
|
||||
@click="openBatchFill"
|
||||
>批量填报({{ selection.length }})</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="el-icon-delete"
|
||||
:disabled="frozen || !selection.length"
|
||||
@click="handleDeleteRows"
|
||||
>删除行({{ selection.length }})</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="loadRows">刷新</el-button>
|
||||
<el-button size="small" icon="el-icon-download" @click="handleExport">导出</el-button>
|
||||
<el-button size="small" icon="el-icon-printer" @click="handlePrint">打印</el-button>
|
||||
<span v-if="frozen" class="tb-frozen-tip">教学任务未发布或已结束,当前只读</span>
|
||||
<span class="tb-tip">合班规则:科目、学时、课类型、成绩分制必须相同;同合班行同色连显</span>
|
||||
</div>
|
||||
@@ -57,7 +84,9 @@
|
||||
<el-table-column prop="zks" label="周课时" width="65" align="center" />
|
||||
<el-table-column prop="cjfz" label="成绩分制" width="80" align="center" />
|
||||
<el-table-column label="责任教员" width="110" align="center">
|
||||
<template slot-scope="{ row }">{{ row.jyxm || '-' }}</template>
|
||||
<template slot-scope="{ row }">
|
||||
{{ row.jyxm || '-' }}<span v-if="row.zr === 1" class="tb-director" title="教研室主任"> *</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="场地" width="130" align="center" show-overflow-tooltip>
|
||||
<template slot-scope="{ row }">{{ row.jsmc || row.jsbh || '-' }}</template>
|
||||
@@ -76,11 +105,12 @@
|
||||
<template slot-scope="{ row }">{{ row.jysjhjyxm || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jysjhbz" label="排课建议" min-width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="210" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="270" align="center" fixed="right">
|
||||
<template slot-scope="{ row }">
|
||||
<el-button type="text" size="small" :disabled="frozen" @click="openTeacher(row)">指定教员</el-button>
|
||||
<el-button type="text" size="small" :disabled="frozen" @click="openRoom(row)">指定场地</el-button>
|
||||
<el-button type="text" size="small" :disabled="frozen" @click="openFill(row)">填报</el-button>
|
||||
<el-button type="text" size="small" style="color:#f56c6c" :disabled="frozen" @click="handleDeleteRows([row])">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -98,6 +128,9 @@
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form label-width="110px">
|
||||
<el-form-item v-if="teacherDialog.batch" label="选中行">
|
||||
<span>{{ selection.length }} 行将统一指定责任教员</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="指定方式">
|
||||
<el-radio-group v-model="teacherDialog.mode" @change="loadTeacherOptions">
|
||||
<el-radio label="unit">责任单位教员</el-radio>
|
||||
@@ -122,7 +155,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="计划教员">
|
||||
<span>{{ teacherDialog.planTeacherName || '(该行没有教研室计划教员)' }}</span>
|
||||
<span>{{ teacherDialog.batch ? '应用各行教研室计划教员' : (teacherDialog.planTeacherName || '(该行没有教研室计划教员)') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="teacherDialog.row && teacherDialog.row.bz2 && teacherDialog.row.bz2 !== 0" label="同步范围">
|
||||
<span class="tb-tip">该行已合班(组{{ teacherDialog.row.bz2 }}),保存后同组其它行同步</span>
|
||||
@@ -143,6 +176,9 @@
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form label-width="110px">
|
||||
<el-form-item v-if="roomDialog.batch" label="选中行">
|
||||
<span>{{ selection.length }} 行将统一指定场地</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="场地类型">
|
||||
<el-radio-group v-model="roomDialog.useSpecial">
|
||||
<el-radio :label="true">班次专用教室</el-radio>
|
||||
@@ -178,13 +214,16 @@
|
||||
<!-- ==================== 填报子对话框 ==================== -->
|
||||
<el-dialog
|
||||
:visible.sync="fillDialog.visible"
|
||||
title="教研室填报"
|
||||
:title="fillDialog.batch ? `批量填报(${selection.length} 行)` : '教研室填报'"
|
||||
width="520px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form label-width="110px">
|
||||
<el-form-item label="课程 / 学员队">
|
||||
<el-form-item v-if="fillDialog.batch" label="选中行">
|
||||
<span>{{ selection.length }} 行将统一应用下方填写的字段(留空字段不改动)</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="课程 / 学员队">
|
||||
<span>{{ fillDialog.row ? `${fillDialog.row.kcmc} / ${fillDialog.row.xydmc}` : '' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="计划教员">
|
||||
@@ -223,6 +262,15 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="课次序号">
|
||||
<el-input-number
|
||||
v-model="fillDialog.kcxh"
|
||||
:min="1"
|
||||
:max="999"
|
||||
placeholder="课序"
|
||||
style="width: 160px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排课建议">
|
||||
<el-input
|
||||
v-model="fillDialog.jysjhbz"
|
||||
@@ -253,8 +301,13 @@ import {
|
||||
taskBookSplit,
|
||||
taskBookSetTeacher,
|
||||
taskBookSetRoom,
|
||||
taskBookBatchSetTeacher,
|
||||
taskBookBatchSetRoom,
|
||||
taskBookBatchFill,
|
||||
taskBookDeleteRows,
|
||||
taskBookFill
|
||||
} from '@/api/teachBusiness/taskBook'
|
||||
import { exportTaskBook } from '@/api/teachBusiness/teachingTask'
|
||||
import { listTeacher } from '@/api/teachOffice/teacher'
|
||||
import { listClassroom } from '@/api/teachBusiness/classroom'
|
||||
|
||||
@@ -275,6 +328,7 @@ export default {
|
||||
selection: [],
|
||||
teacherDialog: {
|
||||
visible: false,
|
||||
batch: false,
|
||||
mode: 'unit',
|
||||
jybh: '',
|
||||
teachers: [],
|
||||
@@ -283,6 +337,7 @@ export default {
|
||||
},
|
||||
roomDialog: {
|
||||
visible: false,
|
||||
batch: false,
|
||||
useSpecial: true,
|
||||
jsbh: '',
|
||||
classrooms: [],
|
||||
@@ -291,9 +346,11 @@ export default {
|
||||
},
|
||||
fillDialog: {
|
||||
visible: false,
|
||||
batch: false,
|
||||
jysjhjybh: '',
|
||||
jsbh: '',
|
||||
jysjhbz: '',
|
||||
kcxh: null,
|
||||
useSpecial: false,
|
||||
teachers: [],
|
||||
classrooms: [],
|
||||
@@ -369,15 +426,25 @@ export default {
|
||||
// ---------- 指定教员 ----------
|
||||
openTeacher(row) {
|
||||
this.teacherDialog.row = row
|
||||
this.teacherDialog.batch = false
|
||||
this.teacherDialog.mode = 'unit'
|
||||
this.teacherDialog.jybh = row.jybh || ''
|
||||
this.teacherDialog.visible = true
|
||||
this.loadTeacherOptions()
|
||||
},
|
||||
openBatchTeacher() {
|
||||
this.teacherDialog.row = null
|
||||
this.teacherDialog.batch = true
|
||||
this.teacherDialog.mode = 'academy'
|
||||
this.teacherDialog.jybh = ''
|
||||
this.teacherDialog.visible = true
|
||||
this.loadTeacherOptions()
|
||||
},
|
||||
loadTeacherOptions() {
|
||||
const row = this.teacherDialog.row
|
||||
if (!row) return
|
||||
const params = this.teacherDialog.mode === 'unit' ? { jysdh: row.jysdh } : {}
|
||||
const params = (!this.teacherDialog.batch && row && this.teacherDialog.mode === 'unit')
|
||||
? { jysdh: row.jysdh }
|
||||
: {}
|
||||
this.teacherDialog.loading = true
|
||||
listTeacher(params)
|
||||
.then(res => {
|
||||
@@ -389,7 +456,14 @@ export default {
|
||||
},
|
||||
submitTeacher() {
|
||||
const d = this.teacherDialog
|
||||
taskBookSetTeacher({ bh: d.row.bh, mode: d.mode, jybh: d.mode === 'plan' ? undefined : d.jybh })
|
||||
const call = d.batch
|
||||
? taskBookBatchSetTeacher({
|
||||
bhList: this.selection.map(r => r.bh),
|
||||
mode: d.mode,
|
||||
jybh: d.mode === 'plan' ? undefined : d.jybh
|
||||
})
|
||||
: taskBookSetTeacher({ bh: d.row.bh, mode: d.mode, jybh: d.mode === 'plan' ? undefined : d.jybh })
|
||||
call
|
||||
.then(res => {
|
||||
d.visible = false
|
||||
this.reload(Promise.resolve(res))
|
||||
@@ -399,9 +473,21 @@ export default {
|
||||
// ---------- 指定场地 ----------
|
||||
openRoom(row) {
|
||||
this.roomDialog.row = row
|
||||
this.roomDialog.batch = false
|
||||
this.roomDialog.useSpecial = true
|
||||
this.roomDialog.jsbh = ''
|
||||
this.roomDialog.visible = true
|
||||
this.loadRoomOptions()
|
||||
},
|
||||
openBatchRoom() {
|
||||
this.roomDialog.row = null
|
||||
this.roomDialog.batch = true
|
||||
this.roomDialog.useSpecial = false
|
||||
this.roomDialog.jsbh = ''
|
||||
this.roomDialog.visible = true
|
||||
this.loadRoomOptions()
|
||||
},
|
||||
loadRoomOptions() {
|
||||
if (!this.roomDialog.classrooms.length) {
|
||||
this.roomDialog.loading = true
|
||||
listClassroom({ pageNum: 1, pageSize: 200 })
|
||||
@@ -416,7 +502,14 @@ export default {
|
||||
},
|
||||
submitRoom() {
|
||||
const d = this.roomDialog
|
||||
taskBookSetRoom({ bh: d.row.bh, jsbh: d.jsbh, useSpecial: d.useSpecial })
|
||||
const call = d.batch
|
||||
? taskBookBatchSetRoom({
|
||||
bhList: this.selection.map(r => r.bh),
|
||||
jsbh: d.jsbh,
|
||||
useSpecial: d.useSpecial
|
||||
})
|
||||
: taskBookSetRoom({ bh: d.row.bh, jsbh: d.jsbh, useSpecial: d.useSpecial })
|
||||
call
|
||||
.then(res => {
|
||||
d.visible = false
|
||||
this.reload(Promise.resolve(res))
|
||||
@@ -426,9 +519,11 @@ export default {
|
||||
// ---------- 填报 ----------
|
||||
openFill(row) {
|
||||
this.fillDialog.row = row
|
||||
this.fillDialog.batch = false
|
||||
this.fillDialog.jysjhjybh = row.jysjhjybh || ''
|
||||
this.fillDialog.jsbh = row.jsbh || ''
|
||||
this.fillDialog.jysjhbz = row.jysjhbz || ''
|
||||
this.fillDialog.kcxh = row.kcxh || null
|
||||
this.fillDialog.useSpecial = false
|
||||
this.fillDialog.visible = true
|
||||
this.fillDialog.loading = true
|
||||
@@ -450,12 +545,57 @@ export default {
|
||||
this.fillDialog.loading = false
|
||||
})
|
||||
},
|
||||
openBatchFill() {
|
||||
this.fillDialog.row = null
|
||||
this.fillDialog.batch = true
|
||||
this.fillDialog.jysjhjybh = ''
|
||||
this.fillDialog.jsbh = ''
|
||||
this.fillDialog.jysjhbz = ''
|
||||
this.fillDialog.kcxh = null
|
||||
this.fillDialog.useSpecial = false
|
||||
this.fillDialog.visible = true
|
||||
this.fillDialog.loading = true
|
||||
Promise.all([
|
||||
listTeacher({}).catch(() => null),
|
||||
this.roomDialog.classrooms.length
|
||||
? Promise.resolve(null)
|
||||
: listClassroom({ pageNum: 1, pageSize: 200 }).catch(() => null)
|
||||
])
|
||||
.then(([tRes, cRes]) => {
|
||||
if (tRes) this.fillDialog.teachers = (tRes.data && (tRes.data.records || tRes.data.rows || tRes.data)) || []
|
||||
if (cRes) {
|
||||
const data = cRes.data || {}
|
||||
this.roomDialog.classrooms = data.records || data.rows || data.list || []
|
||||
}
|
||||
this.fillDialog.classrooms = this.roomDialog.classrooms
|
||||
})
|
||||
.finally(() => {
|
||||
this.fillDialog.loading = false
|
||||
})
|
||||
},
|
||||
submitFill() {
|
||||
const d = this.fillDialog
|
||||
if (d.batch) {
|
||||
taskBookBatchFill({
|
||||
bhList: this.selection.map(r => r.bh),
|
||||
jysjhjybh: d.jysjhjybh || undefined,
|
||||
jysjhbz: d.jysjhbz || undefined,
|
||||
kcxh: d.kcxh || undefined,
|
||||
jsbh: d.useSpecial ? undefined : (d.jsbh || undefined),
|
||||
useSpecial: d.useSpecial
|
||||
})
|
||||
.then(res => {
|
||||
d.visible = false
|
||||
this.reload(Promise.resolve(res))
|
||||
})
|
||||
.catch(err => this.$message.error((err && err.message) || '保存失败'))
|
||||
return
|
||||
}
|
||||
const base = {
|
||||
bh: d.row.bh,
|
||||
jysjhjybh: d.jysjhjybh || '',
|
||||
jysjhbz: d.jysjhbz || ''
|
||||
jysjhbz: d.jysjhbz || '',
|
||||
kcxh: d.kcxh || undefined
|
||||
}
|
||||
// 专用教室走 setRoom(useSpecial) 才能取到班次专用教室编号;其它教室随 fill 同步
|
||||
const call = d.useSpecial
|
||||
@@ -467,6 +607,36 @@ export default {
|
||||
this.reload(Promise.resolve(res))
|
||||
})
|
||||
.catch(err => this.$message.error((err && err.message) || '保存失败'))
|
||||
},
|
||||
// ---------- 行级删除 / 导出 / 打印 ----------
|
||||
handleDeleteRows(rowsArg) {
|
||||
const targets = Array.isArray(rowsArg) ? rowsArg : this.selection
|
||||
const bhList = targets.map(r => r.bh)
|
||||
if (!bhList.length) {
|
||||
this.$message.warning('请先选择要删除的行')
|
||||
return
|
||||
}
|
||||
this.$confirm(`确定删除选中的 ${bhList.length} 条课程任务行吗?已发布到运行课表的须先撤回。`, '删除课程任务行', {
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => this.reload(taskBookDeleteRows(bhList)))
|
||||
.catch(() => {})
|
||||
},
|
||||
handleExport() {
|
||||
if (!this.jxrwbh) return
|
||||
exportTaskBook(this.jxrwbh)
|
||||
.then(res => {
|
||||
const blob = new Blob([res.data || res], { type: 'application/vnd.ms-excel' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `教学任务书_${this.taskName || this.jxrwbh}.xls`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
})
|
||||
.catch(() => this.$message.error('导出失败'))
|
||||
},
|
||||
handlePrint() {
|
||||
window.print()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,4 +669,29 @@ export default {
|
||||
color: #c0c4cc;
|
||||
font-size: 12px;
|
||||
}
|
||||
.tb-director {
|
||||
color: #e6a23c;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
@media print {
|
||||
.tb-toolbar,
|
||||
.el-dialog__headerbtn,
|
||||
.el-dialog__footer,
|
||||
.tb-table .el-table__fixed-right,
|
||||
.v-modal {
|
||||
display: none !important;
|
||||
}
|
||||
.el-dialog {
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.el-dialog__body {
|
||||
max-height: none !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -40,15 +40,38 @@
|
||||
<div class="list-toolbar">
|
||||
<div class="left-group">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建教学任务</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
icon="el-icon-position"
|
||||
:disabled="!selection.length"
|
||||
@click="handleBatchPublish"
|
||||
>批量发布({{ selection.length }})</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
:disabled="!selection.length"
|
||||
@click="handleBatchDelete"
|
||||
>批量删除({{ selection.length }})</el-button>
|
||||
</div>
|
||||
<div class="right-group">
|
||||
<el-button icon="el-icon-collection" @click="openOfficeSummary">教研室任务书汇总</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 3. 数据表格 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table v-loading="loading" :data="tableData" border stripe class="task-table">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="tableData"
|
||||
border
|
||||
stripe
|
||||
class="task-table"
|
||||
@selection-change="val => selection = val"
|
||||
>
|
||||
<template slot="empty">
|
||||
<span>无数据!</span>
|
||||
</template>
|
||||
<el-table-column type="selection" width="45" align="center" />
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="rwmc" label="任务名称" width="250" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="nd" label="年度" width="150" align="center" />
|
||||
@@ -195,6 +218,52 @@
|
||||
:task-name="taskBookRow.rwmc"
|
||||
:task-status="taskBookRow.zt"
|
||||
/>
|
||||
|
||||
<!-- ==================== 7. 教研室任务书汇总对话框 ==================== -->
|
||||
<el-dialog
|
||||
:visible.sync="summaryVisible"
|
||||
title="教研室任务书汇总"
|
||||
width="860px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="教研室">
|
||||
<el-select
|
||||
v-model="summaryJysdh"
|
||||
filterable
|
||||
placeholder="请选择教研室"
|
||||
style="width: 280px"
|
||||
:loading="summaryOfficeLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="o in officeOptions"
|
||||
:key="o.jysdh"
|
||||
:label="`${o.jysmc || o.jysdh}(${o.jysdh})`"
|
||||
:value="o.jysdh"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :disabled="!summaryJysdh" @click="loadOfficeSummary">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="summaryLoading" :data="summaryRows" border stripe max-height="460">
|
||||
<template slot="empty"><span>无数据!</span></template>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="rwmc" label="教学任务" min-width="180" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="nd" label="年度" width="90" align="center" />
|
||||
<el-table-column label="任务状态" width="100" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-tag :type="taskTagType(row.taskZt)" size="small">{{ taskStatusLabel(row.taskZt) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="zt" label="任务书状态" width="110" align="center" />
|
||||
<el-table-column prop="sbsj" label="上报时间" width="170" align="center" :formatter="fmtDateTime" />
|
||||
</el-table>
|
||||
<div slot="footer">
|
||||
<el-button @click="summaryVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -212,10 +281,13 @@ import {
|
||||
addTeachingTask,
|
||||
updateTeachingTask,
|
||||
deleteTeachingTask,
|
||||
batchDeleteTeachingTask,
|
||||
publishTeachingTask,
|
||||
endPublishTeachingTask
|
||||
} from '@/api/teachBusiness/teachingTask'
|
||||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||||
import { listOffice } from '@/api/teachOffice/office'
|
||||
import { taskBookOfficeSummary } from '@/api/teachBusiness/taskBook'
|
||||
import TaskBookFillDialog from './TaskBookFillDialog.vue'
|
||||
|
||||
export default {
|
||||
@@ -244,6 +316,7 @@ export default {
|
||||
// ==================== 2. 表格数据 ====================
|
||||
loading: false,
|
||||
tableData: [],
|
||||
selection: [],
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
@@ -267,7 +340,15 @@ export default {
|
||||
|
||||
// ==================== 5. 任务书填报(阶段 4) ====================
|
||||
taskBookVisible: false,
|
||||
taskBookRow: {}
|
||||
taskBookRow: {},
|
||||
|
||||
// ==================== 6. 教研室任务书汇总 ====================
|
||||
summaryVisible: false,
|
||||
summaryLoading: false,
|
||||
summaryOfficeLoading: false,
|
||||
summaryJysdh: '',
|
||||
officeOptions: [],
|
||||
summaryRows: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -452,6 +533,19 @@ export default {
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
/* ---------- 批量删除 ---------- */
|
||||
handleBatchDelete() {
|
||||
const bhList = this.selection.map(r => r.bh)
|
||||
if (!bhList.length) return
|
||||
this.$confirm(`确定要删除选中的 ${bhList.length} 个教学任务吗?删除将级联删除关联的教研室任务书。`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => batchDeleteTeachingTask(bhList)).then(() => {
|
||||
this.$message.success('批量删除成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
isTaskPublished(row) {
|
||||
return row && (row.zt === '发布' || row.zt === '已发布')
|
||||
@@ -483,11 +577,49 @@ export default {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => publishTeachingTask(row.bh)).then(() => {
|
||||
}).then(() => publishTeachingTask([row.bh])).then(() => {
|
||||
this.$message.success('发布成功')
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
/* ---------- 批量发布 ---------- */
|
||||
handleBatchPublish() {
|
||||
const bhList = this.selection.map(r => r.bh)
|
||||
if (!bhList.length) return
|
||||
this.$confirm(`确定要发布选中的 ${bhList.length} 个教学任务吗?未填写任务书的任务将被拒绝发布。`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => publishTeachingTask(bhList)).then(res => {
|
||||
this.$message.success(`成功发布 ${res.data || 0} 个教学任务`)
|
||||
this.fetchList()
|
||||
}).catch(() => {})
|
||||
},
|
||||
/* ---------- 教研室任务书汇总 ---------- */
|
||||
openOfficeSummary() {
|
||||
this.summaryVisible = true
|
||||
this.summaryRows = []
|
||||
if (!this.officeOptions.length) {
|
||||
this.summaryOfficeLoading = true
|
||||
listOffice({ pageNum: 1, pageSize: 500 }).then(res => {
|
||||
const data = res.data || {}
|
||||
this.officeOptions = data.records || data.rows || data.list || []
|
||||
}).catch(() => {}).finally(() => {
|
||||
this.summaryOfficeLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
loadOfficeSummary() {
|
||||
if (!this.summaryJysdh) return
|
||||
this.summaryLoading = true
|
||||
taskBookOfficeSummary(this.summaryJysdh).then(res => {
|
||||
this.summaryRows = res.data || []
|
||||
}).catch(() => {
|
||||
this.summaryRows = []
|
||||
}).finally(() => {
|
||||
this.summaryLoading = false
|
||||
})
|
||||
},
|
||||
handleEndPublish(row) {
|
||||
this.$confirm(`确定要结束发布教学任务「${row.rwmc || row.bh || '该记录'}」吗?结束后任务书和班次课程任务将只读。`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
|
||||
Reference in New Issue
Block a user