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>
|
||||
Reference in New Issue
Block a user