52a04dbf60
2、天假删除保护; 3、教学任务计划增加了「结束发布」。已发布或库里的「已发布」都可以结束;结束后写入结束时间,任务书和班次课程任务只读 4、班次学期操作内容调整修改;
946 lines
35 KiB
Vue
946 lines
35 KiB
Vue
<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="openLibraryPicker">计划外新添</el-button>
|
||
<el-button size="small" @click="openPlanPicker">计划内必修添加</el-button>
|
||
<el-button size="small" @click="handleAutoGenerate">自动生成必修课程</el-button>
|
||
<el-button size="small" :disabled="!selection.length" @click="handleCopySelected">复制所选</el-button>
|
||
<el-button size="small" :disabled="!clipboardCount" @click="handlePaste">粘贴到其它班次</el-button>
|
||
<el-button size="small" :disabled="!selection.length" @click="handleSyncPlan">同步人培</el-button>
|
||
<el-button size="small" type="danger" plain :disabled="!selection.length" @click="handleBatchDelete">批量删除</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"
|
||
:row-class-name="rowClassName"
|
||
@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 label="人培差异" width="90" align="center">
|
||
<template slot-scope="{ row }">
|
||
<el-tag v-if="isPlanMismatch(row)" type="danger" size="mini">不一致</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="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>
|
||
|
||
<el-dialog
|
||
:visible="libraryVisible"
|
||
title="从课程库选择"
|
||
width="760px"
|
||
append-to-body
|
||
:close-on-click-modal="false"
|
||
@update:visible="val => libraryVisible = val"
|
||
>
|
||
<el-input v-model="libraryKeyword" size="small" placeholder="简称 / 课程名称" clearable style="width: 240px; margin-bottom: 8px" @keyup.enter.native="loadLibrary" />
|
||
<el-button size="small" type="primary" @click="loadLibrary">查询</el-button>
|
||
<el-table v-loading="pickerLoading" :data="libraryRows" border size="small" max-height="360" highlight-current-row @current-change="row => libraryCurrent = row">
|
||
<el-table-column prop="kbh" label="课编号" width="140" show-overflow-tooltip />
|
||
<el-table-column prop="jc" label="简称" width="120" show-overflow-tooltip />
|
||
<el-table-column prop="kmc" label="课程名称" min-width="160" show-overflow-tooltip />
|
||
<el-table-column prop="xs" label="学时" width="70" align="center" />
|
||
<el-table-column prop="klx" label="课类型" width="90" show-overflow-tooltip />
|
||
</el-table>
|
||
<div slot="footer">
|
||
<el-button size="small" @click="libraryVisible = false">取消</el-button>
|
||
<el-button size="small" type="primary" :disabled="!libraryCurrent" @click="confirmLibrary">加入任务</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
:visible="planVisible"
|
||
title="其它学期人培课程"
|
||
width="760px"
|
||
append-to-body
|
||
:close-on-click-modal="false"
|
||
@update:visible="val => planVisible = val"
|
||
>
|
||
<el-table v-loading="pickerLoading" :data="planRows" border size="small" max-height="360" highlight-current-row @current-change="row => planCurrent = row">
|
||
<el-table-column prop="jc" label="简称" width="120" show-overflow-tooltip />
|
||
<el-table-column prop="kbh" label="课编号" width="140" show-overflow-tooltip />
|
||
<el-table-column prop="xqdc" label="学期第次" width="90" align="center" />
|
||
<el-table-column prop="xs" label="学时" width="70" align="center" />
|
||
<el-table-column prop="klx" label="课类型" width="90" show-overflow-tooltip />
|
||
<el-table-column label="完成情况" width="90" align="center">
|
||
<template slot-scope="{ row }">
|
||
<el-tag :type="row.completed ? 'success' : 'info'" size="mini">{{ row.completed ? '已完成' : '未完成' }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div slot="footer">
|
||
<el-button size="small" @click="planVisible = false">取消</el-button>
|
||
<el-button size="small" type="primary" :disabled="!planCurrent || planCurrent.completed" @click="confirmPlan">加入当前学期</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- ==================== 目标班次学期选取弹窗 ==================== -->
|
||
<ClassTeamSelectDialog
|
||
:visible="teamSelectVisible"
|
||
:semester-list="semesterOptions"
|
||
:exclude-bh="safeSemester.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,
|
||
autoGenerateRequiredCourses,
|
||
listPlanCourses,
|
||
addTaskFromLibrary,
|
||
addTaskFromPlan,
|
||
syncTaskFromPlan,
|
||
batchDeleteTasks,
|
||
pasteTasks
|
||
} from '@/api/studentRecords/classCalendar'
|
||
import { listKb } from '@/api/teachOffice/kb'
|
||
|
||
const TASK_CLIPBOARD_KEY = 'education.classTaskClipboard'
|
||
|
||
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: '',
|
||
clipboardCount: 0,
|
||
planMap: {},
|
||
libraryVisible: false,
|
||
libraryKeyword: '',
|
||
libraryRows: [],
|
||
libraryCurrent: null,
|
||
planVisible: false,
|
||
planRows: [],
|
||
planCurrent: null,
|
||
pickerLoading: false
|
||
}
|
||
},
|
||
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.clipboardCount = this.readClipboard().length
|
||
this.loadData()
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
/* ---------- 格式化 ---------- */
|
||
fmtDate(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
|
||
},
|
||
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
|
||
Promise.all([
|
||
listClassCalendar(s.bh),
|
||
listPlanCourses(s.bh, false).catch(() => ({ data: [] }))
|
||
]).then(([taskRes, planRes]) => {
|
||
this.records = (taskRes && taskRes.data) || []
|
||
const plans = (planRes && planRes.data) || []
|
||
const map = {}
|
||
plans.forEach(item => {
|
||
if (item.kbh) map[item.kbh] = item
|
||
})
|
||
this.planMap = map
|
||
this.loading = false
|
||
}).catch(() => {
|
||
this.records = []
|
||
this.loading = false
|
||
})
|
||
},
|
||
isPlanMismatch(row) {
|
||
const plan = row && row.kbh ? this.planMap[row.kbh] : null
|
||
if (!plan) return false
|
||
return String(row.xs || '') !== String(plan.xs || '')
|
||
|| String(row.klx || '') !== String(plan.klx || '')
|
||
|| String(row.xf || '') !== String(plan.xf || '')
|
||
|| String(row.jc || '') !== String(plan.jc || '')
|
||
|| String(row.zks || '') !== String(plan.zks || '')
|
||
},
|
||
rowClassName({ row }) {
|
||
return this.isPlanMismatch(row) ? 'plan-mismatch-row' : ''
|
||
},
|
||
readClipboard() {
|
||
try {
|
||
const raw = sessionStorage.getItem(TASK_CLIPBOARD_KEY)
|
||
const parsed = raw ? JSON.parse(raw) : []
|
||
return Array.isArray(parsed) ? parsed : []
|
||
} catch (e) {
|
||
return []
|
||
}
|
||
},
|
||
|
||
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(() => {})
|
||
},
|
||
|
||
/* ---------- 应用于其它班次 ---------- */
|
||
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
|
||
if (this.applyMode === 'paste') {
|
||
const bhList = this.readClipboard()
|
||
Promise.all(targets.map(item => pasteTasks(bhList, item.bh))).then(results => {
|
||
const count = results.reduce((sum, res) => sum + ((res && res.data) || 0), 0)
|
||
this.$message.success(`已粘贴 ${count} 条独立课程任务`)
|
||
this.teamSelectVisible = false
|
||
this.loadData()
|
||
})
|
||
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()
|
||
})
|
||
}
|
||
},
|
||
|
||
handleCopySelected() {
|
||
const bhList = this.selection.map(item => item.bh).filter(Boolean)
|
||
if (!bhList.length) return
|
||
sessionStorage.setItem(TASK_CLIPBOARD_KEY, JSON.stringify(bhList))
|
||
this.clipboardCount = bhList.length
|
||
this.$message.success(`已复制 ${bhList.length} 条课程任务,可在其它班次粘贴`)
|
||
},
|
||
handlePaste() {
|
||
if (!this.readClipboard().length) {
|
||
this.$message.warning('剪贴板没有课程任务')
|
||
return
|
||
}
|
||
this.applyMode = 'paste'
|
||
this.copyNote = `将剪贴板中的 ${this.clipboardCount} 条课程任务粘贴为独立记录`
|
||
this.teamSelectVisible = true
|
||
},
|
||
handleSyncPlan() {
|
||
const bhList = this.selection.map(item => item.bh).filter(Boolean)
|
||
if (!bhList.length) return
|
||
this.$confirm('将按人培覆盖所选行的学时、类型、学分和周课时,是否继续?', '同步人培', { type: 'warning' }).then(() => {
|
||
syncTaskFromPlan(bhList).then(res => {
|
||
this.$message.success(`已同步 ${res.data || 0} 条`)
|
||
this.loadData()
|
||
})
|
||
}).catch(() => {})
|
||
},
|
||
handleBatchDelete() {
|
||
const bhList = this.selection.map(item => item.bh).filter(Boolean)
|
||
if (!bhList.length) return
|
||
this.$confirm('已发布到任务书或已排课的行会拒绝删除,其余将物理删除。', '批量删除', { type: 'warning' }).then(() => {
|
||
batchDeleteTasks(bhList).then(res => {
|
||
this.$message.success(`已删除 ${res.data || 0} 条`)
|
||
this.loadData()
|
||
})
|
||
}).catch(() => {})
|
||
},
|
||
openLibraryPicker() {
|
||
this.libraryVisible = true
|
||
this.libraryCurrent = null
|
||
this.loadLibrary()
|
||
},
|
||
loadLibrary() {
|
||
this.pickerLoading = true
|
||
const keyword = (this.libraryKeyword || '').trim()
|
||
const unwrap = res => {
|
||
const data = (res && res.data) || {}
|
||
return data.records || (Array.isArray(data) ? data : [])
|
||
}
|
||
const requests = keyword
|
||
? [listKb({ pageNum: 1, pageSize: 50, kmc: keyword }), listKb({ pageNum: 1, pageSize: 50, jc: keyword })]
|
||
: [listKb({ pageNum: 1, pageSize: 50 })]
|
||
Promise.all(requests).then(results => {
|
||
const merged = []
|
||
const seen = {}
|
||
results.forEach(res => {
|
||
unwrap(res).forEach(item => {
|
||
if (!item.kbh || seen[item.kbh]) return
|
||
seen[item.kbh] = true
|
||
merged.push(item)
|
||
})
|
||
})
|
||
this.libraryRows = merged
|
||
this.pickerLoading = false
|
||
}).catch(() => {
|
||
this.libraryRows = []
|
||
this.pickerLoading = false
|
||
})
|
||
},
|
||
confirmLibrary() {
|
||
if (!this.libraryCurrent || !this.semester) return
|
||
addTaskFromLibrary(this.semester.bh, this.libraryCurrent.kbh).then(() => {
|
||
this.libraryVisible = false
|
||
this.$message.success('已从课程库加入,学时和类型已带出')
|
||
this.loadData()
|
||
})
|
||
},
|
||
openPlanPicker() {
|
||
if (!this.semester || !this.semester.bh) return
|
||
this.planVisible = true
|
||
this.planCurrent = null
|
||
this.pickerLoading = true
|
||
listPlanCourses(this.semester.bh, true).then(res => {
|
||
this.planRows = res.data || []
|
||
this.pickerLoading = false
|
||
}).catch(() => {
|
||
this.planRows = []
|
||
this.pickerLoading = false
|
||
})
|
||
},
|
||
confirmPlan() {
|
||
if (!this.planCurrent || !this.semester) return
|
||
addTaskFromPlan(this.semester.bh, this.planCurrent.bh).then(() => {
|
||
this.planVisible = false
|
||
this.$message.success('已加入当前学期任务')
|
||
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;
|
||
}
|
||
}
|
||
|
||
::v-deep .plan-mismatch-row > td {
|
||
background: #fde2e2 !important;
|
||
}
|
||
|
||
// ========== 弹窗内表单分区标题 ==========
|
||
.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>
|
||
|