Files
wangxiaowei d77dbb20f3 完善功能
2025-12-11 01:50:58 +08:00

1061 lines
32 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.

<template>
<view style="padding-bottom: 40rpx;">
<Popup :show="timePopup" :width="750" :padding="0" type="bottom" radius="32rpx 32rpx 0 0">
<reserve-time
@confirm="confirmSelectTime"
@close="timePopup = false"
/>
</Popup>
<!-- 费用明细弹窗 -->
<Popup :show="billPopup" :width='750' :padding="0" type="bottom" backgroundColor="#FBFBFB" radius="32rpx 32rpx 0 0">
<view class="ww100 box-s-b pop-improt typeof pr time-popup">
<image style="width: 64rpx;height: 64rpx;position: absolute; top: 24rpx;right: 30rpx;" src="@/static/icon/close2.png" mode="" @click="closeBillPopup"></image>
<view class="d-c-c pt42" style="padding-top: 38rpx;">
<text class="f34 fb">费用明细</text>
</view>
<view class="bg-white bill-info">
<!-- 网球场 -->
<view v-if="venue.type_id == 1">
<view class="title1 d-b-c">
<view>场地费</view>
<view>
<price-format color="#303133" :subscript-size="30" :first-size="30" :second-size="30" :price="bill.cdf.total"></price-format>
</view>
</view>
<view class="title2 d-b-c">
<view>场地费(¥{{ bill.cdf.price }}元/小时)</view>
<view>x{{ bill.cdf.nums }}</view>
</view>
</view>
<!-- 篮球场 -->
<view v-if="venue.type_id == 2">
<view class="title1 d-b-c">
<view>使用费</view>
<view>
<price-format color="#303133" :subscript-size="30" :first-size="30" :second-size="30" :price="bill.cdf.total"></price-format>
</view>
</view>
<view class="title2 d-b-c">
<view>¥{{ bill.cdf.price }}元/人</view>
<view>x1</view>
</view>
</view>
<view style="margin-top: 52rpx;" v-if="venue.type_id == 1">
<view class="title1 d-b-c">
<view>灯光费</view>
<view>
<price-format color="#303133" :subscript-size="30" :first-size="30" :second-size="30" :price="bill.dgf.total"></price-format>
</view>
</view>
<view class="title2 d-b-c">
<view>灯光费(¥{{ bill.dgf.price }}元/小时)</view>
<view>x{{ bill.dgf.nums }}</view>
</view>
</view>
<view class="line"></view>
<view class="title3 d-b-c">
<view>实付金额</view>
<view>
<price-format color="#303133" :subscript-size="30" :first-size="30" :second-size="30" :price="bill.total"></price-format>
</view>
</view>
</view>
<view class="price2 bg-white" style="margin: 0; padding: 56rpx 22rpx 42rpx;">
<view class="d-b-c price-block" style="margin: 0;">
<view class="d-f d-c j-c-b">
<view class="">
合计: <price-format color="#FF5951" :subscript-size="26" :first-size="40" :second-size="40" :price="bill.total"></price-format>
</view>
<view class="d-f j-c-c a-i-c" style="margin-top: 10rpx;" @click="closeBillPopup">
<view class="price-detail">费用明细</view>
<image style="width: 14rpx;height: 8rpx;margin-left: 10rpx;" src="@/static/icon/up.png" mode=""></image>
</view>
</view>
<view class="price-btn" @tap="toReserve">立即预约</view>
</view>
</view>
</view>
</Popup>
<navbar title="预约网球场馆"></navbar>
<!-- <view class="banner">
<banner :itemData="bannerData"></banner>
</view> -->
<!-- 信息介绍 -->
<view class="info-block">
<view class="info-title mx-30rpx">{{ venue.name }}</view>
<view class="d-f mx-30rpx">
<uni-rate v-model="venue.star" active-color="#FF5951" :size="18" />
<text class="rate">{{ venue.star }}推荐</text>
</view>
<view class="time mx-30rpx">营业时间:{{ venue.day_time }} {{ venue.start_time }}-{{ venue.end_time }}</view>
<view class="line"></view>
<view class="d-b-c mx-30rpx">
<view class="d-f a-i-c">
<image style="width: 36rpx;height: 36rpx;margin-top: 4rpx;" src="@/static/icon/location2.png"
mode=""></image>
<view class="">
<view class="address-time one-line-ellipsis" style="width:400rpx;"> {{ venue.address }}</view>
<!-- <view class="address-time">距您{{ venue.distance }}km</view> -->
</view>
</view>
<view class="d-f">
<view class="d-f d-c a-i-c" style="margin-right: 20rpx;" @tap="handleLocation">
<image style="width: 64rpx;height: 64rpx;margin-top: 2rpx" src="@/static/icon/share.png"
mode=""></image>
<view class="mark-text">导航</view>
</view>
<view class="d-f d-c a-i-c" @tap="handleCall">
<image style="width: 64rpx;height: 64rpx;margin-top: 2rpx" src="@/static/icon/mobile.png"
mode=""></image>
<view class="mark-text">电话</view>
</view>
</view>
</view>
</view>
<!-- 分隔线 -->
<view class="split-line"></view>
<!-- 网球场——场馆信息 -->
<view class="info-block mx-30rpx" v-if="typeId == 1" style="padding-bottom: 180rpx;">
<view class="tag-block">
<view class="tag-title">立即预定</view>
<view class="tag">
<view class="tag1">可预约</view>
<view class="tag2">已选中</view>
<view class="tag3">不可约</view>
</view>
</view>
<view class="date-block">
<view class="">
<view class="date1-block">
<view class="date1-block-item bg-f7f7f7" :class="currentWeek == index ? 'bg-365A9A text-fff' : ''" v-for="(item, index) in weekList" :key="index" @click="selectWeek(item, index, item)">
<view>{{ item.value[0] }}</view>
<view>{{ item.value[1] }}</view>
</view>
</view>
</view>
<view class="">
<!-- 场馆时间 -->
<view class="d-f">
<view class="cg-time-block">
<view class="cg-time" v-for="(item, index1) in timeList" :key="index1">
{{ item.t }}
</view>
</view>
<view class="d-f cg-info" style="width: 800rpx; overflow-x: auto;">
<view style="width: 120rpx;margin-right: 10rpx;" v-for="(item2, index2) in cdList" :key="index2">
<view class="" style="margin-bottom: 6rpx; text-align: center;">{{ item2.title }}</view>
<view
v-for="(item3, index3) in item2.time" :key="index3"
@click="handleSelectTime(item2.title, item3.t, item3.status, item2.room_id)"
class="cg-info-time" :class="[
item3.status == 1
? 'cg-info-time-none'
: selectedTime[item2.title] && selectedTime[item2.title].includes(item3.t)
? 'cg-info-time-select'
: 'cg-info-time-normal'
]" >
<template v-if="item3.price > 0">{{ item3.price }}</template>
<template v-else>免费</template>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 篮球场场馆信息 -->
<view class="info-block" v-if="typeId == 2">
<view class="tab-bar-scroll mx-30rpx">
<view class="tab-bar">
<view v-for="(item, idx) in venueRoomLists" :key="item.display" class="tab-bar-item"
:class="{ active: idx === selectedRoomIndex}" @click="handleTabClick(idx)">
{{ item.title }}
</view>
</view>
</view>
<view class="line"></view>
<view class="mx-30rpx">
<view class="reserve-time">预定时间</view>
<view class="reserve-desc" @click="timePopup = true">
<view class="reserve-desc-title">默认4小时注意闭馆时间</view>
<view class="reserve-desc-info">
<view class="">{{ date || '请选择' }}</view>
<view class="">
<image class="" src="@/static/icon/right.png" style="width: 32rpx; height: 32rpx;"></image>
</view>
</view>
</view>
</view>
</view>
<view class="price">
<view class="line"></view>
<view class="d-b-c price-block">
<view class="d-f d-c j-c-b">
<view class="">
合计 <price-format color="#FF5951" :subscript-size="26" :first-size="40" :second-size="40" :price="bill.total"></price-format>
</view>
<view class="d-f j-c-c a-i-c" style="margin-top: 10rpx;" @tap="handleShowBill">
<view class="price-detail">费用明细</view>
<image style="width: 14rpx;height: 8rpx;margin-left: 10rpx;" src="@/static/icon/down.png" mode=""></image>
</view>
</view>
<view class="price-btn" @tap="toReserve">立即预约</view>
</view>
</view>
</view>
</template>
<script>
import navbar from '@/components/navbar.vue';
import banner from '@/components/diy/banner/banner.vue';
import priceFormat from '@/components/price-format/price-format.vue';
import ReserveTime from '@/components/reserve-time'
import Popup from '@/components/uni-popup.vue';
export default {
components: {
navbar,
banner,
priceFormat,
Popup,
ReserveTime
},
data() {
return {
id: 0,
typeId: 1,
bannerData: {
data: [],
style: { btnColor: "#ffffff", background: "#ffffff", btnShape: "round", imgShape: "round", height: "330" }
},
selectedRoomIndex: 0, // 篮球场--场馆信息-选中tab索引
value: 4,
currentTab: 1,
tab: [
{ type: 1, name: '今天' },
{ type: 2, name: '明天' },
{ type: 3, name: '后天' },
],
venue: {},
venueRoomLists: [],
loadding: true,
timePopup: false,
date: '',
showPrice: true,
bill: {
cdf: {
nums: 0, // 场地费小时数
price: 0, // 场地费单价
total: 0 // 场地费总价
},
dgf: {
nums: 0, // 灯光数量
price: 0, // 灯光单价
total: 0 // 灯光总价
},
total: 0 // 总价
},
billPopup: false,
weekList: [],
cdList: [],
timeList: [],
currentWeek: 0,
selectedTime: [],
selectedWeekDay: '',
selectedReserveTime: [],
countSelectedTime: 0,
};
},
onLoad(args) {
this.id = args.id || 0
this.typeId = args.typeId || 1 // 网球场馆1 篮球场馆2
this.getData()
this.handleGetVenueRoom()
},
methods: {
getData() {
let self = this;
uni.showLoading({
title: '加载中'
});
// 获取场馆详情
self._post(
'ground.ground/groundDetails',
{
app_id: self.getAppId(),
id: self.id,
latitude: uni.getStorageSync('latitude') || 0,
longitude: uni.getStorageSync('longitude') || 0,
},
function (res) {
if (res.code) {
self.loadding = false;
self.venue = res.data.lists
res.data.lists.image_arr.map(items => {
self.bannerData.data.push({ imgUrl: items, height: '400px' })
})
}
}
)
// 获取预定时间
self.getReserveTime()
},
// 获取场馆包间列表
handleGetVenueRoom() {
let self = this;
uni.showLoading({
title: '加载中'
});
self._post(
'ground.ground/groundRoomLists',
{
app_id: self.getAppId(),
ground_id: self.id,
},
function (res) {
if (res.code) {
self.venueRoomLists = res.data.lists
self.loadding = false;
}
}
)
},
// 篮球场场馆-切换tab
handleTabClick(idx) {
this.selectedRoomIndex = idx
},
// 切换tab
handleChangeTab(e) {
this.currentTab = e.type;
this.handleGetVenueRoom()
},
// 跳转预定页面
heandleToReserve(id) {
uni.navigateTo({
url: `/bundle/reserve/reserve?venueId=${this.venue.id}&roomId=${id}`
});
},
// 处理导航
handleLocation() {
uni.openLocation({
latitude: Number(this.venue.latitude),
longitude: Number(this.venue.longitude),
name: this.venue.name,
address: this.venue.address,
scale: 18
});
},
// 处理拨打电话
handleCall() {
uni.makePhoneCall({
phoneNumber: this.venue.contact_phone
});
},
// 确认选择时间
confirmSelectTime(e) {
this.date = e.value
// 计算费用明细
uni.showLoading({
title: '加载中'
});
},
// 计算费用明细
totalPrice() {
let self = this
self._post(
'ground.ground/countPrice',
{
app_id: self.getAppId(),
room_id: self.id,
nums: this.countSelectedTime,
type_id: self.venue.type_id,
},
function(res) {
const result = res.data.lists
self.bill = {
total: result.order_amount,
cdf: {
nums: result.nums,
price: result.room_price,
total: result.room_all_price
},
dgf: {
nums: result.nums,
price: result.light_price,
total: result.light_all_price
}
}
self.loadding = false;
}
)
},
// 显示费用明细
handleShowBill() {
this.showPrice = false;
this.billPopup = true;
},
closeBillPopup() {
this.billPopup = false;
this.showPrice = true;
},
// 篮球场-立即预约
toReserve() {
let self = this
// 一进来就锁定,彻底防止高频点击
if (self.typeId == 1) {
if (self.selectedReserveTime.length === 0) {
uni.showToast({
title: '请选择预约时间',
icon: 'none'
});
return;
}
uni.showLoading({
title: '提交中',
mask: true,
});
try {
// 订单提交
self._post(
'order.groundOrder/submitStoreOrder',
{
app_id: self.getAppId(),
ground_id: self.id,
room_list: JSON.stringify(self.selectedReserveTime),
type: this.typeId,
start_end: self.selectedReserveTime[0].start_time + '-' + self.selectedReserveTime[self.selectedReserveTime.length -1].end_time
},
function(res) {
self.loadding = false;
if(res.code) {
uni.$on('payment', params => {
console.log("🚀 ~ params:", params)
uni.showLoading({
title: '加载中',
mask: true,
});
setTimeout(() => {
self.loadding = false
uni.$off("payment")
if (params.result) {
uni.redirectTo({
url: `/bundle/reserve/notice?order_id=${params.order_id}`
})
} else {
uni.redirectTo({
url: '/pages/order/cg-my-order'
})
}
}, 500)
})
uni.navigateTo({
url: `/bundle/reserve/confirm?venueId=${self.venue.id}&roomId=${self.id}&typeId=${self.typeId}&orderId=${res.data.lists.id}`
});
}
}
)
} catch (error) {
console.error('订单提交失败:', error);
uni.showToast({
title: '订单提交失败,请重试',
icon: 'none'
});
self.loadding = false;
}
} else {
if (!self.date) {
uni.showToast({
title: '请选择预约时间',
icon: 'none'
});
return;
}
uni.navigateTo({
url: `/bundle/reserve/confirm?venueId=${self.venue.id}&roomId=${self.id}&typeId=${self.typeId}`
});
}
},
handleSelectTime(title, time, status) {
if (status == 1) {
return;
}
// 多选逻辑selectedTime为对象按title区分
if (!this.selectedTime[title]) {
this.$set(this.selectedTime, title, []);
}
const idx = this.selectedTime[title].indexOf(time);
if (idx > -1) {
// 已选中则取消
this.selectedTime[title].splice(idx, 1);
} else {
// 未选中则添加
this.selectedTime[title].push(time);
}
this.countSelectedTime = Object.values(this.selectedTime).reduce((acc, times) => acc + times.length, 0);
this.totalPrice()
// 遍历所有已选时间,生成 room_list
const room_list = [];
Object.keys(this.selectedTime).forEach(roomTitle => {
const roomId = this.cdList.find(cd => cd.title === roomTitle)?.room_id;
this.selectedTime[roomTitle].forEach(t => {
// t 可能是 '09:00-10:00' 或 '09:00', 需拆分
let start_time = t, end_time = '';
if (t.includes('-')) {
[start_time, end_time] = t.split('-');
} else {
start_time = t;
// 假设每个时间段为1小时自动+1小时
const [h, m] = t.split(':');
const nextH = (parseInt(h, 10) + 1).toString().padStart(2, '0');
end_time = `${nextH}:${m}`;
}
room_list.push({
room_id: roomId,
day_title: this.selectedWeekDay,
day_time: this.selectedWeekTimes,
start_time,
end_time,
});
});
});
this.selectedReserveTime = room_list;
console.log('🚀 ~ room_list:', this.selectedReserveTime);
},
selectWeek(item, index) {
let self = this
self.currentWeek = index
self.selectedWeekDay = item.value[1] + '' + item.value[0]
self.selectedWeekTimes = item.times
self.selectedTime = []
self.getReserveTime()
},
getReserveTime() {
let self = this;
let params = {}
if (self.selectedWeekTimes) {
params = {
app_id: self.getAppId(),
ground_id: self.id,
today: self.selectedWeekTimes
}
} else {
params = {
app_id: self.getAppId(),
ground_id: self.id,
}
}
self._post(
'ground.ground/getSchedule',params,
function (res) {
if (res.code) {
self.cdList = res.data.lists1
self.weekList = res.data.lists2
self.timeList = res.data.lists3
// 初始化选择的时间
self.selectedWeekDay = res.data.lists2[0].value[1] + '' + res.data.lists2[0].value[0]
self.selectedWeekTimes = res.data.lists2[0].times
self.loadding = false
}
}
)
}
}
};
</script>
<style lang="scss">
page {
background-color: #fff;
}
.banner {
.diy-banner-box {
margin-bottom: 0 !important;
}
}
.info-block {
margin-top: 30rpx;
.info-title {
font-weight: bold;
font-size: 34rpx;
color: #303133;
line-height: 48rpx;
margin-bottom: 10rpx;
}
.rate {
margin-left: 8rpx;
font-weight: 400;
font-size: 26rpx;
color: #606266;
line-height: 36rpx;
}
.time {
font-size: 26rpx;
color: #606266;
line-height: 48rpx;
}
.line {
height: 2rpx;
background-color: #F6F7F9;
margin: 24rpx 0;
}
.address-time {
font-weight: 400;
font-size: 26rpx;
color: #606266;
line-height: 36rpx;
}
.tab {
width: 108rpx;
height: 52rpx;
text-align: center;
line-height: 52rpx;
background-color: #F7F7F7;
font-size: 26rpx;
color: #606266;
border-radius: 8rpx;
margin-top: 16rpx;
margin-right: 10rpx;
margin-bottom: 32rpx;
}
.cg-name {
font-size: 28rpx;
color: #303133;
line-height: 40rpx;
margin-bottom: 74rpx;
}
.reserve-btn {
width: 104rpx;
height: 52rpx;
border-radius: 10rpx;
border: 2rpx solid #365A9A;
font-weight: 400;
font-size: 26rpx;
color: #365A9A;
line-height: 52rpx;
text-align: center;
}
.time-block {
text-align: center;
margin-right: 8rpx;
.time-block-text {
font-weight: 400;
font-size: 20rpx;
color: #365A9A;
line-height: 28rpx;
text-align: center;
}
.time-block-box {
width: 30rpx;
height: 12rpx;
background: #365A9A;
border-radius: 6rpx;
}
}
.tag-block {
display: flex;
justify-content: space-between;
}
.tag-title {
font-weight: 500;
font-size: 32rpx;
color: #303133;
line-height: 44rpx;
padding-top: 10rpx;
}
.tag {
display: flex;
}
.tag1, .tag2, .tag3 {
font-size: 24rpx;
line-height: 34rpx;
color: #909399;
display: flex;
align-items: center;
margin-right: 20rpx;
}
.tag1::before {
content: " ";
display: block;
width: 20rpx;
height: 20rpx;
border-radius: 100%;
background: #F7F7F7;
border: 2rpx solid #E9ECF4;
margin-right: 10rpx;
}
.tag2::before {
content: " ";
display: block;
width: 20rpx;
height: 20rpx;
border-radius: 100%;
background: #365A9A;
border: 2rpx solid #365A9A;
margin-right: 10rpx;
}
.tag3::before {
content: " ";
display: block;
width: 20rpx;
height: 20rpx;
border-radius: 100%;
background: #D4DAE6;
border: 2rpx solid #D4DAE6;
margin-right: 10rpx;
}
.text-303133 {
color: #303133;
}
.text-fff {
color: #fff
}
.bg-f7f7f7 {
background-color: #F7F7F7;
}
.bg-365A9A {
background-color: #365A9A;
}
.bg-D4DAE6 {
background-color: #D4DAE6;
}
.date1-block {
display: flex;
align-items: center;
margin-top: 20rpx;
.date1-block-item {
width: 128rpx;
height: 100rpx;
border-radius: 16rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-right: 20rpx;
}
}
}
.tab-bar-scroll {
overflow-x: auto;
background: #fff;
}
.tab-bar {
display: flex;
flex-direction: row;
background: #fff;
}
.tab-bar-item:first-child{
padding: 0 !important;
}
.tab-bar-item {
flex-shrink: 0;
padding: 0 32rpx;
height: 62rpx;
line-height: 62rpx;
font-size: 32rpx;
color: #303133;
margin: 10rpx 16rpx 10rpx 0;
border-bottom: 4rpx solid transparent;
cursor: pointer;
}
.tab-bar-item.active {
color: #365A9A;
font-weight: bold;
position: relative;
}
.tab-bar-item.active::after {
content: '';
display: block;
width: 60rpx;
height: 10rpx;
background: #365A9A;
border-radius: 6rpx;
position: absolute;
left: 50%;
bottom: -10rpx;
transform: translateX(-50%);
}
.mx-30rpx {
margin-left: 30rpx;
margin-right: 30rpx;
}
.reserve-time {
font-weight: 500;
font-size: 32rpx;
color: #303133;
line-height: 44rpx;
}
.reserve-desc {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 22rpx;
color: #606266;
}
.reserve-desc-info {
display: flex;
align-items: center;
}
.box {
border-radius: 16rpx;
padding: 32rpx 30rpx;
margin: 0 30rpx;
}
.title {
font-size: 32rpx;
color: #303133;
line-height: 44rpx;
margin-bottom: 28rpx;
}
.desc {
font-weight: 500;
font-size: 26rpx;
color: #303133;
line-height: 48rpx;
margin-bottom: 16rpx;
}
.desc:last-child {
margin-bottom: 0;
}
.current {
position: absolute;
left: 0;
top: 38rpx;
width: 8rpx;
height: 32rpx;
background-color: #365A9A;
border-radius: 4rpx;
}
.dot {
width: 8rpx;
height: 8rpx;
background: #6A6363;
margin-right: 12rpx;
}
.mt20 {
margin-top: 20rpx;
}
.desc2 {
display: flex;
align-items: center;
margin-bottom: 16rpx;
}
.desc2:last-child {
margin-bottom: 0;
}
.price {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
.line {
height: 2rpx;
background: #E5E5E5;
width: 100%;
}
.price-block {
margin: 42rpx 22rpx;
}
.price-detail {
color: #365A9A;
font-size: 24rpx;
line-height: 34rpx;
}
}
.price-btn {
width: 368rpx;
height: 90rpx;
line-height: 90rpx;
text-align: center;
background: #365A9A;
border-radius: 8rpx;
font-size: 32rpx;
color: #FFF;
}
.bill-info {
border-radius: 16rpx;
margin: 32rpx 30rpx 22rpx;
padding: 48rpx 30rpx 30rpx;
.title1 {
font-size: 30rpx;
color: #303133;
line-height: 42rpx;
}
.title2 {
margin-top: 12rpx;
font-weight: 400;
font-size: 24rpx;
color: #909399;
line-height: 34rpx;
}
.line {
border: 2rpx solid #F6F7F9;
margin: 34rpx 0 30rpx;
}
.title3 {
font-size: 30rpx;
color: #303133;
line-height: 42rpx;
}
}
.split-line {
height: 20rpx;
background-color: #F6F7F9;
margin-top: 30rpx;
margin-bottom: 28rpx;
}
.cg-time-block {
margin-top: 76rpx;
.cg-time {
font-size: 26rpx;
color: #303133;
height: 60rpx;
line-height: 60rpx;
border: 2rpx solid transparent;
margin: 4rpx;
}
}
.cg-info {
margin-left: 54rpx;
margin-top: 38rpx;
.cg-info-time {
margin: 4rpx;
width: 120rpx;
height: 60rpx;
text-align: center;
line-height: 60rpx;
border-radius: 8rpx;
font-size: 26rpx;
}
.cg-info-time-none {
background: #D4DAE6;
border: 2rpx solid #D4DAE6;
}
.cg-info-time-normal {
background: #F7F7F7;
border: 2rpx solid #E9ECF4;
color: #303133;
}
.cg-info-time-select {
background: #365A9A;
border: 2rpx solid #365A9A;
color: #fff;
}
}
</style>