Files
hhrs/pages/rights/detail.vue
T
jacobxu666andClaude Fable 5 8b3ac20e34 fix: 权益详情页修复底部预约条遮挡「权益说明」
- .bottom-bar 是 position:fixed 不占文档流,「权益说明」作为最后一个内容块,
  短屏滚到底会被预约条压住
- 内容容器按底栏高度预留空间:padding-bottom calc(80px + env(safe-area-inset-bottom))
  (底栏 12 + 按钮 44 + 12 = 68px,另留 12px 呼吸位,并兼容全面屏安全区)
- 仅在存在底栏时生效:类名与底栏共用 detail.isAppointment 条件,不可预约的权益不会多出空白

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-28 16:27:03 +08:00

474 lines
10 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="page">
<!-- xuchao: 底部「立即预约」是 fixed 不占位,短屏滚到底会把「权益说明」压住;
有底栏时给内容区预留 80px(底栏 68px + 12px 呼吸位)+ 安全区 -->
<view v-if="detail" :class="{ 'has-bottom-bar': detail.isAppointment }">
<!-- 门店信息卡片(信息+地址合并容器) -->
<view class="store-card">
<view class="store-main">
<image class="store-img" :src="detail.shopImage" mode="aspectFill" />
<view class="store-info">
<text class="store-name">{{ detail.shopName }}</text>
<text class="store-category">{{ detail.category }}</text>
<text class="store-hours">营业时间:{{ detail.businessTime }}</text>
</view>
</view>
<view class="divider"></view>
<view class="address-row">
<text class="address-text">{{ detail.address }}</text>
<view class="distance-row" v-if="detail.distance !== null">
<u-icon name="map" color="#999999" size="24"></u-icon>
<text class="distance-text">{{ detail.distance < 1 ? Math.round(detail.distance * 1000) + 'm' : Math.round(detail.distance) + 'km' }}</text>
</view>
</view>
</view>
<!-- 选择预约日期 -->
<view class="date-section" v-if="detail.isAppointment && detail.dates && detail.dates.length > 0">
<view class="section-title">
<image src="https://aliyunoss.hn12301.net/hhrs/hhrs_xzyyrq.png" style="width: 30rpx;height: 30rpx;"></image>
<text class="section-title-text">选择预约日期</text>
</view>
<view class="date-list">
<view
v-for="(item, index) in detail.dates"
:key="index"
class="date-card"
:class="{ active: selectedDate === index, disabled: item.disabled }"
@click="!item.disabled && selectDate(index)"
>
<text class="date-weekday">{{ item.weekday }}</text>
<text class="date-day">{{ item.day }}</text>
<text class="date-stock">{{ item.stock }}</text>
</view>
</view>
<!-- 时段选择 -->
<view class="time-slot-section" v-if="detail.dates[selectedDate] && detail.dates[selectedDate].timeSlots">
<text class="section-title-text" style="margin-top:16px;">选择时段</text>
<view class="time-slot-list">
<view v-for="(slot, idx) in detail.dates[selectedDate].timeSlots" :key="slot.id"
class="time-slot-card" :class="{ active: selectedTimeSlot === idx }"
@click="selectTimeSlot(idx)">
<text class="slot-name">{{ slot.name }}</text>
<text class="slot-time">{{ slot.time }}</text>
<text class="slot-stock">{{ slot.stock }}</text>
</view>
</view>
</view>
<text class="date-tip" v-if="detail.isAppointment">仅可预约近 {{ detail.appointmentDays }} 日内时段</text>
</view>
<!-- 使用说明 -->
<view class="discount-section">
<view class="section-title">
<image src="https://aliyunoss.hn12301.net/hhrs/hhrs_qysm.png" style="width: 30rpx;height: 30rpx;"></image>
<text class="discount-title">权益说明</text>
</view>
<view class="discount-list">
<text class="discount-item">{{ detail.cardInstruction }}</text>
</view>
</view>
<!-- 底部预约按钮 -->
<view class="bottom-bar" v-if="detail.isAppointment">
<view class="book-btn" @click="onBook">立即预约</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
ruleId: '',
detail: null,
selectedDate: 0,
selectedTimeSlot: null
}
},
onLoad(options) {
if (options.id) {
this.ruleId = options.id
this.loadDetail(options.id)
}
},
methods: {
selectDate(index) {
this.selectedDate = index
this.selectedTimeSlot = null
},
selectTimeSlot(index) {
this.selectedTimeSlot = index
},
onBook() {
if (!this.detail) return
if (this.detail.isAppointment) {
if (!this.detail.dates || this.detail.dates.length === 0) {
uni.showToast({ title: '暂无可预约日期', icon: 'none' })
return
}
const date = this.detail.dates[this.selectedDate]
if (date.disabled) {
uni.showToast({ title: date.reason || '该日期不可预约', icon: 'none' })
return
}
if (this.detail.dates[this.selectedDate].timeSlots && this.selectedTimeSlot === null) {
uni.showToast({ title: '请选择时段', icon: 'none' })
return
}
const cardNumber = uni.getStorageSync('cardNumber') || ''
const phone = uni.getStorageSync('phone') || ''
const params = {
scenicId: this.detail.ruleId,
channel: 'Channel_SZH',
thirdId: this.detail.cardModelId,
cardNum: cardNumber,
mobile: phone,
cardModelName: this.detail.cardModelName,
appointDate: date.date,
name: this.detail.name,
idNo: this.detail.identityNo
}
if (this.detail.dates[this.selectedDate].timeSlots) {
params.timeId = this.detail.dates[this.selectedDate].timeSlots[this.selectedTimeSlot].id
}
this.$u.api.doAppoint(params).then(res => {
if (res.errcode === 0) {
uni.showToast({ title: '预约成功', icon: 'success' })
setTimeout(() => {
uni.redirectTo({ url: '/pages/rights/rights?status=info' })
}, 1500)
} else {
uni.showToast({ title: res.errmsg || '预约失败', icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '预约失败', icon: 'none' })
})
}
// xuchao: 非预约权益没有“线上使用”动作——到店由商户核销,结果体现在权益包「已使用」列表;
// 底部按钮本身也只在 detail.isAppointment 时渲染,故删除原先只弹假 Toast 的不可达分支
},
loadDetail(ruleId) {
const loc = uni.getStorageSync('lng') && uni.getStorageSync('lat')
? { lng: uni.getStorageSync('lng'), lat: uni.getStorageSync('lat') }
: null
const cardNumber = uni.getStorageSync('cardNumber') || ''
const params = { ruleId: ruleId }
if (loc) {
params.lng = loc.lng
params.lat = loc.lat
}
if (cardNumber) {
params.cardNumber = cardNumber
}
this.$u.api.getRightsDetail(params).then(res => {
if (res.errcode === 0 && res.data) {
this.detail = res.data
if (res.data.isAppointment && res.data.dates && res.data.dates.length > 0) {
const firstAvailable = res.data.dates.findIndex(d => !d.disabled)
this.selectedDate = firstAvailable >= 0 ? firstAvailable : 0
}
}
})
}
}
}
</script>
<style>
page {
background-color: #f2f2f7;
padding-top: 24rpx;
}
</style>
<style scoped lang="less">
/* 门店卡片(信息+地址合并容器,样式与门店详情页一致) */
.store-card {
background-color: #ffffff;
margin: 0 12px 12px 12px;
border-radius: 8px;
overflow: hidden;
}
.store-main {
display: flex;
align-items: flex-start;
padding: 12px;
gap: 12px;
}
.store-img {
width: 100px;
height: 100px;
border-radius: 6px;
flex-shrink: 0;
}
.store-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 8px;
}
.store-name {
font-size: 16px;
font-weight: 500;
color: #1a1818;
}
.store-category {
font-size: 13px;
color: #999999;
}
.store-hours {
font-size: 13px;
color: #666666;
}
.divider {
height: 1px;
background-color: #f2f2f7;
margin: 0 12px;
}
/* 地址行(卡片内) */
.address-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
}
.address-text {
font-size: 13px;
color: #999999;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.distance-row {
display: flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
margin-left: 8px;
}
.distance-text {
font-size: 12px;
color: #999999;
}
/* 选择预约日期 */
.date-section {
background-color: #ffffff;
margin: 12px;
border-radius: 8px;
padding: 16px;
}
.section-title {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 16px;
}
.section-icon {
width: 18px;
height: 18px;
background-color: #0088ff;
color: #ffffff;
border-radius: 50%;
text-align: center;
line-height: 18px;
font-size: 12px;
font-weight: 700;
}
.section-title-text {
font-size: 15px;
font-weight: 600;
color: #1a1818;
}
.date-list {
display: flex;
gap: 12px;
}
.date-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 8px;
border: 1px solid #e8e8e8;
border-radius: 8px;
gap: 4px;
}
.date-card.active {
background-color: #0088ff;
border-color: #0088ff;
}
.date-card.disabled {
opacity: 0.4;
pointer-events: none;
}
.date-weekday {
font-size: 12px;
color: #999999;
}
.date-card.active .date-weekday {
color: rgba(255,255,255,0.8);
}
.date-day {
font-size: 15px;
font-weight: 600;
color: #1a1818;
}
.date-card.active .date-day {
color: #ffffff;
}
.date-stock {
font-size: 11px;
color: #999999;
}
.date-card.active .date-stock {
color: rgba(255,255,255,0.8);
}
.date-tip {
font-size: 12px;
color: #999999;
margin-top: 12px;
display: block;
}
.time-slot-section {
margin-top: 16px;
}
.time-slot-list {
display: flex;
gap: 12px;
margin-top: 12px;
}
.time-slot-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 12px 8px;
border: 1px solid #e8e8e8;
border-radius: 8px;
gap: 4px;
}
.time-slot-card.active {
background-color: #0088ff;
border-color: #0088ff;
}
.slot-name {
font-size: 13px;
font-weight: 600;
color: #1a1818;
}
.time-slot-card.active .slot-name {
color: #ffffff;
}
.slot-time {
font-size: 11px;
color: #999999;
}
.time-slot-card.active .slot-time {
color: rgba(255,255,255,0.8);
}
.slot-stock {
font-size: 11px;
color: #999999;
}
.time-slot-card.active .slot-stock {
color: rgba(255,255,255,0.8);
}
/* 商户优惠 */
.discount-section {
background-color: #ffffff;
margin: 12px;
border-radius: 8px;
padding: 16px;
}
.discount-tag-icon {
font-size: 16px;
color: #0088ff;
}
.discount-title {
font-size: 15px;
font-weight: 600;
color: #1a1818;
}
.discount-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.discount-item {
font-size: 13px;
color: #666666;
line-height: 1.6;
}
/* 底部预约按钮 */
/* xuchao: 内容区为 fixed 底栏预留的高度,短屏下「权益说明」才不会被遮住 */
.has-bottom-bar {
padding-bottom: calc(80px + env(safe-area-inset-bottom));
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
padding: 12px 16px;
padding-bottom: calc(12px + env(safe-area-inset-bottom));
z-index: 100;
}
.book-btn {
background-color: #0088ff;
color: #ffffff;
text-align: center;
font-size: 16px;
font-weight: 500;
height: 44px;
line-height: 44px;
border-radius: 22px;
}
</style>