forked from liweijie/education
1、添加教学场地历;
2、很多表继承了基础类,但是没有添加对应字段导致报错,把继承去掉
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 教学场地历(JXCDL 教学场地历 /classroom/jxcdl)
|
||||
// 稀疏存储:只保存"事件"行(不可用/有备注),没有记录 = 默认可排。
|
||||
// 节次为单节次整数(1~12),段式格子(如 1-2)由前端展开为两行。
|
||||
|
||||
/**
|
||||
* 按教室编号 + 日期范围查询场地历事件
|
||||
* GET /classroom/jxcdl/listByJsbhAndRange
|
||||
*/
|
||||
export function listVenueCalendarRange(jsbh, start, end) {
|
||||
return request({
|
||||
url: '/classroom/jxcdl/listByJsbhAndRange',
|
||||
method: 'get',
|
||||
params: { jsbh, start, end }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存场地历事件(拖选区域写入,按 教室编号+日期+节次 判重覆盖)
|
||||
* POST /classroom/jxcdl/batchSave
|
||||
* @param list JXCDL 数组:{ jsbh, rq(yyyy-MM-dd HH:mm:ss), jc, kpk, mc, bz }
|
||||
*/
|
||||
export function batchSaveVenueCalendar(list) {
|
||||
return request({
|
||||
url: '/classroom/jxcdl/batchSave',
|
||||
method: 'post',
|
||||
data: list
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 ID 批量删除场地历事件(恢复可排)
|
||||
* POST /classroom/jxcdl/batchDelete
|
||||
*/
|
||||
export function batchDeleteVenueCalendar(ids) {
|
||||
return request({
|
||||
url: '/classroom/jxcdl/batchDelete',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询场地在日期范围内已排课次(编辑器蓝格数据源)
|
||||
* GET /classroom/jxcdl/lessons
|
||||
* 返回 [{ rq, jc, sskcbh, kcmc, jyxm, xydmc }]
|
||||
*/
|
||||
export function listVenueLessons(jsbh, start, end, nd) {
|
||||
return request({
|
||||
url: '/classroom/jxcdl/lessons',
|
||||
method: 'get',
|
||||
params: { jsbh, start, end, nd }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定本场地为专用教室的班次(编辑器顶部横幅)
|
||||
* GET /classroom/jxcdl/designatedTeams
|
||||
* 返回字符串数组,如 ["一队(2025年度 第1学期)"]
|
||||
*/
|
||||
export function listVenueDesignatedTeams(jsbh) {
|
||||
return request({
|
||||
url: '/classroom/jxcdl/designatedTeams',
|
||||
method: 'get',
|
||||
params: { jsbh }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 场地占用总览:某日期+节次全部场地的占用状态
|
||||
* GET /classroom/jxcdl/venueSchedule
|
||||
* 返回 [{ jsbh, jsmc, jxldh, status(free/unavailable/occupied/noted), mc, kcmc, xydmc }]
|
||||
*/
|
||||
export function venueSchedule(rq, jc) {
|
||||
return request({
|
||||
url: '/classroom/jxcdl/venueSchedule',
|
||||
method: 'get',
|
||||
params: { rq, jc }
|
||||
})
|
||||
}
|
||||
@@ -17,11 +17,11 @@
|
||||
<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="mode === 'class' ? '请输入班历事件名称' : '请输入校历事件名称'" />
|
||||
<el-checkbox v-model="toolbar.bold" class="sce-cb">加粗显示</el-checkbox>
|
||||
<el-input v-model="toolbar.eventName" size="small" class="sce-event-input" :placeholder="mode === 'class' ? '请输入班历事件名称' : (mode === 'venue' ? '请输入场地事件名称' : '请输入校历事件名称')" />
|
||||
<el-checkbox v-if="mode !== 'venue'" v-model="toolbar.bold" 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>
|
||||
<el-checkbox v-if="mode !== 'venue'" v-model="toolbar.mainCourse" class="sce-cb">正课</el-checkbox>
|
||||
<el-checkbox v-if="mode !== 'venue'" v-model="toolbar.remarkShow" class="sce-cb">备注显示</el-checkbox>
|
||||
<span class="sce-label">备注:</span>
|
||||
<el-input
|
||||
v-model="toolbar.remark"
|
||||
@@ -37,6 +37,7 @@
|
||||
<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 v-if="mode === 'venue'" size="small" icon="el-icon-magic-stick" @click="openRuleDialog">规律生成</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>
|
||||
@@ -45,6 +46,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 场地历:专用教室横幅 -->
|
||||
<div v-if="mode === 'venue' && designatedTeams.length" class="sce-venue-banner">
|
||||
指定本场地为专用教室的班次:{{ designatedTeams.join('、') }}
|
||||
</div>
|
||||
|
||||
<!-- 时间编排区域 -->
|
||||
<div ref="gridWrap" class="sce-grid-wrap" @mousedown="onGridMouseDown">
|
||||
<table class="sce-grid" :class="{ 'is-selecting': selecting }">
|
||||
@@ -72,9 +78,11 @@
|
||||
class="sce-cell"
|
||||
:class="cellClass(wIdx, col)"
|
||||
:data-key="cellKey(wIdx, col.colIndex)"
|
||||
:title="cellTitle(wIdx, col)"
|
||||
@dblclick="onCellDblClick(wIdx, col)"
|
||||
>
|
||||
<span v-if="getEvent(wIdx, col.colIndex) && getEvent(wIdx, col.colIndex).name" class="sce-event-name" :class="{ 'is-bold': getEvent(wIdx, col.colIndex).bold }">{{ getEvent(wIdx, col.colIndex).name }}</span>
|
||||
<span v-else-if="mode === 'venue' && getLessons(wIdx, col.colIndex)" class="sce-lesson-name">{{ lessonTextOf(wIdx, col.colIndex) }}</span>
|
||||
<span v-else class="sce-date-num">{{ dateNumberOf(wIdx, col.dayIndex) }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -85,7 +93,47 @@
|
||||
<div class="sce-status">
|
||||
已选 {{ selectedKeys.length }} 个时间格
|
||||
<span v-if="absorbPreview" class="sce-absorb-tip">已吸取:{{ absorbPreview }}</span>
|
||||
<span v-if="mode === 'venue'" class="sce-legend">
|
||||
<i class="lg lg-red" />不可用
|
||||
<i class="lg lg-yellow" />场地备注
|
||||
<i class="lg lg-blue" />已排课次
|
||||
<i class="lg lg-gray" />校历不可排
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 场地历:按星期规律生成 -->
|
||||
<el-dialog title="按星期规律生成场地事件" :visible.sync="ruleDialog.visible" width="540px" append-to-body
|
||||
:close-on-click-modal="false">
|
||||
<el-form label-width="90px" size="small">
|
||||
<el-form-item label="星期">
|
||||
<el-checkbox-group v-model="ruleDialog.weekdays">
|
||||
<el-checkbox v-for="(d, i) in weekDayOptions" :key="i" :label="i">{{ d }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="节次">
|
||||
<el-checkbox-group v-model="ruleDialog.slots">
|
||||
<el-checkbox v-for="s in slotOptions" :key="s.key" :label="s.key">{{ s.label }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="周次范围">
|
||||
<el-input-number v-model="ruleDialog.weekStart" :min="1" :max="totalWeeks || 1" size="small" class="rule-week" />
|
||||
<span class="rule-sep">至</span>
|
||||
<el-input-number v-model="ruleDialog.weekEnd" :min="1" :max="totalWeeks || 1" size="small" class="rule-week" />
|
||||
<span class="rule-unit">周</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="事件内容">
|
||||
<span class="rule-hint">使用上方工具栏填写的「事件名称 / 可排课 / 备注」</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="预计生成">
|
||||
<span>{{ rulePreviewCount }} 个时间格</span>
|
||||
<span v-if="ruleSkippedCount" class="rule-hint">(跳过校历不可排/已排课 {{ ruleSkippedCount }} 格)</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button size="small" @click="ruleDialog.visible = false">取 消</el-button>
|
||||
<el-button type="primary" size="small" :loading="ruleDialog.submitting" @click="applyRuleGenerate">生 成</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -93,6 +141,13 @@
|
||||
import { getSemester } from '@/api/teachBusiness/semester'
|
||||
import { listXqxlb, addXqxlb, updateXqxlb } from '@/api/teachBusiness/xqxlb'
|
||||
import { listClassCalendarEvent, updateClassCalendarEvent } from '@/api/teachOffice/classCalendarEvent'
|
||||
import {
|
||||
listVenueCalendarRange,
|
||||
batchSaveVenueCalendar,
|
||||
batchDeleteVenueCalendar,
|
||||
listVenueLessons,
|
||||
listVenueDesignatedTeams
|
||||
} from '@/api/teachBusiness/venueCalendar'
|
||||
|
||||
// 节次定义:12/34/56 常显,78/晚上/夜间由开关控制可见
|
||||
const SLOT_DEFS = [
|
||||
@@ -116,6 +171,9 @@ const SLOT_COURSE_MAP = {
|
||||
|
||||
const WEEK_DAYS = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
|
||||
// 场地历:单节次 -> 节次格 key(1-2 节合并在同一格)
|
||||
const JC_SLOT_MAP = { 1: '12', 2: '12', 3: '34', 4: '34', 5: '56', 6: '56', 7: '78', 8: '78', 9: 'night', 10: 'night', 11: 'late', 12: 'late' }
|
||||
|
||||
function pad2(n) {
|
||||
return n < 10 ? '0' + n : '' + n
|
||||
}
|
||||
@@ -142,6 +200,8 @@ export default {
|
||||
mode: { type: String, default: 'school' },
|
||||
xydxqbh: { type: String, default: '' },
|
||||
xydbh: { type: String, default: '' },
|
||||
// 场地历模式:教室编号
|
||||
jsbh: { type: String, default: '' },
|
||||
rangeStart: { type: String, default: '' },
|
||||
rangeEnd: { type: String, default: '' }
|
||||
},
|
||||
@@ -181,7 +241,24 @@ export default {
|
||||
moved: false, // 是否发生移动(区分点击与拖拽)
|
||||
dragAddMode: false, // ctrl 拖拽为追加
|
||||
// 窗口控制
|
||||
absorbPreview: ''
|
||||
absorbPreview: '',
|
||||
// ---- 场地历模式 ----
|
||||
// 校历不可排格:'yyyy-MM-dd#slotKey' -> 校历事件名(置灰只读,不落库)
|
||||
schoolOff: {},
|
||||
// 已排课次(蓝格只读):'yyyy-MM-dd#slotKey' -> [{ kcmc, jyxm, xydmc, jc }]
|
||||
lessons: {},
|
||||
// 指定本场地为专用教室的班次(顶部横幅)
|
||||
designatedTeams: [],
|
||||
weekDayOptions: WEEK_DAYS,
|
||||
slotOptions: SLOT_DEFS,
|
||||
ruleDialog: {
|
||||
visible: false,
|
||||
submitting: false,
|
||||
weekdays: [],
|
||||
slots: [],
|
||||
weekStart: 1,
|
||||
weekEnd: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -202,6 +279,36 @@ export default {
|
||||
// 不再按日历起点/季度取年份,避免跨年度学期解析出错。
|
||||
xqxlbNd() {
|
||||
return Number(String(this.nd).slice(0, 6)) || Number(this.nd) || 0
|
||||
},
|
||||
// 规律生成:预计写入的格子数(排除学期范围外/校历不可排/已排课的格)
|
||||
ruleTargets() {
|
||||
const cells = []
|
||||
let skipped = 0
|
||||
const dlg = this.ruleDialog
|
||||
if (!dlg.weekdays.length || !dlg.slots.length || !this.calendarStart) return { cells, skipped }
|
||||
const w0 = Math.max(0, (dlg.weekStart || 1) - 1)
|
||||
const w1 = Math.min(this.totalWeeks - 1, (dlg.weekEnd || this.totalWeeks) - 1)
|
||||
for (let w = w0; w <= w1; w++) {
|
||||
dlg.weekdays.forEach(dayIndex => {
|
||||
if (this.isOutOfRange(w, dayIndex)) return
|
||||
const d = this.cellDate(w, dayIndex)
|
||||
const dateStr = fmtDate(d)
|
||||
dlg.slots.forEach(slotKey => {
|
||||
if (this.isSchoolOff(dateStr, slotKey) || this.hasLessonAt(dateStr, slotKey)) {
|
||||
skipped++
|
||||
return
|
||||
}
|
||||
cells.push({ dateStr, slotKey })
|
||||
})
|
||||
})
|
||||
}
|
||||
return { cells, skipped }
|
||||
},
|
||||
rulePreviewCount() {
|
||||
return this.ruleTargets.cells.length
|
||||
},
|
||||
ruleSkippedCount() {
|
||||
return this.ruleTargets.skipped
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -217,6 +324,12 @@ export default {
|
||||
if (this.mode === 'class' && val) this.loadClassCalendar()
|
||||
}
|
||||
},
|
||||
jsbh: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (this.mode === 'venue' && val) this.loadVenueEvents()
|
||||
}
|
||||
},
|
||||
visibleColumns(newCols, oldCols) {
|
||||
// 节次列显隐会改变列索引,按「周期-星期-节次」重定位事件,避免事件随时间格列偏移
|
||||
if (oldCols && oldCols.length) {
|
||||
@@ -298,7 +411,8 @@ export default {
|
||||
this.calendarStart = null
|
||||
this.buildWeeks()
|
||||
}
|
||||
this.loadXqxlbEvents()
|
||||
if (this.mode === 'venue') this.loadVenueEvents()
|
||||
else this.loadXqxlbEvents()
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.warning('学期详情获取失败,请确认学期数据')
|
||||
@@ -360,6 +474,195 @@ export default {
|
||||
})
|
||||
this.events = loaded
|
||||
},
|
||||
/* ---------- 场地历(venue 模式) ---------- */
|
||||
// 场地历为稀疏存储:只拉"事件"行,空格默认即"可排";
|
||||
// 校历不可排格(节假日)整格置灰只读不落库;已排课次蓝格只读。
|
||||
loadVenueEvents() {
|
||||
if (!this.jsbh) return
|
||||
const s = this.startDate ? fmtDate(this.startDate) : ''
|
||||
const e = this.endDate ? fmtDate(this.endDate) : ''
|
||||
Promise.all([
|
||||
listXqxlb({ nd: this.xqxlbNd }).catch(() => ({ data: [] })),
|
||||
listVenueCalendarRange(this.jsbh, s, e).catch(() => ({ data: [] })),
|
||||
listVenueLessons(this.jsbh, s, e, this.xqxlbNd).catch(() => ({ data: [] })),
|
||||
listVenueDesignatedTeams(this.jsbh).catch(() => ({ data: [] }))
|
||||
]).then(([xlRes, evRes, lsRes, tmRes]) => {
|
||||
const xlList = Array.isArray(xlRes.data) ? xlRes.data : (xlRes.data && xlRes.data.records) || []
|
||||
const evList = Array.isArray(evRes.data) ? evRes.data : (evRes.data && evRes.data.records) || []
|
||||
const lsList = Array.isArray(lsRes.data) ? lsRes.data : (lsRes.data && lsRes.data.records) || []
|
||||
this.designatedTeams = Array.isArray(tmRes.data) ? tmRes.data : []
|
||||
// 校历不可排格 -> 灰
|
||||
const off = {}
|
||||
xlList.forEach(item => {
|
||||
if (item.kpk === true || item.kpk === 1) return
|
||||
const dateStr = String(item.jqsj || '').slice(0, 10)
|
||||
const slotKey = this.courseClassToSlotKey(item.courseClass)
|
||||
if (dateStr && slotKey) off[dateStr + '#' + slotKey] = item.jqmc || '校历不可排课'
|
||||
})
|
||||
this.schoolOff = off
|
||||
// 已排课次 -> 蓝
|
||||
const lessons = {}
|
||||
lsList.forEach(item => {
|
||||
const dateStr = String(item.rq || '').slice(0, 10)
|
||||
const slotKey = JC_SLOT_MAP[item.jc]
|
||||
if (!dateStr || !slotKey) return
|
||||
const key = dateStr + '#' + slotKey
|
||||
if (!lessons[key]) lessons[key] = []
|
||||
lessons[key].push(item)
|
||||
})
|
||||
this.lessons = lessons
|
||||
this.renderVenueEvents(evList)
|
||||
})
|
||||
},
|
||||
renderVenueEvents(list) {
|
||||
// 同格(日期+节次段)两行单节次记录归并为一个事件
|
||||
const merged = {}
|
||||
list.forEach(row => {
|
||||
const dateStr = String(row.rq || '').slice(0, 10)
|
||||
const slotKey = JC_SLOT_MAP[row.jc]
|
||||
if (!dateStr || !slotKey) return
|
||||
const pos = this.locateDateSlot(dateStr, slotKey)
|
||||
if (!pos) return
|
||||
const colIndex = this.colIndexOf(pos.dayIndex, pos.slotKey)
|
||||
const ck = colIndex >= 0 ? this.cellKey(pos.wIdx, colIndex) : null
|
||||
const storeKey = ck || this.cellStableKey(pos.wIdx, pos.dayIndex, pos.slotKey)
|
||||
const bucket = ck ? merged : this.hiddenEvents
|
||||
const prev = bucket[storeKey]
|
||||
if (prev) {
|
||||
if (row.id) prev.ids.push(row.id)
|
||||
if (!prev.name && row.mc) prev.name = row.mc
|
||||
if (row.kpk === false || row.kpk === 0) prev.schedulable = false
|
||||
if (!prev.remark && row.bz) prev.remark = row.bz
|
||||
} else {
|
||||
bucket[storeKey] = {
|
||||
ids: row.id ? [row.id] : [],
|
||||
name: row.mc || '',
|
||||
schedulable: !(row.kpk === false || row.kpk === 0),
|
||||
remark: row.bz || ''
|
||||
}
|
||||
}
|
||||
})
|
||||
this.events = merged
|
||||
},
|
||||
// 日期 + 节次格 -> 网格位置
|
||||
locateDateSlot(dateStr, slotKey) {
|
||||
if (!this.calendarStart || !dateStr) return null
|
||||
const d = new Date(dateStr.replace(/-/g, '/'))
|
||||
if (isNaN(d.getTime())) return null
|
||||
const offset = Math.round((d - this.calendarStart) / (24 * 3600 * 1000))
|
||||
if (offset < 0) return null
|
||||
return { wIdx: Math.floor(offset / 7), dayIndex: offset % 7, slotKey }
|
||||
},
|
||||
cellDateStr(wIdx, dayIndex) {
|
||||
const d = this.cellDate(wIdx, dayIndex)
|
||||
return d ? fmtDate(d) : ''
|
||||
},
|
||||
isSchoolOff(dateStr, slotKey) {
|
||||
return !!this.schoolOff[dateStr + '#' + slotKey]
|
||||
},
|
||||
hasLessonAt(dateStr, slotKey) {
|
||||
return !!(this.lessons[dateStr + '#' + slotKey] || []).length
|
||||
},
|
||||
getLessons(wIdx, colIndex) {
|
||||
const col = this.visibleColumns.find(c => c.colIndex === colIndex)
|
||||
if (!col) return null
|
||||
return this.lessons[this.cellDateStr(wIdx, col.dayIndex) + '#' + col.slotKey] || null
|
||||
},
|
||||
lessonTextOf(wIdx, colIndex) {
|
||||
const list = this.getLessons(wIdx, colIndex) || []
|
||||
const names = list.map(l => l.kcmc).filter(Boolean)
|
||||
return names.length ? names.join(';') : '已排课'
|
||||
},
|
||||
cellTitle(wIdx, col) {
|
||||
if (this.mode !== 'venue') return ''
|
||||
const dateStr = this.cellDateStr(wIdx, col.dayIndex)
|
||||
const parts = []
|
||||
const offName = this.schoolOff[dateStr + '#' + col.slotKey]
|
||||
if (offName) parts.push('校历:' + offName + '(不可排课)')
|
||||
const ls = this.lessons[dateStr + '#' + col.slotKey] || []
|
||||
ls.forEach(l => {
|
||||
parts.push('课次:' + (l.kcmc || '-') +
|
||||
(l.jyxm ? ' / 教员:' + l.jyxm : '') +
|
||||
(l.xydmc ? ' / 班次:' + l.xydmc : ''))
|
||||
})
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
if (ev && (ev.name || ev.remark)) {
|
||||
parts.push('场地事件:' + (ev.name || '-') + (ev.remark ? '(' + ev.remark + ')' : ''))
|
||||
}
|
||||
return parts.join('\n')
|
||||
},
|
||||
// 场地历可编辑格过滤:剔除校历不可排、已排课次、学期范围外的格
|
||||
filterVenueEditable(keys) {
|
||||
const ok = []
|
||||
let skipped = 0
|
||||
keys.forEach(key => {
|
||||
const pos = this.parseCellKey(key)
|
||||
if (!pos) { skipped++; return }
|
||||
const dateStr = this.cellDateStr(pos.wIdx, pos.dayIndex)
|
||||
if (this.isOutOfRange(pos.wIdx, pos.dayIndex)
|
||||
|| this.isSchoolOff(dateStr, pos.slotKey)
|
||||
|| this.hasLessonAt(dateStr, pos.slotKey)) {
|
||||
skipped++
|
||||
return
|
||||
}
|
||||
ok.push({ key, pos, dateStr })
|
||||
})
|
||||
return { ok, skipped }
|
||||
},
|
||||
periodsOfSlot(slotKey) {
|
||||
const seg = SLOT_COURSE_MAP[slotKey]
|
||||
if (!seg) return []
|
||||
return seg.split('-').map(Number).filter(n => !isNaN(n))
|
||||
},
|
||||
/* ---------- 规律生成 ---------- */
|
||||
openRuleDialog() {
|
||||
if (!this.toolbar.eventName.trim()) {
|
||||
this.$message.warning('请先在上方工具栏填写事件名称')
|
||||
return
|
||||
}
|
||||
this.ruleDialog.weekEnd = this.totalWeeks || 1
|
||||
this.ruleDialog.visible = true
|
||||
},
|
||||
applyRuleGenerate() {
|
||||
const name = this.toolbar.eventName.trim()
|
||||
if (!name) {
|
||||
this.$message.warning('请先填写事件名称')
|
||||
return
|
||||
}
|
||||
const { cells } = this.ruleTargets
|
||||
if (!cells.length) {
|
||||
this.$message.warning('所选条件下没有可写入的时间格')
|
||||
return
|
||||
}
|
||||
this.ruleDialog.submitting = true
|
||||
this.saveVenueCells(cells, name).then(ok => {
|
||||
if (ok) this.ruleDialog.visible = false
|
||||
}).finally(() => {
|
||||
this.ruleDialog.submitting = false
|
||||
})
|
||||
},
|
||||
// 把 [{dateStr, slotKey}] 展开为单节次行并批量保存
|
||||
saveVenueCells(cells, name) {
|
||||
const rows = []
|
||||
cells.forEach(c => {
|
||||
this.periodsOfSlot(c.slotKey).forEach(jc => {
|
||||
rows.push({
|
||||
jsbh: this.jsbh,
|
||||
rq: c.dateStr + ' 00:00:00',
|
||||
jc: jc,
|
||||
kpk: !!this.toolbar.schedulable,
|
||||
mc: name,
|
||||
bz: this.toolbar.remark || null
|
||||
})
|
||||
})
|
||||
})
|
||||
if (!rows.length) return Promise.resolve(false)
|
||||
return batchSaveVenueCalendar(rows).then(res => {
|
||||
this.$message.success((res && res.message) || '已保存 ' + rows.length + ' 条场地历')
|
||||
this.reloadEvents()
|
||||
return true
|
||||
}).catch(() => false)
|
||||
},
|
||||
// 根据假期记录反推时间格位置(jqsj 日期 + courseClass 节次),与列是否可见无关
|
||||
locateXqxlb(item) {
|
||||
if (!this.calendarStart || !item.jqsj) return null
|
||||
@@ -442,13 +745,20 @@ export default {
|
||||
cellClass(wIdx, col) {
|
||||
const classes = []
|
||||
if (this.selectedKeys.includes(this.cellKey(wIdx, col.colIndex))) classes.push('is-selected')
|
||||
// 班历:班次日期范围外的格子不可排,给出明确视觉提示
|
||||
if (this.mode === 'class' && this.isOutOfRange(wIdx, col.dayIndex)) {
|
||||
// 班历/场地历:学期日期范围外的格子不可排,给出明确视觉提示
|
||||
if ((this.mode === 'class' || this.mode === 'venue') && this.isOutOfRange(wIdx, col.dayIndex)) {
|
||||
classes.push('is-out-of-range')
|
||||
}
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
// 可排课的全部白色;不可排课且事件名非空才标红
|
||||
if (ev && ev.name && !ev.schedulable) {
|
||||
if (this.mode === 'venue') {
|
||||
const dateStr = this.cellDateStr(wIdx, col.dayIndex)
|
||||
// 校历不可排(节假日)置灰只读;已排课次蓝色只读
|
||||
if (this.isSchoolOff(dateStr, col.slotKey)) classes.push('is-school-off')
|
||||
if (this.hasLessonAt(dateStr, col.slotKey)) classes.push('has-lesson')
|
||||
// 场地事件:不可排标红,仍可排的纯备注标黄
|
||||
if (ev) classes.push(ev.schedulable ? 'has-note' : 'no-schedule')
|
||||
} else if (ev && ev.name && !ev.schedulable) {
|
||||
// 可排课的全部白色;不可排课且事件名非空才标红
|
||||
classes.push('no-schedule')
|
||||
}
|
||||
return classes
|
||||
@@ -585,6 +895,16 @@ export default {
|
||||
}
|
||||
}
|
||||
const keys = [...this.selectedKeys]
|
||||
// 场地历:过滤不可编辑格(校历不可排/已排课次/范围外),按单节次展开批量保存
|
||||
if (this.mode === 'venue') {
|
||||
const { ok, skipped } = this.filterVenueEditable(keys)
|
||||
if (skipped > 0) {
|
||||
this.$message.warning(`已跳过 ${skipped} 个不可编辑格(校历不可排/已排课次/范围外)`)
|
||||
}
|
||||
if (!ok.length) return
|
||||
this.saveVenueCells(ok.map(t => ({ dateStr: t.dateStr, slotKey: t.pos.slotKey })), name)
|
||||
return
|
||||
}
|
||||
keys.forEach(key => {
|
||||
const prev = this.events[key]
|
||||
this.$set(this.events, key, {
|
||||
@@ -613,6 +933,25 @@ export default {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const keys = [...this.selectedKeys]
|
||||
// 场地历:收集事件行 ID 走批量删除
|
||||
if (this.mode === 'venue') {
|
||||
const ids = []
|
||||
keys.forEach(key => {
|
||||
const ev = this.events[key]
|
||||
if (ev && ev.ids) ids.push(...ev.ids)
|
||||
})
|
||||
if (!ids.length) {
|
||||
this.$message.warning('所选时间格没有场地事件')
|
||||
return
|
||||
}
|
||||
batchDeleteVenueCalendar(ids).then(res => {
|
||||
this.$message.success((res && res.message) || '已删除场地事件')
|
||||
keys.forEach(key => this.$delete(this.events, key))
|
||||
this.selectedKeys = []
|
||||
this.reloadEvents()
|
||||
}).catch(() => {})
|
||||
return
|
||||
}
|
||||
this.persistDeleteEvents(keys).then(({ ok, fail }) => {
|
||||
if (fail === 0) {
|
||||
keys.forEach(key => {
|
||||
@@ -643,6 +982,17 @@ export default {
|
||||
},
|
||||
onCellDblClick(wIdx, col) {
|
||||
const ev = this.getEvent(wIdx, col.colIndex)
|
||||
// 场地历:双击蓝格显示课次详情
|
||||
if (this.mode === 'venue') {
|
||||
const ls = this.getLessons(wIdx, col.colIndex)
|
||||
if (ls && ls.length && !(ev && ev.name)) {
|
||||
const lines = ls.map(l => '课程:' + (l.kcmc || '-') +
|
||||
(l.jyxm ? ' 教员:' + l.jyxm : '') +
|
||||
(l.xydmc ? ' 班次:' + l.xydmc : ''))
|
||||
this.$alert(lines.join('<br/>'), '已排课次', { dangerouslyUseHTMLString: true })
|
||||
return
|
||||
}
|
||||
}
|
||||
if (ev && ev.name) {
|
||||
this.toolbar.eventName = ev.name
|
||||
this.toolbar.bold = !!ev.bold
|
||||
@@ -660,6 +1010,7 @@ export default {
|
||||
this.events = {}
|
||||
this.hiddenEvents = {}
|
||||
if (this.mode === 'class') this.loadClassEvents()
|
||||
else if (this.mode === 'venue') this.loadVenueEvents()
|
||||
else this.loadXqxlbEvents()
|
||||
this.$message.info('已刷新')
|
||||
},
|
||||
@@ -667,6 +1018,7 @@ export default {
|
||||
this.events = {}
|
||||
this.hiddenEvents = {}
|
||||
if (this.mode === 'class') this.loadClassEvents()
|
||||
else if (this.mode === 'venue') this.loadVenueEvents()
|
||||
else this.loadXqxlbEvents()
|
||||
},
|
||||
savePayload(payload) {
|
||||
@@ -765,7 +1117,7 @@ export default {
|
||||
/* ---------- 返回学期管理 ---------- */
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
if (this.mode !== 'class') {
|
||||
if (this.mode === 'school') {
|
||||
this.$router.replace({ path: '/teachBusiness/semester' })
|
||||
}
|
||||
}
|
||||
@@ -827,6 +1179,15 @@ export default {
|
||||
.sce-divider { width: 1px; height: 18px; background: #dcdfe6; margin: 0 14px; }
|
||||
}
|
||||
|
||||
/* 场地历:专用教室横幅 */
|
||||
.sce-venue-banner {
|
||||
padding: 6px 14px;
|
||||
font-size: 12px;
|
||||
color: #e6a23c;
|
||||
background: #fdf6ec;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
/* 时间编排区 */
|
||||
.sce-grid-wrap {
|
||||
flex: 1;
|
||||
@@ -912,6 +1273,34 @@ export default {
|
||||
// 可排课白色,不可排课且有事件标红
|
||||
&.no-schedule { background: #fde2e2; }
|
||||
|
||||
// 场地历:仍可排的纯备注事件标黄
|
||||
&.has-note { background: #fdf6ec; }
|
||||
|
||||
// 场地历:已排课次蓝格(只读)
|
||||
&.has-lesson {
|
||||
background: #e8f1fd;
|
||||
cursor: default;
|
||||
|
||||
.sce-lesson-name {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
color: #409eff;
|
||||
line-height: 1;
|
||||
padding: 2px 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
// 场地历:校历不可排格置灰只读
|
||||
&.is-school-off {
|
||||
background: #f0f0f0;
|
||||
cursor: not-allowed;
|
||||
|
||||
.sce-date-num { color: #c0c4cc; }
|
||||
}
|
||||
|
||||
// 班历:班次日期范围外的格子不可排
|
||||
&.is-out-of-range {
|
||||
background: #f4f4f5;
|
||||
@@ -936,6 +1325,28 @@ export default {
|
||||
background: #fafafa;
|
||||
|
||||
.sce-absorb-tip { margin-left: 12px; color: #00875a; }
|
||||
|
||||
.sce-legend {
|
||||
float: right;
|
||||
|
||||
.lg {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin: 0 4px 0 14px;
|
||||
border: 1px solid #dcdfe6;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.lg-red { background: #fde2e2; }
|
||||
.lg-yellow { background: #fdf6ec; }
|
||||
.lg-blue { background: #e8f1fd; }
|
||||
.lg-gray { background: #f0f0f0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rule-week { width: 110px; }
|
||||
.rule-sep { margin: 0 8px; color: #606266; }
|
||||
.rule-unit { margin-left: 8px; color: #606266; }
|
||||
.rule-hint { font-size: 12px; color: #909399; }
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
:title="`编辑场地历——${venueTitle}`"
|
||||
width="92%"
|
||||
top="4vh"
|
||||
:close-on-click-modal="false"
|
||||
class="venue-calendar-dialog"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<div class="dialog-toolbar">
|
||||
<span class="toolbar-label">学期</span>
|
||||
<el-select
|
||||
v-model="nd"
|
||||
size="small"
|
||||
filterable
|
||||
:loading="semesterLoading"
|
||||
placeholder="请选择学期"
|
||||
class="semester-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in semesterOptions"
|
||||
:key="item.nd"
|
||||
:label="getSemesterName(item.nd)"
|
||||
:value="item.nd"
|
||||
/>
|
||||
</el-select>
|
||||
<span class="hint">场地历只保存本场地特有事件(设备检修、占用等);校历节假日格自动置灰,无需重复标记。</span>
|
||||
</div>
|
||||
<div class="editor-wrap">
|
||||
<SchoolCalendarEditor
|
||||
v-if="dialogVisible && nd && venue && venue.jsbh"
|
||||
ref="editor"
|
||||
mode="venue"
|
||||
:nd="nd"
|
||||
:jsbh="venue.jsbh"
|
||||
:semester-name="venueTitle"
|
||||
@close="dialogVisible = false"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SchoolCalendarEditor from '@/views/teachBusiness/calendar/components/SchoolCalendarEditor.vue'
|
||||
import { listAllSemester } from '@/api/teachBusiness/semester'
|
||||
|
||||
export default {
|
||||
name: 'VenueCalendarEditor',
|
||||
components: { SchoolCalendarEditor },
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
// 场地行(JSB):需要 jsbh/jsmc
|
||||
venue: { type: Object, default: null }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nd: '',
|
||||
semesterOptions: [],
|
||||
semesterLoading: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() { return this.visible },
|
||||
set(val) { this.$emit('update:visible', val) }
|
||||
},
|
||||
venueTitle() {
|
||||
const v = this.venue || {}
|
||||
return v.jsmc || v.jsbh || '场地历'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.loadSemesters()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadSemesters() {
|
||||
if (this.semesterOptions.length && this.nd) return
|
||||
this.semesterLoading = true
|
||||
listAllSemester().then(response => {
|
||||
const list = Array.isArray(response.data) ? response.data : (response.data && response.data.records) || []
|
||||
this.semesterOptions = list
|
||||
if (!this.nd) {
|
||||
const current = list.find(i => i.dqxq) || list[0]
|
||||
if (current) this.nd = current.nd
|
||||
}
|
||||
}).catch(() => {
|
||||
this.semesterOptions = []
|
||||
}).finally(() => {
|
||||
this.semesterLoading = false
|
||||
})
|
||||
},
|
||||
/** 学期代号 -> 学期名称(如 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 + '学期')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.toolbar-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
.semester-select {
|
||||
width: 200px;
|
||||
}
|
||||
.hint {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
.editor-wrap {
|
||||
height: 72vh;
|
||||
}
|
||||
</style>
|
||||
@@ -75,9 +75,10 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sssb" label="设施设备" width="140" show-overflow-tooltip header-align="center" />
|
||||
<el-table-column label="操作" align="center" fixed="right">
|
||||
<el-table-column label="操作" align="center" fixed="right" width="190">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-date" @click="handleCalendarEdit(scope.row)">编辑历表</el-button>
|
||||
<el-button type="text" size="mini" icon="el-icon-delete" class="danger-text-btn" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -202,6 +203,9 @@
|
||||
<el-button type="primary" :loading="dialog.submitting" @click="submitDialog">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 场地历编辑弹窗 -->
|
||||
<VenueCalendarEditor :visible.sync="calendarVisible" :venue="calendarVenue" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -214,9 +218,11 @@ import {
|
||||
listAllClassroom
|
||||
} from '@/api/teachBusiness/classroom'
|
||||
import { listAllBuild } from '@/api/teachBusiness/build'
|
||||
import VenueCalendarEditor from './calendarEditor.vue'
|
||||
|
||||
export default {
|
||||
name: 'Venue',
|
||||
components: { VenueCalendarEditor },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
@@ -241,6 +247,9 @@ export default {
|
||||
submitting: false,
|
||||
form: this.createEmptyForm()
|
||||
},
|
||||
// 场地历编辑弹窗
|
||||
calendarVisible: false,
|
||||
calendarVenue: null,
|
||||
rules: {
|
||||
jsmc: [{ required: true, message: '请输入教室名称', trigger: 'blur' }]
|
||||
}
|
||||
@@ -403,6 +412,16 @@ export default {
|
||||
}).catch(() => {})
|
||||
},
|
||||
|
||||
/* ---------- 场地历 ---------- */
|
||||
handleCalendarEdit(row) {
|
||||
if (!row.jsbh) {
|
||||
this.$message.warning('该场地缺少教室编号,无法维护场地历')
|
||||
return
|
||||
}
|
||||
this.calendarVenue = row
|
||||
this.calendarVisible = true
|
||||
},
|
||||
|
||||
/* ---------- 工具 ---------- */
|
||||
createEmptyForm() {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user