diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/jys/ElectiveCourseSaveDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/jys/ElectiveCourseSaveDTO.java index 69fbcc10c..6fa85a96d 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/jys/ElectiveCourseSaveDTO.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/jys/ElectiveCourseSaveDTO.java @@ -35,6 +35,9 @@ public class ElectiveCourseSaveDTO { /** 教室编号 */ private String jsbh; + /** 教材名称(按名称匹配教材信息,写入课程教材关联) */ + private String jcmc; + /** 计划人数 */ private Integer rs; diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCJCMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCJCMapper.java new file mode 100644 index 000000000..4c1affd73 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCJCMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KCJC; +import org.apache.ibatis.annotations.Mapper; + +/** + * 课程教材 Mapper + */ +@Mapper +public interface KCJCMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveCourseServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveCourseServiceImpl.java index 37f0a85e4..bb5c41e5b 100644 --- a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveCourseServiceImpl.java +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveCourseServiceImpl.java @@ -10,9 +10,11 @@ import com.roomroot.common.utils.poi.ExcelUtil; import com.roomroot.jwgl.dto.jys.ElectiveCourseImportDTO; import com.roomroot.jwgl.dto.jys.ElectiveCourseQuery; import com.roomroot.jwgl.dto.jys.ElectiveCourseSaveDTO; +import com.roomroot.jwgl.entity.JCXX; import com.roomroot.jwgl.entity.JSB; import com.roomroot.jwgl.entity.JYB; import com.roomroot.jwgl.entity.KB; +import com.roomroot.jwgl.entity.KCJC; import com.roomroot.jwgl.entity.XYDB; import com.roomroot.jwgl.entity.XYDNDXQJBXXB; import com.roomroot.jwgl.entity.XYDQB; @@ -23,7 +25,9 @@ import com.roomroot.jwgl.mapper.ClassSemesterMapper; import com.roomroot.jwgl.mapper.ElectiveCourseMapper; import com.roomroot.jwgl.mapper.JYBMapper; import com.roomroot.jwgl.mapper.KBMapper; +import com.roomroot.jwgl.mapper.KCJCMapper; import com.roomroot.jwgl.mapper.StudentTeamTaskMapper; +import com.roomroot.jwgl.mapper.TeachingMaterialMapper; import com.roomroot.jwgl.mapper.XYDBMapper; import com.roomroot.jwgl.mapper.XYDQBMapper; import com.roomroot.jwgl.mapper.ZYBMapper; @@ -93,6 +97,10 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService { private ZYBMapper zybMapper; @Resource private ClassSemesterMapper classSemesterMapper; + @Resource + private KCJCMapper kcjcMapper; + @Resource + private TeachingMaterialMapper teachingMaterialMapper; @Resource private TeamCodeUtil teamCodeUtil; @@ -115,6 +123,8 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService { throw new ServiceException("选修课信息不能为空", BAD_REQUEST); } KB course = requireCourse(dto.getKbh()); + // 先校验教材存在性,避免先建班/学期后失败回滚留下副作用 + linkCourseTextbook(course.getKbh(), dto.getJcmc()); String xydbh = resolveTeamId(dto.getXydbh(), dto.getXxbmc(), dto.getNj(), true, dto.getRs()); XYDRWB task = new XYDRWB(); task.setBh(UuidUtil.getOriginalUUID()); @@ -259,6 +269,11 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService { } jsbh = room.getJsbh(); } + try { + linkCourseTextbook(course.getKbh(), row.getJcmc()); + } catch (ServiceException e) { + throw new ServiceException("第" + line + "行" + e.getMessage(), BAD_REQUEST); + } String xydbh = resolveTeamId(null, row.getXxbmc().trim(), row.getKxbcnj(), true, row.getJhrs()); XYDRWB task = new XYDRWB(); task.setBh(UuidUtil.getOriginalUUID()); @@ -656,6 +671,34 @@ public class ElectiveCourseServiceImpl implements ElectiveCourseService { return course; } + /** + * 按教材名称把教材挂到课程(课程教材表)。教材按「名称」精确匹配, + * 同一课程同一教材的关联已存在则跳过,不覆盖课程已有教材。 + */ + private void linkCourseTextbook(String kbh, String jcmc) { + if (StringUtils.isEmpty(jcmc) || StringUtils.isEmpty(kbh)) { + return; + } + JCXX material = teachingMaterialMapper.selectOne(new LambdaQueryWrapper() + .eq(JCXX::getMc, jcmc.trim()) + .last("LIMIT 1")); + if (material == null) { + throw new ServiceException("教材不存在:" + jcmc, NOT_FOUND); + } + Long exists = kcjcMapper.selectCount(new LambdaQueryWrapper() + .eq(KCJC::getKbh, kbh) + .eq(KCJC::getJcbh, material.getBh())); + if (exists != null && exists > 0) { + return; + } + KCJC link = new KCJC(); + link.setBh(UuidUtil.getOriginalUUID()); + link.setKbh(kbh); + link.setJcbh(material.getBh()); + link.setTy(0); + kcjcMapper.insert(link); + } + private void updateTeamDates(String xydbh, LocalDateTime start, LocalDateTime end) { if (start == null && end == null) { return; diff --git a/frontend/src/views/teachOffice/elective/index.vue b/frontend/src/views/teachOffice/elective/index.vue index 9c6bfe8c7..646bcd0c7 100644 --- a/frontend/src/views/teachOffice/elective/index.vue +++ b/frontend/src/views/teachOffice/elective/index.vue @@ -149,13 +149,57 @@ - + + + - + + + - + + + @@ -191,7 +235,8 @@ import { } from '@/api/teachOffice/elective' import { listKb } from '@/api/teachOffice/kb' import { listTeacher } from '@/api/teachOffice/teacher' -import { listClassroom } from '@/api/teachBusiness/classroom' +import { listAllClassroom } from '@/api/teachBusiness/classroom' +import { listTeachingMaterial } from '@/api/textbook/teachingMaterial' import { saveAs } from 'file-saver' export default { @@ -235,10 +280,18 @@ export default { xf: 0, jh: 0 }, + // ==================== 新建弹窗下拉选项 ==================== + teacherOptions: [], + teacherLoading: false, + classroomOptions: [], + classroomLoading: false, + materialOptions: [], + materialLoading: false, + rules: { xxbmc: [{ required: true, message: '请输入选修班名称', trigger: 'blur' }], kcmc: [{ required: true, message: '请输入课程名称', trigger: 'blur' }], - js: [{ required: true, message: '请输入实施教员', trigger: 'blur' }] + js: [{ required: true, message: '请选择实施教员', trigger: 'change' }] }, // ==================== 分页 ==================== @@ -401,10 +454,50 @@ export default { jh: 0 } this.dialogVisible = true + this.loadTeacherOptions() + this.loadClassroomOptions() + this.loadMaterialOptions() this.$nextTick(() => { this.$refs.formRef && this.$refs.formRef.clearValidate() }) }, + loadTeacherOptions() { + if (this.teacherOptions.length || this.teacherLoading) return + this.teacherLoading = true + listTeacher({ pageNum: 1, pageSize: 500 }).then(res => { + const records = (res && res.data && res.data.records) || [] + this.teacherOptions = records.filter(t => t.jybh && !t.lzzt) + }).catch(() => { + this.teacherOptions = [] + }).finally(() => { + this.teacherLoading = false + }) + }, + loadClassroomOptions() { + if (this.classroomOptions.length || this.classroomLoading) return + this.classroomLoading = true + listAllClassroom().then(res => { + this.classroomOptions = (res && res.data ? res.data : []) + .filter(r => r.jsbh && !(r.ty === true || r.ty === 1 || r.ty === '1' || r.ty === 'true')) + .sort((a, b) => String(a.jsbh).localeCompare(String(b.jsbh), 'zh-CN')) + }).catch(() => { + this.classroomOptions = [] + }).finally(() => { + this.classroomLoading = false + }) + }, + loadMaterialOptions() { + if (this.materialOptions.length || this.materialLoading) return + this.materialLoading = true + listTeachingMaterial({ pageNum: 1, pageSize: 500 }).then(res => { + const records = (res && res.data && res.data.records) || [] + this.materialOptions = records.filter(m => m.mc && !(m.ty === true || m.ty === 1 || m.ty === '1' || m.ty === 'true')) + }).catch(() => { + this.materialOptions = [] + }).finally(() => { + this.materialLoading = false + }) + }, handleNewConfirm() { if (!this.$refs.formRef) return this.$refs.formRef.validate((valid) => { @@ -413,16 +506,9 @@ export default { }) }, submitNewElective() { - const emptyPage = { data: { records: [] } } - Promise.all([ - listKb({ pageNum: 1, pageSize: 1, kmc: this.form.kcmc }), - listTeacher({ pageNum: 1, pageSize: 1, jyxm: this.form.js }), - this.form.jxcd ? listClassroom({ pageNum: 1, pageSize: 1, jsmc: this.form.jxcd }) : Promise.resolve(emptyPage) - ]) - .then(([kbRes, teacherRes, roomRes]) => { + listKb({ pageNum: 1, pageSize: 1, kmc: this.form.kcmc }) + .then(kbRes => { const kbh = kbRes?.data?.records?.[0]?.kbh - const jybh = teacherRes?.data?.records?.[0]?.jybh - const jsbh = roomRes?.data?.records?.[0]?.jsbh if (!kbh) { this.$message.warning(`未找到课程科目:${this.form.kcmc}`) return @@ -431,8 +517,9 @@ export default { kbh: kbh, xxbmc: this.form.xxbmc, nj: this.form.nj || undefined, - jybh: jybh || undefined, - jsbh: jsbh || undefined, + jybh: this.form.js || undefined, + jsbh: this.form.jxcd || undefined, + jcmc: this.form.jc || undefined, rs: this.form.jh || undefined, xs: this.form.jhsxs || undefined, xf: this.form.xf || undefined @@ -486,6 +573,10 @@ export default { } } +.w-full { + width: 100%; +} + // ==================== 6. 数据表格区域 ==================== .table-card { .list-header {