新增培训:完善参训人员与排课前端

This commit is contained in:
wangzhibiao committed 2026-09-14 15:26:39 +08:00
1 parent fb87c95506
commit e65870ac6e
5 files changed
+1567 -3

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
import request from '@/utils/request'
// 3.1 查看本期参训名单 (UC-06) 【P0】
export function listPlanEnrollments(planId, query) {
return request({
url: `/train/plans/${planId}/enrollments`,
method: 'get',
params: query
})
}
// 3.2 添加单名参训人员 (UC-04) 【P0】
export function addPlanEnrollment(planId, data) {
return request({
url: `/train/plans/${planId}/enrollments`,
method: 'post',
data: data
})
}
// 3.3 Excel批量导入参训人员 (UC-05) 【P0】
export function importPlanEnrollments(planId, data) {
return request({
url: `/train/plans/${planId}/enrollments/batch`,
method: 'post',
data: data,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
// 3.4 获取参训档案详情 【P0】
export function getEnrollmentDetail(enrollmentId) {
return request({
url: `/train/enrollments/${enrollmentId}`,
method: 'get'
})
}
// 3.5 变更参训状态 【P1】
export function updateEnrollmentStatus(enrollmentId, data) {
return request({
url: `/train/enrollments/${enrollmentId}/status`,
method: 'put',
data: data
})
}
// 3.6 移除参训人员 【P1】
export function removeEnrollment(enrollmentId) {
return request({
url: `/train/enrollments/${enrollmentId}`,
method: 'delete'
})
}
+44
View File
@@ -0,0 +1,44 @@
import request from '@/utils/request'
// 4.5 获取计划完整课表 (UC-11) 【P0】
export function getPlanTimetable(planId) {
return request({
url: `/train/plans/${planId}/timetable`,
method: 'get'
})
}
// 4.1 新增课次 (UC-07) 【P0】
export function addLesson(planId, data) {
return request({
url: `/train/plans/${planId}/lessons`,
method: 'post',
data: data
})
}
// 4.2 修改课次 (UC-08) 【P0】
export function updateLesson(lessonId, data) {
return request({
url: `/train/lessons/${lessonId}`,
method: 'put',
data: data
})
}
// 4.3 删除课次 (UC-09) 【P1】
export function removeLesson(lessonId) {
return request({
url: `/train/lessons/${lessonId}`,
method: 'delete'
})
}
// 4.4 排课冲突检查接口 (UC-10) 【P0】
export function checkLessonConflict(data) {
return request({
url: '/train/lessons/check-conflict',
method: 'post',
data: data
})
}
@@ -0,0 +1,586 @@
<template>
<div class="enrollment-panel">
<!-- 检索与操作条 -->
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="78px">
<el-form-item label="学员姓名" prop="studentName">
<el-input
v-model="queryParams.studentName"
placeholder="请输入学员姓名"
clearable
style="width: 150px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="参训状态" prop="enrollmentStatus">
<el-select v-model="queryParams.enrollmentStatus" placeholder="全部状态" clearable style="width: 120px">
<el-option
v-for="dict in statusOptions"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="住宿需求" prop="accommodationRequired">
<el-select v-model="queryParams.accommodationRequired" placeholder="全部" clearable style="width: 110px">
<el-option label="需要住宿" :value="1" />
<el-option label="不住宿" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div style="margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center;">
<div>
<span style="font-weight: bold; font-size: 14px; margin-right: 15px;">本期参训名单</span>
<el-tag size="small" type="info">总人数:{{ total }}</el-tag>
</div>
<div>
<el-button
v-hasPermi="['train:enrollment:add']"
type="primary"
icon="el-icon-plus"
size="mini"
:disabled="isPlanLocked"
@click="handleOpenAddModal"
>添加参训人员</el-button>
<el-button
v-hasPermi="['train:enrollment:import']"
type="success"
plain
icon="el-icon-upload2"
size="mini"
:disabled="isPlanLocked"
@click="handleOpenBatchModal"
>批量导入</el-button>
</div>
</div>
<!-- 参训学员列表 -->
<el-table v-loading="loading" :data="enrollmentList" border style="width: 100%">
<el-table-column label="参训编号" align="center" prop="enrollmentId" min-width="120" show-overflow-tooltip />
<el-table-column label="学员编号" align="center" prop="studentId" min-width="110" show-overflow-tooltip />
<el-table-column label="姓名" align="center" prop="studentName" min-width="95" show-overflow-tooltip />
<el-table-column label="性别" align="center" prop="gender" width="65">
<template slot-scope="scope">
<span>{{ scope.row.gender || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="身份证号" align="center" prop="idCardMasked" min-width="170" show-overflow-tooltip>
<template slot-scope="scope">
<!-- 严格使用脱敏字段,不暴露明文 -->
<span>{{ scope.row.idCardMasked || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="选派单位" align="left" prop="orgName" min-width="140" show-overflow-tooltip />
<el-table-column label="住宿需求" align="center" prop="accommodationRequired" width="95">
<template slot-scope="scope">
<el-tag size="mini" :type="scope.row.accommodationRequired === 1 ? 'warning' : 'info'">
{{ scope.row.accommodationRequired === 1 ? '需住宿' : '不住宿' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="参训状态" align="center" prop="enrollmentStatus" width="95">
<template slot-scope="scope">
<el-tag size="mini" :type="statusTagType(scope.row.enrollmentStatus)">
{{ statusLabel(scope.row.enrollmentStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="180" fixed="right">
<template slot-scope="scope">
<el-button
v-hasPermi="['train:enrollment:query']"
size="mini"
type="text"
icon="el-icon-document"
@click="handleViewDetail(scope.row)"
>档案</el-button>
<el-dropdown
v-if="scope.row.enrollmentStatus !== 4"
v-hasPermi="['train:enrollment:edit']"
size="mini"
trigger="click"
@command="cmd => handleStatusAction(cmd, scope.row)"
>
<el-button size="mini" type="text" icon="el-icon-more" :disabled="isPlanLocked">
状态<i class="el-icon-arrow-down el-icon--right" />
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="2" :disabled="scope.row.enrollmentStatus === 2">标记请假</el-dropdown-item>
<el-dropdown-item command="3" :disabled="scope.row.enrollmentStatus === 3">标记退训</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 开班前支持移除 -->
<el-button
v-if="canRemove(scope.row)"
v-hasPermi="['train:enrollment:remove']"
size="mini"
type="text"
icon="el-icon-delete"
style="color: #F56C6C;"
@click="handleRemove(scope.row)"
>移除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页器 -->
<pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加单名参训人员对话框 (UC-04) -->
<el-dialog
title="添加单名参训人员"
:visible.sync="addDialog.open"
width="780px"
append-to-body
@close="closeAddDialog"
>
<div style="margin-bottom: 15px;">
<el-form :model="studentSearchQuery" size="small" :inline="true">
<el-form-item label="学员检索">
<el-input
v-model="studentSearchQuery.studentName"
placeholder="请输入姓名检索学员库"
clearable
style="width: 200px"
@keyup.enter.native="searchStudentPool"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="searchStudentPool">检索学员</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="addDialog.searchLoading"
:data="addDialog.studentCandidates"
highlight-current-row
height="220"
border
@current-change="handleSelectStudent"
>
<el-table-column label="选择" width="55" align="center">
<template slot-scope="scope">
<el-radio v-model="addDialog.selectedStudentId" :label="scope.row.sId" />
</template>
</el-table-column>
<el-table-column label="学号" prop="sId" min-width="100" show-overflow-tooltip />
<el-table-column label="姓名" prop="studentName" min-width="100" />
<el-table-column label="性别" prop="gender" width="65" align="center" />
<el-table-column label="身份证号" prop="idCardMasked" min-width="170" show-overflow-tooltip />
<el-table-column label="选派单位" prop="orgName" min-width="140" show-overflow-tooltip />
</el-table>
</div>
<el-form ref="addForm" :model="addForm" :rules="addRules" label-width="110px" size="small">
<el-form-item label="选中学员" prop="studentId">
<el-input
:value="addDialog.selectedStudent ? `${addDialog.selectedStudent.studentName} (${addDialog.selectedStudent.sId})` : ''"
readonly
placeholder="请在上方列表点击选择学员"
/>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="参训来源" prop="sourceType">
<el-select v-model="addForm.sourceType" placeholder="请选择参训来源" style="width: 100%">
<el-option label="管理员录入" :value="1" />
<el-option label="统一调训" :value="2" />
<el-option label="内部平台同步" :value="3" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="住宿需求" prop="accommodationRequired">
<el-radio-group v-model="addForm.accommodationRequired">
<el-radio :label="1">需要住宿</el-radio>
<el-radio :label="0">不住宿</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注说明" prop="remark">
<el-input v-model="addForm.remark" type="textarea" :rows="2" maxlength="500" show-word-limit placeholder="选填入班备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" :loading="addDialog.submitting" @click="submitAddForm">确认添加</el-button>
<el-button @click="addDialog.open = false">取 消</el-button>
</div>
</el-dialog>
<!-- Excel 批量导入对话框 (UC-05) -->
<el-dialog
title="批量导入参训学员 (Excel)"
:visible.sync="batchDialog.open"
width="520px"
append-to-body
>
<el-form label-width="110px" size="small">
<el-form-item label="上传文件">
<el-upload
ref="upload"
action="#"
:auto-upload="false"
:limit="1"
accept=".xlsx, .xls"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
>
<el-button slot="trigger" size="small" type="primary" icon="el-icon-upload">选取 Excel 文件</el-button>
<div slot="tip" class="el-upload__tip">请上传 .xlsx 或 .xls 文件,单次最大支持 500 行数据。</div>
</el-upload>
</el-form-item>
<el-form-item label="学员档案策略">
<el-switch v-model="batchDialog.autoCreateStudent" active-text="学员不存在时自动建档" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button
type="primary"
:loading="batchDialog.submitting"
:disabled="!batchDialog.file"
@click="submitBatchImport"
>开始导入</el-button>
<el-button @click="batchDialog.open = false">关 闭</el-button>
</div>
</el-dialog>
<!-- 参训档案详情全景对话框 -->
<el-dialog
title="参训档案全景"
:visible.sync="detailDialog.open"
width="680px"
append-to-body
>
<div v-loading="detailDialog.loading">
<el-descriptions title="基础信息" :column="2" border size="small">
<el-descriptions-item label="参训编号">{{ detailDialog.data.enrollmentId || '-' }}</el-descriptions-item>
<el-descriptions-item label="计划名称">{{ detailDialog.data.planName || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员姓名">{{ detailDialog.data.studentName || '-' }}</el-descriptions-item>
<el-descriptions-item label="学员学号">{{ detailDialog.data.studentId || '-' }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ detailDialog.data.gender || '-' }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ detailDialog.data.idCardMasked || '-' }}</el-descriptions-item>
<el-descriptions-item label="选派单位" :span="2">{{ detailDialog.data.orgName || '-' }}</el-descriptions-item>
<el-descriptions-item label="参训状态">
<el-tag size="mini" :type="statusTagType(detailDialog.data.enrollmentStatus)">
{{ statusLabel(detailDialog.data.enrollmentStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="住宿需求">
{{ detailDialog.data.accommodationRequired === 1 ? '需要住宿' : '不住宿' }}
</el-descriptions-item>
</el-descriptions>
<div style="margin-top: 15px;" />
<el-descriptions title="住宿安排" :column="2" border size="small">
<el-descriptions-item label="楼栋">{{ (detailDialog.data.accommodation && detailDialog.data.accommodation.building) || '未分配' }}</el-descriptions-item>
<el-descriptions-item label="房间">{{ (detailDialog.data.accommodation && detailDialog.data.accommodation.room) || '未分配' }}</el-descriptions-item>
<el-descriptions-item label="床位号">{{ (detailDialog.data.accommodation && detailDialog.data.accommodation.bedNo) || '未分配' }}</el-descriptions-item>
<el-descriptions-item label="门禁授权状态">
<el-tag size="mini" :type="detailDialog.data.accommodation && detailDialog.data.accommodation.authStatus === 1 ? 'success' : 'info'">
{{ (detailDialog.data.accommodation && detailDialog.data.accommodation.authStatus === 1) ? '已授权' : '待授权/无' }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
<div style="margin-top: 15px;" />
<el-descriptions title="报到记录" :column="2" border size="small">
<el-descriptions-item label="报到时间">{{ (detailDialog.data.report && detailDialog.data.report.reportTime) || '未报到 (报到功能开发中)' }}</el-descriptions-item>
<el-descriptions-item label="报到方式">
<span v-if="detailDialog.data.report && detailDialog.data.report.reportWay === 1">刷卡/身份证</span>
<span v-else-if="detailDialog.data.report && detailDialog.data.report.reportWay === 2">人工核验</span>
<span v-else-if="detailDialog.data.report && detailDialog.data.report.reportWay === 3">人脸识别</span>
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="核验说明" :span="2">{{ (detailDialog.data.report && detailDialog.data.report.verifyNote) || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="detailDialog.open = false">关 闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listPlanEnrollments,
addPlanEnrollment,
importPlanEnrollments,
getEnrollmentDetail,
updateEnrollmentStatus,
removeEnrollment
} from '@/api/train/enrollment'
import { listStudent } from '@/api/train/student'
export default {
name: 'EnrollmentPanel',
props: {
planId: {
type: [String, Number],
required: true
},
planStatus: {
type: Number,
default: 0
}
},
data() {
return {
loading: false,
enrollmentList: [],
total: 0,
statusOptions: [
{ value: 0, label: '待报到' },
{ value: 1, label: '已报到' },
{ value: 2, label: '请假' },
{ value: 3, label: '退训' },
{ value: 4, label: '已完成' }
],
queryParams: {
pageNum: 1,
pageSize: 10,
studentName: null,
enrollmentStatus: null,
accommodationRequired: null
},
// 单名添加学员对话框
addDialog: {
open: false,
searchLoading: false,
submitting: false,
selectedStudentId: null,
selectedStudent: null,
studentCandidates: []
},
studentSearchQuery: {
studentName: ''
},
addForm: {
studentId: '',
sourceType: 1,
accommodationRequired: 1,
remark: ''
},
addRules: {
studentId: [{ required: true, message: '请先在学员库中选择学员', trigger: 'change' }],
sourceType: [{ required: true, message: '请选择参训来源', trigger: 'change' }],
accommodationRequired: [{ required: true, message: '请选择住宿需求', trigger: 'change' }]
},
// 批量导入
batchDialog: {
open: false,
submitting: false,
file: null,
autoCreateStudent: true
},
// 档案详情
detailDialog: {
open: false,
loading: false,
data: {}
}
}
},
computed: {
isPlanLocked() {
return this.planStatus === 2 || this.planStatus === 3
}
},
watch: {
planId: {
immediate: true,
handler(val) {
if (val) {
this.getList()
}
}
}
},
methods: {
statusLabel(status) {
const found = this.statusOptions.find(item => item.value === status)
return found ? found.label : '未知'
},
statusTagType(status) {
const map = { 0: 'info', 1: 'success', 2: 'warning', 3: 'danger', 4: 'primary' }
return map[status] || 'info'
},
canRemove(row) {
// 班级未结束且处于待报到状态允许开班前移除
return this.planStatus === 0 && row.enrollmentStatus === 0
},
getList() {
if (!this.planId) return
this.loading = true
listPlanEnrollments(this.planId, this.queryParams)
.then(response => {
this.enrollmentList = response.rows || []
this.total = response.total || 0
})
.finally(() => {
this.loading = false
})
},
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
// ---- 查看参训档案全景 ----
handleViewDetail(row) {
this.detailDialog.loading = true
this.detailDialog.open = true
getEnrollmentDetail(row.enrollmentId)
.then(res => {
this.detailDialog.data = res.data || row
})
.catch(() => {
this.detailDialog.data = row
})
.finally(() => {
this.detailDialog.loading = false
})
},
// ---- 变更参训状态 ----
handleStatusAction(command, row) {
const status = Number(command)
const label = status === 2 ? '请假' : '退训'
this.$prompt(`请输入将学员【${row.studentName}】标记为【${label}】的备注原因:`, '变更参训状态', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S+/,
inputErrorMessage: '备注原因不能为空'
}).then(({ value }) => {
return updateEnrollmentStatus(row.enrollmentId, {
enrollmentStatus: status,
remark: value
})
}).then(() => {
this.$modal.msgSuccess(`已标记为${label}`)
this.getList()
}).catch(() => {})
},
// ---- 开班前移除参训人员 ----
handleRemove(row) {
this.$modal.confirm(`确定要从本期培训班中移除学员【${row.studentName}】吗?`).then(() => {
return removeEnrollment(row.enrollmentId)
}).then(() => {
this.$modal.msgSuccess('移除成功')
this.getList()
}).catch(() => {})
},
// ---- 添加单名学员 ----
handleOpenAddModal() {
this.addDialog.selectedStudentId = null
this.addDialog.selectedStudent = null
this.addDialog.studentCandidates = []
this.studentSearchQuery.studentName = ''
this.addForm = {
studentId: '',
sourceType: 1,
accommodationRequired: 1,
remark: ''
}
this.addDialog.open = true
this.searchStudentPool()
},
searchStudentPool() {
this.addDialog.searchLoading = true
listStudent({
pageNum: 1,
pageSize: 20,
studentName: this.studentSearchQuery.studentName
}).then(res => {
this.addDialog.studentCandidates = res.rows || []
}).finally(() => {
this.addDialog.searchLoading = false
})
},
handleSelectStudent(row) {
if (row) {
this.addDialog.selectedStudentId = row.sId
this.addDialog.selectedStudent = row
this.addForm.studentId = row.sId
}
},
submitAddForm() {
this.$refs.addForm.validate(valid => {
if (!valid) return
this.addDialog.submitting = true
addPlanEnrollment(this.planId, {
studentId: this.addForm.studentId,
sourceType: this.addForm.sourceType,
accommodationRequired: this.addForm.accommodationRequired,
remark: this.addForm.remark
}).then(() => {
this.$modal.msgSuccess('成功添加入班')
this.addDialog.open = false
this.getList()
}).finally(() => {
this.addDialog.submitting = false
})
})
},
closeAddDialog() {
this.addDialog.selectedStudentId = null
this.addDialog.selectedStudent = null
},
// ---- Excel 批量导入 ----
handleOpenBatchModal() {
this.batchDialog.file = null
this.batchDialog.autoCreateStudent = true
this.batchDialog.open = true
if (this.$refs.upload) {
this.$refs.upload.clearFiles()
}
},
handleFileChange(file) {
this.batchDialog.file = file.raw
},
handleFileRemove() {
this.batchDialog.file = null
},
submitBatchImport() {
if (!this.batchDialog.file) {
this.$modal.msgWarning('请先选择要导入的 Excel 文件')
return
}
const formData = new FormData()
formData.append('file', this.batchDialog.file)
formData.append('autoCreateStudent', this.batchDialog.autoCreateStudent)
this.batchDialog.submitting = true
importPlanEnrollments(this.planId, formData).then(res => {
this.$modal.msgSuccess(res.msg || '批量导入成功')
this.batchDialog.open = false
this.getList()
}).finally(() => {
this.batchDialog.submitting = false
})
}
}
}
</script>
<style scoped>
.enrollment-panel {
padding: 5px 0;
}
</style>
@@ -0,0 +1,755 @@
<template>
<div class="timetable-panel">
<!-- 头部操作与统计条 -->
<div style="margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center;">
<div>
<span style="font-weight: bold; font-size: 14px; margin-right: 15px;">本期完整课程表</span>
<el-tag size="small" type="info">总课次:{{ allLessons.length }} 节</el-tag>
<span v-if="timetableData.startDate && timetableData.endDate" style="margin-left: 12px; font-size: 12px; color: #909399;">
排课周期:{{ timetableData.startDate }} ~ {{ timetableData.endDate }}
</span>
</div>
<div>
<el-radio-group v-model="viewMode" size="mini" style="margin-right: 10px;">
<el-radio-button label="timeline">按日日程</el-radio-button>
<el-radio-button label="table">课次明细</el-radio-button>
</el-radio-group>
<el-button
v-hasPermi="['train:lesson:add']"
type="primary"
icon="el-icon-plus"
size="mini"
:disabled="isPlanLocked"
@click="handleOpenAddLesson"
>新增课次排课</el-button>
<el-button
icon="el-icon-refresh"
size="mini"
@click="loadTimetable"
>刷新课表</el-button>
</div>
</div>
<!-- 视图一:按日日程视图 -->
<div v-if="viewMode === 'timeline'" v-loading="loading" class="timeline-view">
<div v-if="!timetableData.days || timetableData.days.length === 0" class="empty-tip">
<el-empty description="暂未编排任何课次,请点击右上角【新增课次排课】" />
</div>
<div v-else class="day-cards">
<el-card
v-for="day in timetableData.days"
:key="day.date"
class="day-card"
shadow="hover"
>
<div slot="header" class="day-card-header">
<span style="font-weight: bold; font-size: 14px; color: #303133;">
<i class="el-icon-date" style="margin-right: 6px; color: #409EFF;" />
{{ day.date }}
<el-tag size="mini" type="info" style="margin-left: 8px;">{{ formatDayOfWeek(day.dayOfWeek) }}</el-tag>
</span>
<span style="font-size: 12px; color: #909399;">
共 {{ (day.lessons && day.lessons.length) || 0 }} 节课
</span>
</div>
<div v-if="!day.lessons || day.lessons.length === 0" style="color: #c0c4cc; font-size: 13px; padding: 10px 0;">
本日无课程安排
</div>
<div v-else class="lesson-items">
<div
v-for="lesson in day.lessons"
:key="lesson.lessonId"
class="lesson-item-box"
>
<div class="lesson-time">
<i class="el-icon-time" style="margin-right: 4px;" />
{{ formatTime(lesson.startTime) }} - {{ formatTime(lesson.endTime) }}
</div>
<div class="lesson-main">
<div class="lesson-title">
{{ lesson.courseName }}
</div>
<div class="lesson-meta">
<span class="meta-item">
<i class="el-icon-user" /> 教师:{{ lesson.teacherName || '-' }}
</span>
<span class="meta-item">
<i class="el-icon-location-outline" />
场地:
<el-tag size="mini" :type="lesson.venueType === 1 ? 'primary' : 'warning'">
{{ lesson.venueType === 1 ? '教室' : '实训场地' }}
</el-tag>
{{ lesson.venueName || lesson.classRoomName || lesson.siteName || '-' }}
</span>
<span v-if="lesson.boardSn" class="meta-item">
<i class="el-icon-monitor" /> 班牌:{{ lesson.boardSn }}
</span>
<span v-if="lesson.remark" class="meta-item" style="color: #909399;">
<i class="el-icon-info" /> {{ lesson.remark }}
</span>
</div>
</div>
<div class="lesson-actions">
<el-button
v-hasPermi="['train:lesson:edit']"
size="mini"
type="text"
icon="el-icon-edit"
:disabled="isPlanLocked"
@click="handleEditLesson(lesson)"
>修改</el-button>
<el-button
v-hasPermi="['train:lesson:remove']"
size="mini"
type="text"
icon="el-icon-delete"
style="color: #F56C6C;"
:disabled="isPlanLocked"
@click="handleRemoveLesson(lesson)"
>删除</el-button>
</div>
</div>
</div>
</el-card>
</div>
</div>
<!-- 视图二:平面明细表格视图 -->
<div v-else v-loading="loading">
<el-table :data="allLessons" border style="width: 100%">
<el-table-column label="课次ID" prop="lessonId" width="90" align="center" />
<el-table-column label="课程名称" prop="courseName" min-width="150" show-overflow-tooltip />
<el-table-column label="授课教师" prop="teacherName" width="100" align="center" />
<el-table-column label="开始时间" prop="startTime" width="165" align="center" />
<el-table-column label="结束时间" prop="endTime" width="165" align="center" />
<el-table-column label="场地类型" width="100" align="center">
<template slot-scope="scope">
<el-tag size="mini" :type="scope.row.venueType === 1 ? 'primary' : 'warning'">
{{ scope.row.venueType === 1 ? '教室' : '实训场地' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="上课地点" min-width="140" show-overflow-tooltip>
<template slot-scope="scope">
<span>{{ scope.row.venueName || scope.row.classRoomName || scope.row.siteName || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="电子班牌" prop="boardSn" width="120" align="center" show-overflow-tooltip>
<template slot-scope="scope">
<span>{{ scope.row.boardSn || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="课次说明" prop="remark" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="130" fixed="right">
<template slot-scope="scope">
<el-button
v-hasPermi="['train:lesson:edit']"
size="mini"
type="text"
icon="el-icon-edit"
:disabled="isPlanLocked"
@click="handleEditLesson(scope.row)"
>修改</el-button>
<el-button
v-hasPermi="['train:lesson:remove']"
size="mini"
type="text"
icon="el-icon-delete"
style="color: #F56C6C;"
:disabled="isPlanLocked"
@click="handleRemoveLesson(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 新增 / 编辑课次对话框 (UC-07, UC-08) -->
<el-dialog
:title="dialog.title"
:visible.sync="dialog.open"
width="680px"
append-to-body
@close="closeDialog"
>
<el-form ref="lessonForm" :model="lessonForm" :rules="rules" label-width="110px" size="small">
<el-form-item label="本期课程" prop="planCourseId">
<el-select
v-model="lessonForm.planCourseId"
placeholder="请选择本期配置的课程"
filterable
style="width: 100%"
>
<el-option
v-for="c in planCourseOptions"
:key="c.planCourseId"
:label="`${c.cName || c.courseName} (${c.cCode || '无编码'}) - ${c.cDuration || 0}学时`"
:value="c.planCourseId"
/>
</el-select>
</el-form-item>
<el-form-item label="授课教师" prop="tId">
<el-select
v-model="lessonForm.tId"
placeholder="请选择授课教师"
filterable
style="width: 100%"
@change="triggerAutoConflictCheck"
>
<el-option
v-for="t in teacherOptions"
:key="t.tId"
:label="`${t.name || t.tName} (${t.tId}) - ${t.workDept || t.orgName || '基地'}`"
:value="t.tId"
/>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="课次开始" prop="startTime">
<el-date-picker
v-model="lessonForm.startTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择课次开始时间"
style="width: 100%"
@change="triggerAutoConflictCheck"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="课次结束" prop="endTime">
<el-date-picker
v-model="lessonForm.endTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="选择课次结束时间"
style="width: 100%"
@change="triggerAutoConflictCheck"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="场地类型" prop="venueType">
<el-radio-group v-model="lessonForm.venueType" @change="handleVenueTypeChange">
<el-radio :label="1">普通教学教室</el-radio>
<el-radio :label="2">专业实训场地</el-radio>
</el-radio-group>
</el-form-item>
<!-- 教室与场地二选一 -->
<el-form-item
v-if="lessonForm.venueType === 1"
label="教学教室"
prop="classRoomId"
>
<el-select
v-model="lessonForm.classRoomId"
placeholder="请选择普通教室"
filterable
style="width: 100%"
@change="triggerAutoConflictCheck"
>
<el-option
v-for="item in classroomOptions"
:key="item.classRoomId"
:label="`${item.roomName} (${item.roomLocation || ''}) [容量: ${item.capacity}人]`"
:value="item.classRoomId"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="lessonForm.venueType === 2"
label="实训场地"
prop="siteId"
>
<el-select
v-model="lessonForm.siteId"
placeholder="请选择专业实训场地"
filterable
style="width: 100%"
@change="triggerAutoConflictCheck"
>
<el-option
v-for="item in siteOptions"
:key="item.siteId"
:label="`${item.siteName} (${item.siteLocation || ''}) [容量: ${item.capacity}人]`"
:value="item.siteId"
/>
</el-select>
</el-form-item>
<el-form-item label="课次备注" prop="remark">
<el-input
v-model="lessonForm.remark"
type="textarea"
:rows="2"
maxlength="255"
show-word-limit
placeholder="请输入课次相关注意事项或说明(选填)"
/>
</el-form-item>
<!-- 冲突预检结果展示区域 (UC-10) -->
<div style="margin-left: 110px; margin-bottom: 15px;">
<div style="display: flex; align-items: center; margin-bottom: 8px;">
<el-button
size="mini"
type="warning"
plain
icon="el-icon-connection"
:loading="conflictChecking"
@click="handleManualCheckConflict"
>排课冲突实时预检</el-button>
<span style="font-size: 12px; color: #909399; margin-left: 10px;">选择教师、场地与时间后可实时检测冲突</span>
</div>
<div v-if="conflictResult">
<el-alert
v-if="conflictResult.hasConflict"
:title="`排课冲突:${conflictResult.conflictMessage || conflictTypeLabel(conflictResult.conflictType)}`"
type="error"
:closable="false"
show-icon
>
<div v-if="conflictResult.conflictingLessons && conflictResult.conflictingLessons.length > 0" style="margin-top: 5px; font-size: 12px;">
冲突课次明细:
<div v-for="(item, idx) in conflictResult.conflictingLessons" :key="idx">
- {{ item.courseName || '课次' }} ({{ item.startTime }} ~ {{ item.endTime }})
</div>
</div>
</el-alert>
<el-alert
v-else
title="排课预检通过:教师、场地与班级时间段无重叠冲突"
type="success"
:closable="false"
show-icon
/>
</div>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button
type="primary"
:loading="dialog.submitting"
@click="submitLessonForm"
>保存课次</el-button>
<el-button @click="dialog.open = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
getPlanTimetable,
addLesson,
updateLesson,
removeLesson,
checkLessonConflict
} from '@/api/train/timetable'
import { listPlanCourses } from '@/api/train/planCourse'
import { listTeacher } from '@/api/train/teacher'
import { listClassroom } from '@/api/train/classroom'
import { listSite } from '@/api/train/site'
export default {
name: 'TimetablePanel',
props: {
planId: {
type: [String, Number],
required: true
},
planStatus: {
type: Number,
default: 0
}
},
data() {
return {
loading: false,
viewMode: 'timeline', // 'timeline' 或 'table'
timetableData: {
planId: null,
planName: '',
startDate: '',
endDate: '',
days: []
},
// 基础下拉选项缓存
planCourseOptions: [],
teacherOptions: [],
classroomOptions: [],
siteOptions: [],
// 新增/编辑弹窗
dialog: {
open: false,
title: '新增课次排课',
isEdit: false,
submitting: false
},
lessonForm: {
lessonId: null,
planCourseId: null,
tId: null,
startTime: null,
endTime: null,
venueType: 1,
classRoomId: null,
siteId: null,
remark: ''
},
rules: {
planCourseId: [{ required: true, message: '请选择本期课程', trigger: 'change' }],
tId: [{ required: true, message: '请选择授课教师', trigger: 'change' }],
startTime: [{ required: true, message: '课次开始时间不能为空', trigger: 'change' }],
endTime: [{ required: true, message: '课次结束时间不能为空', trigger: 'change' }],
venueType: [{ required: true, message: '请选择场地类型', trigger: 'change' }],
classRoomId: [{ required: true, message: '请选择普通教室', trigger: 'change' }],
siteId: [{ required: true, message: '请选择实训场地', trigger: 'change' }]
},
// 冲突检测状态与结果
conflictChecking: false,
conflictResult: null
}
},
computed: {
isPlanLocked() {
return this.planStatus === 2 || this.planStatus === 3
},
allLessons() {
if (!this.timetableData.days) return []
const list = []
this.timetableData.days.forEach(day => {
if (day.lessons && day.lessons.length) {
day.lessons.forEach(l => list.push(l))
}
})
return list
}
},
watch: {
planId: {
immediate: true,
handler(val) {
if (val) {
this.loadTimetable()
this.loadOptions()
}
}
}
},
methods: {
formatDayOfWeek(num) {
const map = { 1: '星期一', 2: '星期二', 3: '星期三', 4: '星期四', 5: '星期五', 6: '星期六', 7: '星期日' }
return map[num] || ''
},
formatTime(dateTimeStr) {
if (!dateTimeStr) return ''
if (dateTimeStr.includes(' ')) {
return dateTimeStr.split(' ')[1].substring(0, 5)
}
return dateTimeStr
},
conflictTypeLabel(type) {
const map = {
'NONE': '无冲突',
'TEACHER': '教师时间冲突',
'CLASSROOM': '教室时间占用冲突',
'SITE': '实训场地占用冲突',
'PLAN_OVERLAP': '本班课次时段重叠'
}
return map[type] || '存在排课冲突'
},
// ---- 加载课表 ----
loadTimetable() {
if (!this.planId) return
this.loading = true
getPlanTimetable(this.planId)
.then(res => {
this.timetableData = res.data || { days: [] }
})
.finally(() => {
this.loading = false
})
},
// ---- 预加载基础选项 ----
loadOptions() {
if (!this.planId) return
listPlanCourses(this.planId).then(res => {
this.planCourseOptions = res.data || []
})
listTeacher({ pageNum: 1, pageSize: 100 }).then(res => {
this.teacherOptions = res.rows || []
})
listClassroom({ pageNum: 1, pageSize: 100 }).then(res => {
this.classroomOptions = res.rows || []
})
listSite({ pageNum: 1, pageSize: 100 }).then(res => {
this.siteOptions = res.rows || []
})
},
handleVenueTypeChange(val) {
if (val === 1) {
this.lessonForm.siteId = null
} else {
this.lessonForm.classRoomId = null
}
this.triggerAutoConflictCheck()
},
// ---- 打开新增排课 ----
handleOpenAddLesson() {
this.dialog.isEdit = false
this.dialog.title = '新增课次排课'
this.resetLessonForm()
this.dialog.open = true
this.conflictResult = null
},
// ---- 打开修改排课 (不发明 lesson detail API,直接从已有VO回显) ----
handleEditLesson(row) {
this.dialog.isEdit = true
this.dialog.title = '编辑课次信息'
this.lessonForm = {
lessonId: row.lessonId,
planCourseId: row.planCourseId,
tId: row.tId,
startTime: row.startTime,
endTime: row.endTime,
venueType: row.venueType || 1,
classRoomId: row.classRoomId || null,
siteId: row.siteId || null,
remark: row.remark || ''
}
this.conflictResult = null
this.dialog.open = true
},
resetLessonForm() {
this.lessonForm = {
lessonId: null,
planCourseId: null,
tId: null,
startTime: null,
endTime: null,
venueType: 1,
classRoomId: null,
siteId: null,
remark: ''
}
if (this.$refs.lessonForm) {
this.$refs.lessonForm.clearValidate()
}
},
closeDialog() {
this.conflictResult = null
},
// ---- 冲突预检逻辑 (UC-10) ----
triggerAutoConflictCheck() {
// 当教师、场地与时间均选择后自动静默预检
const f = this.lessonForm
const hasVenue = (f.venueType === 1 && f.classRoomId) || (f.venueType === 2 && f.siteId)
if (f.tId && f.startTime && f.endTime && hasVenue) {
this.performConflictCheck(false)
}
},
handleManualCheckConflict() {
this.performConflictCheck(true)
},
performConflictCheck(showNoticeOnPass) {
const f = this.lessonForm
if (!f.startTime || !f.endTime) {
if (showNoticeOnPass) this.$modal.msgWarning('请先选择课次开始与结束时间')
return
}
if (f.startTime >= f.endTime) {
if (showNoticeOnPass) this.$modal.msgError('课次结束时间必须晚于开始时间')
return
}
const payload = {
planId: this.planId,
lessonId: f.lessonId,
planCourseId: f.planCourseId,
tId: f.tId,
startTime: f.startTime,
endTime: f.endTime,
venueType: f.venueType,
classRoomId: f.venueType === 1 ? f.classRoomId : null,
siteId: f.venueType === 2 ? f.siteId : null
}
this.conflictChecking = true
checkLessonConflict(payload)
.then(res => {
this.conflictResult = res.data || { hasConflict: false }
if (showNoticeOnPass && !this.conflictResult.hasConflict) {
this.$modal.msgSuccess('预检通过,未检测到任何冲突')
}
})
.finally(() => {
this.conflictChecking = false
})
},
// ---- 提交保存课次 (排课冲突告警确认) ----
submitLessonForm() {
this.$refs.lessonForm.validate(valid => {
if (!valid) return
if (this.lessonForm.startTime >= this.lessonForm.endTime) {
this.$modal.msgError('课次结束时间必须晚于开始时间')
return
}
// 构建保存 payload (严格对齐 LessonCreateDTO / LessonUpdateDTO,绝不含 force/override 字段)
const payload = {
planCourseId: this.lessonForm.planCourseId,
tId: this.lessonForm.tId,
startTime: this.lessonForm.startTime,
endTime: this.lessonForm.endTime,
venueType: this.lessonForm.venueType,
classRoomId: this.lessonForm.venueType === 1 ? this.lessonForm.classRoomId : null,
siteId: this.lessonForm.venueType === 2 ? this.lessonForm.siteId : null,
remark: this.lessonForm.remark
}
// 构建预检 DTO (编辑模式传 lessonId 排除自身,新增模式传 null)
const conflictCheckDTO = {
planId: this.planId,
lessonId: this.dialog.isEdit ? this.lessonForm.lessonId : null,
planCourseId: this.lessonForm.planCourseId,
tId: this.lessonForm.tId,
startTime: this.lessonForm.startTime,
endTime: this.lessonForm.endTime,
venueType: this.lessonForm.venueType,
classRoomId: this.lessonForm.venueType === 1 ? this.lessonForm.classRoomId : null,
siteId: this.lessonForm.venueType === 2 ? this.lessonForm.siteId : null
}
this.dialog.submitting = true
// 每次点击保存均重新发起冲突预检,避免依赖过期结果
checkLessonConflict(conflictCheckDTO)
.then(res => {
const checkData = res.data || { hasConflict: false }
this.conflictResult = checkData
if (checkData.hasConflict) {
const warningMsg = checkData.conflictMessage || this.conflictTypeLabel(checkData.conflictType)
this.$confirm(`检测到排课冲突:\n${warningMsg}\n\n是否仍然保存该课次?`, '排课冲突告警确认', {
confirmButtonText: '确认保存',
cancelButtonText: '返回调整',
type: 'warning'
}).then(() => {
this.executeSaveLesson(payload)
}).catch(() => {
this.dialog.submitting = false
})
} else {
this.executeSaveLesson(payload)
}
})
.catch(() => {
this.$modal.msgError('排课冲突检查失败,请稍后重试')
this.dialog.submitting = false
})
})
},
executeSaveLesson(payload) {
this.dialog.submitting = true
const request = this.dialog.isEdit
? updateLesson(this.lessonForm.lessonId, payload)
: addLesson(this.planId, payload)
request
.then(() => {
this.$modal.msgSuccess(this.dialog.isEdit ? '修改课次成功' : '新增课次成功')
this.dialog.open = false
this.loadTimetable()
})
.finally(() => {
this.dialog.submitting = false
})
},
// ---- 删除课次 ----
handleRemoveLesson(row) {
this.$modal.confirm(`确定要删除课次【${row.courseName} (${row.startTime})】吗?`).then(() => {
return removeLesson(row.lessonId)
}).then(() => {
this.$modal.msgSuccess('删除成功')
this.loadTimetable()
}).catch(() => {})
}
}
}
</script>
<style scoped>
.timetable-panel {
padding: 5px 0;
}
.empty-tip {
padding: 40px 0;
}
.day-cards {
display: flex;
flex-direction: column;
gap: 16px;
}
.day-card {
border-radius: 6px;
}
.day-card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.lesson-items {
display: flex;
flex-direction: column;
gap: 10px;
}
.lesson-item-box {
display: flex;
align-items: center;
padding: 10px 14px;
background-color: #f8f9fa;
border-left: 4px solid #409EFF;
border-radius: 4px;
}
.lesson-time {
width: 140px;
font-weight: bold;
color: #303133;
font-size: 13px;
flex-shrink: 0;
}
.lesson-main {
flex-grow: 1;
padding: 0 15px;
}
.lesson-title {
font-size: 14px;
font-weight: 500;
color: #1f2f3d;
margin-bottom: 4px;
}
.lesson-meta {
display: flex;
flex-wrap: wrap;
gap: 15px;
font-size: 12px;
color: #606266;
}
.meta-item {
display: inline-flex;
align-items: center;
gap: 4px;
}
.lesson-actions {
flex-shrink: 0;
}
</style>
+126 -3
View File
@@ -69,9 +69,24 @@
>{{ scope.row.courseCount || 0 }}</el-button>
</template>
</el-table-column>
<el-table-column label="参训人数" align="center" prop="enrolledCount" width="90" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="230" fixed="right">
<el-table-column label="参训人数" align="center" prop="enrolledCount" width="90">
<template slot-scope="scope">
<el-button
v-hasPermi="['train:enrollment:list']"
type="text"
@click="handleOpenPlanDetail(scope.row, 'enrollment')"
>{{ scope.row.enrolledCount || 0 }}</el-button>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="290" fixed="right">
<template slot-scope="scope">
<el-button
v-hasPermi="['train:plan:query']"
size="mini"
type="text"
icon="el-icon-data-board"
@click="handleOpenPlanDetail(scope.row, 'course')"
>计划详情</el-button>
<el-button
v-hasPermi="['train:plan:query']"
size="mini"
@@ -255,6 +270,93 @@
<el-button @click="selectorDialog.open = false">取 消</el-button>
</div>
</el-dialog>
<!-- 计划详情综合对话框 (包含:课程、参训人员、课程表) -->
<el-dialog
:title="`培训计划详情 - ${planDetailDialog.plan.planName || ''}`"
:visible.sync="planDetailDialog.open"
width="1050px"
append-to-body
top="5vh"
>
<!-- 计划基础信息概览 -->
<el-descriptions :column="3" border size="small" style="margin-bottom: 15px;">
<el-descriptions-item label="计划编号">{{ planDetailDialog.plan.planId }}</el-descriptions-item>
<el-descriptions-item label="培训班名称">{{ planDetailDialog.plan.planName }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag size="mini" :type="statusTagType(planDetailDialog.plan.planStatus)">
{{ statusLabel(planDetailDialog.plan.planStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="开班时间">{{ planDetailDialog.plan.startTime }}</el-descriptions-item>
<el-descriptions-item label="结业时间">{{ planDetailDialog.plan.endTime }}</el-descriptions-item>
<el-descriptions-item label="报到截止">{{ planDetailDialog.plan.reportDeadline }}</el-descriptions-item>
<el-descriptions-item label="培训地点" :span="2">{{ planDetailDialog.plan.trainAddress || '-' }}</el-descriptions-item>
<el-descriptions-item label="计划人数">{{ planDetailDialog.plan.recruitNum }} 人</el-descriptions-item>
</el-descriptions>
<!-- 核心三要素 Tab 切换 -->
<el-tabs v-model="planDetailDialog.activeTab">
<!-- Tab 1: 课程 -->
<el-tab-pane label="本期课程" name="course">
<div style="margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: bold; font-size: 14px;">已配置课程 ({{ courseDialog.list.length }} 门)</span>
<el-button
v-hasPermi="['train:plan:edit']"
type="primary"
size="mini"
icon="el-icon-plus"
:disabled="isLocked(planDetailDialog.plan.planStatus)"
@click="handleOpenCourseSelector"
>引入课程</el-button>
</div>
<el-table v-loading="courseDialog.loading" :data="courseDialog.list" border style="width: 100%">
<el-table-column label="序号" type="index" width="55" align="center" />
<el-table-column label="课程编码" prop="cCode" min-width="120" show-overflow-tooltip />
<el-table-column label="课程名称" prop="cName" min-width="180" show-overflow-tooltip />
<el-table-column label="类型" prop="cType" width="80" align="center" />
<el-table-column label="学时" prop="cDuration" width="70" align="center" />
<el-table-column label="排序号" prop="sortNo" width="70" align="center" />
<el-table-column label="配置说明" prop="remark" min-width="120" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="80" fixed="right">
<template slot-scope="scope">
<el-button
v-hasPermi="['train:plan:edit']"
size="mini"
type="text"
icon="el-icon-delete"
style="color: #F56C6C;"
:disabled="isLocked(planDetailDialog.plan.planStatus)"
@click="handleRemoveCourse(scope.row)"
>移除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<!-- Tab 2: 参训人员 -->
<el-tab-pane label="参训人员" name="enrollment">
<enrollment-panel
v-if="planDetailDialog.open && planDetailDialog.plan.planId"
:plan-id="planDetailDialog.plan.planId"
:plan-status="planDetailDialog.plan.planStatus"
/>
</el-tab-pane>
<!-- Tab 3: 课程表 -->
<el-tab-pane label="课程表" name="timetable">
<timetable-panel
v-if="planDetailDialog.open && planDetailDialog.plan.planId"
:plan-id="planDetailDialog.plan.planId"
:plan-status="planDetailDialog.plan.planStatus"
/>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
<el-button @click="planDetailDialog.open = false">关 闭</el-button>
</div>
</el-dialog>
</div>
</template>
@@ -262,9 +364,15 @@
import { addPlan, completePlan, getPlan, listPlan, updatePlan } from '@/api/train/plan'
import { listPlanCourses, configurePlanCourses, removePlanCourse } from '@/api/train/planCourse'
import { listCourse } from '@/api/train/course'
import EnrollmentPanel from './components/EnrollmentPanel.vue'
import TimetablePanel from './components/TimetablePanel.vue'
export default {
name: 'Plan',
components: {
EnrollmentPanel,
TimetablePanel
},
data() {
return {
loading: true,
@@ -317,7 +425,13 @@ export default {
},
list: []
},
selectedCourses: []
selectedCourses: [],
// 计划综合详情对话框
planDetailDialog: {
open: false,
activeTab: 'course',
plan: {}
}
}
},
created() {
@@ -503,6 +617,15 @@ export default {
this.loadPlanCourses(this.courseDialog.planId)
this.getList()
}).catch(() => {})
},
handleOpenPlanDetail(row, activeTab = 'course') {
this.planDetailDialog.plan = row
this.planDetailDialog.activeTab = activeTab
this.planDetailDialog.open = true
this.courseDialog.planId = row.planId
this.courseDialog.planName = row.planName
this.courseDialog.planStatus = row.planStatus
this.loadPlanCourses(row.planId)
}
}
}