Files
education/frontend/src/views/teachBusiness/trainingPlan/index.vue
T
2026-08-25 10:43:24 +08:00

600 lines
22 KiB
Vue

<template>
<div class="app-container training-plan-page">
<!-- ==================== 1. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm" label-width="100px" class="search-form" @submit.native.prevent>
<el-row :gutter="24">
<el-col :xs="24" :md="8">
<el-form-item label="专业名称">
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="专业代码">
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="培训类型">
<el-input v-model="searchForm.pxlx" placeholder="请输入培训类型" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<el-form-item label="培训层次">
<el-input v-model="searchForm.pxcc" placeholder="请输入培训层次" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :md="8">
<div class="search-actions">
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="handleReset">重置</el-button>
</div>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- ==================== 2. 操作与上传区域 ==================== -->
<el-card shadow="never" class="action-card">
<div class="action-row">
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新建</el-button>
<el-button icon="el-icon-download" @click="handleDownload">下载</el-button>
</div>
<div class="action-row">
<el-button icon="el-icon-download" @click="handleTemplateDownload">【人才培养方案目录模板】下载</el-button>
</div>
<div class="upload-row">
<div class="upload-left">
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
<span class="file-name" :class="{ 'has-file': selectedFile }">
<template v-if="selectedFile">{{ fileName }}</template>
<template v-else>请选择要上传的文件</template>
</span>
<input ref="fileInputRef" type="file" style="display: none" @change="handleFileChange" />
</div>
<div class="upload-right">
<el-button type="primary" icon="el-icon-upload2" :disabled="!selectedFile" @click="handleUpload">上传数据</el-button>
</div>
</div>
</el-card>
<!-- ==================== 3. 数据表格区域 ==================== -->
<el-card shadow="never" class="table-card">
<div class="list-header">
<div class="list-title">人才培养方案列表</div>
</div>
<el-table v-loading="loading" :data="tableData" border stripe highlight-current-row>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="zydh" label="专业代号" width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="zymc" label="专业名称" width="140" show-overflow-tooltip header-align="center" />
<el-table-column prop="zyfx" label="专业方向" width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="zydm" label="专业代码" width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxlx" label="培训类型" width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxcc" label="培训层次" width="120" show-overflow-tooltip header-align="center" />
<el-table-column prop="pxlx2" label="培训类型2" width="110" show-overflow-tooltip header-align="center" />
<el-table-column prop="xylb" label="学员类别" width="100" show-overflow-tooltip header-align="center" />
<el-table-column prop="xnz" label="学年制" width="80" show-overflow-tooltip header-align="center" />
<el-table-column prop="xqs" label="学期数" width="80" align="center" />
<el-table-column prop="zybb" label="专业版本" width="100" show-overflow-tooltip header-align="center" />
<el-table-column label="主干专业" width="90" align="center">
<template slot-scope="scope">{{ fmtYesNo(scope.row.zgzy) }}</template>
</el-table-column>
<el-table-column label="状态" width="80" align="center">
<template slot-scope="scope">
<el-tag :type="isTrue(scope.row.ty) ? 'danger' : 'success'" size="mini">
{{ isTrue(scope.row.ty) ? '停用' : '启用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="启用时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.qysj) }}</template>
</el-table-column>
<el-table-column label="停用时间" width="150" align="center">
<template slot-scope="scope">{{ fmtDateTime(scope.row.tysj) }}</template>
</el-table-column>
<el-table-column label="操作" width="240" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-view" @click="handleDetail(scope.row)">详情</el-button>
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" size="mini" icon="el-icon-circle-close" class="danger-text-btn"
:disabled="isTrue(scope.row.ty)" @click="handleDisable(scope.row)">停用</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
background
layout="total, sizes, prev, pager, next, jumper"
:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</el-card>
<!-- 新增/编辑弹窗 -->
<el-dialog :title="dialog.title" :visible.sync="dialog.visible" width="860px" append-to-body
:close-on-click-modal="false">
<el-form ref="trainingForm" :model="dialog.form" :rules="rules" label-width="130px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业代号" prop="zydh">
<el-input v-model="dialog.form.zydh" placeholder="请输入专业代号" :disabled="dialog.isEdit" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业名称" prop="zymc">
<el-input v-model="dialog.form.zymc" placeholder="请输入专业名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业代码" prop="zydm">
<el-input v-model="dialog.form.zydm" placeholder="请输入专业代码" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业方向">
<el-input v-model="dialog.form.zyfx" placeholder="请输入专业方向" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训层次" prop="pxcc">
<el-input v-model="dialog.form.pxcc" placeholder="请输入培训层次" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训类型" prop="pxlx">
<el-input v-model="dialog.form.pxlx" placeholder="请输入培训类型" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="培训类型2">
<el-input v-model="dialog.form.pxlx2" placeholder="请输入培训类型2" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学员类别">
<el-input v-model="dialog.form.xylb" placeholder="请输入学员类别" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学年制">
<el-input v-model="dialog.form.xnz" placeholder="请输入学年制" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学期数">
<el-input-number v-model="dialog.form.xqs" :min="1" :max="20" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业版本">
<el-input v-model="dialog.form.zybb" placeholder="请输入专业版本" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="自定义分类">
<el-input v-model="dialog.form.zdyfl" placeholder="请输入自定义分类" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教学管理机构编号">
<el-input v-model="dialog.form.jxgljgbh" placeholder="请输入教学管理机构编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规范名称">
<el-input v-model="dialog.form.gfmc" placeholder="请输入规范名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业规范">
<el-input v-model="dialog.form.zygf" placeholder="请输入专业规范" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="简称">
<el-input v-model="dialog.form.jc" placeholder="请输入简称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主干专业">
<el-radio-group v-model="dialog.form.zgzy">
<el-radio :label="true">是</el-radio>
<el-radio :label="false">否</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="专业备注">
<el-input v-model="dialog.form.zybz" placeholder="请输入专业备注" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="培养目标">
<el-input v-model="dialog.form.pymb" type="textarea" :rows="3" placeholder="请输入培养目标" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialog.visible = false">取 消</el-button>
<el-button type="primary" :loading="dialog.submitting" @click="submitDialog">确 定</el-button>
</div>
</el-dialog>
<!-- 详情弹窗 -->
<el-dialog title="人才培养方案详情" :visible.sync="detail.visible" width="760px" append-to-body
:close-on-click-modal="false">
<div v-loading="detail.loading" class="detail-body">
<el-descriptions :column="2" border>
<el-descriptions-item label="专业代号">{{ fmtVal(detail.data.zydh) }}</el-descriptions-item>
<el-descriptions-item label="专业名称">{{ fmtVal(detail.data.zymc) }}</el-descriptions-item>
<el-descriptions-item label="专业代码">{{ fmtVal(detail.data.zydm) }}</el-descriptions-item>
<el-descriptions-item label="专业方向">{{ fmtVal(detail.data.zyfx) }}</el-descriptions-item>
<el-descriptions-item label="培训类型">{{ fmtVal(detail.data.pxlx) }}</el-descriptions-item>
<el-descriptions-item label="培训层次">{{ fmtVal(detail.data.pxcc) }}</el-descriptions-item>
<el-descriptions-item label="培训类型2">{{ fmtVal(detail.data.pxlx2) }}</el-descriptions-item>
<el-descriptions-item label="学员类别">{{ fmtVal(detail.data.xylb) }}</el-descriptions-item>
<el-descriptions-item label="学年制">{{ fmtVal(detail.data.xnz) }}</el-descriptions-item>
<el-descriptions-item label="学期数">{{ fmtVal(detail.data.xqs) }}</el-descriptions-item>
<el-descriptions-item label="专业版本">{{ fmtVal(detail.data.zybb) }}</el-descriptions-item>
<el-descriptions-item label="主干专业">{{ fmtYesNo(detail.data.zgzy) }}</el-descriptions-item>
<el-descriptions-item label="自定义分类">{{ fmtVal(detail.data.zdyfl) }}</el-descriptions-item>
<el-descriptions-item label="教学管理机构编号">{{ fmtVal(detail.data.jxgljgbh) }}</el-descriptions-item>
<el-descriptions-item label="规范名称">{{ fmtVal(detail.data.gfmc) }}</el-descriptions-item>
<el-descriptions-item label="专业规范">{{ fmtVal(detail.data.zygf) }}</el-descriptions-item>
<el-descriptions-item label="简称">{{ fmtVal(detail.data.jc) }}</el-descriptions-item>
<el-descriptions-item label="专业标识号">{{ fmtVal(detail.data.zybsh) }}</el-descriptions-item>
<el-descriptions-item label="学科专业信息标识号">{{ fmtVal(detail.data.xkzyxxbsh) }}</el-descriptions-item>
<el-descriptions-item label="教学大纲编号">{{ fmtVal(detail.data.jxdgbh) }}</el-descriptions-item>
<el-descriptions-item label="人培编号">{{ fmtVal(detail.data.rpbh) }}</el-descriptions-item>
<el-descriptions-item label="节次类别">{{ fmtVal(detail.data.jclb) }}</el-descriptions-item>
<el-descriptions-item label="系统模式">{{ fmtVal(detail.data.xtms) }}</el-descriptions-item>
<el-descriptions-item label="启用时间">{{ fmtDateTime(detail.data.qysj) }}</el-descriptions-item>
<el-descriptions-item label="停用时间">{{ fmtDateTime(detail.data.tysj) }}</el-descriptions-item>
<el-descriptions-item label="专业备注" :span="2">{{ fmtVal(detail.data.zybz) }}</el-descriptions-item>
<el-descriptions-item label="培养目标" :span="2">{{ fmtVal(detail.data.pymb) }}</el-descriptions-item>
</el-descriptions>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="detail.visible = false">关 闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
addTraining,
disableTraining,
updateTraining,
getTraining,
listTraining
} from '@/api/teachBusiness/training'
export default {
name: 'TrainingPlan',
data() {
return {
loading: false,
// 查询条件(仅传后端 ZYBMapper 支持的字段)
searchForm: {
zymc: '',
zydm: '',
pxlx: '',
pxcc: ''
},
// 列表
tableData: [],
total: 0,
pageNum: 1,
pageSize: 20,
// 上传
selectedFile: null,
fileName: '',
// 新增/编辑弹窗
dialog: {
visible: false,
title: '',
isEdit: false,
submitting: false,
form: this.createEmptyForm()
},
// 详情弹窗
detail: {
visible: false,
loading: false,
data: {}
},
rules: {
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
zymc: [{ required: true, message: '请输入专业名称', trigger: 'blur' }],
zydm: [{ required: true, message: '请输入专业代码', trigger: 'blur' }],
pxcc: [{ required: true, message: '请输入培训层次', trigger: 'blur' }],
pxlx: [{ required: true, message: '请输入培训类型', trigger: 'blur' }]
}
}
},
created() {
this.fetchList()
},
methods: {
/* ---------- 列表加载 ---------- */
fetchList() {
this.loading = true
const params = {
pageNum: this.pageNum,
pageSize: this.pageSize
}
Object.keys(this.searchForm).forEach(key => {
const value = this.searchForm[key]
if (value !== '' && value !== null && value !== undefined) {
params[key] = value
}
})
listTraining(params).then(response => {
const data = response.data || {}
this.tableData = data.records || []
this.total = data.total || 0
}).catch(() => {
this.tableData = []
this.total = 0
}).finally(() => {
this.loading = false
})
},
/* ---------- 查询 / 重置 ---------- */
handleQuery() {
this.pageNum = 1
this.fetchList()
},
handleReset() {
this.searchForm = { zymc: '', zydm: '', pxlx: '', pxcc: '' }
this.pageNum = 1
this.fetchList()
},
/* ---------- 分页 ---------- */
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
handlePageChange(page) {
this.pageNum = page
this.fetchList()
},
/* ---------- 新增 / 编辑 ---------- */
handleAdd() {
this.dialog.title = '新建人才培养方案'
this.dialog.isEdit = false
this.dialog.form = this.createEmptyForm()
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.trainingForm) this.$refs.trainingForm.clearValidate()
})
},
handleEdit(row) {
this.dialog.title = '编辑人才培养方案'
this.dialog.isEdit = true
this.dialog.form = Object.assign({}, this.createEmptyForm(), row)
this.dialog.visible = true
this.$nextTick(() => {
if (this.$refs.trainingForm) this.$refs.trainingForm.clearValidate()
})
},
submitDialog() {
this.$refs.trainingForm.validate(valid => {
if (!valid) return
this.dialog.submitting = true
const payload = this.cleanPayload(this.dialog.form)
const isEdit = this.dialog.isEdit
const req = isEdit ? updateTraining(payload) : addTraining(payload)
req.then(() => {
this.$message.success(isEdit ? '修改成功' : '新增成功')
this.dialog.visible = false
this.fetchList()
}).catch(() => {}).finally(() => {
this.dialog.submitting = false
})
})
},
/* ---------- 详情 ---------- */
handleDetail(row) {
this.detail.visible = true
this.detail.loading = true
this.detail.data = {}
getTraining(row.zydh).then(response => {
this.detail.data = response.data || {}
}).catch(() => {
this.detail.data = {}
}).finally(() => {
this.detail.loading = false
})
},
/* ---------- 停用(逻辑删除) ---------- */
handleDisable(row) {
this.$confirm('确定停用人才培养方案「' + (row.zymc || row.zydh) + '」吗?停用后列表不再展示。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
return disableTraining(row.zydh)
}).then(() => {
this.$message.success('停用成功')
this.fetchList()
}).catch(() => {})
},
/* ---------- 下载 / 模板下载 / 上传(后端未提供接口) ---------- */
handleDownload() {
this.$message.warning('后端暂未提供该接口')
},
handleTemplateDownload() {
this.$message.warning('后端暂未提供该接口')
},
handleChooseFile() {
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
},
handleFileChange(e) {
const file = e.target.files && e.target.files[0]
this.selectedFile = file || null
this.fileName = file ? file.name : ''
},
handleUpload() {
this.$message.warning('后端暂未提供该接口')
},
/* ---------- 工具 ---------- */
createEmptyForm() {
return {
zydh: '',
zymc: '',
zyfx: '',
zydm: '',
pxcc: '',
pxlx: '',
pxlx2: '',
xylb: '',
xnz: '',
xqs: null,
zybb: '',
zdyfl: '',
jxgljgbh: '',
gfmc: '',
zygf: '',
jc: '',
zgzy: false,
zybz: '',
pymb: ''
}
},
/** 移除空值(''/null/undefined),保留 0/false 等有效值 */
cleanPayload(obj) {
const payload = {}
Object.keys(obj).forEach(key => {
const value = obj[key]
if (value !== '' && value !== null && value !== undefined) {
payload[key] = value
}
})
return payload
},
isTrue(val) {
return val === true || val === 1 || val === '1' || val === 'true'
},
fmtYesNo(val) {
return this.isTrue(val) ? '是' : '否'
},
fmtDateTime(val) {
const s = (val || '').substring(0, 10)
return s || '-'
},
fmtVal(val) {
return val === '' || val === null || val === undefined ? '-' : val
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.training-plan-page {
.search-card {
margin-bottom: 16px;
.search-form {
.w-full {
width: 100%;
}
.search-actions {
padding-top: 4px;
}
}
}
.action-card {
margin-bottom: 16px;
.action-row {
margin-bottom: 12px;
&:last-of-type {
margin-bottom: 0;
}
}
.upload-row {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
padding: 12px;
background: #fafafa;
border: 1px dashed #dcdfe6;
border-radius: 4px;
.upload-left {
display: flex;
align-items: center;
gap: 12px;
.file-name {
font-size: 13px;
color: #909399;
&.has-file {
color: #409eff;
}
}
}
}
}
.table-card {
.list-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
.pagination {
margin-top: 16px;
text-align: right;
}
}
.detail-body {
min-height: 60px;
}
}
.danger-text-btn {
color: #f56c6c;
&:hover {
color: #f78989;
}
}
</style>