前端代码
This commit is contained in:
@@ -0,0 +1,906 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
title="班历设置"
|
||||
width="94%"
|
||||
top="4vh"
|
||||
:close-on-click-modal="false"
|
||||
class="class-calendar-dialog"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<div class="class-calendar">
|
||||
<!-- ==================== 1. 学期与班次信息 ==================== -->
|
||||
<div class="info-panel">
|
||||
<div class="info-title-row">
|
||||
<span class="semester-name">{{ semesterName }}</span>
|
||||
<span class="class-name">班次{{ bc }}</span>
|
||||
</div>
|
||||
<div class="date-range-row">
|
||||
<div class="range-item">从{{ calStart }}到{{ calEnd }},共{{ calWeeks }}周</div>
|
||||
<div class="range-item">从{{ classStart }}到{{ classEnd }},共{{ classWeeks }}周</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 2. 设置面板(事件设置 + 操作按钮) ==================== -->
|
||||
<div class="settings-panel">
|
||||
<div class="event-settings-row">
|
||||
<div class="setting-item">
|
||||
<span class="setting-label">事件名称</span>
|
||||
<el-input v-model="eventName" size="small" style="width: 140px" placeholder="" />
|
||||
</div>
|
||||
<el-checkbox v-model="isBold" size="small">加粗显示</el-checkbox>
|
||||
<el-checkbox v-model="isSchedulable" size="small">可排课</el-checkbox>
|
||||
<el-checkbox v-model="isRegularClass" size="small">正课</el-checkbox>
|
||||
<el-checkbox v-model="showRemark" size="small">课表备注中显示</el-checkbox>
|
||||
<div class="setting-item">
|
||||
<span class="setting-label">备注</span>
|
||||
<el-input v-model="remark" size="small" style="width: 150px" placeholder="" />
|
||||
</div>
|
||||
<el-checkbox v-model="show78" size="small">显示7/8节</el-checkbox>
|
||||
<el-checkbox v-model="showEvening" size="small">显示晚上</el-checkbox>
|
||||
<el-checkbox v-model="showNight" size="small">显示夜间</el-checkbox>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 3. 操作按钮区域 ==================== -->
|
||||
<div class="action-buttons-row">
|
||||
<button type="button" class="gray-btn" @click="handleAbsorb">吸取</button>
|
||||
<button type="button" class="gray-btn" @click="handleSet">设定</button>
|
||||
<button type="button" class="gray-btn" @click="handleDelete">删除</button>
|
||||
<button type="button" class="blue-btn" @click="handleApplyOther">选定区域设置应用于其它班次</button>
|
||||
<button type="button" class="gray-btn" @click="handleApplyWhole">整个班历应用于其它班次</button>
|
||||
<button type="button" class="gray-btn" @click="handleRefresh">刷新</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 4. 时间编排区域 ==================== -->
|
||||
<div class="table-area">
|
||||
<div class="calendar-table-wrapper">
|
||||
<table class="calendar-table">
|
||||
<thead>
|
||||
<tr class="head-row-1">
|
||||
<th rowspan="2" class="cell-week-head">周次</th>
|
||||
<th rowspan="2" class="cell-range-head">日期段</th>
|
||||
<th v-for="name in dayNames" :key="name" :colspan="visiblePeriods.length">{{ name }}</th>
|
||||
</tr>
|
||||
<tr class="head-row-2">
|
||||
<template v-for="(name, di) in dayNames">
|
||||
<th v-for="p in visiblePeriods" :key="name + '-' + p.key">{{ p.label }}</th>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(w, wi) in weekDefs" :key="w.num">
|
||||
<td class="week-num">{{ w.num }}</td>
|
||||
<td class="date-range">{{ w.start }}至{{ w.end }}</td>
|
||||
<template v-for="d in 5">
|
||||
<td
|
||||
v-for="p in visiblePeriods"
|
||||
:key="wi + '-' + (d - 1) + '-' + p.key"
|
||||
class="cell"
|
||||
:class="cellClass(getCell(wi, d - 1, p.key))"
|
||||
:style="cellStyle(getCell(wi, d - 1, p.key))"
|
||||
:title="getCell(wi, d - 1, p.key).isDate ? '该时间格无事件,可选中后设定事件' : '双击可吸取该事件设置'"
|
||||
@mousedown.prevent="onCellMouseDown(getCell(wi, d - 1, p.key), $event)"
|
||||
@mousemove="onCellMouseMove(getCell(wi, d - 1, p.key))"
|
||||
@dblclick="handleCellDblClick(getCell(wi, d - 1, p.key))"
|
||||
>{{ getCell(wi, d - 1, p.key).text }}</td>
|
||||
</template>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="select-tip">
|
||||
提示:在时间编排区域按住鼠标拖动可连片多选,按住 Ctrl 键可用鼠标选择多个区域;双击事件格可吸取其设置。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 学员队选取弹窗 ==================== -->
|
||||
<ClassTeamSelectDialog
|
||||
:visible="teamSelectVisible"
|
||||
@update:visible="val => teamSelectVisible = val"
|
||||
@confirm="handleTeamConfirm"
|
||||
/>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
|
||||
|
||||
// ==================== 模块级常量 ====================
|
||||
|
||||
/** 时段定义:1-2、3-4、5-6 恒显示;7-8 / 9-10(晚上) / 夜间 由复选框控制 */
|
||||
const ALL_PERIODS = [
|
||||
{ key: '1-2', label: '1-2' },
|
||||
{ key: '3-4', label: '3-4' },
|
||||
{ key: '5-6', label: '5-6' },
|
||||
{ key: '7-8', label: '7-8' },
|
||||
{ key: '9-10', label: '9-10' },
|
||||
{ key: 'night', label: '夜间' }
|
||||
]
|
||||
|
||||
/** 事件模式下标 → ALL_PERIODS 下标(1-2、3-4、5-6、9-10) */
|
||||
const PATTERN_PERIOD_IDX = [0, 1, 2, 4]
|
||||
|
||||
/** 事件类型 → 颜色 */
|
||||
const TYPE_STYLE = {
|
||||
red: { bg: '#d94343', fg: '#ffffff' }, // 正常课程/活动(马基、保密、布雷、维修、布修、战术、国庆节、政工)
|
||||
dark: { bg: '#2a8a7f', fg: '#ffffff' }, // 深青/墨绿:游泳、道德、日期标记
|
||||
green: { bg: '#4f9d57', fg: '#ffffff' }, // 特殊课程/活动(核心、组训)
|
||||
cyan: { bg: '#8fd4e0', fg: '#14505a' } // 青/浅蓝:学前、元旦
|
||||
}
|
||||
|
||||
const E = (text, type) => ({ text, type })
|
||||
const D = null // 日期格:显示当天日期
|
||||
|
||||
/** 班历事件(第8周~第29周,严格按截图还原;null 为日期格) */
|
||||
const weekDefs = [
|
||||
// 学前适应(青)
|
||||
{
|
||||
num: 8, start: '08-21', end: '08-27',
|
||||
monday: [E('学前', 'cyan'), E('学前', 'cyan'), E('学前', 'cyan'), E('学前', 'cyan')],
|
||||
others: [E('学前', 'cyan'), E('学前', 'cyan'), E('学前', 'cyan'), E('学前', 'cyan')]
|
||||
},
|
||||
// 马基(红)+ 游泳(墨绿)
|
||||
{
|
||||
num: 9, start: '08-28', end: '09-03',
|
||||
monday: [E('马基', 'red'), E('游泳', 'dark'), D, D],
|
||||
others: [E('马基', 'red'), E('游泳', 'dark'), D, D]
|
||||
},
|
||||
{
|
||||
num: 10, start: '09-04', end: '09-10',
|
||||
monday: [E('马基', 'red'), E('游泳', 'dark'), D, D],
|
||||
others: [E('马基', 'red'), E('游泳', 'dark'), D, D]
|
||||
},
|
||||
// 马基(红)+ 保密(红)
|
||||
{
|
||||
num: 11, start: '09-11', end: '09-17',
|
||||
monday: [E('马基', 'red'), E('保密', 'red'), D, D],
|
||||
others: [E('马基', 'red'), E('保密', 'red'), D, D]
|
||||
},
|
||||
{
|
||||
num: 12, start: '09-18', end: '09-24',
|
||||
monday: [E('马基', 'red'), E('保密', 'red'), D, D],
|
||||
others: [E('马基', 'red'), E('保密', 'red'), D, D]
|
||||
},
|
||||
// 布雷(红)
|
||||
{
|
||||
num: 13, start: '09-25', end: '10-01',
|
||||
monday: [E('布雷', 'red'), E('布雷', 'red'), D, D],
|
||||
others: [E('布雷', 'red'), E('布雷', 'red'), D, D]
|
||||
},
|
||||
// 国庆节(红)
|
||||
{
|
||||
num: 14, start: '10-02', end: '10-08',
|
||||
monday: [E('国庆节', 'red'), E('国庆节', 'red'), E('国庆节', 'red'), E('国庆节', 'red')],
|
||||
others: [E('国庆节', 'red'), E('国庆节', 'red'), E('国庆节', 'red'), E('国庆节', 'red')]
|
||||
},
|
||||
{
|
||||
num: 15, start: '10-09', end: '10-15',
|
||||
monday: [E('布雷', 'red'), E('布雷', 'red'), D, D],
|
||||
others: [E('布雷', 'red'), E('布雷', 'red'), D, D]
|
||||
},
|
||||
{
|
||||
num: 16, start: '10-16', end: '10-22',
|
||||
monday: [E('布雷', 'red'), E('布雷', 'red'), D, D],
|
||||
others: [E('布雷', 'red'), E('布雷', 'red'), D, D]
|
||||
},
|
||||
{
|
||||
num: 17, start: '10-23', end: '10-29',
|
||||
monday: [E('布雷', 'red'), E('布雷', 'red'), D, D],
|
||||
others: [E('布雷', 'red'), E('布雷', 'red'), D, D]
|
||||
},
|
||||
// 维修(红)
|
||||
{
|
||||
num: 18, start: '10-30', end: '11-05',
|
||||
monday: [E('维修', 'red'), E('维修', 'red'), D, D],
|
||||
others: [E('维修', 'red'), E('维修', 'red'), D, D]
|
||||
},
|
||||
// 维修(红)+ 核心(绿)
|
||||
{
|
||||
num: 19, start: '11-06', end: '11-12',
|
||||
monday: [E('维修', 'red'), E('维修', 'red'), E('核心', 'green'), D],
|
||||
others: [E('维修', 'red'), E('维修', 'red'), E('核心', 'green'), D]
|
||||
},
|
||||
// 布修(红)
|
||||
{
|
||||
num: 20, start: '11-13', end: '11-19',
|
||||
monday: [E('布修', 'red'), E('布修', 'red'), D, D],
|
||||
others: [E('布修', 'red'), E('布修', 'red'), D, D]
|
||||
},
|
||||
{
|
||||
num: 21, start: '11-20', end: '11-26',
|
||||
monday: [E('布修', 'red'), E('布修', 'red'), D, D],
|
||||
others: [E('布修', 'red'), E('布修', 'red'), D, D]
|
||||
},
|
||||
{
|
||||
num: 22, start: '11-27', end: '12-03',
|
||||
monday: [E('布修', 'red'), E('布修', 'red'), D, D],
|
||||
others: [E('布修', 'red'), E('布修', 'red'), D, D]
|
||||
},
|
||||
// 维修(红)
|
||||
{
|
||||
num: 23, start: '12-04', end: '12-10',
|
||||
monday: [E('维修', 'red'), E('维修', 'red'), D, D],
|
||||
others: [E('维修', 'red'), E('维修', 'red'), D, D]
|
||||
},
|
||||
{
|
||||
num: 24, start: '12-11', end: '12-17',
|
||||
monday: [E('维修', 'red'), E('维修', 'red'), D, D],
|
||||
others: [E('维修', 'red'), E('维修', 'red'), D, D]
|
||||
},
|
||||
{
|
||||
num: 25, start: '12-18', end: '12-24',
|
||||
monday: [E('维修', 'red'), E('维修', 'red'), D, D],
|
||||
others: [E('维修', 'red'), E('维修', 'red'), D, D]
|
||||
},
|
||||
// 战术(红)
|
||||
{
|
||||
num: 26, start: '12-25', end: '12-31',
|
||||
monday: [E('战术', 'red'), E('战术', 'red'), E('战术', 'red'), E('战术', 'red')],
|
||||
others: [E('战术', 'red'), E('战术', 'red'), E('战术', 'red'), E('战术', 'red')]
|
||||
},
|
||||
// 元旦(青,周一)+ 组训(绿,周二~周五)
|
||||
{
|
||||
num: 27, start: '01-01', end: '01-07',
|
||||
monday: [E('元旦', 'cyan'), E('元旦', 'cyan'), E('元旦', 'cyan'), E('元旦', 'cyan')],
|
||||
others: [E('组训', 'green'), E('组训', 'green'), E('组训', 'green'), E('组训', 'green')]
|
||||
},
|
||||
// 政工(红)+ 道德(墨绿)
|
||||
{
|
||||
num: 28, start: '01-08', end: '01-14',
|
||||
monday: [E('政工', 'red'), E('道德', 'dark'), E('道德', 'dark'), E('0106', 'dark')],
|
||||
others: [E('政工', 'red'), E('道德', 'dark'), E('道德', 'dark'), D]
|
||||
},
|
||||
{
|
||||
num: 29, start: '01-15', end: '01-21',
|
||||
monday: [E('政工', 'red'), E('道德', 'dark'), E('道德', 'dark'), E('0115', 'dark')],
|
||||
others: [E('政工', 'red'), E('道德', 'dark'), E('道德', 'dark'), D]
|
||||
}
|
||||
]
|
||||
|
||||
/** 第8周周一(起始日期) */
|
||||
const START_DATE = '2017-08-21'
|
||||
|
||||
export default {
|
||||
name: 'ClassCalendarDialog',
|
||||
components: { ClassTeamSelectDialog },
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
/** 班次号(如 0203) */
|
||||
bc: { type: String, default: '0399' },
|
||||
/** 学期名称 */
|
||||
semesterName: { type: String, default: '2017年秋季学期' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// ==================== 学期与班次信息 ====================
|
||||
/** 校历范围 */
|
||||
calStart: '2017-07-08',
|
||||
calEnd: '2018-02-11',
|
||||
calWeeks: 32,
|
||||
/** 班历范围(校历基础上为班次安排) */
|
||||
classStart: '2017-08-24',
|
||||
classEnd: '2018-02-05',
|
||||
classWeeks: 25,
|
||||
|
||||
// ==================== 事件设置区域 ====================
|
||||
eventName: '',
|
||||
isBold: false, // 加粗显示
|
||||
isSchedulable: false, // 可排课
|
||||
isRegularClass: false, // 正课
|
||||
showRemark: false, // 课表备注中显示
|
||||
remark: '',
|
||||
show78: false, // 显示7/8节
|
||||
showEvening: true, // 显示晚上(已勾选)
|
||||
showNight: false, // 显示夜间
|
||||
|
||||
// ==================== 班历表格数据 ====================
|
||||
dayNames: ['一', '二', '三', '四', '五'],
|
||||
weekDefs,
|
||||
allCells: this.buildCells(),
|
||||
cellMap: new Map(),
|
||||
selectedIds: new Set(),
|
||||
|
||||
// ==================== 学员队选取弹窗 ====================
|
||||
teamSelectVisible: false,
|
||||
/** 当前应用模式:region=选定区域 / whole=整个班历 */
|
||||
applyMode: 'region'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
},
|
||||
/** 时间编排区域实际显示的时段(由三个复选框控制) */
|
||||
visiblePeriods() {
|
||||
return ALL_PERIODS.filter((p) => {
|
||||
if (p.key === '7-8') return this.show78
|
||||
if (p.key === '9-10') return this.showEvening
|
||||
if (p.key === 'night') return this.showNight
|
||||
return true
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 每次打开对话框时重置选择状态
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.selectedIds = new Set()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始化非响应式拖拽状态
|
||||
this.dragActive = false
|
||||
this.dragMoved = false
|
||||
this.dragCtrl = false
|
||||
this.anchorCell = null
|
||||
this.anchorPos = { r: 0, c: 0 }
|
||||
this.lastPos = { r: 0, c: 0 }
|
||||
this.dragSnapshot = new Set()
|
||||
// 构建 cellMap 索引
|
||||
this.allCells.forEach((c) => this.cellMap.set(c.id, c))
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('mouseup', this.endSelect)
|
||||
document.addEventListener('click', this.onDocClick)
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('mouseup', this.endSelect)
|
||||
document.removeEventListener('click', this.onDocClick)
|
||||
},
|
||||
methods: {
|
||||
/** 计算从起始日期偏移 n 周 + m 天后的 MMDD(替代 dayjs) */
|
||||
formatDateOffset(weeks, days) {
|
||||
const date = new Date(START_DATE + 'T00:00:00')
|
||||
date.setDate(date.getDate() + weeks * 7 + days)
|
||||
const mm = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const dd = String(date.getDate()).padStart(2, '0')
|
||||
return mm + dd
|
||||
},
|
||||
|
||||
/** 根据周定义生成时间格数据(周一~周五 × 全部时段) */
|
||||
buildCells() {
|
||||
const cells = []
|
||||
weekDefs.forEach((w, wi) => {
|
||||
for (let d = 0; d < 5; d++) {
|
||||
const dateText = this.formatDateOffset(wi, d)
|
||||
const pattern = d === 0 ? w.monday : w.others
|
||||
for (let pi = 0; pi < ALL_PERIODS.length; pi++) {
|
||||
const period = ALL_PERIODS[pi]
|
||||
const isSpecialPeriod = period.key === '7-8' || period.key === 'night'
|
||||
const dj = PATTERN_PERIOD_IDX.indexOf(pi)
|
||||
const def = isSpecialPeriod ? null : (pattern[dj] !== undefined ? pattern[dj] : null)
|
||||
const isEvent = !!(def && def.text)
|
||||
cells.push({
|
||||
id: `${wi}-${d}-${period.key}`,
|
||||
weekIdx: wi,
|
||||
day: d,
|
||||
periodKey: period.key,
|
||||
dateText,
|
||||
text: isEvent ? def.text : dateText,
|
||||
isDate: !isEvent,
|
||||
type: isEvent ? def.type : 'dark',
|
||||
bold: false,
|
||||
schedulable: true,
|
||||
regular: false,
|
||||
remark: false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
return cells
|
||||
},
|
||||
|
||||
getCell(weekIdx, day, periodKey) {
|
||||
return this.cellMap.get(`${weekIdx}-${day}-${periodKey}`)
|
||||
},
|
||||
|
||||
/** 单元格样式:事件格用事件色;日期格按星期交替背景色(一、三、五深绿 / 二、四浅绿) */
|
||||
cellStyle(cell) {
|
||||
if (cell.isDate) {
|
||||
return cell.day % 2 === 0
|
||||
? { backgroundColor: '#2a8a7f', color: '#ffffff' }
|
||||
: { backgroundColor: '#d6e9e2', color: '#20604f' }
|
||||
}
|
||||
const st = TYPE_STYLE[cell.type]
|
||||
return { backgroundColor: st.bg, color: st.fg }
|
||||
},
|
||||
|
||||
/** 单元格类:选中态 / 选项语义 */
|
||||
cellClass(cell) {
|
||||
return {
|
||||
'is-event': !cell.isDate,
|
||||
'is-date': cell.isDate,
|
||||
'is-bold': !cell.isDate && cell.bold,
|
||||
selected: this.selectedIds.has(cell.id),
|
||||
'not-schedulable': !cell.isDate && !cell.schedulable,
|
||||
'is-regular': !cell.isDate && cell.regular
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 多选交互(拖动连片 + Ctrl 多区域) ====================
|
||||
/** 计算格子在当前显示列布局下的行列坐标 */
|
||||
getPos(cell) {
|
||||
const periodIdx = this.visiblePeriods.findIndex((p) => p.key === cell.periodKey)
|
||||
return { r: cell.weekIdx, c: cell.day * this.visiblePeriods.length + periodIdx }
|
||||
},
|
||||
|
||||
/** 计算 anchor 与 last 之间矩形内的所有格子 id */
|
||||
rectIds(a, b) {
|
||||
const r1 = Math.min(a.r, b.r), r2 = Math.max(a.r, b.r)
|
||||
const c1 = Math.min(a.c, b.c), c2 = Math.max(a.c, b.c)
|
||||
const ids = []
|
||||
this.allCells.forEach((cell) => {
|
||||
const p = this.getPos(cell)
|
||||
if (p.r >= r1 && p.r <= r2 && p.c >= c1 && p.c <= c2) ids.push(cell.id)
|
||||
})
|
||||
return ids
|
||||
},
|
||||
|
||||
updateSelection() {
|
||||
const ids = this.rectIds(this.anchorPos, this.lastPos)
|
||||
this.selectedIds = this.dragCtrl
|
||||
? new Set([...this.dragSnapshot, ...ids])
|
||||
: new Set(ids)
|
||||
},
|
||||
|
||||
onCellMouseDown(cell, e) {
|
||||
if (e.button !== 0) return
|
||||
this.dragActive = true
|
||||
this.dragMoved = false
|
||||
this.dragCtrl = e.ctrlKey || e.metaKey
|
||||
this.dragSnapshot = new Set(this.selectedIds)
|
||||
this.anchorCell = cell
|
||||
this.anchorPos = this.getPos(cell)
|
||||
this.lastPos = { ...this.anchorPos }
|
||||
// 非 Ctrl 按下时立即清空旧选择(Ctrl 拖动则保留并追加)
|
||||
if (!this.dragCtrl) this.selectedIds = new Set()
|
||||
},
|
||||
|
||||
onCellMouseMove(cell) {
|
||||
if (!this.dragActive) return
|
||||
const pos = this.getPos(cell)
|
||||
if (pos.r === this.lastPos.r && pos.c === this.lastPos.c) return
|
||||
this.lastPos = pos
|
||||
this.dragMoved = true
|
||||
this.updateSelection()
|
||||
},
|
||||
|
||||
endSelect() {
|
||||
if (!this.dragActive) return
|
||||
this.dragActive = false
|
||||
if (!this.dragMoved && this.anchorCell) {
|
||||
if (this.dragCtrl) {
|
||||
// Ctrl + 单击:切换单个格子
|
||||
const set = new Set(this.selectedIds)
|
||||
if (set.has(this.anchorCell.id)) set.delete(this.anchorCell.id)
|
||||
else set.add(this.anchorCell.id)
|
||||
this.selectedIds = set
|
||||
} else {
|
||||
// 普通单击:单选该格
|
||||
this.selectedIds = new Set([this.anchorCell.id])
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/** 点击时间编排区域以外的空白区域时取消选择(按钮/输入框等交互元素不触发) */
|
||||
onDocClick(e) {
|
||||
const t = e.target
|
||||
if (!t || !t.closest) return
|
||||
if (t.closest('.calendar-table')) return
|
||||
if (t.closest('button, input, .el-button, .el-checkbox, .el-select, .el-input, .el-date-editor')) return
|
||||
this.selectedIds = new Set()
|
||||
},
|
||||
|
||||
// ==================== 事件操作 ====================
|
||||
/** 将事件格设置复制到工具栏(吸取) */
|
||||
applyToToolbar(cell) {
|
||||
this.eventName = cell.text
|
||||
this.isBold = cell.bold
|
||||
this.isSchedulable = cell.schedulable
|
||||
this.isRegularClass = cell.regular
|
||||
this.showRemark = cell.remark
|
||||
},
|
||||
|
||||
/** 吸取:将选中事件格的名称和选项设置复制到工具栏 */
|
||||
handleAbsorb() {
|
||||
const cell = [...this.selectedIds].map((id) => this.cellMap.get(id)).find((c) => c && !c.isDate)
|
||||
if (!cell) {
|
||||
this.$message.warning('请先选择要吸取的事件格')
|
||||
return
|
||||
}
|
||||
this.applyToToolbar(cell)
|
||||
this.$message.success('已吸取事件设置到工具栏,可像刷子一样快速设定')
|
||||
},
|
||||
|
||||
/** 双击事件格:直接吸取 */
|
||||
handleCellDblClick(cell) {
|
||||
if (cell.isDate) {
|
||||
this.$message.info('该时间格没有事件,无法吸取')
|
||||
return
|
||||
}
|
||||
this.applyToToolbar(cell)
|
||||
this.$message.success(`已吸取「${cell.text}」的设置到工具栏`)
|
||||
},
|
||||
|
||||
/** 设定:将事件名称和选项应用到所选时间格 */
|
||||
handleSet() {
|
||||
const ids = [...this.selectedIds]
|
||||
if (!ids.length) {
|
||||
this.$message.warning('请先在时间编排区域选择时间格')
|
||||
return
|
||||
}
|
||||
const name = this.eventName.trim()
|
||||
if (!name) {
|
||||
this.$message.warning('请先输入事件名称')
|
||||
return
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
const c = this.cellMap.get(id)
|
||||
c.text = name
|
||||
c.isDate = false
|
||||
c.type = 'red'
|
||||
c.bold = this.isBold
|
||||
c.schedulable = this.isSchedulable
|
||||
c.regular = this.isRegularClass
|
||||
c.remark = this.showRemark
|
||||
})
|
||||
this.$message.success(`已为 ${ids.length} 个时间格设定事件「${name}」`)
|
||||
},
|
||||
|
||||
/** 删除:清除所选时间格的事件,恢复日期显示、低暗背景 */
|
||||
handleDelete() {
|
||||
const ids = [...this.selectedIds]
|
||||
if (!ids.length) {
|
||||
this.$message.warning('请先选择要删除事件的时间格')
|
||||
return
|
||||
}
|
||||
ids.forEach((id) => {
|
||||
const c = this.cellMap.get(id)
|
||||
c.isDate = true
|
||||
c.text = c.dateText
|
||||
c.bold = false
|
||||
c.schedulable = true
|
||||
c.regular = false
|
||||
c.remark = false
|
||||
})
|
||||
this.$message.success(`已删除 ${ids.length} 个时间格的事件`)
|
||||
},
|
||||
|
||||
/** 选定区域设置应用于其它班次:先选择时间格,再弹出学员队选取窗口 */
|
||||
handleApplyOther() {
|
||||
if (!this.selectedIds.size) {
|
||||
this.$message.warning('请先在时间编排区域选择时间格')
|
||||
return
|
||||
}
|
||||
this.applyMode = 'region'
|
||||
this.teamSelectVisible = true
|
||||
},
|
||||
|
||||
/** 整个班历应用于其它班次:弹出学员队选取窗口 */
|
||||
handleApplyWhole() {
|
||||
this.applyMode = 'whole'
|
||||
this.teamSelectVisible = true
|
||||
},
|
||||
|
||||
/** 学员队选取确认后应用 */
|
||||
handleTeamConfirm() {
|
||||
if (this.applyMode === 'whole') {
|
||||
// 后端接口未提供,仅前端演示:将当前班历完整复制到所选班次
|
||||
this.$message.success('整个班历已完整复制到所选班次(后端接口未提供,前端演示)')
|
||||
} else {
|
||||
this.$message.success('已应用选定区域设置到所选班次(前端演示)')
|
||||
}
|
||||
},
|
||||
|
||||
/** 刷新:恢复初始班历数据 */
|
||||
handleRefresh() {
|
||||
this.allCells.splice(0, this.allCells.length, ...this.buildCells())
|
||||
this.cellMap.clear()
|
||||
this.allCells.forEach((c) => this.cellMap.set(c.id, c))
|
||||
this.selectedIds = new Set()
|
||||
this.eventName = ''
|
||||
this.$message.success('已刷新班历')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// `class` 会直接加在 .el-dialog 元素自身,因此高度直接写在这里(不用 :deep)
|
||||
.class-calendar-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 92vh;
|
||||
margin: 4vh auto;
|
||||
overflow: hidden;
|
||||
|
||||
::v-deep .el-dialog__header {
|
||||
flex-shrink: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 14px 18px 18px;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.class-calendar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
// ========== 1. 学期与班次信息 ==========
|
||||
.info-panel {
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.info-title-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 20px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.semester-name,
|
||||
.class-name {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #1e6fff;
|
||||
}
|
||||
}
|
||||
|
||||
.date-range-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.range-item {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e6fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 2. 设置面板 ==========
|
||||
.settings-panel {
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.event-settings-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.setting-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 3. 银灰色 / 蓝色边框按钮 ==========
|
||||
.gray-btn {
|
||||
min-width: 80px;
|
||||
height: 28px;
|
||||
padding: 0 14px;
|
||||
font-size: 12px;
|
||||
color: #000;
|
||||
background: linear-gradient(180deg, #fdfdfd 0%, #ececec 50%, #dcdcdc 100%);
|
||||
border: 1px solid #7a7a7a;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f4f4f4 50%, #e6e6e6 100%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #c8c8c8;
|
||||
}
|
||||
}
|
||||
|
||||
.blue-btn {
|
||||
min-width: 190px;
|
||||
height: 28px;
|
||||
padding: 0 14px;
|
||||
font-size: 12px;
|
||||
color: #1e6fff;
|
||||
background: #ecf5ff;
|
||||
border: 1px solid #1e6fff;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #d9ecff;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #c6e2ff;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 4. 时间编排区域 ==========
|
||||
.table-area {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.calendar-table-wrapper {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #dcdfe6;
|
||||
border-radius: 4px;
|
||||
&:hover { background: #c0c4cc; }
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
}
|
||||
|
||||
.select-tip {
|
||||
flex-shrink: 0;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
border-top: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.calendar-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
|
||||
th {
|
||||
background: #f5f7fa;
|
||||
border: 1px solid #ebeef5;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
height: 28px;
|
||||
padding: 0 4px;
|
||||
text-align: center;
|
||||
min-width: 52px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
td {
|
||||
&.week-num,
|
||||
&.date-range {
|
||||
background: #fafafa;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.week-num {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
&.date-range {
|
||||
width: 130px;
|
||||
}
|
||||
}
|
||||
|
||||
.cell {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
padding: 0 2px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border: 1px solid #ebeef5;
|
||||
cursor: default;
|
||||
transition: filter 0.1s;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.92);
|
||||
}
|
||||
|
||||
&.is-event {
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// 选中高亮:蓝色描边
|
||||
&.selected {
|
||||
outline: 2px solid #1e6fff;
|
||||
outline-offset: -2px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
// 加粗显示
|
||||
&.is-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
// 可排课未勾选 → 左上角灰色小方块(该时段全校不允许排课)
|
||||
&.not-schedulable::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background: #c8c8c8;
|
||||
border: 1px solid #909399;
|
||||
}
|
||||
|
||||
// 正课 → 右上角蓝色小方块
|
||||
&.is-regular::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
background: #1e6fff;
|
||||
border: 1px solid #0b4bc0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user