<template>
|
<div class="app-container">
|
<!-- 页面标题 -->
|
<div class="page-header">
|
<h2>会议室使用查询</h2>
|
</div>
|
|
<!-- 查询条件 -->
|
<el-card class="search-card">
|
<el-form :model="queryForm" label-width="80px" inline>
|
<el-form-item label="查询日期">
|
<el-date-picker
|
v-model="queryForm.meetingDate"
|
type="date"
|
placeholder="请选择日期"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
:clearable="false"
|
/>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="handleSearch">查询</el-button>
|
<el-button @click="resetSearch">重置</el-button>
|
</el-form-item>
|
</el-form>
|
</el-card>
|
|
<!-- 会议室使用情况 -->
|
<el-card class="table-container" :loading="loading">
|
<div class="time-table">
|
<!-- 表头 -->
|
<div class="table-header">
|
<div class="header-cell room-header">会议室</div>
|
<div
|
v-for="timeSlot in timeSlots"
|
:key="timeSlot.value"
|
class="header-cell time-header"
|
>
|
{{ timeSlot.label }}
|
</div>
|
</div>
|
|
<!-- 表格内容 -->
|
<div class="table-body">
|
<div
|
v-for="room in roomUsage"
|
:key="room.id"
|
class="table-row"
|
>
|
<div class="cell room-cell">{{ room.name }}</div>
|
<div class="cells-container">
|
<template v-for="(cell, index) in generateMeetingCells(room)" :key="index">
|
<div
|
class="cell content-cell"
|
:class="[cell.type, `status-${cell.meeting?.status || '0'}`]"
|
:style="{ flex: cell.span-0.2 }"
|
@click="viewMeetingDetails(cell)"
|
>
|
<div v-if="cell.type === 'meeting'" class="meeting-content">
|
<div class="meeting-title">{{ cell.meeting.title }}</div>
|
<div class="meeting-time">{{ cell.startTime }}-{{ cell.endTime }}</div>
|
</div>
|
<div v-else class="free-content">
|
空闲
|
</div>
|
</div>
|
</template>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
|
<!-- 会议详情对话框 -->
|
<el-dialog
|
title="会议详情"
|
v-model="detailDialogVisible"
|
width="800px"
|
>
|
<div v-if="currentMeeting">
|
<el-descriptions :column="1" border>
|
<el-descriptions-item label="会议主题">{{ currentMeeting.title }}</el-descriptions-item>
|
<el-descriptions-item label="会议室">{{ currentMeeting.room }}</el-descriptions-item>
|
<el-descriptions-item label="会议时间">{{ currentMeeting.time }}</el-descriptions-item>
|
<el-descriptions-item label="主持人">{{ currentMeeting.host }}</el-descriptions-item>
|
<el-descriptions-item label="参会人数">{{ currentMeeting.participants }}人</el-descriptions-item>
|
<el-descriptions-item label="会议说明">{{ currentMeeting.description }}</el-descriptions-item>
|
</el-descriptions>
|
</div>
|
<template #footer>
|
<div class="dialog-footer">
|
<el-button @click="detailDialogVisible = false">关 闭</el-button>
|
</div>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import {ref, reactive, onMounted} from 'vue'
|
import {ElMessage} from 'element-plus'
|
import {getMeetingUseList} from "@/api/collaborativeApproval/meeting.js"
|
import dayjs from "dayjs";
|
|
// 查询表单
|
const queryForm = reactive({
|
meetingDate: dayjs().format('YYYY-MM-DD')
|
})
|
let loading = ref(false)
|
// 时间段(以半小时为间隔)
|
const timeSlots = ref([])
|
|
// 会议室使用情况
|
const roomUsage = ref([])
|
|
// 当前查看的会议
|
const currentMeeting = ref(null)
|
|
// 是否显示详情对话框
|
const detailDialogVisible = ref(false)
|
|
// 初始化时间槽(以半小时为间隔,从8:00到18:00)
|
const initTimeSlots = () => {
|
const slots = []
|
for (let hour = 8; hour < 18; hour++) {
|
// 每个小时添加两个时间段:整点和半点
|
slots.push({
|
label: `${hour.toString().padStart(2, '0')}:00`,
|
value: `${hour.toString().padStart(2, '0')}:00`
|
})
|
|
if (hour < 18) { // 到17:30为止
|
slots.push({
|
label: `${hour.toString().padStart(2, '0')}:30`,
|
value: `${hour.toString().padStart(2, '0')}:30`
|
})
|
}
|
}
|
timeSlots.value = slots
|
}
|
|
// 生成会议室的时间单元格
|
const generateMeetingCells = (room) => {
|
const cells = []
|
const meetings = room.meetings || []
|
const occupiedSlots = new Set()
|
|
// 处理每个会议
|
for (const meeting of meetings) {
|
|
const startIdx = timeSlots.value.findIndex(slot => slot.value === meeting.startTime)
|
let endIdx = timeSlots.value.findIndex(slot => slot.value === meeting.endTime)
|
if (endIdx === -1) {
|
endIdx = timeSlots.value.length
|
}
|
console.log('endIdx111', endIdx)
|
if (startIdx !== -1 && endIdx !== -1) {
|
// 标记被占用的时间段
|
for (let i = startIdx; i < endIdx; i++) {
|
occupiedSlots.add(timeSlots.value[i].value)
|
}
|
|
// 创建会议单元格
|
cells.push({
|
type: 'meeting',
|
meeting: meeting,
|
span: endIdx - startIdx,
|
startTime: meeting.startTime,
|
endTime: meeting.endTime
|
})
|
}
|
}
|
|
// 处理空闲时间段
|
for (let i = 0; i < timeSlots.value.length; i++) {
|
const slot = timeSlots.value[i]
|
if (!occupiedSlots.has(slot.value)) {
|
// 查找连续的空闲时间段
|
let span = 1
|
while (i + span < timeSlots.value.length &&
|
!occupiedSlots.has(timeSlots.value[i + span].value)) {
|
occupiedSlots.add(timeSlots.value[i + span].value)
|
span++
|
}
|
|
cells.push({
|
type: 'free',
|
span: span,
|
time: slot.value
|
})
|
}
|
}
|
|
// 按时间排序
|
cells.sort((a, b) => {
|
const timeA = a.startTime || a.time
|
const timeB = b.startTime || b.time
|
return timeSlots.value.findIndex(s => s.value === timeA) -
|
timeSlots.value.findIndex(s => s.value === timeB)
|
})
|
console.log('cells', cells)
|
return cells
|
}
|
|
// 查看会议详情
|
const viewMeetingDetails = (cell) => {
|
if (cell && cell.type === 'meeting') {
|
currentMeeting.value = cell.meeting
|
detailDialogVisible.value = true
|
} else {
|
ElMessage.info('该时间段会议室空闲')
|
}
|
}
|
|
// 查询按钮操作
|
const handleSearch = async () => {
|
loading.value = true
|
let resp = await getMeetingUseList({...queryForm})
|
roomUsage.value = resp.data
|
loading.value = false
|
}
|
|
// 重置搜索表单
|
const resetSearch = () => {
|
queryForm.date = dayjs().format('YYYY-MM-DD')
|
}
|
|
// 页面加载时获取数据
|
onMounted(() => {
|
// 初始化时间槽
|
initTimeSlots()
|
|
// 默认查询今天的数据
|
const today = new Date()
|
queryForm.date = today.toISOString().split('T')[0]
|
handleSearch()
|
})
|
</script>
|
|
<style scoped>
|
.app-container {
|
padding: 20px;
|
}
|
|
.page-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
}
|
|
.page-header h2 {
|
margin: 0;
|
color: #303133;
|
}
|
|
.search-card {
|
margin-bottom: 20px;
|
}
|
|
.table-container {
|
padding: 0;
|
}
|
|
.time-table {
|
width: 100%;
|
border-collapse: collapse;
|
}
|
|
.table-header {
|
display: flex;
|
border: 1px solid;
|
}
|
|
.table-row {
|
display: flex;
|
border: 1px solid #ebeef5;
|
border-top: none;
|
}
|
|
.header-cell {
|
padding: 12px 5px;
|
text-align: center;
|
font-weight: bold;
|
border-right: 1px solid;
|
min-height: 20px;
|
}
|
|
.room-header {
|
width: 120px;
|
}
|
|
.time-header {
|
flex: 1;
|
}
|
|
.cell {
|
padding: 15px 5px;
|
text-align: center;
|
border-right: 1px solid;
|
min-height: 20px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
word-break: break-word;
|
line-height: 1.2;
|
}
|
|
.room-cell {
|
width: 120px;
|
font-weight: bold;
|
}
|
|
.cells-container {
|
flex: 1;
|
display: flex;
|
}
|
|
.content-cell {
|
min-height: 60px;
|
cursor: pointer;
|
transition: all 0.3s;
|
}
|
|
.content-cell:hover {
|
opacity: 0.8;
|
}
|
|
.free {
|
color: #f56c6c;
|
}
|
|
.meeting {
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
}
|
|
.status-1 {
|
background-color: #fef0f0;
|
color: #d14646;
|
}
|
|
.status-0 {
|
background-color: #c7ddc8;
|
color: rgba(230, 162, 60, 0.29);
|
}
|
|
.meeting-content {
|
width: 100%;
|
}
|
|
.meeting-title {
|
font-weight: bold;
|
margin-bottom: 5px;
|
}
|
|
.meeting-time {
|
font-size: 12px;
|
}
|
|
.free-content {
|
color: #909399;
|
}
|
|
.dialog-footer {
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
}
|
</style>
|