| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |