前端代码

This commit is contained in:
liumengyu
2026-08-21 18:42:51 +08:00
parent 054947482f
commit 04e06a9c9a
762 changed files with 62992 additions and 0 deletions
@@ -0,0 +1,533 @@
<template>
<div class="app-container">
<!-- ==================== 2. 查询条件区域 ==================== -->
<div class="section-card query-card">
<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">
<el-checkbox v-model="searchForm.xxkztEnabled" />
<span class="q-label">选修课状态</span>
<el-select v-model="searchForm.xxkzt" class="q-control" :disabled="!searchForm.xxkztEnabled">
<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">
<el-checkbox v-model="searchForm.pxccEnabled" />
<span class="q-label">培训层次</span>
<el-select v-model="searchForm.pxcc" class="q-control" :disabled="!searchForm.pxccEnabled">
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
</el-col>
</el-row>
</el-form>
<div class="notice">注意:勾选"选择框"表示启用该项对应的查询条件;文本输入框非空白表示启用对应的查询条件。</div>
<div class="query-footer">
<el-button class="gray-btn" @click="handleQuery">查询</el-button>
</div>
</div>
<!-- ==================== 3. 导出按钮区域 ==================== -->
<div class="section-card export-card">
<div class="export-bar">
<el-button type="primary" @click="handleDownloadList">下载选修班列表</el-button>
<el-button type="primary" @click="handleDownloadWord">下载选修班学员名单Word</el-button>
<el-button type="primary" @click="handleDownloadExcel">下载选修班学员名单Excel</el-button>
</div>
</div>
<!-- ==================== 4. 操作按钮区域 ==================== -->
<div class="section-card action-card">
<div class="action-row">
<el-button type="primary" @click="handleOpenNew">新建选修课</el-button>
<el-button type="primary" @click="handleOpenEarly">所选批量开放报名</el-button>
<el-button type="primary" @click="handleCancelEarly">所选批量取消开放报名</el-button>
<el-button type="primary" @click="handleStopSignup">批量停止报名</el-button>
<el-button type="primary" @click="handleReverseRange">所选批量根据学员反向确定班次范围</el-button>
</div>
<div class="action-row">
<el-button type="primary" @click="handleDownloadTemplate">【选修课数据文件模板】下载</el-button>
</div>
</div>
<!-- ==================== 5. 文件上传区域 ==================== -->
<div class="section-card upload-card">
<div class="upload-row">
<div class="upload-left">
<el-button @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" @click="handleUpload">上传数据</el-button>
</div>
</div>
</div>
<!-- ==================== 6. 数据表格区域 ==================== -->
<div class="section-card table-card">
<el-table :data="pagedData" 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="course" label="课程" min-width="110" show-overflow-tooltip />
<el-table-column prop="textbook" label="教材" min-width="120" show-overflow-tooltip />
<el-table-column prop="teacher" label="实施教员" width="90" align="center" />
<el-table-column prop="place" label="教学场地" width="100" show-overflow-tooltip />
<el-table-column label="显示可选队别班次" min-width="150" show-overflow-tooltip>
<template slot="header">
<div class="checkable-header">
<el-checkbox />
<span>显示可选队别班次</span>
</div>
</template>
<template slot-scope="{ row }">{{ row.teamClass }}</template>
</el-table-column>
<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.planHours }}</span>
<span>{{ row.runHours }}</span>
<span>{{ row.credit }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="开课结课日期" min-width="150" align="center">
<template slot-scope="{ row }">
{{ row.startDate }} ~ {{ row.endDate }}
</template>
</el-table-column>
<el-table-column label="报名日期" min-width="140" align="center">
<template slot-scope="{ row }">
{{ row.signupStart }} ~ {{ row.signupEnd }}
</template>
</el-table-column>
<el-table-column prop="planCount" label="计划人数" width="90" align="center" />
<el-table-column prop="requirement" label="要求说明" min-width="120" show-overflow-tooltip />
</el-table>
<div class="pagination-wrap">
<el-pagination
background
layout="total, prev, pager, next, jumper"
:total="tableData.length"
: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="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>
export default {
name: 'Elective',
data() {
return {
// ==================== 下拉选项 ====================
statusOptions: ['拟制', '开放报名', '停止报名', '已开课'],
pxccOptions: ['无', '本科', '硕士', '博士'],
// ==================== 查询条件 ====================
searchForm: {
js: '',
kbcnj: '',
xxkztEnabled: false,
xxkzt: '拟制',
kckmmc: '',
xxbmc: '',
pxccEnabled: false,
pxcc: '无'
},
// ==================== 数据表格 mock ====================
tableData: [
{ id: 1, course: '军事英语', textbook: '军事英语教程', teacher: '张三', place: '教室101', teamClass: '2025级学员1-5队', planHours: 32, runHours: 30, credit: 2, startDate: '2026-09-01', endDate: '2027-01-15', signupStart: '2026-08-20', signupEnd: '2026-09-01', planCount: 60, requirement: '限选50人' },
{ id: 2, course: '摄影技术', textbook: '摄影基础', teacher: '李四', place: '教室102', teamClass: '2024级学员1-4队', planHours: 24, runHours: 24, credit: 1.5, startDate: '2026-09-01', endDate: '2027-01-15', signupStart: '2026-08-20', signupEnd: '2026-09-01', planCount: 40, requirement: '自备相机' },
{ id: 3, course: '网球', textbook: '网球教程', teacher: '王五', place: '操场', teamClass: '2025级学员2-6队', planHours: 24, runHours: 22, credit: 1.5, startDate: '2026-09-01', endDate: '2027-01-15', signupStart: '2026-08-21', signupEnd: '2026-09-02', planCount: 30, requirement: '需自备球拍' },
{ id: 4, course: '书法鉴赏', textbook: '中国书法', teacher: '赵六', place: '教室103', teamClass: '2026级学员1-3队', planHours: 16, runHours: 16, credit: 1, startDate: '2026-09-08', endDate: '2026-12-30', signupStart: '2026-08-25', signupEnd: '2026-09-05', planCount: 35, requirement: '' },
{ id: 5, course: '无人机操作', textbook: '无人机原理', teacher: '孙七', place: '室外训练场', teamClass: '2025级学员1-6队', planHours: 32, runHours: 28, credit: 2, startDate: '2026-09-08', endDate: '2027-01-10', signupStart: '2026-08-26', signupEnd: '2026-09-06', planCount: 25, requirement: '需通过体检' }
],
selectedRows: [],
// ==================== 文件上传 ====================
selectedFile: null,
// ==================== 新建选修课弹窗 ====================
dialogVisible: false,
form: {
kcmc: '',
js: '',
jc: '',
jxcd: '',
jhsxs: 0,
yxsxs: 0,
xf: 0,
jh: 0
},
rules: {
kcmc: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
js: [{ required: true, message: '请输入实施教员', trigger: 'blur' }]
},
// ==================== 分页 ====================
currentPage: 1,
pageSize: 20
}
},
computed: {
fileName() {
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
},
pagedData() {
const start = (this.currentPage - 1) * this.pageSize
return this.tableData.slice(start, start + this.pageSize)
}
},
methods: {
// ==================== 查询 ====================
handleQuery() {
console.log('查询条件:', JSON.parse(JSON.stringify(this.searchForm)))
this.$message.success('查询完成(前端模拟)')
},
// ==================== 导出 ====================
downloadBlob(content, filename) {
const blob = new Blob(['\ufeff' + content], { type: 'text/plain;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = filename
link.click()
URL.revokeObjectURL(link.href)
},
handleDownloadList() {
this.downloadBlob('课程,教材,实施教员,教学场地,计划人数\n', '选修班列表.txt')
this.$message.success('选修班列表已下载(前端模拟)')
},
handleDownloadWord() {
this.downloadBlob('选修班学员名单\n', '选修班学员名单Word.txt')
this.$message.success('选修班学员名单Word已下载(前端模拟)')
},
handleDownloadExcel() {
this.downloadBlob('选修班学员名单\n', '选修班学员名单Excel.txt')
this.$message.success('选修班学员名单Excel已下载(前端模拟)')
},
handleDownloadTemplate() {
this.downloadBlob('课程,教材,实施教员,教学场地,计划学时,学分,开课结课日期,报名日期,计划人数,要求说明\n', '选修课数据文件模板.txt')
this.$message.success('【选修课数据文件模板】已下载(前端模拟)')
},
// ==================== 操作按钮 ====================
handleOpenEarly() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择选修班')
return
}
this.$message.success('所选班已早开放报名(前端模拟)')
},
handleCancelEarly() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择选修班')
return
}
this.$message.success('所选班已早取消开放报名(前端模拟)')
},
handleStopSignup() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择选修班')
return
}
this.$message.success('已批量停止报名(前端模拟)')
},
handleReverseRange() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择选修班')
return
}
this.$message.success('已根据学员反向确定班次范围(前端模拟)')
},
// ==================== 文件上传 ====================
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
}
console.log('上传文件:', this.selectedFile.name)
this.$message.success(`文件「${this.selectedFile.name}」上传成功(前端模拟)`)
},
// ==================== 表格多选 ====================
handleSelectionChange(rows) {
this.selectedRows = rows
},
// ==================== 新建选修课弹窗 ====================
handleOpenNew() {
Object.assign(this.form, { kcmc: '', js: '', jc: '', jxcd: '', jhsxs: 0, yxsxs: 0, xf: 0, jh: 0 })
this.dialogVisible = true
},
handleNewConfirm() {
if (!this.$refs.formRef) return
this.$refs.formRef.validate((valid) => {
if (!valid) return
console.log('新建选修课:', JSON.parse(JSON.stringify(this.form)))
this.$message.success('新建选修课成功(前端模拟)')
this.dialogVisible = false
})
},
// ==================== 分页 ====================
handlePageChange(page) {
this.currentPage = page
},
// 表格序号(跨页连续)
tableIndex(index) {
return (this.currentPage - 1) * this.pageSize + index + 1
}
}
}
</script>
<style scoped lang="scss">
// ==================== 公共区域容器 ====================
.section-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
padding: 16px 20px;
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
// ==================== 2. 查询条件区域 ====================
.query-card {
.query-field {
display: flex;
align-items: center;
margin-bottom: 14px;
min-height: 32px;
::v-deep .el-checkbox {
margin-right: 6px;
flex-shrink: 0;
}
.q-label {
width: 120px;
flex-shrink: 0;
font-size: 13px;
color: #303133;
text-align: right;
margin-right: 10px;
white-space: nowrap;
&.no-check {
margin-left: 24px;
}
}
.q-control {
flex: 1;
min-width: 0;
}
}
.notice {
font-size: 12px;
color: #f56c6c;
margin-top: 8px;
text-align: left;
}
.query-footer {
display: flex;
justify-content: flex-end;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
}
// ==================== 3. 导出按钮区域 ====================
.export-card {
.export-bar {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
}
// ==================== 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;
}
}
}
}
}
// ==================== 6. 数据表格区域 ====================
.table-card {
.main-table {
::v-deep .cell {
font-size: 12px;
}
}
.checkable-header {
display: flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
.triple-header,
.triple-cell {
display: flex;
flex-direction: column;
line-height: 1.4;
}
.pagination-wrap {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
}
.w-full {
width: 100%;
}
// ==================== 按钮样式 ====================
.gray-btn {
height: 28px;
padding: 2px 16px;
font-size: 12px;
background: #f5f5f5;
border: 1px solid #c0c4cc;
color: #333;
border-radius: 2px;
&:hover,
&:focus {
background: #ebebeb;
border-color: #c0c4cc;
color: #333;
}
}
</style>
@@ -0,0 +1,341 @@
<template>
<div class="app-container teach-office">
<!-- 查询条件区域 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="88px" v-show="showSearch">
<el-form-item label="教研室代号" prop="jysdh">
<el-input
v-model="queryParams.jysdh"
placeholder="请输入教研室代号"
clearable
style="width: 160px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="教研室名称" prop="jysmc">
<el-input
v-model="queryParams.jysmc"
placeholder="请输入教研室名称(模糊)"
clearable
style="width: 180px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="部系" prop="bx">
<el-input
v-model="queryParams.bx"
placeholder="请输入部系"
clearable
style="width: 160px"
@keyup.enter.native="handleQuery"
/>
</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>
<!-- 操作按钮区域 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-upload2" size="mini" @click="openImport">导入</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 数据列表区域 -->
<el-table v-loading="loading" :data="officeList" border :height="tableHeight">
<el-table-column label="教研室代号" align="center" prop="jysdh" min-width="110" />
<el-table-column label="教研室名称" align="center" prop="jysmc" min-width="160" :show-overflow-tooltip="true" />
<el-table-column label="简称" align="center" prop="jc" min-width="90" />
<el-table-column label="部系" align="center" prop="bx" min-width="100" />
<el-table-column label="序号" align="center" prop="xh" min-width="80" />
<el-table-column label="机关性质" align="center" min-width="90">
<template slot-scope="scope">
<el-tag :type="scope.row.jgxz ? 'primary' : 'info'" size="mini">{{ scope.row.jgxz ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="bz" min-width="120" :show-overflow-tooltip="true" />
<el-table-column label="操作" align="center" min-width="140" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-circle-close"
style="color: #f56c6c"
:disabled="scope.row.ty"
@click="handleDisable(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"
/>
<!-- 新增/编辑对话框 -->
<el-dialog :title="title" :visible.sync="open" width="560px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="教研室代号" prop="jysdh">
<el-input v-model="form.jysdh" placeholder="请输入教研室代号" :disabled="!isAdd" maxlength="20" />
</el-form-item>
<el-form-item label="教研室名称" prop="jysmc">
<el-input v-model="form.jysmc" placeholder="请输入教研室名称" maxlength="50" />
</el-form-item>
<el-form-item label="简称" prop="jc">
<el-input v-model="form.jc" placeholder="请输入简称" maxlength="20" />
</el-form-item>
<el-form-item label="部系" prop="bx">
<el-input v-model="form.bx" placeholder="请输入部系" maxlength="30" />
</el-form-item>
<el-form-item label="序号" prop="xh">
<el-input v-model="form.xh" placeholder="请输入序号" maxlength="10" />
</el-form-item>
<el-form-item label="备注" prop="bz">
<el-input v-model="form.bz" type="textarea" placeholder="请输入备注" :rows="3" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
<!-- 导入对话框 -->
<el-dialog title="教研室数据导入" :visible.sync="importOpen" width="420px" append-to-body>
<el-upload
ref="uploadRef"
:action="importUrl"
:headers="uploadHeaders"
:on-success="handleImportSuccess"
:on-error="handleImportError"
:limit="1"
:before-upload="beforeImport"
accept=".xls, .xlsx"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip text-center" slot="tip">
<span>仅允许导入 xls、xlsx 格式文件。</span>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitImport">确 定</el-button>
<el-button @click="importOpen = false">取 消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listOffice, addOffice, updateOffice, disableOffice } from "@/api/teachOffice/office"
import { getToken } from '@/utils/auth'
export default {
name: "Office",
data() {
return {
// 遮罩层
loading: true,
// 显示搜索条件
showSearch: true,
// 表格高度
tableHeight: window.innerHeight - 280,
// 总条数
total: 0,
// 教研室列表数据
officeList: [],
// 弹出层标题
title: "",
// 是否显示新增/编辑弹出层
open: false,
// 是否新增(false 为编辑)
isAdd: true,
// 是否显示导入弹出层
importOpen: false,
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 20,
jysdh: undefined,
jysmc: undefined,
bx: undefined
},
// 表单参数
form: {},
// 表单校验
rules: {
jysdh: [
{ required: true, message: "教研室代号不能为空", trigger: "blur" }
],
jysmc: [
{ required: true, message: "教研室名称不能为空", trigger: "blur" }
]
}
}
},
computed: {
importUrl() {
return process.env.VUE_APP_BASE_API + '/jys/office/import'
},
uploadHeaders() {
return { Authorization: 'Bearer ' + getToken() }
}
},
created() {
this.getList()
},
methods: {
/** 查询教研室列表 */
getList() {
this.loading = true
listOffice(this.queryParams).then(response => {
// 后端返回 MyBatis-Plus 分页结构:data.records / data.total
const data = response.data || {}
this.officeList = data.records || []
this.total = data.total || 0
this.loading = false
}).catch(() => {
this.officeList = []
this.total = 0
this.loading = false
})
},
/** 查询按钮 */
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
/** 重置按钮 */
resetQuery() {
this.resetForm("queryForm")
this.handleQuery()
},
/** 新增按钮 */
handleAdd() {
this.reset()
this.isAdd = true
this.open = true
this.title = "新增教研室"
},
/** 编辑按钮 */
handleUpdate(row) {
this.reset()
this.isAdd = false
// 编辑:jysdh 必传
this.form = {
jysdh: row.jysdh,
jysmc: row.jysmc,
jc: row.jc,
bx: row.bx,
xh: row.xh,
bz: row.bz
}
this.open = true
this.title = "编辑教研室"
},
/** 停用按钮 */
handleDisable(row) {
const jysdh = row.jysdh
this.$modal.confirm('确认停用教研室【' + row.jysmc + '】吗?').then(() => {
return disableOffice(jysdh)
}).then(() => {
this.getList()
this.$modal.msgSuccess("停用成功")
}).catch(() => {})
},
/** 提交表单 */
submitForm() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.isAdd) {
addOffice(this.form).then(response => {
this.$modal.msgSuccess("新增成功")
this.open = false
this.getList()
}).catch(() => {})
} else {
updateOffice(this.form).then(response => {
this.$modal.msgSuccess("修改成功")
this.open = false
this.getList()
}).catch(() => {})
}
}
})
},
/** 重置表单 */
reset() {
this.form = {
jysdh: undefined,
jysmc: undefined,
jc: undefined,
bx: undefined,
xh: undefined,
bz: undefined
}
this.resetForm("form")
},
/** 取消按钮 */
cancel() {
this.open = false
this.reset()
},
/** 打开导入对话框 */
openImport() {
this.importOpen = true
this.$nextTick(() => {
if (this.$refs.uploadRef) {
this.$refs.uploadRef.clearFiles()
}
})
},
/** 导入前校验文件格式 */
beforeImport(file) {
const name = file.name.toLowerCase()
if (!name.endsWith('.xls') && !name.endsWith('.xlsx')) {
this.$modal.msgError('仅支持 xls、xlsx 格式文件')
return false
}
return true
},
/** 提交导入 */
submitImport() {
const files = this.$refs.uploadRef.uploadFiles
if (!files || files.length === 0) {
this.$modal.msgError('请选择要导入的文件')
return
}
this.$refs.uploadRef.submit()
},
/** 导入成功 */
handleImportSuccess(response) {
this.importOpen = false
this.$alert("<div style='overflow:auto;overflow-x:hidden;max-height:70vh;padding:10px 20px 0;'>" + response.msg + '</div>', '导入结果', { dangerouslyUseHTMLString: true })
this.getList()
},
/** 导入失败 */
handleImportError() {
this.$modal.msgError('导入失败,请稍后重试')
}
}
}
</script>
<style scoped lang="scss">
.teach-office {
.mb8 {
margin-bottom: 8px;
}
}
</style>
@@ -0,0 +1,906 @@
<template>
<el-dialog
:visible="dialogVisible"
title="班历设置"
width="94%"
top="4vh"
:close-on-click-modal="false"
class="class-calendar-dialog"
@update:visible="val => dialogVisible = val"
>
<div class="class-calendar">
<!-- ==================== 1. 学期与班次信息 ==================== -->
<div class="info-panel">
<div class="info-title-row">
<span class="semester-name">{{ semesterName }}</span>
<span class="class-name">班次{{ bc }}</span>
</div>
<div class="date-range-row">
<div class="range-item">从{{ calStart }}到{{ calEnd }},共{{ calWeeks }}周</div>
<div class="range-item">从{{ classStart }}到{{ classEnd }},共{{ classWeeks }}周</div>
</div>
</div>
<!-- ==================== 2. 设置面板(事件设置 + 操作按钮) ==================== -->
<div class="settings-panel">
<div class="event-settings-row">
<div class="setting-item">
<span class="setting-label">事件名称</span>
<el-input v-model="eventName" size="small" style="width: 140px" placeholder="" />
</div>
<el-checkbox v-model="isBold" size="small">加粗显示</el-checkbox>
<el-checkbox v-model="isSchedulable" size="small">可排课</el-checkbox>
<el-checkbox v-model="isRegularClass" size="small">正课</el-checkbox>
<el-checkbox v-model="showRemark" size="small">课表备注中显示</el-checkbox>
<div class="setting-item">
<span class="setting-label">备注</span>
<el-input v-model="remark" size="small" style="width: 150px" placeholder="" />
</div>
<el-checkbox v-model="show78" size="small">显示7/8节</el-checkbox>
<el-checkbox v-model="showEvening" size="small">显示晚上</el-checkbox>
<el-checkbox v-model="showNight" size="small">显示夜间</el-checkbox>
</div>
<!-- ==================== 3. 操作按钮区域 ==================== -->
<div class="action-buttons-row">
<button type="button" class="gray-btn" @click="handleAbsorb">吸取</button>
<button type="button" class="gray-btn" @click="handleSet">设定</button>
<button type="button" class="gray-btn" @click="handleDelete">删除</button>
<button type="button" class="blue-btn" @click="handleApplyOther">选定区域设置应用于其它班次</button>
<button type="button" class="gray-btn" @click="handleApplyWhole">整个班历应用于其它班次</button>
<button type="button" class="gray-btn" @click="handleRefresh">刷新</button>
</div>
</div>
<!-- ==================== 4. 时间编排区域 ==================== -->
<div class="table-area">
<div class="calendar-table-wrapper">
<table class="calendar-table">
<thead>
<tr class="head-row-1">
<th rowspan="2" class="cell-week-head">周次</th>
<th rowspan="2" class="cell-range-head">日期段</th>
<th v-for="name in dayNames" :key="name" :colspan="visiblePeriods.length">{{ name }}</th>
</tr>
<tr class="head-row-2">
<template v-for="(name, di) in dayNames">
<th v-for="p in visiblePeriods" :key="name + '-' + p.key">{{ p.label }}</th>
</template>
</tr>
</thead>
<tbody>
<tr v-for="(w, wi) in weekDefs" :key="w.num">
<td class="week-num">{{ w.num }}</td>
<td class="date-range">{{ w.start }}至{{ w.end }}</td>
<template v-for="d in 5">
<td
v-for="p in visiblePeriods"
:key="wi + '-' + (d - 1) + '-' + p.key"
class="cell"
:class="cellClass(getCell(wi, d - 1, p.key))"
:style="cellStyle(getCell(wi, d - 1, p.key))"
:title="getCell(wi, d - 1, p.key).isDate ? '该时间格无事件,可选中后设定事件' : '双击可吸取该事件设置'"
@mousedown.prevent="onCellMouseDown(getCell(wi, d - 1, p.key), $event)"
@mousemove="onCellMouseMove(getCell(wi, d - 1, p.key))"
@dblclick="handleCellDblClick(getCell(wi, d - 1, p.key))"
>{{ getCell(wi, d - 1, p.key).text }}</td>
</template>
</tr>
</tbody>
</table>
</div>
<div class="select-tip">
提示:在时间编排区域按住鼠标拖动可连片多选,按住 Ctrl 键可用鼠标选择多个区域;双击事件格可吸取其设置。
</div>
</div>
</div>
<!-- ==================== 学员队选取弹窗 ==================== -->
<ClassTeamSelectDialog
:visible="teamSelectVisible"
@update:visible="val => teamSelectVisible = val"
@confirm="handleTeamConfirm"
/>
</el-dialog>
</template>
<script>
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
// ==================== 模块级常量 ====================
/** 时段定义:1-2、3-4、5-6 恒显示;7-8 / 9-10(晚上) / 夜间 由复选框控制 */
const ALL_PERIODS = [
{ key: '1-2', label: '1-2' },
{ key: '3-4', label: '3-4' },
{ key: '5-6', label: '5-6' },
{ key: '7-8', label: '7-8' },
{ key: '9-10', label: '9-10' },
{ key: 'night', label: '夜间' }
]
/** 事件模式下标 → ALL_PERIODS 下标(1-2、3-4、5-6、9-10) */
const PATTERN_PERIOD_IDX = [0, 1, 2, 4]
/** 事件类型 → 颜色 */
const TYPE_STYLE = {
red: { bg: '#d94343', fg: '#ffffff' }, // 正常课程/活动(马基、保密、布雷、维修、布修、战术、国庆节、政工)
dark: { bg: '#2a8a7f', fg: '#ffffff' }, // 深青/墨绿:游泳、道德、日期标记
green: { bg: '#4f9d57', fg: '#ffffff' }, // 特殊课程/活动(核心、组训)
cyan: { bg: '#8fd4e0', fg: '#14505a' } // 青/浅蓝:学前、元旦
}
const E = (text, type) => ({ text, type })
const D = null // 日期格:显示当天日期
/** 班历事件(第8周~第29周,严格按截图还原;null 为日期格) */
const weekDefs = [
// 学前适应(青)
{
num: 8, start: '08-21', end: '08-27',
monday: [E('学前', 'cyan'), E('学前', 'cyan'), E('学前', 'cyan'), E('学前', 'cyan')],
others: [E('学前', 'cyan'), E('学前', 'cyan'), E('学前', 'cyan'), E('学前', 'cyan')]
},
// 马基(红)+ 游泳(墨绿)
{
num: 9, start: '08-28', end: '09-03',
monday: [E('马基', 'red'), E('游泳', 'dark'), D, D],
others: [E('马基', 'red'), E('游泳', 'dark'), D, D]
},
{
num: 10, start: '09-04', end: '09-10',
monday: [E('马基', 'red'), E('游泳', 'dark'), D, D],
others: [E('马基', 'red'), E('游泳', 'dark'), D, D]
},
// 马基(红)+ 保密(红)
{
num: 11, start: '09-11', end: '09-17',
monday: [E('马基', 'red'), E('保密', 'red'), D, D],
others: [E('马基', 'red'), E('保密', 'red'), D, D]
},
{
num: 12, start: '09-18', end: '09-24',
monday: [E('马基', 'red'), E('保密', 'red'), D, D],
others: [E('马基', 'red'), E('保密', 'red'), D, D]
},
// 布雷(红)
{
num: 13, start: '09-25', end: '10-01',
monday: [E('布雷', 'red'), E('布雷', 'red'), D, D],
others: [E('布雷', 'red'), E('布雷', 'red'), D, D]
},
// 国庆节(红)
{
num: 14, start: '10-02', end: '10-08',
monday: [E('国庆节', 'red'), E('国庆节', 'red'), E('国庆节', 'red'), E('国庆节', 'red')],
others: [E('国庆节', 'red'), E('国庆节', 'red'), E('国庆节', 'red'), E('国庆节', 'red')]
},
{
num: 15, start: '10-09', end: '10-15',
monday: [E('布雷', 'red'), E('布雷', 'red'), D, D],
others: [E('布雷', 'red'), E('布雷', 'red'), D, D]
},
{
num: 16, start: '10-16', end: '10-22',
monday: [E('布雷', 'red'), E('布雷', 'red'), D, D],
others: [E('布雷', 'red'), E('布雷', 'red'), D, D]
},
{
num: 17, start: '10-23', end: '10-29',
monday: [E('布雷', 'red'), E('布雷', 'red'), D, D],
others: [E('布雷', 'red'), E('布雷', 'red'), D, D]
},
// 维修(红)
{
num: 18, start: '10-30', end: '11-05',
monday: [E('维修', 'red'), E('维修', 'red'), D, D],
others: [E('维修', 'red'), E('维修', 'red'), D, D]
},
// 维修(红)+ 核心(绿)
{
num: 19, start: '11-06', end: '11-12',
monday: [E('维修', 'red'), E('维修', 'red'), E('核心', 'green'), D],
others: [E('维修', 'red'), E('维修', 'red'), E('核心', 'green'), D]
},
// 布修(红)
{
num: 20, start: '11-13', end: '11-19',
monday: [E('布修', 'red'), E('布修', 'red'), D, D],
others: [E('布修', 'red'), E('布修', 'red'), D, D]
},
{
num: 21, start: '11-20', end: '11-26',
monday: [E('布修', 'red'), E('布修', 'red'), D, D],
others: [E('布修', 'red'), E('布修', 'red'), D, D]
},
{
num: 22, start: '11-27', end: '12-03',
monday: [E('布修', 'red'), E('布修', 'red'), D, D],
others: [E('布修', 'red'), E('布修', 'red'), D, D]
},
// 维修(红)
{
num: 23, start: '12-04', end: '12-10',
monday: [E('维修', 'red'), E('维修', 'red'), D, D],
others: [E('维修', 'red'), E('维修', 'red'), D, D]
},
{
num: 24, start: '12-11', end: '12-17',
monday: [E('维修', 'red'), E('维修', 'red'), D, D],
others: [E('维修', 'red'), E('维修', 'red'), D, D]
},
{
num: 25, start: '12-18', end: '12-24',
monday: [E('维修', 'red'), E('维修', 'red'), D, D],
others: [E('维修', 'red'), E('维修', 'red'), D, D]
},
// 战术(红)
{
num: 26, start: '12-25', end: '12-31',
monday: [E('战术', 'red'), E('战术', 'red'), E('战术', 'red'), E('战术', 'red')],
others: [E('战术', 'red'), E('战术', 'red'), E('战术', 'red'), E('战术', 'red')]
},
// 元旦(青,周一)+ 组训(绿,周二~周五)
{
num: 27, start: '01-01', end: '01-07',
monday: [E('元旦', 'cyan'), E('元旦', 'cyan'), E('元旦', 'cyan'), E('元旦', 'cyan')],
others: [E('组训', 'green'), E('组训', 'green'), E('组训', 'green'), E('组训', 'green')]
},
// 政工(红)+ 道德(墨绿)
{
num: 28, start: '01-08', end: '01-14',
monday: [E('政工', 'red'), E('道德', 'dark'), E('道德', 'dark'), E('0106', 'dark')],
others: [E('政工', 'red'), E('道德', 'dark'), E('道德', 'dark'), D]
},
{
num: 29, start: '01-15', end: '01-21',
monday: [E('政工', 'red'), E('道德', 'dark'), E('道德', 'dark'), E('0115', 'dark')],
others: [E('政工', 'red'), E('道德', 'dark'), E('道德', 'dark'), D]
}
]
/** 第8周周一(起始日期) */
const START_DATE = '2017-08-21'
export default {
name: 'ClassCalendarDialog',
components: { ClassTeamSelectDialog },
props: {
visible: { type: Boolean, default: false },
/** 班次号(如 0203) */
bc: { type: String, default: '0399' },
/** 学期名称 */
semesterName: { type: String, default: '2017年秋季学期' }
},
data() {
return {
// ==================== 学期与班次信息 ====================
/** 校历范围 */
calStart: '2017-07-08',
calEnd: '2018-02-11',
calWeeks: 32,
/** 班历范围(校历基础上为班次安排) */
classStart: '2017-08-24',
classEnd: '2018-02-05',
classWeeks: 25,
// ==================== 事件设置区域 ====================
eventName: '',
isBold: false, // 加粗显示
isSchedulable: false, // 可排课
isRegularClass: false, // 正课
showRemark: false, // 课表备注中显示
remark: '',
show78: false, // 显示7/8节
showEvening: true, // 显示晚上(已勾选)
showNight: false, // 显示夜间
// ==================== 班历表格数据 ====================
dayNames: ['一', '二', '三', '四', '五'],
weekDefs,
allCells: this.buildCells(),
cellMap: new Map(),
selectedIds: new Set(),
// ==================== 学员队选取弹窗 ====================
teamSelectVisible: false,
/** 当前应用模式:region=选定区域 / whole=整个班历 */
applyMode: 'region'
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
},
/** 时间编排区域实际显示的时段(由三个复选框控制) */
visiblePeriods() {
return ALL_PERIODS.filter((p) => {
if (p.key === '7-8') return this.show78
if (p.key === '9-10') return this.showEvening
if (p.key === 'night') return this.showNight
return true
})
}
},
watch: {
// 每次打开对话框时重置选择状态
visible(val) {
if (val) {
this.selectedIds = new Set()
}
}
},
created() {
// 初始化非响应式拖拽状态
this.dragActive = false
this.dragMoved = false
this.dragCtrl = false
this.anchorCell = null
this.anchorPos = { r: 0, c: 0 }
this.lastPos = { r: 0, c: 0 }
this.dragSnapshot = new Set()
// 构建 cellMap 索引
this.allCells.forEach((c) => this.cellMap.set(c.id, c))
},
mounted() {
document.addEventListener('mouseup', this.endSelect)
document.addEventListener('click', this.onDocClick)
},
beforeDestroy() {
document.removeEventListener('mouseup', this.endSelect)
document.removeEventListener('click', this.onDocClick)
},
methods: {
/** 计算从起始日期偏移 n 周 + m 天后的 MMDD(替代 dayjs) */
formatDateOffset(weeks, days) {
const date = new Date(START_DATE + 'T00:00:00')
date.setDate(date.getDate() + weeks * 7 + days)
const mm = String(date.getMonth() + 1).padStart(2, '0')
const dd = String(date.getDate()).padStart(2, '0')
return mm + dd
},
/** 根据周定义生成时间格数据(周一~周五 × 全部时段) */
buildCells() {
const cells = []
weekDefs.forEach((w, wi) => {
for (let d = 0; d < 5; d++) {
const dateText = this.formatDateOffset(wi, d)
const pattern = d === 0 ? w.monday : w.others
for (let pi = 0; pi < ALL_PERIODS.length; pi++) {
const period = ALL_PERIODS[pi]
const isSpecialPeriod = period.key === '7-8' || period.key === 'night'
const dj = PATTERN_PERIOD_IDX.indexOf(pi)
const def = isSpecialPeriod ? null : (pattern[dj] !== undefined ? pattern[dj] : null)
const isEvent = !!(def && def.text)
cells.push({
id: `${wi}-${d}-${period.key}`,
weekIdx: wi,
day: d,
periodKey: period.key,
dateText,
text: isEvent ? def.text : dateText,
isDate: !isEvent,
type: isEvent ? def.type : 'dark',
bold: false,
schedulable: true,
regular: false,
remark: false
})
}
}
})
return cells
},
getCell(weekIdx, day, periodKey) {
return this.cellMap.get(`${weekIdx}-${day}-${periodKey}`)
},
/** 单元格样式:事件格用事件色;日期格按星期交替背景色(一、三、五深绿 / 二、四浅绿) */
cellStyle(cell) {
if (cell.isDate) {
return cell.day % 2 === 0
? { backgroundColor: '#2a8a7f', color: '#ffffff' }
: { backgroundColor: '#d6e9e2', color: '#20604f' }
}
const st = TYPE_STYLE[cell.type]
return { backgroundColor: st.bg, color: st.fg }
},
/** 单元格类:选中态 / 选项语义 */
cellClass(cell) {
return {
'is-event': !cell.isDate,
'is-date': cell.isDate,
'is-bold': !cell.isDate && cell.bold,
selected: this.selectedIds.has(cell.id),
'not-schedulable': !cell.isDate && !cell.schedulable,
'is-regular': !cell.isDate && cell.regular
}
},
// ==================== 多选交互(拖动连片 + Ctrl 多区域) ====================
/** 计算格子在当前显示列布局下的行列坐标 */
getPos(cell) {
const periodIdx = this.visiblePeriods.findIndex((p) => p.key === cell.periodKey)
return { r: cell.weekIdx, c: cell.day * this.visiblePeriods.length + periodIdx }
},
/** 计算 anchor 与 last 之间矩形内的所有格子 id */
rectIds(a, b) {
const r1 = Math.min(a.r, b.r), r2 = Math.max(a.r, b.r)
const c1 = Math.min(a.c, b.c), c2 = Math.max(a.c, b.c)
const ids = []
this.allCells.forEach((cell) => {
const p = this.getPos(cell)
if (p.r >= r1 && p.r <= r2 && p.c >= c1 && p.c <= c2) ids.push(cell.id)
})
return ids
},
updateSelection() {
const ids = this.rectIds(this.anchorPos, this.lastPos)
this.selectedIds = this.dragCtrl
? new Set([...this.dragSnapshot, ...ids])
: new Set(ids)
},
onCellMouseDown(cell, e) {
if (e.button !== 0) return
this.dragActive = true
this.dragMoved = false
this.dragCtrl = e.ctrlKey || e.metaKey
this.dragSnapshot = new Set(this.selectedIds)
this.anchorCell = cell
this.anchorPos = this.getPos(cell)
this.lastPos = { ...this.anchorPos }
// 非 Ctrl 按下时立即清空旧选择(Ctrl 拖动则保留并追加)
if (!this.dragCtrl) this.selectedIds = new Set()
},
onCellMouseMove(cell) {
if (!this.dragActive) return
const pos = this.getPos(cell)
if (pos.r === this.lastPos.r && pos.c === this.lastPos.c) return
this.lastPos = pos
this.dragMoved = true
this.updateSelection()
},
endSelect() {
if (!this.dragActive) return
this.dragActive = false
if (!this.dragMoved && this.anchorCell) {
if (this.dragCtrl) {
// Ctrl + 单击:切换单个格子
const set = new Set(this.selectedIds)
if (set.has(this.anchorCell.id)) set.delete(this.anchorCell.id)
else set.add(this.anchorCell.id)
this.selectedIds = set
} else {
// 普通单击:单选该格
this.selectedIds = new Set([this.anchorCell.id])
}
}
},
/** 点击时间编排区域以外的空白区域时取消选择(按钮/输入框等交互元素不触发) */
onDocClick(e) {
const t = e.target
if (!t || !t.closest) return
if (t.closest('.calendar-table')) return
if (t.closest('button, input, .el-button, .el-checkbox, .el-select, .el-input, .el-date-editor')) return
this.selectedIds = new Set()
},
// ==================== 事件操作 ====================
/** 将事件格设置复制到工具栏(吸取) */
applyToToolbar(cell) {
this.eventName = cell.text
this.isBold = cell.bold
this.isSchedulable = cell.schedulable
this.isRegularClass = cell.regular
this.showRemark = cell.remark
},
/** 吸取:将选中事件格的名称和选项设置复制到工具栏 */
handleAbsorb() {
const cell = [...this.selectedIds].map((id) => this.cellMap.get(id)).find((c) => c && !c.isDate)
if (!cell) {
this.$message.warning('请先选择要吸取的事件格')
return
}
this.applyToToolbar(cell)
this.$message.success('已吸取事件设置到工具栏,可像刷子一样快速设定')
},
/** 双击事件格:直接吸取 */
handleCellDblClick(cell) {
if (cell.isDate) {
this.$message.info('该时间格没有事件,无法吸取')
return
}
this.applyToToolbar(cell)
this.$message.success(`已吸取「${cell.text}」的设置到工具栏`)
},
/** 设定:将事件名称和选项应用到所选时间格 */
handleSet() {
const ids = [...this.selectedIds]
if (!ids.length) {
this.$message.warning('请先在时间编排区域选择时间格')
return
}
const name = this.eventName.trim()
if (!name) {
this.$message.warning('请先输入事件名称')
return
}
ids.forEach((id) => {
const c = this.cellMap.get(id)
c.text = name
c.isDate = false
c.type = 'red'
c.bold = this.isBold
c.schedulable = this.isSchedulable
c.regular = this.isRegularClass
c.remark = this.showRemark
})
this.$message.success(`已为 ${ids.length} 个时间格设定事件「${name}」`)
},
/** 删除:清除所选时间格的事件,恢复日期显示、低暗背景 */
handleDelete() {
const ids = [...this.selectedIds]
if (!ids.length) {
this.$message.warning('请先选择要删除事件的时间格')
return
}
ids.forEach((id) => {
const c = this.cellMap.get(id)
c.isDate = true
c.text = c.dateText
c.bold = false
c.schedulable = true
c.regular = false
c.remark = false
})
this.$message.success(`已删除 ${ids.length} 个时间格的事件`)
},
/** 选定区域设置应用于其它班次:先选择时间格,再弹出学员队选取窗口 */
handleApplyOther() {
if (!this.selectedIds.size) {
this.$message.warning('请先在时间编排区域选择时间格')
return
}
this.applyMode = 'region'
this.teamSelectVisible = true
},
/** 整个班历应用于其它班次:弹出学员队选取窗口 */
handleApplyWhole() {
this.applyMode = 'whole'
this.teamSelectVisible = true
},
/** 学员队选取确认后应用 */
handleTeamConfirm() {
if (this.applyMode === 'whole') {
// 后端接口未提供,仅前端演示:将当前班历完整复制到所选班次
this.$message.success('整个班历已完整复制到所选班次(后端接口未提供,前端演示)')
} else {
this.$message.success('已应用选定区域设置到所选班次(前端演示)')
}
},
/** 刷新:恢复初始班历数据 */
handleRefresh() {
this.allCells.splice(0, this.allCells.length, ...this.buildCells())
this.cellMap.clear()
this.allCells.forEach((c) => this.cellMap.set(c.id, c))
this.selectedIds = new Set()
this.eventName = ''
this.$message.success('已刷新班历')
}
}
}
</script>
<style scoped lang="scss">
// `class` 会直接加在 .el-dialog 元素自身,因此高度直接写在这里(不用 :deep)
.class-calendar-dialog {
display: flex;
flex-direction: column;
height: 92vh;
margin: 4vh auto;
overflow: hidden;
::v-deep .el-dialog__header {
flex-shrink: 0;
margin-right: 0;
}
::v-deep .el-dialog__body {
display: flex;
flex-direction: column;
padding: 14px 18px 18px;
overflow: hidden;
flex: 1;
min-height: 0;
}
}
.class-calendar {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
// ========== 1. 学期与班次信息 ==========
.info-panel {
flex-shrink: 0;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 12px 20px;
margin-bottom: 12px;
.info-title-row {
display: flex;
align-items: baseline;
gap: 20px;
margin-bottom: 8px;
.semester-name,
.class-name {
font-size: 22px;
font-weight: 700;
color: #1e6fff;
}
}
.date-range-row {
display: flex;
flex-direction: column;
gap: 4px;
.range-item {
font-size: 13px;
font-weight: 600;
color: #1e6fff;
}
}
}
// ========== 2. 设置面板 ==========
.settings-panel {
flex-shrink: 0;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 12px 20px;
margin-bottom: 12px;
.event-settings-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 14px;
margin-bottom: 12px;
.setting-item {
display: flex;
align-items: center;
gap: 6px;
.setting-label {
font-size: 13px;
color: #606266;
white-space: nowrap;
}
}
}
.action-buttons-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
padding-top: 10px;
border-top: 1px solid #ebeef5;
}
}
// ========== 3. 银灰色 / 蓝色边框按钮 ==========
.gray-btn {
min-width: 80px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #000;
background: linear-gradient(180deg, #fdfdfd 0%, #ececec 50%, #dcdcdc 100%);
border: 1px solid #7a7a7a;
border-radius: 2px;
cursor: pointer;
&:hover {
background: linear-gradient(180deg, #ffffff 0%, #f4f4f4 50%, #e6e6e6 100%);
}
&:active {
background: #c8c8c8;
}
}
.blue-btn {
min-width: 190px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #1e6fff;
background: #ecf5ff;
border: 1px solid #1e6fff;
border-radius: 2px;
cursor: pointer;
&:hover {
background: #d9ecff;
}
&:active {
background: #c6e2ff;
}
}
// ========== 4. 时间编排区域 ==========
.table-area {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
.calendar-table-wrapper {
flex: 1;
min-height: 0;
overflow: auto;
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 4px;
&:hover { background: #c0c4cc; }
}
&::-webkit-scrollbar-track {
background: #f5f7fa;
}
}
.select-tip {
flex-shrink: 0;
padding: 6px 12px;
font-size: 12px;
color: #909399;
border-top: 1px solid #ebeef5;
background: #fafafa;
}
.calendar-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 12px;
user-select: none;
thead {
position: sticky;
top: 0;
z-index: 2;
th {
background: #f5f7fa;
border: 1px solid #ebeef5;
color: #303133;
font-weight: 600;
height: 28px;
padding: 0 4px;
text-align: center;
min-width: 52px;
white-space: nowrap;
}
}
tbody {
td {
&.week-num,
&.date-range {
background: #fafafa;
color: #303133;
font-weight: 600;
}
&.week-num {
width: 48px;
}
&.date-range {
width: 130px;
}
}
.cell {
position: relative;
height: 30px;
padding: 0 2px;
text-align: center;
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 1px solid #ebeef5;
cursor: default;
transition: filter 0.1s;
&:hover {
filter: brightness(0.92);
}
&.is-event {
cursor: pointer;
font-weight: 500;
}
// 选中高亮:蓝色描边
&.selected {
outline: 2px solid #1e6fff;
outline-offset: -2px;
z-index: 1;
}
// 加粗显示
&.is-bold {
font-weight: 700;
}
// 可排课未勾选 → 左上角灰色小方块(该时段全校不允许排课)
&.not-schedulable::before {
content: '';
position: absolute;
top: 1px;
left: 1px;
width: 7px;
height: 7px;
background: #c8c8c8;
border: 1px solid #909399;
}
// 正课 → 右上角蓝色小方块
&.is-regular::after {
content: '';
position: absolute;
top: 1px;
right: 1px;
width: 7px;
height: 7px;
background: #1e6fff;
border: 1px solid #0b4bc0;
}
}
}
}
}
}
</style>
@@ -0,0 +1,174 @@
<template>
<el-dialog
:visible="dialogVisible"
title="教学班次学期信息明细"
width="720px"
class="class-detail-dialog"
:close-on-click-modal="false"
@update:visible="val => dialogVisible = val"
>
<el-form label-width="100px" class="detail-form">
<el-row :gutter="24">
<!-- 左列 -->
<el-col :span="12">
<el-form-item label="班次">
<el-input v-model="form.bc" readonly />
</el-form-item>
<el-form-item label="专业">
<el-input v-model="form.xkzy" readonly />
</el-form-item>
<el-form-item label="年级">
<el-input v-model="form.nj" readonly />
</el-form-item>
<el-form-item label="学期">
<div class="semester-field">
<el-input v-model="form.xq" readonly />
<el-input v-model="form.ghn" class="plan-input" placeholder="" />
</div>
</el-form-item>
<el-form-item label="开学日期">
<el-date-picker v-model="form.kxrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 100%" />
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="form.jsrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 100%" />
</el-form-item>
<el-form-item label="学期周数">
<el-input v-model="form.zs" />
</el-form-item>
</el-col>
<!-- 右列 -->
<el-col :span="12">
<el-form-item label="教学任务">
<el-select v-model="form.jxrw" clearable placeholder="请选择" style="width: 100%">
<el-option v-for="opt in taskOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</el-form-item>
<el-form-item label="学期层次">
<el-select v-model="form.xqcc" style="width: 100%">
<el-option v-for="opt in levelOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</el-form-item>
<el-form-item label="开放教员排课">
<el-checkbox v-model="form.kfpy" />
</el-form-item>
<el-form-item label="查找教室">
<el-select v-model="form.zxjs" style="width: 100%">
<el-option v-for="opt in roomOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</el-form-item>
<el-form-item label="教学楼">
<el-select v-model="form.jxl" style="width: 100%">
<el-option v-for="opt in buildingOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</el-form-item>
<el-form-item label="专用教室">
<el-select v-model="form.zyjs" style="width: 100%">
<el-option v-for="opt in zyjsOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 备注 -->
<el-form-item label="备注">
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="" style="width: 100%" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'ClassSemesterDetailDialog',
props: {
visible: { type: Boolean, default: false },
/** 选中的班次信息 */
classInfo: { type: Object, default: null },
/** 学期名称 */
semesterName: { type: String, default: '2017年秋季学期' }
},
data() {
return {
form: {
bc: '班次0633', // 班次
xkzy: '专业209', // 专业
nj: '2017级', // 年级
xq: this.semesterName, // 学期
ghn: '规划内', // 规划内
kxrq: '2017-09-03', // 开学日期
jsrq: '2018-02-11', // 结束日期
zs: 24, // 学期周数
jxrw: '', // 教学任务
xqcc: '第1学期', // 学期层次
kfpy: true, // 开放教员排课
zxjs: '10-03&10-03(50)', // 查找教室
jxl: '5号楼', // 教学楼
zyjs: '作战实验中心学术报告厅', // 专用教室
bz: '' // 备注
},
taskOptions: ['2017年下半年教学任务', '研究生2017下半年教学任务'],
levelOptions: ['第1学期', '第2学期', '第3学期'],
roomOptions: ['10-03&10-03(50)', '10-04&10-04(40)', '9-01'],
buildingOptions: ['5号楼', '6号楼', '综合楼'],
zyjsOptions: ['作战实验中心学术报告厅', '综合教学楼101', '学术报告厅']
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
watch: {
// 每次打开时按传入的班次信息初始化表单
visible(val) {
if (!val) return
this.form.bc = (this.classInfo && this.classInfo.bc) || '班次0633'
this.form.xkzy = (this.classInfo && this.classInfo.xkzy) || '专业209'
this.form.nj = (this.classInfo && this.classInfo.nj) || '2017级'
this.form.xq = this.semesterName
}
},
methods: {
handleConfirm() {
this.$emit('confirm')
this.dialogVisible = false
}
}
}
</script>
<style scoped lang="scss">
.class-detail-dialog {
.detail-form {
// 只读输入框灰色底
::v-deep .el-input__inner[readonly] {
background-color: #f5f7fa;
}
.semester-field {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
.el-input:first-child {
flex: 1;
}
.plan-input {
flex-shrink: 0;
width: 120px;
}
}
}
}
</style>
@@ -0,0 +1,439 @@
<template>
<el-dialog
:visible="dialogVisible"
title="学员队选取"
width="90%"
top="5vh"
:close-on-click-modal="false"
class="team-select-dialog"
@update:visible="val => dialogVisible = val"
>
<div class="team-select">
<!-- ==================== 1. 查询条件区域 ==================== -->
<div class="query-panel">
<el-form label-width="88px" class="query-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="专业">
<div class="check-field">
<el-checkbox v-model="queryForm.zyEnabled" />
<el-select v-model="queryForm.zy" :disabled="!queryForm.zyEnabled" style="flex: 1">
<el-option v-for="opt in zyOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</div>
</el-form-item>
<el-form-item label="年级">
<div class="check-field">
<el-checkbox v-model="queryForm.njEnabled" />
<el-input v-model="queryForm.nj" :disabled="!queryForm.njEnabled" placeholder="" />
</div>
</el-form-item>
<el-form-item label="在校状态">
<div class="check-field">
<el-checkbox v-model="queryForm.zxztEnabled" />
<el-select v-model="queryForm.zxzt" :disabled="!queryForm.zxztEnabled" style="flex: 1">
<el-option v-for="opt in zxztOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学员队名称">
<div class="check-field">
<el-checkbox v-model="queryForm.xydmcEnabled" />
<el-input v-model="queryForm.xydmc" :disabled="!queryForm.xydmcEnabled" placeholder="" />
</div>
</el-form-item>
<el-form-item label="任务类别">
<div class="check-field">
<el-checkbox v-model="queryForm.rwlbEnabled" />
<el-select v-model="queryForm.rwlb" :disabled="!queryForm.rwlbEnabled" style="flex: 1">
<el-option v-for="opt in rwlbOptions" :key="opt" :label="opt" :value="opt" />
</el-select>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 分页控制区域 -->
<div class="pager-row">
<button type="button" class="gray-btn" @click="handleQuery">查询</button>
<button type="button" class="gray-btn" @click="handleFirst">首页</button>
<button type="button" class="gray-btn" @click="handlePrev">上页</button>
<button type="button" class="gray-btn" @click="handleNext">下页</button>
<button type="button" class="gray-btn" @click="handleLast">末页</button>
<el-input-number v-model="currentPage" :min="1" size="small" controls-position="right" style="width: 90px" />
<span class="pager-label">每页条数</span>
<el-select v-model="pageSize" size="small" style="width: 70px">
<el-option v-for="n in pageSizeOptions" :key="n" :label="String(n)" :value="n" />
</el-select>
<button type="button" class="gray-btn" @click="handleLocate">定位</button>
<el-select v-model="displaySize" size="small" style="width: 80px">
<el-option v-for="n in displaySizeOptions" :key="n" :label="String(n)" :value="n" />
</el-select>
<button type="button" class="blue-btn" @click="handleSetPageSize">设置页项数</button>
</div>
</div>
<!-- ==================== 2. 上方数据表格 ==================== -->
<div class="table-panel">
<el-table
:data="teamList"
border
stripe
highlight-current-row
size="small"
class="team-table"
@selection-change="handleTopSelectionChange"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="xzdbm" label="行政队别名称" width="100" align="center" show-overflow-tooltip />
<el-table-column prop="xydmc" label="学员队名称" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="nj" label="年级" width="70" align="center" show-overflow-tooltip />
<el-table-column prop="zy" label="专业" width="80" align="center" show-overflow-tooltip />
<el-table-column label="人数" width="60" align="center">
<template slot-scope="{ row }">{{ row.rs }}人</template>
</el-table-column>
<el-table-column prop="zxzt" label="在校状态" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="rwlb" label="任务类别" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="zyjs" label="专用教室" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="rxrq" label="入学日期" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="byrq" label="毕业日期" width="120" align="center" show-overflow-tooltip />
</el-table>
</div>
<!-- ==================== 3. 中间操作按钮区域 ==================== -->
<div class="mid-actions">
<span class="mid-label">已选择列表项</span>
<button type="button" class="gray-btn" @click="handleAdd">加入</button>
<button type="button" class="gray-btn" @click="handleRemove">移除</button>
<button type="button" class="gray-btn" @click="handleClear">清除</button>
<button type="button" class="gray-btn" @click="handleReset">重置</button>
</div>
<!-- ==================== 4. 下方数据表格(已选学员队列表) ==================== -->
<div class="table-panel">
<el-table
:data="selectedList"
border
stripe
size="small"
class="team-table"
@selection-change="handleBottomSelectionChange"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="zy" label="专业" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="xydmc" label="学员队名称" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="nj" label="年级" width="70" align="center" show-overflow-tooltip />
<el-table-column label="人数" width="60" align="center">
<template slot-scope="{ row }">{{ row.rs }}人</template>
</el-table-column>
<el-table-column prop="rwlb" label="任务类别" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="zxzt" label="在校状态" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="zyjs" label="专用教室" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="rxrq" label="入学日期" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="byrq" label="毕业日期" width="120" align="center" show-overflow-tooltip />
<el-table-column prop="bz" label="备注" width="80" align="center" show-overflow-tooltip />
</el-table>
<el-empty v-if="!selectedList.length" description="暂无已选学员队,请从上方表格加入" :image-size="60" />
</div>
<!-- ==================== 5. 底部按钮区域 ==================== -->
<div class="footer-actions">
<button type="button" class="gray-btn" @click="handleConfirm">确定</button>
<button type="button" class="gray-btn" @click="handleCancel">取消</button>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'ClassTeamSelectDialog',
props: {
visible: { type: Boolean, default: false }
},
data() {
return {
// ==================== 查询条件区域 ====================
queryForm: {
zyEnabled: false, // 专业启用复选框(未勾选)
zy: '专业219', // 专业(默认值)
njEnabled: true, // 年级启用复选框(已勾选)
nj: '', // 年级(空值)
zxztEnabled: false, // 在校状态启用复选框(未勾选)
zxzt: '毕业', // 在校状态(默认值)
xydmcEnabled: true, // 学员队名称启用复选框(已勾选)
xydmc: '', // 学员队名称(空值)
rwlbEnabled: false, // 任务类别启用复选框(未勾选)
rwlb: '规划内' // 任务类别(默认值)
},
zyOptions: ['专业219', '专业220', '专业221'],
zxztOptions: ['毕业', '在校'],
rwlbOptions: ['规划内', '规划外'],
// ==================== 分页控制区域 ====================
currentPage: 1,
pageSize: 1,
displaySize: 100,
pageSizeOptions: [1, 5, 10, 20],
displaySizeOptions: [50, 100, 200],
// ==================== 上方数据表格(可选学员队列表) ====================
teamList: [
{ xzdbm: '教学五系', xydmc: '班次0417', nj: '2016级', zy: '专业113', rs: 36, zxzt: '毕业', rwlb: '规划内', zyjs: '8-011', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0423', nj: '2016级', zy: '专业114', rs: 9, zxzt: '毕业', rwlb: '规划内', zyjs: '8-212(南楼)', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0411', nj: '2016级', zy: '专业112', rs: 38, zxzt: '毕业', rwlb: '规划内', zyjs: '6-203', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0405', nj: '2016级', zy: '专业111', rs: 34, zxzt: '毕业', rwlb: '规划内', zyjs: '8-007', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
{ xzdbm: '教学五系', xydmc: '班次0429', nj: '2017级', zy: '专业117', rs: 43, zxzt: '毕业', rwlb: '规划内', zyjs: '6-202', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学五系', xydmc: '班次0431', nj: '2017级', zy: '专业118', rs: 22, zxzt: '毕业', rwlb: '规划内', zyjs: '2-403', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0425', nj: '2017级', zy: '专业115', rs: 19, zxzt: '毕业', rwlb: '规划内', zyjs: '2-406', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0432', nj: '2017级', zy: '专业119', rs: 15, zxzt: '毕业', rwlb: '规划内', zyjs: '8-009(南楼)', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学七系', xydmc: '班次0434', nj: '2017级', zy: '专业120', rs: 45, zxzt: '毕业', rwlb: '规划内', zyjs: '6-303', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
{ xzdbm: '教学六系', xydmc: '班次0427', nj: '2017级', zy: '专业116', rs: 64, zxzt: '毕业', rwlb: '规划内', zyjs: '2-501', rxrq: '2017年8月24日', byrq: '2019年1月21日' }
],
/** 上方表格多选 */
topSelection: [],
/** 下方数据表格(已选学员队列表) */
selectedList: [],
bottomSelection: []
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
methods: {
handleTopSelectionChange(rows) {
this.topSelection = rows
},
handleBottomSelectionChange(rows) {
this.bottomSelection = rows
},
// ==================== 分页控制 ====================
handleQuery() {
this.$message.success('查询(前端演示)')
},
handleFirst() {
this.currentPage = 1
this.$message.success('已跳转首页')
},
handlePrev() {
if (this.currentPage > 1) this.currentPage--
},
handleNext() {
this.currentPage++
},
handleLast() {
this.currentPage = this.teamList.length || 1
},
handleLocate() {
this.$message.success(`已定位到第 ${this.currentPage} 页`)
},
handleSetPageSize() {
this.$message.success(`每页显示条数已设置为 ${this.displaySize}`)
},
// ==================== 中间操作按钮 ====================
/** 加入:将上方表格选中行加入已选列表(去重) */
handleAdd() {
if (!this.topSelection.length) {
this.$message.warning('请先在上方表格选择学员队')
return
}
const existing = new Set(this.selectedList.map((r) => r.xydmc))
let added = 0
this.topSelection.forEach((r) => {
if (!existing.has(r.xydmc)) {
this.selectedList.push({ ...r, bz: '' })
added++
}
})
if (added) this.$message.success(`已加入 ${added} 个学员队`)
else this.$message.info('所选学员队已在列表中')
},
/** 移除:将下方表格选中行移除 */
handleRemove() {
if (!this.bottomSelection.length) {
this.$message.warning('请先在下方表格选择要移除的学员队')
return
}
const removeKeys = new Set(this.bottomSelection.map((r) => r.xydmc))
this.selectedList = this.selectedList.filter((r) => !removeKeys.has(r.xydmc))
this.$message.success(`已移除 ${this.bottomSelection.length} 个学员队`)
},
/** 清除:清空已选列表 */
handleClear() {
if (!this.selectedList.length) {
this.$message.info('已选列表为空')
return
}
this.selectedList = []
this.$message.success('已清空已选列表')
},
/** 重置:清空已选列表并重置查询条件 */
handleReset() {
this.selectedList = []
Object.assign(this.queryForm, {
zyEnabled: false, zy: '专业219',
njEnabled: true, nj: '',
zxztEnabled: false, zxzt: '毕业',
xydmcEnabled: true, xydmc: '',
rwlbEnabled: false, rwlb: '规划内'
})
this.currentPage = 1
this.$message.success('已重置')
},
// ==================== 底部按钮 ====================
handleConfirm() {
if (!this.selectedList.length) {
this.$message.warning('请先选择要应用的学员队')
return
}
this.$emit('confirm', this.selectedList.slice())
this.dialogVisible = false
this.$message.success(`选定区域设置已应用于 ${this.selectedList.length} 个班次`)
},
handleCancel() {
this.dialogVisible = false
}
}
}
</script>
<style scoped lang="scss">
.team-select {
display: flex;
flex-direction: column;
gap: 12px;
// ========== 1. 查询条件区域 ==========
.query-panel {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 12px 16px 8px;
.query-form {
.check-field {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
}
.pager-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
padding-top: 10px;
border-top: 1px solid #ebeef5;
margin-top: 4px;
.pager-label {
font-size: 13px;
color: #606266;
white-space: nowrap;
}
}
}
// ========== 2/4. 表格区域 ==========
.table-panel {
position: relative;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px;
overflow: hidden;
.team-table {
width: 100%;
}
::v-deep .el-empty {
padding: 12px 0;
}
}
// ========== 3. 中间操作按钮区域 ==========
.mid-actions {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
.mid-label {
font-size: 13px;
font-weight: 600;
color: #303133;
white-space: nowrap;
margin-right: 4px;
}
}
// ========== 5. 底部按钮区域 ==========
.footer-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
// ========== 银灰色 / 蓝色边框按钮 ==========
.gray-btn {
min-width: 70px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #000;
background: linear-gradient(180deg, #fdfdfd 0%, #ececec 50%, #dcdcdc 100%);
border: 1px solid #7a7a7a;
border-radius: 2px;
cursor: pointer;
&:hover {
background: linear-gradient(180deg, #ffffff 0%, #f4f4f4 50%, #e6e6e6 100%);
}
&:active {
background: #c8c8c8;
}
}
.blue-btn {
min-width: 110px;
height: 28px;
padding: 0 14px;
font-size: 12px;
color: #1e6fff;
background: #ecf5ff;
border: 1px solid #1e6fff;
border-radius: 2px;
cursor: pointer;
&:hover {
background: #d9ecff;
}
&:active {
background: #c6e2ff;
}
}
}
</style>
@@ -0,0 +1,706 @@
<template>
<el-dialog
:visible="dialogVisible"
:show-close="false"
width="96%"
top="3vh"
:close-on-click-modal="false"
:close-on-press-escape="false"
class="peidang-dialog"
@update:visible="val => dialogVisible = val"
>
<!-- ==================== 自定义标题栏 ==================== -->
<div slot="header">
<div class="peidang-titlebar">
<i class="el-icon-reading title-icon"></i>
<span class="title-text">
班次学期配档——{{ semesterName }}——{{ classLabel }}
</span>
</div>
</div>
<div class="peidang-body">
<!-- ==================== 上半部分:左右分栏 ==================== -->
<div class="upper">
<!-- 左侧 75%:配档课程编组管理表格 -->
<div class="table-area">
<el-table
ref="tableRef"
:data="displayCourses"
border
stripe
highlight-current-row
size="small"
max-height="240"
class="peidang-table"
@current-change="handleCurrentChange"
>
<el-table-column prop="name" label="科目名称" width="100" align="left" show-overflow-tooltip />
<el-table-column prop="short" label="简称" width="60" align="center" show-overflow-tooltip />
<el-table-column prop="planStart" label="计划起始周" width="60" align="center" show-overflow-tooltip />
<el-table-column prop="actualStart" label="实际起始周" width="60" align="center" show-overflow-tooltip />
<el-table-column prop="endWeek" label="结束周" width="60" align="center" show-overflow-tooltip />
<el-table-column label="连排课" width="50" align="center">
<template slot-scope="{ row }">
<el-checkbox :value="row.continuous" />
</template>
</el-table-column>
<el-table-column prop="priority" label="优先序数" width="50" align="center" show-overflow-tooltip />
<el-table-column prop="weeklyHours" label="周学时" width="50" align="center" show-overflow-tooltip />
<el-table-column prop="hours" label="学时" width="50" align="center" show-overflow-tooltip />
<el-table-column label="正课时间" width="60" align="center">
<template slot-scope="{ row }">
<el-checkbox :value="row.scheduleTime" />
</template>
</el-table-column>
<el-table-column label="已排学时" width="50" align="center">
<template slot-scope="{ row }">{{ courseStats(row).scheduled }}</template>
</el-table-column>
<el-table-column label="未排学时" width="50" align="center">
<template slot-scope="{ row }">{{ courseStats(row).remaining }}</template>
</el-table-column>
<el-table-column label="总班次任务数" width="70" align="center">
<template slot-scope="{ row }">{{ row.continuous ? 1 : 1 }}</template>
</el-table-column>
<el-table-column label="配档编组任务数" width="80" align="center">
<template slot-scope="{ row }">{{ row.continuous ? 2 : 1 }}</template>
</el-table-column>
</el-table>
</div>
<!-- 右侧 25%:配档课程属性设置区 + 优先序数上下移动 -->
<div class="prop-area">
<div class="prop-title">配档课程属性</div>
<el-form label-width="72px" size="small" class="prop-form">
<el-form-item label="周学时">
<el-input-number v-model="propForm.weeklyHours" :min="1" :max="40" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="起始周次">
<el-input-number v-model="propForm.startWeek" :min="startWeek" :max="endWeek" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="结束周次">
<el-input-number v-model="propForm.endWeek" :min="startWeek" :max="endWeek" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="连排课">
<el-checkbox v-model="propForm.continuous">单科独进</el-checkbox>
</el-form-item>
<el-form-item label="优先序数">
<div class="priority-field">
<el-input-number v-model="propForm.priority" :min="1" controls-position="right" style="width: 100%" />
<el-button type="primary" plain icon="el-icon-arrow-up" @click="handlePriorityUp" />
<el-button type="primary" plain icon="el-icon-arrow-down" @click="handlePriorityDown" />
</div>
</el-form-item>
<el-form-item label="配档编组">
<el-input v-model="propForm.group" />
</el-form-item>
</el-form>
<el-button type="primary" class="apply-btn" @click="handleApplyProp">应用属性</el-button>
</div>
</div>
<!-- ==================== 下半部分:配档显示区域(甘特图) ==================== -->
<div class="gantt-area">
<div class="gantt-head">
<div class="gantt-label-col">
<div class="gantt-label">课程 / 周次</div>
</div>
<div class="gantt-body">
<!-- 行1:周次刻度(红色竖线为周刻度) -->
<div class="gantt-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell week-cell"
:class="{ 'month-boundary': isMonthBoundary(w) }"
>{{ w }}</div>
</div>
<!-- 行2:本周可安排正课学时 -->
<div class="gantt-row info-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell"
:class="{ 'month-boundary': isMonthBoundary(w) }"
>可排{{ weeklyCapacity }}</div>
</div>
<!-- 行3:已安排学时 / 剩余学时 -->
<div class="gantt-row info-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell"
:class="{ 'month-boundary': isMonthBoundary(w) }"
>
<span class="sch">已{{ monthlyScheduled(w) }}</span>
<span class="rem">余{{ Math.max(0, weeklyCapacity - monthlyScheduled(w)) }}</span>
</div>
</div>
</div>
</div>
<!-- 课程甘特条 -->
<div class="gantt-courses">
<div v-for="course in displayCourses" :key="course.id" class="gantt-course">
<div class="gantt-label-col">
<div class="course-tag" :class="course.continuous ? 'tag-continuous' : 'tag-normal'">
<span class="course-name">{{ course.name }}({{ course.short }})</span>
<span class="course-hours">
已排{{ courseStats(course).scheduled }}
<template v-if="courseStats(course).remaining > 0">+{{ courseStats(course).remaining }}</template>
</span>
</div>
</div>
<div class="gantt-body">
<div class="gantt-row bar-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell bar-cell"
:class="{
'month-boundary': isMonthBoundary(w),
'bar-active': w >= course.actualStart && w <= course.endWeek,
'bar-continuous': course.continuous,
'bar-normal': !course.continuous
}"
>
<span v-if="w >= course.actualStart && w <= course.endWeek" class="bar-hours">
{{ courseWeekHours(course)[w] || '' }}
</span>
</div>
</div>
</div>
</div>
</div>
<!-- 底部:本月可安排正课总学时(月刻度) -->
<div class="gantt-foot">
<div class="gantt-label-col">
<div class="gantt-label">本月正课总学时</div>
</div>
<div class="gantt-body">
<div class="gantt-row">
<div
v-for="w in weeks"
:key="w"
class="gantt-cell month-cell"
:class="{ 'month-boundary': isMonthBoundary(w) }"
>
<span v-if="bottomCells[w] && isMonthEndWeek(w)" class="month-total">
第{{ bottomCells[w].no }}月:可排{{ bottomCells[w].total }}(已排{{ bottomCells[w].scheduled }} + 余{{ bottomCells[w].remaining }})
</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== 底部操作 ==================== -->
<div slot="footer">
<div class="peidang-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'PeidangEditDialog',
props: {
visible: { type: Boolean, default: false },
/** 学年学期名称 */
semesterName: { type: String, default: '2017-2018学年第一学期' },
/** 班次标签(如 教学五系2016级专业0113班次0417) */
classLabel: { type: String, default: '教学五系2016级专业0113班次0417' }
},
data() {
return {
// ==================== 时间轴参数 ====================
/** 该班次开学起始周次 */
startWeek: 8,
/** 该班次学期结束周次 */
endWeek: 32,
/** 该班次每周可安排的正课学时时数(去除不能排课学时) */
weeklyCapacity: 40,
/** 月刻度:每 4 周为一月(8-11 第1月,12-15 第2月...),虚线分隔 */
monthBreaks: [12, 16, 20, 24, 28, 32],
// ==================== 配档课程数据 ====================
courses: [
// ---- 非连排课(按周学时安排,蓝色,每门独立一组) ----
{ id: 'p1', name: '课程科目001', short: '政治', planStart: 8, actualStart: 8, endWeek: 23, continuous: false, priority: 1, weeklyHours: 4, hours: 60, scheduleTime: true, group: '组1' },
{ id: 'p2', name: '课程科目002', short: '军事', planStart: 8, actualStart: 8, endWeek: 20, continuous: false, priority: 2, weeklyHours: 4, hours: 48, scheduleTime: true, group: '组2' },
{ id: 'p3', name: '课程科目003', short: '战术', planStart: 8, actualStart: 10, endWeek: 21, continuous: false, priority: 3, weeklyHours: 4, hours: 44, scheduleTime: true, group: '组3' },
{ id: 'p4', name: '课程科目004', short: '指挥', planStart: 8, actualStart: 12, endWeek: 22, continuous: false, priority: 4, weeklyHours: 4, hours: 40, scheduleTime: true, group: '组4' },
// ---- 连排课(单科独进,红色,共为一组) ----
{ id: 'p5', name: '课程科目005', short: '综合演练', planStart: 8, actualStart: 8, endWeek: 13, continuous: true, priority: 5, weeklyHours: 20, hours: 100, scheduleTime: true, group: '连排组' },
{ id: 'p6', name: '课程科目006', short: '野营拉练', planStart: 14, actualStart: 14, endWeek: 16, continuous: true, priority: 6, weeklyHours: 24, hours: 48, scheduleTime: true, group: '连排组' }
],
// ==================== 表格选中与属性设置 ====================
currentCourse: null,
propForm: {
weeklyHours: 4,
startWeek: 8,
endWeek: 16,
continuous: false,
priority: 1,
group: ''
}
}
},
computed: {
dialogVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
},
/** 周序列:8 ~ 32 */
weeks() {
const arr = []
for (let w = this.startWeek; w <= this.endWeek; w++) arr.push(w)
return arr
},
/** 月份信息:每月包含的周次与可安排正课学时 */
months() {
const list = []
let mNo = 1
let cur = this.startWeek
while (cur <= this.endWeek) {
const next = this.monthBreaks.find((b) => b > cur)
const mEnd = Math.min((next === undefined ? this.endWeek + 1 : next) - 1, this.endWeek)
list.push({ no: mNo, start: cur, end: mEnd, total: (mEnd - cur + 1) * this.weeklyCapacity })
cur = mEnd + 1
mNo++
}
return list
},
/** 底部每月汇总行数据:week -> 该月信息(仅月首格展示文字) */
bottomCells() {
const map = {}
this.weeks.forEach((w) => {
const m = this.months.find((item) => w >= item.start && w <= item.end)
if (!m) return
map[w] = {
no: m.no,
total: m.total,
scheduled: this.monthlyScheduled(w),
remaining: m.total - this.monthlyScheduled(w)
}
})
return map
},
/** 表格展示顺序:非连排课在前,连排课在后;组内按实际起始周、优先序数排序 */
displayCourses() {
return [...this.courses].sort((a, b) => {
if (a.continuous !== b.continuous) return a.continuous ? 1 : -1
if (a.actualStart !== b.actualStart) return a.actualStart - b.actualStart
return a.priority - b.priority
})
}
},
watch: {
visible(val) {
if (!val) return
this.$nextTick(() => {
if (this.$refs.tableRef) {
this.$refs.tableRef.setCurrentRow(this.displayCourses[0])
}
this.currentCourse = this.displayCourses[0] || null
this.syncPropForm()
})
}
},
methods: {
/** 是否为月边界列(左侧画虚线) */
isMonthBoundary(week) {
return this.monthBreaks.includes(week)
},
/** 是否为某月的结束周(仅月结束列展示文字) */
isMonthEndWeek(week) {
return this.months.some((m) => m.end === week)
},
/** 某周所有课程已安排学时之和 */
monthlyScheduled(week) {
return this.courses.reduce((sum, c) => {
const map = this.courseWeekHours(c)
return sum + (map[week] !== undefined ? map[week] : 0)
}, 0)
},
/** 每门课程按周分布的安排学时 */
courseWeekHours(course) {
const map = {}
let remaining = course.hours
let w = course.actualStart
while (remaining > 0 && w <= course.endWeek) {
const h = Math.min(course.weeklyHours, remaining)
map[w] = h
remaining -= h
w++
}
return map
},
/** 课程计算属性:已排学时 / 未排学时 */
courseStats(course) {
const map = this.courseWeekHours(course)
const scheduled = Object.keys(map).reduce((sum, k) => sum + map[k], 0)
return { scheduled, remaining: Math.max(0, course.hours - scheduled) }
},
handleCurrentChange(row) {
this.currentCourse = row
this.syncPropForm()
},
/** 属性设置表单同步(右侧) */
syncPropForm() {
if (!this.currentCourse) return
this.propForm.weeklyHours = this.currentCourse.weeklyHours
this.propForm.startWeek = this.currentCourse.actualStart
this.propForm.endWeek = this.currentCourse.endWeek
this.propForm.continuous = this.currentCourse.continuous
this.propForm.priority = this.currentCourse.priority
this.propForm.group = this.currentCourse.group
},
/** 属性设置保存:应用所选课程的配档属性 */
handleApplyProp() {
if (!this.currentCourse) {
this.$message.warning('请先在表格中选择一门课程')
return
}
const row = this.currentCourse
row.weeklyHours = this.propForm.weeklyHours
row.actualStart = this.propForm.startWeek
row.endWeek = this.propForm.endWeek
row.continuous = this.propForm.continuous
row.priority = this.propForm.priority
row.group = this.propForm.group
this.$message.success('配档属性已应用(前端演示)')
},
/** 优先序数上移:优先序数减 1 */
handlePriorityUp() {
if (!this.currentCourse) {
this.$message.warning('请先在表格中选择一门课程')
return
}
if (this.currentCourse.priority <= 1) {
this.$message.info('已是最高优先序数')
return
}
this.currentCourse.priority -= 1
this.syncPropForm()
},
/** 优先序数下移:优先序数加 1 */
handlePriorityDown() {
if (!this.currentCourse) {
this.$message.warning('请先在表格中选择一门课程')
return
}
this.currentCourse.priority += 1
this.syncPropForm()
},
// ==================== 底部操作 ====================
handleConfirm() {
this.$emit('confirm')
this.dialogVisible = false
}
}
}
</script>
<style scoped lang="scss">
.peidang-dialog {
// ========== 自定义标题栏 ==========
::v-deep .el-dialog__header {
margin: 0;
padding: 0;
}
.peidang-titlebar {
display: flex;
align-items: center;
gap: 8px;
height: 40px;
padding: 0 14px;
background: linear-gradient(90deg, #0a246a 0%, #a6caf0 100%);
color: #fff;
.title-icon {
font-size: 18px;
color: #fff;
}
.title-text {
font-size: 14px;
font-weight: 600;
color: #fff;
letter-spacing: 0.5px;
}
}
::v-deep .el-dialog__body {
padding: 12px;
}
::v-deep .el-dialog__footer {
padding: 8px 12px;
border-top: 1px solid #ebeef5;
}
.peidang-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
}
}
.peidang-body {
display: flex;
flex-direction: column;
gap: 10px;
// ========== 上半部分:左右分栏 ==========
.upper {
display: flex;
gap: 10px;
// 左侧 75%:表格
.table-area {
flex: 3;
min-width: 0;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 6px;
overflow: hidden;
.peidang-table {
width: 100%;
}
}
// 右侧 25%:属性设置
.prop-area {
flex: 1;
min-width: 240px;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px 12px;
.prop-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid #ebeef5;
}
.prop-form {
.priority-field {
display: flex;
align-items: center;
gap: 4px;
width: 100%;
.el-button {
flex-shrink: 0;
margin-left: 0;
}
}
}
.apply-btn {
width: 100%;
margin-top: 4px;
}
}
}
// ========== 下半部分:配档显示区域 ==========
.gantt-area {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 8px;
overflow: auto;
max-height: 320px;
.gantt-head,
.gantt-course,
.gantt-foot {
display: flex;
align-items: stretch;
min-width: 860px;
}
.gantt-label-col {
flex-shrink: 0;
width: 210px;
display: flex;
align-items: center;
.gantt-label {
font-size: 12px;
color: #606266;
font-weight: 600;
white-space: nowrap;
}
}
.gantt-body {
flex: 1;
display: flex;
flex-direction: column;
}
.gantt-row {
display: flex;
.gantt-cell {
flex: 1;
min-width: 34px;
height: 24px;
line-height: 24px;
text-align: center;
font-size: 11px;
color: #303133;
border-right: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
// 周刻度:顶部红色竖线
.week-cell {
font-weight: 600;
color: #f56c6c;
border-top: 2px solid #f56c6c;
background: #fff5f5;
}
.info-row .gantt-cell {
height: 20px;
line-height: 20px;
.sch {
color: #1e6fff;
}
.rem {
color: #e6a23c;
margin-left: 2px;
}
}
// 月刻度虚线
.month-boundary {
border-left: 1px dashed #909399;
}
// 课程条
.bar-cell {
height: 26px;
position: relative;
.bar-hours {
font-size: 11px;
font-weight: 600;
}
&.bar-active {
&.bar-normal {
background: #409eff;
color: #fff;
.bar-hours {
color: #fff;
}
}
&.bar-continuous {
background: #f56c6c;
color: #fff;
.bar-hours {
color: #fff;
}
}
}
}
.month-cell {
height: 22px;
line-height: 22px;
.month-total {
font-size: 11px;
color: #606266;
font-weight: 600;
background: #f4f4f5;
border: 1px solid #dcdfe6;
border-radius: 2px;
padding: 0 4px;
white-space: nowrap;
}
}
}
.gantt-course {
.gantt-label-col {
align-items: center;
.course-tag {
display: flex;
flex-direction: column;
justify-content: center;
gap: 1px;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
&.tag-normal {
background: #ecf5ff;
border: 1px solid #a0cfff;
color: #337ecc;
}
&.tag-continuous {
background: #fef0f0;
border: 1px solid #fbc4c4;
color: #d63031;
}
.course-name {
font-weight: 600;
white-space: nowrap;
}
.course-hours {
font-size: 10px;
white-space: nowrap;
}
}
}
}
.gantt-foot {
.gantt-label-col {
align-items: center;
}
}
}
}
</style>
@@ -0,0 +1,486 @@
<template>
<div class="app-container shift-semester-page">
<!-- ==================== 1. 学年学期标题 ==================== -->
<div class="list-title">{{ semesterName }}</div>
<!-- ==================== 2. 校历 / 预设日期信息区 ==================== -->
<el-card shadow="never" class="search-card">
<el-form class="search-form">
<div class="date-group">
<span class="date-group-label">校历</span>
<el-form-item label="开学日期">
<el-date-picker v-model="semester.kxrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="semester.jsrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
</el-form-item>
<el-form-item label="学期周数">
<el-input-number v-model="semester.weeks" :min="1" controls-position="right" style="width: 90px" />
</el-form-item>
</div>
<div class="date-group">
<span class="date-group-label">预设</span>
<el-form-item label="开学日期">
<el-date-picker v-model="preset.kxrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker v-model="preset.jsrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
</el-form-item>
<el-form-item label="学期周数">
<el-input-number v-model="preset.weeks" :min="1" controls-position="right" style="width: 90px" />
</el-form-item>
<el-button class="absorb-btn" type="primary" plain @click="handleAbsorbDate">吸取日期</el-button>
</div>
</el-form>
</el-card>
<!-- ==================== 3. 工具栏按钮区 ==================== -->
<div class="list-toolbar">
<div class="left-group">
<!-- 新添:下拉列出可新建学期信息的班次 -->
<el-dropdown trigger="click" @command="handleNewCommand">
<el-button type="primary">
<i class="el-icon-plus"></i>
<span>新添</span>
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown" class="new-class-menu">
<el-dropdown-item v-for="item in newableClasses" :key="item.bc" :command="item">
{{ item.bc }}({{ item.xkzy }}、{{ item.nj }})
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button type="primary" plain @click="handleAction('编辑')">编辑</el-button>
<el-button type="primary" plain @click="handleQuickSetDates">快速设定学期日期</el-button>
<el-button type="danger" @click="handleAction('删除')">删除</el-button>
<el-button type="primary" plain @click="handleEditCalendar">编辑班历</el-button>
<el-button type="primary" plain @click="handleAction('自动生成教学计划')">自动生成教学计划</el-button>
<el-button type="primary" plain @click="handleAction('教学计划管理')">教学计划管理</el-button>
<!-- 序号排序:带下拉箭头,点击弹出排序方式 -->
<el-dropdown trigger="click" @command="handleSortCommand">
<el-button type="primary" plain>
<span>{{ sortLabel }}</span>
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="xh">序号排序</el-dropdown-item>
<el-dropdown-item command="bw">波次排序</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button type="primary" plain @click="handleMerge">预设合班</el-button>
<el-button type="primary" plain @click="handleAction('预设拆班')">预设拆班</el-button>
<el-button type="primary" plain @click="handlePeidangEdit">配档编辑</el-button>
</div>
</div>
<!-- ==================== 4. 班次学期信息列表 ==================== -->
<el-card shadow="never" class="table-card">
<el-table
ref="tableRef"
:data="displayData"
border
stripe
highlight-current-row
:span-method="spanMethod"
:row-class-name="rowClassName"
:header-cell-class-name="headerCellClassName"
max-height="480"
class="class-table"
@current-change="handleCurrentChange"
>
<el-table-column prop="xzdw" label="行政队别" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="xh" label="班次序号" width="70" align="center" />
<el-table-column prop="bc" label="班级" width="70" align="center" show-overflow-tooltip />
<el-table-column prop="rs" label="人数" width="55" align="center" show-overflow-tooltip />
<el-table-column prop="nj" label="年级" width="85" align="center" show-overflow-tooltip />
<el-table-column prop="xkzy" label="学科专业" width="85" align="center" show-overflow-tooltip />
<el-table-column prop="kxrq" label="开学日期" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="jsrq" label="结束日期" width="90" align="center" show-overflow-tooltip />
<el-table-column prop="xqcc" label="学期第次" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="zyjs" label="专用教室" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="jxrw" label="教学任务" min-width="160" align="left" show-overflow-tooltip />
<el-table-column prop="zs" label="周数" width="55" align="center" show-overflow-tooltip />
<el-table-column prop="kcrws" label="总课程数" width="80" align="center" show-overflow-tooltip />
<el-table-column prop="kczs" label="总课时数" width="80" align="center" show-overflow-tooltip />
<el-table-column label="周学时" width="80" align="center">
<template slot-scope="{ row }">{{ row.zxs.toFixed(2) }}</template>
</el-table-column>
<el-table-column prop="zxf" label="总学分" width="70" align="center" show-overflow-tooltip />
</el-table>
</el-card>
<!-- ==================== 教学班次学期信息明细弹窗(新添) ==================== -->
<ClassSemesterDetailDialog
:visible="detailVisible"
:class-info="selectedNewClass"
:semester-name="semesterName"
@update:visible="val => detailVisible = val"
@confirm="handleDetailConfirm"
/>
<!-- ==================== 班历设置弹窗(编辑班历) ==================== -->
<ClassCalendarDialog
:visible="calendarVisible"
:bc="currentRow ? currentRow.bc : ''"
:semester-name="semesterName"
@update:visible="val => calendarVisible = val"
/>
<!-- ==================== 预设合班弹窗 ==================== -->
<ClassTeamSelectDialog
:visible="teamVisible"
@update:visible="val => teamVisible = val"
@confirm="handleTeamConfirm"
/>
<!-- ==================== 班次学期配档编辑弹窗 ==================== -->
<PeidangEditDialog
:visible="peidangVisible"
:semester-name="semesterName"
:class-label="peidangClassLabel"
@update:visible="val => peidangVisible = val"
@confirm="handleAction('配档编辑')"
/>
</div>
</template>
<script>
import ClassSemesterDetailDialog from './ClassSemesterDetailDialog.vue'
import ClassCalendarDialog from './ClassCalendarDialog.vue'
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
import PeidangEditDialog from './PeidangEditDialog.vue'
export default {
name: 'ShiftSemester',
components: {
ClassSemesterDetailDialog,
ClassCalendarDialog,
ClassTeamSelectDialog,
PeidangEditDialog
},
data() {
return {
// ==================== 1. 学年学期标题区 ====================
semesterName: '2017-2018学年第一学期',
// ==================== 2. 校历 / 预设日期信息区 ====================
/** 校历日期 */
semester: {
kxrq: '2017-07-08', // 校历开学日期
jsrq: '2018-02-11', // 校历结束日期
weeks: 32 // 学期周数
},
/** 预设(班次)日期 */
preset: {
kxrq: '2017-07-08', // 预设开学日期
jsrq: '2018-02-11', // 预设结束日期
weeks: 32 // 预设学期周数
},
// ==================== 3. 工具栏按钮区 ====================
/** 可新建学期信息的班次 */
newableClasses: [
{ bc: '班次0633', xkzy: '专业209', nj: '2017级' },
{ bc: '班次0634', xkzy: '专业209', nj: '2017级' },
{ bc: '班次0709', xkzy: '专业205', nj: '2017级' },
{ bc: '班次0710', xkzy: '专业205', nj: '2017级' },
{ bc: '班次0901', xkzy: '专业301', nj: '2017级' }
],
/** 教学班次学期信息明细弹窗(新添) */
detailVisible: false,
selectedNewClass: null,
/** 班历设置弹窗(编辑班历) */
calendarVisible: false,
currentRow: null,
/** 预设合班弹窗 */
teamVisible: false,
/** 配档编辑弹窗(班次学期配档)显示状态 */
peidangVisible: false,
/** 配档编辑弹窗标题(班次学期配档——...——班次) */
peidangClassLabel: '',
/** 排序方式:xh=序号排序 / bw=波次排序 */
sortType: 'xh',
/** 波次背景色(同一波次使用同一颜色连在一起显示) */
bwColors: [
'#e3f2fd', // 浅蓝
'#e8f5e9', // 浅绿
'#fff8e1', // 浅黄
'#f3e5f5', // 浅紫
'#e0f2f1' // 浅青
],
// ==================== 4. 班次学期信息列表 ====================
tableData: [
// ---- 学员1队(跨 10 行) ----
{ xzdw: '学员1队', xh: 1, bw: 1, bc: '0150', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-307', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 2, bw: 1, bc: '0151', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-307', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 3, bw: 1, bc: '0152', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-308', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 4, bw: 1, bc: '0153', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-308', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 5, bw: 1, bc: '0154', rs: 42, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-309', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 528, zxs: 22.0, zxf: 42 },
{ xzdw: '学员1队', xh: 6, bw: 1, bc: '0155', rs: 42, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-309', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 528, zxs: 22.0, zxf: 42 },
{ xzdw: '学员1队', xh: 7, bw: 1, bc: '0156', rs: 42, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-310', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 15, kczs: 540, zxs: 22.5, zxf: 43 },
{ xzdw: '学员1队', xh: 8, bw: 1, bc: '0157', rs: 38, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-310', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 15, kczs: 540, zxs: 22.5, zxf: 43 },
{ xzdw: '学员1队', xh: 9, bw: 1, bc: '0158', rs: 38, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-311', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
{ xzdw: '学员1队', xh: 10, bw: 1, bc: '0159', rs: 38, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-311', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
// ---- 教学五系(跨 2 行) ----
{ xzdw: '教学五系', xh: 11, bw: 2, bc: '0201', rs: 30, nj: '2016级', xkzy: '专业113', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '6-202', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 12, kczs: 480, zxs: 19.2, zxf: 36 },
{ xzdw: '教学五系', xh: 12, bw: 2, bc: '0202', rs: 30, nj: '2016级', xkzy: '专业113', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '6-202', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 12, kczs: 480, zxs: 19.2, zxf: 36 },
// ---- 教学七系(跨 3 行) ----
{ xzdw: '教学七系', xh: 13, bw: 3, bc: '0203', rs: 36, nj: '2016级', xkzy: '专业115', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '8-007', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 13, kczs: 520, zxs: 20.8, zxf: 38 },
{ xzdw: '教学七系', xh: 14, bw: 3, bc: '0204', rs: 36, nj: '2016级', xkzy: '专业115', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '8-007', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 13, kczs: 520, zxs: 20.8, zxf: 38 },
{ xzdw: '教学七系', xh: 15, bw: 3, bc: '0205', rs: 36, nj: '2016级', xkzy: '专业115', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '8-008', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 13, kczs: 520, zxs: 20.8, zxf: 38 }
]
}
},
computed: {
/** 排序方式标签 */
sortLabel() {
return this.sortType === 'bw' ? '波次排序' : '序号排序'
},
/** 根据排序方式展示列表:波次排序时按波次分组连排 */
displayData() {
if (this.sortType === 'bw') {
return [...this.tableData].sort((a, b) => a.bw - b.bw || a.xh - b.xh)
}
return this.tableData
}
},
mounted() {
// 默认选中第一行 班次0150(高亮)
this.$nextTick(() => {
if (this.tableData.length && this.$refs.tableRef) {
this.$refs.tableRef.setCurrentRow(this.tableData[0])
}
})
},
methods: {
// ==================== 2. 校历 / 预设日期 ====================
/** 吸取日期:将校历日期复制到预设日期 */
handleAbsorbDate() {
this.preset.kxrq = this.semester.kxrq
this.preset.jsrq = this.semester.jsrq
this.preset.weeks = this.semester.weeks
this.$message.success('已吸取校历日期到预设日期')
},
/** 快速设定学期日期 */
handleQuickSetDates() {
this.$message.success('快速设定学期日期(前端演示)')
},
// ==================== 3. 工具栏按钮区 ====================
/** 通用操作(前端演示) */
handleAction(name) {
this.$message.success(`${name}(前端演示)`)
},
/** 新添:从下拉选择班次后打开明细弹窗 */
handleNewCommand(item) {
if (typeof item === 'string') return
this.selectedNewClass = item
this.detailVisible = true
},
handleDetailConfirm() {
this.$message.success('已创建班次学期信息(前端演示)')
},
/** 编辑班历 */
handleEditCalendar() {
if (!this.currentRow) {
this.$message.warning('请先在列表中选择一条班次学期信息')
return
}
this.calendarVisible = true
},
/** 预设合班 */
handleMerge() {
this.teamVisible = true
},
handleTeamConfirm(teams) {
this.$message.success(`预设合班已应用于 ${teams.length} 个班次(前端演示)`)
},
/** 配档编辑:选择班次后打开班次学期配档窗口 */
handlePeidangEdit() {
if (!this.currentRow) {
this.$message.warning('请先在列表中选择一个班次')
return
}
const row = this.currentRow
this.peidangClassLabel = `${row.xzdw}${row.nj}${row.xkzy}班次${row.bc}`
this.peidangVisible = true
},
handleSortCommand(command) {
this.sortType = command
},
/** 行样式:波次排序时同一波次使用同一背景色 */
rowClassName({ row }) {
if (this.sortType !== 'bw') return ''
const colorIndex = (row.bw - 1) % this.bwColors.length
return `bw-row bw-row-${colorIndex}`
},
// ==================== 4. 班次学期信息列表 ====================
handleCurrentChange(row) {
this.currentRow = row
},
/** 合并单元格:行政队别列(第一列)纵向合并相同值 */
spanMethod({ rowIndex, columnIndex }) {
if (columnIndex === 0) {
const list = this.displayData
const current = list[rowIndex]
// 非分组首行,隐藏
if (rowIndex > 0 && list[rowIndex - 1].xzdw === current.xzdw) {
return { rowspan: 0, colspan: 0 }
}
// 统计连续相同行政队别的行数
let rowspan = 1
for (let i = rowIndex + 1; i < list.length; i++) {
if (list[i].xzdw === current.xzdw) rowspan++
else break
}
return { rowspan, colspan: 1 }
}
return { rowspan: 1, colspan: 1 }
},
/** 表头特殊样式:总课程数 / 总课时数列红色框标注 */
headerCellClassName({ column }) {
if (column.property === 'kcrws' || column.property === 'kczs') {
return 'red-header-cell'
}
return ''
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.shift-semester-page {
// ========== 1. 学年学期标题 ==========
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-left: 10px;
border-left: 4px solid var(--edu-green-primary);
}
// ========== 2. 校历 / 预设日期信息区 ==========
.search-card {
margin-bottom: 16px;
.search-form {
.date-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
.date-group-label {
flex-shrink: 0;
font-size: 14px;
font-weight: 600;
color: #303133;
margin-right: 8px;
}
.absorb-btn {
margin-left: auto;
align-self: center;
}
.el-form-item {
margin-bottom: 0;
margin-right: 16px;
}
}
}
}
// ========== 3. 工具栏按钮区 ==========
.list-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 16px;
.left-group,
.right-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
}
// ========== 新建下拉菜单 ==========
.new-class-menu {
max-height: 320px;
overflow-y: auto;
}
// ========== 4. 数据表格区 ==========
$bwColors: (#e3f2fd, #e8f5e9, #fff8e1, #f3e5f5, #e0f2f1);
.table-card {
flex: 1;
.class-table {
width: 100%;
// 波次背景色(同一合班波次使用同一背景颜色连在一起显示)
@for $i from 0 through length($bwColors) - 1 {
::v-deep(.el-table__body tr.bw-row-#{$i} > td.el-table__cell) {
background-color: nth($bwColors, $i + 1);
}
::v-deep(.el-table__body tr.bw-row-#{$i}:hover > td.el-table__cell) {
background-color: nth($bwColors, $i + 1);
}
}
// 当前行高亮(优先于波次背景色)
::v-deep(.el-table__body tr.current-row > td.el-table__cell) {
background-color: #e1f5ec;
}
::v-deep(.el-table__body tr.current-row:hover > td.el-table__cell) {
background-color: #e1f5ec;
}
// 表头红色框标注列(总课程数 / 总课时数)
::v-deep(th.red-header-cell) {
border: 2px solid #f56c6c !important;
background: #fdeaea !important;
}
}
}
}
</style>
@@ -0,0 +1,875 @@
<template>
<div class="app-container subject-page">
<!-- ==================== 1. 顶部状态提示 ==================== -->
<div class="status-tip">{{ statusTip }}</div>
<!-- ==================== 2. 查询条件区域 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm">
<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.kcbh" placeholder="请输入课程别名" clearable class="q-control" />
</div>
<div class="query-field">
<span class="q-label no-check">简称</span>
<el-input v-model="searchForm.jc" placeholder="请输入简称" clearable class="q-control" />
</div>
<div class="query-field">
<span class="q-label no-check">学分</span>
<div class="q-control range-control">
<el-input v-model="searchForm.xfMin" placeholder="0" />
<span class="range-sep">至</span>
<el-input v-model="searchForm.xfMax" placeholder="0" />
</div>
</div>
<div class="query-field">
<span class="q-label no-check">学时</span>
<div class="q-control range-control">
<el-input v-model="searchForm.xsMin" placeholder="0" />
<span class="range-sep">至</span>
<el-input v-model="searchForm.xsMax" placeholder="0" />
</div>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.zcfsEnabled" />
<span class="q-label">支持方式</span>
<el-select v-model="searchForm.zcfs" class="q-control" :disabled="!searchForm.zcfsEnabled">
<el-option v-for="item in zcfsOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.khlxEnabled" />
<span class="q-label">考核类型</span>
<el-select v-model="searchForm.khlx" class="q-control" :disabled="!searchForm.khlxEnabled">
<el-option v-for="item in khlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<span class="q-label no-check">秘密等级</span>
<el-select v-model="searchForm.mmdj" placeholder="请选择" class="q-control">
<el-option v-for="item in mmdjOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.dgxzEnabled" />
<span class="q-label">大纲性质</span>
<el-radio-group v-model="searchForm.dgxz" class="q-control" :disabled="!searchForm.dgxzEnabled">
<el-radio :label="'大纲课程'">大纲课程</el-radio>
<el-radio :label="'非大纲课程'">非大纲课程</el-radio>
</el-radio-group>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.zzglEnabled" />
<span class="q-label">政治类课程</span>
<el-radio-group v-model="searchForm.zzgl" class="q-control" :disabled="!searchForm.zzglEnabled">
<el-radio :label="'政治类'">政治类</el-radio>
<el-radio :label="'非政治类'">非政治类</el-radio>
</el-radio-group>
</div>
<div class="query-field">
<span class="q-label no-check">备注</span>
<el-input v-model="searchForm.bz" placeholder="请输入备注" clearable class="q-control" />
</div>
</el-col>
<!-- 右栏 -->
<el-col :xs="24" :md="12">
<div class="query-field">
<el-checkbox v-model="searchForm.jysEnabled" />
<span class="q-label">教研室</span>
<el-select v-model="searchForm.jys" class="q-control" :disabled="!searchForm.jysEnabled">
<el-option v-for="item in jysOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.pxccEnabled" />
<span class="q-label">培训层次</span>
<el-select v-model="searchForm.pxcc" class="q-control" :disabled="!searchForm.pxccEnabled">
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.pxlxEnabled" />
<span class="q-label">培训类型</span>
<el-select v-model="searchForm.pxlx" class="q-control" :disabled="!searchForm.pxlxEnabled">
<el-option v-for="item in pxlxOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.kclxEnabled" />
<span class="q-label">课程类型</span>
<el-select v-model="searchForm.kclx" class="q-control" :disabled="!searchForm.kclxEnabled">
<el-option v-for="item in kclxOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.jsfsEnabled" />
<span class="q-label">建设方式</span>
<el-select v-model="searchForm.jsfs" class="q-control" :disabled="!searchForm.jsfsEnabled">
<el-option v-for="item in jsfsOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.pjqkEnabled" />
<span class="q-label">评选情况</span>
<el-select v-model="searchForm.pjqk" class="q-control" :disabled="!searchForm.pjqkEnabled">
<el-option v-for="item in pjqkOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.gljgEnabled" />
<span class="q-label">管理机构</span>
<el-select v-model="searchForm.gljg" class="q-control" :disabled="!searchForm.gljgEnabled">
<el-option v-for="item in gljgOptions" :key="item" :label="item" :value="item" />
</el-select>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.zyxzEnabled" />
<span class="q-label">在用选项</span>
<el-radio-group v-model="searchForm.zyxz" class="q-control" :disabled="!searchForm.zyxzEnabled">
<el-radio :label="'在用'">在用</el-radio>
<el-radio :label="'其他'">其他</el-radio>
</el-radio-group>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.bbxxEnabled" />
<span class="q-label">版本选项</span>
<el-radio-group v-model="searchForm.bbxx" class="q-control" :disabled="!searchForm.bbxxEnabled">
<el-radio :label="'最新版本'">最新版本</el-radio>
<el-radio :label="'存在多版本'">存在多版本</el-radio>
<el-radio :label="'全部版本'">全部版本</el-radio>
</el-radio-group>
</div>
<div class="query-field">
<el-checkbox v-model="searchForm.bmbhEnabled" />
<span class="q-label">别名包含规范名称</span>
<el-radio-group v-model="searchForm.bmbh" class="q-control" :disabled="!searchForm.bmbhEnabled">
<el-radio :label="'完全包含'">完全包含</el-radio>
<el-radio :label="'未完全包含'">未完全包含</el-radio>
</el-radio-group>
</div>
</el-col>
</el-row>
</el-form>
<div class="notice">注意:勾选"选择框"表示启用该项对应的查询条件,输入框非空表示启用查询条件。</div>
<div class="query-footer">
<el-button class="gray-btn" @click="handleQuery">查询</el-button>
</div>
</el-card>
<!-- ==================== 3. 操作按钮区域 ==================== -->
<el-card shadow="never" class="action-card">
<div class="action-row">
<el-button type="primary" @click="handleOpenDialog">新建课程科目</el-button>
<el-button type="primary" @click="handleBatchDelete">删除所选</el-button>
<el-button type="primary" @click="handleDownloadCourse">下载课程科目基本信息</el-button>
<el-button type="primary" @click="handleDownloadTextbook">下载课程教材基本信息</el-button>
</div>
<div class="action-row">
<el-button type="primary" @click="handleDownloadTemplate">【课程科目数据文件模板】下载</el-button>
</div>
</el-card>
<!-- ==================== 4. 文件上传区域 ==================== -->
<el-card shadow="never" class="upload-card">
<div class="upload-row">
<div class="upload-left">
<el-button @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-checkbox v-model="coverExist">覆盖已存在课程科目</el-checkbox>
<el-button type="primary" @click="handleUpload">上传数据</el-button>
</div>
</div>
</el-card>
<!-- ==================== 5. 红色提示文字 ==================== -->
<div class="red-tip">
提示:课程体系按"课程-专题-课题"构建的,课程名称的命名格式为"课程名称\专题名称\课题名称"或"课程名称\课题名称"。
</div>
<!-- ==================== 6. 数据表格区域 ==================== -->
<el-card shadow="never" class="table-card">
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="manage" label="管理" width="70" align="center" />
<el-table-column prop="code" label="编码" width="100" show-overflow-tooltip />
<el-table-column prop="name" label="课程名称" min-width="130" show-overflow-tooltip />
<el-table-column prop="cat" label="分类" width="100" align="center" />
<el-table-column prop="exam" label="考核类型方式" width="110" align="center" />
<el-table-column prop="inClass" label="课内学时" width="90" align="center" />
<el-table-column prop="outClass" label="课外学时" width="90" align="center" />
<el-table-column prop="other" label="其他" width="80" align="center" />
<el-table-column prop="textbook" label="教材" min-width="120" show-overflow-tooltip />
<el-table-column prop="majors" label="开设课程专业名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="teacher" label="在职主讲教员" min-width="110" show-overflow-tooltip />
</el-table>
<el-pagination
:current-page="pageNum"
:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
class="pagination-wrapper"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</el-card>
<!-- 新增/编辑课程科目弹窗 -->
<el-dialog
:visible="dialogVisible"
:title="dialogTitle"
width="1100px"
:close-on-click-modal="false"
class="add-dialog"
@update:visible="val => dialogVisible = val"
>
<el-form ref="addFormRef" :model="addForm" :rules="rules" label-width="120px" class="add-form" :status-icon="false">
<el-row :gutter="24">
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="编号" prop="bh">
<el-input v-model="addForm.bh" placeholder="请输入编号" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="课标号" prop="kbh">
<el-input v-model="addForm.kbh" placeholder="请输入课标号" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="课程名称" prop="mc">
<el-input v-model="addForm.mc" placeholder="请输入课程名称" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="适用专业" prop="syzy">
<el-input v-model="addForm.syzy" placeholder="请输入适用专业" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="课时数" prop="kcs">
<el-input-number v-model="addForm.kcs" :min="0" controls-position="right" class="w-full" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="录入人编号" prop="zxrbh">
<el-input v-model="addForm.zxrbh" placeholder="请输入录入人编号" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
<el-form-item label="录入时间">
<el-input v-model="addForm.zxsj" placeholder="请输入录入时间" clearable />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="正文">
<el-input v-model="addForm.zw" type="textarea" :rows="3" placeholder="请输入正文" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="备注">
<el-input v-model="addForm.bz" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-col>
</el-row>
<div class="classroom-section">
<div class="classroom-header">
<span class="classroom-title">课堂列表</span>
<el-button type="primary" size="small" @click="handleAddClassroom">添加课堂</el-button>
</div>
<el-table :data="addForm.ktList" border size="small">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="课堂编号" width="140">
<template slot-scope="{ row }">
<el-input v-model="row.bh" placeholder="课堂编号" />
</template>
</el-table-column>
<el-table-column label="课堂次序" width="90">
<template slot-scope="{ row }">
<el-input-number v-model="row.ktjc" :min="1" controls-position="right" class="w-full" />
</template>
</el-table-column>
<el-table-column label="教学内容" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxnr" placeholder="教学内容" />
</template>
</el-table-column>
<el-table-column label="教学要点" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxyd" placeholder="教学要点" />
</template>
</el-table-column>
<el-table-column label="教学方法" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxff" placeholder="教学方法" />
</template>
</el-table-column>
<el-table-column label="主讲教员数" width="100">
<template slot-scope="{ row }">
<el-input-number v-model="row.zjjys" :min="0" controls-position="right" class="w-full" />
</template>
</el-table-column>
<el-table-column label="辅讲教员数" width="100">
<template slot-scope="{ row }">
<el-input-number v-model="row.fjjys" :min="0" controls-position="right" class="w-full" />
</template>
</el-table-column>
<el-table-column label="教学目的" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxmd" placeholder="教学目的" />
</template>
</el-table-column>
<el-table-column label="教学要求" min-width="150">
<template slot-scope="{ row }">
<el-input v-model="row.jxyq" placeholder="教学要求" />
</template>
</el-table-column>
<el-table-column label="操作" width="80" align="center" fixed="right">
<template slot-scope="{ $index }">
<el-button type="text" class="text-danger" @click="handleDeleteClassroom($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-form>
<div slot="footer">
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" :loading="addLoading" @click="handleSubmit">保存</el-button>
</div>
</div>
</el-dialog>
<!-- 撤回弹窗 -->
<el-dialog
:visible="withdrawDialogVisible"
title="撤回课程科目"
width="500px"
:close-on-click-modal="false"
@update:visible="val => withdrawDialogVisible = val"
>
<el-form :model="withdrawForm" label-width="100px">
<el-form-item label="课程编号">
<el-input v-model="withdrawForm.bh" disabled />
</el-form-item>
<el-form-item label="撤回原因">
<el-input v-model="withdrawForm.chyy" type="textarea" :rows="4" placeholder="请输入撤回原因" />
</el-form-item>
</el-form>
<div slot="footer">
<div class="dialog-footer">
<el-button @click="handleCancelWithdraw">取消</el-button>
<el-button type="primary" :loading="withdrawLoading" @click="handleWithdraw">确定撤回</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'SubjectIndex',
data() {
return {
// ==================== 1. 顶部状态提示 ====================
statusTip: '请输入查询条件',
// ==================== 下拉选项 ====================
zcfsOptions: ['其他', '讲授', '研讨', '实操'],
khlxOptions: ['讲座', '考试', '考查', '实操'],
mmdjOptions: ['公开', '内部', '秘密', '机密'],
jysOptions: ['未确定教研室', '军事基础教研室', '政治工作教研室', '装备保障教研室'],
pxccOptions: ['无', '本科', '硕士', '博士'],
pxlxOptions: ['军官基础教育', '士兵职业教育', '研究生教育', '其他'],
kclxOptions: ['其他类', '公共基础', '专业基础', '专业核心'],
jsfsOptions: ['其他', '自建', '共建', '引进'],
pjqkOptions: ['无', '国家级', '军队级', '院校级'],
gljgOptions: ['教务处', '教研室1', '教研室2'],
// ==================== 2. 查询条件 ====================
searchForm: {
kcbh: '',
jc: '',
xfMin: '',
xfMax: '',
xsMin: '',
xsMax: '',
zcfsEnabled: false,
zcfs: '其他',
khlxEnabled: false,
khlx: '讲座',
mmdj: '',
dgxzEnabled: false,
dgxz: '大纲课程',
zzglEnabled: false,
zzgl: '政治类',
bz: '',
jysEnabled: false,
jys: '未确定教研室',
pxccEnabled: false,
pxcc: '无',
pxlxEnabled: false,
pxlx: '军官基础教育',
kclxEnabled: false,
kclx: '其他类',
jsfsEnabled: false,
jsfs: '其他',
pjqkEnabled: false,
pjqk: '无',
gljgEnabled: false,
gljg: '教务处',
zyxzEnabled: true,
zyxz: '在用',
bbxxEnabled: true,
bbxx: '最新版本',
bmbhEnabled: false,
bmbh: '完全包含'
},
// ==================== 列表数据 ====================
/** 模拟列表数据(后端接口未提供) */
mockTableData: [
{ id: 1, manage: '编辑', code: 'KCB001', name: '高等数学', cat: '公共基础', exam: '考试', inClass: 48, outClass: 16, other: '—', textbook: '高等数学(上册)', majors: '炮兵装备运用/通信工程', teacher: '张三' },
{ id: 2, manage: '编辑', code: 'KCB002', name: '导弹发射原理', cat: '专业核心', exam: '考试', inClass: 40, outClass: 16, other: '—', textbook: '导弹发射原理', majors: '炮兵装备运用', teacher: '李四' },
{ id: 3, manage: '编辑', code: 'KCB003', name: '通信原理', cat: '专业核心', exam: '考试', inClass: 48, outClass: 16, other: '—', textbook: '通信原理', majors: '通信工程', teacher: '王五' },
{ id: 4, manage: '编辑', code: 'KCB004', name: '军事英语', cat: '公共基础', exam: '考查', inClass: 30, outClass: 8, other: '—', textbook: '军事英语教程', majors: '全军各专业', teacher: '赵六' },
{ id: 5, manage: '编辑', code: 'KCB005', name: '联合作战指挥', cat: '专业核心', exam: '综合考核', inClass: 40, outClass: 20, other: '—', textbook: '联合作战指挥', majors: '军事指挥', teacher: '孙七' },
{ id: 6, manage: '编辑', code: 'KCB006', name: '装备维修训练', cat: '实践环节', exam: '实操', inClass: 0, outClass: 48, other: '—', textbook: '装备维修手册', majors: '装备保障', teacher: '周八' }
],
loading: false,
tableData: [],
total: 0,
pageNum: 1,
pageSize: 20,
selectedRows: [],
// ==================== 文件上传 ====================
selectedFile: null,
coverExist: false,
// ==================== 新增/编辑弹窗 ====================
dialogVisible: false,
addLoading: false,
editLoading: false,
isEdit: false,
addForm: this.createEmptyForm(),
rules: {
bh: [{ required: true, message: '请输入编号', trigger: 'blur' }],
kbh: [{ required: true, message: '请输入课标号', trigger: 'blur' }],
mc: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
syzy: [{ required: true, message: '请输入适用专业', trigger: 'blur' }],
kcs: [{ required: true, message: '请输入课时数', trigger: 'blur' }],
zxrbh: [{ required: true, message: '请输入录入人编号', trigger: 'blur' }]
},
// ==================== 撤回弹窗 ====================
withdrawDialogVisible: false,
withdrawLoading: false,
withdrawForm: { bh: '', chyy: '' }
}
},
computed: {
dialogTitle() {
return this.isEdit ? '编辑课程科目' : '新增课程科目'
},
fileName() {
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
}
},
created() {
// 初始化表单(保证 getCurrentTime 可用)
this.addForm = this.createEmptyForm()
},
mounted() {
this.fetchList()
},
methods: {
// ==================== 表单辅助 ====================
getCurrentTime() {
return new Date().toLocaleString('zh-CN', { hour12: false }).replace(/\//g, '-')
},
/** 创建空白课程表单(含一条空课堂记录) */
createEmptyForm() {
return {
bh: '', kbh: '', mc: '', syzy: '', kcs: 0, bz: '', zxrbh: '', zxsj: this.getCurrentTime(),
zxzt: '0', sbzt: '0', sbsj: '', sbrbh: '', chyy: '', zw: '', jsonzd: '', ktList: [this.defaultClassroom()]
}
},
/** 默认课堂项 */
defaultClassroom() {
return { bh: '', ktjc: 1, jxnr: '', jxyd: '', jxff: '', zjjys: 0, fjjys: 0, bz: '', jxmd: '', jxyq: '' }
},
resetForm() {
this.addForm = this.createEmptyForm()
},
// ==================== 列表加载 ====================
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为 getCourseTeachingList
fetchList() {
this.loading = true
setTimeout(() => {
this.tableData = [...this.mockTableData]
this.total = this.mockTableData.length
this.loading = false
}, 200)
},
// ==================== 查询 ====================
handleQuery() {
const now = new Date()
const hh = String(now.getHours()).padStart(2, '0')
const mm = String(now.getMinutes()).padStart(2, '0')
const ss = String(now.getSeconds()).padStart(2, '0')
this.statusTip = `[${hh}:${mm}:${ss}]查询成功!共检索到${this.total || 982}条记录。`
this.pageNum = 1
this.fetchList()
},
handlePageChange(current) {
this.pageNum = current
this.fetchList()
},
handleSizeChange(size) {
this.pageSize = size
this.pageNum = 1
this.fetchList()
},
handleSelectionChange(rows) {
this.selectedRows = rows
},
// ==================== 删除所选 ====================
handleBatchDelete() {
if (this.selectedRows.length === 0) {
this.$message.warning('请先选择要删除的记录')
return
}
const ids = this.selectedRows.map((r) => r.id)
this.tableData = this.tableData.filter((r) => ids.indexOf(r.id) === -1)
this.total = this.tableData.length
this.$message.success(`已删除所选 ${ids.length} 条记录(前端模拟)`)
},
// ==================== 下载 ====================
downloadBlob(content, filename) {
const blob = new Blob(['\ufeff' + content], { type: 'text/plain;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = filename
link.click()
URL.revokeObjectURL(link.href)
},
handleDownloadCourse() {
this.downloadBlob('编码,课程名称,分类,考核类型方式,课内学时,课外学时\n', '课程科目基本信息.txt')
this.$message.success('课程科目基本信息已下载(前端模拟)')
},
handleDownloadTextbook() {
this.downloadBlob('课程名称,教材,在职主讲教员\n', '课程教材基本信息.txt')
this.$message.success('课程教材基本信息已下载(前端模拟)')
},
handleDownloadTemplate() {
this.downloadBlob('编码,课程名称,分类,考核类型方式,课内学时,课外学时,教材,开设课程专业名称,在职主讲教员\n', '课程科目数据文件模板.txt')
this.$message.success('【课程科目数据文件模板】已下载(前端模拟)')
},
// ==================== 文件上传 ====================
handleSelectFile() {
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
},
handleFileChange(e) {
const input = e.target
this.selectedFile = (input.files && input.files[0]) || null
},
handleUpload() {
if (!this.selectedFile) {
this.$message.warning('请先选择文件')
return
}
this.$message.success(`文件「${this.selectedFile.name}」上传成功(前端模拟)`)
},
// ==================== 新增/编辑弹窗 ====================
handleOpenDialog() {
this.isEdit = false
this.resetForm()
this.dialogVisible = true
},
handleAddClassroom() {
this.addForm.ktList.push(this.defaultClassroom())
},
handleDeleteClassroom(index) {
this.addForm.ktList.splice(index, 1)
},
// TODO: 后端接口未提供,保存成功后刷新列表为前端模拟;接口就绪后替换为 addCourseTeaching/updateCourseTeaching
handleSubmit() {
this.$refs.addFormRef.validate((valid) => {
if (!valid) return
this.addLoading = true
setTimeout(() => {
this.$message.success(this.isEdit ? '修改成功' : '保存成功')
this.dialogVisible = false
this.fetchList()
this.addLoading = false
}, 300)
})
},
handleCancel() {
this.dialogVisible = false
},
// ==================== 撤回弹窗 ====================
// TODO: 后端接口未提供,撤回为前端模拟;接口就绪后替换为 deleteCourseTeaching
handleWithdraw() {
if (!this.withdrawForm.chyy.trim()) {
this.$message.warning('请输入撤回原因')
return
}
this.withdrawLoading = true
setTimeout(() => {
this.$message.success('撤回成功')
this.withdrawDialogVisible = false
this.fetchList()
this.withdrawLoading = false
}, 300)
},
handleCancelWithdraw() {
this.withdrawDialogVisible = false
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
// ==================== 1. 顶部状态提示 ====================
.status-tip {
display: inline-block;
background: #dc3545;
color: #fff;
font-size: 12px;
padding: 3px 10px;
border-radius: 4px;
margin-bottom: 12px;
}
// ==================== 2. 查询条件区域 ====================
.search-card {
margin-bottom: 16px;
.query-field {
display: flex;
align-items: center;
margin-bottom: 14px;
min-height: 32px;
::v-deep(.el-checkbox) {
margin-right: 6px;
flex-shrink: 0;
}
.q-label {
width: 120px;
flex-shrink: 0;
font-size: 13px;
color: #303133;
text-align: right;
margin-right: 10px;
white-space: nowrap;
&.no-check {
margin-left: 24px;
}
}
.q-control {
flex: 1;
min-width: 0;
}
.range-control {
display: flex;
align-items: center;
gap: 6px;
.el-input {
flex: 1;
}
.range-sep {
flex-shrink: 0;
font-size: 12px;
color: #606266;
}
}
}
.notice {
font-size: 12px;
color: #f56c6c;
margin-top: 8px;
text-align: left;
}
.query-footer {
display: flex;
justify-content: flex-end;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
}
// ==================== 3. 操作按钮区域 ====================
.action-card {
margin-bottom: 16px;
.action-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
}
// ==================== 4. 文件上传区域 ====================
.upload-card {
margin-bottom: 16px;
.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;
gap: 12px;
}
}
}
// ==================== 5. 红色提示文字 ====================
.red-tip {
font-size: 14px;
color: #f56c6c;
margin-bottom: 16px;
line-height: 1.6;
}
// ==================== 6. 数据表格区域 ====================
.table-card {
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
::v-deep(.el-table .cell) {
font-size: 12px;
}
}
// ==================== 新增/编辑弹窗 ====================
.add-dialog {
::v-deep(.el-dialog__body) {
max-height: 70vh;
overflow: auto;
}
}
.add-form {
.w-full {
width: 100%;
}
.classroom-section {
margin-top: 16px;
.classroom-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
.classroom-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
}
}
// 课堂列表删除按钮红色文字
.text-danger {
color: #f56c6c;
padding: 0;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.gray-btn {
height: 28px;
padding: 2px 16px;
font-size: 12px;
background: #f5f5f5;
border: 1px solid #c0c4cc;
color: #333;
border-radius: 2px;
&:hover,
&:focus {
background: #ebebeb;
border-color: #c0c4cc;
color: #333;
}
}
</style>
@@ -0,0 +1,487 @@
<template>
<div class="app-container task-plan-page">
<!-- ==================== 1. 学期信息区域 ==================== -->
<el-card shadow="never" class="search-card">
<el-form :inline="true" :model="semester" class="search-form">
<el-form-item label="学期名称">
<el-input v-model="semester.name" style="width: 160px" />
</el-form-item>
<el-form-item label="校历开学日期">
<el-date-picker
v-model="semester.kxrq"
type="date"
format="yyyy年M月d日"
value-format="yyyy-MM-dd"
placeholder="选择日期"
style="width: 160px"
/>
</el-form-item>
<el-form-item label="结束日期">
<el-date-picker
v-model="semester.jsrq"
type="date"
format="yyyy年M月d日"
value-format="yyyy-MM-dd"
placeholder="选择日期"
style="width: 160px"
/>
</el-form-item>
<el-form-item label="学期周数">
<el-input v-model="semester.weeks" style="width: 90px" />
</el-form-item>
</el-form>
</el-card>
<!-- ==================== 2. 数据表格区域 ==================== -->
<div class="list-title">教学任务列表:</div>
<div class="list-toolbar">
<div class="left-group">
<el-button type="primary" @click="handleAdd">新建</el-button>
<el-button type="primary" plain @click="handleEdit">编辑</el-button>
<el-button type="danger" plain @click="handleDelete">删除</el-button>
<el-button type="primary" plain @click="handlePublish">发布教学任务</el-button>
<el-button type="primary" plain @click="handleEndPublish">结束发布</el-button>
<el-button type="primary" plain @click="handleManageTeams">管理教学任务所含学员队</el-button>
</div>
</div>
<el-card shadow="never" class="table-card">
<el-table
ref="tableRef"
v-loading="loading"
:data="tableData"
border
stripe
highlight-current-row
class="task-table"
@selection-change="handleSelectionChange"
@current-change="handleCurrentChange"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="rwmc" label="任务名称" min-width="170" show-overflow-tooltip />
<el-table-column label="状态" width="80" align="center">
<template slot-scope="{ row }">
<span class="status-text">{{ row.zt }}</span>
</template>
</el-table-column>
<el-table-column prop="fbsj" label="发布时间" width="130" align="center" />
<el-table-column prop="cjsj" label="创建时间" width="130" align="center" />
<el-table-column label="结束时间" width="130" align="center">
<template slot-scope="{ row }">
{{ row.jssj || '' }}
</template>
</el-table-column>
<el-table-column prop="jysCount" label="教研室数" width="90" align="center" />
<el-table-column prop="teamCount" label="学员队数" width="90" align="center" />
<el-table-column prop="hbqKcCount" label="合班前课程任务数" width="140" align="center" />
<el-table-column prop="hbqXssCount" label="合班前总学时数" width="140" align="center" />
<el-table-column prop="hbhKcCount" label="合班后课程任务数" width="140" align="center" />
<el-table-column prop="hbhXssCount" label="合班后总学时数" width="140" align="center" />
</el-table>
</el-card>
<!-- ==================== 教学任务信息弹窗 ==================== -->
<el-dialog
:visible="dialogVisible"
title="教学任务信息"
width="900px"
class="teaching-task-dialog"
:close-on-click-modal="false"
@update:visible="val => (dialogVisible = val)"
>
<!-- ==================== 1. 基本信息区域 ==================== -->
<el-form label-width="86px" class="basic-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="学期">
<el-input v-model="form.xq" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务名称">
<el-input v-model="form.rwmc" placeholder="请输入任务名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="创建时间">
<el-input v-model="form.cjsj" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发布时间">
<el-input v-model="form.fbsj" readonly />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="结束时间">
<el-input v-model="form.jssj" readonly />
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- ==================== 2. Tab 标签页区域 ==================== -->
<el-tabs v-model="activeTab" class="tt-tabs">
<!-- 教研室任务书列表 -->
<el-tab-pane label="教研室任务书列表" name="taskbook">
<div class="sub-toolbar">
<el-button type="primary" plain size="small" @click="handleTaskBookDetail">
教研室任务书详细
</el-button>
</div>
<el-table
ref="taskBookTable"
:data="taskBookRows"
border
stripe
highlight-current-row
class="taskbook-table"
@selection-change="handleTaskBookSelection"
>
<el-table-column type="selection" width="40" align="center" />
<el-table-column prop="jys" label="教研室" min-width="120" align="center" />
<el-table-column prop="zt" label="状态" width="80" align="center" />
<el-table-column prop="sbsj" label="上报时间" width="150" align="center" show-overflow-tooltip />
<el-table-column prop="xydkcCount" label="学员队课程任务数" width="150" align="center" />
<el-table-column prop="wczdsCount" label="完成指定数" width="100" align="center" />
</el-table>
</el-tab-pane>
<!-- 学员队列表 -->
<el-tab-pane label="学员队列表" name="team">
<div class="tab-empty">暂无学员队列表数据</div>
</el-tab-pane>
</el-tabs>
<!-- ==================== 3. 底部按钮区域 ==================== -->
<div slot="footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
// ==================== 1. 学期信息区域 ====================
semester: {
name: '2017年秋季学期', // 学期名称
kxrq: '2017-07-08', // 校历开学日期
jsrq: '2018-02-11', // 结束日期
weeks: 32 // 学期周数
},
// ==================== 2. 数据表格 ====================
loading: false,
tableData: [
{
bh: 'task-2017-2',
rwmc: '2017年下半年教学任务',
zt: '发布',
fbsj: '2017年12月1日',
cjsj: '2017年7月8日',
jssj: '',
jysCount: 31,
teamCount: 78,
hbqKcCount: 1126,
hbqXssCount: 36050,
hbhKcCount: 1058,
hbhXssCount: 34050
},
{
bh: 'task-2019-1',
rwmc: '研究生下半年教学任务',
zt: '发布',
fbsj: '2019年12月1日',
cjsj: '2017年7月8日',
jssj: '',
jysCount: 12,
teamCount: 2,
hbqKcCount: 16,
hbqXssCount: 560,
hbhKcCount: 16,
hbhXssCount: 560
}
],
selectedRows: [],
currentRow: null,
// ==================== 操作按钮 ====================
/** 教学任务信息弹窗 */
dialogVisible: false,
/** 弹窗模式:新建 / 编辑 */
dialogMode: 'create',
/** 编辑模式选中的任务行 */
editRow: null,
// ==================== 弹窗:基本信息 ====================
form: {
xq: '2017年秋季学期', // 学期(只读)
rwmc: '', // 任务名称(可编辑)
cjsj: '', // 创建时间(只读)
fbsj: '', // 发布时间(只读)
jssj: '' // 结束时间(只读)
},
// ==================== 弹窗:Tab 标签页 ====================
activeTab: 'taskbook',
// ==================== 弹窗:教研室任务书列表 ====================
taskBookRows: [],
selectedTaskBookRows: [],
/** 编辑模式下的教研室任务书数据(按截图还原) */
editTaskBookRows: [
{ jys: '军事81教研室', zt: '发布', sbsj: '', xydkcCount: 33, wczdsCount: 0 },
{ jys: '翻译室', zt: '发布', sbsj: '', xydkcCount: 2, wczdsCount: 0 },
{ jys: '军事共同教研室', zt: '上报', sbsj: '2019-12-13 10:39', xydkcCount: 97, wczdsCount: 8 },
{ jys: '作战保障教研室', zt: '发布', sbsj: '', xydkcCount: 47, wczdsCount: 0 },
{ jys: '军事10教研室', zt: '发布', sbsj: '', xydkcCount: 21, wczdsCount: 0 },
{ jys: '军事83', zt: '发布', sbsj: '', xydkcCount: 2, wczdsCount: 0 },
{ jys: '军事09教研室', zt: '发布', sbsj: '', xydkcCount: 52, wczdsCount: 0 }
]
}
},
mounted() {
// 默认选中第一行(高亮)
this.$nextTick(() => {
if (this.tableData.length) {
this.$refs.tableRef.setCurrentRow(this.tableData[0])
}
})
},
watch: {
// 弹窗打开时按模式初始化
dialogVisible(val) {
if (!val) return
this.form.xq = this.semester.name
if (this.dialogMode === 'edit') {
this.form.rwmc = (this.editRow && this.editRow.rwmc) || ''
this.form.cjsj = '2017-07-14 17:00'
this.form.fbsj = '2019-12-13 10:39'
this.form.jssj = ''
this.taskBookRows = [...this.editTaskBookRows]
} else {
this.form.rwmc = '新研究生教学任务'
this.form.cjsj = ''
this.form.fbsj = ''
this.form.jssj = ''
this.taskBookRows = []
}
this.activeTab = 'taskbook'
// 默认选中第一行(高亮)
this.$nextTick(() => {
if (this.taskBookRows.length && this.$refs.taskBookTable) {
this.$refs.taskBookTable.setCurrentRow(this.taskBookRows[0])
}
})
}
},
methods: {
// ==================== 表格 ====================
handleSelectionChange(rows) {
this.selectedRows = rows
},
handleCurrentChange(row) {
this.currentRow = row
},
/** 获取当前选中行,未选中时提示 */
getSelected() {
if (!this.currentRow) {
this.$message.warning('请先选择一条教学任务')
return null
}
return this.currentRow
},
// ==================== 操作按钮 ====================
/** 新建教学任务 */
handleAdd() {
this.dialogMode = 'create'
this.editRow = null
this.dialogVisible = true
},
/** 编辑教学任务 */
handleEdit() {
const row = this.getSelected()
if (!row) return
this.dialogMode = 'edit'
this.editRow = row
this.dialogVisible = true
},
/** 弹窗确认保存 */
handleDialogConfirm() {
// TODO: 调用保存接口
this.$message.success('已保存教学任务信息(前端演示)')
},
/** 删除教学任务 */
handleDelete() {
const row = this.getSelected()
if (!row) return
this.$confirm(`确定删除教学任务"${row.rwmc}"吗?删除后不可恢复。`, '删除确认', {
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
// TODO: 调用删除接口
this.$message.success('删除成功(前端演示)')
})
.catch(() => {})
},
/** 发布教学任务 */
handlePublish() {
const row = this.getSelected()
if (!row) return
// TODO: 调用发布接口
this.$message.success(`已发布教学任务:${row.rwmc}`)
},
/** 结束发布 */
handleEndPublish() {
const row = this.getSelected()
if (!row) return
// TODO: 调用结束发布接口
this.$message.success(`已结束发布:${row.rwmc}`)
},
/** 管理教学任务所含学员队 */
handleManageTeams() {
const row = this.getSelected()
if (!row) return
// TODO: 学员队管理弹窗,待接口接入
this.$message.success(`管理教学任务所含学员队:${row.rwmc}`)
},
// ==================== 弹窗 ====================
handleTaskBookSelection(rows) {
this.selectedTaskBookRows = rows
},
/** 打开教研室任务书详细(前端演示) */
handleTaskBookDetail() {
// TODO: 教研室任务书详细弹窗,待接口接入
},
/** 弹窗确定 */
handleConfirm() {
this.handleDialogConfirm()
this.dialogVisible = false
}
}
}
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
.task-plan-page {
// ========== 1. 学期信息区域 ==========
.search-card {
margin-bottom: 16px;
.search-form {
.el-form-item {
margin-bottom: 0;
margin-right: 24px;
}
}
}
// ========== 2. 列表标题 ==========
.list-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-left: 10px;
border-left: 4px solid var(--edu-green-primary);
}
// ========== 3. 工具栏 ==========
.list-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 16px;
.left-group,
.right-group {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
}
// ========== 4. 数据表格区域 ==========
.table-card {
.task-table {
width: 100%;
.status-text {
color: var(--edu-green-primary);
font-weight: 600;
}
// 当前行高亮
::v-deep(.el-table__body tr.current-row > td.el-table__cell) {
background-color: #e1f5ec;
}
::v-deep(.el-table__body tr.current-row:hover > td.el-table__cell) {
background-color: #e1f5ec;
}
}
}
// ========== 4. 教学任务信息弹窗 ==========
.teaching-task-dialog {
.basic-form {
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
margin-bottom: 4px;
}
.tt-tabs {
::v-deep(.el-tabs__header) {
margin-bottom: 12px;
}
.sub-toolbar {
margin-bottom: 12px;
}
.taskbook-table {
width: 100%;
// 当前行高亮
::v-deep(.el-table__body tr.current-row > td.el-table__cell) {
background-color: #e1f5ec;
}
::v-deep(.el-table__body tr.current-row:hover > td.el-table__cell) {
background-color: #e1f5ec;
}
}
.tab-empty {
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 13px;
}
}
}
}
</style>
@@ -0,0 +1,824 @@
<template>
<div class="app-container teacher-page">
<!-- 查询条件 -->
<el-card shadow="never" class="search-card">
<el-form :model="searchForm" label-width="110px" class="search-form">
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jysEnabled">教研室</el-checkbox>
</template>
<el-select v-model="searchForm.jys" placeholder="请选择教研室" class="w-full"
:disabled="!searchForm.jysEnabled" clearable>
<el-option v-for="item in jysOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jyxmEnabled">教员姓名</el-checkbox>
</template>
<el-input v-model="searchForm.jyxm" placeholder="请输入教员姓名" clearable
:disabled="!searchForm.jyxmEnabled" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.zcEnabled">职称</el-checkbox>
</template>
<el-select v-model="searchForm.zc" placeholder="请选择职称" class="w-full"
:disabled="!searchForm.zcEnabled" clearable>
<el-option v-for="item in zcOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.xbEnabled">性别</el-checkbox>
</template>
<el-select v-model="searchForm.xb" placeholder="请选择性别" class="w-full"
:disabled="!searchForm.xbEnabled" clearable>
<el-option v-for="item in xbOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.xlEnabled">学历</el-checkbox>
</template>
<el-select v-model="searchForm.xl" placeholder="请选择学历" class="w-full"
:disabled="!searchForm.xlEnabled" clearable>
<el-option v-for="item in xlOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.xwEnabled">学位</el-checkbox>
</template>
<el-select v-model="searchForm.xw" placeholder="请选择学位" class="w-full"
:disabled="!searchForm.xwEnabled" clearable>
<el-option v-for="item in xwOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item label="进校日期">
<div class="date-range">
<el-date-picker v-model="searchForm.jzrqStart" type="date" value-format="yyyy-MM-dd"
placeholder="开始日期" class="date-picker" />
<span class="date-sep">至</span>
<el-date-picker v-model="searchForm.jzrqEnd" type="date" value-format="yyyy-MM-dd"
placeholder="结束日期" class="date-picker" />
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.lxfsEnabled">联系方式</el-checkbox>
</template>
<el-input v-model="searchForm.lxfs" placeholder="请输入联系方式" clearable
:disabled="!searchForm.lxfsEnabled" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="0">
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.sfsjjzEnabled">是否实教教员</el-checkbox>
</template>
<el-radio-group v-model="searchForm.sfsjjz" :disabled="!searchForm.sfsjjzEnabled">
<el-radio v-for="item in sfsjjzOptions" :key="item" :label="item">{{ item }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template slot="label">
<el-checkbox v-model="searchForm.jylbEnabled">教员类别</el-checkbox>
</template>
<el-select v-model="searchForm.jylb" placeholder="请选择教员类别" clearable class="w-full"
:disabled="!searchForm.jylbEnabled">
<el-option v-for="item in jylbOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="search-tip">
注意:勾选"选择框"表示启用该项对应的查询条件。
</div>
<div class="search-actions">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</div>
</el-card>
<!-- 教员列表 -->
<div class="list-title">教员列表:</div>
<div class="list-toolbar">
<div class="left-group">
<el-button type="primary" @click="handleNew">新建</el-button>
<el-button type="primary" @click="handleDeleteSelected">删除所选</el-button>
<el-button type="primary" @click="handleImport">导入教员信息</el-button>
</div>
<div class="right-group">
<el-button type="primary" @click="handleDownloadTemplate">【教员数据文件模板】下载</el-button>
</div>
</div>
<el-card shadow="never" class="table-card">
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
@selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="jybh" label="教员编号" width="100" align="center" />
<el-table-column prop="jyxm" label="教员姓名" width="100" align="center" />
<el-table-column label="性别" width="60" align="center">
<template slot-scope="scope">{{ scope.row.xb === 0 ? '男' : scope.row.xb === 1 ? '女' : scope.row.xb }}</template>
</el-table-column>
<el-table-column prop="jysmc" label="教研室名称" align="center" />
<el-table-column prop="zc" label="职称" width="100" align="center" />
<el-table-column prop="xl" label="学历" width="120" align="center" />
<el-table-column prop="xw" label="学位" width="100" align="center" />
<el-table-column prop="cssj" label="出生时间" width="120" align="center" :formatter="formatDate" />
<el-table-column prop="sjh" label="联系方式" width="130" align="center" />
<el-table-column label="虚实类型" width="100" align="center">
<template slot-scope="scope">{{ scope.row.xslx === 0 ? '教学型' : scope.row.xslx === 1 ? '科研型' : scope.row.xslx }}</template>
</el-table-column>
<el-table-column prop="sfsjjz" label="是否实教教员" width="120" align="center" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" @click="handleViewAttribute(scope.row)">查看属性</el-button>
<el-button type="text" style="color: #e6a23c" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" style="color: #f56c6c" @click="handleToggleDisable(scope.row)">停用</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrap">
<el-pagination
:current-page="pagination.pageNum"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 教员属性详情对话框 -->
<el-dialog :visible.sync="attributeVisible" title="教员属性" width="720px" :destroy-on-close="true">
<div v-loading="attributeLoading || attributeSaving">
<!-- 查看模式 -->
<el-descriptions v-if="attributeMode === 'view'" :column="2" border>
<el-descriptions-item v-for="(value, key) in attributeData" :key="key" :label="ATTR_LABELS[key] || key"
label-align="right" align="center">
{{ value === null || value === undefined || value === '' ? '—' : value }}
</el-descriptions-item>
</el-descriptions>
<!-- 编辑模式 -->
<el-form v-else-if="attributeMode === 'edit'" label-width="120px" class="attribute-form">
<el-form-item v-for="(value, key) in attributeData" :key="key" :label="ATTR_LABELS[key] || key">
<el-input v-if="isEditableValue(value)" v-model="attributeData[key]" :disabled="key === 'jybh'"
:placeholder="key === 'jybh' ? '' : '请输入'" clearable />
<span v-else class="readonly-value">{{ typeof value === 'object' ? JSON.stringify(value) : value }}</span>
</el-form-item>
</el-form>
<el-empty v-else description="暂无属性数据" />
</div>
<div slot="footer" class="dialog-footer">
<template v-if="attributeMode === 'view'">
<el-button @click="attributeVisible = false">关闭</el-button>
<el-button type="primary" @click="handleStartEdit">编辑</el-button>
</template>
<template v-else>
<el-button @click="handleCancelEdit">取消</el-button>
<el-button type="primary" :loading="attributeSaving" @click="handleSaveAttribute">保存</el-button>
</template>
</div>
</el-dialog>
<!-- 新建教员对话框 -->
<el-dialog :visible.sync="addDialogVisible" title="新建教员" width="800px" :destroy-on-close="true">
<el-form :model="addForm" label-width="140px" class="add-form">
<el-divider content-position="left">教员基本信息</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教员编号" required><el-input v-model="addForm.teacher.jybh" placeholder="请输入" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教员姓名" required><el-input v-model="addForm.teacher.jyxm" placeholder="请输入" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教研室代号" required><el-input v-model="addForm.teacher.jysdh" placeholder="请输入" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" required>
<el-select v-model="addForm.teacher.xb" style="width:100%">
<el-option :value="0" label="男" /><el-option :value="1" label="女" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="身份证号" required><el-input v-model="addForm.teacher.sfzh" placeholder="请输入" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="离职状态" required>
<el-select v-model="addForm.teacher.lzzt" style="width:100%">
<el-option :value="0" label="在职" /><el-option :value="1" label="离职" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="专业技术职务等级" required><el-input v-model="addForm.teacher.zyjszwdj" placeholder="请输入" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="虚实类型" required>
<el-select v-model="addForm.teacher.xslx" style="width:100%">
<el-option :value="0" label="教学型" /><el-option :value="1" label="科研型" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教员类别" required>
<el-select v-model="addForm.teacher.jylb" style="width:100%">
<el-option v-for="item in jylbOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="在职类别" required><el-input v-model="addForm.teacher.zzlb" placeholder="请输入" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="导师类别" required><el-input v-model="addForm.teacher.dslb" placeholder="请输入" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学缘" required><el-input v-model="addForm.teacher.xy" placeholder="请输入" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="额外允许开班数" required>
<el-input-number v-model="addForm.teacher.ewyxkbs" :min="0" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="外训教员" required>
<el-select v-model="addForm.teacher.wxjy" style="width:100%">
<el-option :value="0" label="否" /><el-option :value="1" label="是" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="外训译员" required>
<el-select v-model="addForm.teacher.wxyy" style="width:100%">
<el-option :value="0" label="否" /><el-option :value="1" label="是" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="政治类教员" required>
<el-select v-model="addForm.teacher.zzljy" style="width:100%">
<el-option :value="0" label="否" /><el-option :value="1" label="是" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职称"><el-select v-model="addForm.teacher.zc" placeholder="选填" style="width:100%" clearable>
<el-option v-for="item in zcOptions" :key="item" :label="item" :value="item" /></el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="手机号"><el-input v-model="addForm.teacher.sjh" placeholder="选填" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="拼音"><el-input v-model="addForm.teacher.py" placeholder="选填" /></el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">教员属性</el-divider>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="学历" required>
<el-select v-model="addForm.attributes.xl" placeholder="请选择" style="width:100%">
<el-option v-for="item in xlOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学位" required>
<el-select v-model="addForm.attributes.xw" placeholder="请选择" style="width:100%">
<el-option v-for="item in xwOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教龄" required>
<el-input-number v-model="addForm.attributes.jl" :min="0" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教龄计算日期" required>
<el-date-picker v-model="addForm.attributes.jljsrq" type="datetime" value-format="yyyy-MM-ddTHH:mm:ss"
placeholder="选择日期" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="主任">
<el-select v-model="addForm.attributes.zr" style="width:100%">
<el-option :value="0" label="否" /><el-option :value="1" label="是" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="技术职务"><el-input v-model="addForm.attributes.jszw" placeholder="选填" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="技术职务时间">
<el-date-picker v-model="addForm.attributes.jszwsj" type="datetime" value-format="yyyy-MM-ddTHH:mm:ss"
placeholder="选填" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="技术等级"><el-input v-model="addForm.attributes.jsdj" placeholder="选填" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="技术等级时间">
<el-date-picker v-model="addForm.attributes.jsdjsj" type="datetime" value-format="yyyy-MM-ddTHH:mm:ss"
placeholder="选填" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="军衔文职"><el-input v-model="addForm.attributes.jxwz" placeholder="选填" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="军衔文职时间">
<el-date-picker v-model="addForm.attributes.jxwzsj" type="datetime" value-format="yyyy-MM-ddTHH:mm:ss"
placeholder="选填" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出生时间">
<el-date-picker v-model="addForm.attributes.cssj" type="datetime" value-format="yyyy-MM-ddTHH:mm:ss"
placeholder="选填" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="入伍工作时间">
<el-date-picker v-model="addForm.attributes.rwgzsj" type="datetime" value-format="yyyy-MM-ddTHH:mm:ss"
placeholder="选填" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="addDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="addSaving" @click="handleAddConfirm">确定</el-button>
</div>
</el-dialog>
<!-- 编辑教员基本信息对话框 -->
<el-dialog :visible.sync="editDialogVisible" title="编辑教员信息" width="780px" :destroy-on-close="true">
<div v-loading="editLoading">
<el-form :model="editForm" label-width="140px" class="add-form">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教员编号"><el-input v-model="editForm.jybh" disabled /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教员姓名" required><el-input v-model="editForm.jyxm" placeholder="请输入" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教研室代号" required><el-input v-model="editForm.jysdh" placeholder="请输入" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" required>
<el-select v-model="editForm.xb" style="width:100%">
<el-option :value="0" label="男" /><el-option :value="1" label="女" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="身份证号" required><el-input v-model="editForm.sfzh" placeholder="请输入" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="离职状态" required>
<el-select v-model="editForm.lzzt" style="width:100%">
<el-option :value="0" label="在职" /><el-option :value="1" label="离职" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="专业技术职务等级" required><el-input v-model="editForm.zyjszwdj" placeholder="请输入" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="虚实类型" required>
<el-select v-model="editForm.xslx" style="width:100%">
<el-option :value="0" label="教学型" /><el-option :value="1" label="科研型" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="教员类别" required>
<el-select v-model="editForm.jylb" style="width:100%">
<el-option v-for="item in jylbOptions" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="在职类别" required><el-input v-model="editForm.zzlb" placeholder="请输入" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="导师类别" required><el-input v-model="editForm.dslb" placeholder="请输入" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="学缘" required><el-input v-model="editForm.xy" placeholder="请输入" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="额外允许开班数" required>
<el-input-number v-model="editForm.ewyxkbs" :min="0" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="外训教员" required>
<el-select v-model="editForm.wxjy" style="width:100%">
<el-option :value="0" label="否" /><el-option :value="1" label="是" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="外训译员" required>
<el-select v-model="editForm.wxyy" style="width:100%">
<el-option :value="0" label="否" /><el-option :value="1" label="是" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="政治类教员" required>
<el-select v-model="editForm.zzljy" style="width:100%">
<el-option :value="0" label="否" /><el-option :value="1" label="是" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="职称"><el-select v-model="editForm.zc" style="width:100%" clearable>
<el-option v-for="item in zcOptions" :key="item" :label="item" :value="item" /></el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="手机号"><el-input v-model="editForm.sjh" placeholder="选填" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="拼音"><el-input v-model="editForm.py" placeholder="选填" /></el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="editSaving" @click="handleEditConfirm">保存</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
// 各下拉框选项:数据待后端接口返回后替换
const JYS_OPTIONS = [] // 教研室
const ZC_OPTIONS = ['教授', '副教授', '讲师', '助教'] // 职称
const XL_OPTIONS = ['博士', '硕士', '本科', '大专'] // 学历
const XW_OPTIONS = ['博士', '硕士', '学士'] // 学位
const JYLB_OPTIONS = [] // 教员类别
export default {
name: 'Teacher',
data() {
return {
// 查询条件
searchForm: {
jysEnabled: false, jys: '',
jyxmEnabled: false, jyxm: '',
zcEnabled: false, zc: '',
xbEnabled: false, xb: '',
xlEnabled: false, xl: '',
xwEnabled: false, xw: '',
jzrqStart: '', jzrqEnd: '',
lxfsEnabled: false, lxfs: '',
sfsjjzEnabled: false, sfsjjz: '是',
jylbEnabled: false, jylb: ''
},
options: {
jys: JYS_OPTIONS,
zc: ZC_OPTIONS,
xl: XL_OPTIONS,
xw: XW_OPTIONS,
jylb: JYLB_OPTIONS
},
// 教员列表
loading: false,
tableData: [],
selectedRows: [],
pagination: { pageNum: 1, pageSize: 10, total: 0 },
// 属性详情
attributeVisible: false,
attributeMode: 'view',
attributeData: {},
attributeLoading: false,
attributeSaving: false,
// 新建
addDialogVisible: false,
addSaving: false,
addForm: {
teacher: {
jybh: '', jyxm: '', jysdh: '', xb: '', sfzh: '', lzzt: '', zyjszwdj: '',
xslx: '', jylb: '', zzlb: '', dslb: '', xy: '', ewyxkbs: 0,
wxjy: '', wxyy: '', zzljy: '', zc: '', sjh: '', py: ''
},
attributes: {
xl: '', xw: '', jl: 0, jljsrq: '', zr: '', jszw: '', jszwsj: '',
jsdj: '', jsdjsj: '', jxwz: '', jxwzsj: '', cssj: '', rwgzsj: ''
}
},
// 编辑
editDialogVisible: false,
editLoading: false,
editSaving: false,
editForm: {}
}
},
computed: {
jysOptions() { return this.options.jys },
zcOptions() { return this.options.zc },
xlOptions() { return this.options.xl },
xwOptions() { return this.options.xw },
jylbOptions() { return this.options.jylb },
xbOptions() { return [{ value: 0, label: '男' }, { value: 1, label: '女' }] },
sfsjjzOptions() { return ['是', '否'] },
// 属性字段中文名映射
ATTR_LABELS() { return ATTR_LABELS_MAP }
},
created() {
// TODO: 待后端接口,拉取教研室等下 拉选项与教员列表
// this.loadOptions()
// this.getList()
},
methods: {
// 属性字段中文名(配合接口返回字段使用)
isEditableValue(value) {
// TODO: 根据字段类型判断是否可编辑
return typeof value !== 'object' || value === null
},
formatDate(row, column, cellValue) {
return cellValue ? String(cellValue).substring(0, 10) : ''
},
/** 查询 */
handleSearch() {
// TODO: 调用查询接口
},
/** 重置 */
handleReset() {
this.searchForm = {
jysEnabled: false, jys: '', jyxmEnabled: false, jyxm: '',
zcEnabled: false, zc: '', xbEnabled: false, xb: '',
xlEnabled: false, xl: '', xwEnabled: false, xw: '',
jzrqStart: '', jzrqEnd: '', lxfsEnabled: false, lxfs: '',
sfsjjzEnabled: false, sfsjjz: '是', jylbEnabled: false, jylb: ''
}
},
/** 新建 */
handleNew() {
// TODO: 打开新建对话框
},
/** 删除所选 */
handleDeleteSelected() {
// TODO: 批量删除
},
/** 导入教员信息 */
handleImport() {
// TODO: 导入
},
/** 下载模板 */
handleDownloadTemplate() {
// TODO: 模板下载
},
/** 表格多选 */
handleSelectionChange(rows) {
this.selectedRows = rows
},
/** 查看属性 */
handleViewAttribute(row) {
// TODO: 拉取行属性并打开
this.attributeVisible = true
},
/** 编辑基本信息 */
handleEdit(row) {
// TODO: 回填并打开编辑对话框
this.editDialogVisible = true
},
/** 停用 */
handleToggleDisable(row) {
// TODO: 停用接口
},
/** 分页 */
handleSizeChange(val) {
this.pagination.pageSize = val
// TODO: 重新查询
},
handleCurrentChange(val) {
this.pagination.pageNum = val
// TODO: 重新查询
},
/** 属性-开始编辑 */
handleStartEdit() {
this.attributeMode = 'edit'
},
/** 属性-取消编辑 */
handleCancelEdit() {
this.attributeMode = 'view'
},
/** 属性-保存 */
handleSaveAttribute() {
// TODO: 保存属性接口
this.attributeMode = 'view'
},
/** 新建-确认 */
handleAddConfirm() {
// TODO: 新增接口
this.addDialogVisible = false
},
/** 编辑-确认 */
handleEditConfirm() {
// TODO: 编辑接口
this.editDialogVisible = false
}
}
}
// 属性字段中文名映射(后续按接口字段补全)
const ATTR_LABELS_MAP = {
jybh: '教员编号',
jyxm: '教员姓名',
xb: '性别',
jysdh: '教研室代号',
jysmc: '教研室名称',
zc: '职称',
xl: '学历',
xw: '学位',
cssj: '出生时间',
sjh: '手机号',
xslx: '虚实类型',
sfsjjz: '是否实教教员'
}
</script>
<style scoped lang="scss">
.teacher-page {
.search-card {
margin-bottom: 16px;
}
.search-form {
.w-full {
width: 100%;
}
.date-range {
display: flex;
align-items: center;
width: 100%;
.date-picker {
flex: 1;
}
.date-sep {
margin: 0 8px;
color: #909399;
flex-shrink: 0;
}
}
}
.search-tip {
margin: 8px 0 16px;
font-size: 13px;
color: #909399;
}
.search-actions {
text-align: right;
border-top: 1px solid #ebeef5;
padding-top: 12px;
}
.list-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin: 8px 0 12px;
padding-left: 10px;
border-left: 4px solid var(--edu-green-primary);
}
.list-toolbar {
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
.left-group,
.right-group {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
}
.table-card {
margin-bottom: 16px;
}
.pagination-wrap {
padding: 14px 0 4px;
text-align: right;
}
.attribute-form {
.readonly-value {
color: #606266;
}
}
}
</style>