Files
education/frontend/src/views/teachOffice/shiftSemester/index.vue
T

1057 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="app-container shift-semester-page">
<!-- ==================== 页面标题 ==================== -->
<div class="page-title">班次学期信息管理</div>
<!-- ==================== 1. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="班次编号">
<el-input
v-model="searchForm.xydbh"
placeholder="请输入班次编号(模糊)"
clearable
style="width: 220px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="年度">
<el-select
v-model="filterYear"
placeholder="全部年度"
clearable
filterable
style="width: 140px"
@change="handleQuery"
>
<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>
</el-form-item>
</el-form>
</el-card>
<!-- ==================== 2. 班次学期信息列表 ==================== -->
<el-card shadow="never" class="table-card">
<div class="list-header">
<div class="list-title">班次学期列表</div>
<div class="list-actions">
<el-button type="primary" icon="el-icon-plus" @click="handleBatchAdd">批量创建班次学期</el-button>
<el-button type="primary" plain icon="el-icon-document-add" @click="handleAdd">新增</el-button>
<el-button type="primary" plain icon="el-icon-edit" :disabled="selection.length !== 1" @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="selection.length !== 1" @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>
<el-button type="success" plain icon="el-icon-date" :disabled="selection.length !== 1" @click="handleEditEventCalendar()">编辑班历</el-button>
<el-button type="success" plain icon="el-icon-calendar" :disabled="selection.length !== 1" @click="handleEditCalendar()">编辑班次课程</el-button>
<el-button type="success" plain icon="el-icon-data-line" :disabled="selection.length !== 1" @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>
</div>
<el-table
ref="tableRef"
v-loading="loading"
:data="displayData"
border
stripe
highlight-current-row
size="small"
max-height="480"
class="class-table"
:row-class-name="waveRowClass"
@selection-change="handleSelectionChange"
>
<template slot="empty">
<span>无数据</span>
</template>
<el-table-column type="selection" width="44" align="center" />
<el-table-column type="index" label="序号" width="55" align="center" />
<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 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>
<el-tag v-else type="info" size="small">关闭</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="310" align="center" fixed="right" class-name="table-action-column">
<template slot-scope="{ row }">
<el-button type="text" size="small" @click="handleEdit(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" @click="handleSchedule(row)">排课</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDeleteRow(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="list-pagination"
:current-page="pageNum"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</el-card>
<!-- ==================== 班次学期新增/编辑弹窗 ==================== -->
<ClassSemesterDetailDialog
:visible="detailVisible"
:mode="detailMode"
:semester="detailSemester"
:team-info="detailTeamInfo"
:year-options="yearOptions"
:task-options="taskOptions"
:preset-dates="presetDates"
@update:visible="val => detailVisible = val"
@submit="handleDetailSubmit"
/>
<!-- ==================== 班次课程弹窗 ==================== -->
<ClassCalendarDialog
:visible="calendarVisible"
:semester="calendarSemester"
:semester-options="semesterOptions"
@update:visible="val => calendarVisible = val"
/>
<ClassEventCalendarDialog
:visible="eventCalendarVisible"
:semester="eventCalendarSemester"
:semester-options="semesterOptions"
@update:visible="val => eventCalendarVisible = val"
/>
<TeachingAllocationDialog
:visible="allocationVisible"
:semester="allocationSemester"
@update:visible="val => allocationVisible = val"
/>
<!-- ==================== 批量创建班次学期弹窗 ==================== -->
<el-dialog
:visible="batchAddVisible"
title="批量创建班次学期"
width="520px"
:close-on-click-modal="false"
@update:visible="val => batchAddVisible = val"
>
<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%" @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%" @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">
<el-date-picker v-model="batchAddForm.startTime" type="date" value-format="yyyy-MM-dd" placeholder="请选择开始日期" style="width: 100%" />
</el-form-item>
<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="selection.length !== 1" @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>
<el-button type="primary" :loading="saving" @click="handleBatchAddSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 快速设定学期日期弹窗 ==================== -->
<el-dialog
:visible="dateRangeVisible"
title="快速设定学期日期"
width="480px"
:close-on-click-modal="false"
@update:visible="val => dateRangeVisible = val"
>
<el-alert
type="info"
:closable="false"
show-icon
:title="`将对选中的 ${selection.length} 条班次学期统一设定开学/结束日期`"
style="margin-bottom: 16px"
/>
<el-form ref="dateRangeFormRef" :model="dateRangeForm" :rules="dateRangeRules" label-width="100px">
<el-form-item label="开学日期" prop="kxrq">
<el-date-picker v-model="dateRangeForm.kxrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择开学日期" style="width: 100%" />
</el-form-item>
<el-form-item label="结束日期" prop="jsrq">
<el-date-picker v-model="dateRangeForm.jsrq" type="date" value-format="yyyy-MM-dd" placeholder="请选择结束日期" style="width: 100%" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dateRangeVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleDateRangeSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 批量设置教学任务弹窗 ==================== -->
<el-dialog
:visible="jxrwVisible"
title="批量设置教学任务"
width="480px"
:close-on-click-modal="false"
@update:visible="val => jxrwVisible = val"
>
<el-alert
type="info"
:closable="false"
show-icon
:title="`将对选中的 ${selection.length} 条班次学期统一设置教学任务`"
style="margin-bottom: 16px"
/>
<el-form ref="jxrwFormRef" :model="jxrwForm" :rules="jxrwRules" label-width="100px">
<el-form-item label="教学任务" prop="jxrwbh">
<el-select v-model="jxrwForm.jxrwbh" placeholder="请选择教学任务" filterable style="width: 100%">
<el-option v-for="opt in taskOptions" :key="opt.bh" :label="opt.rwmc" :value="opt.bh" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="jxrwVisible = false">取消</el-button>
<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 ClassEventCalendarDialog from './ClassEventCalendarDialog.vue'
import TeachingAllocationDialog from './TeachingAllocationDialog.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'
import { allocationExport } from '@/api/teachBusiness/allocation'
import { publishRunningCourses, withdrawRunningCourses } from '@/api/teachBusiness/schedulingWindow'
export default {
name: 'ShiftSemester',
components: {
ClassSemesterDetailDialog,
ClassCalendarDialog,
ClassEventCalendarDialog,
TeachingAllocationDialog
},
data() {
return {
loading: false,
saving: false,
/** 运行课表批量发布/删除进行中 */
runningPublishing: false,
// ==================== 查询条件 ====================
searchForm: { xydbh: '', xqdc: undefined },
filterYear: undefined,
sortWave: true,
presetDates: { kxrq: '', jsrq: '' },
// ==================== 列表与分页 ====================
tableData: [],
selection: [],
pageNum: 1,
pageSize: 10,
total: 0,
// ==================== 下拉数据 ====================
/** 年度下拉(来自 /semester/all */
yearOptions: [],
/** 教学任务下拉({ bh, rwmc } */
taskOptions: [],
/** 全部班次学期(用于班历复制目标选择) */
semesterOptions: [],
// ==================== 班次学期新增/编辑 ====================
detailVisible: false,
detailMode: 'add',
detailSemester: null,
detailTeamInfo: null,
// ==================== 班历管理 ====================
calendarVisible: false,
calendarSemester: null,
eventCalendarVisible: false,
eventCalendarSemester: null,
// ==================== 教学配当 ====================
allocationVisible: false,
allocationSemester: null,
// ==================== 批量创建班次学期 ====================
batchAddVisible: false,
batchAddForm: { nd: undefined, xqdc: undefined, startTime: '', endTime: '' },
batchEligibleCount: 0,
batchAddRules: {
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
xqdc: [{ required: true, message: '请选择学期', trigger: 'change' }],
startTime: [{ required: true, message: '请选择开始日期', trigger: 'change' }],
endTime: [{ required: true, message: '请选择结束日期', trigger: 'change' }]
},
// ==================== 快速设定学期日期 ====================
dateRangeVisible: false,
dateRangeForm: { kxrq: '', jsrq: '' },
dateRangeRules: {
kxrq: [{ required: true, message: '请选择开学日期', trigger: 'change' }],
jsrq: [{ required: true, message: '请选择结束日期', trigger: 'change' }]
},
// ==================== 批量设置教学任务 ====================
jxrwVisible: false,
jxrwForm: { jxrwbh: undefined },
jxrwRules: {
jxrwbh: [{ required: true, message: '请选择教学任务', trigger: 'change' }]
},
xqdcVisible: false,
xqdcForm: { xqdc: 1 },
kfjypkVisible: false,
kfjypkForm: { kfjypk: 1 }
}
},
computed: {
displayData() {
return this.tableData
}
},
mounted() {
this.loadYearOptions()
this.loadTaskOptions()
this.loadSemesterOptions()
this.loadList()
},
methods: {
/* ==================== 格式化 ==================== */
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学期'
if (val === 2 || val === '2') return '第2学期'
if (val === 3 || val === '3') return '第3学期'
return val !== undefined && val !== null && val !== '' ? `${val}学期` : '-'
},
/** 教学任务编号 → 名称 */
taskName(bh) {
if (!bh) return '-'
const opt = this.taskOptions.find(o => o.bh === bh)
return opt ? opt.rwmc : bh
},
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
return payload
},
/* ==================== 下拉数据加载 ==================== */
loadYearOptions() {
listAllSemester().then(res => {
const list = (res && res.data) || []
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 = []
})
},
loadTaskOptions() {
listTeachingTask({ pageNum: 1, pageSize: 1000 }).then(res => {
const list = (res && res.data && res.data.records) || []
this.taskOptions = list.filter(o => o.bh)
}).catch(() => {
this.taskOptions = []
})
},
loadSemesterOptions() {
allSemester().then(res => {
this.semesterOptions = (res && res.data) || []
}).catch(() => {
this.semesterOptions = []
})
},
/* ==================== 列表加载 ==================== */
loadList() {
this.loading = true
const query = this.cleanPayload({
pageNum: this.pageNum,
pageSize: this.pageSize,
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.loading = false
// fixed 克隆层在异步数据回填后需要重排,否则行位置与主表错位会透出下层文字
this.$nextTick(() => {
this.$refs.tableRef && this.$refs.tableRef.doLayout()
})
}).catch(() => {
this.tableData = []
this.total = 0
this.loading = false
})
},
handleSizeChange(size) {
this.pageSize = size
this.loadList()
},
handlePageChange(page) {
this.pageNum = page
this.loadList()
},
handleSelectionChange(rows) {
this.selection = rows
},
/* ==================== 查询 / 重置 ==================== */
handleQuery() {
this.pageNum = 1
this.loadList()
},
handleReset() {
this.searchForm.xydbh = ''
this.searchForm.xqdc = undefined
this.filterYear = undefined
this.pageNum = 1
this.loadList()
},
/* ==================== 新增 / 编辑 ==================== */
handleAdd() {
this.detailMode = 'add'
this.detailSemester = null
this.detailTeamInfo = null
this.detailVisible = true
},
handleEdit(row) {
const target = row || this.selection[0]
if (!target) {
this.$message.warning('请先选择一条班次学期信息')
return
}
this.detailMode = 'edit'
this.detailSemester = target
this.detailTeamInfo = null
this.detailVisible = true
},
handleDetailSubmit(payload) {
this.saving = true
const req = this.detailMode === 'edit'
? updateSemester(payload)
: addSemester(payload)
req.then(() => {
this.saving = false
this.detailVisible = false
this.$message.success(this.detailMode === 'edit' ? '班次学期已更新' : '班次学期已新增')
this.loadList()
this.loadSemesterOptions()
}).catch(() => {
this.saving = false
})
},
/* ==================== 删除 ==================== */
handleDelete() {
if (!this.selection.length) {
this.$message.warning('请先勾选要删除的班次学期')
return
}
this.$confirm(`确定删除选中的 ${this.selection.length} 条班次学期吗?若已有课程任务将禁止删除。`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const bhList = this.selection.map(r => r.bh)
batchDeleteSemester(bhList).then(() => {
this.$message.success('已删除')
this.loadList()
this.loadSemesterOptions()
})
}).catch(() => {})
},
handleDeleteRow(row) {
this.$confirm(`确定删除班次学期「${row.xydbh || row.bh}」吗?若已有课程任务将禁止删除。`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delSemester(row.bh).then(() => {
this.$message.success('已删除')
this.loadList()
this.loadSemesterOptions()
})
}).catch(() => {})
},
handleEditEventCalendar(row) {
const target = row || this.selection[0]
if (!target) {
this.$message.warning('请先在列表中选择一条班次学期信息')
return
}
this.eventCalendarSemester = target
this.eventCalendarVisible = true
},
// 跳转到排课窗口,并带上当前班次学期编号
handleSchedule(row) {
const target = row || this.selection[0]
if (!target) {
this.$message.warning('请先在列表中选择一条班次学期信息')
return
}
const bh = target.bh || target.xydxqbh
if (!bh) {
this.$message.warning('该班次学期缺少编号,无法进入排课窗口')
return
}
this.$router.push({ path: '/schedule/schedulingWindow', query: { xydxqbh: bh } })
},
handleAllocation(row) {
const target = row || this.selection[0]
if (!target) {
this.$message.warning('请先在列表中选择一条班次学期信息')
return
}
this.allocationSemester = target
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()
if (!rows.length) {
this.$message.warning('请先勾选班次学期')
return
}
const ids = rows.map(item => item.bh || item.xydxqbh).filter(Boolean)
this.$confirm(
`将发布所选 ${ids.length} 个班次学期的课程到运行课表。<br/>发布前要求:教学任务已发布未结束、每门课程已指定责任教员与默认场地。`,
'批量发布【运行课表】',
{ type: 'warning', dangerouslyUseHTMLString: true }
).then(() => {
this.batchRunningPublish(publishRunningCourses, ids, '发布')
}).catch(() => {})
},
// 批量删除【运行课表】(手册 12.2):撤回运行课程,已排课次一并删除
handleWithdrawRunning() {
const rows = this.selection.slice()
if (!rows.length) {
this.$message.warning('请先勾选班次学期')
return
}
const ids = rows.map(item => item.bh || item.xydxqbh).filter(Boolean)
this.$confirm(
`将从运行课表删除所选 ${ids.length} 个班次学期的全部课程。<br/><b>这些班次的排课信息(已排课次)将一并删除</b>,编制侧任务保留。`,
'批量删除【运行课表】',
{ type: 'warning', dangerouslyUseHTMLString: true }
).then(() => {
this.batchRunningPublish(withdrawRunningCourses, ids, '删除')
}).catch(() => {})
},
// 批量发布/删除公共处理:逐条展示成功与失败原因
batchRunningPublish(api, ids, action) {
this.runningPublishing = true
api(ids).then(res => {
const list = (res && res.data) || []
const ok = list.filter(i => i.success)
const fail = list.filter(i => !i.success)
const okText = ok.length
? `成功 ${ok.length} 个:` + ok.map(i => `${this.semesterNameOf(i.xydxqbh)}${i.message}`).join('')
: ''
const failText = fail.length
? `失败 ${fail.length} 个:` + fail.map(i => `${this.semesterNameOf(i.xydxqbh)}${i.message}`).join('')
: ''
const html = [okText, failText].filter(Boolean).join('<br/>')
if (fail.length) {
this.$alert(html, `运行课表${action}结果`, { dangerouslyUseHTMLString: true })
} else {
this.$message.success(`运行课表${action}完成:成功 ${ok.length}`)
}
this.loadList()
}).finally(() => {
this.runningPublishing = false
})
},
semesterNameOf(bh) {
const row = this.selection.find(i => (i.bh || i.xydxqbh) === bh)
return row ? (row.xydmc || row.bh) : bh
},
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.selection[0]
if (!target) {
this.$message.warning('请先在列表中选择一条班次学期信息')
return
}
this.calendarSemester = target
this.calendarVisible = true
},
/* ==================== 批量创建班次学期 ==================== */
handleBatchAdd() {
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.selection.length !== 1) return
this.absorbDatesFrom(this.selection[0])
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
batchCreateSemester({
nd: f.nd,
xqdc: f.xqdc,
kxrq: f.startTime,
jsrq: f.endTime
}).then(res => {
this.saving = false
this.batchAddVisible = false
const count = Number((res && res.data) || 0)
this.$message.success(`已创建 ${count} 个班次学期`)
this.loadList()
this.loadSemesterOptions()
}).catch(() => {
this.saving = false
})
})
},
handleAbsorbDates() {
if (this.selection.length !== 1) {
this.$message.warning('请勾选一条班次学期')
return
}
this.absorbDatesFrom(this.selection[0])
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() {
if (!this.selection.length) {
this.$message.warning('请先勾选要设定日期的班次学期')
return
}
this.dateRangeForm = {
kxrq: this.presetDates.kxrq || '',
jsrq: this.presetDates.jsrq || ''
}
this.dateRangeVisible = true
this.$nextTick(() => {
if (this.$refs.dateRangeFormRef) this.$refs.dateRangeFormRef.clearValidate()
})
},
handleDateRangeSubmit() {
this.$refs.dateRangeFormRef.validate(valid => {
if (!valid) return
const f = this.dateRangeForm
this.saving = true
batchUpdateDateRange({
bhList: this.selection.map(r => r.bh),
xqkssj: f.kxrq,
xqjssj: f.jsrq
}).then(res => {
this.saving = false
this.dateRangeVisible = false
const count = (res && res.data) || 0
this.$message.success(`已更新 ${count} 条班次学期的日期`)
this.loadList()
}).catch(() => {
this.saving = false
})
})
},
/* ==================== 批量设置教学任务 ==================== */
handleSetJxrw() {
if (!this.selection.length) {
this.$message.warning('请先勾选要设置教学任务的班次学期')
return
}
this.jxrwForm = { jxrwbh: undefined }
this.jxrwVisible = true
this.$nextTick(() => {
if (this.$refs.jxrwFormRef) this.$refs.jxrwFormRef.clearValidate()
})
},
handleJxrwSubmit() {
this.$refs.jxrwFormRef.validate(valid => {
if (!valid) return
this.saving = true
batchUpdateJxrwbh({
bhList: this.selection.map(r => r.bh),
jxrwbh: this.jxrwForm.jxrwbh
}).then(res => {
this.saving = false
this.jxrwVisible = false
const count = (res && res.data) || 0
this.$message.success(`已更新 ${count} 条班次学期的教学任务`)
this.loadList()
}).catch(() => {
this.saving = false
})
})
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.shift-semester-page {
// ========== 页面标题 ==========
.page-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-left: 10px;
border-left: 4px solid var(--edu-green-primary);
}
// ========== 1. 查询条件区域 ==========
.search-card {
margin-bottom: 16px;
}
// ========== 2. 数据表格区 ==========
.table-card {
.list-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 16px;
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
white-space: nowrap;
}
.list-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
.el-button + .el-button {
margin-left: 0;
}
}
}
.class-table {
width: 100%;
}
.text-danger {
color: #f56c6c;
}
.list-pagination {
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>