Files
xdd-uniapp-new/pkg_product/views/groupBuyDetails.vue
T
2026-05-12 15:19:37 +08:00

1020 lines
30 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 @click="handleDate" 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" @click="handleDate" :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-mt-3 v12-px-3" v-if="detail.experienceCouponRule">
<view class="section v12-radius-60 v12-white v12-px-3 v12-pt-3 experience-card">
<view class="v12-mt-2 v12-pl-2 v12-font-bold v12-font-36 v12-align-center title-wrap">
<text class="v12-mr-1">{{ detail.experienceCouponRule.ruleTitle1 }}</text>
</view>
<view class="v12-mt-2">
<view class="grid-txt v12-justify-between v12-align-center">
<view class="v12-px-2 v12-py-1"><u-icon :name="webUrl + '/icon/gift.png'" size="18"></u-icon></view>
<text class="v12-px-2 v12-py-1 v12-white v12-radius-100">{{ detail.experienceCouponRule.ruleSubTitle1 }}</text>
</view>
</view>
<view class="v12-mt-3 image-grid">
<view class="image-item" v-for="(img, idx) in detail.experienceCouponRule.ruleImages" :key="idx">
<image :src="img" mode="aspectFill" @click="previewHero(detail.experienceCouponRule.ruleImages, img)" />
</view>
</view>
<view class="v12-mt-3 v12-pl-2 v12-font-bold v12-font-36 v12-align-center title-wrap">
<text class="v12-mr-1">{{ detail.experienceCouponRule.ruleTitle2 }}</text>
</view>
<view class="v12-mt-2 v12-font-26">
<rich-text :nodes="detail.experienceCouponRule.ruleContent"></rich-text>
</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="v12-mt-3">
<view class="section v12-white review-section">
<view class="review-header" @click="goEvaluateList">
<text class="v12-pl-2 v12-font-bold v12-font-36 v12-align-center title-wrap">店铺评价</text>
<text class="iconfont icon-jiantou review-arrow"></text>
</view>
<view v-if="reviewList.length">
<UserEvaluation :reply="latestReviewList" />
</view>
<view v-else class="review-empty">
暂无评价
</view>
</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="handleBuyClick">
<view>
<u-icon :name="webUrl + '/home/icon-cart.png'" size="24"></u-icon>
</view>
<view>立即购买</view>
</view>
</view>
</view>
<DateRangePicker
ref="dateRangePicker"
:show="showDatePicker"
:minBookingDays="detail.minBookingDays"
:travelGroupProductId="detail.id"
@update:show="showDatePicker = false"
@input="handleDateSelected"
>
</DateRangePicker>
<u-popup
:show="showAgreementPopup"
mode="center"
round="16"
:safe-area-inset-bottom="false"
>
<view class="agreement-popup">
<view class="agreement-header">
<text class="agreement-title">{{ agreementTitle }}</text>
<text class="iconfont icon-guanbi agreement-close" @click="closeAgreement"></text>
</view>
<scroll-view scroll-y class="agreement-body">
<u-parse :content="agreementContent"></u-parse>
</scroll-view>
<view class="agreement-actions">
<button class="agreement-btn agree" @click="confirmAgreement">我同意</button>
<button class="agreement-btn cancel" @click="closeAgreement">返回</button>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import { getJiaLouProductDetail, getJiaLouCalendar,getJiaLouBookingRules, getJiaLouProductReply, getTravelGroupOrderAgreement } from '@/api/qianxian'
import {getGiftCardSendBackground} from "@/api/gift"
import DateRangePicker from '@/components/DateRangePicker/DateRangePicker.vue'
import UserEvaluation from '@/components/UserEvaluation'
import uParse from '@/uni_modules/uview-ui/components/u-parse/u-parse.vue'
export default {
name: 'GroupBuyDetails',
components: {
DateRangePicker,
UserEvaluation,
uParse
},
data() {
return {
dateRange: [null, null],
showDatePicker: false,
webUrl: this.$VUE_APP_RESOURCES_URL,
giftBg: '',
id: '',
activePeriod: 0,
detail: {},
calendar: [],
currentWeek: [],
rules: "",
rangeDate: {},
reviewActiveType: 0,
reviewList: [],
reviewPage: 1,
reviewLimit: 5,
reviewLoading: false,
reviewLoadEnd: false,
showAgreementPopup: false,
agreementTitle: '团购协议',
agreementContent: '',
enableAgreement: false
}
},
computed: {
latestReviewList() {
if (!this.reviewList || !this.reviewList.length) return []
return [this.reviewList[0]]
}
},
onShow() {
getGiftCardSendBackground().then(res => {
if (res.status === 200) {
this.giftBg = res.data || ''
}
})
},
onLoad(query) {
this.id = (query && query.id) || ''
this.getDetail()
this.fetchReviewList(true)
getJiaLouBookingRules(this.id).then(res => {
if (res.status === 200) {
this.rules = res.data || ''
}
})
this.getOrderAgreement()
},
onReachBottom() {
if (!this.reviewLoadEnd) {
this.fetchReviewList()
}
},
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
this.showAgreementPopup = true
},
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 !== '')
const inventoryList = Array.isArray(res.data.inventoryStatus) ? res.data.inventoryStatus : []
const todayStr = this.formatYMD(new Date())
const futureList = inventoryList.filter(item => item.date >= todayStr)
const limitedList = (futureList.length ? futureList : inventoryList).slice(0, 5)
this.currentWeek = limitedList.map((item, index) => {
const dateStr = item.date
return {
date: dateStr,
week: this.getWeekLabel(dateStr),
status: this.mapInventoryStatus(item.inventoryStatus),
monthDay: index === 0 ? '今日' : this.getMonthDay(dateStr)
}
})
}
},
contactService() {
if(!this.detail.merPhone){
uni.showToast({
title: '暂无电话',
icon: 'none'
})
return
}
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'
})
}
})
}
}
})
},
async getOrderAgreement() {
try {
const res = await getTravelGroupOrderAgreement()
if (res && res.status === 200 && res.data) {
const data = res.data
this.agreementTitle = data.agreementTitle || '团购协议'
this.agreementContent = data.agreementContent || ''
this.enableAgreement = !!this.agreementContent
}
} catch (e) {
}
},
handleBuyClick() {
if (this.enableAgreement && this.agreementContent) {
this.showAgreementPopup = true
return
}
this.bookNow()
},
closeAgreement() {
this.showAgreementPopup = false
},
confirmAgreement() {
this.showAgreementPopup = false
this.bookNow()
},
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] })
},
goEvaluateList() {
if (!this.id) return
this.$yrouter.push({
path: '/pagesShop/shop/EvaluateList/index',
query: {
id: this.id,
isTravelGroup: 1
}
})
},
/**
* 查找指定日期的预约状态
* @param {string} dateStr YYYY-MM-DD格式的日期字符串
* @returns {string} 预约状态文本:可约、已约、不可约
*/
findDateStatus(dateStr) {
const matchedDate = this.calendar.find(item => item.date === dateStr)
console.log('未找到匹配日期:', matchedDate)
if (matchedDate) {
return this.mapInventoryStatus(matchedDate.inventoryStatus)
}
return '不可订'
},
mapInventoryStatus(status) {
switch (status) {
case 0:
return '可订'
case 1:
case 2:
default:
return '不可订'
}
},
async fetchReviewList(reset = false) {
if (this.reviewLoading) return
if (!reset && this.reviewLoadEnd) return
if (!this.id) return
if (reset) {
this.reviewPage = 1
this.reviewLoadEnd = false
this.reviewList = []
}
this.reviewLoading = true
try {
const res = await getJiaLouProductReply({
travelGroupProductId: this.id,
type: this.reviewActiveType,
page: this.reviewPage,
limit: this.reviewLimit
})
if (res && res.status === 200 && res.data) {
const pageData = res.data
const rawList = Array.isArray(pageData.records) ? pageData.records : []
const list = rawList.map(item => {
const productScore = Number(item.productScore) || 0
const serviceScore = Number(item.serviceScore) || 0
let star = productScore || serviceScore || 5
const skuText = '默认'
const picturesArr = Array.isArray(item.pics) ? item.pics : []
return {
...item,
star,
sku: item.travelGroupProductName || skuText,
picturesArr
}
})
this.reviewList = this.reviewList.concat(list)
this.reviewLoadEnd = list.length === 0 || this.reviewPage >= (pageData.pages || 0)
if (!this.reviewLoadEnd) {
this.reviewPage += 1
}
}
} finally {
this.reviewLoading = false
}
},
/**
* 检查日期是否被选中
* @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;
}
.experience-card .benefit-chip{
display: inline-flex;
align-items: center;
background: #FADE9F;
border: 1rpx solid #FADE9F;
color: #333;
border-radius: 100rpx;
padding: 8rpx 16rpx;
font-size: 24rpx;
}
.experience-card .image-grid{
display: flex;
gap: 16rpx;
overflow-x: auto;
padding-bottom: 8rpx;
}
.experience-card .image-item{
flex: 0 0 auto;
width: 200rpx;
height: 200rpx;
border-radius: 20rpx;
overflow: hidden;
background: #f5f5f5;
}
.experience-card .image-item image{
width: 100%;
height: 100%;
}
.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;
}
.review-section{
padding-top: 20rpx;
padding-bottom: 24rpx;
}
.review-header{
display: flex;
align-items: center;
justify-content: space-between;
}
.review-arrow{
font-size: 28rpx;
color: #333;
}
.review-title{
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.review-tabs{
margin-top: 24rpx;
display: flex;
flex-wrap: nowrap;
}
.review-tab-item{
padding: 8rpx 24rpx;
border-radius: 40rpx;
background: #f5f5f5;
font-size: 24rpx;
color: #666;
margin-right: 16rpx;
}
.review-tab-item.active{
background: #C52733;
color: #fff;
}
.review-load-more{
margin-top: 24rpx;
text-align: center;
font-size: 24rpx;
color: #999;
}
.review-no-more{
color: #ccc;
}
.review-empty{
margin-top: 32rpx;
text-align: center;
font-size: 24rpx;
color: #999;
}
.intro-text{
font-weight: 400;
font-size: 26rpx;
color: #666666;
line-height: 44rpx;
letter-spacing: 2rpx;
}
.package-item{
margin-bottom: 24rpx;
}
.agreement-popup {
width: 620rpx;
background: #fff;
border-radius: 24rpx;
padding: 40rpx 32rpx 32rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.agreement-header {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-bottom: 30rpx;
flex-shrink: 0;
}
.agreement-title {
font-size: 34rpx;
font-weight: 700;
color: #333;
}
.agreement-close {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
font-size: 32rpx;
color: #999;
padding: 10rpx;
}
.agreement-body {
height: 600rpx;
margin-bottom: 30rpx;
background: #f8f8f8;
border-radius: 12rpx;
padding: 20rpx;
box-sizing: border-box;
}
.agreement-actions {
display: flex;
gap: 20rpx;
flex-shrink: 0;
}
.agreement-btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
font-size: 28rpx;
font-weight: 600;
margin: 0;
&::after { border: none; }
}
.agreement-btn.agree {
background: #E53935;
color: #fff;
box-shadow: 0 4rpx 12rpx rgba(229, 57, 53, 0.3);
}
.agreement-btn.cancel {
background: #f5f5f5;
color: #666;
}
.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>