帮我优化一下 在线选座代码的座位 改为接口模拟 到时候我写出接口 给我提供结构 包括座位的价格也是后台接口来的 包括时间段也是后台来的
<template>
<view class="container">
<!-- 场馆信息 -->
<view class="venue-info">
<text class="venue-name">{{ venueInfo.name }}</text>
<text class="venue-address">{{ venueInfo.address }}</text>
</view>
<!-- 日期选择 -->
<view class="date-section">
<view class="section-title">选择日期</view>
<scroll-view scroll-x class="date-scroll">
<view class="date-list">
<view
class="date-item"
v-for="(date, index) in dateList"
:key="index"
:class="{ active: selectedDate === date.value }"
@click="selectDate(date.value)"
>
<text class="date-week">{{ date.week }}</text>
<text class="date-day">{{ date.day }}</text>
<text class="date-label">{{ date.label }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 多选模式切换 -->
<view class="mode-toggle">
<text>跨场地多选:</text>
<switch :checked="allowMultiSelect" @change="toggleMultiSelect" />
</view>
<!-- 图例说明 -->
<view class="reserve_legend">
<view class="dp">
<view class="reserve_legend_view">
<view class="reserve_legend_view_i bg_one"></view>
<text>可预订</text>
</view>
<view class="reserve_legend_view">
<view class="reserve_legend_view_i bg_two"></view>
<text>已被订</text>
</view>
<view class="reserve_legend_view">
<view class="reserve_legend_view_i bg_three"></view>
<text>已选中</text>
</view>
</view>
</view>
<!-- 核心表格区域 -->
<view class="reserve_middle">
<scroll-view
class="middle_scroll_container"
scroll-x
scroll-y
:scroll-top="scrollTop"
:style="{ height: scrollContainerHeight + 'rpx' }"
>
<view class="table_content" :style="{ width: contentWidth + 'rpx', height: contentHeight + 'rpx' }">
<view class="header-sticky-row">
<view class="corner-cell">
<text>时间</text>
</view>
<view class="mid_site_text" v-for="(court, index) in courtList" :key="'header-' + index" >
{{ court.name }}
</view>
</view>
<view class="time-sticky-col">
<view
class="mid_time_cell"
v-for="(timeSlot, idx) in timeSlots"
:key="'time-' + idx"
>
{{ timeSlot.label }}
</view>
</view>
<view class="data-scroll-area">
<view
v-for="(timeSlot, timeIdx) in timeSlots"
:key="'row-' + timeIdx"
class="mid_row dp"
>
<view
class="mid_col"
v-for="(court, colIdx) in courtList"
:key="'cell-' + timeIdx + '-' + colIdx"
@click="handleChoose(timeIdx, colIdx)"
>
<view
class="mid_col_v"
:class="[
bookedData[timeIdx][colIdx] ? 'bought-grid' : '',
isSelected(timeIdx, colIdx) ? 'selected-grid' : '',
!bookedData[timeIdx][colIdx] && !isSelected(timeIdx, colIdx) ? 'unselected-grid' : ''
]"
>
<text class="price-text">¥{{ getPrice(timeSlot, court) }}</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="" style="height: 100px;">
</view>
<!-- 底部操作栏 -->
<view class="reserve_bottom">
<view class="btm_txt" @click="showSelectionDetails">
<text class="btm_price">当前选择:</text>
<text class="btm_nums">{{ selectionSummary }}</text>
</view>
<view class="btm_order" :class="hasSelection ? 'btm_blue' : 'btm_gray'" @click="openPage">
<text>提交订单</text>
</view>
</view>
<!-- 多选详情弹窗 -->
<uni-popup ref="popup" type="bottom">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">已选场地详情</text>
<uni-icons type="closeempty" size="24" color="#999" @click="closePopup"></uni-icons>
</view>
<scroll-view scroll-y class="selection-list">
<view v-for="(item, index) in selectedSeats" :key="index" class="selection-item">
<text class="item-time">{{ timeSlots[item.row].label }}</text>
<text class="item-court">{{ courtList[item.col].name }}</text>
<text class="item-price">¥{{ getPrice(timeSlots[item.row], courtList[item.col]) }}</text>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script>
export default {
data() {
return {
allowMultiSelect: false, // 控制是否允许跨场地多选
selectedSeats: [], // 存储所有选中的座位 [{row, col}]
weekArray: [],
weekIndex: 0,
courtList: [],
timeSlots: [],
priceMatrix: {},
bookedData: [],
selectedSeat: null,
selectedTimeSlot: null,
selectedCourt: null,
chooseInfo: '',
scrollLeft: 0,
scrollTop: 0,
cellWidth: 140,
rowHeight: 88,
colCount: 8,//几点开始
rowCount: 16, // 8点到24点共16个时间段
scrollContainerHeight: 0, // 新增:滚动容器高度
contentWidth: 0,
contentHeight: 0,
selectedDate: '',
dateList: [],
venueInfo: {
name: '飞扬羽毛球馆',
address: '朝阳区建国路88号'
},
}
},
mounted() {
this.calculateContainerHeight();
// 监听窗口大小变化
uni.onWindowResize(this.calculateContainerHeight);
},
beforeDestroy() {
// 移除监听
uni.offWindowResize(this.calculateContainerHeight);
},
onLoad() {
this.generateDateList()
if (this.dateList.length > 0) {
this.selectedDate = this.dateList[0].value
}
this.generateCourtList()
this.generateTimeSlots()
this.initPriceMatrix()
this.initBookedData()
this.contentWidth = 95 + this.colCount * this.cellWidth
this.contentHeight = 40 + this.rowCount * this.rowHeight
},
computed: {
// 当前是否有选中的座位
hasSelection() {
return this.selectedSeats.length > 0;
},
// 底部显示的摘要信息
selectionSummary() {
if (this.selectedSeats.length === 0) return '未选择';
if (!this.allowMultiSelect) {
const seat = this.selectedSeats[0];
return `${this.courtList[seat.col].name} ${this.timeSlots[seat.row].label}`;
}
return ` ${this.selectedSeats.length} 个场地 (点击查看)`;
}
},
methods: {
calculateContainerHeight() {
// 获取系统信息
uni.getSystemInfo({
success: (res) => {
// 计算可用高度(屏幕高度 - 顶部导航栏高度 - 底部操作栏高度 - 其他元素高度)
const windowHeight = res.windowHeight * res.pixelRatio;
const topHeight = 200; // 顶部区域大约高度(rpx)
const bottomHeight = 120; // 底部按钮高度(rpx)
const otherElementsHeight = 300; // 其他元素高度(rpx)
// 计算滚动区域可用高度
const availableHeight = windowHeight - topHeight - bottomHeight - otherElementsHeight;
// 设置滚动容器高度(确保不小于最小高度)
this.scrollContainerHeight = Math.max(availableHeight, 600);
}
});
},
// 切换多选模式
toggleMultiSelect(e) {
this.allowMultiSelect = e.detail.value;
// 切换模式时清空选择
this.selectedSeats = [];
},
// 检查指定位置是否被选中
isSelected(timeIdx, colIdx) {
return this.selectedSeats.some(seat =>
seat.row === timeIdx && seat.col === colIdx
);
},
// 处理用户选择
handleChoose(timeIdx, colIdx) {
if (this.bookedData[timeIdx][colIdx]) return;
const seatIndex = this.selectedSeats.findIndex(seat =>
seat.row === timeIdx && seat.col === colIdx
);
if (seatIndex !== -1) {
// 已选中则取消
this.selectedSeats.splice(seatIndex, 1);
} else {
// 多选模式直接添加
if (this.allowMultiSelect) {
this.selectedSeats.push({ row: timeIdx, col: colIdx });
} else {
// 单选模式替换当前选择
this.selectedSeats = [{ row: timeIdx, col: colIdx }];
}
}
},
// 显示多选详情弹窗
showSelectionDetails() {
if (this.allowMultiSelect && this.selectedSeats.length > 0) {
this.$refs.popup.open();
}
},
// 关闭弹窗
closePopup() {
this.$refs.popup.close();
},
// 生成日期列表
generateDateList() {
const dates = []
const today = new Date()
for (let i = 0; i < 7; i++) {
const date = new Date(today)
date.setDate(today.getDate() + i)
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
const week = weekDays[date.getDay()]
const day = date.getDate()
const month = date.getMonth() + 1
let label = ''
if (i === 0) {
label = '今天'
} else if (i === 1) {
label = '明天'
} else {
label = `${month}月${day}日`
}
dates.push({
value: date.toISOString().split('T')[0],
week,
day,
label
})
}
this.dateList = dates
},
// 选择日期
selectDate(date) {
this.selectedDate = date
// 重新获取时间段和场地数据
console.log('重新获取时间段和场地数据');
},
generateCourtList() {
this.courtList = Array.from({ length: this.colCount }, (_, i) => ({
id: i + 1,
name: `${i + 1}号场`
}))
},
// generateTimeSlots() {
// const slots = []
// for (let h = 8; h < 8 + this.rowCount; h++) {
// slots.push({
// value: `${h}:00`,
// label: `${h}:00`
// })
// }
// this.timeSlots = slots
// },
generateTimeSlots() {
const slots = [];
// 生成8:00-24:00的时间段
for (let h = 8; h < 24; h++) {
const nextHour = h + 1;
const startTime = `${String(h).padStart(2, '0')}:00`;
const endTime = nextHour === 24 ? '24:00' : `${String(nextHour).padStart(2, '0')}:00`;
slots.push({
value: startTime,
label: `${startTime}-${endTime}` // 显示为"8:00-9:00"格式
});
}
this.timeSlots = slots;
},
initPriceMatrix() {
this.priceMatrix = {}
this.timeSlots.forEach(ts => {
this.priceMatrix[ts.value] = {}
this.courtList.forEach(court => {
this.priceMatrix[ts.value][court.id] = ts.value >= '18:00' ? 120 + (court.id - 1) * 10 : 80 + (court.id - 1) * 5
})
})
},
getPrice(timeSlot, court) {
return this.priceMatrix[timeSlot.value]?.[court.id] || 80
},
initBookedData() {
this.bookedData = this.timeSlots.map(() => Array(this.colCount).fill(false))
const booked = ['0-0', '0-1', '1-2', '2-3', '5-4']
booked.forEach(str => {
const [col, row] = str.split('-').map(Number)
if (row < this.bookedData.length && col < this.colCount) {
this.$set(this.bookedData[row], col, true)
}
})
},
onScroll(e) {
this.scrollLeft = e.detail.scrollLeft
this.scrollTop = e.detail.scrollTop
},
openPage() {
if(!global.token){
this.$message.info('请先登录')
setTimeout(function(){
uni.switchTab({
url:'/pages/profile/profile'
})
},800)
return false
}
if (!this.hasSelection) return
if (this.allowMultiSelect) {
const details = this.selectedSeats.map(seat => ({
time: this.timeSlots[seat.row].label,
court: this.courtList[seat.col].name,
price: this.getPrice(this.timeSlots[seat.row], this.courtList[seat.col])
}));
uni.showToast({ title: `提交${details.length}个场地`, icon: 'success' });
console.log('预定详情:', details);
} else {
const seat = this.selectedSeats[0];
uni.showToast({ title: '提交成功', icon: 'success' });
console.log('预定:', {
time: this.timeSlots[seat.row].label,
court: this.courtList[seat.col].name,
price: this.getPrice(this.timeSlots[seat.row], this.courtList[seat.col])
})
}
}
}
}
</script>
<style>
page {
width: 100%;
height: 100%;
}
/* 新增样式:确保时间单元格内容可换行 */
.mid_time_cell {
white-space: normal !important;
line-height: 1.4 !important;
padding: 10rpx 5rpx !important;
font-size: 24rpx !important; /* 适当减小字体大小 */
}
/* 调整时间列宽度 */
.time-sticky-col {
width: 120rpx !important; /* 增加宽度以容纳更长的文本 */
}
/* 调整数据区域位置 */
.data-scroll-area {
left: 120rpx !important; /* 与时间列宽度一致 */
}
/* 调整单元格高度 */
.mid_time_cell,
.mid_col {
height: 80rpx !important; /* 略微减小单元格高度 */
}
/* 确保表格内容区域高度自适应 */
.table_content {
min-height: 100% !important;
}
/* 添加响应式字体大小 */
@media (max-width: 600px) {
.mid_time_cell {
font-size: 22rpx !important;
}
}
.venue-info {
background-color: #ffffff;
padding: 30rpx;
margin-bottom: 20rpx;
}
.venue-name {
font-size: 36rpx;
font-weight: bold;
color: #000000;
display: block;
margin-bottom: 10rpx;
}
.venue-address {
font-size: 28rpx;
color: #666666;
}
/* 添加多选切换样式 */
.mode-toggle {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: #fff;
margin-bottom: 20rpx;
font-size: 28rpx;
}
/* 弹窗样式 */
.popup-content {
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
padding: 30rpx;
}
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.popup-title {
font-size: 32rpx;
font-weight: bold;
}
.selection-list {
max-height: 60vh;
}
.selection-item {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1rpx solid #eee;
}
.item-time, .item-court {
font-size: 28rpx;
}
.item-price {
color: #f96010;
font-weight: bold;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #000000;
margin-bottom: 20rpx;
padding: 0 30rpx;
}
.date-section {
background-color: #ffffff;
padding: 30rpx 0;
margin-bottom: 20rpx;
}
.date-scroll {
white-space: nowrap;
}
.date-list {
display: flex;
padding: 0 30rpx;
gap: 20rpx;
}
.date-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx 30rpx;
background-color: #f5f5f5;
border-radius: 16rpx;
min-width: 120rpx;
}
.active {
background-color: #f96010;
color: #ffffff;
}
.date-week {
font-size: 24rpx;
margin-bottom: 8rpx;
}
.date-day {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 8rpx;
}
.date-label {
font-size: 24rpx;
}
.venue_reserve {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background-color: #f5f5f5;
}
/* ========== 顶部日期 ========== */
.reserve_top {}
.top_view {
display: flex;
justify-content: space-around;
padding: 20rpx 0;
background: white;
border-bottom: 1rpx solid #eee;
}
.top_date {
width: 13%;
height: 70rpx;
color: #666;
background-color: #F5F5F8;
border: 1rpx solid #ccc;
text-align: center;
font-size: 24rpx;
padding-top: 10rpx;
line-height: 1.4;
}
.top_date_active {
background-color: #FFFFFF;
border: 1rpx solid #f96010;
color: #f96010;
}
/* ========== 图例 ========== */
.reserve_legend {
display: flex;
justify-content: center;
margin: 20rpx 0;
font-size: 24rpx;
}
.dp {
display: flex;
}
.reserve_legend_view {
display: flex;
align-items: center;
margin: 0 30rpx;
}
.reserve_legend_view_i {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
}
.bg_one {
background-color: #6acb6c;
}
.bg_two {
background-color: #CCCCCC;
}
.bg_three {
background-color: #f96010;
}
/* ========== 表格区域 ========== */
.reserve_middle {
flex: 1;
margin: 0 30rpx;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.05);
position: relative;
}
.middle_scroll_container {
width: 100%;
height: 100%;
white-space: nowrap;
}
.table_content {
position: relative;
background: white;
}
/* 左上角小方块 */
.corner-cell {
position: sticky;
left: 0;
top: 0;
width: 122rpx;
height: 40rpx;
background: #eaeaea;
z-index: 10;
display: flex;
justify-content: center;
align-items: center;
font-size: 24rpx;
font-weight: bold;
border-right: 1rpx solid #ddd;
border-bottom: 1rpx solid #eee;
}
/* 顶部:场地标题行(横向滚动时固定在上方) */
.header-sticky-row {
position: sticky;
top: 0;
z-index: 9;
display: flex;
height: 40rpx;
background: #f8f8f8;
border-bottom: 1rpx solid #eee;
}
.mid_site_text {
width: 140rpx;
text-align: center;
font-size: 24rpx;
color: #666;
display: flex;
justify-content: center;
align-items: center;
}
/* 左侧:时间列(纵向滚动时固定在左边) */
.time-sticky-col {
position: sticky;
left: 0;
z-index: 8;
width: 95rpx;
background: #f8f8f8;
border-right: 1rpx solid #ddd;
}
.mid_time_cell {
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 26rpx;
color: #333;
border-bottom: 1rpx solid #eee;
font-weight: bold;
}
/* 数据区域:真正滚动的内容 */
.data-scroll-area {
position: absolute;
left: 95rpx;
top: 40rpx;
min-width: max-content;
}
.mid_row {
display: flex;
border-bottom: 1rpx solid #eee;
}
.mid_col {
width: 140rpx;
height: 88rpx;
display: flex;
justify-content: center;
align-items: center;
padding: 10rpx;
box-sizing: border-box;
}
.mid_col_v {
width: 100%;
height: 100%;
background-size: 100% 100%;
background-repeat: no-repeat;
background-position: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
font-size: 24rpx;
font-weight: bold;
text-shadow: 1rpx 1rpx 2rpx rgba(0,0,0,0.5);
}
.unselected-grid {
background: #6acb6c;
}
.bought-grid {
background: #CCCCCC;
}
.selected-grid {
background-color: #f96010;
}
.price-text {
color: white;
font-size: 22rpx;
font-weight: normal;
text-shadow: 1rpx 1rpx 2rpx rgba(0,0,0,0.8);
}
/* ========== 底部按钮 ========== */
.reserve_bottom {
position: fixed;
bottom: 0;
left: 0;
z-index: 999;
display: flex;
height: 100rpx;
width: 100%;
line-height: 100rpx;
background: white;
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.05);
/* 增加安全区域处理 */
padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */
box-sizing: content-box; /* 确保padding不包含在高度内 */
}
.btm_txt {
width: 70%;
padding-left: 30rpx;
color: #A5A5A5;
font-size: 28rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.btm_nums {
color: #FF0000;
font-size: 32rpx;
font-weight: bold;
}
.btm_order {
width: 30%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 30rpx;
}
.btm_gray {
background-color: #CCCCCC;
}
.btm_blue {
background-color: #f96010;
}
</style>