后端代码

This commit is contained in:
liumengyu
2026-08-24 17:48:55 +08:00
parent e0485f7366
commit aa4145fd33
419 changed files with 3684 additions and 53031 deletions
@@ -1,784 +0,0 @@
<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">{{ semester.xydmc || semester.xydbh || '' }}</span>
</div>
<div class="date-range-row">
<div class="range-item">学期日期{{ fmtDate(semester.kxrq) }} {{ fmtDate(semester.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">
<button type="button" class="blue-btn" @click="handleAdd">新增记录</button>
<button type="button" class="gray-btn" @click="handleAutoGenerate">自动生成必修课程</button>
<button type="button" class="gray-btn" @click="handlePresetMerge">预设合班</button>
<button type="button" class="gray-btn" @click="handlePresetSplit">预设拆班</button>
<button type="button" class="gray-btn" @click="handleApplyRegion">选定区域应用于其它班次</button>
<button type="button" class="gray-btn" @click="handleApplyWhole">整个班历应用于其它班次</button>
<button type="button" class="gray-btn" @click="handleRefresh">刷新</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)
}
},
semesterTitle() {
const s = this.semester || {}
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.semester || {}
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;
}
}
// ========== 银灰色 / 蓝色边框按钮 ==========
.gray-btn {
min-width: 90px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #000;
background: linear-gradient(180deg, #fdfdfd 0%, #ececec 50%, #dcdcdc 100%);
border: 1px solid #7a7a7a;
border-radius: 2px;
cursor: pointer;
&:hover {
background: linear-gradient(180deg, #ffffff 0%, #f4f4f4 50%, #e6e6e6 100%);
}
&:active {
background: #c8c8c8;
}
}
.blue-btn {
min-width: 90px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #1e6fff;
background: #ecf5ff;
border: 1px solid #1e6fff;
border-radius: 2px;
cursor: pointer;
&:hover {
background: #d9ecff;
}
&:active {
background: #c6e2ff;
}
}
</style>