forked from liweijie/education
940 lines
36 KiB
Vue
940 lines
36 KiB
Vue
<template>
|
||
<div class="page-container">
|
||
<!-- 统计卡片 -->
|
||
<div class="section-card">
|
||
<div class="stat-grid">
|
||
<div
|
||
v-for="card in statCards"
|
||
:key="card.title"
|
||
class="stat-card"
|
||
:style="{ background: card.gradient }"
|
||
tabindex="0"
|
||
role="button"
|
||
@click="handleStatClick(card)"
|
||
@keydown.enter="handleStatClick(card)"
|
||
>
|
||
<span class="stat-text">{{ card.title }}</span>
|
||
<span class="stat-value">{{ card.value }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 查询条件 -->
|
||
<div class="section-card">
|
||
<el-form :model="queryParams" :inline="true" size="small" class="search-form" @submit.native.prevent>
|
||
<el-form-item label="年度">
|
||
<el-select v-model="queryParams.nd" placeholder="请选择年度" clearable style="width: 140px">
|
||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="申请教员编号">
|
||
<el-input v-model="queryParams.sqjybh" placeholder="请输入申请教员编号" clearable style="width: 180px" @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="教研室审批状态">
|
||
<el-select v-model="queryParams.jyspzzt" placeholder="请选择" clearable style="width: 150px">
|
||
<el-option v-for="item in auditStatusOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
|
||
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<!-- 调课调整申请列表 -->
|
||
<div class="section-card">
|
||
<div class="section-header">
|
||
<div class="section-title">调停补课申请列表</div>
|
||
<div class="header-actions">
|
||
<el-button type="primary" plain icon="el-icon-plus" size="small" @click="openAdd">新增申请</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-table :data="tableData" v-loading="loading" size="small" border stripe class="compact-table" max-height="520">
|
||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||
<el-table-column prop="kcmc" label="课程名称" width="200" align="center" show-overflow-tooltip />
|
||
<el-table-column label="申请教员" width="150" align="center" show-overflow-tooltip>
|
||
<template slot-scope="scope">{{ scope.row.sqjyxm || scope.row.sqjybh || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="sy" label="调课事由" width="230" show-overflow-tooltip header-align="center" />
|
||
<el-table-column label="拟调整时间" width="200" align="center" show-overflow-tooltip>
|
||
<template slot-scope="scope">{{ fmtRqJc(scope.row.rq, scope.row.jc) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="nd" label="年度" width="150" align="center" />
|
||
<el-table-column label="教研室审批状态" width="150" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag :type="auditStatusTag(scope.row.jyspzzt).type" size="mini">{{ auditStatusTag(scope.row.jyspzzt).label }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="教研室查收状态" width="150" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag :type="receiveStatusTag(scope.row.jyscszt).type" size="mini">{{ receiveStatusTag(scope.row.jyscszt).label }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="创建时间" width="150" align="center">
|
||
<template slot-scope="scope">{{ fmtDateTime(scope.row.cjsj) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||
<template slot-scope="scope">
|
||
<div class="ops-cell">
|
||
<el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">详情</el-button>
|
||
<el-button v-if="showAuditMenu(scope.row)" type="text" size="mini" @click="openAudit(scope.row)">审批</el-button>
|
||
<el-button v-if="scope.row.sczt === 0 && scope.row.jyspzzt !== 1 && scope.row.jyspzzt !== 3" type="text" size="mini" class="danger-btn" @click="handleCancel(scope.row)">撤销</el-button>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-empty v-show="!loading && tableData.length === 0" description="暂无调整申请" />
|
||
<pagination
|
||
v-show="total > 0"
|
||
:total="total"
|
||
:page.sync="queryParams.pageNum"
|
||
:limit.sync="queryParams.pageSize"
|
||
@pagination="loadList"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 新增申请弹窗 -->
|
||
<el-dialog title="新增课表调整申请" :visible.sync="addVisible" width="640px" append-to-body>
|
||
<el-form ref="addForm" :model="addForm" :rules="addRules" label-width="110px">
|
||
<el-form-item label="年度" prop="nd">
|
||
<el-select v-model="addForm.nd" placeholder="请选择年度" style="width: 100%" @change="handleAddYearChange">
|
||
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="课次编号" prop="sskcbbh">
|
||
<el-input v-model="addForm.sskcbbh" placeholder="请从可供调课课次中选择" disabled />
|
||
<el-button type="primary" plain size="mini" class="pick-lesson-btn" :loading="lessonLoading" @click="loadAdjustableLessons">选择可供调课课次</el-button>
|
||
</el-form-item>
|
||
<el-form-item label="调课事由" prop="sy">
|
||
<el-input
|
||
v-model="addForm.sy"
|
||
type="textarea"
|
||
:rows="2"
|
||
maxlength="200"
|
||
show-word-limit
|
||
placeholder="请输入调课事由"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="拟调整日期" prop="rq">
|
||
<el-date-picker v-model="addForm.rq" type="date" value-format="yyyy-MM-dd" placeholder="请选择日期" style="width: 100%" />
|
||
</el-form-item>
|
||
<el-form-item label="拟调整节次" prop="jc">
|
||
<el-select v-model="addForm.jc" placeholder="请选择节次" style="width: 100%">
|
||
<el-option v-for="n in jcOptions" :key="n" :label="jcLabel(n)" :value="n" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="申请教员">
|
||
<el-input v-model="addForm.sqjybh" placeholder="由服务端根据当前登录人写入" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="学员队名称">
|
||
<el-input v-model="addForm.xydmc" placeholder="选择可供调课课次后自动带出" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="原教室编号">
|
||
<el-input v-model="addForm.yjsbh" placeholder="选择可供调课课次后自动带出" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="拟调整教室编号">
|
||
<el-select
|
||
v-model="addForm.xjsbh"
|
||
placeholder="请选择拟调整教室"
|
||
clearable
|
||
filterable
|
||
:loading="classroomLoading"
|
||
style="width: 100%"
|
||
>
|
||
<el-option
|
||
v-for="room in classroomOptions"
|
||
:key="room.jsbh"
|
||
:label="classroomOptionLabel(room)"
|
||
:value="room.jsbh"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="原上课时间">
|
||
<el-input v-model="addForm.ydsjap" placeholder="选择可供调课课次后自动带出" disabled />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="primary" :loading="addLoading" @click="submitAdd">提交申请</el-button>
|
||
<el-button @click="addVisible = false">取消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 可供调课课次选择弹窗 -->
|
||
<el-dialog title="选择可供调课课次" :visible.sync="lessonDialogVisible" width="760px" append-to-body>
|
||
<el-table :data="lessonOptions" v-loading="lessonLoading" size="small" border stripe max-height="420">
|
||
<el-table-column prop="kcmc" label="课程名称" min-width="140" align="center" show-overflow-tooltip />
|
||
<el-table-column label="原上课时间" width="160" align="center">
|
||
<template slot-scope="scope">{{ fmtRqJc(scope.row.rq, scope.row.jc) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="xydmc" label="学员队" min-width="120" align="center" show-overflow-tooltip />
|
||
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
|
||
<el-table-column prop="jsbh" label="教室编号" width="110" align="center" show-overflow-tooltip />
|
||
<el-table-column label="操作" width="90" align="center">
|
||
<template slot-scope="scope">
|
||
<el-button type="text" size="mini" @click="pickLesson(scope.row)">选择</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-empty v-show="!lessonLoading && lessonOptions.length === 0" description="暂无可供调课课次" />
|
||
</el-dialog>
|
||
|
||
<!-- 审批弹窗 -->
|
||
<el-dialog title="教研室审批" :visible.sync="auditVisible" width="520px" append-to-body>
|
||
<el-form ref="auditForm" :model="auditForm" label-width="100px">
|
||
<el-form-item label="审批结果" required>
|
||
<el-radio-group v-model="auditForm.spzt">
|
||
<el-radio :label="1">同意</el-radio>
|
||
<el-radio :label="2">发回</el-radio>
|
||
<el-radio :label="3">拒绝</el-radio>
|
||
</el-radio-group>
|
||
<div class="form-tip">点审批即已查收</div>
|
||
</el-form-item>
|
||
<el-form-item label="审批意见">
|
||
<el-input v-model="auditForm.fhyj" type="textarea" :rows="3" placeholder="请输入审批意见(发回/拒绝时必填)" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="primary" :loading="auditLoading" @click="submitAudit">确定</el-button>
|
||
<el-button @click="auditVisible = false">取消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 查看详情弹窗 -->
|
||
<el-dialog title="申请详情" :visible.sync="detailVisible" width="760px" append-to-body>
|
||
<div v-loading="detailLoading">
|
||
<el-descriptions v-if="detailData" :column="2" border size="small">
|
||
<el-descriptions-item label="课程名称">{{ detailData.kcmc || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="申请教员">{{ detailData.sqjyxm || detailData.sqjybh || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="调课事由" :span="2">{{ detailData.sy || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="原上课时间">{{ detailData.ydsjap || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="拟调整时间">{{ fmtRqJc(detailData.rq, detailData.jc) }}</el-descriptions-item>
|
||
<el-descriptions-item label="年度">{{ detailData.nd || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="创建时间">{{ fmtDateTime(detailData.cjsj) }}</el-descriptions-item>
|
||
<el-descriptions-item label="教研室审批状态">
|
||
<el-tag :type="auditStatusTag(detailData.jyspzzt).type" size="mini">{{ auditStatusTag(detailData.jyspzzt).label }}</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="教研室查收状态">
|
||
<el-tag :type="receiveStatusTag(detailData.jyscszt).type" size="mini">{{ receiveStatusTag(detailData.jyscszt).label }}</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="教研室审批人">{{ detailData.jyspzrbh || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="教研室审批时间">{{ fmtDateTime(detailData.jyspzsj) }}</el-descriptions-item>
|
||
<el-descriptions-item label="审批意见" :span="2">{{ detailData.fhyj || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="教研室查收人">{{ detailData.jyscsrbh || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="教研室查收时间">{{ fmtDateTime(detailData.jyscssj) }}</el-descriptions-item>
|
||
<el-descriptions-item label="教学内容" :span="2">{{ detailData.jxnr || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="教学要点" :span="2">{{ detailData.jxyd || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="教学方法" :span="2">{{ detailData.jxff || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="教学保障备注" :span="2">{{ detailData.jxbzbz || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="用车信息" :span="2">{{ detailData.ycxx || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="备注" :span="2">{{ detailData.bz || '-' }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
|
||
<!-- 新教室 -->
|
||
<div v-if="detailData && detailData.roomList && detailData.roomList.length" class="sub-section">
|
||
<div class="sub-title">新教室</div>
|
||
<el-table :data="detailData.roomList" size="small" border stripe>
|
||
<el-table-column prop="jsbh" label="教室编号" min-width="120" align="center" show-overflow-tooltip />
|
||
<el-table-column prop="jsmc" label="教室名称" min-width="140" align="center" show-overflow-tooltip />
|
||
<el-table-column prop="bz" label="备注" min-width="160" show-overflow-tooltip header-align="center" />
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 新辅助教员 -->
|
||
<div v-if="detailData && detailData.teacherList && detailData.teacherList.length" class="sub-section">
|
||
<div class="sub-title">新辅助教员</div>
|
||
<el-table :data="detailData.teacherList" size="small" border stripe>
|
||
<el-table-column prop="fzjybh" label="教员编号" min-width="120" align="center" show-overflow-tooltip />
|
||
<el-table-column prop="fzjyxm" label="教员姓名" min-width="120" align="center" show-overflow-tooltip />
|
||
<el-table-column label="是否主教员" width="100" align="center">
|
||
<template slot-scope="scope">{{ scope.row.zjy === 1 ? '是' : '否' }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="bz" label="备注" min-width="140" show-overflow-tooltip header-align="center" />
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 新学员队 -->
|
||
<div v-if="detailData && detailData.teamList && detailData.teamList.length" class="sub-section">
|
||
<div class="sub-title">新学员队</div>
|
||
<el-table :data="detailData.teamList" size="small" border stripe>
|
||
<el-table-column prop="xydbh" label="学员队编号" min-width="140" align="center" show-overflow-tooltip />
|
||
<el-table-column prop="xydmc" label="学员队名称" min-width="140" align="center" show-overflow-tooltip />
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 新保障明细 -->
|
||
<div v-if="detailData && detailData.supportList && detailData.supportList.length" class="sub-section">
|
||
<div class="sub-title">新保障明细</div>
|
||
<el-table :data="detailData.supportList" size="small" border stripe>
|
||
<el-table-column prop="bztgmxbh" label="保障编号" min-width="140" align="center" show-overflow-tooltip />
|
||
<el-table-column prop="bztgmxmc" label="保障名称" min-width="160" align="center" show-overflow-tooltip />
|
||
<el-table-column prop="sl" label="数量" width="80" align="center" />
|
||
<el-table-column prop="bz" label="备注" min-width="140" show-overflow-tooltip header-align="center" />
|
||
</el-table>
|
||
</div>
|
||
|
||
<!-- 机关审批记录 -->
|
||
<div v-if="detailData && detailData.auditList && detailData.auditList.length" class="sub-section">
|
||
<div class="sub-title">机关审批记录</div>
|
||
<el-table :data="detailData.auditList" size="small" border stripe>
|
||
<el-table-column prop="jgmc" label="机关" min-width="140" align="center" show-overflow-tooltip>
|
||
<template slot-scope="scope">{{ scope.row.jgmc || scope.row.jgbh || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="审批状态" width="100" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag :type="auditStatusTag(scope.row.spzt).type" size="mini">{{ auditStatusTag(scope.row.spzt).label }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="sprxm" label="审批人" min-width="110" align="center" show-overflow-tooltip>
|
||
<template slot-scope="scope">{{ scope.row.sprxm || scope.row.sprbh || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="审批时间" width="150" align="center">
|
||
<template slot-scope="scope">{{ fmtDateTime(scope.row.spsj) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="fhyj" label="发回意见" min-width="140" show-overflow-tooltip header-align="center" />
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="detailVisible = false">关闭</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
listScheduleAdjust,
|
||
listAdjustableLessons,
|
||
getScheduleAdjustDetail,
|
||
submitScheduleAdjust,
|
||
auditByTeachingOffice,
|
||
cancelScheduleAdjust
|
||
} from '@/api/teachBusiness/courseRunning'
|
||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||
import { listAllClassroom } from '@/api/teachBusiness/classroom'
|
||
import { mapGetters } from 'vuex'
|
||
|
||
// 节次 -> 节次区间映射
|
||
const JC_SECTION = {
|
||
1: '1-2节', 2: '3-4节', 3: '5-6节', 4: '7-8节', 5: '9-10节', 6: '11-12节'
|
||
}
|
||
|
||
// 审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝
|
||
const AUDIT_STATUS = {
|
||
0: { label: '未审批', type: 'info' },
|
||
1: { label: '已同意', type: 'success' },
|
||
2: { label: '已发回', type: 'warning' },
|
||
3: { label: '已拒绝', type: 'danger' }
|
||
}
|
||
|
||
// 查收状态:0-未查收,1-已查收
|
||
const RECEIVE_STATUS = {
|
||
0: { label: '未查收', type: 'info' },
|
||
1: { label: '已查收', type: 'success' }
|
||
}
|
||
|
||
export default {
|
||
name: 'TimetableAdjust',
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
tableData: [],
|
||
total: 0,
|
||
queryParams: {
|
||
nd: undefined,
|
||
sqjybh: undefined,
|
||
jyspzzt: undefined,
|
||
pageNum: 1,
|
||
pageSize: 20
|
||
},
|
||
// 年度下拉数据来自 /semester/all(真实后端数据)
|
||
yearOptions: [],
|
||
defaultNd: undefined,
|
||
jcOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||
auditStatusOptions: [
|
||
{ label: '未审批', value: 0 },
|
||
{ label: '已同意', value: 1 },
|
||
{ label: '已发回', value: 2 },
|
||
{ label: '已拒绝', value: 3 }
|
||
],
|
||
statCards: this.createStatCards([0, 0, 0, 0]),
|
||
// 新增申请
|
||
addVisible: false,
|
||
addLoading: false,
|
||
addForm: {},
|
||
addRules: {
|
||
nd: [{ required: true, message: '请选择年度', trigger: 'change' }],
|
||
sskcbbh: [{ required: true, message: '请选择可供调课课次', trigger: 'change' }],
|
||
sy: [{ required: true, message: '请输入调课事由', trigger: 'blur' }],
|
||
rq: [{ required: true, message: '请选择拟调整日期', trigger: 'change' }],
|
||
jc: [{ required: true, message: '请选择拟调整节次', trigger: 'change' }]
|
||
},
|
||
// 可供调课课次
|
||
lessonDialogVisible: false,
|
||
lessonLoading: false,
|
||
lessonOptions: [],
|
||
classroomLoading: false,
|
||
classroomOptions: [],
|
||
// 审批
|
||
auditVisible: false,
|
||
auditLoading: false,
|
||
auditForm: { ssdksqbh: '', spzt: 1, fhyj: '' },
|
||
// 详情
|
||
detailVisible: false,
|
||
detailLoading: false,
|
||
detailData: null
|
||
}
|
||
},
|
||
computed: {
|
||
...mapGetters(['roles']),
|
||
isTeacher() {
|
||
const r = this.roles || []
|
||
const hasTeacher = r.includes('TEACHER') || r.includes('teacher') || r.includes('ROLE_TEACHER')
|
||
const hasAdmin = r.includes('admin') || r.includes('ROLE_ADMIN')
|
||
return hasTeacher && !hasAdmin
|
||
}
|
||
},
|
||
created() {
|
||
this.loadYearOptions().then(() => {
|
||
this.loadStatistics()
|
||
this.loadList()
|
||
})
|
||
},
|
||
methods: {
|
||
/* ---------- 年度下拉(数据来自 /semester/all) ---------- */
|
||
loadYearOptions() {
|
||
return listAllSemester().then(response => {
|
||
const list = response.data || []
|
||
// 去重并按年度倒序
|
||
const map = {}
|
||
list.forEach(item => {
|
||
if (item.nd !== null && item.nd !== undefined && item.nd !== '') map[item.nd] = item
|
||
})
|
||
const arr = Object.keys(map).sort((a, b) => String(b).localeCompare(String(a)))
|
||
this.yearOptions = arr
|
||
// 默认年度:优先当前学期(dqxq=true),否则取最新年度
|
||
const current = list.find(item => item.dqxq === true)
|
||
this.defaultNd = (current && current.nd) || arr[0]
|
||
if (!this.queryParams.nd) this.queryParams.nd = this.defaultNd
|
||
return arr
|
||
}).catch(() => {
|
||
this.yearOptions = []
|
||
this.defaultNd = undefined
|
||
return []
|
||
})
|
||
},
|
||
/* ---------- 通用格式化 ---------- */
|
||
fmtDateTime(v) {
|
||
if (!v) return '-'
|
||
return String(v).replace('T', ' ').substring(0, 16)
|
||
},
|
||
fmtRqJc(rq, jc) {
|
||
if (!rq) return '-'
|
||
const date = String(rq).substring(0, 10)
|
||
const sec = jc === null || jc === undefined || jc === '' ? '第?节' : (JC_SECTION[jc] || '第' + jc + '节')
|
||
return date + ' ' + sec
|
||
},
|
||
jcLabel(n) {
|
||
return JC_SECTION[n] || ('第' + n + '节')
|
||
},
|
||
auditStatusTag(v) {
|
||
return AUDIT_STATUS[v] || { label: '-', type: 'info' }
|
||
},
|
||
receiveStatusTag(v) {
|
||
return RECEIVE_STATUS[v] || { label: '-', type: 'info' }
|
||
},
|
||
/* ---------- 列表查询 ---------- */
|
||
loadList() {
|
||
const params = {
|
||
pageNum: this.queryParams.pageNum,
|
||
pageSize: this.queryParams.pageSize
|
||
}
|
||
if (this.queryParams.nd) params.nd = this.queryParams.nd
|
||
if (this.queryParams.sqjybh) params.sqjybh = this.queryParams.sqjybh
|
||
if (this.queryParams.jyspzzt !== undefined && this.queryParams.jyspzzt !== '') {
|
||
params.jyspzzt = this.queryParams.jyspzzt
|
||
}
|
||
this.loading = true
|
||
listScheduleAdjust(params).then(response => {
|
||
const data = response.data || {}
|
||
this.tableData = data.records || []
|
||
this.total = data.total || 0
|
||
this.loading = false
|
||
}).catch(() => {
|
||
this.tableData = []
|
||
this.total = 0
|
||
this.loading = false
|
||
})
|
||
},
|
||
handleQuery() {
|
||
this.queryParams.pageNum = 1
|
||
this.loadList()
|
||
},
|
||
resetQuery() {
|
||
this.queryParams = {
|
||
nd: this.defaultNd,
|
||
sqjybh: undefined,
|
||
jyspzzt: undefined,
|
||
pageNum: 1,
|
||
pageSize: 20
|
||
}
|
||
this.loadList()
|
||
},
|
||
loadStatistics() {
|
||
const baseParams = {
|
||
nd: this.defaultNd,
|
||
pageNum: 1,
|
||
pageSize: 1
|
||
}
|
||
const statusFilters = [undefined, 0, 1, 3]
|
||
const requests = statusFilters.map(status => {
|
||
const params = Object.assign({}, baseParams)
|
||
if (status !== undefined) {
|
||
params.jyspzzt = status
|
||
}
|
||
return listScheduleAdjust(params)
|
||
.then(response => Number((response.data && response.data.total) || 0))
|
||
.catch(() => 0)
|
||
})
|
||
return Promise.all(requests).then(counts => {
|
||
this.statCards = this.createStatCards(counts)
|
||
})
|
||
},
|
||
createStatCards(counts) {
|
||
return [
|
||
{
|
||
title: '本页申请',
|
||
value: counts[0],
|
||
gradient: 'linear-gradient(135deg, #00875a 0%, #00b877 100%)',
|
||
filter: undefined
|
||
},
|
||
{
|
||
title: '教研室待审批',
|
||
value: counts[1],
|
||
gradient: 'linear-gradient(135deg, #e6a23c 0%, #f8b04c 100%)',
|
||
filter: 0
|
||
},
|
||
{
|
||
title: '教研室已同意',
|
||
value: counts[2],
|
||
gradient: 'linear-gradient(135deg, #409eff 0%, #66b1ff 100%)',
|
||
filter: 1
|
||
},
|
||
{
|
||
title: '教研室已拒绝',
|
||
value: counts[3],
|
||
gradient: 'linear-gradient(135deg, #f56c6c 0%, #f78989 100%)',
|
||
filter: 3
|
||
}
|
||
]
|
||
},
|
||
handleStatClick(card) {
|
||
this.queryParams.jyspzzt = card.filter
|
||
this.handleQuery()
|
||
},
|
||
/* ---------- 新增申请 ---------- */
|
||
openAdd() {
|
||
this.addForm = {
|
||
nd: this.defaultNd || this.yearOptions[0],
|
||
sskcbbh: '',
|
||
sy: '',
|
||
rq: '',
|
||
jc: 1,
|
||
sqjybh: '',
|
||
xydrwbh: '',
|
||
kbh: '',
|
||
kcbbh: '',
|
||
xydmc: '',
|
||
yjsbh: '',
|
||
xjsbh: '',
|
||
ydsjap: '',
|
||
jxnr: '',
|
||
jxyd: '',
|
||
jxff: '',
|
||
jxbzbz: '',
|
||
ycxx: '',
|
||
bz: '',
|
||
roomList: [],
|
||
teacherList: [],
|
||
teamList: [],
|
||
supportList: []
|
||
}
|
||
this.lessonOptions = []
|
||
this.loadClassroomOptions()
|
||
this.$nextTick(() => this.$refs.addForm && this.$refs.addForm.clearValidate())
|
||
this.addVisible = true
|
||
},
|
||
handleAddYearChange() {
|
||
Object.assign(this.addForm, {
|
||
sskcbbh: '',
|
||
rq: '',
|
||
jc: 1,
|
||
sqjybh: '',
|
||
xydrwbh: '',
|
||
kbh: '',
|
||
kcbbh: '',
|
||
xydmc: '',
|
||
yjsbh: '',
|
||
xjsbh: '',
|
||
ydsjap: '',
|
||
jxnr: '',
|
||
jxyd: '',
|
||
jxff: '',
|
||
jxbzbz: '',
|
||
ycxx: '',
|
||
bz: '',
|
||
roomList: [],
|
||
teacherList: [],
|
||
teamList: [],
|
||
supportList: []
|
||
})
|
||
this.lessonOptions = []
|
||
},
|
||
loadClassroomOptions() {
|
||
this.classroomLoading = true
|
||
return listAllClassroom().then(response => {
|
||
const classrooms = Array.isArray(response.data) ? response.data : []
|
||
this.classroomOptions = classrooms
|
||
.filter(room => room.jsbh && !this.isDisabledValue(room.ty))
|
||
.sort((first, second) => String(first.jsbh).localeCompare(String(second.jsbh), 'zh-CN'))
|
||
}).catch(() => {
|
||
this.classroomOptions = []
|
||
this.$message.warning('教室列表加载失败,请稍后重试')
|
||
}).finally(() => {
|
||
this.classroomLoading = false
|
||
})
|
||
},
|
||
classroomOptionLabel(room) {
|
||
return room.jsmc ? room.jsbh + ' · ' + room.jsmc : room.jsbh
|
||
},
|
||
isDisabledValue(value) {
|
||
return value === true || value === 1 || value === '1' || value === 'true'
|
||
},
|
||
loadAdjustableLessons() {
|
||
if (!this.addForm.nd) {
|
||
this.$message.warning('请先选择年度')
|
||
return
|
||
}
|
||
this.lessonLoading = true
|
||
listAdjustableLessons({ nd: this.addForm.nd }).then(response => {
|
||
const data = response.data || []
|
||
this.lessonOptions = Array.isArray(data) ? data : (data.records || [])
|
||
this.lessonLoading = false
|
||
this.lessonDialogVisible = true
|
||
}).catch(() => {
|
||
this.lessonLoading = false
|
||
this.$message.warning('加载可供调课课次失败,请稍后重试')
|
||
})
|
||
},
|
||
pickLesson(row) {
|
||
const lessonNumber = row.sskcbbh || row.bh || ''
|
||
if (!lessonNumber) {
|
||
this.$message.warning('当前课次缺少课次编号,无法选择')
|
||
return
|
||
}
|
||
const currentRoomNumber = row.jsbh || row.yjsbh || row.xjsbh || ''
|
||
const roomList = this.normalizeRoomList(row.roomList, currentRoomNumber)
|
||
const teamList = this.normalizeTeamList(row.teamList, row.xydbh)
|
||
Object.assign(this.addForm, {
|
||
sskcbbh: lessonNumber,
|
||
rq: row.rq ? String(row.rq).substring(0, 10) : '',
|
||
jc: row.jc === null || row.jc === undefined ? 1 : Number(row.jc),
|
||
nd: row.nd || this.addForm.nd,
|
||
sqjybh: row.sqjybh || '',
|
||
xydrwbh: row.xydrwbh || '',
|
||
kbh: row.kbh || '',
|
||
kcbbh: row.kcbbh || '',
|
||
xydmc: row.xydmc || '',
|
||
yjsbh: row.yjsbh || currentRoomNumber,
|
||
xjsbh: row.xjsbh || currentRoomNumber,
|
||
ydsjap: row.rq ? this.fmtRqJc(row.rq, row.jc) : (row.ydsjap || ''),
|
||
jxnr: row.jxnr || '',
|
||
jxyd: row.jxyd || '',
|
||
jxff: row.jxff || '',
|
||
jxbzbz: row.jxbzbz || '',
|
||
ycxx: row.ycxx || '',
|
||
bz: row.bz || '',
|
||
roomList: roomList,
|
||
teacherList: this.normalizeTeacherList(row.teacherList),
|
||
teamList: teamList,
|
||
supportList: this.normalizeSupportList(row.supportList)
|
||
})
|
||
;['jysdh', 'kcmc', 'sqjyxm'].forEach(key => {
|
||
if (row[key] !== undefined && row[key] !== null) this.addForm[key] = row[key]
|
||
})
|
||
this.lessonDialogVisible = false
|
||
this.$nextTick(() => this.$refs.addForm && this.$refs.addForm.validateField('sskcbbh'))
|
||
this.$message.success('已选择课次:' + (row.kcmc || lessonNumber))
|
||
},
|
||
submitAdd() {
|
||
this.$refs.addForm.validate(valid => {
|
||
if (!valid) return
|
||
const payload = {
|
||
sskcbbh: this.addForm.sskcbbh,
|
||
sy: this.addForm.sy,
|
||
rq: this.addForm.rq + 'T00:00:00',
|
||
jc: this.addForm.jc,
|
||
nd: this.addForm.nd,
|
||
roomList: this.buildSubmitRoomList(),
|
||
teacherList: this.normalizeTeacherList(this.addForm.teacherList),
|
||
teamList: this.normalizeTeamList(this.addForm.teamList),
|
||
supportList: this.normalizeSupportList(this.addForm.supportList)
|
||
}
|
||
const optionalFields = [
|
||
'sqjybh', 'xydrwbh', 'kbh', 'kcbbh', 'xjsbh', 'jxnr', 'jxyd',
|
||
'jxff', 'jxbzbz', 'ycxx', 'bz'
|
||
]
|
||
optionalFields.forEach(key => {
|
||
if (this.addForm[key]) payload[key] = this.addForm[key]
|
||
})
|
||
this.addLoading = true
|
||
submitScheduleAdjust(payload).then(() => {
|
||
this.addLoading = false
|
||
this.addVisible = false
|
||
this.$message.success('申请提交成功')
|
||
this.loadList()
|
||
}).catch(() => {
|
||
this.addLoading = false
|
||
})
|
||
})
|
||
},
|
||
normalizeRoomList(roomList, fallbackRoomNumber) {
|
||
const normalizedList = (Array.isArray(roomList) ? roomList : [])
|
||
.filter(item => item && item.jsbh)
|
||
.map(item => ({
|
||
jsbh: item.jsbh,
|
||
bz: item.bz || ''
|
||
}))
|
||
if (normalizedList.length === 0 && fallbackRoomNumber) {
|
||
normalizedList.push({ jsbh: fallbackRoomNumber, bz: '' })
|
||
}
|
||
return normalizedList
|
||
},
|
||
normalizeTeacherList(teacherList) {
|
||
return (Array.isArray(teacherList) ? teacherList : [])
|
||
.filter(item => item && item.fzjybh)
|
||
.map(item => ({ fzjybh: item.fzjybh }))
|
||
},
|
||
normalizeTeamList(teamList, fallbackTeamNumber) {
|
||
const normalizedList = (Array.isArray(teamList) ? teamList : [])
|
||
.filter(item => item && item.xydbh)
|
||
.map(item => ({ xydbh: item.xydbh }))
|
||
if (normalizedList.length === 0 && fallbackTeamNumber) {
|
||
normalizedList.push({ xydbh: fallbackTeamNumber })
|
||
}
|
||
return normalizedList
|
||
},
|
||
normalizeSupportList(supportList) {
|
||
return (Array.isArray(supportList) ? supportList : [])
|
||
.filter(item => item && item.bztgmxbh)
|
||
.map(item => ({
|
||
bztgmxbh: item.bztgmxbh,
|
||
sl: item.sl === null || item.sl === undefined ? undefined : Number(item.sl)
|
||
}))
|
||
},
|
||
buildSubmitRoomList() {
|
||
if (!this.addForm.xjsbh) return []
|
||
const currentRoomList = this.normalizeRoomList(this.addForm.roomList)
|
||
if (this.addForm.xjsbh === this.addForm.yjsbh && currentRoomList.length > 0) {
|
||
return currentRoomList
|
||
}
|
||
const currentRoom = currentRoomList.find(item => item.jsbh === this.addForm.xjsbh)
|
||
return [{
|
||
jsbh: this.addForm.xjsbh,
|
||
bz: currentRoom ? currentRoom.bz : ''
|
||
}]
|
||
},
|
||
/* ---------- 审批 ---------- */
|
||
showAuditMenu(row) {
|
||
// 教员角色不展示审批入口;已拒绝状态不展示审批
|
||
if (this.isTeacher) return false
|
||
return row.sczt === 0 && (row.jyspzzt === 0 || row.jyspzzt === 2)
|
||
},
|
||
openAudit(row) {
|
||
this.auditForm = {
|
||
ssdksqbh: row.ssdksqbh,
|
||
spzt: 1,
|
||
fhyj: ''
|
||
}
|
||
this.auditVisible = true
|
||
},
|
||
submitAudit() {
|
||
const f = this.auditForm
|
||
if (f.spzt !== 1 && !f.fhyj) {
|
||
this.$message.warning('发回/拒绝时请填写审批意见')
|
||
return
|
||
}
|
||
const payload = {
|
||
ssdksqbh: f.ssdksqbh,
|
||
spzt: f.spzt,
|
||
fhyj: f.fhyj
|
||
}
|
||
this.auditLoading = true
|
||
auditByTeachingOffice(payload).then(() => {
|
||
this.auditLoading = false
|
||
this.auditVisible = false
|
||
this.$message.success('审批成功')
|
||
this.loadList()
|
||
}).catch(() => {
|
||
this.auditLoading = false
|
||
})
|
||
},
|
||
/* ---------- 撤销 ---------- */
|
||
handleCancel(row) {
|
||
this.$confirm('确认撤销该调课申请?', '提示', { type: 'warning' })
|
||
.then(() => {
|
||
cancelScheduleAdjust({ ssdksqbh: row.ssdksqbh }).then(() => {
|
||
this.$message.success('撤销成功')
|
||
this.loadList()
|
||
}).catch(() => {})
|
||
})
|
||
.catch(() => {})
|
||
},
|
||
/* ---------- 详情 ---------- */
|
||
openDetail(row) {
|
||
this.detailVisible = true
|
||
this.detailLoading = true
|
||
this.detailData = null
|
||
getScheduleAdjustDetail(row.ssdksqbh).then(response => {
|
||
this.detailData = response.data || {}
|
||
this.detailLoading = false
|
||
}).catch(() => {
|
||
this.detailData = null
|
||
this.detailLoading = false
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.page-container {
|
||
padding: 16px;
|
||
|
||
.section-card {
|
||
background: #fff;
|
||
border: 1px solid #ebeef5;
|
||
border-radius: 6px;
|
||
padding: 16px 20px;
|
||
margin-bottom: 16px;
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #303133;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
margin-bottom: 16px;
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 统计卡片 */
|
||
.stat-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 16px;
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.stat-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
padding: 16px 20px;
|
||
border-radius: 6px;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||
|
||
&:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||
}
|
||
|
||
.stat-text {
|
||
font-size: 13px;
|
||
opacity: 0.92;
|
||
}
|
||
|
||
.stat-value {
|
||
margin-top: 8px;
|
||
font-size: 26px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
}
|
||
}
|
||
|
||
/* 查询区域 */
|
||
.search-form {
|
||
margin-bottom: -16px;
|
||
|
||
::v-deep .el-form-item {
|
||
margin-bottom: 16px;
|
||
}
|
||
}
|
||
|
||
.compact-table {
|
||
width: 100%;
|
||
}
|
||
|
||
.danger-btn {
|
||
color: #f56c6c;
|
||
|
||
&:hover {
|
||
color: #f78989;
|
||
}
|
||
}
|
||
|
||
.ops-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
gap: 0 4px;
|
||
}
|
||
|
||
.form-tip {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
line-height: 1.4;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.pick-lesson-btn {
|
||
margin-top: 6px;
|
||
width: 100%;
|
||
}
|
||
|
||
.sub-section {
|
||
margin-top: 16px;
|
||
|
||
.sub-title {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #303133;
|
||
margin-bottom: 8px;
|
||
}
|
||
}
|
||
}
|
||
|
||
@media (max-width: 992px) {
|
||
.page-container .stat-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
</style>
|