会议页面

This commit is contained in:
zjf committed 2025-06-17 17:57:57 +08:00
1 parent eff72e49b6
commit 3de34399b4
4 files changed
+282 -90

No files matched your search

+7 -7
View File
@@ -544,13 +544,13 @@
"iconPath": "",
"selectedIconPath": ""
}
// ,
// {
// "pagePath": "pages/integrated/index",
// "text": "综合服务",
// "iconPath": "static/tabbar/integrated.png",
// "selectedIconPath": "static/tabbar/integrated-select.png"
// }
,
{
"pagePath": "pages/integrated/index",
"text": "综合服务",
"iconPath": "static/tabbar/integrated.png",
"selectedIconPath": "static/tabbar/integrated-select.png"
}
]
}
}
+246 -83
View File
@@ -22,30 +22,34 @@
<!-- 日期切换 -->
<view class="date-tabs">
<view v-for="(d, i) in dateTabs" :key="i" :class="['date-tab', i === activeDate ? 'active' : '']" @tap="activeDate = i">{{ d }}</view>
<view v-for="(d, i) in dateTabs" :key="i" :class="['date-tab', i === activeDate ? 'active' : '']" @tap="handleDateChange(i)">{{ d }}</view>
</view>
<!-- 时间选择表格 -->
<view class="time-table">
<view class="time-row" v-for="(row, rowIdx) in timeTable" :key="rowIdx">
<view class="time-cell" v-for="(cell, colIdx) in row" :key="colIdx"
:class="cell.status"
@tap="selectTime(rowIdx, colIdx)"
>
<text v-if="cell.status === 'booked'">{{ cell.user }}</text>
<text v-else-if="cell.status === 'disabled'">🚫</text>
<!-- 时间选择列表 -->
<view class="time-list-container">
<scroll-view class="time-list" scroll-y>
<view class="time-list-content">
<view v-for="(slot, index) in timeSlots" :key="index"
class="time-item"
:class="[slot.status, selectedTime.includes(index) ? 'selected' : '']"
@tap="selectTime(index)"
>
<view class="time-range">{{ slot.startTime }} - {{ slot.endTime }}</view>
<view class="time-status">
<text v-if="slot.status === 'booked'">🚫 已预约: {{ slot.bookedBy?.userName }}</text>
<text v-else-if="slot.status === 'disabled'">🚫 不可预约</text>
<text v-else>✓ 可预约</text>
</view>
</view>
</view>
</view>
</view>
<view class="time-labels">
<text v-for="h in 10" :key="h" class="time-label">{{ 8 + h }}:00</text>
</scroll-view>
</view>
<!-- 预约信息 -->
<view class="reserve-info">
<view>会议时间:{{ selectedTimeText }}</view>
<view>时长:{{ selectedDuration }}分钟</view>
<view>发起人:张三</view>
<view>发起人:{{ currentUser.name }}</view>
</view>
<!-- 预约按钮 -->
@@ -54,62 +58,216 @@
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import meetingRoomApi from '@/sheep/api/meetingRoom'
// 定义类型
interface TimeSlotItem {
startTime: string
endTime: string
status: 'free' | 'booked' | 'disabled'
bookedBy: {
userId: string
userName: string
} | null
}
interface ApiResponse<T> {
code: number
msg: string | null
data: T
ok: boolean
}
// 会议室ID
const roomId = ref('')
// 获取页面参数
const props = defineProps<{
roomId?: string
roomInfo?: any
}>()
// 初始化数据
onMounted(() => {
console.log('Detail page mounted, props:', props)
console.log('roomId from props:', props.roomId)
// 从props获取roomId
if (props.roomId) {
roomId.value = props.roomId
console.log('roomId set to:', roomId.value)
} else {
console.log('No roomId found in props')
}
// 获取当天的时间段
const today = getDateString(0)
console.log('Fetching time slots for date:', today)
fetchTimeSlots(today)
})
const goBack = () => {
uni.navigateBack()
}
// 假数据,可替换为实际接口
// 使用传入的会议室信息,如果没有则使用默认值
const room = ref({
name: '一号会议室',
capacity: 20,
address: '公司总部南水吧台旁',
tags: ['电视', '白板', '投影仪'],
img: '/static/meeting/room1.jpg',
...props.roomInfo
})
const dateTabs = ['今天', '明天', '后天', '选择日期']
// 当前用户信息
const currentUser = ref({
name: '张三',
id: '1'
})
// 日期选项
const dateTabs = ['今天', '明天', '后天', '大后天']
const activeDate = ref(0)
// 时间表格(4行10列,每格15分钟)
const timeTable = ref([
[
{ status: 'disabled' }, { status: 'disabled' }, { status: 'booked', user: '张三' }, { status: 'booked', user: '张三' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }
],
[
{ status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'booked', user: '张三' }, { status: 'booked', user: '张三' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }
],
[
{ status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }
],
[
{ status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }, { status: 'free' }
]
])
// 时间段数据
const timeSlots = ref<TimeSlotItem[]>([])
const selectedTime = ref<{row: number, col: number} | null>(null)
// 选中的时间
const selectedTime = ref<number[]>([])
const selectedTimeText = computed(() => {
if (!selectedTime.value) return '未选择'
const start = 8 + selectedTime.value.col * 0.5
const end = start + 0.5
return `${Math.floor(start)}:${start % 1 === 0 ? '00' : '30'}~${Math.floor(end)}:${end % 1 === 0 ? '00' : '30'}`
if (selectedTime.value.length === 0) return '未选择'
const start = timeSlots.value[selectedTime.value[0]]
const end = timeSlots.value[selectedTime.value[selectedTime.value.length - 1]]
return `${start.startTime} - ${end.endTime}`
})
const selectedDuration = computed(() => 30)
const selectedDuration = computed(() => selectedTime.value.length * 30)
function selectTime(row: number, col: number) {
const cell = timeTable.value[row][col]
if (cell.status === 'free') {
selectedTime.value = { row, col }
// 获取日期字符串
const getDateString = (index: number) => {
const date = new Date()
date.setDate(date.getDate() + index)
return date.toISOString().split('T')[0]
}
// 处理日期切换
const handleDateChange = async (index: number) => {
activeDate.value = index
const date = getDateString(index)
await fetchTimeSlots(date)
}
// 获取时间段数据
const fetchTimeSlots = async (date: string) => {
if (!roomId.value) {
console.log('No roomId available for API request')
uni.showToast({
title: '会议室ID不存在',
icon: 'none'
})
return
}
console.log('Making API request with roomId:', roomId.value)
try {
const res = await meetingRoomApi.getTimeSlots(roomId.value, date)
console.log('Raw API response:', res)
const response = res as unknown as ApiResponse<TimeSlotItem[]>
console.log('Parsed response:', response)
if (response.code === 0 && response.data) {
console.log('Time slots data:', response.data)
timeSlots.value = response.data
} else {
console.error('Invalid response:', response)
uni.showToast({
title: response.msg || '获取时间段失败',
icon: 'none'
})
}
} catch (error) {
console.error('API request failed:', error)
uni.showToast({
title: '获取时间段失败',
icon: 'none'
})
}
}
function reserveMeeting() {
if (!selectedTime.value) {
// 选择时间
function selectTime(index: number) {
const slot = timeSlots.value[index]
if (slot.status === 'free') {
// 检查是否已经选中
const currentIndex = selectedTime.value.indexOf(index)
if (currentIndex === -1) {
// 检查是否连续
if (selectedTime.value.length === 0) {
selectedTime.value.push(index)
} else {
const last = selectedTime.value[selectedTime.value.length - 1]
if (last === index - 1) {
selectedTime.value.push(index)
} else {
uni.showToast({
title: '请选择连续的时间段',
icon: 'none'
})
}
}
} else {
// 取消选中
selectedTime.value.splice(currentIndex, 1)
}
}
}
// 预约会议
async function reserveMeeting() {
if (selectedTime.value.length === 0) {
uni.showToast({ title: '请选择时间', icon: 'none' })
return
}
uni.showToast({ title: '预约成功', icon: 'success' })
if (!roomId.value) {
uni.showToast({ title: '会议室ID不存在', icon: 'none' })
return
}
try {
const date = getDateString(activeDate.value)
const startSlot = timeSlots.value[selectedTime.value[0]]
const endSlot = timeSlots.value[selectedTime.value[selectedTime.value.length - 1]]
const res = await meetingRoomApi.bookMeetingRoom(roomId.value, {
date,
startTime: startSlot.startTime,
endTime: endSlot.endTime,
title: '会议',
description: '',
attendees: []
})
const response = res as unknown as ApiResponse<any>
if (response.code === 0) {
uni.showToast({ title: '预约成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: response.msg || '预约失败',
icon: 'none'
})
}
} catch (error) {
uni.showToast({
title: '预约失败',
icon: 'none'
})
}
}
</script>
@@ -117,6 +275,8 @@ function reserveMeeting() {
.container {
min-height: 100vh;
background: #f5f6fa;
display: flex;
flex-direction: column;
padding-bottom: 32rpx;
}
.custom-nav {
@@ -220,58 +380,62 @@ function reserveMeeting() {
background: #e6f0ff;
font-weight: 600;
}
.time-table {
.time-list-container {
margin: 24rpx;
background: #fff;
border-radius: 12rpx;
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
}
.time-list {
flex: 1;
}
.time-list-content {
padding: 16rpx;
}
.time-row {
display: flex;
margin-bottom: 8rpx;
}
.time-cell {
flex: 1;
height: 48rpx;
margin-right: 4rpx;
border-radius: 6rpx;
background: #e6eaf2;
.time-item {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: center;
color: #bbb;
font-size: 22rpx;
padding: 24rpx;
border-bottom: 1px solid #f0f0f0;
background: #f8f9fc;
margin-bottom: 16rpx;
border-radius: 8rpx;
position: relative;
}
.time-cell:last-child {
margin-right: 0;
.time-item:last-child {
margin-bottom: 0;
}
.time-cell.free {
background: #4d8fff;
color: #fff;
.time-item.free {
background: #e6f0ff;
color: #1777FF;
}
.time-cell.booked {
background: #ffb74d;
color: #fff;
.time-item.booked {
background: #fff3e0;
color: #ff9800;
}
.time-cell.disabled {
background: #f0f0f0;
color: #ccc;
.time-item.disabled {
background: #f5f5f5;
color: #999;
}
.time-cell.selected {
.time-item.selected {
border: 2rpx solid #1777FF;
}
.time-labels {
display: flex;
margin: 0 24rpx 16rpx 24rpx;
.time-range {
font-size: 28rpx;
font-weight: 500;
}
.time-label {
flex: 1;
text-align: center;
color: #bbb;
font-size: 20rpx;
.time-status {
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 4rpx;
background: rgba(255, 255, 255, 0.5);
}
.reserve-info {
margin: 0 24rpx 16rpx 24rpx;
margin: 0 24rpx;
background: #fff;
border-radius: 12rpx;
padding: 16rpx;
@@ -279,7 +443,7 @@ function reserveMeeting() {
font-size: 26rpx;
}
.reserve-btn {
margin: 0 24rpx;
margin: 24rpx;
background: linear-gradient(90deg, #0094ff, #06be36);
color: #fff;
font-size: 30rpx;
@@ -287,6 +451,5 @@ function reserveMeeting() {
border-radius: 40rpx;
padding: 24rpx 0;
font-weight: 600;
margin-top: 16rpx;
}
</style>
+2
View File
@@ -74,6 +74,8 @@ function fetchMeetingRooms() {
}
function goDetail(room) {
console.log('Navigating to detail page with room:', room)
console.log('Room ID:', room.id)
uni.navigateTo({
url: `/pages/integrated/meeting/detail?roomId=${room.id}`
})
+27
View File
@@ -10,4 +10,31 @@ export default {
method: "GET",
params,
}),
/**
* 获取会议室时间段
*/
getTimeSlots: (roomId: string, date: string) =>
request({
url: `/integrated/meetingRoom/${roomId}/time-slots`,
method: "GET",
params: { date },
}),
/**
* 预约会议室
*/
bookMeetingRoom: (roomId: string, data: {
date: string;
startTime: string;
endTime: string;
title: string;
description: string;
attendees: string[];
}) =>
request({
url: `/integrated/meetingRoom/${roomId}/book`,
method: "POST",
data,
}),
}