多余组件删除

This commit is contained in:
2026-08-23 21:02:32 +08:00
parent 526b9082df
commit 71257bdce0
@@ -1,706 +0,0 @@
<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" header-align="center" 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>