1、添加实施计划统计分析;

2、添加单课程实施计划列表;
3、恢复同步班历;
4、冲突项添加颜色分级;
5、校历补上正课;
6、添加教学场地总览;
This commit is contained in:
2026-09-17 18:20:48 +08:00
parent 351df2cba2
commit 971ea46da5
11 changed files with 973 additions and 48 deletions
@@ -108,3 +108,85 @@ export function cancelScheduleAdjust(params) {
params
})
}
// ==================== 运行分析 /course-running/analysis ====================
// 接口依据:CourseRunningController 五个 analysis 端点(POSTbody 为 OperationAnalysisQueryDTO
/**
* 教学运行概览指标
* POST /course-running/analysis/overview
* 请求体:{ nd, xqd, jxxbh, startDate, endDate, dimension }
* 返回:OperationOverviewVO(排课完成率/缺勤率/场地使用率/周均课时/调课数等)
*/
export function getAnalysisOverview(data) {
return request({
url: '/course-running/analysis/overview',
method: 'post',
data
})
}
/**
* 各教学系排课完成率
* POST /course-running/analysis/course-completion
* 返回:List<CourseCompletionByDeptVO>jxxbh/jxxmc/completionRate/completedCount/totalCount
*/
export function getCourseCompletion(data) {
return request({
url: '/course-running/analysis/course-completion',
method: 'post',
data
})
}
/**
* 场地使用率分布
* POST /course-running/analysis/venue-usage
* 返回:List<VenueUsageRateVO>cdlybh/cdlymc/usageRate/usageCount/totalCount
*/
export function getVenueUsage(data) {
return request({
url: '/course-running/analysis/venue-usage',
method: 'post',
data
})
}
/**
* 调课频次趋势
* POST /course-running/analysis/adjust-trend
* 返回:List<ScheduleAdjustTrendVO>period/periodName/adjustCount
*/
export function getAdjustTrend(data) {
return request({
url: '/course-running/analysis/adjust-trend',
method: 'post',
data
})
}
/**
* 导出运行分析报表(四个 sheet:概览/系别完成率/场地使用率/调课趋势)
* POST /course-running/analysis/export
*/
export function exportAnalysisReport(data) {
return request({
url: '/course-running/analysis/export',
method: 'post',
data,
responseType: 'blob'
})
}
/**
* 导出教学实施计划课次列表
* GET /course-running/plan/export(参数同 listTeachingPlan,无分页)
*/
export function exportTeachingPlan(params) {
return request({
url: '/course-running/plan/export',
method: 'get',
params,
responseType: 'blob'
})
}
@@ -0,0 +1,304 @@
<template>
<div class="app-container analysis-page">
<!-- 筛选区 -->
<div class="section-card">
<div class="section-header">
<div>
<div class="section-title">实施计划分析</div>
<div class="section-desc">教学运行关键指标与分布统计数据来自课程教学运行模块</div>
</div>
<div class="header-actions">
<el-form inline class="toolbar-form" @submit.native.prevent>
<el-form-item label="年度">
<el-select v-model="query.nd" placeholder="全部" clearable filterable style="width: 120px">
<el-option v-for="y in yearOptions" :key="y" :label="y + ' 年'" :value="y" />
</el-select>
</el-form-item>
<el-form-item label="学期第次">
<el-select v-model="query.xqd" placeholder="全部" clearable style="width: 120px">
<el-option v-for="n in [1, 2, 3, 4]" :key="n" :label="'第 ' + n + ' 学期'" :value="String(n)" />
</el-select>
</el-form-item>
<el-form-item label="日期范围">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 260px"
/>
</el-form-item>
</el-form>
<el-button icon="el-icon-refresh" size="mini" @click="handleReset">重置</el-button>
<el-button type="primary" size="mini" icon="el-icon-search" :loading="loading" @click="loadAll">查询</el-button>
<el-button type="success" size="mini" icon="el-icon-download" :loading="exporting" @click="handleExport">导出报表</el-button>
</div>
</div>
</div>
<!-- 概览指标卡 -->
<div class="metric-grid" v-loading="loading">
<div v-for="m in metricCards" :key="m.label" class="metric-card">
<div class="metric-value" :style="{ color: m.color }">{{ m.value }}</div>
<div class="metric-label">{{ m.label }}</div>
</div>
</div>
<!-- 明细统计 -->
<div class="detail-grid">
<div class="section-card">
<div class="section-title small">各教学系排课完成率</div>
<el-table :data="completionList" size="mini" border stripe v-loading="loading">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="jxxmc" label="教学系" min-width="140" show-overflow-tooltip />
<el-table-column label="完成 / 总数" width="110" align="center">
<template slot-scope="scope">{{ scope.row.completedCount || 0 }} / {{ scope.row.totalCount || 0 }}</template>
</el-table-column>
<el-table-column label="完成率" min-width="160" align="center">
<template slot-scope="scope">
<el-progress :percentage="pct(scope.row.completionRate)" :stroke-width="12" />
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && completionList.length === 0" description="暂无数据" />
</div>
<div class="section-card">
<div class="section-title small">场地使用率分布</div>
<el-table :data="venueList" size="mini" border stripe v-loading="loading">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="cdlymc" label="场地类型" min-width="140" show-overflow-tooltip />
<el-table-column label="使用 / 总数" width="110" align="center">
<template slot-scope="scope">{{ scope.row.usageCount || 0 }} / {{ scope.row.totalCount || 0 }}</template>
</el-table-column>
<el-table-column label="使用率" min-width="160" align="center">
<template slot-scope="scope">
<el-progress :percentage="pct(scope.row.usageRate)" :stroke-width="12" color="#e6a23c" />
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && venueList.length === 0" description="暂无数据" />
</div>
</div>
<div class="section-card">
<div class="section-title small">学期调课频次趋势</div>
<el-table :data="trendList" size="mini" border stripe v-loading="loading">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="periodName" label="时段" min-width="160" show-overflow-tooltip />
<el-table-column prop="adjustCount" label="调课次数" width="120" align="center" />
<el-table-column label="分布" min-width="200" align="center">
<template slot-scope="scope">
<el-progress
:percentage="trendPct(scope.row.adjustCount)"
:stroke-width="12"
:show-text="false"
color="#f56c6c"
/>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && trendList.length === 0" description="暂无数据" />
</div>
</div>
</template>
<script>
import {
getAnalysisOverview,
getCourseCompletion,
getVenueUsage,
getAdjustTrend,
exportAnalysisReport
} from '@/api/teachBusiness/courseRunning'
import { listAllSemester } from '@/api/teachBusiness/semester'
import { saveAs } from 'file-saver'
export default {
name: 'OperationAnalysis',
data() {
return {
loading: false,
exporting: false,
query: { nd: undefined, xqd: undefined, jxxbh: undefined },
dateRange: null,
yearOptions: [],
overview: {},
completionList: [],
venueList: [],
trendList: []
}
},
computed: {
metricCards() {
const o = this.overview || {}
const pct = v => (v === null || v === undefined) ? '-' : Number(v).toFixed(1) + '%'
const num = v => (v === null || v === undefined) ? '-' : v
return [
{ label: '整体排课完成率', value: pct(o.overallCourseCompletionRate), color: '#00875a' },
{ label: '学员缺勤率', value: pct(o.studentAbsenceRate), color: '#e6a23c' },
{ label: '场地平均使用率', value: pct(o.venueUsageRate), color: '#e6a23c' },
{ label: '教师周均课时', value: num(o.avgWeeklyHoursPerTeacher), color: '#303133' },
{ label: '调课申请总数', value: num(o.totalScheduleAdjustCount), color: '#303133' },
{ label: '待审批调课', value: num(o.pendingAdjustCount), color: '#f56c6c' },
{ label: '保障计划数', value: num(o.totalSupportPlanCount), color: '#303133' },
{ label: '请假总数 / 待审批', value: num(o.totalLeaveCount) + ' / ' + num(o.pendingLeaveCount), color: '#303133' }
]
}
},
created() {
this.loadYears()
this.loadAll()
},
methods: {
pct(v) {
const n = Number(v)
if (isNaN(n)) return 0
return Math.min(100, Math.max(0, Math.round(n * 10) / 10))
},
trendPct(count) {
const max = Math.max(1, ...this.trendList.map(t => t.adjustCount || 0))
return Math.round(((count || 0) / max) * 100)
},
buildQuery() {
const q = {
nd: this.query.nd ? String(this.query.nd) : undefined,
xqd: this.query.xqd || undefined
}
if (this.dateRange && this.dateRange.length === 2) {
q.startDate = this.dateRange[0] + 'T00:00:00'
q.endDate = this.dateRange[1] + 'T23:59:59'
}
return q
},
loadYears() {
listAllSemester().then(response => {
const set = new Set()
;(response.data || []).forEach(item => {
if (item.nd !== null && item.nd !== undefined && item.nd !== '') set.add(item.nd)
})
this.yearOptions = Array.from(set).sort((a, b) => String(b).localeCompare(String(a)))
if (!this.query.nd && this.yearOptions.length) {
const current = (response.data || []).find(i => i.dqxq === true)
this.query.nd = String((current && current.nd) || this.yearOptions[0])
this.loadAll()
}
}).catch(() => {})
},
loadAll() {
const q = this.buildQuery()
this.loading = true
Promise.all([
getAnalysisOverview(q),
getCourseCompletion(q),
getVenueUsage(q),
getAdjustTrend(q)
]).then(([ov, cc, vu, at]) => {
this.overview = ov.data || {}
this.completionList = cc.data || []
this.venueList = vu.data || []
this.trendList = at.data || []
}).catch(() => {}).finally(() => {
this.loading = false
})
},
handleReset() {
this.query = { nd: undefined, xqd: undefined, jxxbh: undefined }
this.dateRange = null
this.loadAll()
},
handleExport() {
this.exporting = true
exportAnalysisReport(this.buildQuery()).then(blob => {
saveAs(blob, '运行分析报表.xlsx')
}).catch(() => {
this.$message.error('导出失败')
}).finally(() => {
this.exporting = false
})
}
}
}
</script>
<style scoped lang="scss">
.analysis-page {
.section-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 6px;
padding: 16px 20px;
margin-bottom: 16px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
.header-actions {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
.toolbar-form ::v-deep .el-form-item {
margin-bottom: 0;
}
}
}
.section-title {
font-size: 16px;
font-weight: 700;
color: #303133;
&.small {
font-size: 14px;
margin-bottom: 12px;
}
}
.section-desc {
margin-top: 8px;
font-size: 13px;
color: #909399;
}
.metric-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 16px;
}
.metric-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 6px;
padding: 18px 20px;
text-align: center;
.metric-value {
font-size: 26px;
font-weight: 700;
}
.metric-label {
margin-top: 8px;
font-size: 13px;
color: #909399;
}
}
.detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
}
</style>
@@ -1578,8 +1578,8 @@ export default {
}
&.is-warn {
background: #fdf8e8;
box-shadow: inset 0 0 0 1px #e6c974;
background: #fdf6ec;
box-shadow: inset 0 0 0 1px #e6a23c;
}
&.is-picked {
@@ -1708,14 +1708,14 @@ export default {
font-size: 12px;
}
/* 冲突格着色(橙色,按所选冲突维度命中 */
/* 冲突格着色(深红=硬冲突,与冲突明细页色板统一;橙色仅用于软提示 */
.tb-slot.is-conflict {
background: #fdece0;
box-shadow: inset 0 0 0 2px #d97706;
background: #fde2e2;
box-shadow: inset 0 0 0 2px #f56c6c;
}
.dot-cur { background: #f7c9d8; border: 1px solid #c2477a; }
.dot-conflict { background: #fdece0; border: 1px solid #d97706; }
.dot-conflict { background: #fde2e2; border: 1px solid #f56c6c; }
/* 全学期视图:每周一段,可跨周框选 */
.timetable.is-overview .tb-weekline {
@@ -0,0 +1,252 @@
<template>
<div class="app-container plan-page">
<div class="section-card">
<div class="section-header">
<div>
<div class="section-title">教学实施计划</div>
<div class="section-desc">按日期 / 节次排序的课次列表含主讲辅讲与上课场地</div>
</div>
<div class="header-actions">
<el-button icon="el-icon-refresh" size="mini" @click="handleReset">重置</el-button>
<el-button type="primary" size="mini" icon="el-icon-search" :loading="loading" @click="handleQuery">查询</el-button>
<el-button type="success" size="mini" icon="el-icon-download" :loading="exporting" @click="handleExport">导出</el-button>
</div>
</div>
<el-form inline class="filter-form" @submit.native.prevent>
<el-form-item label="年度">
<el-select v-model="query.nd" placeholder="全部" clearable filterable style="width: 120px" @change="handleQuery">
<el-option v-for="y in yearOptions" :key="y" :label="y + ' 年'" :value="Number(y)" />
</el-select>
</el-form-item>
<el-form-item label="上课日期">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 260px"
@change="handleQuery"
/>
</el-form-item>
<el-form-item label="教学方法">
<el-input v-model="query.jxff" placeholder="如:理论讲授" clearable style="width: 140px" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="填写状态">
<el-select v-model="query.txzt" placeholder="全部" clearable style="width: 120px" @change="handleQuery">
<el-option label="未填写" :value="0" />
<el-option label="已填写" :value="1" />
<el-option label="已提交" :value="2" />
</el-select>
</el-form-item>
</el-form>
</div>
<div class="section-card">
<el-table v-loading="loading" :data="sortedRecords" border stripe size="mini">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column prop="kcmc" label="课程名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="kcxh" label="课次" width="70" align="center" />
<el-table-column label="上课日期" width="110" align="center">
<template slot-scope="scope">{{ fmtDate(scope.row.rq) }}</template>
</el-table-column>
<el-table-column prop="jc" label="节次" width="70" align="center" />
<el-table-column prop="zjyxm" label="主讲" min-width="90" align="center" show-overflow-tooltip />
<el-table-column prop="fjyxm" label="辅讲" min-width="110" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.fjyxm || '-' }}</template>
</el-table-column>
<el-table-column prop="jsmc" label="场地" min-width="120" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.jsmc || '-' }}</template>
</el-table-column>
<el-table-column prop="jxnr" label="教学内容" min-width="160" show-overflow-tooltip />
<el-table-column prop="jxff" label="教学方法" width="100" align="center" />
<el-table-column label="填写状态" width="90" align="center">
<template slot-scope="scope">
<el-tag size="mini" :type="txztTag(scope.row.txzt)">{{ txztText(scope.row.txzt) }}</el-tag>
</template>
</el-table-column>
</el-table>
<el-pagination
v-if="total > 0"
class="detail-pagination"
background
layout="total, sizes, prev, pager, next"
:current-page="query.pageNum"
:page-size="query.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
<el-empty v-if="total === 0 && !loading" description="暂无课次数据" />
</div>
</div>
</template>
<script>
import { listTeachingPlan, exportTeachingPlan } from '@/api/teachBusiness/courseRunning'
import { listAllSemester } from '@/api/teachBusiness/semester'
import { saveAs } from 'file-saver'
export default {
name: 'TeachingPlan',
data() {
return {
loading: false,
exporting: false,
query: { nd: undefined, jxff: '', txzt: undefined, pageNum: 1, pageSize: 20 },
dateRange: null,
yearOptions: [],
records: [],
total: 0
}
},
computed: {
/* 按上课日期 + 节次排序(后端按课程分组排序,本页按时间轴展示) */
sortedRecords() {
return [...this.records].sort((a, b) => {
const da = this.fmtDate(a.rq)
const db = this.fmtDate(b.rq)
if (da !== db) return da < db ? -1 : 1
return (a.jc || 0) - (b.jc || 0)
})
}
},
created() {
this.loadYears()
this.loadList()
},
methods: {
fmtDate(rq) {
return (rq || '').substring(0, 10)
},
txztText(v) {
return v === 2 ? '已提交' : v === 1 ? '已填写' : '未填写'
},
txztTag(v) {
return v === 2 ? 'success' : v === 1 ? 'primary' : 'info'
},
buildParams() {
const p = {
nd: this.query.nd,
jxff: this.query.jxff || undefined,
txzt: this.query.txzt,
pageNum: this.query.pageNum,
pageSize: this.query.pageSize
}
if (this.dateRange && this.dateRange.length === 2) {
p.rqStart = this.dateRange[0] + 'T00:00:00'
p.rqEnd = this.dateRange[1] + 'T23:59:59'
}
return p
},
loadYears() {
listAllSemester().then(response => {
const set = new Set()
;(response.data || []).forEach(item => {
if (item.nd !== null && item.nd !== undefined && item.nd !== '') set.add(item.nd)
})
this.yearOptions = Array.from(set).sort((a, b) => String(b).localeCompare(String(a)))
if (!this.query.nd && this.yearOptions.length) {
const current = (response.data || []).find(i => i.dqxq === true)
this.query.nd = Number((current && current.nd) || this.yearOptions[0])
this.loadList()
}
}).catch(() => {})
},
loadList() {
this.loading = true
listTeachingPlan(this.buildParams()).then(response => {
const page = response.data || {}
this.records = page.records || []
this.total = page.total || 0
}).catch(() => {
this.records = []
this.total = 0
}).finally(() => {
this.loading = false
})
},
handleQuery() {
this.query.pageNum = 1
this.loadList()
},
handleReset() {
this.query = { nd: undefined, jxff: '', txzt: undefined, pageNum: 1, pageSize: 20 }
this.dateRange = null
this.loadList()
},
handlePageChange(page) {
this.query.pageNum = page
this.loadList()
},
handleSizeChange(size) {
this.query.pageSize = size
this.query.pageNum = 1
this.loadList()
},
handleExport() {
const p = this.buildParams()
delete p.pageNum
delete p.pageSize
this.exporting = true
exportTeachingPlan(p).then(blob => {
saveAs(blob, '教学实施计划.xlsx')
}).catch(() => {
this.$message.error('导出失败')
}).finally(() => {
this.exporting = false
})
}
}
}
</script>
<style scoped lang="scss">
.plan-page {
.section-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 6px;
padding: 16px 20px;
margin-bottom: 16px;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 12px;
.header-actions {
display: flex;
align-items: center;
gap: 8px;
}
}
.section-title {
font-size: 16px;
font-weight: 700;
color: #303133;
}
.section-desc {
margin-top: 8px;
font-size: 13px;
color: #909399;
}
.filter-form ::v-deep .el-form-item {
margin-bottom: 0;
}
.detail-pagination {
margin-top: 12px;
text-align: right;
}
}
</style>
@@ -61,8 +61,27 @@
<span v-if="activeKey" class="detail-count"> {{ detailTotal }} 条记录</span>
</div>
<template v-if="activeKey">
<el-table v-loading="detailLoading" :data="activeDetails" border stripe size="mini" max-height="500">
<div class="level-legend">
<span class="legend-item"><i class="legend-swatch legend-hard" />硬冲突双占 / 历表不可排</span>
<span class="legend-item"><i class="legend-swatch legend-soft" />提示非正课时段排课</span>
</div>
<el-table
v-loading="detailLoading"
:data="activeDetails"
border
stripe
size="mini"
max-height="500"
:row-class-name="conflictRowClass"
>
<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="scope.row.level === 'soft' ? 'warning' : 'danger'" size="mini" effect="dark">
{{ scope.row.level === 'soft' ? '提示' : '硬冲突' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="冲突号" width="90" align="center" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.conflictNo || '-' }}</template>
</el-table-column>
@@ -75,6 +94,7 @@
<el-table-column prop="teacherNames" label="教员" min-width="120" align="center" show-overflow-tooltip />
<el-table-column prop="classroomNames" label="场地" min-width="140" align="center" show-overflow-tooltip />
<el-table-column prop="responsibleDept" label="责任单位" min-width="110" align="center" show-overflow-tooltip />
<el-table-column prop="reason" label="冲突说明" min-width="200" show-overflow-tooltip />
</el-table>
<el-pagination
v-if="detailTotal > 0"
@@ -157,6 +177,10 @@ export default {
fmtDate(rq) {
return (rq || '').substring(0, 10)
},
/* 硬冲突行深红底、提示行橙底(与排课窗软硬两级口径一致) */
conflictRowClass({ row }) {
return row && row.level === 'soft' ? 'conflict-row-soft' : 'conflict-row-hard'
},
getStatus(item) {
if (!item.checked) return { type: 'info', text: '未检查' }
return item.count > 0
@@ -416,6 +440,45 @@ export default {
color: #909399;
}
.level-legend {
display: flex;
gap: 18px;
margin-bottom: 8px;
font-size: 12px;
color: #606266;
.legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
.legend-swatch {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 2px;
}
.legend-hard {
background: #fde2e2;
border: 1px solid #f56c6c;
}
.legend-soft {
background: #fdf6ec;
border: 1px solid #e6a23c;
}
}
::v-deep .el-table .conflict-row-hard td {
background-color: #fde2e2;
}
::v-deep .el-table .conflict-row-soft td {
background-color: #fdf6ec;
}
.detail-pagination {
margin-top: 12px;
text-align: right;
@@ -50,6 +50,7 @@
<div class="list-header">
<div class="list-title">教室列表</div>
<div class="list-actions">
<el-button icon="el-icon-view" @click="openScheduleOverview">场地占用总览</el-button>
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">新增</el-button>
</div>
</div>
@@ -206,6 +207,47 @@
<!-- 场地历编辑弹窗 -->
<VenueCalendarEditor :visible.sync="calendarVisible" :venue="calendarVenue" />
<!-- 场地占用总览某日某节全部场地的占用状态 -->
<el-dialog title="场地占用总览" :visible.sync="scheduleVisible" width="860px" append-to-body>
<el-form inline @submit.native.prevent>
<el-form-item label="日期">
<el-date-picker
v-model="scheduleQuery.rq"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择日期"
style="width: 160px"
@change="loadScheduleOverview"
/>
</el-form-item>
<el-form-item label="节次">
<el-select v-model="scheduleQuery.jc" placeholder="选择节次" style="width: 120px" @change="loadScheduleOverview">
<el-option v-for="n in 12" :key="n" :label="'第 ' + n + ' 节'" :value="n" />
</el-select>
</el-form-item>
</el-form>
<el-table v-loading="scheduleLoading" :data="scheduleList" border stripe size="mini" max-height="460">
<el-table-column prop="jsbh" label="教室编号" width="110" align="center" show-overflow-tooltip />
<el-table-column prop="jsmc" label="教室名称" min-width="130" show-overflow-tooltip />
<el-table-column prop="jxldh" label="教学楼" width="110" align="center" show-overflow-tooltip />
<el-table-column label="状态" width="90" align="center">
<template slot-scope="scope">
<el-tag size="mini" :type="scheduleStatusTag(scope.row.status)">{{ scheduleStatusText(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="kcmc" label="占用课程" min-width="140" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.kcmc || '-' }}</template>
</el-table-column>
<el-table-column prop="xydmc" label="学员队" min-width="110" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.xydmc || '-' }}</template>
</el-table-column>
<el-table-column prop="mc" label="备注" min-width="120" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.mc || '-' }}</template>
</el-table-column>
</el-table>
<el-empty v-if="!scheduleLoading && scheduleList.length === 0" description="请选择日期与节次查看占用情况" />
</el-dialog>
</div>
</template>
@@ -218,6 +260,7 @@ import {
listAllClassroom
} from '@/api/teachBusiness/classroom'
import { listAllBuild } from '@/api/teachBusiness/build'
import { venueSchedule } from '@/api/teachBusiness/venueCalendar'
import VenueCalendarEditor from './calendarEditor.vue'
export default {
@@ -250,6 +293,11 @@ export default {
// 场地历编辑弹窗
calendarVisible: false,
calendarVenue: null,
// 场地占用总览
scheduleVisible: false,
scheduleLoading: false,
scheduleQuery: { rq: '', jc: undefined },
scheduleList: [],
rules: {
jsmc: [{ required: true, message: '请输入教室名称', trigger: 'blur' }]
}
@@ -277,6 +325,37 @@ export default {
const base = [item.jxldh, item.jxlmc].filter(Boolean).join(' ')
return base || item.id
},
/* ---------- 场地占用总览 ---------- */
openScheduleOverview() {
this.scheduleVisible = true
if (!this.scheduleQuery.rq) {
const now = new Date()
const p = n => String(n).padStart(2, '0')
this.scheduleQuery.rq = `${now.getFullYear()}-${p(now.getMonth() + 1)}-${p(now.getDate())}`
}
this.loadScheduleOverview()
},
loadScheduleOverview() {
if (!this.scheduleQuery.rq || !this.scheduleQuery.jc) {
this.scheduleList = []
return
}
this.scheduleLoading = true
venueSchedule(this.scheduleQuery.rq + ' 00:00:00', this.scheduleQuery.jc).then(response => {
this.scheduleList = response.data || []
}).catch(() => {
this.scheduleList = []
}).finally(() => {
this.scheduleLoading = false
})
},
scheduleStatusText(status) {
return { free: '空闲', unavailable: '不可用', occupied: '已占用', noted: '有备注' }[status] || status || '-'
},
scheduleStatusTag(status) {
return { free: 'success', unavailable: 'danger', occupied: 'warning', noted: 'info' }[status] || 'info'
},
/**
* 独立加载全部场地类型,避免查询结果收窄后下拉候选项同步丢失。
*/