forked from liweijie/education
前端代码
This commit is contained in:
@@ -0,0 +1,719 @@
|
||||
<template>
|
||||
<div class="school-calendar-editor">
|
||||
<!-- 标题栏 -->
|
||||
<div class="sce-titlebar">
|
||||
<div class="sce-title-left">
|
||||
<span class="sce-semester-name">{{ semesterName }}</span>
|
||||
</div>
|
||||
<div class="sce-title-center">
|
||||
<span class="sce-date-range">从 {{ dateRangeText }} ,共 {{ totalWeeks }} 周</span>
|
||||
</div>
|
||||
<div class="sce-title-right">
|
||||
<i class="el-icon-close" title="关闭" @click="handleClose" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 设置面板 -->
|
||||
<div class="sce-panel">
|
||||
<div class="sce-panel-row">
|
||||
<span class="sce-label">事件名称:</span>
|
||||
<el-input v-model="toolbar.eventName" size="small" class="sce-event-input" placeholder="请输入校历事件名称" />
|
||||
<el-checkbox v-model="boldDisplay" class="sce-cb">加粗显示</el-checkbox>
|
||||
<el-checkbox v-model="toolbar.schedulable" class="sce-cb">可排课</el-checkbox>
|
||||
<el-checkbox v-model="toolbar.mainCourse" class="sce-cb">正课</el-checkbox>
|
||||
<el-checkbox v-model="toolbar.remarkShow" class="sce-cb">备注显示</el-checkbox>
|
||||
<span class="sce-label">备注:</span>
|
||||
<el-input
|
||||
v-model="toolbar.remark"
|
||||
size="small"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
class="sce-remark-input"
|
||||
resize="none"
|
||||
placeholder="备注信息"
|
||||
/>
|
||||
</div>
|
||||
<div class="sce-panel-row">
|
||||
<el-button type="primary" size="small" icon="el-icon-check" @click="applyEvent">设定</el-button>
|
||||
<el-button type="danger" size="small" icon="el-icon-delete" @click="deleteEvent">删除</el-button>
|
||||
<el-button size="small" icon="el-icon-brush" @click="absorbEvent">吸取</el-button>
|
||||
<el-button size="small" icon="el-icon-refresh" @click="refreshGrid">刷新</el-button>
|
||||
<span class="sce-divider" />
|
||||
<el-checkbox v-model="show78" class="sce-cb">显示78节</el-checkbox>
|
||||
<el-checkbox v-model="showNight" class="sce-cb">显示晚上</el-checkbox>
|
||||
<el-checkbox v-model="showLateNight" class="sce-cb">显示夜间</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时间编排区域 -->
|
||||
<div ref="gridWrap" class="sce-grid-wrap" @mousedown="onGridMouseDown">
|
||||
<table class="sce-grid" :class="{ 'is-selecting': selecting }">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sce-corner sce-th-weekno" :rowspan="2">周次</th>
|
||||
<th class="sce-corner sce-th-weekrange" :rowspan="2">日期段</th>
|
||||
<th v-for="col in visibleColumns" :key="'h1-' + col.colIndex" :colspan="1" class="sce-th-day">
|
||||
{{ weekDayLabel(col.dayIndex) }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th v-for="col in visibleColumns" :key="'h2-' + col.colIndex" class="sce-th-slot">
|
||||
{{ col.slotLabel }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(week, wIdx) in weeks" :key="'w-' + wIdx">
|
||||
<td class="sce-week-cell sce-weekno-cell">{{ wIdx + 1 }}</td>
|
||||
<td class="sce-week-cell sce-weekrange-cell">{{ week.rangeText }}</td>
|
||||
<td
|
||||
v-for="col in visibleColumns"
|
||||
:key="'c-' + wIdx + '-' + col.colIndex"
|
||||
class="sce-cell"
|
||||
:class="cellClass(wIdx, col)"
|
||||
:data-key="cellKey(wIdx, col.colIndex)"
|
||||
@dblclick="onCellDblClick(wIdx, col)"
|
||||
>
|
||||
<span v-if="getEvent(wIdx, col.colIndex)" class="sce-event-name" :class="{ 'is-bold': boldDisplay }">{{ getEvent(wIdx, col.colIndex).name }}</span>
|
||||
<span v-else class="sce-date-num">{{ dateNumberOf(wIdx, col.dayIndex) }}</span>
|
||||
<span
|
||||
v-if="getEvent(wIdx, col.colIndex) && !getEvent(wIdx, col.colIndex).schedulable"
|
||||
class="sce-flag sce-flag-noschedule"
|
||||
title="全校不允许排课"
|
||||
/>
|
||||
<span
|
||||
v-if="getEvent(wIdx, col.colIndex) && getEvent(wIdx, col.colIndex).mainCourse"
|
||||
class="sce-flag sce-flag-maincourse"
|
||||
title="正课"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="sce-status">
|
||||
已选 {{ selectedKeys.length }} 个时间格
|
||||
<span v-if="absorbPreview" class="sce-absorb-tip">已吸取:{{ absorbPreview }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSemester } from '@/api/teachBusiness/semester'
|
||||
import { listXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
|
||||
|
||||
// 节次定义:12/34/56 常显,78/晚上/夜间由开关控制可见
|
||||
const SLOT_DEFS = [
|
||||
{ key: '12', label: '1-2', ctrl: null },
|
||||
{ key: '34', label: '3-4', ctrl: null },
|
||||
{ key: '56', label: '5-6', ctrl: null },
|
||||
{ key: '78', label: '7-8', ctrl: 'show78' },
|
||||
{ key: 'night', label: '晚上', ctrl: 'showNight' },
|
||||
{ key: 'late', label: '夜间', ctrl: 'showLateNight' }
|
||||
]
|
||||
|
||||
// 前端节次 key -> 后端 xqxlb.courseClass 节次范围(如 12 节 -> "1-2"、夜间 -> "11-12")
|
||||
const SLOT_COURSE_MAP = {
|
||||
'12': '1-2',
|
||||
'34': '3-4',
|
||||
'56': '5-6',
|
||||
'78': '7-8',
|
||||
night: '9-10',
|
||||
late: '11-12'
|
||||
}
|
||||
|
||||
const WEEK_DAYS = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
|
||||
function pad2(n) {
|
||||
return n < 10 ? '0' + n : '' + n
|
||||
}
|
||||
|
||||
function fmtDate(d) {
|
||||
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SchoolCalendarEditor',
|
||||
props: {
|
||||
nd: { type: [String, Number], default: '' },
|
||||
semesterName: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 学期日期范围
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
totalWeeks: 0,
|
||||
dateRangeText: '',
|
||||
weeks: [],
|
||||
// 加粗显示:全局开关,勾选后所有有事件的格子事件名加粗(纯前端效果,不持久化)
|
||||
boldDisplay: false,
|
||||
// 显示开关(默认不勾选,每天仅显示 1-2/3-4/5-6 三个节次)
|
||||
show78: false,
|
||||
showNight: false,
|
||||
showLateNight: false,
|
||||
// 工具栏表单
|
||||
toolbar: {
|
||||
eventName: '',
|
||||
schedulable: false,
|
||||
mainCourse: false,
|
||||
remarkShow: false,
|
||||
remark: ''
|
||||
},
|
||||
// 事件存储:key = cellKey -> event
|
||||
events: {},
|
||||
// 选择集(用数组以保证 Vue2 响应式)
|
||||
selectedKeys: [],
|
||||
// 拖拽状态
|
||||
selecting: false,
|
||||
dragStart: null,
|
||||
dragStartSelected: false, // 按下起点在按下前是否已选中
|
||||
moved: false, // 是否发生移动(区分点击与拖拽)
|
||||
dragAddMode: false, // ctrl 拖拽为追加
|
||||
// 窗口控制
|
||||
absorbPreview: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 当前可见列(由开关过滤,默认每天三个节次)
|
||||
visibleColumns() {
|
||||
const cols = []
|
||||
let colIndex = 0
|
||||
WEEK_DAYS.forEach((dayLabel, dayIndex) => {
|
||||
SLOT_DEFS.forEach(slot => {
|
||||
if (slot.ctrl && !this[slot.ctrl]) return
|
||||
cols.push({ colIndex: colIndex, dayIndex: dayIndex, slotKey: slot.key, slotLabel: slot.label, dayLabel: dayLabel })
|
||||
colIndex++
|
||||
})
|
||||
})
|
||||
return cols
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
nd: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (val) this.loadSemester()
|
||||
}
|
||||
},
|
||||
visibleColumns() {
|
||||
// 列变化后清空无效选择(列索引含义会变)
|
||||
this.selectedKeys = []
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.removeGlobalListeners()
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 数据加载 ---------- */
|
||||
loadSemester() {
|
||||
if (!this.nd) return
|
||||
getSemester(this.nd)
|
||||
.then(res => {
|
||||
const data = res.data || res || {}
|
||||
const kx = (data.kxrq || '').toString().slice(0, 10)
|
||||
const jx = (data.jsrq || '').toString().slice(0, 10)
|
||||
if (kx && jx) {
|
||||
this.startDate = new Date(kx.replace(/-/g, '/'))
|
||||
this.endDate = new Date(jx.replace(/-/g, '/'))
|
||||
this.buildWeeks()
|
||||
}
|
||||
this.loadXqxlbEvents()
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.warning('学期详情获取失败,请确认学期数据')
|
||||
})
|
||||
},
|
||||
// 从后端拉取本学期校历假期,渲染到对应时间格(nd 为 6 位学期代号,如 202601)
|
||||
loadXqxlbEvents() {
|
||||
listXqxlb({ nd: this.nd }).then(res => {
|
||||
const data = res.data
|
||||
const list = Array.isArray(data) ? data : (data && data.records) || []
|
||||
return list
|
||||
}).then(list => {
|
||||
const loaded = {}
|
||||
list.forEach(item => {
|
||||
const pos = this.locateXqxlb(item)
|
||||
if (pos) {
|
||||
loaded[pos.key] = {
|
||||
bh: item.bh,
|
||||
name: item.jqmc || '',
|
||||
schedulable: !!item.kpk,
|
||||
mainCourse: !!item.zdpk,
|
||||
remarkShow: !!item.bzxs,
|
||||
remark: item.bz || ''
|
||||
}
|
||||
}
|
||||
})
|
||||
// 合并到现有格子(后端数据覆盖已有状态)
|
||||
this.events = Object.assign({}, this.events, loaded)
|
||||
}).catch(() => {
|
||||
this.$message.warning('校历事件加载失败')
|
||||
})
|
||||
},
|
||||
// 根据假期记录反推时间格 key(jqsj 日期 + courseClass 节次)
|
||||
locateXqxlb(item) {
|
||||
if (!this.startDate || !item.jqsj) return null
|
||||
const d = new Date(String(item.jqsj).slice(0, 10).replace(/-/g, '/'))
|
||||
if (isNaN(d.getTime())) return null
|
||||
const offset = Math.round((d - this.startDate) / (24 * 3600 * 1000))
|
||||
if (offset < 0) return null
|
||||
const wIdx = Math.floor(offset / 7)
|
||||
const dayIndex = offset % 7
|
||||
const slotKey = this.courseClassToSlotKey(item.courseClass)
|
||||
if (slotKey === null) return null
|
||||
const colIndex = this.colIndexOf(dayIndex, slotKey)
|
||||
if (colIndex < 0) return null
|
||||
return { key: this.cellKey(wIdx, colIndex) }
|
||||
},
|
||||
// 后端 courseClass 节次范围 -> 前端节次 key
|
||||
courseClassToSlotKey(courseClass) {
|
||||
if (!courseClass) return null
|
||||
const s = String(courseClass).trim()
|
||||
for (const key in SLOT_COURSE_MAP) {
|
||||
if (s === SLOT_COURSE_MAP[key]) return key
|
||||
}
|
||||
// 兼容 "910"/"1112" 等紧凑写法
|
||||
if (s === '910') return 'night'
|
||||
if (s === '1112') return 'late'
|
||||
return null
|
||||
},
|
||||
buildWeeks() {
|
||||
if (!this.startDate || !this.endDate) return
|
||||
const start = new Date(this.startDate)
|
||||
const end = new Date(this.endDate)
|
||||
const days = Math.round((end - start) / (24 * 3600 * 1000)) + 1
|
||||
this.totalWeeks = Math.ceil(days / 7)
|
||||
this.dateRangeText = fmtDate(start) + ' 到 ' + fmtDate(end)
|
||||
const weeks = []
|
||||
for (let w = 0; w < this.totalWeeks; w++) {
|
||||
const wkStart = new Date(start)
|
||||
wkStart.setDate(start.getDate() + w * 7)
|
||||
const wkEnd = new Date(wkStart)
|
||||
wkEnd.setDate(wkStart.getDate() + 6)
|
||||
weeks.push({
|
||||
rangeText: pad2(wkStart.getMonth() + 1) + '-' + pad2(wkStart.getDate()) +
|
||||
'至' + pad2(wkEnd.getMonth() + 1) + '-' + pad2(wkEnd.getDate())
|
||||
})
|
||||
}
|
||||
this.weeks = weeks
|
||||
},
|
||||
colIndexOf(dayIndex, slotKey) {
|
||||
const col = this.visibleColumns.find(c => c.dayIndex === dayIndex && c.slotKey === slotKey)
|
||||
return col ? col.colIndex : -1
|
||||
},
|
||||
/* ---------- 单元格工具 ---------- */
|
||||
cellKey(wIdx, colIndex) {
|
||||
return 'w' + wIdx + '-c' + colIndex
|
||||
},
|
||||
getEvent(wIdx, colIndex) {
|
||||
return this.events[this.cellKey(wIdx, colIndex)] || null
|
||||
},
|
||||
weekDayLabel(dayIndex) {
|
||||
return WEEK_DAYS[dayIndex]
|
||||
},
|
||||
// 单元格内显示的日期数字(如 0703)
|
||||
dateNumberOf(wIdx, dayIndex) {
|
||||
if (!this.startDate) return ''
|
||||
const d = new Date(this.startDate)
|
||||
d.setDate(this.startDate.getDate() + wIdx * 7 + dayIndex)
|
||||
return pad2(d.getMonth() + 1) + pad2(d.getDate())
|
||||
},
|
||||
cellClass(wIdx, col) {
|
||||
const classes = []
|
||||
// 星期交替背景(单双日不同底色)
|
||||
if (col.dayIndex % 2 === 0) classes.push('sce-cell-odd')
|
||||
else classes.push('sce-cell-even')
|
||||
if (this.selectedKeys.includes(this.cellKey(wIdx, col.colIndex))) classes.push('is-selected')
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
classes.push('has-event')
|
||||
if (!ev.schedulable) classes.push('no-schedule')
|
||||
}
|
||||
return classes
|
||||
},
|
||||
/* ---------- 点击 / 拖拽多选 ---------- */
|
||||
onGridMouseDown(e) {
|
||||
const cell = e.target.closest('.sce-cell')
|
||||
if (!cell) {
|
||||
// 点击空白区(非单元格,含表头、周次列等)取消所有选中
|
||||
if (this.selectedKeys.length) this.selectedKeys = []
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
const key = cell.getAttribute('data-key')
|
||||
this.dragStart = key
|
||||
this.dragAddMode = e.ctrlKey || e.metaKey
|
||||
// 记录起点格在按下时的选中状态,用于松手时判定"纯点击"的切换行为
|
||||
this.dragStartSelected = this.selectedKeys.includes(key)
|
||||
this.moved = false
|
||||
this.selecting = true
|
||||
// 非 ctrl 模式下,若起点本身已选中(可能准备拖拽扩展),先不清除,避免误清空
|
||||
if (!this.dragAddMode && !this.dragStartSelected) this.selectedKeys = []
|
||||
this.addGlobalListeners()
|
||||
},
|
||||
addGlobalListeners() {
|
||||
document.addEventListener('mousemove', this.onGridMouseMove)
|
||||
document.addEventListener('mouseup', this.onGridMouseUp)
|
||||
},
|
||||
removeGlobalListeners() {
|
||||
document.removeEventListener('mousemove', this.onGridMouseMove)
|
||||
document.removeEventListener('mouseup', this.onGridMouseUp)
|
||||
},
|
||||
onGridMouseMove(e) {
|
||||
if (!this.selecting || !this.dragStart) return
|
||||
if (!this.moved) this.moved = true
|
||||
const el = document.elementFromPoint(e.clientX, e.clientY)
|
||||
const cell = el && el.closest ? el.closest('.sce-cell') : null
|
||||
if (!cell) return
|
||||
const curKey = cell.getAttribute('data-key')
|
||||
this.selectRectangle(this.dragStart, curKey, this.dragAddMode)
|
||||
},
|
||||
onGridMouseUp() {
|
||||
if (!this.selecting) {
|
||||
this.removeGlobalListeners()
|
||||
return
|
||||
}
|
||||
// 纯点击(未移动)时,做单选 / 切换
|
||||
if (!this.moved) {
|
||||
const key = this.dragStart
|
||||
if (this.dragAddMode) {
|
||||
// Ctrl + 单击:切换该格选中
|
||||
if (this.selectedKeys.includes(key)) {
|
||||
this.selectedKeys = this.selectedKeys.filter(k => k !== key)
|
||||
} else {
|
||||
this.selectedKeys.push(key)
|
||||
}
|
||||
} else {
|
||||
// 普通单击:已选中则取消该格,未选中则仅选中该格
|
||||
if (this.dragStartSelected) {
|
||||
this.selectedKeys = this.selectedKeys.filter(k => k !== key)
|
||||
} else {
|
||||
this.selectedKeys = [key]
|
||||
}
|
||||
}
|
||||
}
|
||||
this.selecting = false
|
||||
this.dragStart = null
|
||||
this.dragStartSelected = false
|
||||
this.removeGlobalListeners()
|
||||
},
|
||||
// 根据起点终点 key 选中矩形范围内所有格
|
||||
selectRectangle(startKey, endKey, addMode) {
|
||||
const parse = k => {
|
||||
const m = k.match(/^w(\d+)-c(\d+)$/)
|
||||
return m ? { w: +m[1], c: +m[2] } : null
|
||||
}
|
||||
const s = parse(startKey)
|
||||
const t = parse(endKey)
|
||||
if (!s || !t) return
|
||||
if (!addMode) this.selectedKeys = []
|
||||
const minW = Math.min(s.w, t.w)
|
||||
const maxW = Math.max(s.w, t.w)
|
||||
const minC = Math.min(s.c, t.c)
|
||||
const maxC = Math.max(s.c, t.c)
|
||||
for (let w = minW; w <= maxW; w++) {
|
||||
for (let c = minC; c <= maxC; c++) {
|
||||
const key = this.cellKey(w, c)
|
||||
if (this.selectedKeys.indexOf(key) === -1) this.selectedKeys.push(key)
|
||||
}
|
||||
}
|
||||
},
|
||||
/* ---------- 事件操作 ---------- */
|
||||
applyEvent() {
|
||||
if (!this.selectedKeys.length) {
|
||||
this.$message.warning('请先选择时间格')
|
||||
return
|
||||
}
|
||||
const name = this.toolbar.eventName.trim()
|
||||
if (!name) {
|
||||
this.$message.warning('请输入事件名称')
|
||||
return
|
||||
}
|
||||
const keys = [...this.selectedKeys]
|
||||
keys.forEach(key => {
|
||||
const prev = this.events[key]
|
||||
this.$set(this.events, key, {
|
||||
bh: prev ? prev.bh : undefined,
|
||||
name: name,
|
||||
schedulable: this.toolbar.schedulable,
|
||||
mainCourse: this.toolbar.mainCourse,
|
||||
remarkShow: this.toolbar.remarkShow,
|
||||
remark: this.toolbar.remark
|
||||
})
|
||||
})
|
||||
this.persistEvents(keys)
|
||||
},
|
||||
deleteEvent() {
|
||||
if (!this.selectedKeys.length) {
|
||||
this.$message.warning('请先选择时间格')
|
||||
return
|
||||
}
|
||||
const keys = [...this.selectedKeys]
|
||||
keys.forEach(key => {
|
||||
this.$delete(this.events, key)
|
||||
})
|
||||
this.$message.success('已删除 ' + keys.length + ' 个时间格事件')
|
||||
},
|
||||
absorbEvent() {
|
||||
// 吸取:取选择中第一个有事件的格
|
||||
let target = null
|
||||
for (const key of this.selectedKeys) {
|
||||
if (this.events[key]) { target = this.events[key]; break }
|
||||
}
|
||||
if (!target) {
|
||||
this.$message.warning('所选时间格中无事件可吸取')
|
||||
return
|
||||
}
|
||||
this.toolbar.eventName = target.name
|
||||
this.toolbar.schedulable = !!target.schedulable
|
||||
this.toolbar.mainCourse = !!target.mainCourse
|
||||
this.toolbar.remarkShow = !!target.remarkShow
|
||||
this.toolbar.remark = target.remark || ''
|
||||
this.absorbPreview = target.name
|
||||
},
|
||||
onCellDblClick(wIdx, col) {
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
this.toolbar.eventName = ev.name
|
||||
this.toolbar.schedulable = !!ev.schedulable
|
||||
this.toolbar.mainCourse = !!ev.mainCourse
|
||||
this.toolbar.remarkShow = !!ev.remarkShow
|
||||
this.toolbar.remark = ev.remark || ''
|
||||
this.absorbPreview = ev.name
|
||||
this.$message.info('已吸取事件:' + ev.name)
|
||||
}
|
||||
},
|
||||
refreshGrid() {
|
||||
this.selectedKeys = []
|
||||
this.absorbPreview = ''
|
||||
this.events = {}
|
||||
this.loadXqxlbEvents()
|
||||
this.$message.info('已刷新')
|
||||
},
|
||||
/* ---------- 后端持久化(xqxlb 接口) ---------- */
|
||||
// 批量保存选中格事件,返回各格子保存成功/失败的个数
|
||||
persistEvents(keys) {
|
||||
const tasks = keys
|
||||
.filter(key => this.events[key])
|
||||
.map(key => {
|
||||
const payload = this.buildXqxlbPayload(key, this.events[key])
|
||||
if (!payload) return Promise.resolve(false)
|
||||
return updateXqxlb(payload)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
})
|
||||
return Promise.all(tasks).then(results => {
|
||||
const ok = results.filter(r => r === true).length
|
||||
const fail = results.length - ok
|
||||
if (fail > 0) {
|
||||
this.$message.error(fail + ' 个事件保存失败,请检查后端接口')
|
||||
} else if (ok > 0) {
|
||||
this.$message.success('已设定 ' + ok + ' 个时间格')
|
||||
}
|
||||
// 保存成功后重新拉取,回写各记录主键,避免重复新增
|
||||
this.loadXqxlbEvents()
|
||||
return { ok: ok, fail: fail }
|
||||
})
|
||||
},
|
||||
// 构造 xqxlb 提交数据
|
||||
buildXqxlbPayload(key, ev) {
|
||||
const pos = this.parseCellKey(key)
|
||||
if (!pos) return null
|
||||
const d = new Date(this.startDate)
|
||||
d.setDate(this.startDate.getDate() + pos.wIdx * 7 + pos.dayIndex)
|
||||
return {
|
||||
delFlag: 0,
|
||||
bh: ev.bh || undefined,
|
||||
nd: this.nd,
|
||||
jqsj: fmtDate(d),
|
||||
jqmc: ev.name || '',
|
||||
jc: null,
|
||||
bz: ev.remark || null,
|
||||
kpk: !!ev.schedulable,
|
||||
bzxs: !!ev.remarkShow,
|
||||
zdpk: !!ev.mainCourse,
|
||||
courseClass: SLOT_COURSE_MAP[pos.slotKey] || null
|
||||
}
|
||||
},
|
||||
// 时间格 key -> { wIdx, dayIndex, slotKey }
|
||||
parseCellKey(key) {
|
||||
const m = key.match(/^w(\d+)-c(\d+)$/)
|
||||
if (!m) return null
|
||||
const colIndex = +m[2]
|
||||
const col = this.visibleColumns.find(c => c.colIndex === colIndex)
|
||||
if (!col) return null
|
||||
return { wIdx: +m[1], dayIndex: col.dayIndex, slotKey: col.slotKey }
|
||||
},
|
||||
/* ---------- 返回学期管理 ---------- */
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
this.$router.replace({ path: '/teachBusiness/semester' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.school-calendar-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
/* 标题栏 */
|
||||
.sce-titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
padding: 0 14px;
|
||||
background: var(--edu-green-primary, #00875a);
|
||||
color: #fff;
|
||||
|
||||
.sce-title-left { flex: 1; font-size: 15px; font-weight: 600; }
|
||||
.sce-title-center { flex: 1; text-align: center; font-size: 13px; opacity: 0.9; }
|
||||
.sce-title-right { flex: 0 0 auto; text-align: right; }
|
||||
|
||||
.sce-title-right i {
|
||||
margin-left: 14px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
opacity: 0.85;
|
||||
&:hover { opacity: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
/* 设置面板 */
|
||||
.sce-panel {
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
|
||||
.sce-panel-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 8px;
|
||||
|
||||
&:last-child { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
.sce-label { font-size: 13px; color: #606266; margin-right: 6px; white-space: nowrap; }
|
||||
.sce-event-input { width: 200px; margin-right: 12px; }
|
||||
.sce-remark-input { width: 240px; margin-left: 6px; }
|
||||
.sce-cb { margin-right: 14px; }
|
||||
.sce-divider { width: 1px; height: 18px; background: #dcdfe6; margin: 0 14px; }
|
||||
}
|
||||
|
||||
/* 时间编排区 */
|
||||
.sce-grid-wrap {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 8px 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sce-grid {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
|
||||
th, td {
|
||||
border: 1px solid #e4e7ed;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sce-corner {
|
||||
background: #f5f7fa;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.sce-th-weekno { width: 52px; }
|
||||
.sce-th-weekrange { width: 110px; }
|
||||
|
||||
.sce-th-day {
|
||||
background: var(--edu-green-primary, #00875a);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.sce-th-slot {
|
||||
background: #ecf5f0;
|
||||
color: #00875a;
|
||||
height: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sce-week-cell {
|
||||
background: #f5f7fa;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sce-weekno-cell {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sce-weekrange-cell {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.sce-cell {
|
||||
height: 30px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
transition: background 0.15s;
|
||||
|
||||
.sce-date-num { color: #c0c4cc; }
|
||||
|
||||
// 星期交替底色:周一/三/五/日为浅绿,周二/四/六为白,便于区分星期
|
||||
&.sce-cell-odd { background: #eef6f1; }
|
||||
&.sce-cell-even { background: #ffffff; }
|
||||
&.has-event { background: #e6f4ea; }
|
||||
&.has-event .sce-event-name { color: #00663e; font-weight: 500; }
|
||||
&.has-event .sce-event-name.is-bold { font-weight: 700; }
|
||||
&.no-schedule { background: #fde2e2; }
|
||||
|
||||
&.is-selected {
|
||||
outline: 2px solid var(--edu-green-primary, #00875a);
|
||||
outline-offset: -2px;
|
||||
background: #d4ecdf !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 状态标记小方块 */
|
||||
.sce-flag {
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
.sce-flag-noschedule { left: 2px; top: 2px; background: #909399; }
|
||||
.sce-flag-maincourse { right: 2px; top: 2px; background: #409eff; }
|
||||
}
|
||||
|
||||
.sce-status {
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
|
||||
.sce-absorb-tip { margin-left: 12px; color: #00875a; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,509 @@
|
||||
<template>
|
||||
<div class="teach-calendar app-container">
|
||||
<!-- 顶部标题:学期名称-校历 -->
|
||||
<div class="cal-titlebar">
|
||||
<span class="cal-title">{{ titleText }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 显示控制栏 -->
|
||||
<div class="cal-panel">
|
||||
<el-checkbox v-model="showDate" class="cal-cb">显示日期</el-checkbox>
|
||||
<div class="cal-panel-right">
|
||||
<el-button size="mini" type="primary" icon="el-icon-download" @click="exportExcel">另存为Excel</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 学期校历表格 -->
|
||||
<div class="cal-grid-wrap">
|
||||
<table class="cal-grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cal-corner cal-th-weekno" :rowspan="2">周次</th>
|
||||
<th class="cal-corner cal-th-weekrange" :rowspan="2">日期段</th>
|
||||
<th v-for="day in weekDayHeaders" :key="'h1-' + day.dayIndex" :colspan="day.colCount" class="cal-th-day">
|
||||
{{ day.label }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th v-for="col in visibleColumns" :key="'h2-' + col.colIndex" class="cal-th-slot">
|
||||
{{ col.slotLabel }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(week, wIdx) in weeks" :key="'w-' + wIdx">
|
||||
<td class="cal-week-cell cal-weekno-cell">{{ wIdx + 1 }}</td>
|
||||
<td class="cal-week-cell cal-weekrange-cell">{{ week.rangeText }}</td>
|
||||
<td
|
||||
v-for="col in visibleColumns"
|
||||
:key="'c-' + wIdx + '-' + col.colIndex"
|
||||
class="cal-cell"
|
||||
:class="cellClass(wIdx, col)"
|
||||
>
|
||||
<template v-if="getEvent(wIdx, col.colIndex)">
|
||||
<span class="cal-event-name">{{ getEvent(wIdx, col.colIndex).name }}</span>
|
||||
<span
|
||||
v-if="!getEvent(wIdx, col.colIndex).schedulable"
|
||||
class="cal-flag cal-flag-noschedule"
|
||||
title="全校不允许排课"
|
||||
/>
|
||||
<span
|
||||
v-if="getEvent(wIdx, col.colIndex).mainCourse"
|
||||
class="cal-flag cal-flag-maincourse"
|
||||
title="正课"
|
||||
/>
|
||||
</template>
|
||||
<span v-else-if="showDate" class="cal-date-num">{{ dateNumberOf(wIdx, col.dayIndex) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 底部状态栏 -->
|
||||
<div class="cal-status">
|
||||
<span v-if="nd">从 {{ dateRangeText }} ,共 {{ totalWeeks }} 周</span>
|
||||
<span v-else>请选择学期</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { saveAs } from 'file-saver'
|
||||
import { listSemester, getSemester } from '@/api/teachBusiness/semester'
|
||||
import { listXqxlb } from '@/api/teachBusiness/xqxlb'
|
||||
|
||||
// 节次定义:全部常显
|
||||
const SLOT_DEFS = [
|
||||
{ key: '12', label: '1-2' },
|
||||
{ key: '34', label: '3-4' },
|
||||
{ key: '56', label: '5-6' },
|
||||
{ key: '78', label: '7-8' },
|
||||
{ key: 'night', label: '晚上' },
|
||||
{ key: 'late', label: '夜间' }
|
||||
]
|
||||
|
||||
// 前端节次 key -> 后端 xqxlb.courseClass 节次范围
|
||||
const SLOT_COURSE_MAP = {
|
||||
'12': '1-2',
|
||||
'34': '3-4',
|
||||
'56': '5-6',
|
||||
'78': '7-8',
|
||||
night: '9-10',
|
||||
late: '11-12'
|
||||
}
|
||||
|
||||
const WEEK_DAYS = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
|
||||
function pad2(n) {
|
||||
return n < 10 ? '0' + n : '' + n
|
||||
}
|
||||
|
||||
function fmtDate(d) {
|
||||
return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate())
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'SemesterCalendar',
|
||||
data() {
|
||||
return {
|
||||
// 当前学期代号(跟随顶栏切换学期,自动更新为当前学期)
|
||||
nd: '',
|
||||
// 学期日期范围
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
totalWeeks: 0,
|
||||
dateRangeText: '',
|
||||
weeks: [],
|
||||
// 显示控制:默认勾选显示日期;节次全部展示
|
||||
showDate: true,
|
||||
// 事件存储:key = cellKey -> event
|
||||
events: {},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 页面标题:学期名称-校历
|
||||
titleText() {
|
||||
if (!this.nd) return '学期校历'
|
||||
return this.getSemesterName(this.nd) + '-校历'
|
||||
},
|
||||
// 当前可见列(全部节次常显)
|
||||
visibleColumns() {
|
||||
const cols = []
|
||||
let colIndex = 0
|
||||
WEEK_DAYS.forEach((dayLabel, dayIndex) => {
|
||||
SLOT_DEFS.forEach(slot => {
|
||||
cols.push({ colIndex: colIndex, dayIndex: dayIndex, slotKey: slot.key, slotLabel: slot.label, dayLabel: dayLabel })
|
||||
colIndex++
|
||||
})
|
||||
})
|
||||
return cols
|
||||
},
|
||||
// 表头第一行星期:每个星期合并其下全部节次列
|
||||
weekDayHeaders() {
|
||||
return WEEK_DAYS.map((label, dayIndex) => ({
|
||||
dayIndex: dayIndex,
|
||||
label: label,
|
||||
colCount: SLOT_DEFS.length
|
||||
}))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始加载当前学期
|
||||
this.loadCurrentSemester()
|
||||
// 顶栏切换学期后自动刷新为当前学期的校历
|
||||
this.$root.$on('semester-current-changed', this.loadCurrentSemester)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off('semester-current-changed', this.loadCurrentSemester)
|
||||
},
|
||||
methods: {
|
||||
/* ---------- 加载当前学期 ---------- */
|
||||
loadCurrentSemester() {
|
||||
this.loading = true
|
||||
listSemester({ pageNum: 1, pageSize: 100 }).then(response => {
|
||||
const data = response.data || {}
|
||||
const list = data.records || []
|
||||
// 跟随顶栏当前学期(dqxq=true),无则取第一条
|
||||
const current = list.find(i => i.dqxq) || list[0]
|
||||
this.loading = false
|
||||
if (current) {
|
||||
if (String(this.nd) !== String(current.nd)) {
|
||||
this.nd = current.nd
|
||||
this.loadSemester()
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/* ---------- 数据加载 ---------- */
|
||||
loadSemester() {
|
||||
if (!this.nd) return
|
||||
this.startDate = null
|
||||
this.endDate = null
|
||||
getSemester(this.nd)
|
||||
.then(res => {
|
||||
const data = res.data || res || {}
|
||||
const kx = (data.kxrq || '').toString().slice(0, 10)
|
||||
const jx = (data.jsrq || '').toString().slice(0, 10)
|
||||
if (kx && jx) {
|
||||
this.startDate = new Date(kx.replace(/-/g, '/'))
|
||||
this.endDate = new Date(jx.replace(/-/g, '/'))
|
||||
this.buildWeeks()
|
||||
}
|
||||
this.loadXqxlbEvents()
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.warning('学期详情获取失败,请确认学期数据')
|
||||
})
|
||||
},
|
||||
// 从后端拉取本学期校历假期,渲染到对应时间格(nd 为 6 位学期代号,如 202601)
|
||||
loadXqxlbEvents() {
|
||||
listXqxlb({ nd: this.nd }).then(res => {
|
||||
const data = res.data
|
||||
const list = Array.isArray(data) ? data : (data && data.records) || []
|
||||
const loaded = {}
|
||||
list.forEach(item => {
|
||||
const pos = this.locateXqxlb(item)
|
||||
if (pos) {
|
||||
loaded[pos.key] = {
|
||||
bh: item.bh,
|
||||
name: item.jqmc || '',
|
||||
schedulable: !!item.kpk,
|
||||
mainCourse: !!item.zdpk,
|
||||
remarkShow: !!item.bzxs,
|
||||
remark: item.bz || ''
|
||||
}
|
||||
}
|
||||
})
|
||||
this.events = loaded
|
||||
}).catch(() => {
|
||||
this.$message.warning('校历事件加载失败')
|
||||
})
|
||||
},
|
||||
// 根据假期记录反推时间格 key(jqsj 日期 + courseClass 节次)
|
||||
locateXqxlb(item) {
|
||||
if (!this.startDate || !item.jqsj) return null
|
||||
const d = new Date(String(item.jqsj).slice(0, 10).replace(/-/g, '/'))
|
||||
if (isNaN(d.getTime())) return null
|
||||
const offset = Math.round((d - this.startDate) / (24 * 3600 * 1000))
|
||||
if (offset < 0) return null
|
||||
const wIdx = Math.floor(offset / 7)
|
||||
const dayIndex = offset % 7
|
||||
const slotKey = this.courseClassToSlotKey(item.courseClass)
|
||||
if (slotKey === null) return null
|
||||
const colIndex = this.colIndexOf(dayIndex, slotKey)
|
||||
if (colIndex < 0) return null
|
||||
return { key: this.cellKey(wIdx, colIndex) }
|
||||
},
|
||||
// 后端 courseClass 节次范围 -> 前端节次 key
|
||||
courseClassToSlotKey(courseClass) {
|
||||
if (!courseClass) return null
|
||||
const s = String(courseClass).trim()
|
||||
for (const key in SLOT_COURSE_MAP) {
|
||||
if (s === SLOT_COURSE_MAP[key]) return key
|
||||
}
|
||||
// 兼容 "910"/"1112" 等紧凑写法
|
||||
if (s === '910') return 'night'
|
||||
if (s === '1112') return 'late'
|
||||
return null
|
||||
},
|
||||
buildWeeks() {
|
||||
if (!this.startDate || !this.endDate) return
|
||||
const start = new Date(this.startDate)
|
||||
const end = new Date(this.endDate)
|
||||
const days = Math.round((end - start) / (24 * 3600 * 1000)) + 1
|
||||
this.totalWeeks = Math.ceil(days / 7)
|
||||
this.dateRangeText = fmtDate(start) + ' 到 ' + fmtDate(end)
|
||||
const weeks = []
|
||||
for (let w = 0; w < this.totalWeeks; w++) {
|
||||
const wkStart = new Date(start)
|
||||
wkStart.setDate(start.getDate() + w * 7)
|
||||
const wkEnd = new Date(wkStart)
|
||||
wkEnd.setDate(wkStart.getDate() + 6)
|
||||
weeks.push({
|
||||
rangeText: pad2(wkStart.getMonth() + 1) + '-' + pad2(wkStart.getDate()) +
|
||||
'至' + pad2(wkEnd.getMonth() + 1) + '-' + pad2(wkEnd.getDate())
|
||||
})
|
||||
}
|
||||
this.weeks = weeks
|
||||
},
|
||||
colIndexOf(dayIndex, slotKey) {
|
||||
const col = this.visibleColumns.find(c => c.dayIndex === dayIndex && c.slotKey === slotKey)
|
||||
return col ? col.colIndex : -1
|
||||
},
|
||||
/* ---------- 单元格工具 ---------- */
|
||||
cellKey(wIdx, colIndex) {
|
||||
return 'w' + wIdx + '-c' + colIndex
|
||||
},
|
||||
getEvent(wIdx, colIndex) {
|
||||
return this.events[this.cellKey(wIdx, colIndex)] || null
|
||||
},
|
||||
weekDayLabel(dayIndex) {
|
||||
return WEEK_DAYS[dayIndex]
|
||||
},
|
||||
// 单元格内显示的日期数字(如 0703)
|
||||
dateNumberOf(wIdx, dayIndex) {
|
||||
if (!this.startDate) return ''
|
||||
const d = new Date(this.startDate)
|
||||
d.setDate(this.startDate.getDate() + wIdx * 7 + dayIndex)
|
||||
return pad2(d.getMonth() + 1) + pad2(d.getDate())
|
||||
},
|
||||
cellClass(wIdx, col) {
|
||||
const classes = []
|
||||
// 星期交替背景(单双日不同底色)
|
||||
if (col.dayIndex % 2 === 0) classes.push('cal-cell-odd')
|
||||
else classes.push('cal-cell-even')
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
classes.push('has-event')
|
||||
if (!ev.schedulable) classes.push('no-schedule')
|
||||
}
|
||||
return classes
|
||||
},
|
||||
/** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */
|
||||
getSemesterName(nd) {
|
||||
if (!nd) return ''
|
||||
const s = String(nd)
|
||||
const xn = s.slice(0, 4)
|
||||
const xq = s.slice(4)
|
||||
const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }
|
||||
return xn + '年' + (map[xq] || '第' + xq + '学期')
|
||||
},
|
||||
/* ---------- 另存为Excel ---------- */
|
||||
exportExcel() {
|
||||
if (!this.weeks.length) {
|
||||
this.$message.warning('暂无校历数据可导出')
|
||||
return
|
||||
}
|
||||
// 按星期分组节次列,用于表头合并
|
||||
const dayCols = []
|
||||
this.visibleColumns.forEach(col => {
|
||||
if (!dayCols[col.dayIndex]) dayCols[col.dayIndex] = []
|
||||
dayCols[col.dayIndex].push(col)
|
||||
})
|
||||
let html = '<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel">'
|
||||
html += '<head><meta charset="utf-8"></head><body>'
|
||||
html += '<table border="1" cellspacing="0" cellpadding="4" style="border-collapse:collapse;">'
|
||||
// 标题行
|
||||
html += '<tr><td colspan="' + (this.visibleColumns.length + 2) + '" style="font-size:15px;font-weight:bold;text-align:center;">' + this.titleText + '</td></tr>'
|
||||
// 表头第一行:星期(合并同类天)
|
||||
html += '<tr><th rowspan="2">周次</th><th rowspan="2">日期段</th>'
|
||||
WEEK_DAYS.forEach((dayLabel, dayIndex) => {
|
||||
const cols = dayCols[dayIndex] || []
|
||||
if (cols.length) html += '<th colspan="' + cols.length + '">' + dayLabel + '</th>'
|
||||
})
|
||||
html += '</tr>'
|
||||
// 表头第二行:节次
|
||||
html += '<tr>'
|
||||
this.visibleColumns.forEach(col => {
|
||||
html += '<th>' + col.slotLabel + '</th>'
|
||||
})
|
||||
html += '</tr>'
|
||||
// 数据行
|
||||
this.weeks.forEach((week, wIdx) => {
|
||||
html += '<tr>'
|
||||
html += '<td>' + (wIdx + 1) + '</td>'
|
||||
html += '<td>' + week.rangeText + '</td>'
|
||||
this.visibleColumns.forEach(col => {
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev) {
|
||||
const remark = ev.remark && ev.remarkShow ? '(' + ev.remark + ')' : ''
|
||||
html += '<td>' + ev.name + remark + '</td>'
|
||||
} else if (this.showDate) {
|
||||
html += '<td>' + this.dateNumberOf(wIdx, col.dayIndex) + '</td>'
|
||||
} else {
|
||||
html += '<td></td>'
|
||||
}
|
||||
})
|
||||
html += '</tr>'
|
||||
})
|
||||
html += '</table></body></html>'
|
||||
const blob = new Blob(['\ufeff' + html], { type: 'application/vnd.ms-excel;charset=utf-8' })
|
||||
saveAs(blob, (this.titleText || '学期校历') + '.xls')
|
||||
this.$message.success('已导出Excel')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.teach-calendar {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
/* 顶部标题栏 */
|
||||
.cal-titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 44px;
|
||||
background: var(--edu-green-primary, #00875a);
|
||||
color: #fff;
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
.cal-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
/* 显示控制栏 */
|
||||
.cal-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
|
||||
.cal-cb { margin-right: 14px; }
|
||||
.cal-panel-right { margin-left: auto; }
|
||||
}
|
||||
|
||||
/* 表格区域 */
|
||||
.cal-grid-wrap {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 8px 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.cal-grid {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
|
||||
th, td {
|
||||
border: 1px solid #e4e7ed;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cal-corner {
|
||||
background: #f5f7fa;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cal-th-weekno { width: 52px; }
|
||||
.cal-th-weekrange { width: 110px; }
|
||||
|
||||
.cal-th-day {
|
||||
background: var(--edu-green-primary, #00875a);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.cal-th-slot {
|
||||
background: #ecf5f0;
|
||||
color: #00875a;
|
||||
height: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.cal-week-cell {
|
||||
background: #f5f7fa;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cal-weekno-cell {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cal-weekrange-cell {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.cal-cell {
|
||||
height: 30px;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
|
||||
.cal-date-num { color: #c0c4cc; }
|
||||
|
||||
// 星期交替底色:周一/三/五/日为浅绿,周二/四/六为白
|
||||
&.cal-cell-odd { background: #eef6f1; }
|
||||
&.cal-cell-even { background: #ffffff; }
|
||||
&.has-event { background: #e6f4ea; }
|
||||
&.has-event .cal-event-name { color: #00663e; font-weight: 500; }
|
||||
&.no-schedule { background: #fde2e2; }
|
||||
}
|
||||
|
||||
/* 状态标记小方块 */
|
||||
.cal-flag {
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
.cal-flag-noschedule { left: 2px; top: 2px; background: #909399; }
|
||||
.cal-flag-maincourse { right: 2px; top: 2px; background: #409eff; }
|
||||
}
|
||||
|
||||
/* 底部状态栏 */
|
||||
.cal-status {
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="teach-calendar app-container">
|
||||
<school-calendar-editor
|
||||
v-if="nd"
|
||||
:nd="nd"
|
||||
:semester-name="semesterName"
|
||||
@close="handleClose"
|
||||
/>
|
||||
<div v-else class="calendar-empty">未指定学期,请从学期管理页面进入。</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SchoolCalendarEditor from '@/views/teachBusiness/calendar/components/SchoolCalendarEditor'
|
||||
|
||||
export default {
|
||||
name: 'SemesterCalendarEdit',
|
||||
components: { SchoolCalendarEditor },
|
||||
data() {
|
||||
return {
|
||||
nd: '',
|
||||
semesterName: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.nd = this.$route.query.nd || ''
|
||||
this.semesterName = this.$route.query.name || ''
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.$router.replace({ path: '/teachBusiness/semester' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.teach-calendar {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.calendar-empty {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,596 @@
|
||||
<template>
|
||||
<div class="app-container teach-semester">
|
||||
<!-- 工具栏 -->
|
||||
<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="success" plain icon="el-icon-edit" size="mini" :disabled="single" @click="handleUpdate">编辑</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="el-icon-date" size="mini" :disabled="single" @click="handleEditCalendar">编辑校历</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="semesterList"
|
||||
border
|
||||
:height="tableHeight"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" align="center" width="50" />
|
||||
<el-table-column label="学期名称" align="center" min-width="190">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getSemesterName(scope.row.nd) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前" align="center" width="70">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.dqxq" type="success" size="mini">当前</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="开学日期" align="center" prop="kxrq" min-width="130">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatDate(scope.row.kxrq) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束日期" align="center" prop="jsrq" min-width="130">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatDate(scope.row.jsrq) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="周数" align="center" prop="sdzs" width="70" />
|
||||
<el-table-column label="调课不审批" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.tkbsp ? 'primary' : 'info'" size="mini">{{ scope.row.tkbsp ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="禁止调课" align="center" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.jztk ? 'danger' : 'info'" size="mini">{{ scope.row.jztk ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="终结成绩定及格" align="center" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.zjcjdjg ? 'primary' : 'info'" size="mini">{{ scope.row.zjcjdjg ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="课时系数方案" align="center" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ getCoefficientLabel(scope.row.ksxsfabh) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="锁定时长" align="center" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ scope.row.sdjldw || '-' }}</span>
|
||||
</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="900px" append-to-body class="semester-dialog">
|
||||
<el-form ref="form" class="semester-dialog-form" :model="form" :rules="rules" label-width="96px" label-position="right">
|
||||
<el-row :gutter="20" class="semester-dialog-body">
|
||||
<!-- 左侧表单区 -->
|
||||
<el-col :span="15" class="semester-form-col">
|
||||
<el-form-item label="学期名称">
|
||||
<el-input
|
||||
:value="getSemesterName(buildNd(form.xn, form.xq))"
|
||||
readonly
|
||||
placeholder="由年份+学期类型自动拼接"
|
||||
prefix-icon="el-icon-office-building"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年份" prop="xn">
|
||||
<el-select v-model="form.xn" placeholder="请选择年份" :disabled="!isAdd" style="width: 100%">
|
||||
<el-option v-for="item in yearOptions" :key="item" :label="item + '年'" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学期类型" prop="xq">
|
||||
<el-select v-model="form.xq" placeholder="请选择学期类型" :disabled="!isAdd" style="width: 100%">
|
||||
<el-option v-for="item in semesterOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开学日期" prop="kxrq">
|
||||
<el-date-picker
|
||||
v-model="form.kxrq"
|
||||
type="date"
|
||||
placeholder="开学日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束日期" prop="jsrq">
|
||||
<el-date-picker
|
||||
v-model="form.jsrq"
|
||||
type="date"
|
||||
placeholder="结束日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="14">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学期周数" prop="sdzs">
|
||||
<el-input-number v-model="form.sdzs" :min="1" :max="60" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课时系数方案" prop="ksxsfabh">
|
||||
<el-select v-model="form.ksxsfabh" placeholder="请选择" clearable style="width: 100%">
|
||||
<el-option v-for="item in coefficientOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="锁定时长">
|
||||
<div class="lock-group">
|
||||
<el-input-number v-model="form.lockValue" :min="0" :max="99" controls-position="right" style="width: 120px" />
|
||||
<el-radio-group v-model="form.lockUnit" class="lock-unit" style="margin-left: 28px">
|
||||
<el-radio label="周">周</el-radio>
|
||||
<el-radio label="月">月</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<div class="form-tip">以当前周星期四中午12点为界限,锁定【锁定周数】周的教学实施计划,被锁定的教学实施计划不允许教员自行调整。</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧业务开关区 -->
|
||||
<el-col :span="9" class="semester-switch-col">
|
||||
<div class="switch-panel-title">业务开关</div>
|
||||
<div class="switch-panel">
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.dqxq">当前学期</el-checkbox>
|
||||
<div class="switch-tip">设定当前学期后,系统启动时会自动将该学期设置为默认学期。</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.jztk">禁止调课</el-checkbox>
|
||||
<div class="switch-tip">设定【禁止调课】后,教学信息系统中就无法对课程信息进行调整。</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.tkbsp">调课不审批</el-checkbox>
|
||||
<div class="switch-tip">设定【调课不审批】后,在教学信息系统中调课时,如果只是更改教员,或更改授课地点,调课申请会立即生效。而对于改变授课地点,调课申请会立即生效。而对于改变授课时间、增加或减少授课教员的调课申请依然不受该设置影响,依然需要逐级审批后才能生效。</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.zjcjdjg">终结成绩定及格</el-checkbox>
|
||||
<div class="switch-tip">设定【终结成绩定及格】后,学员终结性成绩不及格的,最终成绩即为不及格。</div>
|
||||
</div>
|
||||
|
||||
<div class="switch-item">
|
||||
<el-checkbox v-model="form.jwldshtk">教务机关审核教学计划调整</el-checkbox>
|
||||
<div class="switch-tip">勾选:教务机关需要从行政入口对每个教学计划调整申请进行审批,同时再由管理员身份教务参谋进行最后的把关;不勾选:仅由管理员身份教务参谋进行最后的把关。</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listSemester, getSemester, addSemester, updateSemester, delSemester } from "@/api/teachBusiness/semester"
|
||||
|
||||
export default {
|
||||
name: "Semester",
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 表格高度
|
||||
tableHeight: window.innerHeight - 280,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 学期列表数据
|
||||
semesterList: [],
|
||||
// 选中的学期数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 是否新增
|
||||
isAdd: true,
|
||||
// 学年选项(当前年份 ± 10 年)
|
||||
yearOptions: [],
|
||||
// 学期选项:01 春季、02 夏季、03 秋季
|
||||
semesterOptions: [
|
||||
{ value: '01', label: '春季学期' },
|
||||
{ value: '02', label: '夏季学期' },
|
||||
{ value: '03', label: '秋季学期' }
|
||||
],
|
||||
// 课时系数方案选项(引用基础数据表,暂以静态占位)
|
||||
coefficientOptions: [
|
||||
{ value: '1', label: '标准系数方案' },
|
||||
{ value: '2', label: '综合系数方案' },
|
||||
{ value: '3', label: '实训系数方案' }
|
||||
],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
xn: [
|
||||
{ required: true, message: "请选择年份", trigger: "change" }
|
||||
],
|
||||
xq: [
|
||||
{ required: true, message: "请选择学期类型", trigger: "change" }
|
||||
],
|
||||
kxrq: [
|
||||
{ required: true, message: "请选择开学日期", trigger: "change" }
|
||||
],
|
||||
jsrq: [
|
||||
{ required: true, message: "请选择结束日期", trigger: "change" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initYearOptions()
|
||||
this.getList()
|
||||
// 顶栏切换当前学期后,自动刷新本页数据
|
||||
this.$root.$on('semester-current-changed', this.handleCurrentChanged)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$root.$off('semester-current-changed', this.handleCurrentChanged)
|
||||
},
|
||||
methods: {
|
||||
/** 顶栏切换当前学期后自动刷新 */
|
||||
handleCurrentChanged() {
|
||||
this.getList()
|
||||
},
|
||||
/** 初始化学年选项 */
|
||||
initYearOptions() {
|
||||
const current = new Date().getFullYear()
|
||||
const list = []
|
||||
for (let i = current - 10; i <= current + 10; i++) {
|
||||
list.push(i)
|
||||
}
|
||||
this.yearOptions = list
|
||||
},
|
||||
/** 学年+学期 -> 学期代号(如 2026 + 01 = 202601) */
|
||||
buildNd(xn, xq) {
|
||||
if (xn === undefined || xn === null || xn === '' || !xq) return ''
|
||||
return String(xn) + String(xq)
|
||||
},
|
||||
/** 学期代号 -> 学期名称(如 202601 -> 2026年春季学期) */
|
||||
getSemesterName(nd) {
|
||||
if (!nd) return ''
|
||||
const s = String(nd)
|
||||
const xn = s.slice(0, 4)
|
||||
const xq = s.slice(4)
|
||||
const map = { '01': '春季学期', '02': '夏季学期', '03': '秋季学期' }
|
||||
return xn + '年' + (map[xq] || '第' + xq + '学期')
|
||||
},
|
||||
/** 学期代号 -> {xn, xq} */
|
||||
parseNd(nd) {
|
||||
if (!nd) return { xn: undefined, xq: undefined }
|
||||
const s = String(nd)
|
||||
return { xn: s.slice(0, 4), xq: s.slice(4) }
|
||||
},
|
||||
/** 课时系数方案编号 -> 名称 */
|
||||
getCoefficientLabel(value) {
|
||||
if (!value && value !== 0) return '-'
|
||||
const found = this.coefficientOptions.find(o => String(o.value) === String(value))
|
||||
return found ? found.label : value
|
||||
},
|
||||
/** 格式化后端 LocalDateTime(2026-02-23T00:00:00 -> 2026-02-23) */
|
||||
formatDate(value) {
|
||||
if (!value) return ''
|
||||
return String(value).slice(0, 10)
|
||||
},
|
||||
/** 查询学期列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listSemester(this.queryParams).then(response => {
|
||||
const data = response.data || {}
|
||||
this.semesterList = data.records || []
|
||||
this.total = data.total || 0
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
this.semesterList = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 多选变化 */
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.nd)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 组装提交数据 */
|
||||
buildPayload(form) {
|
||||
const lockValue = form.lockValue !== undefined && form.lockValue !== null ? form.lockValue : ''
|
||||
return {
|
||||
delFlag: 0,
|
||||
nd: this.buildNd(form.xn, form.xq),
|
||||
dqxq: !!form.dqxq,
|
||||
kxrq: form.kxrq ? form.kxrq + 'T00:00:00' : null,
|
||||
jsrq: form.jsrq ? form.jsrq + 'T00:00:00' : null,
|
||||
tkbsp: !!form.tkbsp,
|
||||
jztk: !!form.jztk,
|
||||
zjcjdjg: !!form.zjcjdjg,
|
||||
jwldshtk: !!form.jwldshtk,
|
||||
ksxsfabh: form.ksxsfabh || null,
|
||||
sdzs: form.sdzs,
|
||||
sdjldw: lockValue === '' ? '' : lockValue + form.lockUnit,
|
||||
xh: form.xh,
|
||||
jsonzd: "{'1':1}",
|
||||
kstjsj: null
|
||||
}
|
||||
},
|
||||
/** 新增按钮 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.isAdd = true
|
||||
this.open = true
|
||||
this.title = "学期信息 - 新增"
|
||||
},
|
||||
/** 编辑按钮 */
|
||||
handleUpdate() {
|
||||
if (this.ids.length !== 1) return
|
||||
this.reset()
|
||||
this.isAdd = false
|
||||
getSemester(this.ids[0]).then(response => {
|
||||
const data = response.data || {}
|
||||
const { xn, xq } = this.parseNd(data.nd)
|
||||
// 解析锁定时长(如 "2周" / "1月" / "1")
|
||||
let lockValue = data.sdjldw
|
||||
let lockUnit = '周'
|
||||
if (lockValue) {
|
||||
const s = String(lockValue)
|
||||
if (s.endsWith('周')) { lockValue = s.slice(0, -1); lockUnit = '周' }
|
||||
else if (s.endsWith('月')) { lockValue = s.slice(0, -1); lockUnit = '月' }
|
||||
}
|
||||
this.form = {
|
||||
xn: xn,
|
||||
xq: xq,
|
||||
dqxq: !!data.dqxq,
|
||||
jztk: !!data.jztk,
|
||||
tkbsp: !!data.tkbsp,
|
||||
zjcjdjg: !!data.zjcjdjg,
|
||||
jwldshtk: !!data.jwldshtk,
|
||||
ksxsfabh: data.ksxsfabh,
|
||||
kxrq: this.formatDate(data.kxrq),
|
||||
jsrq: this.formatDate(data.jsrq),
|
||||
sdzs: data.sdzs,
|
||||
lockValue: lockValue,
|
||||
lockUnit: lockUnit,
|
||||
xh: data.xh
|
||||
}
|
||||
this.open = true
|
||||
this.title = "学期信息 - 编辑"
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 编辑校历按钮 */
|
||||
handleEditCalendar() {
|
||||
if (this.ids.length !== 1) return
|
||||
const nd = this.ids[0]
|
||||
this.$router.push({
|
||||
path: '/teachBusiness/semester/semesterCalendar',
|
||||
query: { nd: nd, name: this.getSemesterName(nd) }
|
||||
})
|
||||
},
|
||||
/** 删除按钮 */
|
||||
handleDelete() {
|
||||
if (!this.ids.length) return
|
||||
const names = this.ids.map(nd => this.getSemesterName(nd)).join('、')
|
||||
this.$modal.confirm('确认删除学期【' + names + '】吗?').then(() => {
|
||||
// 批量删除:逐个调用
|
||||
const delList = this.ids.map(nd => delSemester(nd))
|
||||
return Promise.all(delList)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 提交表单 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
const payload = this.buildPayload(this.form)
|
||||
const selfNd = payload.nd
|
||||
// 当前学期全局只能有一个:若本次设为当前,先把其他 dqxq=true 的取消
|
||||
const cancelOthers = payload.dqxq
|
||||
? this.cancelOtherCurrent(selfNd)
|
||||
: Promise.resolve()
|
||||
cancelOthers.then(() => {
|
||||
if (this.isAdd) {
|
||||
return addSemester(payload)
|
||||
}
|
||||
return updateSemester(payload)
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess(this.isAdd ? "新增成功" : "修改成功")
|
||||
this.open = false
|
||||
this.getList()
|
||||
}).catch(() => {})
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 取消其它“当前学期”,保证全局唯一 */
|
||||
cancelOtherCurrent(selfNd) {
|
||||
return listSemester({ pageNum: 1, pageSize: 100 }).then(response => {
|
||||
const data = response.data || {}
|
||||
const list = data.records || []
|
||||
const others = list.filter(i => i.dqxq && String(i.nd) !== String(selfNd))
|
||||
if (!others.length) return Promise.resolve()
|
||||
return Promise.all(others.map(o => updateSemester({ ...o, dqxq: false })))
|
||||
})
|
||||
},
|
||||
/** 重置表单 */
|
||||
reset() {
|
||||
this.form = {
|
||||
xn: undefined,
|
||||
xq: undefined,
|
||||
dqxq: false,
|
||||
jztk: false,
|
||||
tkbsp: false,
|
||||
zjcjdjg: false,
|
||||
jwldshtk: false,
|
||||
ksxsfabh: undefined,
|
||||
kxrq: undefined,
|
||||
jsrq: undefined,
|
||||
sdzs: 20,
|
||||
lockValue: 1,
|
||||
lockUnit: '周',
|
||||
xh: 1
|
||||
}
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 取消按钮 */
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.teach-semester {
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 弹窗内表单:防止 label 文字换行、字段拥挤换行 */
|
||||
::v-deep .el-dialog__body {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 对话框整体两栏布局 */
|
||||
.semester-dialog-body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.semester-form-col {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.semester-switch-col {
|
||||
padding-left: 24px;
|
||||
border-left: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.semester-dialog-form {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__label {
|
||||
white-space: nowrap;
|
||||
line-height: 32px;
|
||||
padding-right: 10px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item__content {
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.el-input,
|
||||
.el-select,
|
||||
.el-date-editor {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
margin-top: 4px;
|
||||
white-space: normal;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.lock-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.switch-panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 14px;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.switch-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.switch-item {
|
||||
padding: 11px 13px;
|
||||
background: #fafafa;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--edu-green-primary);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
height: 26px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.switch-tip {
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: #909399;
|
||||
text-align: justify;
|
||||
margin: 2px 0 0 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div class="app-container syllabus-page">
|
||||
<!-- 查询条件 -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="120px" class="search-form">
|
||||
<el-row :gutter="32">
|
||||
<!-- 左栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<el-form-item label="专业名称">
|
||||
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业方向">
|
||||
<el-input v-model="searchForm.zyfx" placeholder="请输入专业方向" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label"><el-checkbox v-model="searchForm.pxlxEnabled">培训类型</el-checkbox></template>
|
||||
<el-select v-model="searchForm.pxlx" placeholder="请选择培训类型" clearable class="w-full" :disabled="!searchForm.pxlxEnabled">
|
||||
<el-option v-for="item in pxlxOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label"><el-checkbox v-model="searchForm.pxccEnabled">培训层次</el-checkbox></template>
|
||||
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" clearable class="w-full" :disabled="!searchForm.pxccEnabled">
|
||||
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="自定义分类">
|
||||
<el-input v-model="searchForm.zdyfl" placeholder="请输入自定义分类" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 右栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<el-form-item label="专业代码">
|
||||
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="学年制">
|
||||
<el-input v-model="searchForm.xnz" placeholder="请输入学年制" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="培训类型2">
|
||||
<el-select v-model="searchForm.pxlx2" placeholder="请选择培训类型2" clearable class="w-full">
|
||||
<el-option v-for="item in pxlx2Options" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用状态">
|
||||
<el-checkbox v-model="searchForm.ty">停用</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="大纲版本">
|
||||
<el-input v-model="searchForm.dgbb" placeholder="请输入大纲版本" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label"><el-checkbox v-model="searchForm.gljgEnabled">管理机构</el-checkbox></template>
|
||||
<el-select v-model="searchForm.gljg" placeholder="请选择管理机构" clearable class="w-full" :disabled="!searchForm.gljgEnabled">
|
||||
<el-option v-for="item in gljgOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="search-footer">
|
||||
<div class="notice">注意:勾选"选择框"表示启用该项对应的查询条件。</div>
|
||||
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据列表 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="list-header">
|
||||
<div class="list-title">教学大纲列表</div>
|
||||
<el-button type="primary" @click="handleDownload">下载</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData" border stripe style="width: 100%">
|
||||
<el-table-column prop="jxgljg" label="教学管理机构" min-width="140" />
|
||||
<el-table-column prop="xkzyxx" label="学科专业信息" min-width="140" />
|
||||
<el-table-column prop="jxdgxx" label="教学大纲信息" min-width="200" />
|
||||
<el-table-column prop="fl" label="分类" width="100" align="center" />
|
||||
<el-table-column prop="xylb" label="学员类别" width="100" align="center" />
|
||||
<el-table-column prop="xnz" label="学年制" width="80" align="center" />
|
||||
<el-table-column prop="xsfb" label="学时分布" min-width="140" align="center" />
|
||||
<el-table-column prop="zy" label="停用" width="80" align="center" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 模拟列表数据(后端接口未提供,接口就绪后可删除并改为接口加载)
|
||||
const mockTableData = [
|
||||
{ jxgljg: '教务处', xkzyxx: '通信工程', jxdgxx: '通信工程导论教学大纲', fl: '专业基础', xylb: '军官', xnz: '4', xsfb: '理论48/实践16', zy: '教学' },
|
||||
{ jxgljg: '教务处', xkzyxx: '信息工程', jxdgxx: '信息工程基础教学大纲', fl: '专业基础', xylb: '军官', xnz: '4', xsfb: '理论64/实践32', zy: '教学' }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'SyllabusIndex',
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询表单 ====================
|
||||
searchForm: {
|
||||
zymc: '',
|
||||
zydm: '',
|
||||
zyfx: '',
|
||||
xnz: '',
|
||||
pxlx: '军官基础教育', pxlxEnabled: false,
|
||||
pxlx2: '其他',
|
||||
pxcc: '无', pxccEnabled: false,
|
||||
ty: false,
|
||||
zdyfl: '',
|
||||
dgbb: '',
|
||||
gljg: '教务处', gljgEnabled: false
|
||||
},
|
||||
|
||||
// ==================== 下拉选项(字典数据,接口就绪后可由后端字典加载) ====================
|
||||
pxlxOptions: ['军官基础教育', '士兵职业教育', '研究生教育', '其他'],
|
||||
pxlx2Options: ['军官基础教育', '其他', '任职培训', '研究生教育'],
|
||||
pxccOptions: ['无', '本科', '硕士', '博士'],
|
||||
gljgOptions: ['教务处', '教研室1', '教研室2', '学员队'],
|
||||
|
||||
// ==================== 表格数据 ====================
|
||||
tableData: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
// ==================== 列表加载 ====================
|
||||
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为 getSyllabusList
|
||||
fetchList() {
|
||||
this.tableData = mockTableData.slice()
|
||||
},
|
||||
|
||||
// ==================== 查询 ====================
|
||||
// TODO: 后端接口未提供,查询为前端模拟;接口就绪后替换为后端查询
|
||||
handleQuery() {
|
||||
this.fetchList()
|
||||
this.$message.success('查询教学大纲(前端模拟)')
|
||||
},
|
||||
|
||||
// ==================== 下载 ====================
|
||||
// TODO: 后端接口未提供,下载为前端模拟;接口就绪后替换为 downloadSyllabusList
|
||||
handleDownload() {
|
||||
this.$message.success('下载教学大纲列表(前端模拟)')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.syllabus-page {
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.search-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
|
||||
.notice {
|
||||
font-size: 12px;
|
||||
color: #f56c6c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.list-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<div class="app-container teaching-task-page">
|
||||
<!-- 页面标题 -->
|
||||
<div class="page-title">{{ pageTitle }}</div>
|
||||
|
||||
<!-- 查询条件 -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="110px" class="search-form">
|
||||
<el-row :gutter="32">
|
||||
<!-- 左栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<el-form-item label="授课教员">
|
||||
<el-input v-model="searchForm.skjy" placeholder="请输入授课教员" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="可选班次年级">
|
||||
<el-input v-model="searchForm.kxbcnj" placeholder="请输入可选班次年级" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label">
|
||||
<el-checkbox v-model="searchForm.fzkztEnabled">分组课状态</el-checkbox>
|
||||
</template>
|
||||
<el-select v-model="searchForm.fzkzt" placeholder="请选择分组课状态" class="w-full"
|
||||
:disabled="!searchForm.fzkztEnabled">
|
||||
<el-option v-for="item in groupStatusOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 右栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<el-form-item label="课程科目名称">
|
||||
<el-input v-model="searchForm.kckmmc" placeholder="请输入课程科目名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="分组班名称">
|
||||
<el-input v-model="searchForm.fzbmc" placeholder="请输入分组班名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label">
|
||||
<el-checkbox v-model="searchForm.pxccEnabled">培训层次</el-checkbox>
|
||||
</template>
|
||||
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" class="w-full"
|
||||
:disabled="!searchForm.pxccEnabled">
|
||||
<el-option v-for="item in trainLevelOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="search-tip">
|
||||
注意:勾选"选择框"表示启用该项对应的查询条件;文本输入框非空白表示启用对应的查询条件。
|
||||
</div>
|
||||
<div class="search-actions">
|
||||
<el-button class="gray-btn" @click="handleSearch">查询</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 下载按钮 -->
|
||||
<div class="toolbar-right">
|
||||
<el-button type="primary" @click="handleDownloadGroupCourseList">下载分组班列表</el-button>
|
||||
<el-button type="primary" @click="handleDownloadWord">下载分组班学员名单Word</el-button>
|
||||
<el-button type="primary" @click="handleDownloadExcel">下载分组班学员名单Excel</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 批量操作 -->
|
||||
<el-card shadow="never" class="action-card">
|
||||
<div class="action-groups">
|
||||
<div class="action-group">
|
||||
<el-button type="primary" plain @click="handleNewGroupCourse">新建分组课</el-button>
|
||||
<el-button type="primary" plain @click="handleBatchOpen">所选班早开放报名</el-button>
|
||||
<el-button type="primary" plain @click="handleBatchCancelOpen">所选班早取消开放报名</el-button>
|
||||
</div>
|
||||
<div class="action-group">
|
||||
<el-button type="primary" plain @click="handleBatchStop">批量停止报名</el-button>
|
||||
<el-button type="primary" plain @click="handleBatchReverseRange">所选班早根据学员反向确定班次范围</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 导入数据 -->
|
||||
<el-card shadow="never" class="import-card">
|
||||
<div class="import-toolbar">
|
||||
<el-button type="primary" @click="handleDownloadTemplate">【分组课数据文件模板】下载</el-button>
|
||||
<div class="file-input">
|
||||
<input ref="fileInput" type="file" accept=".xls,.xlsx" style="display: none" @change="handleFileChange" />
|
||||
<el-button type="primary" plain @click="handleChooseFile">选择文件</el-button>
|
||||
<span class="file-name">{{ fileName }}</span>
|
||||
</div>
|
||||
<el-button type="primary" plain @click="handleUploadData">上传数据</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table v-loading="loading" :data="tableData" stripe border highlight-current-row
|
||||
@selection-change="handleSelectionChange">
|
||||
<template slot="empty">
|
||||
<span>无数据!</span>
|
||||
</template>
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column type="index" label="序号" width="100" align="center" />
|
||||
<el-table-column prop="kc" label="课程" width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="jc" label="教材" width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="ssjy" label="实施教员" width="150" align="center" />
|
||||
<el-table-column prop="jxcd" label="教学场地" width="150" align="center" />
|
||||
<el-table-column align="center">
|
||||
<template slot="header">
|
||||
<el-checkbox v-model="showOptionalClasses">显示可选队别班次</el-checkbox>
|
||||
</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.jhxs }}</span>
|
||||
<span>{{ row.zxs }}</span>
|
||||
<span>{{ row.xf }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kkjkrq" label="开课结课日期" width="110" align="center" :formatter="formatDate" />
|
||||
<el-table-column prop="bmrq" label="报名日期" width="120" align="center" :formatter="formatDate" />
|
||||
<el-table-column prop="jhrs" label="计划人数" width="90" align="center" />
|
||||
<el-table-column prop="yqsm" label="要求说明" width="150" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'TeachingTask',
|
||||
data() {
|
||||
return {
|
||||
pageTitle: '分组课列表',
|
||||
|
||||
// ==================== 查询表单 ====================
|
||||
searchForm: {
|
||||
skjy: '',
|
||||
kckmmc: '',
|
||||
kxbcnj: '',
|
||||
fzbmc: '',
|
||||
fzkzt: '拟制',
|
||||
fzkztEnabled: false,
|
||||
pxcc: '无',
|
||||
pxccEnabled: false
|
||||
},
|
||||
groupStatusOptions: ['拟制', '已发布', '已结课'],
|
||||
trainLevelOptions: ['无', '初级', '中级', '高级'],
|
||||
|
||||
// ==================== 批量操作 ====================
|
||||
selectedRows: [],
|
||||
|
||||
// ==================== 表格数据 ====================
|
||||
loading: false,
|
||||
showOptionalClasses: false,
|
||||
/** 模拟列表数据(后端接口未提供) */
|
||||
tableData: [
|
||||
{ xh: 1, kc: '高等数学', jc: '高等数学(上册)', ssjy: '张三', jxcd: '1-301', jhxs: 48, zxs: 46, xf: 3, kkjkrq: '2017-09-03', bmrq: '2017-08-20', jhrs: 40, yqsm: '按教学计划执行' },
|
||||
{ xh: 2, kc: '军事英语', jc: '军事英语教程', ssjy: '李四', jxcd: '2-101', jhxs: 32, zxs: 30, xf: 2, kkjkrq: '2017-09-03', bmrq: '2017-08-22', jhrs: 36, yqsm: '含听力训练' },
|
||||
{ xh: 3, kc: '导弹发射原理', jc: '导弹发射原理', ssjy: '王五', jxcd: '3-205', jhxs: 40, zxs: 40, xf: 2.5, kkjkrq: '2017-09-10', bmrq: '2017-08-25', jhrs: 42, yqsm: '实验课随堂进行' },
|
||||
{ xh: 4, kc: '通信原理', jc: '通信原理', ssjy: '赵六', jxcd: '5-102', jhxs: 48, zxs: 44, xf: 3, kkjkrq: '2017-09-03', bmrq: '2017-08-28', jhrs: 38, yqsm: '' },
|
||||
{ xh: 5, kc: '联合作战指挥', jc: '联合作战指挥', ssjy: '孙七', jxcd: '学术报告厅', jhxs: 40, zxs: 40, xf: 2.5, kkjkrq: '2017-09-17', bmrq: '2017-09-01', jhrs: 45, yqsm: '理论授课+想定作业' }
|
||||
],
|
||||
|
||||
// ==================== 导入 ====================
|
||||
selectedFile: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fileName() {
|
||||
return (this.selectedFile && this.selectedFile.name) || '未选择任何文件'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
/** 表格日期格式化 */
|
||||
formatDate(row, column, cellValue) {
|
||||
return cellValue ? String(cellValue).substring(0, 10) : ''
|
||||
},
|
||||
|
||||
// ==================== 列表加载 ====================
|
||||
// TODO: 后端接口未提供,暂用模拟数据;接口就绪后替换为真实查询接口
|
||||
fetchList() {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, 200)
|
||||
},
|
||||
|
||||
// ==================== 查询 ====================
|
||||
handleSearch() {
|
||||
this.$message.success('查询分组课列表(前端模拟)')
|
||||
},
|
||||
|
||||
// ==================== 批量操作 ====================
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
|
||||
handleNewGroupCourse() {
|
||||
this.$message.success('新建分组课(前端模拟)')
|
||||
},
|
||||
|
||||
handleBatchOpen() {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$message.warning('请先选择数据')
|
||||
return
|
||||
}
|
||||
this.$message.success('所选批量开放报名(前端模拟)')
|
||||
},
|
||||
|
||||
handleBatchCancelOpen() {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$message.warning('请先选择数据')
|
||||
return
|
||||
}
|
||||
this.$message.success('所选批量取消开放报名(前端模拟)')
|
||||
},
|
||||
|
||||
handleBatchStop() {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$message.warning('请先选择数据')
|
||||
return
|
||||
}
|
||||
this.$message.success('批量停止报名(前端模拟)')
|
||||
},
|
||||
|
||||
handleBatchReverseRange() {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$message.warning('请先选择数据')
|
||||
return
|
||||
}
|
||||
this.$message.success('所选批量根据学员反向确定班次范围(前端模拟)')
|
||||
},
|
||||
|
||||
// ==================== 下载按钮 ====================
|
||||
handleDownloadGroupCourseList() {
|
||||
this.$message.success('下载分组课列表(前端模拟)')
|
||||
},
|
||||
|
||||
handleDownloadWord() {
|
||||
this.$message.success('下载分组学员名单Word(前端模拟)')
|
||||
},
|
||||
|
||||
handleDownloadExcel() {
|
||||
this.$message.success('下载分组学员名单Excel(前端模拟)')
|
||||
},
|
||||
|
||||
// ==================== 导入 ====================
|
||||
handleChooseFile() {
|
||||
this.$refs.fileInput && this.$refs.fileInput.click()
|
||||
},
|
||||
|
||||
handleFileChange(e) {
|
||||
const input = e.target
|
||||
if (input.files && input.files.length > 0) {
|
||||
this.selectedFile = input.files[0]
|
||||
}
|
||||
},
|
||||
|
||||
handleDownloadTemplate() {
|
||||
this.$message.success('下载分组课数据文件模板(前端模拟)')
|
||||
},
|
||||
|
||||
handleUploadData() {
|
||||
if (!this.selectedFile) {
|
||||
this.$message.warning('请先选择文件')
|
||||
return
|
||||
}
|
||||
this.$message.success('上传数据(前端模拟)')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.search-tip {
|
||||
font-size: 12px;
|
||||
color: #f56c6c;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.action-groups {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.action-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
|
||||
.el-button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.import-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.import-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.file-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.file-name {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
::v-deep(.el-table) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.triple-header,
|
||||
.triple-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.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,654 @@
|
||||
<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="toolbar-card">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量设置备注信息</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量变更时间</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量平移时间</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量变更授课教员</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量变更授课场地</el-button>
|
||||
<el-button type="danger" size="mini" :disabled="!todaySelected.length" @click="handleComing">批量删除</el-button>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<el-button type="primary" size="mini" @click="handleComing">自定义格式下载</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleComing">下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 今日表格 -->
|
||||
<div class="table-card">
|
||||
<el-table :data="todayList" v-loading="loading" stripe border @selection-change="todaySelected = $event">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column label="上课时间" width="130" align="center">
|
||||
<template slot-scope="scope">{{ formatTime(scope.row.jc) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kcmc" label="课程" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="zrdw" label="责任单位" width="110" align="center" />
|
||||
<el-table-column prop="bc" label="班次" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="skjy" label="教员" width="90" align="center" />
|
||||
<el-table-column label="场地" width="140" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row.jxcd || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="教学内容方法" min-width="160" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row._jxnrff || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jybz" label="备注" width="100" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row.jybz || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划变更明细" width="120" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openDetail(scope.row)">变更明细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openRemark(scope.row)">编辑备注</el-button>
|
||||
<el-button type="text" size="mini" class="danger-text" @click="handleComing">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-show="!loading && !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="toolbar-card">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量设置备注信息</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量变更时间</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量平移时间</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量变更授课教员</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量变更授课场地</el-button>
|
||||
<el-button type="danger" size="mini" :disabled="!weekSelected.length" @click="handleComing">批量删除</el-button>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<el-button type="primary" size="mini" @click="handleComing">报批下载</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleComing">下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 本周表格 -->
|
||||
<div class="table-card">
|
||||
<el-table :data="weekList" v-loading="loading" stripe border @selection-change="weekSelected = $event">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column label="上课时间" width="170" align="center" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row._sjsj || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kcmc" label="课程" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="zrdw" label="责任单位" width="100" align="center" />
|
||||
<el-table-column prop="bc" label="班次" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="skjy" label="教员" width="90" align="center" />
|
||||
<el-table-column label="场地" width="140" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row.jxcd || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="教学内容方法" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row._jxnrff || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jybz" label="备注" width="100" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row.jybz || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划变更明细" width="120" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openDetail(scope.row)">计划变更明细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openRemark(scope.row)">编辑备注</el-button>
|
||||
<el-button type="text" size="mini" class="danger-text" @click="handleComing">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-show="!loading && !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>
|
||||
|
||||
<!-- 批量操作栏 -->
|
||||
<div class="toolbar-card">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-left">
|
||||
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量设置备注信息</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量变更时间</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量平移时间</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量变更授课教员</el-button>
|
||||
<el-button type="primary" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量变更授课场地</el-button>
|
||||
<el-button type="danger" size="mini" :disabled="!practiceSelected.length" @click="handleComing">批量删除</el-button>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<el-button type="primary" size="mini" @click="handleComing">报批下载</el-button>
|
||||
<el-button type="primary" size="mini" @click="handleComing">下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 三实表格 -->
|
||||
<div class="table-card">
|
||||
<el-table :data="practiceList" v-loading="loading" stripe border @selection-change="practiceSelected = $event">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column label="上课时间" width="170" align="center" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row._sjsj || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kcmc" label="课程" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="zrdw" label="责任单位" width="100" align="center" />
|
||||
<el-table-column prop="bc" label="班次" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="skjy" label="教员" width="90" align="center" />
|
||||
<el-table-column label="场地" width="140" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row.jxcd || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="教学内容方法" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row._jxnrff || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jybz" label="备注" width="100" show-overflow-tooltip>
|
||||
<template slot-scope="scope">{{ scope.row.jybz || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="计划变更明细" width="120" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openDetail(scope.row)">计划变更明细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openRemark(scope.row)">编辑备注</el-button>
|
||||
<el-button type="text" size="mini" class="danger-text" @click="handleComing">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-show="!loading && !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="shiftTeamClass" placeholder="请选择队别班次" clearable class="shift-select">
|
||||
<el-option label="一班" value="一班" />
|
||||
<el-option label="二班" value="二班" />
|
||||
<el-option label="三班" value="三班" />
|
||||
<el-option label="四班" value="四班" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleComing">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 标题 -->
|
||||
<div class="shift-title">2026年秋季学期{{ shiftTeamClass || '' }}课程表</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期教学实施计划</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期课程表(周次星期)</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期课程表(班次视图)</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期课程表(排课周)含选修</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期教学实施计划(含选修)</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期课程表(周次星期)详细</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-download" size="mini" @click="handleComing">学期成绩表</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="el-icon-download" size="mini" @click="handleComing">课程列表另存Excel</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 班次课程表 -->
|
||||
<el-table v-loading="loading" :data="shiftList" border stripe>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column label="课程名称/课时系数" min-width="220">
|
||||
<template slot-scope="scope">
|
||||
<div>{{ scope.row.kcmc }}</div>
|
||||
<div class="sub-text">{{ scope.row.kssk }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="zrjs" label="责任教员/课次" min-width="150" />
|
||||
<el-table-column prop="bc" label="班次" min-width="120" />
|
||||
<el-table-column prop="nj" label="年级" width="80" align="center" />
|
||||
<el-table-column prop="zy" label="专业" min-width="120" />
|
||||
<el-table-column prop="jhxs" label="计划学时" width="90" align="center" />
|
||||
<el-table-column prop="yxxs" label="运行学时" width="90" align="center" />
|
||||
<el-table-column prop="khlxfs" label="考核类型方式" min-width="140" />
|
||||
<el-table-column prop="ssjs" label="实施教员" min-width="120" />
|
||||
<el-table-column prop="rscd" label="人数/场地" min-width="140" />
|
||||
<el-table-column prop="kcjd" label="课程进度" width="120" align="center" />
|
||||
<el-table-column prop="ssjhbg" label="实施计划变更" min-width="120" align="center" />
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 编辑备注弹窗 -->
|
||||
<el-dialog title="编辑备注" :visible.sync="remarkVisible" width="500px" append-to-body>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="课程">
|
||||
<span>{{ remarkTarget.kcmc || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注内容">
|
||||
<el-input v-model="remarkText" type="textarea" :rows="4" placeholder="请输入备注内容" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="confirmRemark">确定</el-button>
|
||||
<el-button @click="remarkVisible = false">取消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 计划变更明细弹窗 -->
|
||||
<el-dialog title="计划变更明细" :visible.sync="detailVisible" width="560px" append-to-body>
|
||||
<el-descriptions :column="2" border size="small" v-if="detailTarget">
|
||||
<el-descriptions-item label="课程">{{ detailTarget.kcmc }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班次">{{ detailTarget.bc }}</el-descriptions-item>
|
||||
<el-descriptions-item label="责任单位">{{ detailTarget.zrdw }}</el-descriptions-item>
|
||||
<el-descriptions-item label="上课时间">{{ detailTarget._sjsj || formatTime(detailTarget.jc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原节次">{{ detailTarget.yjc || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="授课教员">{{ detailTarget.skjy }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教学场地">{{ detailTarget.jxcd || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="检查结果">{{ detailTarget.jcjg || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="已通过检查" :span="2">
|
||||
<el-tag :type="detailTarget.ytgjc ? 'success' : 'info'" size="mini">{{ detailTarget.ytgjc ? '是' : '否' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 节次 -> 时间映射
|
||||
const JC_TIME = {
|
||||
1: '08:00-08:45',
|
||||
2: '08:55-09:40',
|
||||
3: '10:00-10:45',
|
||||
4: '10:55-11:40',
|
||||
5: '14:30-15:15',
|
||||
6: '15:25-16:10',
|
||||
7: '16:20-17:05',
|
||||
8: '19:00-19:45',
|
||||
9: '19:55-20:40'
|
||||
}
|
||||
// 节次 -> 节次区间映射
|
||||
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',
|
||||
loading: false,
|
||||
// 今日
|
||||
todayDate: new Date(),
|
||||
todayList: [],
|
||||
todaySelected: [],
|
||||
// 本周
|
||||
weekStart: getMonday(new Date()),
|
||||
weekList: [],
|
||||
weekSelected: [],
|
||||
weekTotal: 0,
|
||||
weekQuery: { pageNum: 1, pageSize: 20 },
|
||||
// 三实
|
||||
practiceStart: getMonday(new Date()),
|
||||
practiceList: [],
|
||||
practiceSelected: [],
|
||||
practiceTotal: 0,
|
||||
practiceQuery: { pageNum: 1, pageSize: 20 },
|
||||
// 班次(仅占位数据,待对接接口)
|
||||
shiftTeamClass: '',
|
||||
shiftList: [],
|
||||
// 弹窗
|
||||
remarkVisible: false,
|
||||
remarkTarget: {},
|
||||
remarkText: '',
|
||||
detailVisible: false,
|
||||
detailTarget: null
|
||||
}
|
||||
},
|
||||
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) + ') 三实教学实施计划'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getTodayList()
|
||||
this.getWeekList()
|
||||
this.getPracticeList()
|
||||
},
|
||||
methods: {
|
||||
weekDayName(d) {
|
||||
return WEEK_DAY_NAMES[d.getDay()]
|
||||
},
|
||||
formatTime(jc) {
|
||||
if (!jc) return '-'
|
||||
return JC_TIME[jc] || ('第' + jc + '节')
|
||||
},
|
||||
formatSection(jc) {
|
||||
if (!jc) return ''
|
||||
return JC_SECTION[jc] || ('第' + jc + '节')
|
||||
},
|
||||
buildSjsj(rq, jc) {
|
||||
const date = (rq || '').substring(0, 10)
|
||||
if (!date) return '-'
|
||||
const d = new Date(date.replace(/-/g, '/'))
|
||||
const wn = WEEK_DAY_NAMES[d.getDay()]
|
||||
const section = this.formatSection(jc)
|
||||
return (date + ' 周' + wn + ' ' + section).trim()
|
||||
},
|
||||
/* ---------- 今日 ---------- */
|
||||
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() {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.todayList = this.buildMockList(this.todayDate)
|
||||
this.loading = false
|
||||
}, 200)
|
||||
},
|
||||
/* ---------- 本周 ---------- */
|
||||
handlePrevWeek() {
|
||||
const d = new Date(this.weekStart)
|
||||
d.setDate(d.getDate() - 7)
|
||||
this.weekStart = d
|
||||
this.getWeekList()
|
||||
},
|
||||
handleNextWeek() {
|
||||
const d = new Date(this.weekStart)
|
||||
d.setDate(d.getDate() + 7)
|
||||
this.weekStart = d
|
||||
this.getWeekList()
|
||||
},
|
||||
getWeekList() {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.weekList = this.buildMockWeekList(this.weekStart)
|
||||
this.weekTotal = this.weekList.length
|
||||
this.loading = false
|
||||
}, 200)
|
||||
},
|
||||
/* ---------- 三实 ---------- */
|
||||
handlePrevWeek3() {
|
||||
const d = new Date(this.practiceStart)
|
||||
d.setDate(d.getDate() - 7)
|
||||
this.practiceStart = d
|
||||
this.getPracticeList()
|
||||
},
|
||||
handleNextWeek3() {
|
||||
const d = new Date(this.practiceStart)
|
||||
d.setDate(d.getDate() + 7)
|
||||
this.practiceStart = d
|
||||
this.getPracticeList()
|
||||
},
|
||||
getPracticeList() {
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.practiceList = this.buildMockWeekList(this.practiceStart)
|
||||
this.practiceTotal = this.practiceList.length
|
||||
this.loading = false
|
||||
}, 200)
|
||||
},
|
||||
/* ---------- 静态示例数据(待对接接口) ---------- */
|
||||
buildMockList(date) {
|
||||
const dateStr = formatDate(date)
|
||||
const samples = [
|
||||
{ jc: 1, kcmc: '高等数学', zrdw: '基础部', bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', jxnr: '函数与极限', jxff: '讲授', jybz: '携带教材' },
|
||||
{ jc: 3, kcmc: '大学英语', zrdw: '基础部', bc: '二班', skjy: '李教员', jxcd: '2号教学楼-205', jxnr: 'Unit 3 课文精读', jxff: '讲授/讨论', jybz: '' },
|
||||
{ jc: 5, kcmc: '计算机基础', zrdw: '信息工程系', bc: '三班', skjy: '王教员', jxcd: '实验楼-303', jxnr: 'Word 文档排版', jxff: '实操', jybz: '机房上课' }
|
||||
]
|
||||
return samples.map((item, index) => ({
|
||||
...item,
|
||||
xh: index + 1,
|
||||
rq: dateStr,
|
||||
_jxnrff: [item.jxnr, item.jxff].filter(Boolean).join(' / ')
|
||||
}))
|
||||
},
|
||||
buildMockWeekList(weekStart) {
|
||||
const list = []
|
||||
const dates = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const d = new Date(weekStart)
|
||||
d.setDate(d.getDate() + i)
|
||||
dates.push(formatDate(d))
|
||||
}
|
||||
const courses = [
|
||||
{ jc: 1, kcmc: '高等数学', zrdw: '基础部', bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', jxnr: '函数与极限', jxff: '讲授', jybz: '携带教材' },
|
||||
{ jc: 2, kcmc: '大学英语', zrdw: '基础部', bc: '二班', skjy: '李教员', jxcd: '2号教学楼-205', jxnr: 'Unit 3 课文精读', jxff: '讲授/讨论', jybz: '' },
|
||||
{ jc: 3, kcmc: '计算机基础', zrdw: '信息工程系', bc: '三班', skjy: '王教员', jxcd: '实验楼-303', jxnr: 'Word 文档排版', jxff: '实操', jybz: '机房上课' },
|
||||
{ jc: 4, kcmc: '军事理论', zrdw: '政工系', bc: '一班', skjy: '赵教员', jxcd: '3号教学楼-110', jxnr: '国防动员概述', jxff: '讲授', jybz: '' }
|
||||
]
|
||||
dates.forEach((dateStr, i) => {
|
||||
courses.forEach((item, idx) => {
|
||||
// 隔天轮换部分课程,保证每周有内容且不重复
|
||||
if ((i + idx) % 2 === 0) {
|
||||
list.push({
|
||||
...item,
|
||||
xh: list.length + 1,
|
||||
rq: dateStr,
|
||||
_sjsj: this.buildSjsj(dateStr, item.jc),
|
||||
_jxnrff: [item.jxnr, item.jxff].filter(Boolean).join(' / ')
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
return list
|
||||
},
|
||||
/* ---------- 弹窗 ---------- */
|
||||
openRemark(row) {
|
||||
this.remarkTarget = row
|
||||
this.remarkText = row.jybz || ''
|
||||
this.remarkVisible = true
|
||||
},
|
||||
confirmRemark() {
|
||||
this.$message.success('备注已更新')
|
||||
this.remarkVisible = false
|
||||
},
|
||||
openDetail(row) {
|
||||
this.detailTarget = row
|
||||
this.detailVisible = true
|
||||
},
|
||||
handleComing() {
|
||||
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;
|
||||
}
|
||||
|
||||
/* 日期/周次导航 */
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 批量操作栏 */
|
||||
.toolbar-card {
|
||||
margin-bottom: 12px;
|
||||
padding: 10px 14px;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
|
||||
.toolbar-left,
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格区 */
|
||||
.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>
|
||||
@@ -0,0 +1,415 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 近期申请统计卡片 -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">近期课表调整申请</div>
|
||||
<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">
|
||||
<el-form-item label="课程名称">
|
||||
<el-input v-model="queryParams.kcmc" placeholder="请输入课程名称" clearable style="width: 180px" @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="队别(班次)">
|
||||
<el-input v-model="queryParams.dbbc" placeholder="请输入队别班次" clearable style="width: 160px" @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请教员">
|
||||
<el-input v-model="queryParams.sqjy" placeholder="请输入申请教员" clearable style="width: 160px" @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 140px">
|
||||
<el-option v-for="item in statusOptions" :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="filteredData" 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="课程名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="jxsj" label="教学时间" width="110" align="center" />
|
||||
<el-table-column prop="sksj" label="上课时间" width="180" />
|
||||
<el-table-column prop="jy" label="教员" width="90" align="center" />
|
||||
<el-table-column prop="jxcd" label="教学场地" width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="dbbc" label="队别(班次)" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="sqjy" label="申请教员" width="90" align="center" />
|
||||
<el-table-column prop="zxkb" label="拟调整时间" width="180" />
|
||||
<el-table-column label="申请类型" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="typeTag(scope.row.lx)" size="mini">{{ scope.row.lx }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusTag(scope.row.status).type" size="mini">{{ statusTag(scope.row.status).label }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">查看</el-button>
|
||||
<template v-if="scope.row.status === '0'">
|
||||
<el-button type="text" size="mini" icon="el-icon-check" @click="approve(scope.row)">批准</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-close" class="danger-btn" @click="reject(scope.row)">驳回</el-button>
|
||||
</template>
|
||||
<el-button v-else type="text" size="mini" class="danger-btn" @click="removeRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-show="!loading && filteredData.length === 0" description="暂无调整申请" />
|
||||
</div>
|
||||
|
||||
<!-- 新增申请弹窗 -->
|
||||
<el-dialog title="新增课表调整申请" :visible.sync="dialogVisible" width="640px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="课程名称" prop="kcmc">
|
||||
<el-input v-model="form.kcmc" placeholder="请输入课程名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请类型" prop="lx">
|
||||
<el-radio-group v-model="form.lx">
|
||||
<el-radio label="调课" />
|
||||
<el-radio label="停课" />
|
||||
<el-radio label="补课" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="队别(班次)" prop="dbbc">
|
||||
<el-input v-model="form.dbbc" placeholder="请输入队别班次" />
|
||||
</el-form-item>
|
||||
<el-form-item label="原上课时间" prop="sksj">
|
||||
<el-input v-model="form.sksj" placeholder="如:周一 第3-4节" />
|
||||
</el-form-item>
|
||||
<el-form-item label="拟调整时间" prop="zxkb">
|
||||
<el-input v-model="form.zxkb" placeholder="请输入拟调整后的上课时间" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请理由" prop="bz">
|
||||
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="请输入申请理由" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitForm">提交申请</el-button>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看详情弹窗 -->
|
||||
<el-dialog title="申请详情" :visible.sync="detailVisible" width="580px" append-to-body>
|
||||
<el-descriptions v-if="detailTarget" :column="2" border size="small">
|
||||
<el-descriptions-item label="课程名称">{{ detailTarget.kcmc }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请类型">
|
||||
<el-tag :type="typeTag(detailTarget.lx)" size="mini">{{ detailTarget.lx }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="队别(班次)">{{ detailTarget.dbbc }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教员">{{ detailTarget.jy }} / {{ detailTarget.sqjy }}</el-descriptions-item>
|
||||
<el-descriptions-item label="教学场地">{{ detailTarget.jxcd }}</el-descriptions-item>
|
||||
<el-descriptions-item label="执行时间">{{ detailTarget.zxsj }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原上课时间" :span="2">{{ detailTarget.sksj }}</el-descriptions-item>
|
||||
<el-descriptions-item label="拟调整时间" :span="2">{{ detailTarget.zxkb }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请理由" :span="2">{{ detailTarget.bz }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 课表调整申请静态示例数据(待对接 getTimetableAdjustList 接口)
|
||||
const MOCK_LIST = [
|
||||
{ id: 1, kcmc: '高等数学', lx: '调课', jxsj: '2026-2027-1', sksj: '周一 第1-2节', zxkb: '周二 第3-4节', jy: '张教员', sqjy: '张教员', jxcd: '1号教学楼-101', dbbc: '一班', zxsj: '2026-09-01', bz: '因学院集中活动调整上课时间', status: '0' },
|
||||
{ id: 2, kcmc: '大学英语', lx: '停课', jxsj: '2026-2027-1', sksj: '周三 第5-6节', zxkb: '—', jy: '李教员', sqjy: '王教员', jxcd: '1号教学楼-205', dbbc: '二班', zxsj: '2026-09-03', bz: '教员参加教研会议,申请停课顺延', status: '1' },
|
||||
{ id: 3, kcmc: '计算机基础', lx: '补课', jxsj: '2026-2027-1', sksj: '周五 第7-8节', zxkb: '周六 第1-2节', jy: '王教员', sqjy: '王教员', jxcd: '实验楼-303', dbbc: '三班', zxsj: '2026-09-04', bz: '补上周五因停课落下的课程', status: '1' },
|
||||
{ id: 4, kcmc: '军事理论', lx: '调课', jxsj: '2026-2027-1', sksj: '周四 第3-4节', zxkb: '周五 第3-4节', jy: '赵教员', sqjy: '赵教员', jxcd: '3号教学楼-110', dbbc: '一班', zxsj: '2026-09-05', bz: '场地临时被占用,调换上课时间', status: '2' },
|
||||
{ id: 5, kcmc: '高等数学', lx: '补课', jxsj: '2026-2027-1', sksj: '周二 第1-2节', zxkb: '周四 第7-8节', jy: '张教员', sqjy: '张教员', jxcd: '1号教学楼-101', dbbc: '二班', zxsj: '2026-09-06', bz: '补国庆假期调休停上的课程', status: '0' }
|
||||
]
|
||||
|
||||
// 状态描述
|
||||
const STATUS_MAP = {
|
||||
'0': { label: '待审批', type: 'warning' },
|
||||
'1': { label: '已批准', type: 'success' },
|
||||
'2': { label: '已驳回', type: 'danger' }
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'TimetableAdjust',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
submitLoading: false,
|
||||
allData: [],
|
||||
queryParams: {
|
||||
kcmc: undefined,
|
||||
dbbc: undefined,
|
||||
sqjy: undefined,
|
||||
status: undefined
|
||||
},
|
||||
statusOptions: [
|
||||
{ label: '待审批', value: '0' },
|
||||
{ label: '已批准', value: '1' },
|
||||
{ label: '已驳回', value: '2' }
|
||||
],
|
||||
statCards: [],
|
||||
dialogVisible: false,
|
||||
detailVisible: false,
|
||||
detailTarget: null,
|
||||
form: {},
|
||||
rules: {
|
||||
kcmc: [{ required: true, message: '请输入课程名称', trigger: 'blur' }],
|
||||
lx: [{ required: true, message: '请选择申请类型', trigger: 'change' }],
|
||||
dbbc: [{ required: true, message: '请输入队别班次', trigger: 'blur' }],
|
||||
bz: [{ required: true, message: '请输入申请理由', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredData() {
|
||||
const q = this.queryParams
|
||||
return this.allData.filter(item => {
|
||||
if (q.kcmc && !(item.kcmc || '').includes(q.kcmc)) return false
|
||||
if (q.dbbc && !(item.dbbc || '').includes(q.dbbc)) return false
|
||||
if (q.sqjy && !(item.sqjy || '').includes(q.sqjy)) return false
|
||||
if (q.status !== undefined && q.status !== '' && item.status !== q.status) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
typeTag(lx) {
|
||||
const map = { '调课': 'warning', '停课': 'danger', '补课': 'success' }
|
||||
return map[lx] || 'info'
|
||||
},
|
||||
statusTag(status) {
|
||||
return STATUS_MAP[status] || { label: status, type: 'info' }
|
||||
},
|
||||
loadData() {
|
||||
this.loading = true
|
||||
// 模拟接口延迟,后续替换为真实接口
|
||||
setTimeout(() => {
|
||||
this.allData = MOCK_LIST.map((item, index) => ({ ...item, xh: index + 1 }))
|
||||
this.refreshStatCards()
|
||||
this.loading = false
|
||||
}, 200)
|
||||
},
|
||||
refreshStatCards() {
|
||||
const total = this.allData.length
|
||||
const pending = this.allData.filter(i => i.status === '0').length
|
||||
const approved = this.allData.filter(i => i.status === '1').length
|
||||
const rejected = this.allData.filter(i => i.status === '2').length
|
||||
this.statCards = [
|
||||
{ title: '申请总数', value: total, gradient: 'linear-gradient(135deg, #00875a 0%, #00b877 100%)' },
|
||||
{ title: '待审批', value: pending, gradient: 'linear-gradient(135deg, #e6a23c 0%, #f8b04c 100%)' },
|
||||
{ title: '已批准', value: approved, gradient: 'linear-gradient(135deg, #409eff 0%, #66b1ff 100%)' },
|
||||
{ title: '已驳回', value: rejected, gradient: 'linear-gradient(135deg, #f56c6c 0%, #f78989 100%)' }
|
||||
]
|
||||
},
|
||||
handleStatClick(card) {
|
||||
const statusMap = { '待审批': '0', '已批准': '1', '已驳回': '2' }
|
||||
if (statusMap[card.title]) {
|
||||
this.queryParams.status = this.queryParams.status === statusMap[card.title] ? undefined : statusMap[card.title]
|
||||
this.$message.info(`已按「${card.title}」筛选`)
|
||||
}
|
||||
},
|
||||
handleQuery() {
|
||||
this.$message.success('查询完成')
|
||||
},
|
||||
resetQuery() {
|
||||
this.queryParams = {
|
||||
kcmc: undefined,
|
||||
dbbc: undefined,
|
||||
sqjy: undefined,
|
||||
status: undefined
|
||||
}
|
||||
},
|
||||
openAdd() {
|
||||
this.form = { kcmc: '', lx: '调课', dbbc: '', sksj: '', zxkb: '', bz: '' }
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
this.dialogVisible = true
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
this.submitLoading = true
|
||||
setTimeout(() => {
|
||||
const nextId = this.allData.length ? Math.max(...this.allData.map(i => i.id)) + 1 : 1
|
||||
this.allData.unshift({
|
||||
id: nextId,
|
||||
kcmc: this.form.kcmc,
|
||||
lx: this.form.lx,
|
||||
jxsj: '2026-2027-1',
|
||||
sksj: this.form.sksj || '待定',
|
||||
zxkb: this.form.lx === '停课' ? '—' : (this.form.zxkb || '待定'),
|
||||
jy: '-',
|
||||
sqjy: this.form.sqjy || '-',
|
||||
jxcd: '-',
|
||||
dbbc: this.form.dbbc,
|
||||
zxsj: this.getNow(),
|
||||
bz: this.form.bz,
|
||||
status: '0'
|
||||
})
|
||||
this.refreshStatCards()
|
||||
this.submitLoading = false
|
||||
this.dialogVisible = false
|
||||
this.$message.success('申请已提交')
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
getNow() {
|
||||
const d = new Date()
|
||||
const p = n => (n < 10 ? '0' + n : '' + n)
|
||||
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate())
|
||||
},
|
||||
approve(row) {
|
||||
row.status = '1'
|
||||
this.refreshStatCards()
|
||||
this.$message.success('已批准该申请')
|
||||
},
|
||||
reject(row) {
|
||||
row.status = '2'
|
||||
this.refreshStatCards()
|
||||
this.$message.warning('已驳回该申请')
|
||||
},
|
||||
removeRow(row) {
|
||||
this.$confirm('确认删除该申请?', '提示', { type: 'warning' })
|
||||
.then(() => {
|
||||
this.allData = this.allData.filter(i => i.id !== row.id)
|
||||
this.refreshStatCards()
|
||||
this.$message.success('删除成功')
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
openDetail(row) {
|
||||
this.detailTarget = row
|
||||
this.detailVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.page-container .stat-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div class="app-container conflict-page">
|
||||
<!-- 冲突检查区域 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<div class="section-title">教学资源冲突检查</div>
|
||||
<div class="section-desc">2026年秋季学期教学资源冲突检查,可单独检查或执行全部检查</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button size="mini" :disabled="loading" @click="handleReset">重置</el-button>
|
||||
<el-button type="primary" size="mini" :loading="loading" @click="handleCheckAll">执行全部检查</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 检查项卡片网格 -->
|
||||
<div class="check-grid">
|
||||
<div
|
||||
v-for="item in conflictItems"
|
||||
:key="item.key"
|
||||
class="check-item-card"
|
||||
:class="{ active: activeKey === item.key }"
|
||||
@click="activeKey = item.checked ? item.key : activeKey"
|
||||
>
|
||||
<div class="check-item-header">
|
||||
<span class="check-item-name">{{ item.label }}</span>
|
||||
<el-tag :type="getStatus(item).type" size="mini">{{ getStatus(item).text }}</el-tag>
|
||||
</div>
|
||||
<div class="check-item-desc">{{ item.desc }}</div>
|
||||
<div class="check-item-footer">
|
||||
<span class="conflict-count">
|
||||
冲突数:
|
||||
<b :class="item.count > 0 ? 'danger-text' : 'normal-text'">{{ item.count }}</b>
|
||||
</span>
|
||||
<el-button type="primary" plain size="mini" :disabled="loading" @click.stop="handleCheck(item)">检查</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 冲突明细区域 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header detail-header">
|
||||
<div class="section-title">
|
||||
冲突明细
|
||||
<span v-if="activeKey" class="active-label">— {{ activeLabel }}</span>
|
||||
</div>
|
||||
<span v-if="activeKey" class="detail-count">共 {{ activeDetails.length }} 条记录</span>
|
||||
</div>
|
||||
<el-table v-if="activeKey" :data="activeDetails" border stripe size="mini" max-height="500">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column label="冲突号" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="danger" size="mini">#{{ Math.floor(scope.$index / 2) + 1 }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kcmc" label="课程" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="日期" width="100">
|
||||
<template slot-scope="scope">{{ formatDateStr(scope.row.rq) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jc" label="节次" width="70" align="center" />
|
||||
<el-table-column prop="bc" label="班次" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="skjy" label="教员" width="90" />
|
||||
<el-table-column prop="jxcd" label="场地" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="zrdw" label="责任单位" width="100" />
|
||||
</el-table>
|
||||
<el-empty v-else v-show="!loading" description="点击上方卡片「检查」按钮,在此查看冲突明细" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 教学计划静态示例数据(待对接 getTeachingPlanList 接口)
|
||||
const MOCK_PLANS = [
|
||||
{ kcmc: '高等数学', rq: '2026-09-01', jc: 1, bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', zrdw: '基础部' },
|
||||
{ kcmc: '高等数学', rq: '2026-09-01', jc: 1, bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', zrdw: '基础部' },
|
||||
{ kcmc: '大学英语', rq: '2026-09-01', jc: 1, bc: '二班', skjy: '李教员', jxcd: '1号教学楼-101', zrdw: '基础部' },
|
||||
{ kcmc: '大学英语', rq: '2026-09-02', jc: 3, bc: '二班', skjy: '李教员', jxcd: '2号教学楼-205', zrdw: '基础部' },
|
||||
{ kcmc: '计算机基础', rq: '2026-09-01', jc: 1, bc: '三班', skjy: '王教员', jxcd: '实验楼-303', zrdw: '信息工程系' },
|
||||
{ kcmc: '军事理论', rq: '2026-09-02', jc: 3, bc: '一班', skjy: '赵教员', jxcd: '3号教学楼-110', zrdw: '政工系' }
|
||||
]
|
||||
|
||||
function formatDateStr(rq) {
|
||||
return (rq || '').substring(0, 10)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'TimetableConflict',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
allRecords: [],
|
||||
activeKey: '',
|
||||
conflictItems: [
|
||||
{ key: 'jxbssj', label: '教学班次时间冲突检查', desc: '同一教学班次在同一时间段被安排多门课程', count: 0, checked: false, details: [] },
|
||||
{ key: 'jxbskcsj', label: '教学班次必修与选修时间冲突检查', desc: '教学班次必修课与选修课时间重叠', count: 0, checked: false, details: [] },
|
||||
{ key: 'jysj', label: '教员时间冲突检查', desc: '同一教员在同一时间段被安排多门课程', count: 0, checked: false, details: [] },
|
||||
{ key: 'jssj', label: '教室时间冲突检查', desc: '同一教室在同一时间段被多门课程占用', count: 0, checked: false, details: [] }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeLabel() {
|
||||
const item = this.conflictItems.find(i => i.key === this.activeKey)
|
||||
return item ? item.label : ''
|
||||
},
|
||||
// 当前选中检查项的展开明细(打平所有冲突记录)
|
||||
activeDetails() {
|
||||
const item = this.conflictItems.find(i => i.key === this.activeKey)
|
||||
if (!item) return []
|
||||
const flat = []
|
||||
item.details.forEach(d => {
|
||||
d.items.forEach(it => flat.push(it))
|
||||
})
|
||||
return flat
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始化演示数据(模拟接口返回)
|
||||
this.allRecords = MOCK_PLANS.map((item, index) => ({ ...item, xh: index + 1 }))
|
||||
},
|
||||
methods: {
|
||||
getStatus(item) {
|
||||
if (!item.checked) return { type: 'info', text: '未检查' }
|
||||
return item.count > 0
|
||||
? { type: 'danger', text: '存在 ' + item.count + ' 处冲突' }
|
||||
: { type: 'success', text: '无冲突' }
|
||||
},
|
||||
/** 通用冲突检测:按分组字段找 > 1 的记录 */
|
||||
detectConflicts(records, groupFields) {
|
||||
const map = {}
|
||||
records.forEach(item => {
|
||||
const key = groupFields.map(f => f + '=' + (item[f] !== undefined && item[f] !== null ? item[f] : '')).join('|') +
|
||||
'|rq=' + formatDateStr(item.rq) + '|jc=' + (item.jc !== undefined && item.jc !== null ? item.jc : '')
|
||||
if (!map[key]) map[key] = []
|
||||
map[key].push(item)
|
||||
})
|
||||
return Object.keys(map)
|
||||
.filter(k => map[k].length > 1)
|
||||
.map(k => ({ key: k, items: map[k] }))
|
||||
.sort((a, b) => a.key.localeCompare(b.key))
|
||||
},
|
||||
handleCheck(item) {
|
||||
if (this.allRecords.length === 0) return
|
||||
let details = []
|
||||
switch (item.key) {
|
||||
case 'jxbssj':
|
||||
details = this.detectConflicts(this.allRecords, ['bc'])
|
||||
break
|
||||
case 'jxbskcsj':
|
||||
details = this.detectConflicts(this.allRecords, ['bc'])
|
||||
break
|
||||
case 'jysj':
|
||||
details = this.detectConflicts(this.allRecords, ['skjy'])
|
||||
break
|
||||
case 'jssj':
|
||||
details = this.detectConflicts(this.allRecords, ['jxcd'])
|
||||
break
|
||||
}
|
||||
item.details = details
|
||||
item.count = details.length
|
||||
item.checked = true
|
||||
this.activeKey = item.key
|
||||
if (item.count > 0) {
|
||||
this.$message.warning('「' + item.label + '」发现 ' + item.count + ' 处冲突')
|
||||
} else {
|
||||
this.$message.success('「' + item.label + '」未发现冲突')
|
||||
}
|
||||
},
|
||||
handleCheckAll() {
|
||||
this.conflictItems.forEach(item => this.handleCheck(item))
|
||||
const firstConflict = this.conflictItems.find(i => i.count > 0)
|
||||
if (firstConflict) {
|
||||
this.activeKey = firstConflict.key
|
||||
}
|
||||
const total = this.conflictItems.reduce((sum, item) => sum + item.count, 0)
|
||||
this.$message.success('全部检查完成,共发现 ' + total + ' 处冲突')
|
||||
},
|
||||
handleReset() {
|
||||
this.conflictItems.forEach(item => {
|
||||
item.count = 0
|
||||
item.checked = false
|
||||
item.details = []
|
||||
})
|
||||
this.activeKey = ''
|
||||
this.$message.info('已重置检查结果')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.conflict-page {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 检查项卡片网格 */
|
||||
.check-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.check-item-card {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
padding: 14px 16px;
|
||||
transition: box-shadow 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--edu-green-primary, #00875a);
|
||||
box-shadow: 0 0 0 2px rgba(0, 135, 90, 0.15);
|
||||
}
|
||||
|
||||
.check-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
|
||||
.check-item-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.check-item-desc {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.check-item-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
|
||||
.conflict-count {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
|
||||
b {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
color: #f56c6c;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.normal-text {
|
||||
color: #67c23a;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.active-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--edu-green-primary, #00875a);
|
||||
}
|
||||
|
||||
.detail-count {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,475 @@
|
||||
<template>
|
||||
<div class="app-container training-plan-page">
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="searchForm" label-width="120px" class="search-form">
|
||||
<el-row :gutter="32">
|
||||
<!-- 左栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<el-form-item label="专业名称">
|
||||
<el-input v-model="searchForm.zymc" placeholder="请输入专业名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业方向">
|
||||
<el-input v-model="searchForm.zyfx" placeholder="请输入专业方向" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label">
|
||||
<el-checkbox v-model="searchForm.pxlxEnabled">培训类型</el-checkbox>
|
||||
</template>
|
||||
<el-select v-model="searchForm.pxlx" placeholder="请选择培训类型" clearable class="w-full"
|
||||
:disabled="!searchForm.pxlxEnabled">
|
||||
<el-option v-for="item in pxlxOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label">
|
||||
<el-checkbox v-model="searchForm.pxccEnabled">培训层次</el-checkbox>
|
||||
</template>
|
||||
<el-select v-model="searchForm.pxcc" placeholder="请选择培训层次" clearable class="w-full"
|
||||
:disabled="!searchForm.pxccEnabled">
|
||||
<el-option v-for="item in pxccOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="自定义分类">
|
||||
<el-input v-model="searchForm.zdyfl" placeholder="请输入自定义分类" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label">
|
||||
<el-checkbox v-model="searchForm.zgzyEnabled">主干专业</el-checkbox>
|
||||
</template>
|
||||
<el-select v-model="searchForm.zgzy" placeholder="请选择" clearable class="w-full"
|
||||
:disabled="!searchForm.zgzyEnabled">
|
||||
<el-option v-for="item in zgzyOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 右栏 -->
|
||||
<el-col :xs="24" :md="12">
|
||||
<el-form-item label="专业代码">
|
||||
<el-input v-model="searchForm.zydm" placeholder="请输入专业代码" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="学年制">
|
||||
<el-input v-model="searchForm.xnz" placeholder="请输入学年制" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label">
|
||||
<el-checkbox v-model="searchForm.pxlx2Enabled">培训类型2</el-checkbox>
|
||||
</template>
|
||||
<el-select v-model="searchForm.pxlx2" placeholder="请选择培训类型2" clearable class="w-full"
|
||||
:disabled="!searchForm.pxlx2Enabled">
|
||||
<el-option v-for="item in pxlx2Options" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template slot="label">
|
||||
<el-checkbox v-model="searchForm.qyztEnabled">启用状态</el-checkbox>
|
||||
</template>
|
||||
<el-radio-group v-model="searchForm.qyzt" :disabled="!searchForm.qyztEnabled">
|
||||
<el-radio :label="'启用'">启用</el-radio>
|
||||
<el-radio :label="'停用'">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="大纲版本">
|
||||
<el-input v-model="searchForm.zybb" placeholder="请输入大纲版本" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="培养目标">
|
||||
<el-input v-model="searchForm.pymb" placeholder="请输入培养目标" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="search-footer">
|
||||
<div class="notice">注意:勾选"选择框"表示启用该项对应的查询条件。</div>
|
||||
<div class="search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 2. 操作与上传区域 ==================== -->
|
||||
<el-card shadow="never" class="action-card">
|
||||
<div class="action-row">
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleOpenAdd">新建</el-button>
|
||||
<el-button icon="el-icon-download" @click="handleDownload">下载</el-button>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<el-button type="primary" @click="handleTemplateDownload">【人才培养方案目录模板】下载</el-button>
|
||||
</div>
|
||||
<div class="upload-row">
|
||||
<div class="upload-left">
|
||||
<el-button icon="el-icon-folder-opened" @click="handleChooseFile">选择文件</el-button>
|
||||
<span class="file-name" :class="{ 'has-file': selectedFile }">
|
||||
<template v-if="selectedFile">{{ fileName }}</template>
|
||||
<template v-else>请选择要上传的文件</template>
|
||||
</span>
|
||||
<input ref="fileInputRef" type="file" style="display: none" @change="handleFileChange" />
|
||||
</div>
|
||||
<div class="upload-right">
|
||||
<el-button type="primary" icon="el-icon-upload2" :disabled="!selectedFile" @click="handleUpload">上传数据</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 数据表格区域 ==================== -->
|
||||
<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="jxgljg" label="教学管理机构" show-overflow-tooltip />
|
||||
<el-table-column prop="xkzyxx" label="学科专业信息" show-overflow-tooltip />
|
||||
<el-table-column prop="rpyx" label="人培信息" width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="fl" label="分类" align="center" />
|
||||
<el-table-column prop="xylb" label="学员类别" align="center" />
|
||||
<el-table-column prop="xnz" label="学年制" align="center" />
|
||||
<el-table-column prop="xsfb" label="学时分布" align="center" />
|
||||
<el-table-column prop="zt" label="状态" align="center" />
|
||||
<el-table-column prop="wjzl" label="文件资料" align="center" />
|
||||
<el-table-column label="操作" align="center" fixed="right" width="140">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button type="text" style="color: #f56c6c" :disabled="scope.row.ty === 1 || scope.row.zt === '停用'"
|
||||
@click="handleDisable(scope.row)">
|
||||
停用
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</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.sync="dialogVisible" :title="dialogTitle" width="1000px" :align-center="true"
|
||||
:destroy-on-close="true">
|
||||
<div v-loading="editLoading">
|
||||
<el-form ref="addFormRef" :model="addForm" :rules="rules" label-width="140px" class="add-form">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业代号" prop="zydh">
|
||||
<el-input v-model="addForm.zydh" placeholder="请输入专业代号" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业名称" prop="zymc">
|
||||
<el-input v-model="addForm.zymc" placeholder="请输入专业名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业方向">
|
||||
<el-input v-model="addForm.zyfx" placeholder="请输入专业方向" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业代码" prop="zydm">
|
||||
<el-input v-model="addForm.zydm" placeholder="请输入专业代码" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业版本">
|
||||
<el-input v-model="addForm.zybb" placeholder="请输入专业版本" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训层次" prop="pxcc">
|
||||
<el-input v-model="addForm.pxcc" placeholder="硕士研究生/博士研究生/本科" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训类型" prop="pxlx">
|
||||
<el-input v-model="addForm.pxlx" placeholder="学历教育/非学历教育" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="培训类型2">
|
||||
<el-input v-model="addForm.pxlx2" placeholder="全日制/非全日制" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员类别">
|
||||
<el-input v-model="addForm.xylb" placeholder="请输入学员类别" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学年制">
|
||||
<el-input v-model="addForm.xnz" placeholder="两年半/三年/四年" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学期数">
|
||||
<el-input-number v-model="addForm.xqs" :min="1" :max="12" class="w-full" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="自定义分类">
|
||||
<el-input v-model="addForm.zdyfl" placeholder="兵器类/电子信息类" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学管理机构编号">
|
||||
<el-input v-model="addForm.jxgljgbh" placeholder="请输入" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="规范名称">
|
||||
<el-input v-model="addForm.gfmc" placeholder="请输入规范名称" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业备注">
|
||||
<el-input v-model="addForm.zybz" placeholder="请输入备注" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="培养目标" prop="pymb">
|
||||
<el-input v-model="addForm.pymb" type="textarea" :rows="3" placeholder="请输入培养目标" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" :loading="addLoading" @click="handleSubmit">保存</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 各下拉框选项:数据待后端接口返回后替换
|
||||
const PXLX_OPTIONS = [] // 培训类型
|
||||
const PXCC_OPTIONS = [] // 培训层次
|
||||
const ZGZY_OPTIONS = [] // 主干专业
|
||||
const PXLX2_OPTIONS = [] // 培训类型2
|
||||
|
||||
export default {
|
||||
name: 'TrainingPlan',
|
||||
data() {
|
||||
return {
|
||||
options: {
|
||||
pxlx: PXLX_OPTIONS,
|
||||
pxcc: PXCC_OPTIONS,
|
||||
zgzy: ZGZY_OPTIONS,
|
||||
pxlx2: PXLX2_OPTIONS
|
||||
},
|
||||
// 查询条件
|
||||
searchForm: {
|
||||
zymc: '', zyfx: '',
|
||||
pxlxEnabled: false, pxlx: '',
|
||||
pxccEnabled: false, pxcc: '',
|
||||
zdyfl: '',
|
||||
zgzyEnabled: false, zgzy: '',
|
||||
zydm: '', xnz: '',
|
||||
pxlx2Enabled: false, pxlx2: '',
|
||||
qyztEnabled: false, qyzt: '启用',
|
||||
zybb: '', pymb: ''
|
||||
},
|
||||
// 列表
|
||||
loading: false,
|
||||
tableData: [],
|
||||
selectedRows: [],
|
||||
total: 0,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
// 上传
|
||||
selectedFile: null,
|
||||
fileName: '',
|
||||
// 新增/编辑弹窗
|
||||
dialogVisible: false,
|
||||
dialogTitle: '新建',
|
||||
editLoading: false,
|
||||
addLoading: false,
|
||||
addForm: {
|
||||
zydh: '', zymc: '', zyfx: '', zydm: '', zybb: '', pxcc: '', pxlx: '',
|
||||
pxlx2: '', xylb: '', xnz: '', xqs: 1, zdyfl: '', jxgljgbh: '',
|
||||
gfmc: '', zybz: '', pymb: ''
|
||||
},
|
||||
rules: {
|
||||
zydh: [{ required: true, message: '请输入专业代号', trigger: 'blur' }],
|
||||
zymc: [{ required: true, message: '请输入专业名称', trigger: 'blur' }],
|
||||
zydm: [{ required: true, message: '请输入专业代码', trigger: 'blur' }],
|
||||
pxcc: [{ required: true, message: '请输入培训层次', trigger: 'blur' }],
|
||||
pxlx: [{ required: true, message: '请输入培训类型', trigger: 'blur' }],
|
||||
pymb: [{ required: true, message: '请输入培养目标', trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pxlxOptions() { return this.options.pxlx },
|
||||
pxccOptions() { return this.options.pxcc },
|
||||
zgzyOptions() { return this.options.zgzy },
|
||||
pxlx2Options() { return this.options.pxlx2 }
|
||||
},
|
||||
created() {
|
||||
// TODO: 待后端接口,拉取下拉选项与列表
|
||||
// this.loadOptions()
|
||||
// this.getList()
|
||||
},
|
||||
methods: {
|
||||
/** 查询 */
|
||||
handleSearch() {
|
||||
// TODO: 调用查询接口
|
||||
},
|
||||
/** 重置 */
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
zymc: '', zyfx: '', pxlxEnabled: false, pxlx: '',
|
||||
pxccEnabled: false, pxcc: '', zdyfl: '', zgzyEnabled: false, zgzy: '',
|
||||
zydm: '', xnz: '', pxlx2Enabled: false, pxlx2: '',
|
||||
qyztEnabled: false, qyzt: '启用', zybb: '', pymb: ''
|
||||
}
|
||||
},
|
||||
/** 新建 */
|
||||
handleOpenAdd() {
|
||||
// TODO: 打开新建对话框
|
||||
this.dialogVisible = true
|
||||
this.dialogTitle = '新建人才培养方案'
|
||||
},
|
||||
/** 下载 */
|
||||
handleDownload() {
|
||||
// TODO: 列表数据下载
|
||||
},
|
||||
/** 模板下载 */
|
||||
handleTemplateDownload() {
|
||||
// TODO: 模板下载
|
||||
},
|
||||
/** 选择文件 */
|
||||
handleChooseFile() {
|
||||
this.$refs.fileInputRef && this.$refs.fileInputRef.click()
|
||||
},
|
||||
handleFileChange(e) {
|
||||
const file = e.target.files && e.target.files[0]
|
||||
this.selectedFile = file || null
|
||||
this.fileName = file ? file.name : ''
|
||||
// TODO: 校验文件类型后保留
|
||||
},
|
||||
/** 上传数据 */
|
||||
handleUpload() {
|
||||
// TODO: 上传接口(上传 selectedFile)
|
||||
},
|
||||
/** 表格多选 */
|
||||
handleSelectionChange(rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
/** 编辑 */
|
||||
handleEdit(row) {
|
||||
// TODO: 回填并打开编辑对话框
|
||||
this.dialogVisible = true
|
||||
this.dialogTitle = '编辑人才培养方案'
|
||||
},
|
||||
/** 停用 */
|
||||
handleDisable(row) {
|
||||
// TODO: 停用接口
|
||||
},
|
||||
/** 分页 */
|
||||
handlePageChange(val) {
|
||||
this.pageNum = val
|
||||
// TODO: 重新查询
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val
|
||||
// TODO: 重新查询
|
||||
},
|
||||
/** 取消 */
|
||||
handleCancel() {
|
||||
this.dialogVisible = false
|
||||
this.$refs.addFormRef && this.$refs.addFormRef.clearValidate()
|
||||
},
|
||||
/** 保存 */
|
||||
handleSubmit() {
|
||||
this.$refs.addFormRef.validate(valid => {
|
||||
if (!valid) return
|
||||
// TODO: 新增/编辑接口
|
||||
this.dialogVisible = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.training-plan-page {
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
|
||||
.notice {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.action-row {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 4px;
|
||||
|
||||
.upload-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.file-name {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
|
||||
&.has-file {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.pagination-wrapper {
|
||||
margin-top: 14px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<placeholder-page title="教学场地" icon="build" description="维护教学楼、教室、实验室等教学场地资源,支持场地类型、容量与使用状态管理。" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PlaceholderPage from '@/components/PlaceholderPage'
|
||||
|
||||
export default {
|
||||
name: 'Venue',
|
||||
components: { PlaceholderPage }
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user