Files
education/frontend/src/views/teachOffice/taskPlan/TaskBookFillDialog.vue
T
liweijie f3ae9fb2fd 1、校历的78、晚上、夜间等直接落库,
2、配当、任务书完善、排课窗完善;
3、任务书批量发布、删除等;
4、班历同步校历
2026-09-18 10:38:39 +08:00

698 lines
24 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="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-user"
:disabled="frozen || !selection.length"
@click="openBatchTeacher"
>批量指定教员{{ selection.length }}</el-button>
<el-button
size="small"
icon="el-icon-office-building"
:disabled="frozen || !selection.length"
@click="openBatchRoom"
>批量指定场地{{ selection.length }}</el-button>
<el-button
size="small"
icon="el-icon-edit-outline"
:disabled="frozen || !selection.length"
@click="openBatchFill"
>批量填报{{ selection.length }}</el-button>
<el-button
type="danger"
size="small"
icon="el-icon-delete"
:disabled="frozen || !selection.length"
@click="handleDeleteRows"
>删除行{{ selection.length }}</el-button>
<el-button size="small" icon="el-icon-refresh" @click="loadRows">刷新</el-button>
<el-button size="small" icon="el-icon-download" @click="handleExport">导出</el-button>
<el-button size="small" icon="el-icon-printer" @click="handlePrint">打印</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 || '-' }}<span v-if="row.zr === 1" class="tb-director" title="教研室主任"> *</span>
</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="270" 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>
<el-button type="text" size="small" style="color:#f56c6c" :disabled="frozen" @click="handleDeleteRows([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 v-if="teacherDialog.batch" label="选中行">
<span>{{ selection.length }} 行将统一指定责任教员</span>
</el-form-item>
<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.batch ? '应用各行教研室计划教员' : (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 v-if="roomDialog.batch" label="选中行">
<span>{{ selection.length }} 行将统一指定场地</span>
</el-form-item>
<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="fillDialog.batch ? `批量填报(${selection.length} 行)` : '教研室填报'"
width="520px"
append-to-body
:close-on-click-modal="false"
>
<el-form label-width="110px">
<el-form-item v-if="fillDialog.batch" label="选中行">
<span>{{ selection.length }} 行将统一应用下方填写的字段(留空字段不改动)</span>
</el-form-item>
<el-form-item v-else 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-number
v-model="fillDialog.kcxh"
:min="1"
:max="999"
placeholder="课序"
style="width: 160px"
/>
</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,
taskBookBatchSetTeacher,
taskBookBatchSetRoom,
taskBookBatchFill,
taskBookDeleteRows,
taskBookFill
} from '@/api/teachBusiness/taskBook'
import { exportTaskBook } from '@/api/teachBusiness/teachingTask'
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,
batch: false,
mode: 'unit',
jybh: '',
teachers: [],
loading: false,
row: null
},
roomDialog: {
visible: false,
batch: false,
useSpecial: true,
jsbh: '',
classrooms: [],
loading: false,
row: null
},
fillDialog: {
visible: false,
batch: false,
jysjhjybh: '',
jsbh: '',
jysjhbz: '',
kcxh: null,
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.batch = false
this.teacherDialog.mode = 'unit'
this.teacherDialog.jybh = row.jybh || ''
this.teacherDialog.visible = true
this.loadTeacherOptions()
},
openBatchTeacher() {
this.teacherDialog.row = null
this.teacherDialog.batch = true
this.teacherDialog.mode = 'academy'
this.teacherDialog.jybh = ''
this.teacherDialog.visible = true
this.loadTeacherOptions()
},
loadTeacherOptions() {
const row = this.teacherDialog.row
const params = (!this.teacherDialog.batch && row && 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
const call = d.batch
? taskBookBatchSetTeacher({
bhList: this.selection.map(r => r.bh),
mode: d.mode,
jybh: d.mode === 'plan' ? undefined : d.jybh
})
: taskBookSetTeacher({ bh: d.row.bh, mode: d.mode, jybh: d.mode === 'plan' ? undefined : d.jybh })
call
.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.batch = false
this.roomDialog.useSpecial = true
this.roomDialog.jsbh = ''
this.roomDialog.visible = true
this.loadRoomOptions()
},
openBatchRoom() {
this.roomDialog.row = null
this.roomDialog.batch = true
this.roomDialog.useSpecial = false
this.roomDialog.jsbh = ''
this.roomDialog.visible = true
this.loadRoomOptions()
},
loadRoomOptions() {
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
const call = d.batch
? taskBookBatchSetRoom({
bhList: this.selection.map(r => r.bh),
jsbh: d.jsbh,
useSpecial: d.useSpecial
})
: taskBookSetRoom({ bh: d.row.bh, jsbh: d.jsbh, useSpecial: d.useSpecial })
call
.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.batch = false
this.fillDialog.jysjhjybh = row.jysjhjybh || ''
this.fillDialog.jsbh = row.jsbh || ''
this.fillDialog.jysjhbz = row.jysjhbz || ''
this.fillDialog.kcxh = row.kcxh || null
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
})
},
openBatchFill() {
this.fillDialog.row = null
this.fillDialog.batch = true
this.fillDialog.jysjhjybh = ''
this.fillDialog.jsbh = ''
this.fillDialog.jysjhbz = ''
this.fillDialog.kcxh = null
this.fillDialog.useSpecial = false
this.fillDialog.visible = true
this.fillDialog.loading = true
Promise.all([
listTeacher({}).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
if (d.batch) {
taskBookBatchFill({
bhList: this.selection.map(r => r.bh),
jysjhjybh: d.jysjhjybh || undefined,
jysjhbz: d.jysjhbz || undefined,
kcxh: d.kcxh || undefined,
jsbh: d.useSpecial ? undefined : (d.jsbh || undefined),
useSpecial: d.useSpecial
})
.then(res => {
d.visible = false
this.reload(Promise.resolve(res))
})
.catch(err => this.$message.error((err && err.message) || '保存失败'))
return
}
const base = {
bh: d.row.bh,
jysjhjybh: d.jysjhjybh || '',
jysjhbz: d.jysjhbz || '',
kcxh: d.kcxh || undefined
}
// 专用教室走 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) || '保存失败'))
},
// ---------- 行级删除 / 导出 / 打印 ----------
handleDeleteRows(rowsArg) {
const targets = Array.isArray(rowsArg) ? rowsArg : this.selection
const bhList = targets.map(r => r.bh)
if (!bhList.length) {
this.$message.warning('请先选择要删除的行')
return
}
this.$confirm(`确定删除选中的 ${bhList.length} 条课程任务行吗?已发布到运行课表的须先撤回。`, '删除课程任务行', {
type: 'warning'
})
.then(() => this.reload(taskBookDeleteRows(bhList)))
.catch(() => {})
},
handleExport() {
if (!this.jxrwbh) return
exportTaskBook(this.jxrwbh)
.then(res => {
const blob = new Blob([res.data || res], { type: 'application/vnd.ms-excel' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = `教学任务书_${this.taskName || this.jxrwbh}.xls`
link.click()
URL.revokeObjectURL(link.href)
})
.catch(() => this.$message.error('导出失败'))
},
handlePrint() {
window.print()
}
}
}
</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;
}
.tb-director {
color: #e6a23c;
font-weight: bold;
}
</style>
<style>
@media print {
.tb-toolbar,
.el-dialog__headerbtn,
.el-dialog__footer,
.tb-table .el-table__fixed-right,
.v-modal {
display: none !important;
}
.el-dialog {
width: 100% !important;
margin: 0 !important;
box-shadow: none !important;
}
.el-dialog__body {
max-height: none !important;
overflow: visible !important;
}
}
</style>