forked from liweijie/education
610 lines
20 KiB
Vue
610 lines
20 KiB
Vue
<template>
|
||
<div class="app-container management-page elective-page">
|
||
<!-- ==================== 2. 查询条件区域 ==================== -->
|
||
<div class="section-card query-card">
|
||
<div class="section-heading">
|
||
<div>
|
||
<h2 class="section-title">筛选条件</h2>
|
||
<p class="section-description">按课程、班次、教员及状态筛选选修课</p>
|
||
</div>
|
||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||
</div>
|
||
<el-form>
|
||
<el-row :gutter="32">
|
||
<!-- 左栏 -->
|
||
<el-col :xs="24" :md="12">
|
||
<div class="query-field">
|
||
<span class="q-label no-check">授课教员</span>
|
||
<el-input v-model="searchForm.js" placeholder="请输入授课教员" clearable class="q-control" />
|
||
</div>
|
||
<div class="query-field">
|
||
<span class="q-label no-check">可选班次年级</span>
|
||
<el-input v-model="searchForm.kbcnj" placeholder="请输入可选班次年级" clearable class="q-control" />
|
||
</div>
|
||
<div class="query-field">
|
||
<span class="q-label no-check">选修课状态</span>
|
||
<el-select
|
||
v-model="searchForm.xxkzt"
|
||
placeholder="请选择选修课状态"
|
||
clearable
|
||
class="q-control"
|
||
>
|
||
<el-option v-for="item in statusOptions" :key="item" :label="item" :value="item" />
|
||
</el-select>
|
||
</div>
|
||
</el-col>
|
||
|
||
<!-- 右栏 -->
|
||
<el-col :xs="24" :md="12">
|
||
<div class="query-field">
|
||
<span class="q-label no-check">课程科目名称</span>
|
||
<el-input v-model="searchForm.kckmmc" placeholder="请输入课程科目名称" clearable class="q-control" />
|
||
</div>
|
||
<div class="query-field">
|
||
<span class="q-label no-check">选修班名称</span>
|
||
<el-input v-model="searchForm.xxbmc" placeholder="请输入选修班名称" clearable class="q-control" />
|
||
</div>
|
||
<div class="query-field">
|
||
<span class="q-label no-check">培训层次</span>
|
||
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" clearable class="q-control">
|
||
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
|
||
</el-select>
|
||
</div>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
|
||
</div>
|
||
|
||
<!-- ==================== 3. 导出按钮区域 ==================== -->
|
||
<div class="section-card export-card">
|
||
<div class="section-heading section-heading--compact">
|
||
<div>
|
||
<h2 class="section-title">数据导出</h2>
|
||
<p class="section-description">下载选修班及学员名单</p>
|
||
</div>
|
||
</div>
|
||
<div class="export-bar">
|
||
<el-button icon="el-icon-download" @click="handleDownloadList">下载选修班列表</el-button>
|
||
<el-button icon="el-icon-download" @click="handleDownloadWord">下载选修班学员名单Word</el-button>
|
||
<el-button icon="el-icon-download" @click="handleDownloadExcel">下载选修班学员名单Excel</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== 4. 操作按钮区域 ==================== -->
|
||
<div class="section-card action-card">
|
||
<div class="section-heading section-heading--compact">
|
||
<div>
|
||
<h2 class="section-title">选修课操作</h2>
|
||
<p class="section-description">新建选修课,或对已选数据执行批量操作</p>
|
||
</div>
|
||
</div>
|
||
<div class="action-row">
|
||
<el-button type="primary" icon="el-icon-plus" @click="handleOpenNew">新建选修课</el-button>
|
||
<el-button type="primary" plain icon="el-icon-circle-check" @click="handleOpenEarly">
|
||
所选批量开放报名
|
||
</el-button>
|
||
<el-button type="warning" plain icon="el-icon-remove-outline" @click="handleCancelEarly">
|
||
所选批量取消开放报名
|
||
</el-button>
|
||
<el-button type="danger" plain icon="el-icon-circle-close" @click="handleStopSignup">
|
||
批量停止报名
|
||
</el-button>
|
||
<!-- <el-button icon="el-icon-refresh" @click="handleReverseRange">
|
||
所选批量根据学员反向确定班次范围
|
||
</el-button> -->
|
||
</div>
|
||
<div class="action-row">
|
||
<el-button icon="el-icon-download" @click="handleDownloadTemplate">【选修课数据文件模板】下载</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== 5. 文件上传区域 ==================== -->
|
||
<div class="section-card upload-card">
|
||
<div class="section-heading section-heading--compact">
|
||
<div>
|
||
<h2 class="section-title">数据导入</h2>
|
||
<p class="section-description">选择填写完成的模板文件并上传</p>
|
||
</div>
|
||
</div>
|
||
<div class="upload-row">
|
||
<div class="upload-left">
|
||
<el-button icon="el-icon-folder-opened" @click="handleSelectFile">选择文件</el-button>
|
||
<span class="file-name" :class="{ 'has-file': selectedFile }">{{ fileName }}</span>
|
||
<input ref="fileInputRef" type="file" style="display: none" @change="handleFileChange" />
|
||
</div>
|
||
<div class="upload-right">
|
||
<el-button type="primary" icon="el-icon-upload2" @click="handleUpload">上传数据</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== 6. 数据表格区域 ==================== -->
|
||
<div class="section-card table-card">
|
||
<div class="section-heading section-heading--compact table-heading">
|
||
<div>
|
||
<h2 class="section-title">选修课列表</h2>
|
||
<p class="section-description">共 {{ total }} 条数据</p>
|
||
</div>
|
||
</div>
|
||
<el-table v-loading="loading" :data="tableData" border stripe class="main-table" @selection-change="handleSelectionChange">
|
||
<template slot="empty">
|
||
<span>无数据!</span>
|
||
</template>
|
||
<el-table-column type="selection" width="40" align="center" />
|
||
<el-table-column type="index" label="序号" width="60" align="center" :index="tableIndex" />
|
||
<el-table-column prop="kmc" label="课程" min-width="110" show-overflow-tooltip />
|
||
<el-table-column prop="jcmc" label="教材" min-width="120" show-overflow-tooltip />
|
||
<el-table-column prop="jyxm" label="实施教员" width="90" align="center" />
|
||
<el-table-column prop="jsmc" label="教学场地" width="100" show-overflow-tooltip />
|
||
<el-table-column prop="kxdlbc" label="显示可选队别班次" min-width="150" align="center" show-overflow-tooltip />
|
||
<el-table-column label="计划学时/运行学时/学分" width="130" align="center">
|
||
<template slot="header">
|
||
<div class="triple-header">
|
||
<span>计划学时</span>
|
||
<span>运行学时</span>
|
||
<span>学分</span>
|
||
</div>
|
||
</template>
|
||
<template slot-scope="{ row }">
|
||
<div class="triple-cell">
|
||
<span>{{ row.jhyxxsxf }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="开课结课日期" min-width="150" align="center">
|
||
<template slot-scope="{ row }">
|
||
{{ row.kkjkrq }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="报名日期" min-width="140" align="center">
|
||
<template slot-scope="{ row }">
|
||
{{ row.bmrq }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="jhrs" label="计划人数" width="90" align="center" />
|
||
<el-table-column prop="yqsm" label="要求说明" min-width="120" show-overflow-tooltip />
|
||
</el-table>
|
||
|
||
<div class="pagination-wrap">
|
||
<el-pagination
|
||
background
|
||
layout="total, prev, pager, next, jumper"
|
||
:total="total"
|
||
:page-size="pageSize"
|
||
:current-page="currentPage"
|
||
@current-change="handlePageChange"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新建选修课弹窗 -->
|
||
<el-dialog
|
||
:visible="dialogVisible"
|
||
title="新建选修课"
|
||
width="560px"
|
||
@update:visible="val => (dialogVisible = val)"
|
||
>
|
||
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
|
||
<el-form-item label="选修班名称" prop="xxbmc">
|
||
<el-input v-model="form.xxbmc" placeholder="请输入选修班名称" clearable />
|
||
</el-form-item>
|
||
<el-form-item label="年级">
|
||
<el-input v-model="form.nj" placeholder="新建选修班时填写,如 2024" clearable />
|
||
</el-form-item>
|
||
<el-form-item label="课程名称" prop="kcmc">
|
||
<el-input v-model="form.kcmc" placeholder="请输入课程名称,提交时按名称查找课编号" clearable />
|
||
</el-form-item>
|
||
<el-form-item label="实施教员" prop="js">
|
||
<el-input v-model="form.js" placeholder="请输入实施教员,提交时按名称查找教员编号" clearable />
|
||
</el-form-item>
|
||
<el-form-item label="教材">
|
||
<el-input v-model="form.jc" placeholder="请输入教材" clearable />
|
||
</el-form-item>
|
||
<el-form-item label="教学场地">
|
||
<el-input v-model="form.jxcd" placeholder="请输入教学场地,提交时按名称查找教室编号" clearable />
|
||
</el-form-item>
|
||
<el-form-item label="计划学时">
|
||
<el-input-number v-model="form.jhsxs" :min="0" :precision="0" class="w-full" />
|
||
</el-form-item>
|
||
<el-form-item label="运行学时">
|
||
<el-input-number v-model="form.yxsxs" :min="0" :precision="0" class="w-full" />
|
||
</el-form-item>
|
||
<el-form-item label="学分">
|
||
<el-input-number v-model="form.xf" :min="0" :precision="1" class="w-full" />
|
||
</el-form-item>
|
||
<el-form-item label="计划人数">
|
||
<el-input-number v-model="form.jh" :min="0" :precision="0" class="w-full" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer">
|
||
<el-button @click="dialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="handleNewConfirm">确定</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
listElective,
|
||
addElective,
|
||
batchOpenElective,
|
||
batchCancelOpenElective,
|
||
batchStopElective,
|
||
exportElectiveList,
|
||
exportElectiveStudentsExcel,
|
||
exportElectiveStudentsWord
|
||
} from '@/api/teachOffice/elective'
|
||
import { listKb } from '@/api/teachOffice/kb'
|
||
import { listTeacher } from '@/api/teachOffice/teacher'
|
||
import { listClassroom } from '@/api/teachBusiness/classroom'
|
||
import { saveAs } from 'file-saver'
|
||
|
||
export default {
|
||
name: 'Elective',
|
||
data() {
|
||
return {
|
||
// ==================== 下拉选项 ====================
|
||
statusOptions: ['拟制', '开放报名', '停止报名', '已开课'],
|
||
pxccOptions: ['无', '本科', '硕士', '博士'],
|
||
|
||
// ==================== 查询条件 ====================
|
||
searchForm: {
|
||
js: '',
|
||
kbcnj: '',
|
||
xxkzt: '',
|
||
kckmmc: '',
|
||
xxbmc: '',
|
||
pxcc: ''
|
||
},
|
||
|
||
// ==================== 数据表格 ====================
|
||
tableData: [],
|
||
total: 0,
|
||
loading: false,
|
||
selectedRows: [],
|
||
|
||
// ==================== 文件上传 ====================
|
||
selectedFile: null,
|
||
|
||
// ==================== 新建选修课弹窗 ====================
|
||
dialogVisible: false,
|
||
form: {
|
||
xxbmc: '',
|
||
nj: '',
|
||
kcmc: '',
|
||
js: '',
|
||
jc: '',
|
||
jxcd: '',
|
||
jhsxs: 0,
|
||
yxsxs: 0,
|
||
xf: 0,
|
||
jh: 0
|
||
},
|
||
rules: {
|
||
xxbmc: [{ required: true, message: '请输入选修班名称', trigger: 'blur' }],
|
||
kcmc: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
|
||
js: [{ required: true, message: '请输入实施教员', trigger: 'blur' }]
|
||
},
|
||
|
||
// ==================== 分页 ====================
|
||
currentPage: 1,
|
||
pageSize: 20
|
||
}
|
||
},
|
||
computed: {
|
||
fileName() {
|
||
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
|
||
}
|
||
},
|
||
created() {
|
||
this.loadList()
|
||
},
|
||
methods: {
|
||
// 组装查询过滤条件(列表与导出共用,导出不含分页参数)
|
||
buildFilter() {
|
||
const q = {}
|
||
if (this.searchForm.js) q.jyxm = this.searchForm.js
|
||
if (this.searchForm.kbcnj) q.kxbcnj = this.searchForm.kbcnj
|
||
if (this.searchForm.kckmmc) q.kmc = this.searchForm.kckmmc
|
||
if (this.searchForm.xxbmc) q.xxbmc = this.searchForm.xxbmc
|
||
if (this.searchForm.xxkzt) q.xkzt = this.searchForm.xxkzt
|
||
if (this.searchForm.pxcc) q.pxcc = this.searchForm.pxcc
|
||
return q
|
||
},
|
||
|
||
// ==================== 查询 ====================
|
||
handleQuery() {
|
||
this.currentPage = 1
|
||
this.loadList()
|
||
},
|
||
loadList() {
|
||
this.loading = true
|
||
const params = Object.assign({ pageNum: this.currentPage, pageSize: this.pageSize }, this.buildFilter())
|
||
listElective(params)
|
||
.then(res => {
|
||
const data = (res && res.data) || {}
|
||
this.tableData = data.records || []
|
||
this.total = data.total || 0
|
||
})
|
||
.finally(() => {
|
||
this.loading = false
|
||
})
|
||
},
|
||
|
||
// ==================== 导出 ====================
|
||
handleDownloadList() {
|
||
exportElectiveList(this.buildFilter())
|
||
.then(blob => {
|
||
saveAs(blob, '选修班列表.xlsx')
|
||
this.$message.success('选修班列表已下载')
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
handleDownloadWord() {
|
||
const bhList = this.selectedRows.map(row => row.bh)
|
||
if (bhList.length === 0) {
|
||
this.$message.warning('请先选择选修班')
|
||
return
|
||
}
|
||
exportElectiveStudentsWord(bhList)
|
||
.then(blob => {
|
||
saveAs(blob, '选修班学员名单.docx')
|
||
this.$message.success('选修班学员名单Word已下载')
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
handleDownloadExcel() {
|
||
const bhList = this.selectedRows.map(row => row.bh)
|
||
if (bhList.length === 0) {
|
||
this.$message.warning('请先选择选修班')
|
||
return
|
||
}
|
||
exportElectiveStudentsExcel(bhList)
|
||
.then(blob => {
|
||
saveAs(blob, '选修班学员名单.xlsx')
|
||
this.$message.success('选修班学员名单Excel已下载')
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
|
||
// ==================== 操作按钮 ====================
|
||
handleOpenEarly() {
|
||
const bhList = this.selectedRows.map(row => row.bh)
|
||
if (bhList.length === 0) {
|
||
this.$message.warning('请先选择选修班')
|
||
return
|
||
}
|
||
batchOpenElective(bhList)
|
||
.then(() => {
|
||
this.$message.success('所选班已开放报名')
|
||
this.loadList()
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
handleCancelEarly() {
|
||
const bhList = this.selectedRows.map(row => row.bh)
|
||
if (bhList.length === 0) {
|
||
this.$message.warning('请先选择选修班')
|
||
return
|
||
}
|
||
batchCancelOpenElective(bhList)
|
||
.then(() => {
|
||
this.$message.success('所选班已取消开放报名')
|
||
this.loadList()
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
handleStopSignup() {
|
||
const bhList = this.selectedRows.map(row => row.bh)
|
||
if (bhList.length === 0) {
|
||
this.$message.warning('请先选择选修班')
|
||
return
|
||
}
|
||
batchStopElective(bhList)
|
||
.then(() => {
|
||
this.$message.success('已批量停止报名')
|
||
this.loadList()
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
handleReverseRange() {
|
||
this.$message.warning('后端暂未提供该接口')
|
||
},
|
||
handleDownloadTemplate() {
|
||
this.$message.warning('后端暂未提供该接口')
|
||
},
|
||
|
||
// ==================== 文件上传 ====================
|
||
handleSelectFile() {
|
||
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
|
||
},
|
||
handleFileChange(e) {
|
||
this.selectedFile = e.target.files[0] || null
|
||
},
|
||
handleUpload() {
|
||
if (!this.selectedFile) {
|
||
this.$message.warning('请先选择文件')
|
||
return
|
||
}
|
||
this.$message.warning('后端暂未提供该接口')
|
||
},
|
||
|
||
// ==================== 表格多选 ====================
|
||
handleSelectionChange(rows) {
|
||
this.selectedRows = rows
|
||
},
|
||
|
||
// ==================== 新建选修课弹窗 ====================
|
||
handleOpenNew() {
|
||
this.form = {
|
||
xxbmc: '',
|
||
nj: '',
|
||
kcmc: '',
|
||
js: '',
|
||
jc: '',
|
||
jxcd: '',
|
||
jhsxs: 0,
|
||
yxsxs: 0,
|
||
xf: 0,
|
||
jh: 0
|
||
}
|
||
this.dialogVisible = true
|
||
this.$nextTick(() => {
|
||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||
})
|
||
},
|
||
handleNewConfirm() {
|
||
if (!this.$refs.formRef) return
|
||
this.$refs.formRef.validate((valid) => {
|
||
if (!valid) return
|
||
this.submitNewElective()
|
||
})
|
||
},
|
||
submitNewElective() {
|
||
const emptyPage = { data: { records: [] } }
|
||
Promise.all([
|
||
listKb({ pageNum: 1, pageSize: 1, kmc: this.form.kcmc }),
|
||
listTeacher({ pageNum: 1, pageSize: 1, jyxm: this.form.js }),
|
||
this.form.jxcd ? listClassroom({ pageNum: 1, pageSize: 1, jsmc: this.form.jxcd }) : Promise.resolve(emptyPage)
|
||
])
|
||
.then(([kbRes, teacherRes, roomRes]) => {
|
||
const kbh = kbRes?.data?.records?.[0]?.kbh
|
||
const jybh = teacherRes?.data?.records?.[0]?.jybh
|
||
const jsbh = roomRes?.data?.records?.[0]?.jsbh
|
||
if (!kbh) {
|
||
this.$message.warning(`未找到课程科目:${this.form.kcmc}`)
|
||
return
|
||
}
|
||
const dto = {
|
||
kbh: kbh,
|
||
xxbmc: this.form.xxbmc,
|
||
nj: this.form.nj || undefined,
|
||
jybh: jybh || undefined,
|
||
jsbh: jsbh || undefined,
|
||
rs: this.form.jh || undefined,
|
||
xs: this.form.jhsxs || undefined,
|
||
xf: this.form.xf || undefined
|
||
}
|
||
return addElective(dto).then(() => {
|
||
this.$message.success('新建选修课成功')
|
||
this.dialogVisible = false
|
||
this.loadList()
|
||
})
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
|
||
// ==================== 分页 ====================
|
||
handlePageChange(page) {
|
||
this.currentPage = page
|
||
this.loadList()
|
||
},
|
||
|
||
// 表格序号(跨页连续)
|
||
tableIndex(index) {
|
||
return (this.currentPage - 1) * this.pageSize + index + 1
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
// ==================== 2. 查询条件区域 ====================
|
||
.query-card {
|
||
.query-field {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 14px;
|
||
min-height: 32px;
|
||
|
||
.q-label {
|
||
width: 120px;
|
||
flex-shrink: 0;
|
||
font-size: 13px;
|
||
color: #303133;
|
||
text-align: right;
|
||
margin-right: 10px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.q-control {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
.export-bar {
|
||
align-items: center;
|
||
}
|
||
|
||
// ==================== 4. 操作按钮区域 ====================
|
||
.action-card {
|
||
.action-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ==================== 5. 文件上传区域 ====================
|
||
.upload-card {
|
||
.upload-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
|
||
.upload-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
|
||
.file-name {
|
||
font-size: 12px;
|
||
color: #999;
|
||
|
||
&.has-file {
|
||
color: #303133;
|
||
}
|
||
}
|
||
}
|
||
|
||
.upload-right {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ==================== 6. 数据表格区域 ====================
|
||
.table-card {
|
||
.main-table {
|
||
::v-deep .cell {
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
|
||
.triple-header,
|
||
.triple-cell {
|
||
display: flex;
|
||
flex-direction: column;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.pagination-wrap {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 16px;
|
||
}
|
||
}
|
||
</style>
|