huminmin
2026-06-01 a563ea879ef5fb6897e76d2df661e465dce2ab9b
src/views/collaborativeApproval/notificationManagement/meetIndex/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,363 @@
<template>
  <div>
    <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 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>