forked from liweijie/education
前端代码
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>
|
||||
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
title="教学班次学期信息明细"
|
||||
width="720px"
|
||||
class="class-detail-dialog"
|
||||
:close-on-click-modal="false"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<el-form label-width="100px" class="detail-form">
|
||||
<el-row :gutter="24">
|
||||
<!-- 左列 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班次">
|
||||
<el-input v-model="form.bc" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业">
|
||||
<el-input v-model="form.xkzy" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="年级">
|
||||
<el-input v-model="form.nj" readonly />
|
||||
</el-form-item>
|
||||
<el-form-item label="学期">
|
||||
<div class="semester-field">
|
||||
<el-input v-model="form.xq" readonly />
|
||||
<el-input v-model="form.ghn" class="plan-input" placeholder="" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="开学日期">
|
||||
<el-date-picker v-model="form.kxrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="form.jsrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学期周数">
|
||||
<el-input v-model="form.zs" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 右列 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="教学任务">
|
||||
<el-select v-model="form.jxrw" clearable placeholder="请选择" style="width: 100%">
|
||||
<el-option v-for="opt in taskOptions" :key="opt" :label="opt" :value="opt" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学期层次">
|
||||
<el-select v-model="form.xqcc" style="width: 100%">
|
||||
<el-option v-for="opt in levelOptions" :key="opt" :label="opt" :value="opt" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开放教员排课">
|
||||
<el-checkbox v-model="form.kfpy" />
|
||||
</el-form-item>
|
||||
<el-form-item label="查找教室">
|
||||
<el-select v-model="form.zxjs" style="width: 100%">
|
||||
<el-option v-for="opt in roomOptions" :key="opt" :label="opt" :value="opt" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="教学楼">
|
||||
<el-select v-model="form.jxl" style="width: 100%">
|
||||
<el-option v-for="opt in buildingOptions" :key="opt" :label="opt" :value="opt" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="专用教室">
|
||||
<el-select v-model="form.zyjs" style="width: 100%">
|
||||
<el-option v-for="opt in zyjsOptions" :key="opt" :label="opt" :value="opt" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 备注 -->
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.bz" type="textarea" :rows="3" placeholder="" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ClassSemesterDetailDialog',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
/** 选中的班次信息 */
|
||||
classInfo: { type: Object, default: null },
|
||||
/** 学期名称 */
|
||||
semesterName: { type: String, default: '2017年秋季学期' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
bc: '班次0633', // 班次
|
||||
xkzy: '专业209', // 专业
|
||||
nj: '2017级', // 年级
|
||||
xq: this.semesterName, // 学期
|
||||
ghn: '规划内', // 规划内
|
||||
kxrq: '2017-09-03', // 开学日期
|
||||
jsrq: '2018-02-11', // 结束日期
|
||||
zs: 24, // 学期周数
|
||||
jxrw: '', // 教学任务
|
||||
xqcc: '第1学期', // 学期层次
|
||||
kfpy: true, // 开放教员排课
|
||||
zxjs: '10-03&10-03(50)', // 查找教室
|
||||
jxl: '5号楼', // 教学楼
|
||||
zyjs: '作战实验中心学术报告厅', // 专用教室
|
||||
bz: '' // 备注
|
||||
},
|
||||
taskOptions: ['2017年下半年教学任务', '研究生2017下半年教学任务'],
|
||||
levelOptions: ['第1学期', '第2学期', '第3学期'],
|
||||
roomOptions: ['10-03&10-03(50)', '10-04&10-04(40)', '9-01'],
|
||||
buildingOptions: ['5号楼', '6号楼', '综合楼'],
|
||||
zyjsOptions: ['作战实验中心学术报告厅', '综合教学楼101', '学术报告厅']
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 每次打开时按传入的班次信息初始化表单
|
||||
visible(val) {
|
||||
if (!val) return
|
||||
this.form.bc = (this.classInfo && this.classInfo.bc) || '班次0633'
|
||||
this.form.xkzy = (this.classInfo && this.classInfo.xkzy) || '专业209'
|
||||
this.form.nj = (this.classInfo && this.classInfo.nj) || '2017级'
|
||||
this.form.xq = this.semesterName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleConfirm() {
|
||||
this.$emit('confirm')
|
||||
this.dialogVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.class-detail-dialog {
|
||||
.detail-form {
|
||||
// 只读输入框灰色底
|
||||
::v-deep .el-input__inner[readonly] {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.semester-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
.el-input:first-child {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.plan-input {
|
||||
flex-shrink: 0;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,439 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
title="学员队选取"
|
||||
width="90%"
|
||||
top="5vh"
|
||||
:close-on-click-modal="false"
|
||||
class="team-select-dialog"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<div class="team-select">
|
||||
<!-- ==================== 1. 查询条件区域 ==================== -->
|
||||
<div class="query-panel">
|
||||
<el-form label-width="88px" class="query-form">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业">
|
||||
<div class="check-field">
|
||||
<el-checkbox v-model="queryForm.zyEnabled" />
|
||||
<el-select v-model="queryForm.zy" :disabled="!queryForm.zyEnabled" style="flex: 1">
|
||||
<el-option v-for="opt in zyOptions" :key="opt" :label="opt" :value="opt" />
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="年级">
|
||||
<div class="check-field">
|
||||
<el-checkbox v-model="queryForm.njEnabled" />
|
||||
<el-input v-model="queryForm.nj" :disabled="!queryForm.njEnabled" placeholder="" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="在校状态">
|
||||
<div class="check-field">
|
||||
<el-checkbox v-model="queryForm.zxztEnabled" />
|
||||
<el-select v-model="queryForm.zxzt" :disabled="!queryForm.zxztEnabled" style="flex: 1">
|
||||
<el-option v-for="opt in zxztOptions" :key="opt" :label="opt" :value="opt" />
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学员队名称">
|
||||
<div class="check-field">
|
||||
<el-checkbox v-model="queryForm.xydmcEnabled" />
|
||||
<el-input v-model="queryForm.xydmc" :disabled="!queryForm.xydmcEnabled" placeholder="" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务类别">
|
||||
<div class="check-field">
|
||||
<el-checkbox v-model="queryForm.rwlbEnabled" />
|
||||
<el-select v-model="queryForm.rwlb" :disabled="!queryForm.rwlbEnabled" style="flex: 1">
|
||||
<el-option v-for="opt in rwlbOptions" :key="opt" :label="opt" :value="opt" />
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<!-- 分页控制区域 -->
|
||||
<div class="pager-row">
|
||||
<button type="button" class="gray-btn" @click="handleQuery">查询</button>
|
||||
<button type="button" class="gray-btn" @click="handleFirst">首页</button>
|
||||
<button type="button" class="gray-btn" @click="handlePrev">上页</button>
|
||||
<button type="button" class="gray-btn" @click="handleNext">下页</button>
|
||||
<button type="button" class="gray-btn" @click="handleLast">末页</button>
|
||||
<el-input-number v-model="currentPage" :min="1" size="small" controls-position="right" style="width: 90px" />
|
||||
<span class="pager-label">每页条数</span>
|
||||
<el-select v-model="pageSize" size="small" style="width: 70px">
|
||||
<el-option v-for="n in pageSizeOptions" :key="n" :label="String(n)" :value="n" />
|
||||
</el-select>
|
||||
<button type="button" class="gray-btn" @click="handleLocate">定位</button>
|
||||
<el-select v-model="displaySize" size="small" style="width: 80px">
|
||||
<el-option v-for="n in displaySizeOptions" :key="n" :label="String(n)" :value="n" />
|
||||
</el-select>
|
||||
<button type="button" class="blue-btn" @click="handleSetPageSize">设置页项数</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 2. 上方数据表格 ==================== -->
|
||||
<div class="table-panel">
|
||||
<el-table
|
||||
:data="teamList"
|
||||
border
|
||||
stripe
|
||||
highlight-current-row
|
||||
size="small"
|
||||
class="team-table"
|
||||
@selection-change="handleTopSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column prop="xzdbm" label="行政队别名称" width="100" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xydmc" label="学员队名称" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="nj" label="年级" width="70" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zy" label="专业" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="人数" width="60" align="center">
|
||||
<template slot-scope="{ row }">{{ row.rs }}人</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="zxzt" label="在校状态" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="rwlb" label="任务类别" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zyjs" label="专用教室" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="rxrq" label="入学日期" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="byrq" label="毕业日期" width="120" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 3. 中间操作按钮区域 ==================== -->
|
||||
<div class="mid-actions">
|
||||
<span class="mid-label">已选择列表项</span>
|
||||
<button type="button" class="gray-btn" @click="handleAdd">加入</button>
|
||||
<button type="button" class="gray-btn" @click="handleRemove">移除</button>
|
||||
<button type="button" class="gray-btn" @click="handleClear">清除</button>
|
||||
<button type="button" class="gray-btn" @click="handleReset">重置</button>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 4. 下方数据表格(已选学员队列表) ==================== -->
|
||||
<div class="table-panel">
|
||||
<el-table
|
||||
:data="selectedList"
|
||||
border
|
||||
stripe
|
||||
size="small"
|
||||
class="team-table"
|
||||
@selection-change="handleBottomSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column prop="zy" label="专业" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xydmc" label="学员队名称" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="nj" label="年级" width="70" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="人数" width="60" align="center">
|
||||
<template slot-scope="{ row }">{{ row.rs }}人</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="rwlb" label="任务类别" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zxzt" label="在校状态" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zyjs" label="专用教室" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="rxrq" label="入学日期" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="byrq" label="毕业日期" width="120" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="bz" label="备注" width="80" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-empty v-if="!selectedList.length" description="暂无已选学员队,请从上方表格加入" :image-size="60" />
|
||||
</div>
|
||||
|
||||
<!-- ==================== 5. 底部按钮区域 ==================== -->
|
||||
<div class="footer-actions">
|
||||
<button type="button" class="gray-btn" @click="handleConfirm">确定</button>
|
||||
<button type="button" class="gray-btn" @click="handleCancel">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ClassTeamSelectDialog',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// ==================== 查询条件区域 ====================
|
||||
queryForm: {
|
||||
zyEnabled: false, // 专业启用复选框(未勾选)
|
||||
zy: '专业219', // 专业(默认值)
|
||||
njEnabled: true, // 年级启用复选框(已勾选)
|
||||
nj: '', // 年级(空值)
|
||||
zxztEnabled: false, // 在校状态启用复选框(未勾选)
|
||||
zxzt: '毕业', // 在校状态(默认值)
|
||||
xydmcEnabled: true, // 学员队名称启用复选框(已勾选)
|
||||
xydmc: '', // 学员队名称(空值)
|
||||
rwlbEnabled: false, // 任务类别启用复选框(未勾选)
|
||||
rwlb: '规划内' // 任务类别(默认值)
|
||||
},
|
||||
zyOptions: ['专业219', '专业220', '专业221'],
|
||||
zxztOptions: ['毕业', '在校'],
|
||||
rwlbOptions: ['规划内', '规划外'],
|
||||
|
||||
// ==================== 分页控制区域 ====================
|
||||
currentPage: 1,
|
||||
pageSize: 1,
|
||||
displaySize: 100,
|
||||
pageSizeOptions: [1, 5, 10, 20],
|
||||
displaySizeOptions: [50, 100, 200],
|
||||
|
||||
// ==================== 上方数据表格(可选学员队列表) ====================
|
||||
teamList: [
|
||||
{ xzdbm: '教学五系', xydmc: '班次0417', nj: '2016级', zy: '专业113', rs: 36, zxzt: '毕业', rwlb: '规划内', zyjs: '8-011', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
|
||||
{ xzdbm: '教学七系', xydmc: '班次0423', nj: '2016级', zy: '专业114', rs: 9, zxzt: '毕业', rwlb: '规划内', zyjs: '8-212(南楼)', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
|
||||
{ xzdbm: '教学七系', xydmc: '班次0411', nj: '2016级', zy: '专业112', rs: 38, zxzt: '毕业', rwlb: '规划内', zyjs: '6-203', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
|
||||
{ xzdbm: '教学七系', xydmc: '班次0405', nj: '2016级', zy: '专业111', rs: 34, zxzt: '毕业', rwlb: '规划内', zyjs: '8-007', rxrq: '2016年8月26日', byrq: '2018年1月21日' },
|
||||
{ xzdbm: '教学五系', xydmc: '班次0429', nj: '2017级', zy: '专业117', rs: 43, zxzt: '毕业', rwlb: '规划内', zyjs: '6-202', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
|
||||
{ xzdbm: '教学五系', xydmc: '班次0431', nj: '2017级', zy: '专业118', rs: 22, zxzt: '毕业', rwlb: '规划内', zyjs: '2-403', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
|
||||
{ xzdbm: '教学七系', xydmc: '班次0425', nj: '2017级', zy: '专业115', rs: 19, zxzt: '毕业', rwlb: '规划内', zyjs: '2-406', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
|
||||
{ xzdbm: '教学七系', xydmc: '班次0432', nj: '2017级', zy: '专业119', rs: 15, zxzt: '毕业', rwlb: '规划内', zyjs: '8-009(南楼)', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
|
||||
{ xzdbm: '教学七系', xydmc: '班次0434', nj: '2017级', zy: '专业120', rs: 45, zxzt: '毕业', rwlb: '规划内', zyjs: '6-303', rxrq: '2017年8月24日', byrq: '2019年1月21日' },
|
||||
{ xzdbm: '教学六系', xydmc: '班次0427', nj: '2017级', zy: '专业116', rs: 64, zxzt: '毕业', rwlb: '规划内', zyjs: '2-501', rxrq: '2017年8月24日', byrq: '2019年1月21日' }
|
||||
],
|
||||
/** 上方表格多选 */
|
||||
topSelection: [],
|
||||
/** 下方数据表格(已选学员队列表) */
|
||||
selectedList: [],
|
||||
bottomSelection: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTopSelectionChange(rows) {
|
||||
this.topSelection = rows
|
||||
},
|
||||
handleBottomSelectionChange(rows) {
|
||||
this.bottomSelection = rows
|
||||
},
|
||||
|
||||
// ==================== 分页控制 ====================
|
||||
handleQuery() {
|
||||
this.$message.success('查询(前端演示)')
|
||||
},
|
||||
handleFirst() {
|
||||
this.currentPage = 1
|
||||
this.$message.success('已跳转首页')
|
||||
},
|
||||
handlePrev() {
|
||||
if (this.currentPage > 1) this.currentPage--
|
||||
},
|
||||
handleNext() {
|
||||
this.currentPage++
|
||||
},
|
||||
handleLast() {
|
||||
this.currentPage = this.teamList.length || 1
|
||||
},
|
||||
handleLocate() {
|
||||
this.$message.success(`已定位到第 ${this.currentPage} 页`)
|
||||
},
|
||||
handleSetPageSize() {
|
||||
this.$message.success(`每页显示条数已设置为 ${this.displaySize}`)
|
||||
},
|
||||
|
||||
// ==================== 中间操作按钮 ====================
|
||||
/** 加入:将上方表格选中行加入已选列表(去重) */
|
||||
handleAdd() {
|
||||
if (!this.topSelection.length) {
|
||||
this.$message.warning('请先在上方表格选择学员队')
|
||||
return
|
||||
}
|
||||
const existing = new Set(this.selectedList.map((r) => r.xydmc))
|
||||
let added = 0
|
||||
this.topSelection.forEach((r) => {
|
||||
if (!existing.has(r.xydmc)) {
|
||||
this.selectedList.push({ ...r, bz: '' })
|
||||
added++
|
||||
}
|
||||
})
|
||||
if (added) this.$message.success(`已加入 ${added} 个学员队`)
|
||||
else this.$message.info('所选学员队已在列表中')
|
||||
},
|
||||
|
||||
/** 移除:将下方表格选中行移除 */
|
||||
handleRemove() {
|
||||
if (!this.bottomSelection.length) {
|
||||
this.$message.warning('请先在下方表格选择要移除的学员队')
|
||||
return
|
||||
}
|
||||
const removeKeys = new Set(this.bottomSelection.map((r) => r.xydmc))
|
||||
this.selectedList = this.selectedList.filter((r) => !removeKeys.has(r.xydmc))
|
||||
this.$message.success(`已移除 ${this.bottomSelection.length} 个学员队`)
|
||||
},
|
||||
|
||||
/** 清除:清空已选列表 */
|
||||
handleClear() {
|
||||
if (!this.selectedList.length) {
|
||||
this.$message.info('已选列表为空')
|
||||
return
|
||||
}
|
||||
this.selectedList = []
|
||||
this.$message.success('已清空已选列表')
|
||||
},
|
||||
|
||||
/** 重置:清空已选列表并重置查询条件 */
|
||||
handleReset() {
|
||||
this.selectedList = []
|
||||
Object.assign(this.queryForm, {
|
||||
zyEnabled: false, zy: '专业219',
|
||||
njEnabled: true, nj: '',
|
||||
zxztEnabled: false, zxzt: '毕业',
|
||||
xydmcEnabled: true, xydmc: '',
|
||||
rwlbEnabled: false, rwlb: '规划内'
|
||||
})
|
||||
this.currentPage = 1
|
||||
this.$message.success('已重置')
|
||||
},
|
||||
|
||||
// ==================== 底部按钮 ====================
|
||||
handleConfirm() {
|
||||
if (!this.selectedList.length) {
|
||||
this.$message.warning('请先选择要应用的学员队')
|
||||
return
|
||||
}
|
||||
this.$emit('confirm', this.selectedList.slice())
|
||||
this.dialogVisible = false
|
||||
this.$message.success(`选定区域设置已应用于 ${this.selectedList.length} 个班次`)
|
||||
},
|
||||
handleCancel() {
|
||||
this.dialogVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.team-select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
// ========== 1. 查询条件区域 ==========
|
||||
.query-panel {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 12px 16px 8px;
|
||||
|
||||
.query-form {
|
||||
.check-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.pager-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
margin-top: 4px;
|
||||
|
||||
.pager-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 2/4. 表格区域 ==========
|
||||
.table-panel {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
.team-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .el-empty {
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 3. 中间操作按钮区域 ==========
|
||||
.mid-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.mid-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
white-space: nowrap;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 5. 底部按钮区域 ==========
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
// ========== 银灰色 / 蓝色边框按钮 ==========
|
||||
.gray-btn {
|
||||
min-width: 70px;
|
||||
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: 110px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,706 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible="dialogVisible"
|
||||
:show-close="false"
|
||||
width="96%"
|
||||
top="3vh"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
class="peidang-dialog"
|
||||
@update:visible="val => dialogVisible = val"
|
||||
>
|
||||
<!-- ==================== 自定义标题栏 ==================== -->
|
||||
<div slot="header">
|
||||
<div class="peidang-titlebar">
|
||||
<i class="el-icon-reading title-icon"></i>
|
||||
<span class="title-text">
|
||||
班次学期配档——{{ semesterName }}——{{ classLabel }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="peidang-body">
|
||||
<!-- ==================== 上半部分:左右分栏 ==================== -->
|
||||
<div class="upper">
|
||||
<!-- 左侧 75%:配档课程编组管理表格 -->
|
||||
<div class="table-area">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="displayCourses"
|
||||
border
|
||||
stripe
|
||||
highlight-current-row
|
||||
size="small"
|
||||
max-height="240"
|
||||
class="peidang-table"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<el-table-column prop="name" label="科目名称" width="100" align="left" show-overflow-tooltip />
|
||||
<el-table-column prop="short" label="简称" width="60" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="planStart" label="计划起始周" width="60" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="actualStart" label="实际起始周" width="60" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="endWeek" label="结束周" width="60" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="连排课" width="50" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-checkbox :value="row.continuous" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="priority" label="优先序数" width="50" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="weeklyHours" label="周学时" width="50" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="hours" label="学时" width="50" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="正课时间" width="60" align="center">
|
||||
<template slot-scope="{ row }">
|
||||
<el-checkbox :value="row.scheduleTime" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已排学时" width="50" align="center">
|
||||
<template slot-scope="{ row }">{{ courseStats(row).scheduled }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="未排学时" width="50" align="center">
|
||||
<template slot-scope="{ row }">{{ courseStats(row).remaining }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总班次任务数" width="70" align="center">
|
||||
<template slot-scope="{ row }">{{ row.continuous ? 1 : 1 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="配档编组任务数" width="80" align="center">
|
||||
<template slot-scope="{ row }">{{ row.continuous ? 2 : 1 }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 右侧 25%:配档课程属性设置区 + 优先序数上下移动 -->
|
||||
<div class="prop-area">
|
||||
<div class="prop-title">配档课程属性</div>
|
||||
<el-form label-width="72px" size="small" class="prop-form">
|
||||
<el-form-item label="周学时">
|
||||
<el-input-number v-model="propForm.weeklyHours" :min="1" :max="40" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="起始周次">
|
||||
<el-input-number v-model="propForm.startWeek" :min="startWeek" :max="endWeek" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束周次">
|
||||
<el-input-number v-model="propForm.endWeek" :min="startWeek" :max="endWeek" controls-position="right" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="连排课">
|
||||
<el-checkbox v-model="propForm.continuous">单科独进</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="优先序数">
|
||||
<div class="priority-field">
|
||||
<el-input-number v-model="propForm.priority" :min="1" controls-position="right" style="width: 100%" />
|
||||
<el-button type="primary" plain icon="el-icon-arrow-up" @click="handlePriorityUp" />
|
||||
<el-button type="primary" plain icon="el-icon-arrow-down" @click="handlePriorityDown" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="配档编组">
|
||||
<el-input v-model="propForm.group" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="primary" class="apply-btn" @click="handleApplyProp">应用属性</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 下半部分:配档显示区域(甘特图) ==================== -->
|
||||
<div class="gantt-area">
|
||||
<div class="gantt-head">
|
||||
<div class="gantt-label-col">
|
||||
<div class="gantt-label">课程 / 周次</div>
|
||||
</div>
|
||||
<div class="gantt-body">
|
||||
<!-- 行1:周次刻度(红色竖线为周刻度) -->
|
||||
<div class="gantt-row">
|
||||
<div
|
||||
v-for="w in weeks"
|
||||
:key="w"
|
||||
class="gantt-cell week-cell"
|
||||
:class="{ 'month-boundary': isMonthBoundary(w) }"
|
||||
>{{ w }}</div>
|
||||
</div>
|
||||
<!-- 行2:本周可安排正课学时 -->
|
||||
<div class="gantt-row info-row">
|
||||
<div
|
||||
v-for="w in weeks"
|
||||
:key="w"
|
||||
class="gantt-cell"
|
||||
:class="{ 'month-boundary': isMonthBoundary(w) }"
|
||||
>可排{{ weeklyCapacity }}</div>
|
||||
</div>
|
||||
<!-- 行3:已安排学时 / 剩余学时 -->
|
||||
<div class="gantt-row info-row">
|
||||
<div
|
||||
v-for="w in weeks"
|
||||
:key="w"
|
||||
class="gantt-cell"
|
||||
:class="{ 'month-boundary': isMonthBoundary(w) }"
|
||||
>
|
||||
<span class="sch">已{{ monthlyScheduled(w) }}</span>
|
||||
<span class="rem">余{{ Math.max(0, weeklyCapacity - monthlyScheduled(w)) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 课程甘特条 -->
|
||||
<div class="gantt-courses">
|
||||
<div v-for="course in displayCourses" :key="course.id" class="gantt-course">
|
||||
<div class="gantt-label-col">
|
||||
<div class="course-tag" :class="course.continuous ? 'tag-continuous' : 'tag-normal'">
|
||||
<span class="course-name">{{ course.name }}({{ course.short }})</span>
|
||||
<span class="course-hours">
|
||||
已排{{ courseStats(course).scheduled }}
|
||||
<template v-if="courseStats(course).remaining > 0">+{{ courseStats(course).remaining }}</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gantt-body">
|
||||
<div class="gantt-row bar-row">
|
||||
<div
|
||||
v-for="w in weeks"
|
||||
:key="w"
|
||||
class="gantt-cell bar-cell"
|
||||
:class="{
|
||||
'month-boundary': isMonthBoundary(w),
|
||||
'bar-active': w >= course.actualStart && w <= course.endWeek,
|
||||
'bar-continuous': course.continuous,
|
||||
'bar-normal': !course.continuous
|
||||
}"
|
||||
>
|
||||
<span v-if="w >= course.actualStart && w <= course.endWeek" class="bar-hours">
|
||||
{{ courseWeekHours(course)[w] || '' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部:本月可安排正课总学时(月刻度) -->
|
||||
<div class="gantt-foot">
|
||||
<div class="gantt-label-col">
|
||||
<div class="gantt-label">本月正课总学时</div>
|
||||
</div>
|
||||
<div class="gantt-body">
|
||||
<div class="gantt-row">
|
||||
<div
|
||||
v-for="w in weeks"
|
||||
:key="w"
|
||||
class="gantt-cell month-cell"
|
||||
:class="{ 'month-boundary': isMonthBoundary(w) }"
|
||||
>
|
||||
<span v-if="bottomCells[w] && isMonthEndWeek(w)" class="month-total">
|
||||
第{{ bottomCells[w].no }}月:可排{{ bottomCells[w].total }}(已排{{ bottomCells[w].scheduled }} + 余{{ bottomCells[w].remaining }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 底部操作 ==================== -->
|
||||
<div slot="footer">
|
||||
<div class="peidang-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PeidangEditDialog',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
/** 学年学期名称 */
|
||||
semesterName: { type: String, default: '2017-2018学年第一学期' },
|
||||
/** 班次标签(如 教学五系2016级专业0113班次0417) */
|
||||
classLabel: { type: String, default: '教学五系2016级专业0113班次0417' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// ==================== 时间轴参数 ====================
|
||||
/** 该班次开学起始周次 */
|
||||
startWeek: 8,
|
||||
/** 该班次学期结束周次 */
|
||||
endWeek: 32,
|
||||
/** 该班次每周可安排的正课学时时数(去除不能排课学时) */
|
||||
weeklyCapacity: 40,
|
||||
/** 月刻度:每 4 周为一月(8-11 第1月,12-15 第2月...),虚线分隔 */
|
||||
monthBreaks: [12, 16, 20, 24, 28, 32],
|
||||
|
||||
// ==================== 配档课程数据 ====================
|
||||
courses: [
|
||||
// ---- 非连排课(按周学时安排,蓝色,每门独立一组) ----
|
||||
{ id: 'p1', name: '课程科目001', short: '政治', planStart: 8, actualStart: 8, endWeek: 23, continuous: false, priority: 1, weeklyHours: 4, hours: 60, scheduleTime: true, group: '组1' },
|
||||
{ id: 'p2', name: '课程科目002', short: '军事', planStart: 8, actualStart: 8, endWeek: 20, continuous: false, priority: 2, weeklyHours: 4, hours: 48, scheduleTime: true, group: '组2' },
|
||||
{ id: 'p3', name: '课程科目003', short: '战术', planStart: 8, actualStart: 10, endWeek: 21, continuous: false, priority: 3, weeklyHours: 4, hours: 44, scheduleTime: true, group: '组3' },
|
||||
{ id: 'p4', name: '课程科目004', short: '指挥', planStart: 8, actualStart: 12, endWeek: 22, continuous: false, priority: 4, weeklyHours: 4, hours: 40, scheduleTime: true, group: '组4' },
|
||||
// ---- 连排课(单科独进,红色,共为一组) ----
|
||||
{ id: 'p5', name: '课程科目005', short: '综合演练', planStart: 8, actualStart: 8, endWeek: 13, continuous: true, priority: 5, weeklyHours: 20, hours: 100, scheduleTime: true, group: '连排组' },
|
||||
{ id: 'p6', name: '课程科目006', short: '野营拉练', planStart: 14, actualStart: 14, endWeek: 16, continuous: true, priority: 6, weeklyHours: 24, hours: 48, scheduleTime: true, group: '连排组' }
|
||||
],
|
||||
|
||||
// ==================== 表格选中与属性设置 ====================
|
||||
currentCourse: null,
|
||||
propForm: {
|
||||
weeklyHours: 4,
|
||||
startWeek: 8,
|
||||
endWeek: 16,
|
||||
continuous: false,
|
||||
priority: 1,
|
||||
group: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.visible
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:visible', val)
|
||||
}
|
||||
},
|
||||
/** 周序列:8 ~ 32 */
|
||||
weeks() {
|
||||
const arr = []
|
||||
for (let w = this.startWeek; w <= this.endWeek; w++) arr.push(w)
|
||||
return arr
|
||||
},
|
||||
/** 月份信息:每月包含的周次与可安排正课学时 */
|
||||
months() {
|
||||
const list = []
|
||||
let mNo = 1
|
||||
let cur = this.startWeek
|
||||
while (cur <= this.endWeek) {
|
||||
const next = this.monthBreaks.find((b) => b > cur)
|
||||
const mEnd = Math.min((next === undefined ? this.endWeek + 1 : next) - 1, this.endWeek)
|
||||
list.push({ no: mNo, start: cur, end: mEnd, total: (mEnd - cur + 1) * this.weeklyCapacity })
|
||||
cur = mEnd + 1
|
||||
mNo++
|
||||
}
|
||||
return list
|
||||
},
|
||||
/** 底部每月汇总行数据:week -> 该月信息(仅月首格展示文字) */
|
||||
bottomCells() {
|
||||
const map = {}
|
||||
this.weeks.forEach((w) => {
|
||||
const m = this.months.find((item) => w >= item.start && w <= item.end)
|
||||
if (!m) return
|
||||
map[w] = {
|
||||
no: m.no,
|
||||
total: m.total,
|
||||
scheduled: this.monthlyScheduled(w),
|
||||
remaining: m.total - this.monthlyScheduled(w)
|
||||
}
|
||||
})
|
||||
return map
|
||||
},
|
||||
/** 表格展示顺序:非连排课在前,连排课在后;组内按实际起始周、优先序数排序 */
|
||||
displayCourses() {
|
||||
return [...this.courses].sort((a, b) => {
|
||||
if (a.continuous !== b.continuous) return a.continuous ? 1 : -1
|
||||
if (a.actualStart !== b.actualStart) return a.actualStart - b.actualStart
|
||||
return a.priority - b.priority
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (!val) return
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.tableRef) {
|
||||
this.$refs.tableRef.setCurrentRow(this.displayCourses[0])
|
||||
}
|
||||
this.currentCourse = this.displayCourses[0] || null
|
||||
this.syncPropForm()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 是否为月边界列(左侧画虚线) */
|
||||
isMonthBoundary(week) {
|
||||
return this.monthBreaks.includes(week)
|
||||
},
|
||||
/** 是否为某月的结束周(仅月结束列展示文字) */
|
||||
isMonthEndWeek(week) {
|
||||
return this.months.some((m) => m.end === week)
|
||||
},
|
||||
|
||||
/** 某周所有课程已安排学时之和 */
|
||||
monthlyScheduled(week) {
|
||||
return this.courses.reduce((sum, c) => {
|
||||
const map = this.courseWeekHours(c)
|
||||
return sum + (map[week] !== undefined ? map[week] : 0)
|
||||
}, 0)
|
||||
},
|
||||
|
||||
/** 每门课程按周分布的安排学时 */
|
||||
courseWeekHours(course) {
|
||||
const map = {}
|
||||
let remaining = course.hours
|
||||
let w = course.actualStart
|
||||
while (remaining > 0 && w <= course.endWeek) {
|
||||
const h = Math.min(course.weeklyHours, remaining)
|
||||
map[w] = h
|
||||
remaining -= h
|
||||
w++
|
||||
}
|
||||
return map
|
||||
},
|
||||
|
||||
/** 课程计算属性:已排学时 / 未排学时 */
|
||||
courseStats(course) {
|
||||
const map = this.courseWeekHours(course)
|
||||
const scheduled = Object.keys(map).reduce((sum, k) => sum + map[k], 0)
|
||||
return { scheduled, remaining: Math.max(0, course.hours - scheduled) }
|
||||
},
|
||||
|
||||
handleCurrentChange(row) {
|
||||
this.currentCourse = row
|
||||
this.syncPropForm()
|
||||
},
|
||||
|
||||
/** 属性设置表单同步(右侧) */
|
||||
syncPropForm() {
|
||||
if (!this.currentCourse) return
|
||||
this.propForm.weeklyHours = this.currentCourse.weeklyHours
|
||||
this.propForm.startWeek = this.currentCourse.actualStart
|
||||
this.propForm.endWeek = this.currentCourse.endWeek
|
||||
this.propForm.continuous = this.currentCourse.continuous
|
||||
this.propForm.priority = this.currentCourse.priority
|
||||
this.propForm.group = this.currentCourse.group
|
||||
},
|
||||
|
||||
/** 属性设置保存:应用所选课程的配档属性 */
|
||||
handleApplyProp() {
|
||||
if (!this.currentCourse) {
|
||||
this.$message.warning('请先在表格中选择一门课程')
|
||||
return
|
||||
}
|
||||
const row = this.currentCourse
|
||||
row.weeklyHours = this.propForm.weeklyHours
|
||||
row.actualStart = this.propForm.startWeek
|
||||
row.endWeek = this.propForm.endWeek
|
||||
row.continuous = this.propForm.continuous
|
||||
row.priority = this.propForm.priority
|
||||
row.group = this.propForm.group
|
||||
this.$message.success('配档属性已应用(前端演示)')
|
||||
},
|
||||
|
||||
/** 优先序数上移:优先序数减 1 */
|
||||
handlePriorityUp() {
|
||||
if (!this.currentCourse) {
|
||||
this.$message.warning('请先在表格中选择一门课程')
|
||||
return
|
||||
}
|
||||
if (this.currentCourse.priority <= 1) {
|
||||
this.$message.info('已是最高优先序数')
|
||||
return
|
||||
}
|
||||
this.currentCourse.priority -= 1
|
||||
this.syncPropForm()
|
||||
},
|
||||
|
||||
/** 优先序数下移:优先序数加 1 */
|
||||
handlePriorityDown() {
|
||||
if (!this.currentCourse) {
|
||||
this.$message.warning('请先在表格中选择一门课程')
|
||||
return
|
||||
}
|
||||
this.currentCourse.priority += 1
|
||||
this.syncPropForm()
|
||||
},
|
||||
|
||||
// ==================== 底部操作 ====================
|
||||
handleConfirm() {
|
||||
this.$emit('confirm')
|
||||
this.dialogVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.peidang-dialog {
|
||||
// ========== 自定义标题栏 ==========
|
||||
::v-deep .el-dialog__header {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.peidang-titlebar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
height: 40px;
|
||||
padding: 0 14px;
|
||||
background: linear-gradient(90deg, #0a246a 0%, #a6caf0 100%);
|
||||
color: #fff;
|
||||
|
||||
.title-icon {
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__body {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__footer {
|
||||
padding: 8px 12px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.peidang-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.peidang-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
// ========== 上半部分:左右分栏 ==========
|
||||
.upper {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
// 左侧 75%:表格
|
||||
.table-area {
|
||||
flex: 3;
|
||||
min-width: 0;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
.peidang-table {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 右侧 25%:属性设置
|
||||
.prop-area {
|
||||
flex: 1;
|
||||
min-width: 240px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
|
||||
.prop-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.prop-form {
|
||||
.priority-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
|
||||
.el-button {
|
||||
flex-shrink: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.apply-btn {
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 下半部分:配档显示区域 ==========
|
||||
.gantt-area {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
overflow: auto;
|
||||
max-height: 320px;
|
||||
|
||||
.gantt-head,
|
||||
.gantt-course,
|
||||
.gantt-foot {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
min-width: 860px;
|
||||
}
|
||||
|
||||
.gantt-label-col {
|
||||
flex-shrink: 0;
|
||||
width: 210px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.gantt-label {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.gantt-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.gantt-row {
|
||||
display: flex;
|
||||
|
||||
.gantt-cell {
|
||||
flex: 1;
|
||||
min-width: 34px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
color: #303133;
|
||||
border-right: 1px solid #ebeef5;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// 周刻度:顶部红色竖线
|
||||
.week-cell {
|
||||
font-weight: 600;
|
||||
color: #f56c6c;
|
||||
border-top: 2px solid #f56c6c;
|
||||
background: #fff5f5;
|
||||
}
|
||||
|
||||
.info-row .gantt-cell {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
|
||||
.sch {
|
||||
color: #1e6fff;
|
||||
}
|
||||
|
||||
.rem {
|
||||
color: #e6a23c;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// 月刻度虚线
|
||||
.month-boundary {
|
||||
border-left: 1px dashed #909399;
|
||||
}
|
||||
|
||||
// 课程条
|
||||
.bar-cell {
|
||||
height: 26px;
|
||||
position: relative;
|
||||
|
||||
.bar-hours {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.bar-active {
|
||||
&.bar-normal {
|
||||
background: #409eff;
|
||||
color: #fff;
|
||||
|
||||
.bar-hours {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&.bar-continuous {
|
||||
background: #f56c6c;
|
||||
color: #fff;
|
||||
|
||||
.bar-hours {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.month-cell {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
|
||||
.month-total {
|
||||
font-size: 11px;
|
||||
color: #606266;
|
||||
font-weight: 600;
|
||||
background: #f4f4f5;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 2px;
|
||||
padding: 0 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gantt-course {
|
||||
.gantt-label-col {
|
||||
align-items: center;
|
||||
|
||||
.course-tag {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 1px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
|
||||
&.tag-normal {
|
||||
background: #ecf5ff;
|
||||
border: 1px solid #a0cfff;
|
||||
color: #337ecc;
|
||||
}
|
||||
|
||||
&.tag-continuous {
|
||||
background: #fef0f0;
|
||||
border: 1px solid #fbc4c4;
|
||||
color: #d63031;
|
||||
}
|
||||
|
||||
.course-name {
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.course-hours {
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gantt-foot {
|
||||
.gantt-label-col {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,486 @@
|
||||
<template>
|
||||
<div class="app-container shift-semester-page">
|
||||
<!-- ==================== 1. 学年学期标题 ==================== -->
|
||||
<div class="list-title">{{ semesterName }}</div>
|
||||
|
||||
<!-- ==================== 2. 校历 / 预设日期信息区 ==================== -->
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form class="search-form">
|
||||
<div class="date-group">
|
||||
<span class="date-group-label">校历</span>
|
||||
<el-form-item label="开学日期">
|
||||
<el-date-picker v-model="semester.kxrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="semester.jsrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学期周数">
|
||||
<el-input-number v-model="semester.weeks" :min="1" controls-position="right" style="width: 90px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="date-group">
|
||||
<span class="date-group-label">预设</span>
|
||||
<el-form-item label="开学日期">
|
||||
<el-date-picker v-model="preset.kxrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期">
|
||||
<el-date-picker v-model="preset.jsrq" type="date" format="yyyy年M月d日" value-format="yyyy-MM-dd" style="width: 160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学期周数">
|
||||
<el-input-number v-model="preset.weeks" :min="1" controls-position="right" style="width: 90px" />
|
||||
</el-form-item>
|
||||
<el-button class="absorb-btn" type="primary" plain @click="handleAbsorbDate">吸取日期</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 3. 工具栏按钮区 ==================== -->
|
||||
<div class="list-toolbar">
|
||||
<div class="left-group">
|
||||
<!-- 新添:下拉列出可新建学期信息的班次 -->
|
||||
<el-dropdown trigger="click" @command="handleNewCommand">
|
||||
<el-button type="primary">
|
||||
<i class="el-icon-plus"></i>
|
||||
<span>新添</span>
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown" class="new-class-menu">
|
||||
<el-dropdown-item v-for="item in newableClasses" :key="item.bc" :command="item">
|
||||
{{ item.bc }}({{ item.xkzy }}、{{ item.nj }})
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<el-button type="primary" plain @click="handleAction('编辑')">编辑</el-button>
|
||||
<el-button type="primary" plain @click="handleQuickSetDates">快速设定学期日期</el-button>
|
||||
<el-button type="danger" @click="handleAction('删除')">删除</el-button>
|
||||
<el-button type="primary" plain @click="handleEditCalendar">编辑班历</el-button>
|
||||
<el-button type="primary" plain @click="handleAction('自动生成教学计划')">自动生成教学计划</el-button>
|
||||
<el-button type="primary" plain @click="handleAction('教学计划管理')">教学计划管理</el-button>
|
||||
|
||||
<!-- 序号排序:带下拉箭头,点击弹出排序方式 -->
|
||||
<el-dropdown trigger="click" @command="handleSortCommand">
|
||||
<el-button type="primary" plain>
|
||||
<span>{{ sortLabel }}</span>
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</el-button>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item command="xh">序号排序</el-dropdown-item>
|
||||
<el-dropdown-item command="bw">波次排序</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<el-button type="primary" plain @click="handleMerge">预设合班</el-button>
|
||||
<el-button type="primary" plain @click="handleAction('预设拆班')">预设拆班</el-button>
|
||||
<el-button type="primary" plain @click="handlePeidangEdit">配档编辑</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== 4. 班次学期信息列表 ==================== -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="displayData"
|
||||
border
|
||||
stripe
|
||||
highlight-current-row
|
||||
:span-method="spanMethod"
|
||||
:row-class-name="rowClassName"
|
||||
:header-cell-class-name="headerCellClassName"
|
||||
max-height="480"
|
||||
class="class-table"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<el-table-column prop="xzdw" label="行政队别" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xh" label="班次序号" width="70" align="center" />
|
||||
<el-table-column prop="bc" label="班级" width="70" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="rs" label="人数" width="55" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="nj" label="年级" width="85" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xkzy" label="学科专业" width="85" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="kxrq" label="开学日期" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jsrq" label="结束日期" width="90" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="xqcc" label="学期第次" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="zyjs" label="专用教室" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="jxrw" label="教学任务" min-width="160" align="left" show-overflow-tooltip />
|
||||
<el-table-column prop="zs" label="周数" width="55" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="kcrws" label="总课程数" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="kczs" label="总课时数" width="80" align="center" show-overflow-tooltip />
|
||||
<el-table-column label="周学时" width="80" align="center">
|
||||
<template slot-scope="{ row }">{{ row.zxs.toFixed(2) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="zxf" label="总学分" width="70" align="center" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-card>
|
||||
|
||||
<!-- ==================== 教学班次学期信息明细弹窗(新添) ==================== -->
|
||||
<ClassSemesterDetailDialog
|
||||
:visible="detailVisible"
|
||||
:class-info="selectedNewClass"
|
||||
:semester-name="semesterName"
|
||||
@update:visible="val => detailVisible = val"
|
||||
@confirm="handleDetailConfirm"
|
||||
/>
|
||||
|
||||
<!-- ==================== 班历设置弹窗(编辑班历) ==================== -->
|
||||
<ClassCalendarDialog
|
||||
:visible="calendarVisible"
|
||||
:bc="currentRow ? currentRow.bc : ''"
|
||||
:semester-name="semesterName"
|
||||
@update:visible="val => calendarVisible = val"
|
||||
/>
|
||||
|
||||
<!-- ==================== 预设合班弹窗 ==================== -->
|
||||
<ClassTeamSelectDialog
|
||||
:visible="teamVisible"
|
||||
@update:visible="val => teamVisible = val"
|
||||
@confirm="handleTeamConfirm"
|
||||
/>
|
||||
|
||||
<!-- ==================== 班次学期配档编辑弹窗 ==================== -->
|
||||
<PeidangEditDialog
|
||||
:visible="peidangVisible"
|
||||
:semester-name="semesterName"
|
||||
:class-label="peidangClassLabel"
|
||||
@update:visible="val => peidangVisible = val"
|
||||
@confirm="handleAction('配档编辑')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClassSemesterDetailDialog from './ClassSemesterDetailDialog.vue'
|
||||
import ClassCalendarDialog from './ClassCalendarDialog.vue'
|
||||
import ClassTeamSelectDialog from './ClassTeamSelectDialog.vue'
|
||||
import PeidangEditDialog from './PeidangEditDialog.vue'
|
||||
|
||||
export default {
|
||||
name: 'ShiftSemester',
|
||||
components: {
|
||||
ClassSemesterDetailDialog,
|
||||
ClassCalendarDialog,
|
||||
ClassTeamSelectDialog,
|
||||
PeidangEditDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// ==================== 1. 学年学期标题区 ====================
|
||||
semesterName: '2017-2018学年第一学期',
|
||||
|
||||
// ==================== 2. 校历 / 预设日期信息区 ====================
|
||||
/** 校历日期 */
|
||||
semester: {
|
||||
kxrq: '2017-07-08', // 校历开学日期
|
||||
jsrq: '2018-02-11', // 校历结束日期
|
||||
weeks: 32 // 学期周数
|
||||
},
|
||||
/** 预设(班次)日期 */
|
||||
preset: {
|
||||
kxrq: '2017-07-08', // 预设开学日期
|
||||
jsrq: '2018-02-11', // 预设结束日期
|
||||
weeks: 32 // 预设学期周数
|
||||
},
|
||||
|
||||
// ==================== 3. 工具栏按钮区 ====================
|
||||
/** 可新建学期信息的班次 */
|
||||
newableClasses: [
|
||||
{ bc: '班次0633', xkzy: '专业209', nj: '2017级' },
|
||||
{ bc: '班次0634', xkzy: '专业209', nj: '2017级' },
|
||||
{ bc: '班次0709', xkzy: '专业205', nj: '2017级' },
|
||||
{ bc: '班次0710', xkzy: '专业205', nj: '2017级' },
|
||||
{ bc: '班次0901', xkzy: '专业301', nj: '2017级' }
|
||||
],
|
||||
|
||||
/** 教学班次学期信息明细弹窗(新添) */
|
||||
detailVisible: false,
|
||||
selectedNewClass: null,
|
||||
|
||||
/** 班历设置弹窗(编辑班历) */
|
||||
calendarVisible: false,
|
||||
currentRow: null,
|
||||
|
||||
/** 预设合班弹窗 */
|
||||
teamVisible: false,
|
||||
|
||||
/** 配档编辑弹窗(班次学期配档)显示状态 */
|
||||
peidangVisible: false,
|
||||
/** 配档编辑弹窗标题(班次学期配档——...——班次) */
|
||||
peidangClassLabel: '',
|
||||
|
||||
/** 排序方式:xh=序号排序 / bw=波次排序 */
|
||||
sortType: 'xh',
|
||||
|
||||
/** 波次背景色(同一波次使用同一颜色连在一起显示) */
|
||||
bwColors: [
|
||||
'#e3f2fd', // 浅蓝
|
||||
'#e8f5e9', // 浅绿
|
||||
'#fff8e1', // 浅黄
|
||||
'#f3e5f5', // 浅紫
|
||||
'#e0f2f1' // 浅青
|
||||
],
|
||||
|
||||
// ==================== 4. 班次学期信息列表 ====================
|
||||
tableData: [
|
||||
// ---- 学员1队(跨 10 行) ----
|
||||
{ xzdw: '学员1队', xh: 1, bw: 1, bc: '0150', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-307', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
|
||||
{ xzdw: '学员1队', xh: 2, bw: 1, bc: '0151', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-307', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
|
||||
{ xzdw: '学员1队', xh: 3, bw: 1, bc: '0152', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-308', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
|
||||
{ xzdw: '学员1队', xh: 4, bw: 1, bc: '0153', rs: 40, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-308', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
|
||||
{ xzdw: '学员1队', xh: 5, bw: 1, bc: '0154', rs: 42, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-309', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 528, zxs: 22.0, zxf: 42 },
|
||||
{ xzdw: '学员1队', xh: 6, bw: 1, bc: '0155', rs: 42, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-309', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 528, zxs: 22.0, zxf: 42 },
|
||||
{ xzdw: '学员1队', xh: 7, bw: 1, bc: '0156', rs: 42, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-310', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 15, kczs: 540, zxs: 22.5, zxf: 43 },
|
||||
{ xzdw: '学员1队', xh: 8, bw: 1, bc: '0157', rs: 38, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-310', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 15, kczs: 540, zxs: 22.5, zxf: 43 },
|
||||
{ xzdw: '学员1队', xh: 9, bw: 1, bc: '0158', rs: 38, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-311', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
|
||||
{ xzdw: '学员1队', xh: 10, bw: 1, bc: '0159', rs: 38, nj: '2017级秋', xkzy: '专业5', kxrq: '2017-09-03', jsrq: '2018-02-05', xqcc: '第1学期', zyjs: '1-311', jxrw: '2017年下半年教学任务', zs: 24, kcrws: 14, kczs: 512, zxs: 21.33, zxf: 40 },
|
||||
// ---- 教学五系(跨 2 行) ----
|
||||
{ xzdw: '教学五系', xh: 11, bw: 2, bc: '0201', rs: 30, nj: '2016级', xkzy: '专业113', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '6-202', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 12, kczs: 480, zxs: 19.2, zxf: 36 },
|
||||
{ xzdw: '教学五系', xh: 12, bw: 2, bc: '0202', rs: 30, nj: '2016级', xkzy: '专业113', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '6-202', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 12, kczs: 480, zxs: 19.2, zxf: 36 },
|
||||
// ---- 教学七系(跨 3 行) ----
|
||||
{ xzdw: '教学七系', xh: 13, bw: 3, bc: '0203', rs: 36, nj: '2016级', xkzy: '专业115', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '8-007', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 13, kczs: 520, zxs: 20.8, zxf: 38 },
|
||||
{ xzdw: '教学七系', xh: 14, bw: 3, bc: '0204', rs: 36, nj: '2016级', xkzy: '专业115', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '8-007', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 13, kczs: 520, zxs: 20.8, zxf: 38 },
|
||||
{ xzdw: '教学七系', xh: 15, bw: 3, bc: '0205', rs: 36, nj: '2016级', xkzy: '专业115', kxrq: '2017-08-24', jsrq: '2018-02-05', xqcc: '第3学期', zyjs: '8-008', jxrw: '2017年下半年教学任务', zs: 25, kcrws: 13, kczs: 520, zxs: 20.8, zxf: 38 }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** 排序方式标签 */
|
||||
sortLabel() {
|
||||
return this.sortType === 'bw' ? '波次排序' : '序号排序'
|
||||
},
|
||||
/** 根据排序方式展示列表:波次排序时按波次分组连排 */
|
||||
displayData() {
|
||||
if (this.sortType === 'bw') {
|
||||
return [...this.tableData].sort((a, b) => a.bw - b.bw || a.xh - b.xh)
|
||||
}
|
||||
return this.tableData
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 默认选中第一行 班次0150(高亮)
|
||||
this.$nextTick(() => {
|
||||
if (this.tableData.length && this.$refs.tableRef) {
|
||||
this.$refs.tableRef.setCurrentRow(this.tableData[0])
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// ==================== 2. 校历 / 预设日期 ====================
|
||||
/** 吸取日期:将校历日期复制到预设日期 */
|
||||
handleAbsorbDate() {
|
||||
this.preset.kxrq = this.semester.kxrq
|
||||
this.preset.jsrq = this.semester.jsrq
|
||||
this.preset.weeks = this.semester.weeks
|
||||
this.$message.success('已吸取校历日期到预设日期')
|
||||
},
|
||||
|
||||
/** 快速设定学期日期 */
|
||||
handleQuickSetDates() {
|
||||
this.$message.success('快速设定学期日期(前端演示)')
|
||||
},
|
||||
|
||||
// ==================== 3. 工具栏按钮区 ====================
|
||||
/** 通用操作(前端演示) */
|
||||
handleAction(name) {
|
||||
this.$message.success(`${name}(前端演示)`)
|
||||
},
|
||||
|
||||
/** 新添:从下拉选择班次后打开明细弹窗 */
|
||||
handleNewCommand(item) {
|
||||
if (typeof item === 'string') return
|
||||
this.selectedNewClass = item
|
||||
this.detailVisible = true
|
||||
},
|
||||
|
||||
handleDetailConfirm() {
|
||||
this.$message.success('已创建班次学期信息(前端演示)')
|
||||
},
|
||||
|
||||
/** 编辑班历 */
|
||||
handleEditCalendar() {
|
||||
if (!this.currentRow) {
|
||||
this.$message.warning('请先在列表中选择一条班次学期信息')
|
||||
return
|
||||
}
|
||||
this.calendarVisible = true
|
||||
},
|
||||
|
||||
/** 预设合班 */
|
||||
handleMerge() {
|
||||
this.teamVisible = true
|
||||
},
|
||||
handleTeamConfirm(teams) {
|
||||
this.$message.success(`预设合班已应用于 ${teams.length} 个班次(前端演示)`)
|
||||
},
|
||||
|
||||
/** 配档编辑:选择班次后打开班次学期配档窗口 */
|
||||
handlePeidangEdit() {
|
||||
if (!this.currentRow) {
|
||||
this.$message.warning('请先在列表中选择一个班次')
|
||||
return
|
||||
}
|
||||
const row = this.currentRow
|
||||
this.peidangClassLabel = `${row.xzdw}${row.nj}${row.xkzy}班次${row.bc}`
|
||||
this.peidangVisible = true
|
||||
},
|
||||
|
||||
handleSortCommand(command) {
|
||||
this.sortType = command
|
||||
},
|
||||
|
||||
/** 行样式:波次排序时同一波次使用同一背景色 */
|
||||
rowClassName({ row }) {
|
||||
if (this.sortType !== 'bw') return ''
|
||||
const colorIndex = (row.bw - 1) % this.bwColors.length
|
||||
return `bw-row bw-row-${colorIndex}`
|
||||
},
|
||||
|
||||
// ==================== 4. 班次学期信息列表 ====================
|
||||
handleCurrentChange(row) {
|
||||
this.currentRow = row
|
||||
},
|
||||
|
||||
/** 合并单元格:行政队别列(第一列)纵向合并相同值 */
|
||||
spanMethod({ rowIndex, columnIndex }) {
|
||||
if (columnIndex === 0) {
|
||||
const list = this.displayData
|
||||
const current = list[rowIndex]
|
||||
// 非分组首行,隐藏
|
||||
if (rowIndex > 0 && list[rowIndex - 1].xzdw === current.xzdw) {
|
||||
return { rowspan: 0, colspan: 0 }
|
||||
}
|
||||
// 统计连续相同行政队别的行数
|
||||
let rowspan = 1
|
||||
for (let i = rowIndex + 1; i < list.length; i++) {
|
||||
if (list[i].xzdw === current.xzdw) rowspan++
|
||||
else break
|
||||
}
|
||||
return { rowspan, colspan: 1 }
|
||||
}
|
||||
return { rowspan: 1, colspan: 1 }
|
||||
},
|
||||
|
||||
/** 表头特殊样式:总课程数 / 总课时数列红色框标注 */
|
||||
headerCellClassName({ column }) {
|
||||
if (column.property === 'kcrws' || column.property === 'kczs') {
|
||||
return 'red-header-cell'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.shift-semester-page {
|
||||
// ========== 1. 学年学期标题 ==========
|
||||
.list-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid var(--edu-green-primary);
|
||||
}
|
||||
|
||||
// ========== 2. 校历 / 预设日期信息区 ==========
|
||||
.search-card {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.date-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.date-group-label {
|
||||
flex-shrink: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.absorb-btn {
|
||||
margin-left: auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 3. 工具栏按钮区 ==========
|
||||
.list-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.left-group,
|
||||
.right-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 新建下拉菜单 ==========
|
||||
.new-class-menu {
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
// ========== 4. 数据表格区 ==========
|
||||
$bwColors: (#e3f2fd, #e8f5e9, #fff8e1, #f3e5f5, #e0f2f1);
|
||||
|
||||
.table-card {
|
||||
flex: 1;
|
||||
|
||||
.class-table {
|
||||
width: 100%;
|
||||
|
||||
// 波次背景色(同一合班波次使用同一背景颜色连在一起显示)
|
||||
@for $i from 0 through length($bwColors) - 1 {
|
||||
::v-deep(.el-table__body tr.bw-row-#{$i} > td.el-table__cell) {
|
||||
background-color: nth($bwColors, $i + 1);
|
||||
}
|
||||
|
||||
::v-deep(.el-table__body tr.bw-row-#{$i}:hover > td.el-table__cell) {
|
||||
background-color: nth($bwColors, $i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 当前行高亮(优先于波次背景色)
|
||||
::v-deep(.el-table__body tr.current-row > td.el-table__cell) {
|
||||
background-color: #e1f5ec;
|
||||
}
|
||||
|
||||
::v-deep(.el-table__body tr.current-row:hover > td.el-table__cell) {
|
||||
background-color: #e1f5ec;
|
||||
}
|
||||
|
||||
// 表头红色框标注列(总课程数 / 总课时数)
|
||||
::v-deep(th.red-header-cell) {
|
||||
border: 2px solid #f56c6c !important;
|
||||
background: #fdeaea !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user