forked from liweijie/education
前端代码
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div class="app-container conflict-page">
|
||||
<!-- 冲突检查区域 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<div class="section-title">教学资源冲突检查</div>
|
||||
<div class="section-desc">2026年秋季学期教学资源冲突检查,可单独检查或执行全部检查</div>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<el-button size="mini" :disabled="loading" @click="handleReset">重置</el-button>
|
||||
<el-button type="primary" size="mini" :loading="loading" @click="handleCheckAll">执行全部检查</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 检查项卡片网格 -->
|
||||
<div class="check-grid">
|
||||
<div
|
||||
v-for="item in conflictItems"
|
||||
:key="item.key"
|
||||
class="check-item-card"
|
||||
:class="{ active: activeKey === item.key }"
|
||||
@click="activeKey = item.checked ? item.key : activeKey"
|
||||
>
|
||||
<div class="check-item-header">
|
||||
<span class="check-item-name">{{ item.label }}</span>
|
||||
<el-tag :type="getStatus(item).type" size="mini">{{ getStatus(item).text }}</el-tag>
|
||||
</div>
|
||||
<div class="check-item-desc">{{ item.desc }}</div>
|
||||
<div class="check-item-footer">
|
||||
<span class="conflict-count">
|
||||
冲突数:
|
||||
<b :class="item.count > 0 ? 'danger-text' : 'normal-text'">{{ item.count }}</b>
|
||||
</span>
|
||||
<el-button type="primary" plain size="mini" :disabled="loading" @click.stop="handleCheck(item)">检查</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 冲突明细区域 -->
|
||||
<div class="section-card">
|
||||
<div class="section-header detail-header">
|
||||
<div class="section-title">
|
||||
冲突明细
|
||||
<span v-if="activeKey" class="active-label">— {{ activeLabel }}</span>
|
||||
</div>
|
||||
<span v-if="activeKey" class="detail-count">共 {{ activeDetails.length }} 条记录</span>
|
||||
</div>
|
||||
<el-table v-if="activeKey" :data="activeDetails" border stripe size="mini" max-height="500">
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column label="冲突号" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="danger" size="mini">#{{ Math.floor(scope.$index / 2) + 1 }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kcmc" label="课程" min-width="130" show-overflow-tooltip />
|
||||
<el-table-column label="日期" width="100">
|
||||
<template slot-scope="scope">{{ formatDateStr(scope.row.rq) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="jc" label="节次" width="70" align="center" />
|
||||
<el-table-column prop="bc" label="班次" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="skjy" label="教员" width="90" />
|
||||
<el-table-column prop="jxcd" label="场地" width="130" show-overflow-tooltip />
|
||||
<el-table-column prop="zrdw" label="责任单位" width="100" />
|
||||
</el-table>
|
||||
<el-empty v-else v-show="!loading" description="点击上方卡片「检查」按钮,在此查看冲突明细" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 教学计划静态示例数据(待对接 getTeachingPlanList 接口)
|
||||
const MOCK_PLANS = [
|
||||
{ kcmc: '高等数学', rq: '2026-09-01', jc: 1, bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', zrdw: '基础部' },
|
||||
{ kcmc: '高等数学', rq: '2026-09-01', jc: 1, bc: '一班', skjy: '张教员', jxcd: '1号教学楼-101', zrdw: '基础部' },
|
||||
{ kcmc: '大学英语', rq: '2026-09-01', jc: 1, bc: '二班', skjy: '李教员', jxcd: '1号教学楼-101', zrdw: '基础部' },
|
||||
{ kcmc: '大学英语', rq: '2026-09-02', jc: 3, bc: '二班', skjy: '李教员', jxcd: '2号教学楼-205', zrdw: '基础部' },
|
||||
{ kcmc: '计算机基础', rq: '2026-09-01', jc: 1, bc: '三班', skjy: '王教员', jxcd: '实验楼-303', zrdw: '信息工程系' },
|
||||
{ kcmc: '军事理论', rq: '2026-09-02', jc: 3, bc: '一班', skjy: '赵教员', jxcd: '3号教学楼-110', zrdw: '政工系' }
|
||||
]
|
||||
|
||||
function formatDateStr(rq) {
|
||||
return (rq || '').substring(0, 10)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'TimetableConflict',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
allRecords: [],
|
||||
activeKey: '',
|
||||
conflictItems: [
|
||||
{ key: 'jxbssj', label: '教学班次时间冲突检查', desc: '同一教学班次在同一时间段被安排多门课程', count: 0, checked: false, details: [] },
|
||||
{ key: 'jxbskcsj', label: '教学班次必修与选修时间冲突检查', desc: '教学班次必修课与选修课时间重叠', count: 0, checked: false, details: [] },
|
||||
{ key: 'jysj', label: '教员时间冲突检查', desc: '同一教员在同一时间段被安排多门课程', count: 0, checked: false, details: [] },
|
||||
{ key: 'jssj', label: '教室时间冲突检查', desc: '同一教室在同一时间段被多门课程占用', count: 0, checked: false, details: [] }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeLabel() {
|
||||
const item = this.conflictItems.find(i => i.key === this.activeKey)
|
||||
return item ? item.label : ''
|
||||
},
|
||||
// 当前选中检查项的展开明细(打平所有冲突记录)
|
||||
activeDetails() {
|
||||
const item = this.conflictItems.find(i => i.key === this.activeKey)
|
||||
if (!item) return []
|
||||
const flat = []
|
||||
item.details.forEach(d => {
|
||||
d.items.forEach(it => flat.push(it))
|
||||
})
|
||||
return flat
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 初始化演示数据(模拟接口返回)
|
||||
this.allRecords = MOCK_PLANS.map((item, index) => ({ ...item, xh: index + 1 }))
|
||||
},
|
||||
methods: {
|
||||
getStatus(item) {
|
||||
if (!item.checked) return { type: 'info', text: '未检查' }
|
||||
return item.count > 0
|
||||
? { type: 'danger', text: '存在 ' + item.count + ' 处冲突' }
|
||||
: { type: 'success', text: '无冲突' }
|
||||
},
|
||||
/** 通用冲突检测:按分组字段找 > 1 的记录 */
|
||||
detectConflicts(records, groupFields) {
|
||||
const map = {}
|
||||
records.forEach(item => {
|
||||
const key = groupFields.map(f => f + '=' + (item[f] !== undefined && item[f] !== null ? item[f] : '')).join('|') +
|
||||
'|rq=' + formatDateStr(item.rq) + '|jc=' + (item.jc !== undefined && item.jc !== null ? item.jc : '')
|
||||
if (!map[key]) map[key] = []
|
||||
map[key].push(item)
|
||||
})
|
||||
return Object.keys(map)
|
||||
.filter(k => map[k].length > 1)
|
||||
.map(k => ({ key: k, items: map[k] }))
|
||||
.sort((a, b) => a.key.localeCompare(b.key))
|
||||
},
|
||||
handleCheck(item) {
|
||||
if (this.allRecords.length === 0) return
|
||||
let details = []
|
||||
switch (item.key) {
|
||||
case 'jxbssj':
|
||||
details = this.detectConflicts(this.allRecords, ['bc'])
|
||||
break
|
||||
case 'jxbskcsj':
|
||||
details = this.detectConflicts(this.allRecords, ['bc'])
|
||||
break
|
||||
case 'jysj':
|
||||
details = this.detectConflicts(this.allRecords, ['skjy'])
|
||||
break
|
||||
case 'jssj':
|
||||
details = this.detectConflicts(this.allRecords, ['jxcd'])
|
||||
break
|
||||
}
|
||||
item.details = details
|
||||
item.count = details.length
|
||||
item.checked = true
|
||||
this.activeKey = item.key
|
||||
if (item.count > 0) {
|
||||
this.$message.warning('「' + item.label + '」发现 ' + item.count + ' 处冲突')
|
||||
} else {
|
||||
this.$message.success('「' + item.label + '」未发现冲突')
|
||||
}
|
||||
},
|
||||
handleCheckAll() {
|
||||
this.conflictItems.forEach(item => this.handleCheck(item))
|
||||
const firstConflict = this.conflictItems.find(i => i.count > 0)
|
||||
if (firstConflict) {
|
||||
this.activeKey = firstConflict.key
|
||||
}
|
||||
const total = this.conflictItems.reduce((sum, item) => sum + item.count, 0)
|
||||
this.$message.success('全部检查完成,共发现 ' + total + ' 处冲突')
|
||||
},
|
||||
handleReset() {
|
||||
this.conflictItems.forEach(item => {
|
||||
item.count = 0
|
||||
item.checked = false
|
||||
item.details = []
|
||||
})
|
||||
this.activeKey = ''
|
||||
this.$message.info('已重置检查结果')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.conflict-page {
|
||||
.section-card {
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 检查项卡片网格 */
|
||||
.check-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.check-item-card {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 6px;
|
||||
padding: 14px 16px;
|
||||
transition: box-shadow 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--edu-green-primary, #00875a);
|
||||
box-shadow: 0 0 0 2px rgba(0, 135, 90, 0.15);
|
||||
}
|
||||
|
||||
.check-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
|
||||
.check-item-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.check-item-desc {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.check-item-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
|
||||
.conflict-count {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
|
||||
b {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.danger-text {
|
||||
color: #f56c6c;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.normal-text {
|
||||
color: #67c23a;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.active-label {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--edu-green-primary, #00875a);
|
||||
}
|
||||
|
||||
.detail-count {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user