Files
admin-vben5/apps/web-antd/src/views/property/roomBooking/conferenceView/index.vue
2025-07-23 20:54:54 +08:00

425 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref, computed, onMounted, h } from 'vue';
import { Page } from '@vben/common-ui';
import dayjs from 'dayjs';
import type { TableColumnType } from 'ant-design-vue';
import { Radio, Select, Button, Table } from 'ant-design-vue';
import { getMeetName } from '#/api/property/roomBooking';
import {
getAppointmentListByDate,
getAppointmentListBymeetId,
} from '#/api/property/roomBooking/conferenceView';
import type { RadioChangeEvent } from 'ant-design-vue';
import type { SelectValue } from 'ant-design-vue/es/select';
// 会议室和预约信息的接口类型
interface ConferenceRoom {
id: string;
name: string;
}
interface ConferenceBooking {
id: string | number;
tbConferenceId: string | number; // 会议室ID
bookingName: string; // 预约人
deptName: string; // 部门名称
subject: string; // 会议主题
startTime: string; // 开始时间
endTime: string; // 结束时间
bookingDate: string; // 预约日期
status: number; // 状态
}
// 视图模式
const viewMode = ref<'date' | 'room'>('date');
// 会议室列表
const roomList = ref<ConferenceRoom[]>([]);
// 选中的会议室
const selectedRoom = ref<string>('');
// 选中的日期
const selectedDate = ref<string>('');
// 一周的日期
const weekDates = ref<string[]>([]);
// 预约数据
const bookings = ref<any[]>([]);
// 新增:表格渲染用的结构
const bookingTable = ref<Record<string, Record<string, any>>>({});
// 新增:加载状态
const loading = ref(false);
// 时间段只显示"上午" "下午"
const timeSlots = ['上午', '下午'];
// 生成一周日期
function generateWeekDates(): void {
const today = dayjs();
// 获取本周的周一
const startOfWeek = today.startOf('week');
const dates = Array.from({ length: 7 }, (_, i) => {
return startOfWeek.add(i, 'day').format('YYYY-MM-DD');
});
weekDates.value = dates;
// 默认选中今天
selectedDate.value = today.format('YYYY-MM-DD');
}
// 获取预约数据
async function fetchBookings(): Promise<void> {
loading.value = true;
try {
if (viewMode.value === 'date') {
const roomRes = await getMeetName();
roomList.value = roomRes.map((item: any) => ({
id: item.value,
name: item.name,
}));
const appointmentRes = await getAppointmentListByDate({
appointmentDate: selectedDate.value,
});
bookings.value = (appointmentRes || []).map((item: any) => ({
id: item.id,
meetId: item.meetId,
name: item.name,
person: item.person,
scheduledEndtime: item.scheduledEndtime,
scheduledStarttime: item.scheduledStarttime,
unit: item.unit,
personName: item.personName,
slots: item.slots,
unitName: item.unitName,
subject: item.subject,
deptName: item.unitName,
bookingName: item.personName,
startTime: item.scheduledStarttime,
endTime: item.scheduledEndtime,
bookingDate: item.scheduledStarttime,
tbConferenceId: item.meetId,
// 兼容后续渲染
}));
// 处理为表格结构
const table: Record<string, Record<string, any>> = { 上午: {}, 下午: {} };
roomList.value.forEach((room) => {
['上午', '下午'].forEach((slot) => {
const booking = bookings.value.find(
(b) => b.name === room.name && b.slots === slot,
);
table[slot][room.name] = booking || null;
});
});
bookingTable.value = table;
} else {
const res = await getAppointmentListBymeetId({
meetId: selectedRoom.value,
});
bookings.value = (res || []).map((item: any) => ({
id: item.id,
meetId: item.meetId,
name: item.name,
person: item.person,
scheduledEndtime: item.scheduledEndtime,
scheduledStarttime: item.scheduledStarttime,
unit: item.unit,
personName: item.personName,
slots: item.slots,
unitName: item.unitName,
subject: item.subject,
deptName: item.unitName,
bookingName: item.personName,
startTime: item.scheduledStarttime,
endTime: item.scheduledEndtime,
bookingDate: item.scheduledStarttime,
tbConferenceId: item.meetId,
}));
const table: Record<string, Record<string, any>> = { 上午: {}, 下午: {} };
weekDates.value.forEach((date) => {
['上午', '下午'].forEach((slot) => {
const booking = bookings.value.find(
(b) =>
dayjs(b.scheduledStarttime).format('YYYY-MM-DD') === date &&
b.slots === slot,
);
table[slot][date] = booking || null;
});
});
bookingTable.value = table;
}
} catch (error) {
console.error('获取预约数据失败:', error);
} finally {
loading.value = false;
}
}
// 切换视图模式
function handleViewModeChange(e: RadioChangeEvent): void {
viewMode.value = e.target.value;
if (viewMode.value === 'date') {
// 默认选中今天
selectedDate.value = dayjs().format('YYYY-MM-DD');
} else {
selectedRoom.value = roomList.value[0]?.id ?? '';
}
fetchBookings();
}
// 切换日期
function handleDateChange(date: string): void {
selectedDate.value = date;
fetchBookings();
}
// 切换会议室
function handleRoomChange(value: SelectValue): void {
selectedRoom.value = value as string;
fetchBookings();
}
// 获取单元格预约信息(上午/下午)
function getCellBooking(
col: string,
time: string,
): ConferenceBooking | undefined {
// 判断时间属于上午还是下午
function isMorning(startTime: string) {
const hour = dayjs(startTime).hour();
return hour < 12;
}
if (viewMode.value === 'date') {
const room = roomList.value.find((r) => r.name === col);
return bookings.value.find(
(b) =>
b.tbConferenceId === room?.id &&
((time === '上午' && isMorning(b.startTime)) ||
(time === '下午' && !isMorning(b.startTime))),
);
} else {
return bookings.value.find(
(b) =>
dayjs(b.bookingDate).format('YYYY-MM-DD') === col &&
((time === '上午' && isMorning(b.startTime)) ||
(time === '下午' && !isMorning(b.startTime))),
);
}
}
// 修改渲染预约单元格的函数
function renderBookingCell(booking: ConferenceBooking) {
return h(
'div',
{
class: 'booking-cell',
},
[
h('span', { class: 'booking-user' }, `预约人:${booking.bookingName}`),
h('span', { class: 'booking-dept' }, `单位:${booking.deptName}`),
h('span', { class: 'booking-subject' }, `主题:${booking.subject}`),
],
);
}
// 随机浅色背景色生成函数
function getRandomBgColor() {
const r = Math.floor(200 + Math.random() * 55);
const g = Math.floor(200 + Math.random() * 55);
const b = Math.floor(200 + Math.random() * 55);
return {
background: `rgba(${r},${g},${b},0.5)`,
borderRadius: '6px',
padding: '60px 10px',
transition: 'background 0.3s',
};
}
// 表格列配置
interface TableRecord {
time: string;
key: string;
[key: string]: any;
}
// 会议室列补全到7个
function getFullRoomList() {
const rooms = roomList.value.slice();
const count = rooms.length;
if (viewMode.value === 'date' && count < 7) {
for (let i = count; i < 7; i++) {
rooms.push({ id: `empty_${i}`, name: '' });
}
}
return rooms;
}
const columns = computed<TableColumnType<TableRecord>[]>(() => {
const baseColumns: TableColumnType<TableRecord>[] = [
{
title: '时间',
dataIndex: 'slot',
key: 'slot',
width: 100,
fixed: 'left' as const,
},
];
const dynamicColumns: TableColumnType<TableRecord>[] =
viewMode.value === 'date'
? getFullRoomList().map((room) => ({
title: room.name || ' ',
dataIndex: room.name,
key: room.name,
width: 200,
}))
: weekDates.value.map((date) => ({
title: dayjs(date).format('YYYY-MM-DD'),
dataIndex: date,
key: date,
width: 200,
}));
return [...baseColumns, ...dynamicColumns];
});
const tableData = computed<TableRecord[]>(() => {
const slots = ['上午', '下午'];
return slots.map((slot) => {
const row: any = { slot };
const cols =
viewMode.value === 'date'
? getFullRoomList().map((room) => room.name)
: weekDates.value;
cols.forEach((col) => {
row[col] = bookingTable.value[slot]?.[col] || null;
});
return row;
});
});
onMounted(() => {
generateWeekDates();
fetchBookings();
});
</script>
<template>
<Page>
<div class="conference-view">
<!-- 顶部控制区 -->
<div class="control-panel">
<Radio.Group v-model:value="viewMode" @change="handleViewModeChange">
<Radio.Button value="date">按日期</Radio.Button>
<Radio.Button value="room">按会议室</Radio.Button>
</Radio.Group>
<Select
v-if="viewMode === 'room'"
v-model:value="selectedRoom"
class="room-select"
placeholder="请选择会议室"
@change="handleRoomChange"
>
<Select.Option
v-for="room in roomList"
:key="room.id"
:value="room.id"
>
{{ room.name }}
</Select.Option>
</Select>
</div>
<div v-if="viewMode === 'date'" class="date-buttons">
<Button
v-for="date in weekDates"
:key="date"
:type="date === selectedDate ? 'primary' : 'default'"
@click="handleDateChange(date)"
>
{{ dayjs(date).format('YYYY-MM-DD') }}
</Button>
</div>
<Table
:columns="columns"
:data-source="tableData"
:pagination="false"
bordered
:scroll="{ x: 'max-content' }"
:loading="loading"
tableLayout="fixed"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex !== 'slot'">
<div v-if="record[column.dataIndex]" :style="getRandomBgColor()">
<div>预约人{{ record[column.dataIndex].personName }}</div>
<div>单位{{ record[column.dataIndex].unitName }}</div>
</div>
<div v-else></div>
</template>
</template>
</Table>
</div>
</Page>
</template>
<style lang="scss" scoped>
.conference-view {
.control-panel {
margin-bottom: 16px;
display: flex;
align-items: center;
.room-select {
width: 200px;
margin-left: 16px;
}
}
.date-buttons {
margin-top: 16px;
display: flex;
justify-content: center;
:deep(.ant-btn) {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
}
.booking-cell {
background: #e6f7ff;
padding: 8px;
border-radius: 4px;
min-height: 60px;
display: flex;
flex-direction: column;
gap: 4px;
span {
display: block;
font-size: 12px;
line-height: 1.5;
&.booking-user {
color: #1890ff;
font-weight: 500;
}
&.booking-dept {
color: #666;
}
&.booking-subject {
color: #333;
}
}
&:hover {
background: #bae7ff;
}
}
:deep(.ant-table-tbody > tr > td:first-child) {
padding: 10px;
height: 200px;
}
}
</style>