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
@@ -1,6 +1,6 @@
import request from '@/utils/request'
// ======================== 班历管理(实体 XYDRWB 学员队任务表) ========================
// ======================== 班次教学任务(实体 XYDRWB 学员队任务表,不是班历 ========================
// 接口依据:api2.txt「班次学期日历(班历)」分组 + StudentTeamTaskController
// 每个班次学期(xydxqbh)下挂若干条课程任务记录:
// bh 编号、nd 年度、xydbh 学员队编号、kbh 课编号、xqdc 学期第次、jybh 教员编号、kcxh 课次序号、
@@ -103,3 +103,59 @@ export function autoGenerateRequiredCourses(xydbh, xqdc) {
params: { xydbh: xydbh, xqdc: xqdc }
})
}
export function batchAutoGenerateTasks(xydxqbhList) {
return request({
url: '/studentTeamTask/batchAutoGenerate',
method: 'post',
data: xydxqbhList
})
}
export function listPlanCourses(xydxqbh, otherOnly) {
return request({
url: '/studentTeamTask/planCourses',
method: 'get',
params: { xydxqbh, otherOnly }
})
}
export function addTaskFromLibrary(xydxqbh, kbh) {
return request({
url: '/studentTeamTask/addFromLibrary',
method: 'post',
params: { xydxqbh, kbh }
})
}
export function addTaskFromPlan(xydxqbh, planBh) {
return request({
url: '/studentTeamTask/addFromPlan',
method: 'post',
params: { xydxqbh, planBh }
})
}
export function syncTaskFromPlan(bhList) {
return request({
url: '/studentTeamTask/syncFromPlan',
method: 'post',
data: bhList
})
}
export function batchDeleteTasks(bhList) {
return request({
url: '/studentTeamTask/batchDelete',
method: 'post',
data: bhList
})
}
export function pasteTasks(bhList, targetXydxqbh) {
return request({
url: '/studentTeamTask/paste',
method: 'post',
data: { bhList, targetXydxqbh }
})
}
+70 -1
View File
@@ -52,7 +52,7 @@ export function updateSemester(data) {
})
}
// 删除班次学期(逻辑删除并级联删除其班历记录
// 删除班次学期(逻辑删除;已有课程任务时后端拒绝
export function delSemester(bh) {
return request({
url: '/student-records/semester/delete',
@@ -115,3 +115,72 @@ export function batchAddFromElective(params) {
params: params
})
}
// 未毕业、且该年度学期尚未建立班次学期的班次
export function listCreatableClasses(params) {
return request({
url: '/student-records/semester/creatableClasses',
method: 'get',
params: params
})
}
// 按学校学期带出开学/结束日期
export function presetSemesterDates(params) {
return request({
url: '/student-records/semester/presetDates',
method: 'get',
params: params
})
}
// 为符合条件的全部班次创建班次学期,返回实际创建数
export function batchCreateSemester(data) {
return request({
url: '/student-records/semester/batchCreate',
method: 'post',
data: data
})
}
// 学期级预设合班,只改预设编班号
export function batchWaveMerge(data) {
return request({
url: '/student-records/semester/batchWaveMerge',
method: 'post',
data: data
})
}
// 按学制带出学期第次、日期、专用教室。xqdc 为窗口已选学期第次
export function initClassSemester(params) {
return request({
url: '/elective/semester/init',
method: 'get',
params: params
})
}
export function listSemesterBuildings() {
return request({
url: '/elective/semester/buildings',
method: 'get'
})
}
export function listSemesterRooms(params) {
return request({
url: '/elective/semester/rooms',
method: 'get',
params: params
})
}
// 学期级预设拆班,所选班次各自独立成波
export function batchWaveSplit(data) {
return request({
url: '/student-records/semester/batchWaveSplit',
method: 'post',
data: data
})
}
@@ -88,3 +88,15 @@ export function publishTeachingTask(bh) {
params: { bh }
})
}
/**
* 结束发布。仅已发布任务可结束,结束后任务书与班次课程任务只读。
* POST /teachingTask/endPublish?bh=
*/
export function endPublishTeachingTask(bh) {
return request({
url: '/teachingTask/endPublish',
method: 'post',
params: { bh }
})
}
@@ -0,0 +1,25 @@
import request from '@/utils/request'
export function listClassCalendarEvent(xydxqbh) {
return request({
url: '/class-calendar/list',
method: 'get',
params: { xydxqbh }
})
}
export function updateClassCalendarEvent(data) {
return request({
url: '/class-calendar/update',
method: 'post',
data
})
}
export function applyClassCalendarEvent(data) {
return request({
url: '/class-calendar/apply',
method: 'post',
data
})
}
@@ -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' })
}
}
}
}
@@ -218,7 +218,7 @@ export default {
bh: item.bh,
name: item.jqmc || '',
schedulable: !!item.kpk,
mainCourse: !!item.zdpk,
mainCourse: !!item.zk,
remarkShow: !!item.bzxs,
remark: item.bz || ''
}
@@ -397,7 +397,7 @@ export default {
ksxsfabh: form.ksxsfabh || null,
sdzs: form.sdzs,
sdjldw: lockValue === '' ? '' : lockValue + form.lockUnit,
xh: form.xh,
xh: Number(String(form.xq || '').slice(-1)) || 0,
jsonzd: "{'1':1}",
kstjsj: null
}
@@ -1,7 +1,7 @@
<template>
<el-dialog
:visible="dialogVisible"
:title="`班历管理——${semesterTitle}`"
:title="`班次教学任务——${semesterTitle}`"
width="1200px"
top="6vh"
:close-on-click-modal="false"
@@ -17,19 +17,23 @@
</div>
<div class="date-range-row">
<div class="range-item">学期日期{{ fmtDate(safeSemester.kxrq) }} {{ fmtDate(safeSemester.jsrq) }} {{ semesterWeeks }} </div>
<div class="range-item">班历记录{{ records.length }} 总学时 {{ summary.totalXs }}总学分 {{ summary.totalXf }}</div>
<div class="range-item">课程任务{{ records.length }} 总学时 {{ summary.totalXs }}总学分 {{ summary.totalXf }}</div>
</div>
</div>
<!-- ==================== 2. 操作按钮区域 ==================== -->
<div class="settings-panel">
<div class="action-buttons-row">
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">新增记录</el-button>
<el-button type="primary" size="small" icon="el-icon-plus" @click="handleAdd">新增课程任务</el-button>
<el-button size="small" @click="openLibraryPicker">计划外新添</el-button>
<el-button size="small" @click="openPlanPicker">计划内必修添加</el-button>
<el-button size="small" @click="handleAutoGenerate">自动生成必修课程</el-button>
<el-button size="small" @click="handlePresetMerge">预设合班</el-button>
<el-button size="small" @click="handlePresetSplit">预设拆班</el-button>
<el-button size="small" :disabled="!selection.length" @click="handleCopySelected">复制所选</el-button>
<el-button size="small" :disabled="!clipboardCount" @click="handlePaste">粘贴到其它班次</el-button>
<el-button size="small" :disabled="!selection.length" @click="handleSyncPlan">同步人培</el-button>
<el-button size="small" type="danger" plain :disabled="!selection.length" @click="handleBatchDelete">批量删除</el-button>
<el-button size="small" @click="handleApplyRegion">选定区域应用于其它班次</el-button>
<el-button size="small" @click="handleApplyWhole">整个班应用于其它班次</el-button>
<el-button size="small" @click="handleApplyWhole">整个班次教学任务应用于其它班次</el-button>
<el-button size="small" icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
</div>
</div>
@@ -44,6 +48,7 @@
size="small"
max-height="480"
class="calendar-table"
:row-class-name="rowClassName"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="40" align="center" />
@@ -63,6 +68,11 @@
<el-table-column prop="rs" label="人数" width="60" align="center" show-overflow-tooltip />
<el-table-column prop="ksbh" label="考试编号" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="ksdd" label="考试地点" width="110" align="center" show-overflow-tooltip />
<el-table-column label="人培差异" width="90" align="center">
<template slot-scope="{ row }">
<el-tag v-if="isPlanMismatch(row)" type="danger" size="mini">不一致</el-tag>
</template>
</el-table-column>
<el-table-column prop="bz" label="备注" min-width="120" align="left" header-align="center" show-overflow-tooltip />
<el-table-column label="操作" width="120" align="center" fixed="right">
<template slot-scope="{ row }">
@@ -72,7 +82,7 @@
</el-table-column>
</el-table>
<div class="select-tip">
提示勾选多条记录后可进行 预设合班 / 预设拆班 / 选定区域应用于其它班次 操作删除为物理删除请谨慎操作
提示粉红行为与人培不一致可用同步人培按人培覆盖已发布到任务书或已排课的行不能删除复制后粘贴会生成独立任务
</div>
</div>
</div>
@@ -304,6 +314,55 @@
</div>
</el-dialog>
<el-dialog
:visible="libraryVisible"
title="从课程库选择"
width="760px"
append-to-body
:close-on-click-modal="false"
@update:visible="val => libraryVisible = val"
>
<el-input v-model="libraryKeyword" size="small" placeholder="简称 / 课程名称" clearable style="width: 240px; margin-bottom: 8px" @keyup.enter.native="loadLibrary" />
<el-button size="small" type="primary" @click="loadLibrary">查询</el-button>
<el-table v-loading="pickerLoading" :data="libraryRows" border size="small" max-height="360" highlight-current-row @current-change="row => libraryCurrent = row">
<el-table-column prop="kbh" label="课编号" width="140" show-overflow-tooltip />
<el-table-column prop="jc" label="简称" width="120" show-overflow-tooltip />
<el-table-column prop="kmc" label="课程名称" min-width="160" show-overflow-tooltip />
<el-table-column prop="xs" label="学时" width="70" align="center" />
<el-table-column prop="klx" label="课类型" width="90" show-overflow-tooltip />
</el-table>
<div slot="footer">
<el-button size="small" @click="libraryVisible = false">取消</el-button>
<el-button size="small" type="primary" :disabled="!libraryCurrent" @click="confirmLibrary">加入任务</el-button>
</div>
</el-dialog>
<el-dialog
:visible="planVisible"
title="其它学期人培课程"
width="760px"
append-to-body
:close-on-click-modal="false"
@update:visible="val => planVisible = val"
>
<el-table v-loading="pickerLoading" :data="planRows" border size="small" max-height="360" highlight-current-row @current-change="row => planCurrent = row">
<el-table-column prop="jc" label="简称" width="120" show-overflow-tooltip />
<el-table-column prop="kbh" label="课编号" width="140" show-overflow-tooltip />
<el-table-column prop="xqdc" label="学期第次" width="90" align="center" />
<el-table-column prop="xs" label="学时" width="70" align="center" />
<el-table-column prop="klx" label="课类型" width="90" show-overflow-tooltip />
<el-table-column label="完成情况" width="90" align="center">
<template slot-scope="{ row }">
<el-tag :type="row.completed ? 'success' : 'info'" size="mini">{{ row.completed ? '已完成' : '未完成' }}</el-tag>
</template>
</el-table-column>
</el-table>
<div slot="footer">
<el-button size="small" @click="planVisible = false">取消</el-button>
<el-button size="small" type="primary" :disabled="!planCurrent || planCurrent.completed" @click="confirmPlan">加入当前学期</el-button>
</div>
</el-dialog>
<!-- ==================== 目标班次学期选取弹窗 ==================== -->
<ClassTeamSelectDialog
:visible="teamSelectVisible"
@@ -325,10 +384,17 @@ import {
batchCopyCalendarBySemester,
addTeamTask,
delTeamTask,
batchPresetMerge,
batchPresetSplit,
autoGenerateRequiredCourses
autoGenerateRequiredCourses,
listPlanCourses,
addTaskFromLibrary,
addTaskFromPlan,
syncTaskFromPlan,
batchDeleteTasks,
pasteTasks
} from '@/api/studentRecords/classCalendar'
import { listKb } from '@/api/teachOffice/kb'
const TASK_CLIPBOARD_KEY = 'education.classTaskClipboard'
export default {
name: 'ClassCalendarDialog',
@@ -358,7 +424,17 @@ export default {
// ==================== 复制到其它班次 ====================
teamSelectVisible: false,
applyMode: 'region',
copyNote: ''
copyNote: '',
clipboardCount: 0,
planMap: {},
libraryVisible: false,
libraryKeyword: '',
libraryRows: [],
libraryCurrent: null,
planVisible: false,
planRows: [],
planCurrent: null,
pickerLoading: false
}
},
computed: {
@@ -398,12 +474,13 @@ export default {
return { totalXs, totalXf }
},
editTitle() {
return this.editMode === 'edit' ? '编辑班历记录' : '新增班历记录'
return this.editMode === 'edit' ? '编辑课程任务' : '新增课程任务'
}
},
watch: {
visible(val) {
if (val) {
this.clipboardCount = this.readClipboard().length
this.loadData()
}
}
@@ -411,8 +488,21 @@ export default {
methods: {
/* ---------- 格式化 ---------- */
fmtDate(val) {
if (!val) return '-'
return String(val).slice(0, 10)
if (val == null || val === '') return '-'
if (Array.isArray(val) && val.length >= 3) {
return `${val[0]}-${String(val[1]).padStart(2, '0')}-${String(val[2]).padStart(2, '0')}`
}
if (typeof val === 'object') {
const year = val.year
const month = val.monthValue || val.month
const day = val.dayOfMonth || val.day
if (year && month && day) {
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
}
return '-'
}
const text = String(val)
return text.length >= 10 ? text.slice(0, 10) : text
},
xqdcLabel(val) {
if (val === 1 || val === '1') return '第1学期'
@@ -436,14 +526,44 @@ export default {
const s = this.semester || {}
if (!s.bh) return
this.loading = true
listClassCalendar(s.bh).then(res => {
this.records = (res && res.data) || []
Promise.all([
listClassCalendar(s.bh),
listPlanCourses(s.bh, false).catch(() => ({ data: [] }))
]).then(([taskRes, planRes]) => {
this.records = (taskRes && taskRes.data) || []
const plans = (planRes && planRes.data) || []
const map = {}
plans.forEach(item => {
if (item.kbh) map[item.kbh] = item
})
this.planMap = map
this.loading = false
}).catch(() => {
this.records = []
this.loading = false
})
},
isPlanMismatch(row) {
const plan = row && row.kbh ? this.planMap[row.kbh] : null
if (!plan) return false
return String(row.xs || '') !== String(plan.xs || '')
|| String(row.klx || '') !== String(plan.klx || '')
|| String(row.xf || '') !== String(plan.xf || '')
|| String(row.jc || '') !== String(plan.jc || '')
|| String(row.zks || '') !== String(plan.zks || '')
},
rowClassName({ row }) {
return this.isPlanMismatch(row) ? 'plan-mismatch-row' : ''
},
readClipboard() {
try {
const raw = sessionStorage.getItem(TASK_CLIPBOARD_KEY)
const parsed = raw ? JSON.parse(raw) : []
return Array.isArray(parsed) ? parsed : []
} catch (e) {
return []
}
},
handleSelectionChange(rows) {
this.selection = rows
@@ -529,7 +649,7 @@ export default {
req.then(() => {
this.saving = false
this.editVisible = false
this.$message.success(this.editMode === 'edit' ? '班历记录已更新' : '班历记录已新增')
this.$message.success(this.editMode === 'edit' ? '课程任务已更新' : '课程任务已新增')
this.loadData()
}).catch(() => {
this.saving = false
@@ -539,7 +659,7 @@ export default {
/* ---------- 删除 ---------- */
handleDelete(row) {
this.$confirm(`确定删除该班历记录${row.jc || row.bh})吗?`, '提示', {
this.$confirm(`确定删除该课程任务${row.jc || row.bh})吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@@ -571,65 +691,36 @@ export default {
}).catch(() => {})
},
/* ---------- 预设合班 / 拆班 ---------- */
handlePresetMerge() {
const bhs = this.selection.map(r => r.bh)
if (!bhs.length) {
this.$message.warning('请先勾选要合班的班历记录')
return
}
this.$confirm(`将按年度为选中的 ${bhs.length} 条记录设置统一的课次序号,是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
batchPresetMerge(bhs).then(res => {
const count = (res && res.data) || 0
this.$message.success(`已完成 ${count} 条记录的预设合班`)
this.loadData()
})
}).catch(() => {})
},
handlePresetSplit() {
const bhs = this.selection.map(r => r.bh)
if (!bhs.length) {
this.$message.warning('请先勾选要拆班的班历记录')
return
}
this.$confirm(`将清空选中的 ${bhs.length} 条记录的课次序号,是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
batchPresetSplit(bhs).then(res => {
const count = (res && res.data) || 0
this.$message.success(`已完成 ${count} 条记录的预设拆班`)
this.loadData()
})
}).catch(() => {})
},
/* ---------- 应用于其它班次 ---------- */
handleApplyRegion() {
const bhs = this.selection.map(r => r.bh)
if (!bhs.length) {
this.$message.warning('请先勾选要应用的班历记录(选定区域)')
this.$message.warning('请先勾选要应用的课程任务(选定区域)')
return
}
this.applyMode = 'region'
this.copyNote = `将选中的 ${bhs.length}班历记录复制到所选班次`
this.copyNote = `将选中的 ${bhs.length}课程任务复制到所选班次`
this.teamSelectVisible = true
},
handleApplyWhole() {
this.applyMode = 'whole'
this.copyNote = '将整个班复制到所选班次'
this.copyNote = '将整个班次教学任务复制到所选班次'
this.teamSelectVisible = true
},
handleTeamConfirm(targets) {
if (!targets || !targets.length) return
if (this.applyMode === 'paste') {
const bhList = this.readClipboard()
Promise.all(targets.map(item => pasteTasks(bhList, item.bh))).then(results => {
const count = results.reduce((sum, res) => sum + ((res && res.data) || 0), 0)
this.$message.success(`已粘贴 ${count} 条独立课程任务`)
this.teamSelectVisible = false
this.loadData()
})
return
}
const targetBhList = targets.map(t => t.bh)
if (this.applyMode === 'whole') {
batchCopyCalendarBySemester({
@@ -637,7 +728,7 @@ export default {
targetBhList: targetBhList
}).then(res => {
const data = (res && res.data) || {}
this.$message.success(`已复制整个班,共新增 ${data.count || 0} 条记录`)
this.$message.success(`已复制整个班次教学任务,共新增 ${data.count || 0} 条记录`)
this.loadData()
})
} else {
@@ -652,10 +743,108 @@ export default {
}
},
handleCopySelected() {
const bhList = this.selection.map(item => item.bh).filter(Boolean)
if (!bhList.length) return
sessionStorage.setItem(TASK_CLIPBOARD_KEY, JSON.stringify(bhList))
this.clipboardCount = bhList.length
this.$message.success(`已复制 ${bhList.length} 条课程任务,可在其它班次粘贴`)
},
handlePaste() {
if (!this.readClipboard().length) {
this.$message.warning('剪贴板没有课程任务')
return
}
this.applyMode = 'paste'
this.copyNote = `将剪贴板中的 ${this.clipboardCount} 条课程任务粘贴为独立记录`
this.teamSelectVisible = true
},
handleSyncPlan() {
const bhList = this.selection.map(item => item.bh).filter(Boolean)
if (!bhList.length) return
this.$confirm('将按人培覆盖所选行的学时、类型、学分和周课时,是否继续?', '同步人培', { type: 'warning' }).then(() => {
syncTaskFromPlan(bhList).then(res => {
this.$message.success(`已同步 ${res.data || 0}`)
this.loadData()
})
}).catch(() => {})
},
handleBatchDelete() {
const bhList = this.selection.map(item => item.bh).filter(Boolean)
if (!bhList.length) return
this.$confirm('已发布到任务书或已排课的行会拒绝删除,其余将物理删除。', '批量删除', { type: 'warning' }).then(() => {
batchDeleteTasks(bhList).then(res => {
this.$message.success(`已删除 ${res.data || 0}`)
this.loadData()
})
}).catch(() => {})
},
openLibraryPicker() {
this.libraryVisible = true
this.libraryCurrent = null
this.loadLibrary()
},
loadLibrary() {
this.pickerLoading = true
const keyword = (this.libraryKeyword || '').trim()
const unwrap = res => {
const data = (res && res.data) || {}
return data.records || (Array.isArray(data) ? data : [])
}
const requests = keyword
? [listKb({ pageNum: 1, pageSize: 50, kmc: keyword }), listKb({ pageNum: 1, pageSize: 50, jc: keyword })]
: [listKb({ pageNum: 1, pageSize: 50 })]
Promise.all(requests).then(results => {
const merged = []
const seen = {}
results.forEach(res => {
unwrap(res).forEach(item => {
if (!item.kbh || seen[item.kbh]) return
seen[item.kbh] = true
merged.push(item)
})
})
this.libraryRows = merged
this.pickerLoading = false
}).catch(() => {
this.libraryRows = []
this.pickerLoading = false
})
},
confirmLibrary() {
if (!this.libraryCurrent || !this.semester) return
addTaskFromLibrary(this.semester.bh, this.libraryCurrent.kbh).then(() => {
this.libraryVisible = false
this.$message.success('已从课程库加入,学时和类型已带出')
this.loadData()
})
},
openPlanPicker() {
if (!this.semester || !this.semester.bh) return
this.planVisible = true
this.planCurrent = null
this.pickerLoading = true
listPlanCourses(this.semester.bh, true).then(res => {
this.planRows = res.data || []
this.pickerLoading = false
}).catch(() => {
this.planRows = []
this.pickerLoading = false
})
},
confirmPlan() {
if (!this.planCurrent || !this.semester) return
addTaskFromPlan(this.semester.bh, this.planCurrent.bh).then(() => {
this.planVisible = false
this.$message.success('已加入当前学期任务')
this.loadData()
})
},
/* ---------- 刷新 ---------- */
handleRefresh() {
this.loadData()
this.$message.success('已刷新班')
this.$message.success('已刷新班次教学任务')
}
}
}
@@ -735,6 +924,10 @@ export default {
}
}
::v-deep .plan-mismatch-row > td {
background: #fde2e2 !important;
}
// ========== 弹窗内表单分区标题 ==========
.edit-form {
.form-section-title {
@@ -0,0 +1,154 @@
<template>
<el-dialog
:visible="dialogVisible"
:title="`编辑班历——${titleText}`"
width="92%"
top="4vh"
:close-on-click-modal="false"
class="class-event-calendar-dialog"
@update:visible="val => dialogVisible = val"
>
<div class="dialog-toolbar">
<el-button size="small" :disabled="!semesterBh" @click="handleApplyRegion">选定区域应用于其它班次</el-button>
<el-button size="small" :disabled="!semesterBh" @click="handleApplyWhole">整个班历应用于其它班次</el-button>
<span class="hint">应用到其它班次只覆盖班历时间格不改课程任务</span>
</div>
<div class="editor-wrap">
<SchoolCalendarEditor
v-if="dialogVisible && semesterBh"
ref="editor"
mode="class"
:xydxqbh="semesterBh"
:xydbh="xydbh"
:range-start="rangeStart"
:range-end="rangeEnd"
:semester-name="titleText"
@close="dialogVisible = false"
/>
</div>
<ClassTeamSelectDialog
:visible="teamSelectVisible"
title="选择要覆盖班历的班次"
:semester-list="semesterOptions"
:exclude-bh="semesterBh"
:exclude-note="applyNote"
@update:visible="val => teamSelectVisible = val"
@confirm="handleTeamConfirm"
/>
</el-dialog>
</template>
<script>
import SchoolCalendarEditor from '@/views/teachBusiness/calendar/components/SchoolCalendarEditor.vue'
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
import { applyClassCalendarEvent } from '@/api/teachOffice/classCalendarEvent'
export default {
name: 'ClassEventCalendarDialog',
components: { SchoolCalendarEditor, ClassTeamSelectDialog },
props: {
visible: { type: Boolean, default: false },
semester: { type: Object, default: null },
semesterOptions: { type: Array, default: () => [] }
},
data() {
return {
teamSelectVisible: false,
applyWhole: false
}
},
computed: {
dialogVisible: {
get() { return this.visible },
set(val) { this.$emit('update:visible', val) }
},
semesterBh() {
return (this.semester && this.semester.bh) || ''
},
xydbh() {
return (this.semester && this.semester.xydbh) || ''
},
rangeStart() {
return this.fmtDate(this.semester && this.semester.kxrq)
},
rangeEnd() {
return this.fmtDate(this.semester && this.semester.jsrq)
},
titleText() {
const s = this.semester || {}
const name = s.xydmc || s.xydbh || ''
return name ? `${name} ${s.nd || ''}年 第${s.xqdc || '-'}学期` : '班历'
},
applyNote() {
return this.applyWhole
? '将覆盖目标班次相同日期和节次的班历事件,不改课程任务。'
: '只覆盖当前选中的时间格,不改课程任务。'
}
},
methods: {
fmtDate(val) {
if (val == null || val === '') return ''
if (Array.isArray(val) && val.length >= 3) {
return `${val[0]}-${String(val[1]).padStart(2, '0')}-${String(val[2]).padStart(2, '0')}`
}
if (typeof val === 'object') {
const year = val.year
const month = val.monthValue || val.month
const day = val.dayOfMonth || val.day
if (year && month && day) {
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
}
return ''
}
return String(val).slice(0, 10)
},
handleApplyRegion() {
const editor = this.$refs.editor
const bhs = editor && editor.selectedEventBhs ? editor.selectedEventBhs() : []
if (!bhs.length) {
this.$message.warning('请先在班历中选定时间格')
return
}
this.applyWhole = false
this.teamSelectVisible = true
},
handleApplyWhole() {
this.applyWhole = true
this.teamSelectVisible = true
},
handleTeamConfirm(rows) {
const targetBhList = (rows || []).map(item => item.bh).filter(Boolean)
if (!targetBhList.length) return
const editor = this.$refs.editor
const eventBhList = this.applyWhole
? []
: (editor && editor.selectedEventBhs ? editor.selectedEventBhs() : [])
applyClassCalendarEvent({
sourceXydxqbh: this.semesterBh,
targetBhList,
eventBhList
}).then(res => {
this.teamSelectVisible = false
this.$message.success(`已覆盖 ${res.data || 0} 个时间格`)
})
}
}
}
</script>
<style scoped>
.dialog-toolbar {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.hint {
color: #909399;
font-size: 12px;
}
.editor-wrap {
height: 72vh;
}
</style>
@@ -2,7 +2,7 @@
<el-dialog
:visible="dialogVisible"
:title="isEdit ? '编辑班次学期信息' : '教学班次学期信息明细'"
width="620px"
width="720px"
class="class-detail-dialog"
:close-on-click-modal="false"
@update:visible="val => dialogVisible = val"
@@ -16,24 +16,41 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="班次编号">
<el-input v-model="form.xydbh" :readonly="isEdit" :disabled="isEdit" placeholder="编辑模式不可修改" />
<el-form-item label="班次编号" prop="xydbh">
<el-select
v-if="!isEdit"
v-model="form.xydbh"
filterable
placeholder="请先选择年度和学期第次"
style="width: 100%"
:loading="classLoading"
@change="handleClassChange"
>
<el-option
v-for="item in creatableClasses"
:key="item.xydbh"
:label="classOptionLabel(item)"
:value="item.xydbh"
/>
</el-select>
<el-input v-else v-model="form.xydbh" disabled />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="年度" prop="nd">
<el-select v-model="form.nd" placeholder="请选择年度" filterable style="width: 100%">
<el-select v-model="form.nd" placeholder="请选择年度" filterable style="width: 100%" @change="handleSemesterFilterChange">
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学期第次" prop="xqdc">
<el-select v-model="form.xqdc" placeholder="请选择学期" style="width: 100%">
<el-select v-model="form.xqdc" placeholder="请选择学期" style="width: 100%" :disabled="halfYear" @change="handleXqdcChange">
<el-option v-for="opt in levelOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
</el-select>
<div v-if="halfYear" class="field-tip">学制半年以内学期第次固定为第1学期</div>
</el-form-item>
</el-col>
</el-row>
@@ -52,7 +69,25 @@
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="专用教室">
<el-input v-model="form.zyjsbh" placeholder="请输入专用教室编号" clearable />
<el-select
v-model="form.zyjsbh"
filterable
clearable
remote
reserve-keyword
placeholder="按名称、编号或拼音查找"
style="width: 100%"
:remote-method="searchRooms"
:loading="roomLoading"
@focus="loadRooms()"
>
<el-option
v-for="room in roomOptions"
:key="room.id || room.jsbh"
:label="roomLabel(room)"
:value="room.id || room.jsbh"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
@@ -79,6 +114,8 @@
</template>
<script>
import { initClassSemester, listCreatableClasses, listSemesterRooms, presetSemesterDates } from '@/api/studentRecords/semester'
export default {
name: 'ClassSemesterDetailDialog',
props: {
@@ -92,7 +129,9 @@ export default {
/** 年度下拉 */
yearOptions: { type: Array, default: () => [] },
/** 教学任务下拉({ bh, rwmc } */
taskOptions: { type: Array, default: () => [] }
taskOptions: { type: Array, default: () => [] },
/** 页面已吸取的开学/结束日期,作为半年以上班次的窗口预设 */
presetDates: { type: Object, default: () => ({ kxrq: '', jsrq: '' }) }
},
data() {
return {
@@ -103,7 +142,13 @@ export default {
{ label: '第3学期', value: 3 }
],
saving: false,
classLoading: false,
creatableClasses: [],
halfYear: false,
roomLoading: false,
roomOptions: [],
rules: {
xydbh: [{ required: true, message: '请选择可新建班次', trigger: 'change' }],
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
xqdc: [{ required: true, message: '请选择学期第次', trigger: 'change' }],
kxrq: [{ required: true, message: '请选择开学日期', trigger: 'change' }],
@@ -165,14 +210,119 @@ export default {
form.jxrwbh = row.jxrwbh || undefined
form.kfjypk = Number(row.kfjypk) === 1
form.bz = row.bz || ''
this.ensureRoomOption(row.zyjsbh, row.zyjsmc)
} else if (this.teamInfo) {
form.xydbh = this.teamInfo.xydbh || ''
form.xydmc = this.teamInfo.xydmc || ''
// 默认取当前选中年度的第一学期,日期可后续修改
form.nd = this.yearOptions[0]
form.xqdc = 1
} else {
form.nd = this.yearOptions[0]
form.xqdc = 1
}
this.form = form
this.halfYear = false
if (!this.isEdit) this.loadCreatableClasses()
if (form.zyjsbh) this.ensureRoomOption(form.zyjsbh, this.semester && this.semester.zyjsmc)
},
classOptionLabel(item) {
return item.xydmc ? `${item.xydmc}${item.xydbh}` : item.xydbh
},
handleSemesterFilterChange() {
if (this.isEdit) return
this.form.xydbh = ''
this.form.xydmc = ''
this.halfYear = false
this.loadCreatableClasses()
},
handleXqdcChange() {
this.handleSemesterFilterChange()
if (this.form.xydbh) this.applySmartDefaults(false)
},
loadCreatableClasses() {
if (!this.form.nd || !this.form.xqdc) {
this.creatableClasses = []
return
}
this.classLoading = true
listCreatableClasses({ nd: this.form.nd, xqdc: this.form.xqdc }).then(res => {
this.creatableClasses = (res && res.data) || []
this.classLoading = false
}).catch(() => {
this.creatableClasses = []
this.classLoading = false
})
},
handleClassChange(xydbh) {
const item = this.creatableClasses.find(row => row.xydbh === xydbh)
this.form.xydmc = item ? (item.xydmc || '') : ''
this.applySmartDefaults(true)
},
applySmartDefaults(overwriteDates) {
if (!this.form.xydbh) return
const preset = this.presetDates || {}
initClassSemester({
xydbh: this.form.xydbh,
xqdc: this.form.xqdc,
presetKxrq: preset.kxrq || this.form.kxrq || undefined,
presetJsrq: preset.jsrq || this.form.jsrq || undefined
}).then(res => {
const data = (res && res.data) || {}
this.halfYear = !!data.halfYear
if (this.halfYear) {
this.form.xqdc = 1
this.form.kxrq = data.kxrq ? String(data.kxrq).slice(0, 10) : this.form.kxrq
this.form.jsrq = data.jsrq ? String(data.jsrq).slice(0, 10) : this.form.jsrq
} else if (overwriteDates) {
if (!this.form.kxrq && data.kxrq) this.form.kxrq = String(data.kxrq).slice(0, 10)
if (!this.form.jsrq && data.jsrq) this.form.jsrq = String(data.jsrq).slice(0, 10)
if (preset.kxrq) this.form.kxrq = preset.kxrq
if (preset.jsrq) this.form.jsrq = preset.jsrq
}
if (data.zyjsbh) {
this.form.zyjsbh = data.zyjsbh
this.ensureRoomOption(data.zyjsbh, data.zyjsmc)
}
if (!this.halfYear && overwriteDates && (!this.form.kxrq || !this.form.jsrq) && this.form.nd && this.form.xqdc) {
this.fillSchoolDates()
}
}).catch(() => {})
},
fillSchoolDates() {
presetSemesterDates({ nd: this.form.nd, xqdc: this.form.xqdc }).then(res => {
const data = (res && res.data) || {}
if (!this.form.kxrq && data.kxrq) this.form.kxrq = data.kxrq
if (!this.form.jsrq && data.jsrq) this.form.jsrq = data.jsrq
}).catch(() => {})
},
roomLabel(room) {
if (!room) return ''
const name = room.jsmc || room.zyjsmc || ''
const code = room.jsbh || room.id || ''
return name && code ? `${name}${code}` : (name || code)
},
ensureRoomOption(id, name) {
if (!id) return
if (this.roomOptions.some(item => (item.id || item.jsbh) === id)) return
this.roomOptions = [{ id: id, jsbh: id, jsmc: name || '' }].concat(this.roomOptions)
},
loadRooms(keyword) {
this.roomLoading = true
listSemesterRooms({ py: keyword || undefined }).then(res => {
const list = (res && res.data) || []
const current = this.form.zyjsbh
const currentOpt = this.roomOptions.find(item => (item.id || item.jsbh) === current)
this.roomOptions = list
if (current && !list.some(item => item.id === current || item.jsbh === current)) {
this.ensureRoomOption(current, currentOpt && (currentOpt.jsmc || currentOpt.zyjsmc))
}
this.roomLoading = false
}).catch(() => {
this.roomLoading = false
})
},
searchRooms(keyword) {
this.loadRooms(keyword)
},
/** 移除空值(保留 0 等有效值) */
cleanPayload(obj) {
@@ -218,6 +368,12 @@ export default {
::v-deep .el-input__inner[readonly] {
background-color: #f5f7fa;
}
.field-tip {
margin-top: 4px;
color: #909399;
font-size: 12px;
line-height: 1.4;
}
}
}
</style>
@@ -54,8 +54,12 @@
<template slot-scope="{ row }">{{ xqdcLabel(row.xqdc) }}</template>
</el-table-column>
<el-table-column prop="xydbh" label="班次编号" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="kxrq" label="开学日期" width="110" align="center" :formatter="fmtDate" />
<el-table-column prop="jsrq" label="结束日期" width="110" align="center" :formatter="fmtDate" />
<el-table-column label="开学日期" width="110" align="center">
<template slot-scope="{ row }">{{ formatDate(row.kxrq) }}</template>
</el-table-column>
<el-table-column label="结束日期" width="110" align="center">
<template slot-scope="{ row }">{{ formatDate(row.jsrq) }}</template>
</el-table-column>
<el-table-column prop="bz" label="备注" min-width="120" align="left" header-align="center" show-overflow-tooltip />
</el-table>
<el-empty v-if="!filteredList.length" description="暂无可选的目标班次学期" :image-size="60" />
@@ -125,9 +129,22 @@ export default {
}
},
methods: {
fmtDate(val) {
if (!val) return '-'
return String(val).slice(0, 10)
formatDate(val) {
if (val == null || val === '') return '-'
if (Array.isArray(val) && val.length >= 3) {
return `${val[0]}-${String(val[1]).padStart(2, '0')}-${String(val[2]).padStart(2, '0')}`
}
if (typeof val === 'object') {
const year = val.year
const month = val.monthValue || val.month
const day = val.dayOfMonth || val.day
if (year && month && day) {
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
}
return '-'
}
const text = String(val)
return text.length >= 10 ? text.slice(0, 10) : text
},
xqdcLabel(val) {
if (val === 1 || val === '1') return '第1学期'
@@ -22,11 +22,21 @@
clearable
filterable
style="width: 140px"
@change="handleYearChange"
@change="handleQuery"
>
<el-option v-for="y in yearFilterOptions" :key="y" :label="`${y}年`" :value="y" />
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" />
</el-select>
</el-form-item>
<el-form-item label="学期第次">
<el-select v-model="searchForm.xqdc" placeholder="全部" clearable style="width: 120px" @change="handleQuery">
<el-option label="第1学期" :value="1" />
<el-option label="第2学期" :value="2" />
<el-option label="第3学期" :value="3" />
</el-select>
</el-form-item>
<el-form-item>
<el-checkbox v-model="sortWave" @change="handleQuery">按波次排序</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
@@ -42,10 +52,17 @@
<el-button type="primary" plain icon="el-icon-edit" :disabled="!currentRow" @click="handleEdit">编辑</el-button>
<el-button type="danger" plain icon="el-icon-delete" :disabled="!selection.length" @click="handleDelete">删除所选</el-button>
<el-button type="primary" plain icon="el-icon-date" :disabled="!selection.length" @click="handleQuickSetDates">快速设定学期日期</el-button>
<el-button type="primary" plain icon="el-icon-download" :disabled="!currentRow" @click="handleAbsorbDates">吸取预设日期</el-button>
<el-button type="primary" plain icon="el-icon-notebook-2" :disabled="!selection.length" @click="handleSetJxrw">批量设置教学任务</el-button>
<el-button type="primary" plain :disabled="!selection.length" @click="handleSetXqdc">批量学期第次</el-button>
<el-button type="primary" plain :disabled="!selection.length" @click="handleSetKfjypk">开放教员排课</el-button>
<el-button type="warning" plain :disabled="selection.length < 2" @click="handleWaveMerge">预设合班</el-button>
<el-button type="warning" plain :disabled="!selection.length" @click="handleWaveSplit">预设拆班</el-button>
<el-button type="primary" plain :disabled="!selection.length" @click="handleBatchGenerateTasks">批量生成必修课程</el-button>
</div>
<div class="right-group">
<el-button type="success" plain icon="el-icon-calendar" :disabled="!currentRow" @click="handleEditCalendar">编辑班历</el-button>
<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>
</div>
</div>
@@ -61,6 +78,7 @@
size="small"
max-height="480"
class="class-table"
:row-class-name="waveRowClass"
@selection-change="handleSelectionChange"
@current-change="handleCurrentChange"
>
@@ -69,17 +87,32 @@
</template>
<el-table-column type="selection" width="44" align="center" />
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="xydbh" label="班次编号" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="xydmc" label="班次名称" min-width="140" align="center" show-overflow-tooltip>
<template slot-scope="{ row }">{{ row.xydmc || '-' }}</template>
</el-table-column>
<el-table-column prop="xydbh" label="班次编号" min-width="130" align="center" show-overflow-tooltip />
<el-table-column prop="ysbbh" label="预设编班号" width="100" align="center">
<template slot-scope="{ row }">{{ waveLabel(row.ysbbh) }}</template>
</el-table-column>
<el-table-column prop="nd" label="年度" width="80" align="center" show-overflow-tooltip />
<el-table-column label="学期第次" width="90" align="center">
<template slot-scope="{ row }">{{ xqdcLabel(row.xqdc) }}</template>
</el-table-column>
<el-table-column prop="kxrq" label="开学日期" width="110" align="center" :formatter="fmtDate" />
<el-table-column prop="jsrq" label="结束日期" width="110" align="center" :formatter="fmtDate" />
<el-table-column prop="zyjsbh" label="专用教室" width="100" align="center" show-overflow-tooltip />
<el-table-column label="开学日期" width="110" align="center">
<template slot-scope="{ row }">{{ formatDate(row.kxrq) }}</template>
</el-table-column>
<el-table-column label="结束日期" width="110" align="center">
<template slot-scope="{ row }">{{ formatDate(row.jsrq) }}</template>
</el-table-column>
<el-table-column label="专用教室" min-width="140" align="center" show-overflow-tooltip>
<template slot-scope="{ row }">{{ roomLabel(row) }}</template>
</el-table-column>
<el-table-column label="教学任务" min-width="160" align="center" show-overflow-tooltip>
<template slot-scope="{ row }">{{ taskName(row.jxrwbh) }}</template>
</el-table-column>
<el-table-column prop="zkcs" label="总课程数" width="90" align="center">
<template slot-scope="{ row }">{{ row.zkcs == null ? 0 : row.zkcs }}</template>
</el-table-column>
<el-table-column label="开放教员排课" width="110" align="center">
<template slot-scope="{ row }">
<el-tag v-if="Number(row.kfjypk) === 1" type="success" size="small">开放</el-tag>
@@ -87,10 +120,11 @@
</template>
</el-table-column>
<el-table-column prop="bz" label="备注" min-width="120" align="left" header-align="center" show-overflow-tooltip />
<el-table-column label="操作" width="200" align="center" fixed="right">
<el-table-column label="操作" width="250" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="text" size="small" @click="handleEditCalendar(row)">班历</el-button>
<el-button type="text" size="small" @click="handleEditEventCalendar(row)">班历</el-button>
<el-button type="text" size="small" @click="handleEditCalendar(row)">教学任务</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDeleteRow(row)">删除</el-button>
</template>
</el-table-column>
@@ -116,11 +150,12 @@
:team-info="detailTeamInfo"
:year-options="yearOptions"
:task-options="taskOptions"
:preset-dates="presetDates"
@update:visible="val => detailVisible = val"
@submit="handleDetailSubmit"
/>
<!-- ==================== 历管理弹窗 ==================== -->
<!-- ==================== 次教学任务弹窗 ==================== -->
<ClassCalendarDialog
:visible="calendarVisible"
:semester="calendarSemester"
@@ -128,6 +163,13 @@
@update:visible="val => calendarVisible = val"
/>
<ClassEventCalendarDialog
:visible="eventCalendarVisible"
:semester="eventCalendarSemester"
:semester-options="semesterOptions"
@update:visible="val => eventCalendarVisible = val"
/>
<!-- ==================== 批量创建班次学期弹窗 ==================== -->
<el-dialog
:visible="batchAddVisible"
@@ -138,15 +180,15 @@
>
<el-form ref="batchAddFormRef" :model="batchAddForm" :rules="batchAddRules" label-width="100px">
<el-form-item label="年度" prop="nd">
<el-select v-model="batchAddForm.nd" placeholder="请选择年度" filterable style="width: 100%">
<el-select v-model="batchAddForm.nd" placeholder="请选择年度" filterable style="width: 100%" @change="loadBatchPreset">
<el-option v-for="y in yearOptions" :key="y" :label="`${y}年`" :value="y" />
</el-select>
</el-form-item>
<el-form-item label="学期" prop="xqdc">
<el-select v-model="batchAddForm.xqdc" placeholder="请选择学期" style="width: 100%">
<el-option label="春季学期" value="春季学期" />
<el-option label="夏季学期" value="夏季学期" />
<el-option label="秋季学期" value="秋季学期" />
<el-select v-model="batchAddForm.xqdc" placeholder="请选择学期" style="width: 100%" @change="loadBatchPreset">
<el-option label="第1学期" :value="1" />
<el-option label="第2学期" :value="2" />
<el-option label="第3学期" :value="3" />
</el-select>
</el-form-item>
<el-form-item label="开始日期" prop="startTime">
@@ -155,6 +197,16 @@
<el-form-item label="结束日期" prop="endTime">
<el-date-picker v-model="batchAddForm.endTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择结束日期" style="width: 100%" />
</el-form-item>
<el-form-item>
<el-button size="small" @click="loadBatchPreset">按学校学期带出日期</el-button>
<el-button size="small" :disabled="!currentRow" @click="absorbIntoBatch">吸取选中班次日期</el-button>
</el-form-item>
<el-alert
type="info"
:closable="false"
show-icon
:title="`将为未毕业且尚未建立的班次创建学期信息,当前可新建 ${batchEligibleCount} 个班次`"
/>
</el-form>
<div slot="footer">
<el-button @click="batchAddVisible = false">取消</el-button>
@@ -218,13 +270,58 @@
<el-button type="primary" :loading="saving" @click="handleJxrwSubmit">确定</el-button>
</div>
</el-dialog>
<el-dialog
:visible="xqdcVisible"
title="批量设定学期第次"
width="420px"
:close-on-click-modal="false"
@update:visible="val => xqdcVisible = val"
>
<el-form label-width="100px">
<el-form-item label="学期第次">
<el-select v-model="xqdcForm.xqdc" placeholder="请选择学期第次" style="width: 100%">
<el-option label="第1学期" :value="1" />
<el-option label="第2学期" :value="2" />
<el-option label="第3学期" :value="3" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="xqdcVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleXqdcSubmit">确定</el-button>
</div>
</el-dialog>
<el-dialog
:visible="kfjypkVisible"
title="批量设定开放教员排课"
width="420px"
:close-on-click-modal="false"
@update:visible="val => kfjypkVisible = val"
>
<el-form label-width="120px">
<el-form-item label="开放教员排课">
<el-radio-group v-model="kfjypkForm.kfjypk">
<el-radio :label="1">开放</el-radio>
<el-radio :label="0">关闭</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="kfjypkVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleKfjypkSubmit">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import ClassSemesterDetailDialog from './ClassSemesterDetailDialog.vue'
import ClassCalendarDialog from './ClassCalendarDialog.vue'
import { listSemester, allSemester, addSemester, updateSemester, delSemester, batchDeleteSemester, batchUpdateDateRange, batchUpdateJxrwbh, batchAddFromElective } from '@/api/studentRecords/semester'
import ClassEventCalendarDialog from './ClassEventCalendarDialog.vue'
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'
@@ -232,7 +329,8 @@ export default {
name: 'ShiftSemester',
components: {
ClassSemesterDetailDialog,
ClassCalendarDialog
ClassCalendarDialog,
ClassEventCalendarDialog
},
data() {
return {
@@ -240,9 +338,10 @@ export default {
saving: false,
// ==================== 查询条件 ====================
searchForm: { xydbh: '' },
searchForm: { xydbh: '', xqdc: undefined },
filterYear: undefined,
yearFilterOptions: [],
sortWave: true,
presetDates: { kxrq: '', jsrq: '' },
// ==================== 列表与分页 ====================
tableData: [],
@@ -269,10 +368,13 @@ export default {
// ==================== 班历管理 ====================
calendarVisible: false,
calendarSemester: null,
eventCalendarVisible: false,
eventCalendarSemester: null,
// ==================== 批量创建班次学期 ====================
batchAddVisible: false,
batchAddForm: { nd: undefined, xqdc: '', startTime: '', endTime: '' },
batchAddForm: { nd: undefined, xqdc: undefined, startTime: '', endTime: '' },
batchEligibleCount: 0,
batchAddRules: {
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
xqdc: [{ required: true, message: '请选择学期', trigger: 'change' }],
@@ -293,14 +395,16 @@ export default {
jxrwForm: { jxrwbh: undefined },
jxrwRules: {
jxrwbh: [{ required: true, message: '请选择教学任务', trigger: 'change' }]
}
},
xqdcVisible: false,
xqdcForm: { xqdc: 1 },
kfjypkVisible: false,
kfjypkForm: { kfjypk: 1 }
}
},
computed: {
/** 本地按年度过滤(后端 /semester/list 仅支持 xydbh 模糊筛选) */
displayData() {
if (!this.filterYear) return this.tableData
return this.tableData.filter(item => String(item.nd) === String(this.filterYear))
return this.tableData
}
},
mounted() {
@@ -311,9 +415,42 @@ export default {
},
methods: {
/* ==================== 格式化 ==================== */
fmtDate(val) {
if (!val) return '-'
return String(val).slice(0, 10)
formatDate(val) {
if (val == null || val === '') return '-'
if (Array.isArray(val) && val.length >= 3) {
return `${val[0]}-${String(val[1]).padStart(2, '0')}-${String(val[2]).padStart(2, '0')}`
}
if (typeof val === 'object') {
const year = val.year
const month = val.monthValue || val.month
const day = val.dayOfMonth || val.day
if (year && month && day) {
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
}
return '-'
}
const text = String(val)
return text.length >= 10 ? text.slice(0, 10) : text
},
yearFromNd(nd) {
const text = String(nd || '')
if (text.length >= 6) return Number(text.slice(0, 4))
const n = Number(text)
return Number.isFinite(n) && n > 0 ? n : null
},
roomLabel(row) {
if (!row) return '-'
if (row.zyjsmc && row.zyjsbh) return `${row.zyjsmc}${row.zyjsbh}`
return row.zyjsmc || row.zyjsbh || '-'
},
waveLabel(val) {
const n = Number(val)
return n > 0 ? n : '未编波'
},
waveRowClass({ row }) {
const n = Number(row.ysbbh)
if (!n) return ''
return 'wave-row wave-row-' + (n % 8)
},
xqdcLabel(val) {
if (val === 1 || val === '1') return '第1学期'
@@ -342,7 +479,7 @@ export default {
loadYearOptions() {
listAllSemester().then(res => {
const list = (res && res.data) || []
const years = list.map(x => x.nd).filter(v => v !== undefined && v !== null && v !== '')
const years = list.map(x => this.yearFromNd(x.nd)).filter(v => v)
this.yearOptions = [...new Set(years)].sort((a, b) => Number(a) - Number(b))
}).catch(() => {
this.yearOptions = []
@@ -370,13 +507,15 @@ export default {
const query = this.cleanPayload({
pageNum: this.pageNum,
pageSize: this.pageSize,
xydbh: this.searchForm.xydbh
xydbh: this.searchForm.xydbh,
nd: this.filterYear,
xqdc: this.searchForm.xqdc,
sortWave: this.sortWave ? 1 : 0
})
listSemester(query).then(res => {
const data = (res && res.data) || {}
this.tableData = data.records || []
this.total = data.total || 0
this.buildYearFilterOptions()
this.loading = false
}).catch(() => {
this.tableData = []
@@ -384,17 +523,6 @@ export default {
this.loading = false
})
},
/** 本地年度过滤下拉(从当前数据动态生成,避免硬编码) */
buildYearFilterOptions() {
const years = this.tableData.map(r => r.nd).filter(v => v !== undefined && v !== null && v !== '')
this.yearFilterOptions = [...new Set(years)].sort((a, b) => Number(a) - Number(b))
},
handleYearChange() {
this.$nextTick(() => {
if (this.$refs.tableRef) this.$refs.tableRef.setCurrentRow(null)
this.currentRow = null
})
},
handleSizeChange(size) {
this.pageSize = size
this.loadList()
@@ -417,6 +545,7 @@ export default {
},
handleReset() {
this.searchForm.xydbh = ''
this.searchForm.xqdc = undefined
this.filterYear = undefined
this.pageNum = 1
this.loadList()
@@ -462,7 +591,7 @@ export default {
this.$message.warning('请先勾选要删除的班次学期')
return
}
this.$confirm(`确定删除选中的 ${this.selection.length} 条班次学期吗?删除后其下班历记录将一并删除。`, '提示', {
this.$confirm(`确定删除选中的 ${this.selection.length} 条班次学期吗?若已有课程任务将禁止删除。`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@@ -476,7 +605,7 @@ export default {
}).catch(() => {})
},
handleDeleteRow(row) {
this.$confirm(`确定删除班次学期「${row.xydbh || row.bh}」吗?删除后其下班历记录将一并删除。`, '提示', {
this.$confirm(`确定删除班次学期「${row.xydbh || row.bh}」吗?若已有课程任务将禁止删除。`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@@ -489,7 +618,33 @@ export default {
}).catch(() => {})
},
/* ==================== 编辑班历 ==================== */
handleEditEventCalendar(row) {
const target = row || this.currentRow
if (!target) {
this.$message.warning('请先在列表中选择一条班次学期信息')
return
}
this.eventCalendarSemester = target
this.eventCalendarVisible = true
},
handleBatchGenerateTasks() {
if (!this.selection.length) {
this.$message.warning('请先勾选班次学期')
return
}
const ids = this.selection.map(item => item.bh).filter(Boolean)
this.$confirm(`将为所选 ${ids.length} 个班次学期按人培补齐必修课程,已有课程不会重复生成。`, '批量生成必修课程', {
type: 'warning'
}).then(() => {
batchAutoGenerateTasks(ids).then(res => {
this.$message.success(`已生成 ${res.data || 0} 条课程任务`)
this.loadList()
})
}).catch(() => {})
},
/* ==================== 编辑班次教学任务 ==================== */
handleEditCalendar(row) {
const target = row || this.currentRow
if (!target) {
@@ -502,26 +657,53 @@ export default {
/* ==================== 批量创建班次学期 ==================== */
handleBatchAdd() {
this.batchAddForm = { nd: this.yearOptions[0] || undefined, xqdc: '', startTime: '', endTime: '' }
this.batchAddForm = {
nd: this.filterYear || this.yearOptions[this.yearOptions.length - 1] || undefined,
xqdc: this.searchForm.xqdc || 1,
startTime: this.presetDates.kxrq || '',
endTime: this.presetDates.jsrq || ''
}
this.batchEligibleCount = 0
this.batchAddVisible = true
this.loadBatchPreset()
this.$nextTick(() => {
if (this.$refs.batchAddFormRef) this.$refs.batchAddFormRef.clearValidate()
})
},
loadBatchPreset() {
const f = this.batchAddForm
if (!f.nd || !f.xqdc) return
presetSemesterDates({ nd: f.nd, xqdc: f.xqdc }).then(res => {
const data = (res && res.data) || {}
if (data.kxrq) this.batchAddForm.startTime = data.kxrq
if (data.jsrq) this.batchAddForm.endTime = data.jsrq
}).catch(() => {})
listCreatableClasses({ nd: f.nd, xqdc: f.xqdc }).then(res => {
this.batchEligibleCount = ((res && res.data) || []).length
}).catch(() => {
this.batchEligibleCount = 0
})
},
absorbIntoBatch() {
if (!this.currentRow) return
this.absorbDatesFrom(this.currentRow)
this.batchAddForm.startTime = this.presetDates.kxrq
this.batchAddForm.endTime = this.presetDates.jsrq
},
handleBatchAddSubmit() {
this.$refs.batchAddFormRef.validate(valid => {
if (!valid) return
const f = this.batchAddForm
this.saving = true
batchAddFromElective({
batchCreateSemester({
nd: f.nd,
xqdc: f.xqdc,
startTime: f.startTime,
endTime: f.endTime
kxrq: f.startTime,
jsrq: f.endTime
}).then(res => {
this.saving = false
this.batchAddVisible = false
const count = (res && res.data) || 0
const count = Number((res && res.data) || 0)
this.$message.success(`已创建 ${count} 个班次学期`)
this.loadList()
this.loadSemesterOptions()
@@ -530,6 +712,79 @@ export default {
})
})
},
handleAbsorbDates() {
if (!this.currentRow) {
this.$message.warning('请先选中一条班次学期')
return
}
this.absorbDatesFrom(this.currentRow)
this.$message.success(`已吸取开学日期 ${this.presetDates.kxrq}、结束日期 ${this.presetDates.jsrq}`)
},
absorbDatesFrom(row) {
this.presetDates = {
kxrq: this.formatDate(row.kxrq) === '-' ? '' : this.formatDate(row.kxrq),
jsrq: this.formatDate(row.jsrq) === '-' ? '' : this.formatDate(row.jsrq)
}
},
handleSetXqdc() {
if (!this.selection.length) return
this.xqdcForm = { xqdc: this.selection[0].xqdc || 1 }
this.xqdcVisible = true
},
handleXqdcSubmit() {
this.saving = true
batchUpdateXqdc({
bhList: this.selection.map(r => r.bh),
xqdc: this.xqdcForm.xqdc
}).then(res => {
this.saving = false
this.xqdcVisible = false
this.$message.success(`已更新 ${Number((res && res.data) || 0)} 条学期第次`)
this.loadList()
}).catch(() => {
this.saving = false
})
},
handleSetKfjypk() {
if (!this.selection.length) return
this.kfjypkForm = { kfjypk: 1 }
this.kfjypkVisible = true
},
handleKfjypkSubmit() {
this.saving = true
batchUpdateKfjypk({
bhList: this.selection.map(r => r.bh),
kfjypk: this.kfjypkForm.kfjypk
}).then(res => {
this.saving = false
this.kfjypkVisible = false
this.$message.success(`已更新 ${Number((res && res.data) || 0)} 条开放教员排课`)
this.loadList()
}).catch(() => {
this.saving = false
})
},
handleWaveMerge() {
if (this.selection.length < 2) {
this.$message.warning('请至少勾选两个班次')
return
}
this.$confirm(`将选中的 ${this.selection.length} 个班次设为同一预设编班号。只改编波,不改课次序号。`, '预设合班', {
type: 'warning'
}).then(() => batchWaveMerge({ bhList: this.selection.map(r => r.bh) })).then(res => {
this.$message.success(`已合班 ${Number((res && res.data) || 0)} 个班次`)
this.loadList()
}).catch(() => {})
},
handleWaveSplit() {
if (!this.selection.length) return
this.$confirm(`将选中的 ${this.selection.length} 个班次各自独立成波。只改编波,不改课次序号。`, '预设拆班', {
type: 'warning'
}).then(() => batchWaveSplit({ bhList: this.selection.map(r => r.bh) })).then(res => {
this.$message.success(`已拆班 ${Number((res && res.data) || 0)} 个班次`)
this.loadList()
}).catch(() => {})
},
/* ==================== 快速设定学期日期 ==================== */
handleQuickSetDates() {
@@ -537,7 +792,10 @@ export default {
this.$message.warning('请先勾选要设定日期的班次学期')
return
}
this.dateRangeForm = { kxrq: '', jsrq: '' }
this.dateRangeForm = {
kxrq: this.presetDates.kxrq || '',
jsrq: this.presetDates.jsrq || ''
}
this.dateRangeVisible = true
this.$nextTick(() => {
if (this.$refs.dateRangeFormRef) this.$refs.dateRangeFormRef.clearValidate()
@@ -651,6 +909,15 @@ export default {
margin-top: 12px;
text-align: right;
}
::v-deep .wave-row-1 td { background: #f0f9eb !important; }
::v-deep .wave-row-2 td { background: #ecf5ff !important; }
::v-deep .wave-row-3 td { background: #fdf6ec !important; }
::v-deep .wave-row-4 td { background: #fef0f0 !important; }
::v-deep .wave-row-5 td { background: #f4f4f5 !important; }
::v-deep .wave-row-6 td { background: #f0f9ff !important; }
::v-deep .wave-row-7 td { background: #f9f0ff !important; }
::v-deep .wave-row-0 td { background: #fff7e6 !important; }
}
}
</style>
@@ -23,7 +23,7 @@
<el-form-item label="状态">
<el-select v-model="searchForm.zt" placeholder="请选择状态" clearable style="width: 140px">
<el-option
v-for="opt in statusOptions"
v-for="opt in searchStatusOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
@@ -54,8 +54,8 @@
<el-table-column prop="nd" label="年度" width="150" align="center" />
<el-table-column label="状态" width="100" align="center">
<template slot-scope="{ row }">
<el-tag :type="row.zt === '发布' ? 'success' : 'info'" size="small">
{{ row.zt || '-' }}
<el-tag :type="taskTagType(row.zt)" size="small">
{{ taskStatusLabel(row.zt) }}
</el-tag>
</template>
</el-table-column>
@@ -63,17 +63,23 @@
<el-table-column prop="jssj" label="结束时间" width="180" align="center" :formatter="fmtDateTime" />
<el-table-column prop="cjsj" label="创建时间" width="180" align="center" :formatter="fmtDateTime" />
<el-table-column prop="jcxqscsj" label="教材需求生成时间" align="center" :formatter="fmtDateTime" />
<el-table-column label="操作" width="300" align="center" fixed="right">
<el-table-column label="操作" width="380" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleDetail(row)">详情</el-button>
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="text" size="small" :disabled="isTaskLocked(row)" @click="handleEdit(row)">编辑</el-button>
<el-button
type="text"
size="small"
:disabled="row.zt === '发布'"
:disabled="isTaskPublished(row) || isTaskEnded(row)"
@click="handlePublish(row)"
>发布</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
<el-button
type="text"
size="small"
:disabled="!isTaskPublished(row)"
@click="handleEndPublish(row)"
>结束发布</el-button>
<el-button type="text" size="small" class="text-danger" :disabled="isTaskEnded(row)" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -116,7 +122,7 @@
<el-form-item label="状态" prop="zt">
<el-select v-model="form.zt" placeholder="请选择状态" style="width: 100%">
<el-option
v-for="opt in statusOptions"
v-for="opt in formStatusOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
@@ -197,7 +203,8 @@ import {
addTeachingTask,
updateTeachingTask,
deleteTeachingTask,
publishTeachingTask
publishTeachingTask,
endPublishTeachingTask
} from '@/api/teachBusiness/teachingTask'
import { listAllSemester } from '@/api/teachBusiness/semester'
@@ -212,7 +219,13 @@ export default {
zt: ''
},
yearOptions: [],
statusOptions: [
searchStatusOptions: [
{ label: '未发布', value: '未发布' },
{ label: '发布', value: '发布' },
{ label: '已发布', value: '已发布' },
{ label: '已结束', value: '已结束' }
],
formStatusOptions: [
{ label: '未发布', value: '未发布' },
{ label: '发布', value: '发布' }
],
@@ -425,6 +438,25 @@ export default {
}).catch(() => {})
},
isTaskPublished(row) {
return row && (row.zt === '发布' || row.zt === '已发布')
},
isTaskEnded(row) {
return row && row.zt === '已结束'
},
isTaskLocked(row) {
return this.isTaskPublished(row) || this.isTaskEnded(row)
},
taskStatusLabel(zt) {
if (zt === '已结束') return '已结束'
if (zt === '发布' || zt === '已发布') return '已发布'
return zt || '未发布'
},
taskTagType(zt) {
if (zt === '已结束') return 'warning'
if (zt === '发布' || zt === '已发布') return 'success'
return 'info'
},
/* ---------- 发布 ---------- */
handlePublish(row) {
this.$confirm(`确定要发布教学任务「${row.rwmc || row.bh || '该记录'}」吗?发布前请先填写关联的教研室任务书。`, '系统提示', {
@@ -435,6 +467,16 @@ export default {
this.$message.success('发布成功')
this.fetchList()
}).catch(() => {})
},
handleEndPublish(row) {
this.$confirm(`确定要结束发布教学任务「${row.rwmc || row.bh || '该记录'}」吗?结束后任务书和班次课程任务将只读。`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => endPublishTeachingTask(row.bh)).then(() => {
this.$message.success('已结束发布')
this.fetchList()
}).catch(() => {})
}
}
}