Files
education/frontend/src/views/teachBusiness/timetable/index.vue
T

655 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="app-container timetable-page">
<el-tabs v-model="activeTab" class="timetable-tabs">
<!-- 今日 -->
<el-tab-pane label="今日" name="today">
<div class="tab-body">
<!-- 日期导航 -->
<div class="nav-header">
<el-link type="primary" :underline="false" class="nav-link" @click="handlePrevDay">前一天</el-link>
<div class="nav-title">{{ todayTitle }}</div>
<el-link type="primary" :underline="false" class="nav-link" @click="handleNextDay">后一天</el-link>
</div>
<!-- 今日表格 -->
<div class="table-card">
<el-table :data="todayList" v-loading="todayLoading" stripe border>
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="上课时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtLessonTime(scope.row) }}</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column label="教学内容" min-width="180" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jxnr || '-' }}</template>
</el-table-column>
<el-table-column label="教学方法" width="130" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxff || '-' }}</template>
</el-table-column>
<el-table-column label="填写状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="txztType(scope.row.txzt)" size="mini">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-empty v-show="!todayLoading && !todayList.length" description="当日暂无教学计划数据" />
</div>
</div>
</el-tab-pane>
<!-- 本周 -->
<el-tab-pane label="本周" name="week">
<div class="tab-body">
<!-- 周次导航 -->
<div class="nav-header">
<el-link type="primary" :underline="false" class="nav-link" @click="handlePrevWeek">上一周</el-link>
<div class="nav-title">{{ weekTitle }}</div>
<el-link type="primary" :underline="false" class="nav-link" @click="handleNextWeek">下一周</el-link>
</div>
<!-- 本周表格 -->
<div class="table-card">
<el-table :data="weekList" v-loading="weekLoading" stripe border>
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="上课时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtLessonTime(scope.row) }}</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column label="教学内容" min-width="180" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jxnr || '-' }}</template>
</el-table-column>
<el-table-column label="教学方法" width="130" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxff || '-' }}</template>
</el-table-column>
<el-table-column label="填写状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="txztType(scope.row.txzt)" size="mini">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-empty v-show="!weekLoading && !weekList.length" description="本周暂无教学计划数据" />
<pagination
v-show="weekTotal > 0"
:total="weekTotal"
:page.sync="weekQuery.pageNum"
:limit.sync="weekQuery.pageSize"
@pagination="getWeekList"
/>
</div>
</div>
</el-tab-pane>
<!-- 三实 -->
<el-tab-pane label="三实" name="practice">
<div class="tab-body">
<!-- 周次导航 -->
<div class="nav-header">
<el-link type="primary" :underline="false" class="nav-link" @click="handlePrevWeek3">上一周</el-link>
<div class="nav-title">{{ practiceTitle }}</div>
<el-link type="primary" :underline="false" class="nav-link" @click="handleNextWeek3">下一周</el-link>
</div>
<el-alert
class="mb8"
type="info"
:closable="false"
show-icon
title="说明:后端教学实施计划课次接口未返回课程类型字段,无法按三实课类型筛选,当前展示本周全部教学实施计划课次。"
/>
<!-- 三实表格 -->
<div class="table-card">
<el-table :data="practiceList" v-loading="practiceLoading" stripe border>
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="上课时间" width="200" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ fmtLessonTime(scope.row) }}</template>
</el-table-column>
<el-table-column prop="kcmc" label="课程" min-width="150" show-overflow-tooltip header-align="center" />
<el-table-column prop="kcxh" label="课次序号" width="90" align="center" />
<el-table-column label="教学内容" min-width="180" show-overflow-tooltip header-align="center">
<template slot-scope="scope">{{ scope.row.jxnr || '-' }}</template>
</el-table-column>
<el-table-column label="教学方法" width="130" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jxff || '-' }}</template>
</el-table-column>
<el-table-column label="填写状态" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="txztType(scope.row.txzt)" size="mini">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-empty v-show="!practiceLoading && !practiceList.length" description="本周暂无三实教学计划数据" />
<pagination
v-show="practiceTotal > 0"
:total="practiceTotal"
:page.sync="practiceQuery.pageNum"
:limit.sync="practiceQuery.pageSize"
@pagination="getPracticeList"
/>
</div>
</div>
</el-tab-pane>
<!-- 班次 -->
<el-tab-pane label="班次" name="shift">
<div class="tab-body">
<!-- 查询条件 -->
<el-form :inline="true" class="shift-search">
<el-form-item label="队别班次">
<el-select
v-model="shiftXydbh"
placeholder="请选择队别班次"
clearable
filterable
class="shift-select"
>
<el-option v-for="t in teamOptions" :key="t.xydbh" :label="t.xydmc" :value="t.xydbh" />
</el-select>
</el-form-item>
<el-form-item label="年度">
<el-select v-model="shiftNd" placeholder="请选择年度" class="shift-select">
<el-option v-for="y in yearOptions" :key="y" :label="y" :value="y" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleShiftSearch">查询</el-button>
</el-form-item>
</el-form>
<!-- 标题 -->
<div class="shift-title">{{ shiftTitle }}</div>
<!-- 操作按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期教学实施计划</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表周次星期</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表班次视图</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表排课周含选修</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期教学实施计划含选修</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期课程表周次星期详细</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleNotProvided">学期成绩表</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="el-icon-download" size="mini" @click="handleNotProvided">课程列表另存Excel</el-button>
</el-col>
</el-row>
<!-- 班次课程表 -->
<div class="table-card">
<el-table v-loading="shiftLoading" :data="shiftList" border stripe>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="课程名称/简称" min-width="200" header-align="center">
<template slot-scope="scope">
<div>{{ scope.row._kcmc || '-' }}</div>
<div class="sub-text">{{ scope.row.jc || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="责任教员/课次" width="160" header-align="center">
<template slot-scope="scope">
<div>{{ scope.row._zrjy || '-' }}</div>
<div class="sub-text">{{ scope.row.kcxh || '-' }}次课</div>
</template>
</el-table-column>
<el-table-column prop="xydmc" label="班次" width="130" show-overflow-tooltip header-align="center" />
<el-table-column prop="xs" label="计划学时" width="90" align="center" />
<el-table-column prop="llxs" label="理论学时" width="90" align="center" />
<el-table-column prop="sjxs" label="实践学时" width="90" align="center" />
<el-table-column prop="cjfz" label="考核类型方式" min-width="110" show-overflow-tooltip header-align="center" />
<el-table-column label="实施教员" width="110" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row._ssjy || '-' }}</template>
</el-table-column>
<el-table-column label="人数/场地" width="130" header-align="center">
<template slot-scope="scope">
<div>{{ scope.row.rs || '-' }} </div>
<div class="sub-text">{{ scope.row.jsbh || '-' }}</div>
</template>
</el-table-column>
<el-table-column prop="xf" label="学分" width="80" align="center" />
<el-table-column label="实施计划变更" width="150" header-align="center">
<template slot-scope="scope">{{ fmtKbbdsj(scope.row.kbbdsj) }}</template>
</el-table-column>
</el-table>
<el-empty v-show="!shiftLoading && !shiftList.length" description="该班次暂无课程数据" />
<pagination
v-show="shiftTotal > 0"
:total="shiftTotal"
:page.sync="shiftQuery.pageNum"
:limit.sync="shiftQuery.pageSize"
@pagination="getShiftList"
/>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { listTeachingPlan } from '@/api/teachBusiness/courseRunning'
import { listStudentTeamTask } from '@/api/teachBusiness/studentTeamTask'
import { listTeam } from '@/api/studentRecords/team'
import { listKb } from '@/api/teachOffice/kb'
import { listTeacher } from '@/api/teachOffice/teacher'
import { listAllSemester } from '@/api/teachBusiness/semester'
// 节次 -> 节次区间映射
const JC_SECTION = {
1: '1-2节', 2: '3-4节', 3: '5-6节', 4: '7-8节', 5: '9-10节', 6: '11-12节'
}
const WEEK_DAY_NAMES = ['日', '一', '二', '三', '四', '五', '六']
function pad2(n) {
return n < 10 ? '0' + n : '' + n
}
function formatDate(d) {
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
}
function getMonday(d) {
const day = d.getDay() || 7
const monday = new Date(d)
monday.setDate(d.getDate() - day + 1)
return monday
}
export default {
name: 'Timetable',
data() {
return {
activeTab: 'today',
// 今日
todayDate: new Date(),
todayList: [],
todayLoading: false,
// 本周
weekStart: getMonday(new Date()),
weekList: [],
weekLoading: false,
weekTotal: 0,
weekQuery: { pageNum: 1, pageSize: 20 },
// 三实
practiceStart: getMonday(new Date()),
practiceList: [],
practiceLoading: false,
practiceTotal: 0,
practiceQuery: { pageNum: 1, pageSize: 20 },
// 班次
teamOptions: [],
shiftXydbh: '',
shiftNd: undefined,
shiftList: [],
shiftLoading: false,
shiftTotal: 0,
shiftQuery: { pageNum: 1, pageSize: 20 },
// 年度下拉数据来自 /semester/all(真实后端数据)
yearOptions: [],
defaultNd: undefined,
// 名称映射(真实接口数据,用于班次 tab 课程/教员编号转名称)
kbMap: {},
teacherMap: {}
}
},
computed: {
todayTitle() {
return '当前日期:' + formatDate(this.todayDate) + '时间顺序教学实施计划'
},
weekTitle() {
const end = new Date(this.weekStart)
end.setDate(end.getDate() + 6)
return formatDate(this.weekStart) + '(' + this.weekDayName(this.weekStart) + ') 至 ' +
formatDate(end) + '(' + this.weekDayName(end) + ') 教学实施计划'
},
practiceTitle() {
const end = new Date(this.practiceStart)
end.setDate(end.getDate() + 6)
return formatDate(this.practiceStart) + '(' + this.weekDayName(this.practiceStart) + ') 至 ' +
formatDate(end) + '(' + this.weekDayName(end) + ') 三实教学实施计划'
},
shiftTitle() {
const team = this.teamOptions.find(t => t.xydbh === this.shiftXydbh)
const name = (team && team.xydmc) || ''
return (this.shiftNd ? this.shiftNd + '年' : '') + name + '课程表'
}
},
created() {
this.loadYearOptions()
this.getTodayList()
this.getWeekList()
this.getPracticeList()
this.loadTeamOptions()
this.loadKbMap()
this.loadTeacherMap()
},
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.shiftNd) this.shiftNd = this.defaultNd
return arr
}).catch(() => {
this.yearOptions = []
this.defaultNd = undefined
return []
})
},
weekDayName(d) {
return WEEK_DAY_NAMES[d.getDay()]
},
formatSection(jc) {
if (jc === null || jc === undefined || jc === '') return '第?节'
return JC_SECTION[jc] || ('第' + jc + '节')
},
/** 后端 LocalDateTime -> 日期+周X+节次区间 */
fmtLessonTime(row) {
const rq = row.rq || ''
const date = String(rq).substring(0, 10)
if (!date) return '-'
const d = new Date(date.replace(/-/g, '/'))
const wn = WEEK_DAY_NAMES[d.getDay()]
return (date + ' 周' + wn + ' ' + this.formatSection(row.jc)).trim()
},
/** 后端 LocalDateTime -> yyyy-MM-dd HH:mm */
fmtKbbdsj(v) {
if (!v) return '-'
return String(v).replace('T', ' ').substring(0, 16)
},
txztText(txzt) {
if (txzt === 1) return '已填写'
if (txzt === 2) return '已提交'
return '未填写'
},
txztType(txzt) {
if (txzt === 1) return 'success'
if (txzt === 2) return 'primary'
return 'info'
},
/* ---------- 今日 ---------- */
handlePrevDay() {
const d = new Date(this.todayDate)
d.setDate(d.getDate() - 1)
this.todayDate = d
this.getTodayList()
},
handleNextDay() {
const d = new Date(this.todayDate)
d.setDate(d.getDate() + 1)
this.todayDate = d
this.getTodayList()
},
getTodayList() {
const day = formatDate(this.todayDate)
const params = {
rqStart: day + 'T00:00:00',
rqEnd: day + 'T23:59:59',
pageNum: 1,
pageSize: 200
}
this.todayLoading = true
listTeachingPlan(params).then(response => {
const data = response.data || {}
this.todayList = data.records || []
this.todayLoading = false
}).catch(() => {
this.todayList = []
this.todayLoading = false
})
},
/* ---------- 本周 ---------- */
handlePrevWeek() {
const d = new Date(this.weekStart)
d.setDate(d.getDate() - 7)
this.weekStart = d
this.weekQuery.pageNum = 1
this.getWeekList()
},
handleNextWeek() {
const d = new Date(this.weekStart)
d.setDate(d.getDate() + 7)
this.weekStart = d
this.weekQuery.pageNum = 1
this.getWeekList()
},
weekRange() {
const start = formatDate(this.weekStart) + 'T00:00:00'
const endDate = new Date(this.weekStart)
endDate.setDate(endDate.getDate() + 6)
const end = formatDate(endDate) + 'T23:59:59'
return { start: start, end: end }
},
getWeekList() {
const range = this.weekRange()
const params = {
rqStart: range.start,
rqEnd: range.end,
pageNum: this.weekQuery.pageNum,
pageSize: this.weekQuery.pageSize
}
this.weekLoading = true
listTeachingPlan(params).then(response => {
const data = response.data || {}
this.weekList = data.records || []
this.weekTotal = data.total || 0
this.weekLoading = false
}).catch(() => {
this.weekList = []
this.weekTotal = 0
this.weekLoading = false
})
},
/* ---------- 三实 ---------- */
handlePrevWeek3() {
const d = new Date(this.practiceStart)
d.setDate(d.getDate() - 7)
this.practiceStart = d
this.practiceQuery.pageNum = 1
this.getPracticeList()
},
handleNextWeek3() {
const d = new Date(this.practiceStart)
d.setDate(d.getDate() + 7)
this.practiceStart = d
this.practiceQuery.pageNum = 1
this.getPracticeList()
},
practiceRange() {
const start = formatDate(this.practiceStart) + 'T00:00:00'
const endDate = new Date(this.practiceStart)
endDate.setDate(endDate.getDate() + 6)
const end = formatDate(endDate) + 'T23:59:59'
return { start: start, end: end }
},
getPracticeList() {
// 后端 plan/list 接口未返回课程类型字段,无法按三实课筛选,与本周共用同一数据源
const range = this.practiceRange()
const params = {
rqStart: range.start,
rqEnd: range.end,
pageNum: this.practiceQuery.pageNum,
pageSize: this.practiceQuery.pageSize
}
this.practiceLoading = true
listTeachingPlan(params).then(response => {
const data = response.data || {}
this.practiceList = data.records || []
this.practiceTotal = data.total || 0
this.practiceLoading = false
}).catch(() => {
this.practiceList = []
this.practiceTotal = 0
this.practiceLoading = false
})
},
/* ---------- 班次 ---------- */
loadTeamOptions() {
listTeam({ pageNum: 1, pageSize: 1000 }).then(response => {
const data = response.data || {}
this.teamOptions = data.records || []
}).catch(() => {
this.teamOptions = []
})
},
loadKbMap() {
listKb({ pageNum: 1, pageSize: 1000 }).then(response => {
const data = response.data || {}
const map = {}
;(data.records || []).forEach(item => {
if (item.kbh) map[item.kbh] = item.kmc
})
this.kbMap = map
}).catch(() => {
this.kbMap = {}
})
},
loadTeacherMap() {
listTeacher({ pageNum: 1, pageSize: 1000 }).then(response => {
const data = response.data || {}
const map = {}
;(data.records || []).forEach(item => {
if (item.jybh) map[item.jybh] = item.jyxm
})
this.teacherMap = map
}).catch(() => {
this.teacherMap = {}
})
},
handleShiftSearch() {
if (!this.shiftXydbh) {
this.$message.warning('请先选择队别班次')
return
}
this.shiftQuery.pageNum = 1
this.getShiftList()
},
getShiftList() {
const params = {
xydbh: this.shiftXydbh,
nd: this.shiftNd,
pageNum: this.shiftQuery.pageNum,
pageSize: this.shiftQuery.pageSize
}
this.shiftLoading = true
listStudentTeamTask(params).then(response => {
const data = response.data || {}
const team = this.teamOptions.find(t => t.xydbh === this.shiftXydbh)
this.shiftList = (data.records || []).map(item => ({
...item,
xydmc: (team && team.xydmc) || item.xydbh,
_kcmc: this.kbMap[item.kbh] || item.kbh,
_zrjy: this.teacherMap[item.jysjhjybh] || item.jysjhjybh || '-',
_ssjy: this.teacherMap[item.jybh] || item.jybh || '-'
}))
this.shiftTotal = data.total || 0
this.shiftLoading = false
}).catch(() => {
this.shiftList = []
this.shiftTotal = 0
this.shiftLoading = false
})
},
/* ---------- 无后端接口的操作 ---------- */
handleNotProvided() {
this.$message.info('后端暂未提供该接口')
}
}
}
</script>
<style scoped lang="scss">
.timetable-page {
.timetable-tabs {
background: #fff;
padding: 0 16px;
border: 1px solid #ebeef5;
border-radius: 6px;
}
.tab-body {
padding-bottom: 16px;
}
.mb8 {
margin-bottom: 8px;
}
/* 日期/周次导航 */
.nav-header {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
margin-bottom: 12px;
padding: 14px 16px;
background: #fafafa;
border: 1px solid #ebeef5;
border-radius: 4px;
.nav-link {
font-size: 14px;
}
.nav-title {
font-size: 18px;
font-weight: 700;
color: #303133;
}
}
/* 表格区 */
.table-card {
.danger-text {
color: #f56c6c;
}
}
/* 班次 */
.shift-search {
margin-bottom: 8px;
}
.shift-select {
width: 220px;
}
.shift-title {
font-size: 17px;
font-weight: 700;
color: #303133;
text-align: center;
margin-bottom: 12px;
}
.sub-text {
font-size: 12px;
color: #909399;
margin-top: 2px;
}
}
</style>