校历编辑

This commit is contained in:
2026-08-28 11:40:34 +08:00
parent 2183b33a7f
commit 7f853afff6
5 changed files with 127 additions and 31 deletions
@@ -91,7 +91,7 @@
<script>
import { getSemester } from '@/api/teachBusiness/semester'
import { listXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
import { listXqxlb, addXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
// 节次定义:12/34/56 常显,78/晚上/夜间由开关控制可见
const SLOT_DEFS = [
@@ -182,8 +182,11 @@ export default {
})
return cols
},
// xqxlb 的年度(nd)为年份(如 2026),由 6 位学期代号(如 202601)截取前 4 位得出
// xqxlb 的 nd 存的是「年份」,必须与某条记录的 jqsj 实际日期同一年,
// 按「学期代号前4位」取会与格子真实年月脱节(如 2027 春学期首周落在 2026 年)。
// 这里统一以「日历起点 calendarStart 所在年」作为年度口径。
xqxlbNd() {
if (this.calendarStart) return this.calendarStart.getFullYear()
return Number(String(this.nd).slice(0, 4))
}
},
@@ -260,16 +263,39 @@ export default {
})
},
// 从后端拉取本学期校历事件,渲染到对应时间格(年度由 6 位学期代号截取前 4 位)
// XQXLB 的 nd 存的是「年份」(如 2026),春/夏/秋三学期共用同一批年度记录;
// 后端 updateById 只能改已存在的 bh,所以每个格子必须有预生成的记录才能存。
// 若本学期日期区间 [kxrq, jsrq] 内还没有任何占位记录(首次进入该学期),
// 调用 /xqxlb/add 只按本学期 kxrq/jsrq 预生成这一张网格,再重新拉取,
// 这样后续 /xqxlb/update 的 updateById 才能命中每一格。(区间有覆盖则跳过,幂等)
loadXqxlbEvents() {
listXqxlb({ nd: this.xqxlbNd }).then(res => {
const data = res.data
const list = Array.isArray(data) ? data : (data && data.records) || []
return list
}).then(list => {
const loaded = {}
const fetchList = () => {
console.warn('[校历编辑] 查询nd=', this.xqxlbNd, '学期nd=', this.nd)
return listXqxlb({ nd: this.xqxlbNd }).then(res => {
const data = res.data
const arr = Array.isArray(data) ? data : (data && data.records) || []
console.warn('[校历编辑] 返回条数=', arr.length, '含事件名=', arr.filter(it => it.jqmc).map(it => ({ nd: it.nd, jqsj: it.jqsj, jqmc: it.jqmc, courseClass: it.courseClass })).slice(0, 10))
return arr
})
}
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 || '',
@@ -289,10 +315,27 @@ export default {
})
// 合并到现有格子(后端数据覆盖已有状态)
this.events = Object.assign({}, this.events, loaded)
}).catch(() => {
}
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(() => {
this.$message.warning('校历事件加载失败')
})
},
// 判断某条记录(jqsj 日期)是否落在本学期 [startDate, endDate] 区间内
isInRange(jqsj) {
if (!this.startDate || !this.endDate || !jqsj) return false
const d = new Date(String(jqsj).slice(0, 10).replace(/-/g, '/'))
if (isNaN(d.getTime())) return false
return d >= this.startDate && d <= this.endDate
},
// 根据假期记录反推时间格位置(jqsj 日期 + courseClass 节次),与列是否可见无关
locateXqxlb(item) {
if (!this.calendarStart || !item.jqsj) return null
@@ -485,7 +528,9 @@ export default {
keys.forEach(key => {
const prev = this.events[key]
this.$set(this.events, key, {
bh: prev ? prev.bh : undefined,
// 编辑已有事件:沿用后端返回的 bh;新增事件:bh 由 loadXqxlbEvents
// 通过 /xqxlb/add 预生成的占位行提供(首次进入学期时已落库)
bh: prev && prev.bh ? prev.bh : undefined,
name: name,
bold: this.toolbar.bold,
schedulable: this.toolbar.schedulable,
@@ -615,7 +660,9 @@ export default {
return {
delFlag: 0,
bh: ev.bh || undefined,
nd: this.xqxlbNd,
// nd 必须与 jqsj 同一年:取本条记录真实日期的年份,
// 不能沿用学派代号前4位(如 2027 春学期首周可能落在 2026 年,会存错年度、查不到)。
nd: d.getFullYear(),
jqsj: fmtDate(d),
jqmc: ev.name || '',
jc: !!ev.bold,
@@ -192,29 +192,58 @@ export default {
this.$message.warning('学期详情获取失败,请确认学期数据')
})
},
// 从后端拉取本学期校历事件,渲染到对应时间格(年度为 6 位学期代号截取前 4 位)
// 从后端拉取校历事件,渲染到对应时间格
// xqxlb.nd 存的是记录真实日期的年份,不是学期代号前4位(避免跨年度学期查询出错)
loadXqxlbEvents() {
listXqxlb({ nd: String(this.nd).slice(0, 4) }).then(res => {
const data = res.data
const list = Array.isArray(data) ? data : (data && data.records) || []
if (!this.calendarStart) return
// 学期校历从 calendarStart 到 endDate,查询覆盖整个跨度的全部年份
const startYear = this.calendarStart.getFullYear()
const endYear = (this.endDate || this.calendarStart).getFullYear()
// 一次性拉取覆盖起止年份的所有记录,由前端过滤
const requests = []
const range = []
for (let y = startYear; y <= endYear; y++) {
range.push(y)
requests.push(listXqxlb({ nd: y }))
}
console.warn('[校历只读] 查询nd范围=', range, 'calendarStart=', fmtDate(this.calendarStart), 'endDate=', this.endDate ? fmtDate(this.endDate) : null)
Promise.all(requests).then(responses => {
const list = []
responses.forEach(res => {
const data = res.data
const part = Array.isArray(data) ? data : (data && data.records) || []
list.push(...part)
})
console.warn('[校历只读] listXqxlb 返回记录条数=', list.length, 'nd字段分布=', list.reduce((m, it) => { const k = String(it.nd); m[k] = (m[k] || 0) + 1; return m }, {}), '样例=', list.slice(0, 3).map(it => ({ nd: it.nd, jqsj: it.jqsj, jqmc: it.jqmc, courseClass: it.courseClass })))
const loaded = {}
let matched = 0
// 23694 条里同一格可能有多条(add() 去重失效累积的重复占位)。
// 按格子去重:同一格优先保留「有事件名(jqmc非空)」的那条,避免空占位把事件盖成空白。
const unique = {}
list.forEach(item => {
const pos = this.locateXqxlb(item)
if (pos) {
loaded[pos.key] = {
bh: item.bh,
name: item.jqmc || '',
schedulable: !!item.kpk,
mainCourse: !!item.zdpk,
remarkShow: !!item.bzxs,
remark: item.bz || ''
}
if (!pos) return
const prev = unique[pos.key]
const has = !!item.jqmc
if (!prev || (has && !prev.has)) {
unique[pos.key] = { has: has, item: item }
}
})
Object.keys(unique).forEach(key => {
const { item } = unique[key]
loaded[key] = {
bh: item.bh,
name: item.jqmc || '',
schedulable: !!item.kpk,
mainCourse: !!item.zdpk,
remarkShow: !!item.bzxs,
remark: item.bz || ''
}
if (item.jqmc) matched++
})
console.warn('[校历只读] 定位到的事件条数=', matched, 'events=', Object.keys(loaded).length, loaded)
this.events = loaded
}).catch(() => {
this.$message.warning('校历事件加载失败')
})
}).catch(e => { console.error('[校历只读] 拉取失败', e) })
},
// 根据假期记录反推时间格 keyjqsj 日期 + courseClass 节次)
locateXqxlb(item) {
@@ -2,6 +2,7 @@
<div class="teach-calendar app-container">
<school-calendar-editor
v-if="nd"
:key="nd"
:nd="nd"
:semester-name="semesterName"
@close="handleClose"
@@ -26,6 +27,16 @@ export default {
this.nd = this.$route.query.nd || ''
this.semesterName = this.$route.query.name || ''
},
watch: {
// 编辑校历页被 keep-alive 缓存复用:同路径下 query.nd 变化时不会重新走 created()
// 需在此同步到 nd,并通过 :key="nd" 让编辑器随学期切换全新挂载,读取正确学期数据。
'$route.query.nd'(val) {
if (val && String(val) !== String(this.nd)) {
this.nd = val
this.semesterName = this.$route.query.name || ''
}
}
},
methods: {
handleClose() {
this.$router.replace({ path: '/teachBusiness/semester' })
@@ -43,18 +43,18 @@
<span>{{ formatDate(scope.row.jsrq) }}</span>
</template>
</el-table-column>
<el-table-column label="周数" align="center" prop="sdzs" width="100" />
<el-table-column label="调课不审批" align="center" width="120">
<el-table-column label="周数" align="center" prop="sdzs" width="55" />
<el-table-column label="调课不审批" align="center" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.tkbsp ? 'primary' : 'info'" size="mini">{{ scope.row.tkbsp ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="禁止调课" align="center" width="120">
<el-table-column label="禁止调课" align="center" width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.jztk ? 'danger' : 'info'" size="mini">{{ scope.row.jztk ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="终结成绩定及格" align="center" width="160">
<el-table-column label="终结成绩定及格" align="center" width="130">
<template slot-scope="scope">
<el-tag :type="scope.row.zjcjdjg ? 'primary' : 'info'" size="mini">{{ scope.row.zjcjdjg ? '是' : '否'
}}</el-tag>