Files
education/frontend/src/views/teachOffice/shiftSemester/ClassCalendarDialog.vue
T
2026-08-25 17:17:00 +08:00

752 lines
26 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>
<el-dialog
:visible="dialogVisible"
:title="`班历管理——${semesterTitle}`"
width="1200px"
top="6vh"
:close-on-click-modal="false"
class="class-calendar-dialog"
@update:visible="val => dialogVisible = val"
>
<div v-loading="loading" class="class-calendar">
<!-- ==================== 1. 学期与班次信息 ==================== -->
<div class="info-panel">
<div class="info-title-row">
<span class="semester-name">{{ semesterTitle }}</span>
<span class="class-name">{{ className }}</span>
</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>
</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 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" @click="handleApplyRegion">选定区域应用于其它班次</el-button>
<el-button size="small" @click="handleApplyWhole">整个班历应用于其它班次</el-button>
<el-button size="small" icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
</div>
</div>
<!-- ==================== 3. 班历记录表格 ==================== -->
<div class="table-area">
<el-table
ref="tableRef"
:data="records"
border
stripe
size="small"
max-height="480"
class="calendar-table"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="kcxh" label="课次序号" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="jhkcxh" label="计划课次" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="jc" label="简称" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="kbh" label="课编号" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="klx" label="课类型" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="jybh" label="教员编号" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="jsbh" label="教室编号" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="zks" label="周课时" width="70" align="center" show-overflow-tooltip />
<el-table-column prop="xs" label="学时" width="60" align="center" show-overflow-tooltip />
<el-table-column prop="llxs" label="理论学时" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="sjxs" label="实践学时" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="xf" label="学分" width="60" align="center" show-overflow-tooltip />
<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 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 }">
<el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="text" size="small" class="text-danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="select-tip">
提示:勾选多条记录后可进行 预设合班 / 预设拆班 / 选定区域应用于其它班次 操作;删除为物理删除,请谨慎操作。
</div>
</div>
</div>
<div slot="footer">
<el-button @click="dialogVisible = false">关闭</el-button>
</div>
<!-- ==================== 班历记录新增/编辑弹窗 ==================== -->
<el-dialog
:visible="editVisible"
:title="editTitle"
width="760px"
:close-on-click-modal="false"
append-to-body
@update:visible="val => editVisible = val"
>
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="120px" size="small" class="edit-form">
<div class="form-section-title">基本信息</div>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="简称" prop="jc">
<el-input v-model="editForm.jc" placeholder="课程简称" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="课编号">
<el-input v-model="editForm.kbh" placeholder="课程编号" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="课类型">
<el-input v-model="editForm.klx" placeholder="如 必修/选修/实践" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="课次序号">
<el-input-number v-model="editForm.kcxh" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="计划课次序号">
<el-input-number v-model="editForm.jhkcxh" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="序号标识">
<el-input-number v-model="editForm.xhbs" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教员编号">
<el-input v-model="editForm.jybh" placeholder="教员编号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教室编号">
<el-input v-model="editForm.jsbh" placeholder="教室编号" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="人数">
<el-input-number v-model="editForm.rs" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="周课时">
<el-input-number v-model="editForm.zks" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<div class="form-section-title">学时学分</div>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="学时">
<el-input-number v-model="editForm.xs" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="理论学时">
<el-input-number v-model="editForm.llxs" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="实践学时">
<el-input-number v-model="editForm.sjxs" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学分">
<el-input-number v-model="editForm.xf" :min="0" :precision="1" :step="0.5" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<div class="form-section-title">考试信息</div>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="考试课时">
<el-input-number v-model="editForm.ksks" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="考试编号">
<el-input v-model="editForm.ksbh" placeholder="考试编号" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="考试地点">
<el-input v-model="editForm.ksdd" placeholder="考试地点" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成绩分制">
<el-input v-model="editForm.cjfz" placeholder="如 百分制/等级制" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="不计入平均分">
<el-select v-model="editForm.bjrxypjf" clearable style="width: 100%">
<el-option label="是" :value="1" />
<el-option label="否" :value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="考试课时不显示">
<el-select v-model="editForm.ksksbxs" clearable style="width: 100%">
<el-option label="是" :value="1" />
<el-option label="否" :value="0" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<div class="form-section-title">教研室计划</div>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教研室代号">
<el-input v-model="editForm.jysdh" placeholder="教研室代号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="计划教员编号">
<el-input v-model="editForm.jysjhjybh" placeholder="教研室计划教员编号" clearable />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="计划备注">
<el-input v-model="editForm.jysjhbz" type="textarea" :rows="2" placeholder="教研室计划备注" clearable />
</el-form-item>
<div class="form-section-title">配档设置</div>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="配档序号">
<el-input-number v-model="editForm.pdxh" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="配档起始周">
<el-input-number v-model="editForm.pdqsz" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="配档按周">
<el-input-number v-model="editForm.pdaz" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="占正课时间">
<el-input-number v-model="editForm.pdzzksj" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="配档编组">
<el-input-number v-model="editForm.pdbz" :min="1" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="同班异课选修编组">
<el-select v-model="editForm.pdtbykxxbz" clearable style="width: 100%">
<el-option label="是" :value="1" />
<el-option label="否" :value="0" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="启用自定义学时">
<el-select v-model="editForm.pdqyzdyxs" clearable style="width: 100%">
<el-option label="是" :value="1" />
<el-option label="否" :value="0" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="编组">
<el-input-number v-model="editForm.bz2" :min="0" controls-position="right" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="自定义学时分布">
<el-input v-model="editForm.pdzdyxsfbsj" type="textarea" :rows="2" placeholder="配档自定义学时分布数据" clearable />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="editForm.bz" type="textarea" :rows="2" placeholder="备注" clearable />
</el-form-item>
</el-form>
<div slot="footer">
<el-button size="small" @click="editVisible = false">取消</el-button>
<el-button size="small" type="primary" :loading="saving" @click="handleEditSubmit">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 目标班次学期选取弹窗 ==================== -->
<ClassTeamSelectDialog
:visible="teamSelectVisible"
:semester-list="semesterOptions"
:exclude-bh="semester.bh"
:exclude-note="copyNote"
@update:visible="val => teamSelectVisible = val"
@confirm="handleTeamConfirm"
/>
</el-dialog>
</template>
<script>
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
import {
listClassCalendar,
updateClassCalendar,
batchCopyCalendar,
batchCopyCalendarBySemester,
addTeamTask,
delTeamTask,
batchPresetMerge,
batchPresetSplit,
autoGenerateRequiredCourses
} from '@/api/studentRecords/classCalendar'
export default {
name: 'ClassCalendarDialog',
components: { ClassTeamSelectDialog },
props: {
visible: { type: Boolean, default: false },
/** 选中的班次学期记录(含 bh/xydbh/xqdc/nd/xydmc/kxrq/jsrq) */
semester: { type: Object, default: null },
/** 可选的目标班次学期列表(已含班次名称的完整列表) */
semesterOptions: { type: Array, default: () => [] }
},
data() {
return {
loading: false,
saving: false,
records: [],
selection: [],
// ==================== 新增/编辑 ====================
editVisible: false,
editMode: 'add',
editForm: this.createEmptyEditForm(),
editRules: {
jc: [{ required: true, message: '请输入课程简称', trigger: 'blur' }]
},
// ==================== 复制到其它班次 ====================
teamSelectVisible: false,
applyMode: 'region',
copyNote: ''
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
},
safeSemester() {
return this.semester || {}
},
className() {
const s = this.safeSemester
return s.xydmc || s.xydbh || ''
},
semesterTitle() {
const s = this.safeSemester
const nd = s.nd !== undefined && s.nd !== null && s.nd !== '' ? `${s.nd}年` : ''
const xq = this.xqdcLabel(s.xqdc)
return `${nd}${xq}`
},
semesterWeeks() {
const s = this.safeSemester
if (!s.kxrq || !s.jsrq) return '-'
const start = new Date(String(s.kxrq).slice(0, 10))
const end = new Date(String(s.jsrq).slice(0, 10))
const diff = Math.round((end - start) / (1000 * 60 * 60 * 24)) + 1
return diff > 0 ? Math.ceil(diff / 7) : '-'
},
/** 班历统计:课程数 / 总学时 / 总学分 */
summary() {
const totalXs = this.records.reduce((sum, r) => sum + (Number(r.xs) || 0), 0)
const totalXf = this.records.reduce((sum, r) => sum + (Number(r.xf) || 0), 0)
return { totalXs, totalXf }
},
editTitle() {
return this.editMode === 'edit' ? '编辑班历记录' : '新增班历记录'
}
},
watch: {
visible(val) {
if (val) {
this.loadData()
}
}
},
methods: {
/* ---------- 格式化 ---------- */
fmtDate(val) {
if (!val) return '-'
return String(val).slice(0, 10)
},
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}学期` : '-'
},
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
return payload
},
/* ---------- 列表加载 ---------- */
loadData() {
const s = this.semester || {}
if (!s.bh) return
this.loading = true
listClassCalendar(s.bh).then(res => {
this.records = (res && res.data) || []
this.loading = false
}).catch(() => {
this.records = []
this.loading = false
})
},
handleSelectionChange(rows) {
this.selection = rows
},
/* ---------- 新增 / 编辑 ---------- */
createEmptyEditForm() {
const s = this.semester || {}
return {
bh: '',
nd: s.nd,
xydbh: s.xydbh || '',
xqdc: s.xqdc,
xydxqbh: s.bh || '',
jc: '',
kbh: '',
klx: '',
kcxh: undefined,
jhkcxh: undefined,
xhbs: undefined,
jybh: '',
jsbh: '',
rs: undefined,
zks: undefined,
xs: undefined,
llxs: undefined,
sjxs: undefined,
xf: undefined,
ksks: undefined,
ksbh: '',
ksdd: '',
cjfz: '',
bjrxypjf: undefined,
ksksbxs: undefined,
jysdh: '',
jysjhjybh: '',
jysjhbz: '',
pdxh: undefined,
pdqsz: undefined,
pdaz: undefined,
pdzzksj: undefined,
pdbz: undefined,
pdtbykxxbz: undefined,
pdqyzdyxs: undefined,
pdzdyxsfbsj: '',
bz2: undefined,
bz: ''
}
},
handleAdd() {
this.editMode = 'add'
this.editForm = this.createEmptyEditForm()
this.editVisible = true
this.$nextTick(() => {
if (this.$refs.editFormRef) this.$refs.editFormRef.clearValidate()
})
},
handleEdit(row) {
this.editMode = 'edit'
const s = this.semester || {}
const form = this.createEmptyEditForm()
Object.keys(form).forEach(key => {
if (row[key] !== undefined && row[key] !== null) form[key] = row[key]
})
form.xydxqbh = s.bh || row.xydxqbh
this.editForm = form
this.editVisible = true
this.$nextTick(() => {
if (this.$refs.editFormRef) this.$refs.editFormRef.clearValidate()
})
},
handleEditSubmit() {
this.$refs.editFormRef.validate(valid => {
if (!valid) return
const payload = this.cleanPayload({ ...this.editForm })
const req = this.editMode === 'edit'
? updateClassCalendar(payload)
: addTeamTask(payload)
this.saving = true
req.then(() => {
this.saving = false
this.editVisible = false
this.$message.success(this.editMode === 'edit' ? '班历记录已更新' : '班历记录已新增')
this.loadData()
}).catch(() => {
this.saving = false
})
})
},
/* ---------- 删除 ---------- */
handleDelete(row) {
this.$confirm(`确定删除该班历记录(${row.jc || row.bh})吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
delTeamTask(row.bh).then(() => {
this.$message.success('已删除')
this.loadData()
})
}).catch(() => {})
},
/* ---------- 自动生成必修课程 ---------- */
handleAutoGenerate() {
const s = this.semester || {}
if (!s.xydbh || !s.xqdc) {
this.$message.warning('当前班次学期缺少学员队编号或学期第次,无法自动生成')
return
}
this.$confirm('将按专业教学计划自动生成该班次的必修课程(已存在的课程不会重复添加),是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
autoGenerateRequiredCourses(s.xydbh, s.xqdc).then(res => {
const count = (res && res.data) || 0
this.$message.success(`已自动生成 ${count} 门必修课程`)
this.loadData()
})
}).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('请先勾选要应用的班历记录(选定区域)')
return
}
this.applyMode = 'region'
this.copyNote = `将选中的 ${bhs.length} 条班历记录复制到所选班次`
this.teamSelectVisible = true
},
handleApplyWhole() {
this.applyMode = 'whole'
this.copyNote = '将整个班历复制到所选班次'
this.teamSelectVisible = true
},
handleTeamConfirm(targets) {
if (!targets || !targets.length) return
const targetBhList = targets.map(t => t.bh)
if (this.applyMode === 'whole') {
batchCopyCalendarBySemester({
sourceXydxqbh: this.semester.bh,
targetBhList: targetBhList
}).then(res => {
const data = (res && res.data) || {}
this.$message.success(`已复制整个班历,共新增 ${data.count || 0} 条记录`)
this.loadData()
})
} else {
batchCopyCalendar({
sourceBhList: this.selection.map(r => r.bh),
targetBhList: targetBhList
}).then(res => {
const data = (res && res.data) || {}
this.$message.success(`已复制选定区域,共新增 ${data.count || 0} 条记录`)
this.loadData()
})
}
},
/* ---------- 刷新 ---------- */
handleRefresh() {
this.loadData()
this.$message.success('已刷新班历')
}
}
}
</script>
<style scoped lang="scss">
.class-calendar {
// ========== 1. 学期与班次信息 ==========
.info-panel {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 12px 20px;
margin-bottom: 12px;
.info-title-row {
display: flex;
align-items: baseline;
gap: 20px;
margin-bottom: 8px;
.semester-name,
.class-name {
font-size: 20px;
font-weight: 700;
color: #1e6fff;
}
}
.date-range-row {
display: flex;
flex-direction: column;
gap: 4px;
.range-item {
font-size: 13px;
font-weight: 600;
color: #1e6fff;
}
}
}
// ========== 2. 操作按钮区域 ==========
.settings-panel {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 12px 20px;
margin-bottom: 12px;
.action-buttons-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
}
// ========== 3. 表格区域 ==========
.table-area {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
.select-tip {
padding: 6px 12px;
font-size: 12px;
color: #909399;
border-top: 1px solid #ebeef5;
background: #fafafa;
}
}
.text-danger {
color: #f56c6c;
}
}
// ========== 弹窗内表单分区标题 ==========
.edit-form {
.form-section-title {
font-size: 13px;
font-weight: 600;
color: #303133;
margin: 6px 0 10px;
padding: 6px 10px;
background: #f0f7ff;
border-left: 3px solid #1e6fff;
}
}
</style>