1、正课与学期关系关联;

2、天假删除保护;
3、教学任务计划增加了「结束发布」。已发布或库里的「已发布」都可以结束;结束后写入结束时间,任务书和班次课程任务只读
4、班次学期操作内容调整修改;
This commit is contained in:
2026-09-14 16:30:52 +08:00
parent 30f2182453
commit 52a04dbf60
50 changed files with 2939 additions and 293 deletions
@@ -17,7 +17,7 @@
<div class="sce-panel">
<div class="sce-panel-row">
<span class="sce-label">事件名称:</span>
<el-input v-model="toolbar.eventName" size="small" class="sce-event-input" placeholder="请输入校历事件名称" />
<el-input v-model="toolbar.eventName" size="small" class="sce-event-input" :placeholder="mode === 'class' ? '请输入班历事件名称' : '请输入校历事件名称'" />
<el-checkbox v-model="toolbar.bold" class="sce-cb">加粗显示</el-checkbox>
<el-checkbox v-model="toolbar.schedulable" class="sce-cb">可排课</el-checkbox>
<el-checkbox v-model="toolbar.mainCourse" class="sce-cb">正课</el-checkbox>
@@ -92,6 +92,7 @@
<script>
import { getSemester } from '@/api/teachBusiness/semester'
import { listXqxlb, addXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
import { listClassCalendarEvent, updateClassCalendarEvent } from '@/api/teachOffice/classCalendarEvent'
// 节次定义:12/34/56 常显,78/晚上/夜间由开关控制可见
const SLOT_DEFS = [
@@ -137,7 +138,12 @@ export default {
name: 'SchoolCalendarEditor',
props: {
nd: { type: [String, Number], default: '' },
semesterName: { type: String, default: '' }
semesterName: { type: String, default: '' },
mode: { type: String, default: 'school' },
xydxqbh: { type: String, default: '' },
xydbh: { type: String, default: '' },
rangeStart: { type: String, default: '' },
rangeEnd: { type: String, default: '' }
},
data() {
return {
@@ -202,7 +208,13 @@ export default {
nd: {
immediate: true,
handler(val) {
if (val) this.loadSemester()
if (this.mode !== 'class' && val) this.loadSemester()
}
},
xydxqbh: {
immediate: true,
handler(val) {
if (this.mode === 'class' && val) this.loadClassCalendar()
}
},
visibleColumns(newCols, oldCols) {
@@ -251,6 +263,28 @@ export default {
},
methods: {
/* ---------- 数据加载 ---------- */
loadClassCalendar() {
const start = (this.rangeStart || '').slice(0, 10)
const end = (this.rangeEnd || '').slice(0, 10)
if (!start || !end) {
this.$message.warning('班次学期缺少开学或结束日期')
return
}
this.startDate = new Date(start.replace(/-/g, '/'))
this.endDate = new Date(end.replace(/-/g, '/'))
this.calendarStart = null
this.buildWeeks()
this.loadClassEvents()
},
loadClassEvents() {
listClassCalendarEvent(this.xydxqbh).then(res => {
const data = res.data
const list = Array.isArray(data) ? data : (data && data.records) || []
this.renderEventList(list)
}).catch(() => {
this.$message.warning('班历加载失败')
})
},
loadSemester() {
if (!this.nd) return
getSemester(this.nd)
@@ -281,57 +315,53 @@ export default {
const data = res.data
return Array.isArray(data) ? data : (data && data.records) || []
})
const render = (list) => {
// 后端 add() 的按「月日」去重对已存的全量日期不生效,同一年度多次打开/刷新会
// 积累同一格的重复占位记录(各带不同 bh)。updateById 只改其中一行,其余空记录
// 会在重新拉取时把刚保存的事件名覆盖掉。故这里按「周-星期-节次」先去重:
// 同一格保留有事件名(jqmc非空)的一条;都没有则保留最后一条兜底(保证有 bh)。
const unique = {}
list.forEach(item => {
const pos = this.locateXqxlb(item)
if (!pos) return
const stable = this.cellStableKey(pos.wIdx, pos.dayIndex, pos.slotKey)
const prev = unique[stable]
if (!prev || (item.jqmc && !prev.item.jqmc)) {
unique[stable] = { item: item, pos: pos }
}
})
const loaded = {}
Object.keys(unique).forEach(stable => {
const { item, pos } = unique[stable]
const ev = {
bh: item.bh,
name: item.jqmc || '',
bold: !!item.jc,
schedulable: !!item.kpk,
mainCourse: !!item.zdpk,
remarkShow: !!item.bzxs,
remark: item.bz || ''
}
const colIndex = this.colIndexOf(pos.dayIndex, pos.slotKey)
if (colIndex >= 0) {
loaded[this.cellKey(pos.wIdx, colIndex)] = ev
} else {
// 该节次列当前隐藏:暂存(含 bh),待列显示时由 visibleColumns 监听恢复
this.hiddenEvents[this.cellStableKey(pos.wIdx, pos.dayIndex, pos.slotKey)] = ev
}
})
// 合并到现有格子(后端数据覆盖已有状态)
this.events = Object.assign({}, this.events, loaded)
}
fetchList().then(list => {
const hasCoverage = list.some(item => this.isInRange(item.jqsj))
if (!hasCoverage && this.startDate && this.endDate) {
// 本学期区间尚无占位记录:仅用本期 kxrq/jsrq 预生成整张网格
const startStr = fmtDate(this.startDate)
const endStr = fmtDate(this.endDate)
return addXqxlb(startStr, endStr).then(() => fetchList())
}
return list
}).then(render).catch(() => {
}).then(list => {
this.renderEventList(list)
}).catch(() => {
this.$message.warning('校历事件加载失败')
})
},
renderEventList(list) {
const unique = {}
list.forEach(item => {
const pos = this.locateXqxlb(item)
if (!pos) return
const stable = this.cellStableKey(pos.wIdx, pos.dayIndex, pos.slotKey)
const prev = unique[stable]
if (!prev || (item.jqmc && !prev.item.jqmc)) {
unique[stable] = { item: item, pos: pos }
}
})
const loaded = {}
Object.keys(unique).forEach(stable => {
const { item, pos } = unique[stable]
const ev = {
bh: item.bh,
name: item.jqmc || '',
bold: !!item.jc,
schedulable: !!item.kpk,
autoSchedule: !!item.zdpk,
mainCourse: !!item.zk,
remarkShow: !!item.bzxs,
remark: item.bz || ''
}
const colIndex = this.colIndexOf(pos.dayIndex, pos.slotKey)
if (colIndex >= 0) {
loaded[this.cellKey(pos.wIdx, colIndex)] = ev
} else {
this.hiddenEvents[this.cellStableKey(pos.wIdx, pos.dayIndex, pos.slotKey)] = ev
}
})
this.events = loaded
},
// 判断某条记录(jqsj 日期)是否落在本学期 [startDate, endDate] 区间内
isInRange(jqsj) {
if (!this.startDate || !this.endDate || !jqsj) return false
@@ -537,6 +567,7 @@ export default {
name: name,
bold: this.toolbar.bold,
schedulable: this.toolbar.schedulable,
autoSchedule: !!(prev && prev.autoSchedule),
mainCourse: this.toolbar.mainCourse,
remarkShow: this.toolbar.remarkShow,
remark: this.toolbar.remark
@@ -601,9 +632,19 @@ export default {
this.absorbPreview = ''
this.events = {}
this.hiddenEvents = {}
this.loadXqxlbEvents()
if (this.mode === 'class') this.loadClassEvents()
else this.loadXqxlbEvents()
this.$message.info('已刷新')
},
reloadEvents() {
this.events = {}
this.hiddenEvents = {}
if (this.mode === 'class') this.loadClassEvents()
else this.loadXqxlbEvents()
},
savePayload(payload) {
return this.mode === 'class' ? updateClassCalendarEvent(payload) : updateXqxlb(payload)
},
/* ---------- 后端持久化(xqxlb 接口) ---------- */
// 批量保存选中格事件,返回各格子保存成功/失败的个数
persistEvents(keys) {
@@ -612,11 +653,8 @@ export default {
.map(key => {
const payload = this.buildXqxlbPayload(key, this.events[key])
if (!payload) return Promise.resolve(false)
return updateXqxlb(payload)
.then(() => {
console.warn('[设定] key=', key, 'bh来源=', this.events[key] && this.events[key].bh ? '已有' : '生成', 'bh=', payload.bh, 'nd=', payload.nd, 'name=', payload.jqmc)
return true
})
return this.savePayload(payload)
.then(() => true)
.catch(err => {
console.warn('[设定] key=', key, '保存失败', err && (err.message || err))
return false
@@ -630,8 +668,7 @@ export default {
} else if (ok > 0) {
this.$message.success('已设定 ' + ok + ' 个时间格')
}
// 保存成功后重新拉取,回写各记录主键,避免重复新增
this.loadXqxlbEvents()
this.reloadEvents()
return { ok: ok, fail: fail }
})
},
@@ -643,7 +680,7 @@ export default {
const payload = this.buildXqxlbPayload(key, this.events[key])
if (!payload) return Promise.resolve(false)
payload.jqmc = ''
return updateXqxlb(payload)
return this.savePayload(payload)
.then(() => true)
.catch(() => false)
})
@@ -655,8 +692,7 @@ export default {
} else if (ok > 0) {
this.$message.success('已删除 ' + ok + ' 个时间格事件')
}
// 删除成功后重新拉取,同步后端状态
this.loadXqxlbEvents()
this.reloadEvents()
return { ok: ok, fail: fail }
})
},
@@ -678,10 +714,18 @@ export default {
bz: ev.remark || null,
kpk: !!ev.schedulable,
bzxs: !!ev.remarkShow,
zdpk: !!ev.mainCourse,
courseClass: SLOT_COURSE_MAP[pos.slotKey] || null
zdpk: !!ev.autoSchedule,
zk: !!ev.mainCourse,
courseClass: SLOT_COURSE_MAP[pos.slotKey] || null,
xydxqbh: this.mode === 'class' ? this.xydxqbh : undefined,
xydbh: this.mode === 'class' ? this.xydbh : undefined
}
},
selectedEventBhs() {
return this.selectedKeys
.map(key => this.events[key] && this.events[key].bh)
.filter(Boolean)
},
// 时间格 key -> { wIdx, dayIndex, slotKey }
parseCellKey(key) {
const m = key.match(/^w(\d+)-c(\d+)$/)
@@ -694,7 +738,9 @@ export default {
/* ---------- 返回学期管理 ---------- */
handleClose() {
this.$emit('close')
this.$router.replace({ path: '/teachBusiness/semester' })
if (this.mode !== 'class') {
this.$router.replace({ path: '/teachBusiness/semester' })
}
}
}
}