前端文件

This commit is contained in:
2026-09-10 17:42:46 +08:00
parent 255a0e56a5
commit 345f425a1d
361 changed files with 73359 additions and 0 deletions
@@ -0,0 +1,603 @@
<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>
<!-- ==================== 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('后端暂未提供该接口')
},
// ==================== 文件上传 ====================
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>