1、校历的78、晚上、夜间等直接落库,

2、配当、任务书完善、排课窗完善;
3、任务书批量发布、删除等;
4、班历同步校历
This commit is contained in:
2026-09-18 10:38:39 +08:00
parent 911dfe911b
commit f3ae9fb2fd
62 changed files with 3508 additions and 143 deletions
@@ -106,8 +106,9 @@
<el-radio-group v-model="viewMode" size="mini" style="margin-right: 12px">
<el-radio-button label="week">单周视图</el-radio-button>
<el-radio-button label="all">全学期视图</el-radio-button>
<el-radio-button label="h">横版视图</el-radio-button>
</el-radio-group>
<template v-if="viewMode === 'week'">
<template v-if="viewMode !== 'all'">
<el-button size="mini" icon="el-icon-arrow-left" :disabled="weekIndex <= 0" @click="weekIndex--">上一周</el-button>
<span class="week-text">
<b>{{ currentWeek ? currentWeek.weekNo : '—' }}</b>
@@ -138,6 +139,9 @@
<el-checkbox-button label="night">夜间</el-checkbox-button>
</el-checkbox-group>
<el-button size="mini" type="text" @click="conflictDims = []; periodToggles = ['p78','eve','night']">重置</el-button>
<el-checkbox v-model="moveMode" size="mini" style="margin-left: 10px">拖拽调课</el-checkbox>
<el-button size="mini" icon="el-icon-download" style="margin-left: 8px" @click="handleExport">导出</el-button>
<el-button size="mini" icon="el-icon-printer" @click="handlePrint">打印</el-button>
</div>
</div>
@@ -178,13 +182,65 @@
@mousedown.prevent="startDrag(d.fi, jc, d, $event)"
@mouseenter="hoverCell(d.fi, jc)"
@dblclick="dblclickCell(d, jc)"
@dragover="onCellDragover(d, jc, $event)"
@dragleave="onCellDragleave(d, jc)"
@drop="onCellDrop(d, jc, $event)"
>
<template v-if="lessonsOf(d, jc).length">
<div
v-for="l in lessonsOf(d, jc)"
:key="l.bh"
class="lesson"
:class="{ 'is-current': currentCourse && l.sskcbh === currentCourse.sskcbh }"
:class="{ 'is-current': currentCourse && l.sskcbh === currentCourse.sskcbh, 'is-draggable': moveMode && lessonEditable(l) }"
:draggable="moveMode && lessonEditable(l)"
@dragstart="onLessonDragstart(l, d, jc, $event)"
>
<div class="lesson-name">{{ shortName(l) }}</div>
<div class="lesson-meta">{{ [l.kcmc ? l.jxnr : '', l.jyxm, l.jsmc].filter(Boolean).join(' · ') || '—' }}</div>
</div>
</template>
<div v-else-if="isSelected(d.date, jc)" class="slot-picked">已选</div>
<div v-else-if="cellOf(d, jc) && cellOf(d, jc).unavailable" class="slot-block">{{ cellOf(d, jc).reason || '不可排' }}</div>
</div>
</div>
</template>
<!-- ==================== 横版=日期 =节次E4 ==================== -->
<template v-if="viewMode === 'h' && currentWeek">
<div class="tb-row tb-head">
<div class="tb-cell tb-corner">日期</div>
<div
v-for="jc in jcList"
:key="'hjc' + jc"
class="tb-cell tb-day"
:title="periodLabel(jc)"
>{{ jc }}</div>
</div>
<div v-for="d in hDays" :key="'hd' + d.date" class="tb-row">
<div class="tb-cell tb-jc" :class="{ 'day-off': !!d.unavailableReason }" :title="d.unavailableReason || ''">
{{ weekdayName(d.weekday) }} {{ fmtDate(d.date) }}
</div>
<div
v-for="jc in jcList"
:key="d.date + '#h#' + jc"
class="tb-cell tb-slot"
:class="slotClass(d, jc)"
:title="slotTitle(d, jc)"
@mousedown.prevent="startDrag(d.fi, jc, d, $event)"
@mouseenter="hoverCell(d.fi, jc)"
@dblclick="dblclickCell(d, jc)"
@dragover="onCellDragover(d, jc, $event)"
@dragleave="onCellDragleave(d, jc)"
@drop="onCellDrop(d, jc, $event)"
>
<template v-if="lessonsOf(d, jc).length">
<div
v-for="l in lessonsOf(d, jc)"
:key="l.bh"
class="lesson"
:class="{ 'is-current': currentCourse && l.sskcbh === currentCourse.sskcbh, 'is-draggable': moveMode && lessonEditable(l) }"
:draggable="moveMode && lessonEditable(l)"
@dragstart="onLessonDragstart(l, d, jc, $event)"
>
<div class="lesson-name">{{ shortName(l) }}</div>
<div class="lesson-meta">{{ [l.kcmc ? l.jxnr : '', l.jyxm, l.jsmc].filter(Boolean).join(' · ') || '—' }}</div>
@@ -520,6 +576,8 @@
import {
getSchedulingView,
arrangeLessons,
moveLesson,
exportSchedulingView,
deleteLessonCells,
clearCourseLessons,
deleteRunningCourse,
@@ -529,6 +587,7 @@ import {
} from '@/api/teachBusiness/schedulingWindow'
import { listSemester } from '@/api/studentRecords/semester'
import { listAllClassroom } from '@/api/teachBusiness/classroom'
import { saveAs } from 'file-saver'
export default {
name: 'SchedulingWindow',
@@ -551,8 +610,12 @@ export default {
additiveDrag: false,
/** 当前聚焦格(⑨ 当前格子编排信息) */
focusedCell: null,
/** 视图模式:week 单周 / all 全学期 */
/** 视图模式:week 单周 / all 全学期 / h 横版 */
viewMode: 'week',
/** 拖拽调课模式(E4):开启后已排课次可拖到其它格 */
moveMode: false,
/** 当前拖放悬停目标格 key */
dropTarget: null,
/** 冲突显示选项(按维度着色) */
conflictDims: [],
conflictDimOptions: [
@@ -624,8 +687,9 @@ export default {
})
return { list, index }
},
/** 网格行:单周一行 / 全学期每周一组 */
/** 网格行:单周一行 / 全学期每周一组;横版走 hDays */
gridRows() {
if (this.viewMode === 'h') return []
if (this.viewMode === 'all') {
return this.weeks.map(w => ({
key: 'w' + w.weekNo,
@@ -641,6 +705,12 @@ export default {
days: (w.days || []).map(d => Object.assign({}, d, { fi: this.fiOf(d.date) }))
}]
},
/** 横版视图的当前周日列表(行=日期) */
hDays() {
const w = this.currentWeek
if (!w) return []
return (w.days || []).map(d => Object.assign({}, d, { fi: this.fiOf(d.date) }))
},
/** 节次字典(来自节次时间表),缺失时按数据推导 */
periodDict() {
const list = (this.view && this.view.periods) || []
@@ -914,6 +984,7 @@ export default {
'is-picked': !hasLesson && this.isSelected(day.date, jc),
'has-lesson': hasLesson,
'is-conflict': !!(cell && !cell.unavailable && this.conflictHit(day, jc)),
'drop-hover': this.moveMode && this.dropTarget === this.cellKey(day.date, jc),
'day-off': !!day.unavailableReason
}
},
@@ -950,6 +1021,12 @@ export default {
this.$message.warning(cell.reason || '该节次不可排课')
return
}
// 拖拽调课模式下按住格子只做聚焦,课次块用 HTML5 拖拽移动
if (this.moveMode) {
this.focusCell(day.date, jc)
if (this.$refs.gridWrap && this.$refs.gridWrap.focus) this.$refs.gridWrap.focus()
return
}
this.dragging = true
this.additiveDrag = !!(ev && (ev.ctrlKey || ev.metaKey))
this.dragStart = { fi, jc }
@@ -1006,6 +1083,78 @@ export default {
clearSelection() {
this.selectedCells = []
},
/* ==================== 拖拽调课(E4 ==================== */
lessonEditable(l) {
const c = this.courses.find(x => x.sskcbh === l.sskcbh)
return !!(c && c.editable)
},
onLessonDragstart(l, day, jc, ev) {
if (!this.moveMode || !this.lessonEditable(l)) {
ev.preventDefault()
return
}
ev.dataTransfer.setData('text/plain', JSON.stringify({
sskcbh: l.sskcbh,
rq: this.fmtDate(day.date),
jc
}))
ev.dataTransfer.effectAllowed = 'move'
ev.stopPropagation()
},
onCellDragover(day, jc, ev) {
if (!this.moveMode) return
const cell = this.cellOf(day, jc)
if (cell && cell.unavailable) return
ev.preventDefault()
if (ev.dataTransfer) ev.dataTransfer.dropEffect = 'move'
this.dropTarget = this.cellKey(day.date, jc)
},
onCellDragleave(day, jc) {
if (this.dropTarget === this.cellKey(day.date, jc)) this.dropTarget = null
},
onCellDrop(day, jc, ev) {
if (!this.moveMode) return
ev.preventDefault()
this.dropTarget = null
let payload = null
try {
payload = JSON.parse(ev.dataTransfer.getData('text/plain'))
} catch (e) {
return
}
if (!payload || !payload.sskcbh) return
const to = { rq: this.fmtDate(day.date), jc }
if (payload.rq === to.rq && payload.jc === to.jc) return
this.submitting = true
moveLesson({ sskcbh: payload.sskcbh, from: { rq: payload.rq, jc: payload.jc }, to })
.then(res => {
const data = (res && res.data) || {}
if (data.warning) {
this.$message.warning('已移动,但 ' + data.warning)
} else {
this.$message.success('课次已移动')
}
this.loadView()
})
.catch(err => {
this.$message.error((err && err.message) || '移动失败')
})
.finally(() => {
this.submitting = false
})
},
/* ==================== 导出 / 打印(E4 ==================== */
handleExport() {
if (!this.xydxqbh) return
exportSchedulingView(this.xydxqbh)
.then(blob => {
saveAs(blob, `排课窗口_${this.xydxqbh}.xlsx`)
})
.catch(() => this.$message.error('导出失败'))
},
handlePrint() {
window.print()
},
/** ⑨ 当前格子编排信息 */
focusCell(rq, jc) {
this.focusedCell = { rq: this.fmtDate(rq), jc }
@@ -1889,3 +2038,36 @@ export default {
padding: 0;
}
</style>
<style lang="scss">
/* E4:拖拽调课与打印(非 scoped,拖拽/打印需命中 body 级) */
.tb-slot.drop-hover {
outline: 2px dashed var(--edu-color-primary, #2f7d5c);
outline-offset: -2px;
background: var(--edu-color-primary-faint, #eef7f2);
}
.lesson.is-draggable {
cursor: grab;
}
.lesson.is-draggable:active {
cursor: grabbing;
}
@media print {
.page-head,
.op-bar,
.cell-info-bar,
.grid-controls,
.week-nav .el-radio-group,
.week-nav .el-button,
.el-dialog__wrapper,
.v-modal {
display: none !important;
}
.timetable-wrap {
max-height: none !important;
overflow: visible !important;
}
}
</style>