Files
xdd-uniapp-new/pkg_product/views/groupBuyDetails.vue
T

742 lines
22 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 class="group-detail">
<!-- 头部 -->
<hx-navbar
:fixed="true"
:background-color="[0,[0,0,0,0],[0,0,0,0]]"
statusBarFontColor="#ffffff"
barPlaceholder="hidden"
transparent="auto"
color='#ffffff'
/>
<scroll-view scroll-y class="page-scroll">
<!-- 顶部大图/轮播 -->
<swiper class="hero-swiper" indicator-dots autoplay circular interval="3000">
<swiper-item v-for="(img, idx) in detail.carouselImages" :key="idx">
<image :src="img" class="hero-img" mode="aspectFill" />
</swiper-item>
</swiper>
<!-- 价格与标题 -->
<view class="price-title">
<view class="price v12-primary-text">
<text class="v12-font-34">¥</text>
<text class="v12-font-bold v12-font-40">{{ detail.price }}</text>
</view>
<view class="title v12-font-bold v12-font-36 more-t">{{ detail.name }}</view>
</view>
<!-- 礼物 -->
<view class="v12-px-3" v-if="detail.isGift">
<view
@click="toGift"
class=" v12-white-text v12-px-2 v12-py-1 v12-mt-2 v12-radius-20 v12-font-bold v12-justify-between v12-align-center gift-wrap"
:style="giftBg ? 'background: url(' + giftBg + ') no-repeat center/cover' : ''"
:class="giftBg ? '' : 'v12-primary'"
>
<!-- <text v-if="storeInfo.discount > 0" class="v12-font-28 v12-yellow-text ll-text">平台限时补贴{{ storeInfo.discount }}</text> -->
<view class="v12-font-28 v12-align-center" style="height: 74rpx; opacity: 0;">
<image class="gift-img" :src="webUrl + '/orderIcon/gift.png'" mode="widthFix" ></image>
支持礼包赠送
</view>
<view @click="toGift" class="v12-white v12-dark-text v12-radius-20 v12-font-28 gift-btn" v-if="!giftBg">
送给朋友
</view>
</view>
</view>
<!-- 档期 -->
<view class="v12-mt-3 v12-px-3">
<view class="section v12-radius-60 v12-white">
<view class="intro-text">
{{ detail.intro }}
</view>
<view class="intro-text v12-mt-2">* 下单后与商家确认入住时间</view>
<view class="v12-justify-between v12-align-center v12-mt-2">
<view class="v12-primary v12-white-text v12-font-30 date-item v12-px-1" style="height: 160rpx;">
<view></view>
<view></view>
</view>
<view v-for="(d) in currentWeek" :key="d.date" class="date-item" style="width: 74rpx;">
<view>{{ d.week }}</view>
<view class="v12-my-1 v12-primary-text">{{ d.monthDay }}</view>
<view>{{ d.status }}</view>
</view>
<view @click="handleDate" class="v12-align-center v12-primary-text v12-font-24 v12-white v12-px-1">
<view>
<view></view>
<view></view>
<view></view>
<view></view>
</view>
<view class="v12-ml-1">
<u-icon name="arrow-right" color="#C6262E"></u-icon>
</view>
</view>
</view>
</view>
</view>
<!-- 套餐详情模拟 -->
<view class="v12-px-3 v12-mt-3" v-if="detail.packageDetails && detail.packageDetails.length > 0">
<view class="section v12-radius-60 v12-white v12-px-3 v12-pt-3">
<view class="v12-font-bold v12-font-36">套餐详情</view>
<view class="v12-mt-3">
<view v-for="(c, i) in detail.packageDetails" :key="i" class="package-item v12-mb-2">
<view class="grid-txt v12-justify-between v12-align-center" v-if="c.title">
<view class="v12-px-2 v12-py-1">
<u-icon v-if="c.type === 'steward'" :name="webUrl + '/home/我.png'"></u-icon>
<u-icon v-if="c.type === 'hotel'" :name="webUrl + '/home/1home.png'"></u-icon>
<u-icon v-if="c.type === 'food'" :name="webUrl + '/home/coffee.png'"></u-icon>
<u-icon v-if="c.type === 'ride'" :name="webUrl + '/home/che.png'"></u-icon>
</view>
<view class="v12-px-2 v12-py-1 v12-white v12-radius-100">{{ c.title }}</view>
</view>
<swiper
v-if="c.title &&c.images && c.images.length > 0"
class="package-swiper v12-mt-2"
:indicator-dots="false"
circular
next-margin="380rpx"
:autoplay="c.images.length > 2"
interval="3000"
duration="10000">
<swiper-item v-for="(img, idx) in c.images" :key="idx">
<image
@click="previewHero(c.images, img)"
:src="img"
class="swiper-img"
mode="aspectFill" />
</swiper-item>
</swiper>
<view v-if="c.title &&c.description" class="v12-font-26 v12-mt-2">
<rich-text :nodes="c.description"></rich-text>
</view>
</view>
</view>
</view>
</view>
<view class="v12-px-3 v12-mt-3">
<view class="section v12-radius-60 v12-white v12-px-3 v12-pt-3">
<view class="v12-mt-2 v12-pl-2 v12-font-bold v12-font-36 v12-align-center title-wrap">
<text class="v12-mr-1">预定必读</text>
<u-icon bold size="18" color="#333" name="info-circle"></u-icon>
</view>
<view class="v12-mt-3 v12-font-26">
<rich-text :nodes="rules"></rich-text>
</view>
<!-- 表格 -->
<!-- <view class="simple-table">
<view class="table-row">
<view class="table-cell">申请退款时间</view>
<view class="table-cell">费用</view>
</view>
<view class="table-row">
<view class="table-cell">下单未预约</view>
<view class="table-cell">免费</view>
</view>
<view class="table-row">
<view class="table-cell">入住前x天</view>
<view class="table-cell"></view>
</view>
<view class="table-row">
<view class="table-cell">入住前x天</view>
<view class="table-cell"></view>
</view>
</view> -->
</view>
</view>
<view class="v12-px-3 v12-mt-3" v-if="detail.detailImages && detail.detailImages.length > 0">
<u-divider text="图片详情"></u-divider>
</view>
<!-- 图片 -->
<view class="v12-mt-3 v12-px-3">
<view v-for="(img, idx) in detail.detailImages" :key="idx" >
<image :src="img" class="v12-radius-40" style="width: 100%;" mode="aspectFill" @click="previewHero(detail.detailImages, img)" />
</view>
</view>
<view class="bottom-space"></view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="total " >
<text>合计</text>
<text class="v12-primary-text">
<text class="v12-font-20">¥ </text>
{{ detail.price }}</text>
</view>
<view class="bar-actions">
<view class="mini-btn" @click="contactService">
<view>
<u-icon :name="webUrl + '/home/pho.png'"></u-icon>
</view>
<view>联系商家</view>
</view>
<view class="buy-btn" @click="bookNow">
<view>
<u-icon :name="webUrl + '/home/icon-cart.png'" size="24"></u-icon>
</view>
<view>立即购买</view>
</view>
</view>
</view>
<!-- <Calendar ref="calendar"
:minDay="detail.minBookingDays"
@dateSelected="handleDateSelected"
></Calendar> -->
<DateRangePicker
ref="dateRangePicker"
:show="showDatePicker"
:minBookingDays="detail.minBookingDays"
:travelGroupProductId="detail.id"
@update:show="showDatePicker = false"
@input="handleDateSelected"
>
</DateRangePicker>
</view>
</template>
<script>
import { getJiaLouProductDetail, getJiaLouCalendar,getJiaLouBookingRules } from '@/api/qianxian'
import {getGiftCardSendBackground} from "@/api/gift"
import DateRangePicker from '@/components/DateRangePicker/DateRangePicker.vue'
export default {
name: 'GroupBuyDetails',
components: {
DateRangePicker
},
data() {
return {
dateRange: [null, null],
showDatePicker: false,
webUrl: this.$VUE_APP_RESOURCES_URL,
giftBg: '',
id: '',
activePeriod: 0,
detail: {},
calendar: [],
currentWeek: [],
rules: "",
rangeDate: {}
}
},
onShow() {
getGiftCardSendBackground().then(res => {
if (res.status === 200) {
this.giftBg = res.data || ''
}
})
},
onLoad(query) {
this.id = (query && query.id) || ''
this.getDetail()
this.getCalendar()
getJiaLouBookingRules(this.id).then(res => {
if (res.status === 200) {
this.rules = res.data || ''
}
})
},
methods: {
previewHero(urls, current) {
console.log(urls);
uni.previewImage({ urls, current })
},
toGift(){
uni.navigateTo({
// + '&couponId=' + coupId
url: '/pkg_user/views/gift/gift?cartId=' + this.id + '&isGroupBuy=1'
})
},
handleDateSelected(dateRange) {
this.dateRange = dateRange
},
handleDate() {
this.showDatePicker = true
},
/**
* 获取产品可预约日期
*/
getCalendar() {
const currentMonth = new Date().toISOString().slice(0, 7)
getJiaLouCalendar({
travelGroupProductId: this.id,
month: currentMonth
}).then(res => {
if (res.status === 200) {
this.calendar = res.data || []
// 计算当前日及后面4天
this.currentWeek = this.computeCurrentWeekFromCalendar()
}
})
},
/**
* 根据 calendar 数据计算:今天 + 接下来4天(共5天)
* 返回 YYYY-MM-DD 字符串数组
*/
computeCurrentWeekFromCalendar() {
const result = []
const base = new Date()
for (let i = 0; i < 5; i++) {
const d = new Date(base)
d.setDate(base.getDate() + i)
const dateStr = this.formatYMD(d)
result.push({
date: dateStr,
week: this.getWeekLabel(dateStr),
status: this.findDateStatus(dateStr),
monthDay: i === 0 ? '今日' : this.getMonthDay(dateStr)
})
}
return result
},
formatYMD(d) {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
},
getWeekLabel(dateStr) {
const [y, m, d] = dateStr.split('-').map(Number)
const date = new Date(y, m - 1, d)
const map = ['周日','周一','周二','周三','周四','周五','周六']
return map[date.getDay()]
},
getMonthDay(dateStr) {
const [y, m, d] = dateStr.split('-').map(Number)
return `${m}/${d}`
},
/**
* 获取产品详情
*/
async getDetail() {
if (!this.id) {
uni.showToast({ title: '参数错误', icon: 'none' })
return
}
const res = await getJiaLouProductDetail(this.id)
if (res.status === 200) {
this.detail = res.data
this.detail.packageDetails = this.detail.packageDetails.filter(e => e.title !== '')
}
},
contactService() {
uni.showModal({
title: '商家电话',
content: this.detail.merPhone || '暂无电话',
confirmText: '立即拨打',
cancelText: '取消',
success: (res) => {
if (res.confirm && this.detail.merPhone) {
uni.makePhoneCall({
phoneNumber: this.detail.merPhone,
fail: () => {
uni.showToast({
title: '拨打电话失败',
icon: 'none'
})
}
})
}
}
})
},
bookNow() {
if(!this.dateRange[0] || !this.dateRange[1]){
uni.showToast({
title: '请选择预约日期',
icon: 'none'
})
return
}
uni.navigateTo({ url: '/pkg_product/views/submitOrder?id=' + this.id + '&orderStartDate=' + this.dateRange[0] + '&orderEndDate=' + this.dateRange[1] })
},
/**
* 查找指定日期的预约状态
* @param {string} dateStr YYYY-MM-DD格式的日期字符串
* @returns {string} 预约状态文本:可约、已约、不可约
*/
findDateStatus(dateStr) {
// 在calendar数组中查找匹配的日期
const matchedDate = this.calendar.find(item => item.date === dateStr)
console.log('未找到匹配日期:', matchedDate)
// 如果找到匹配的日期,根据inventoryStatus返回对应文本
if (matchedDate) {
switch (matchedDate.inventoryStatus) {
case 0:
return '可订'
case 1:
return '不可订'
case 2:
return '不可订'
default:
return '不可订'
}
}
// 如果没有找到匹配的日期,返回不可约
return '不可订'
},
/**
* 处理日期点击事件
* @param {Object} date 日期对象
*/
handleDateClick(date) {
// 如果是不可订的日期,直接返回
if (date.status === '不可订') {
uni.showToast({
title: '该日期不可预订',
icon: 'none'
})
return
}
// 检查是否已选中
const index = this.rangeDate.indexOf(date.date)
if (index > -1) {
// 如果已选中,则取消选中
this.rangeDate.splice(index, 1)
} else {
// 如果未选中,则添加到选中列表
this.rangeDate.push(date.date)
// 对日期进行排序
this.rangeDate.sort()
// 检查日期连续性
if (this.rangeDate.length > 1) {
const isConsecutive = this.checkConsecutiveDates(this.rangeDate)
if (!isConsecutive) {
uni.showToast({
title: '请选择连续的日期',
icon: 'none'
})
// 移除最后添加的日期
this.rangeDate.pop()
}
}
}
},
/**
* 检查日期是否被选中
* @param {string} date 日期字符串
* @returns {boolean} 是否被选中
*/
isDateSelected(date) {
return this.rangeDate.includes(date)
},
/**
* 检查日期是否连续
* @param {Array} dates 日期数组
* @returns {boolean} 是否连续
*/
checkConsecutiveDates(dates) {
for (let i = 1; i < dates.length; i++) {
const curr = new Date(dates[i])
const prev = new Date(dates[i-1])
const diffDays = (curr - prev) / (1000 * 60 * 60 * 24)
if (diffDays !== 1) {
return false
}
}
return true
},
}
}
</script>
<style lang="less" scoped>
.group-detail {
min-height: 100vh;
}
.gift-img{
width: 34rpx;
height: 34rpx;
margin-right: 10rpx;
}
.page-scroll { height: calc(100vh - 120rpx); }
.hero-swiper {
width: 100%;
height: 700rpx;
}
.hero-img { width: 100%; height: 100%; }
.price-title {
padding: 24rpx 24rpx;
background: #fff;
}
.price { color: #E53935; font-size: 40rpx; font-weight: 700; }
.title { margin-top: 8rpx; color: #333; font-size: 28rpx; line-height: 40rpx; }
.action-row { margin-top: 16rpx; display: flex; gap: 16rpx; }
.btn { padding: 14rpx 24rpx; border-radius: 30rpx; font-size: 26rpx; }
.btn.ghost { border: 1rpx solid #ccc; color: #333; }
.btn.primary { background: #E53935; color: #fff; }
.section { padding: 20rpx 24rpx; }
.section-hd { display: flex; align-items: center; font-size: 28rpx; color: #111; font-weight: 600; margin-bottom: 12rpx; }
.dot { width: 14rpx; height: 14rpx; border-radius: 50%; background: #E53935; margin-right: 12rpx; }
.schedule { white-space: nowrap; }
.schedule-inner { display: flex; }
.period { padding: 14rpx 20rpx; border: 1rpx solid #eee; border-radius: 12rpx; margin-right: 12rpx; }
.period.active { border-color: #E53935; color: #E53935; }
.p1 { font-size: 26rpx; font-weight: 600; }
.p2 { font-size: 22rpx; color: #666; }
.grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
.grid-img {
width: 204rpx;
height: 204rpx;
border-radius: 16rpx;
}
.grid-txt {
width: fit-content;
font-size: 24rpx;
color: #333;
border: 1px solid #F9E49D;
background: #F9E49D;
border-radius: 100rpx;
margin-bottom: 12rpx;
}
.bullets { }
.bullet { display: flex; align-items: flex-start; margin-bottom: 12rpx; }
.bullet-icon { width: 28rpx; text-align: center; color: #E53935; font-size: 28rpx; line-height: 32rpx; }
.bullet-txt { flex: 1; font-size: 26rpx; color: #333; }
.fee-table { margin-top: 12rpx; border: 1rpx solid #eee; border-radius: 12rpx; overflow: hidden; }
.fee-row { display: flex; }
.fee-row + .fee-row { border-top: 1rpx solid #f2f2f2; }
.fee-col { flex: 1; padding: 16rpx; font-size: 26rpx; }
.fee-head { background: #f8f8f8; font-weight: 600; }
.name { color: #333; }
.val { color: #666; text-align: right; }
.gallery-img { width: 100%; margin-bottom: 16rpx; border-radius: 12rpx; overflow: hidden; }
.bottom-space { height: 140rpx; }
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-top: 1rpx solid #eee;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx 20rpx 30rpx;
height: 140rpx;
box-sizing: border-box;
}
.total { font-size: 30rpx; color: #111; font-weight: 700; }
.bar-actions { display: flex; gap: 16rpx; }
.mini-btn {
padding: 10rpx 22rpx;
border: 1rpx solid #ccc;
border-radius: 132rpx;
font-size: 30rpx;
color: #333;
display: flex;
align-items: center;
}
.buy-btn {
padding: 10rpx 28rpx;
border-radius: 132rpx;
background: #C52733;
color: #fff;
font-size: 30rpx;
display: flex;
align-items: center;
}
.gift-wrap{
position: relative;
}
.intro-text{
font-weight: 400;
font-size: 26rpx;
color: #666666;
line-height: 44rpx;
letter-spacing: 2rpx;
}
.package-item{
margin-bottom: 24rpx;
}
.date-item{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(240,240,240,0.69);
border-radius: 40rpx;
font-size: 24rpx;
color: #333;
height: 160rpx;
padding: 0 4rpx;
.selected-date {
background-color: #E9BABA;
font-weight: bold;
border-radius: 100rpx;
}
}
.simple-table {
margin-top: 12rpx;
border: 1rpx solid #FADE9F;
border-radius: 12rpx;
overflow: hidden;
border-radius: 60rpx 60rpx 0 0;
}
.table-row {
display: flex;
background: #fff;
&:first-child {
background: #FADE9F;
color: #333;
}
}
.table-row + .table-row {
border-top: 1rpx solid #FADE9F;
}
.table-cell {
flex: 1;
padding: 16rpx;
font-size: 26rpx;
color: #333;
border-right: 1rpx solid #FADE9F;
text-align: center;
font-weight: 500;
}
.table-cell:last-child {
border-right: 0;
}
.package-swiper {
width: 100%;
height: 240rpx;
border-radius: 20rpx;
overflow: hidden;
}
.swiper-img {
width: 240rpx;
height: 100%;
border-radius: 20rpx;
}
.grid-txt {
width: fit-content;
font-size: 24rpx;
color: #333;
border: 1px solid #F9E49D;
background: #F9E49D;
border-radius: 100rpx;
margin-bottom: 12rpx;
}
.bullets { }
.bullet { display: flex; align-items: flex-start; margin-bottom: 12rpx; }
.bullet-icon { width: 28rpx; text-align: center; color: #E53935; font-size: 28rpx; line-height: 32rpx; }
.bullet-txt { flex: 1; font-size: 26rpx; color: #333; }
.fee-table { margin-top: 12rpx; border: 1rpx solid #eee; border-radius: 12rpx; overflow: hidden; }
.fee-row { display: flex; }
.fee-row + .fee-row { border-top: 1rpx solid #f2f2f2; }
.fee-col { flex: 1; padding: 16rpx; font-size: 26rpx; }
.fee-head { background: #f8f8f8; font-weight: 600; }
.name { color: #333; }
.val { color: #666; text-align: right; }
.gallery-img { width: 100%; margin-bottom: 16rpx; border-radius: 12rpx; overflow: hidden; }
.bottom-space { height: 140rpx; }
.total { font-size: 30rpx; color: #111; font-weight: 700; }
.bar-actions { display: flex; gap: 16rpx; }
.mini-btn {
padding: 10rpx 22rpx;
border: 1rpx solid #ccc;
border-radius: 132rpx;
font-size: 30rpx;
color: #333;
display: flex;
align-items: center;
}
.buy-btn {
padding: 10rpx 28rpx;
border-radius: 132rpx;
background: #C52733;
color: #fff;
font-size: 30rpx;
display: flex;
align-items: center;
}
.gift-wrap{
position: relative;
}
.intro-text{
font-weight: 400;
font-size: 26rpx;
color: #666666;
line-height: 44rpx;
letter-spacing: 4rpx;
}
.package-item{
margin-bottom: 24rpx;
}
.date-item{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(240,240,240,0.69);
border-radius: 40rpx;
font-size: 24rpx;
color: #333;
height: 160rpx;
padding: 0 4rpx;
.selected-date {
background-color: #E9BABA;
font-weight: bold;
border-radius: 100rpx;
}
}
.simple-table {
margin-top: 12rpx;
border: 1rpx solid #FADE9F;
border-radius: 12rpx;
overflow: hidden;
border-radius: 60rpx 60rpx 0 0;
}
.table-row {
display: flex;
background: #fff;
&:first-child {
background: #FADE9F;
color: #333;
}
}
.table-row + .table-row {
border-top: 1rpx solid #FADE9F;
}
.table-cell {
flex: 1;
padding: 16rpx;
font-size: 26rpx;
color: #333;
border-right: 1rpx solid #FADE9F;
text-align: center;
font-weight: 500;
}
.table-cell:last-child {
border-right: 0;
}
.title-wrap{
position: relative;
&::after{
content: '';
position: absolute;
bottom: 4rpx;
left: 0;
width: 6rpx;
height: 80%;
border-radius: 10rpx;
background: #CE4852;
}
}
</style>