教学任务书填报

This commit is contained in:
2026-09-15 14:44:16 +08:00
parent e8a58b19fe
commit 3905e5cb54
@@ -0,0 +1,502 @@
<template>
<el-dialog
:visible="visible"
:title="`教研室任务书填报${taskName ? '' + taskName : ''}`"
width="96%"
top="4vh"
:close-on-click-modal="false"
@update:visible="val => $emit('update:visible', val)"
@open="handleOpen"
>
<!-- ==================== 工具栏合班 / 拆班 ==================== -->
<div class="tb-toolbar">
<el-button
type="primary"
size="small"
icon="el-icon-connection"
:disabled="frozen || selection.length < 2"
@click="handleMerge"
>手动合班{{ selection.length }}</el-button>
<el-button
type="primary"
size="small"
icon="el-icon-s-grid"
:disabled="frozen || !selection.length"
@click="handleMergeByPreset"
>按预设合班{{ selection.length }}</el-button>
<el-button
type="warning"
size="small"
icon="el-icon-scissors"
:disabled="frozen || !selection.length"
@click="handleSplit"
>拆班</el-button>
<el-button size="small" icon="el-icon-refresh" @click="loadRows">刷新</el-button>
<span v-if="frozen" class="tb-frozen-tip">教学任务未发布或已结束当前只读</span>
<span class="tb-tip">合班规则科目学时课类型成绩分制必须相同同合班行同色连显</span>
</div>
<!-- ==================== 任务书行表格 ==================== -->
<el-table
v-loading="loading"
:data="rows"
border
stripe
max-height="540"
class="tb-table"
@selection-change="val => selection = val"
>
<template slot="empty">
<span>无数据!</span>
</template>
<el-table-column type="selection" width="42" align="center" :selectable="row => !frozen" />
<el-table-column prop="kcmc" label="课程" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="xydmc" label="学员队" min-width="150" align="center" show-overflow-tooltip />
<el-table-column prop="klx" label="课类型" width="80" align="center" />
<el-table-column prop="xs" label="学时" width="60" align="center" />
<el-table-column prop="zks" label="周课时" width="65" align="center" />
<el-table-column prop="cjfz" label="成绩分制" width="80" align="center" />
<el-table-column label="责任教员" width="110" align="center">
<template slot-scope="{ row }">{{ row.jyxm || '-' }}</template>
</el-table-column>
<el-table-column label="场地" width="130" align="center" show-overflow-tooltip>
<template slot-scope="{ row }">{{ row.jsmc || row.jsbh || '-' }}</template>
</el-table-column>
<el-table-column prop="kcxh" label="课次" width="60" align="center" />
<el-table-column label="合班" width="86" align="center">
<template slot-scope="{ row }">
<el-tag v-if="row.bz2 && row.bz2 !== 0" size="small" :color="groupColor(row.bz2)" class="tb-group-tag">
组{{ row.bz2 }}
</el-tag>
<span v-else class="tb-unmerged">未合班</span>
</template>
</el-table-column>
<el-table-column prop="jysmc" label="教研室" width="120" align="center" show-overflow-tooltip />
<el-table-column label="计划教员" width="100" align="center">
<template slot-scope="{ row }">{{ row.jysjhjyxm || '-' }}</template>
</el-table-column>
<el-table-column prop="jysjhbz" label="排课建议" min-width="120" align="center" show-overflow-tooltip />
<el-table-column label="操作" width="210" align="center" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" size="small" :disabled="frozen" @click="openTeacher(row)">指定教员</el-button>
<el-button type="text" size="small" :disabled="frozen" @click="openRoom(row)">指定场地</el-button>
<el-button type="text" size="small" :disabled="frozen" @click="openFill(row)">填报</el-button>
</template>
</el-table-column>
</el-table>
<div slot="footer">
<el-button @click="$emit('update:visible', false)">关 闭</el-button>
</div>
<!-- ==================== 指定教员子对话框 ==================== -->
<el-dialog
:visible.sync="teacherDialog.visible"
title="指定责任教员"
width="480px"
append-to-body
:close-on-click-modal="false"
>
<el-form label-width="110px">
<el-form-item label="指定方式">
<el-radio-group v-model="teacherDialog.mode" @change="loadTeacherOptions">
<el-radio label="unit">责任单位教员</el-radio>
<el-radio label="academy">全院教员</el-radio>
<el-radio label="plan">应用教研室计划教员</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="teacherDialog.mode !== 'plan'" label="教员">
<el-select
v-model="teacherDialog.jybh"
filterable
placeholder="请选择教员"
style="width: 100%"
:loading="teacherDialog.loading"
>
<el-option
v-for="t in teacherDialog.teachers"
:key="t.jybh"
:label="`${t.jyxm || t.jybh}${t.jysmc ? '' + t.jysmc + '' : ''}`"
:value="t.jybh"
/>
</el-select>
</el-form-item>
<el-form-item v-else label="计划教员">
<span>{{ teacherDialog.planTeacherName || '(该行没有教研室计划教员)' }}</span>
</el-form-item>
<el-form-item v-if="teacherDialog.row && teacherDialog.row.bz2 && teacherDialog.row.bz2 !== 0" label="同步范围">
<span class="tb-tip">该行已合班(组{{ teacherDialog.row.bz2 }}),保存后同组其它行同步</span>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="teacherDialog.visible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="submitTeacher">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 指定场地子对话框 ==================== -->
<el-dialog
:visible.sync="roomDialog.visible"
title="指定场地"
width="480px"
append-to-body
:close-on-click-modal="false"
>
<el-form label-width="110px">
<el-form-item label="场地类型">
<el-radio-group v-model="roomDialog.useSpecial">
<el-radio :label="true">班次专用教室</el-radio>
<el-radio :label="false">其它教室</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="!roomDialog.useSpecial" label="教室">
<el-select
v-model="roomDialog.jsbh"
filterable
placeholder="请选择教室"
style="width: 100%"
:loading="roomDialog.loading"
>
<el-option
v-for="c in roomDialog.classrooms"
:key="c.jsbh || c.id"
:label="c.jsmc"
:value="c.jsbh || c.id"
/>
</el-select>
</el-form-item>
<el-form-item v-if="roomDialog.row && roomDialog.row.bz2 && roomDialog.row.bz2 !== 0" label="同步范围">
<span class="tb-tip">该行已合班(组{{ roomDialog.row.bz2 }}),保存后同组其它行同步</span>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="roomDialog.visible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="submitRoom">确定</el-button>
</div>
</el-dialog>
<!-- ==================== 填报子对话框 ==================== -->
<el-dialog
:visible.sync="fillDialog.visible"
title="教研室填报"
width="520px"
append-to-body
:close-on-click-modal="false"
>
<el-form label-width="110px">
<el-form-item label="课程 / 学员队">
<span>{{ fillDialog.row ? `${fillDialog.row.kcmc} / ${fillDialog.row.xydmc}` : '' }}</span>
</el-form-item>
<el-form-item label="计划教员">
<el-select
v-model="fillDialog.jysjhjybh"
filterable
clearable
placeholder="请选择教研室计划教员"
style="width: 100%"
:loading="fillDialog.loading"
>
<el-option
v-for="t in fillDialog.teachers"
:key="t.jybh"
:label="t.jyxm || t.jybh"
:value="t.jybh"
/>
</el-select>
</el-form-item>
<el-form-item label="场地">
<el-checkbox v-model="fillDialog.useSpecial" style="margin-right: 12px">班次专用教室</el-checkbox>
<el-select
v-if="!fillDialog.useSpecial"
v-model="fillDialog.jsbh"
filterable
clearable
placeholder="请选择教室"
style="width: 260px"
:loading="fillDialog.loading"
>
<el-option
v-for="c in fillDialog.classrooms"
:key="c.jsbh || c.id"
:label="c.jsmc"
:value="c.jsbh || c.id"
/>
</el-select>
</el-form-item>
<el-form-item label="排课建议">
<el-input
v-model="fillDialog.jysjhbz"
type="textarea"
:rows="3"
placeholder="填写排课建议教研室计划备注"
/>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="fillDialog.visible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="submitFill">保存</el-button>
</div>
</el-dialog>
</el-dialog>
</template>
<script>
/**
* 教研室任务书填报对话框(阶段 4)
* 对接 /taskBookFill/*:列表(课程→责任教员→课次排序、合班分组号同色)、
* 手动合班 / 按预设合班 / 拆班、责任教员三种指定、场地指定、填报字段。
*/
import {
taskBookList,
taskBookMerge,
taskBookMergeByPreset,
taskBookSplit,
taskBookSetTeacher,
taskBookSetRoom,
taskBookFill
} from '@/api/teachBusiness/taskBook'
import { listTeacher } from '@/api/teachOffice/teacher'
import { listClassroom } from '@/api/teachBusiness/classroom'
export default {
name: 'TaskBookFillDialog',
props: {
visible: { type: Boolean, default: false },
jxrwbh: { type: String, default: '' },
taskName: { type: String, default: '' },
/** 任务状态:非「已发布/已发布(旧值)」或已结束时只读 */
taskStatus: { type: String, default: '' }
},
data() {
return {
loading: false,
saving: false,
rows: [],
selection: [],
teacherDialog: {
visible: false,
mode: 'unit',
jybh: '',
teachers: [],
loading: false,
row: null
},
roomDialog: {
visible: false,
useSpecial: true,
jsbh: '',
classrooms: [],
loading: false,
row: null
},
fillDialog: {
visible: false,
jysjhjybh: '',
jsbh: '',
jysjhbz: '',
useSpecial: false,
teachers: [],
classrooms: [],
loading: false,
row: null
}
}
},
computed: {
frozen() {
return this.taskStatus !== '发布' && this.taskStatus !== '已发布'
}
},
methods: {
handleOpen() {
this.loadRows()
},
loadRows() {
if (!this.jxrwbh) return
this.loading = true
taskBookList(this.jxrwbh)
.then(res => {
this.rows = res.data || []
this.selection = []
})
.finally(() => {
this.loading = false
})
},
/** 合班分组同色:按组号取色相 */
groupColor(bz2) {
if (!bz2) return ''
const hue = (bz2 * 47) % 360
return `hsl(${hue}, 65%, 82%)`
},
reload(after) {
return Promise.resolve(after)
.then(() => {
this.$message.success('操作成功')
this.loadRows()
})
.catch(err => {
this.$message.error((err && err.message) || '操作失败')
})
},
handleMerge() {
const bhList = this.selection.map(r => r.bh)
if (bhList.length < 2) {
this.$message.warning('请至少选择两行进行合班')
return
}
this.$confirm(`把选中的 ${bhList.length} 行合成一个班?`, '手动合班', { type: 'warning' })
.then(() => this.reload(taskBookMerge(bhList).then(res => {
this.$message.info(`已合入编组 ${res.data}`)
})))
.catch(() => {})
},
handleMergeByPreset() {
const bhList = this.selection.map(r => r.bh)
this.$confirm('按课程科目分组、组内按预设编班号合班?', '按预设合班', { type: 'warning' })
.then(() => this.reload(taskBookMergeByPreset(bhList).then(res => {
const groups = res.data || []
this.$message.info(`已形成 ${groups.length} 个合班组`)
})))
.catch(() => {})
},
handleSplit() {
const bhList = this.selection.map(r => r.bh)
this.$confirm(`拆开选中的 ${bhList.length} 行(编组置 0)?`, '拆班', { type: 'warning' })
.then(() => this.reload(taskBookSplit(bhList)))
.catch(() => {})
},
// ---------- 指定教员 ----------
openTeacher(row) {
this.teacherDialog.row = row
this.teacherDialog.mode = 'unit'
this.teacherDialog.jybh = row.jybh || ''
this.teacherDialog.visible = true
this.loadTeacherOptions()
},
loadTeacherOptions() {
const row = this.teacherDialog.row
if (!row) return
const params = this.teacherDialog.mode === 'unit' ? { jysdh: row.jysdh } : {}
this.teacherDialog.loading = true
listTeacher(params)
.then(res => {
this.teacherDialog.teachers = (res.data && (res.data.records || res.data.rows || res.data)) || []
})
.finally(() => {
this.teacherDialog.loading = false
})
},
submitTeacher() {
const d = this.teacherDialog
taskBookSetTeacher({ bh: d.row.bh, mode: d.mode, jybh: d.mode === 'plan' ? undefined : d.jybh })
.then(res => {
d.visible = false
this.reload(Promise.resolve(res))
})
.catch(err => this.$message.error((err && err.message) || '保存失败'))
},
// ---------- 指定场地 ----------
openRoom(row) {
this.roomDialog.row = row
this.roomDialog.useSpecial = true
this.roomDialog.jsbh = ''
this.roomDialog.visible = true
if (!this.roomDialog.classrooms.length) {
this.roomDialog.loading = true
listClassroom({ pageNum: 1, pageSize: 200 })
.then(res => {
const data = res.data || {}
this.roomDialog.classrooms = data.records || data.rows || data.list || []
})
.finally(() => {
this.roomDialog.loading = false
})
}
},
submitRoom() {
const d = this.roomDialog
taskBookSetRoom({ bh: d.row.bh, jsbh: d.jsbh, useSpecial: d.useSpecial })
.then(res => {
d.visible = false
this.reload(Promise.resolve(res))
})
.catch(err => this.$message.error((err && err.message) || '保存失败'))
},
// ---------- 填报 ----------
openFill(row) {
this.fillDialog.row = row
this.fillDialog.jysjhjybh = row.jysjhjybh || ''
this.fillDialog.jsbh = row.jsbh || ''
this.fillDialog.jysjhbz = row.jysjhbz || ''
this.fillDialog.useSpecial = false
this.fillDialog.visible = true
this.fillDialog.loading = true
Promise.all([
listTeacher({ jysdh: row.jysdh }).catch(() => null),
this.roomDialog.classrooms.length
? Promise.resolve(null)
: listClassroom({ pageNum: 1, pageSize: 200 }).catch(() => null)
])
.then(([tRes, cRes]) => {
if (tRes) this.fillDialog.teachers = (tRes.data && (tRes.data.records || tRes.data.rows || tRes.data)) || []
if (cRes) {
const data = cRes.data || {}
this.roomDialog.classrooms = data.records || data.rows || data.list || []
}
this.fillDialog.classrooms = this.roomDialog.classrooms
})
.finally(() => {
this.fillDialog.loading = false
})
},
submitFill() {
const d = this.fillDialog
const base = {
bh: d.row.bh,
jysjhjybh: d.jysjhjybh || '',
jysjhbz: d.jysjhbz || ''
}
// 专用教室走 setRoom(useSpecial) 才能取到班次专用教室编号;其它教室随 fill 同步
const call = d.useSpecial
? taskBookFill(base).then(() => taskBookSetRoom({ bh: d.row.bh, jsbh: '', useSpecial: true }))
: taskBookFill({ ...base, jsbh: d.jsbh || undefined })
call
.then(res => {
d.visible = false
this.reload(Promise.resolve(res))
})
.catch(err => this.$message.error((err && err.message) || '保存失败'))
}
}
}
</script>
<style scoped>
.tb-toolbar {
margin-bottom: 12px;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
}
.tb-tip {
color: #909399;
font-size: 12px;
margin-left: 8px;
}
.tb-frozen-tip {
color: #e6a23c;
font-size: 12px;
margin-left: 8px;
font-weight: bold;
}
.tb-group-tag {
border: none;
color: #303133;
}
.tb-unmerged {
color: #c0c4cc;
font-size: 12px;
}
</style>