feat(private-detail): 新增取消预订按钮+阶梯退款弹窗

This commit is contained in:
gqt
2026-08-14 16:39:34 +08:00
parent 3832a686d8
commit 7765d3dae1
@@ -63,8 +63,43 @@
</view> </view>
</view> </view>
<view v-if="booking.status === 'pending_pay'" class="fixed-action-bar"> <view v-if="booking.status === 'pending_pay' || (booking.status === 'booked' && booking.can_cancel)" class="fixed-action-bar">
<button class="primary-button action-btn" @tap="goPay">去支付</button> <button v-if="booking.status === 'booked' && booking.can_cancel" class="ghost-button action-btn" @tap="onCancelBooking">取消预订</button>
<button v-if="booking.status === 'pending_pay'" class="primary-button action-btn" @tap="goPay">去支付</button>
</view>
<view v-if="refundPopupVisible" class="refund-popup-mask" @tap="closeRefundPopup">
<view class="refund-popup" @tap.stop>
<view class="refund-popup-header">
<text class="refund-popup-title">取消预订</text>
<text class="refund-popup-close" @tap="closeRefundPopup">×</text>
</view>
<view class="refund-popup-body" v-if="refundQuote">
<view class="refund-rules">
<view class="refund-rules-header">
<text>距开始时间</text>
<text>退款比例</text>
<text>说明</text>
</view>
<view v-for="(rule, idx) in refundQuote.rules" :key="idx" class="refund-rule-row" :class="{ active: rule.description === refundQuote.applicable_tier_description }">
<text>{{ formatHoursBefore(rule.hours_before_start) }}</text>
<text>{{ rule.refund_percentage }}%</text>
<text>{{ rule.description }}</text>
</view>
</view>
<view class="refund-amount-box">
<text class="refund-amount-label">本次退款金额</text>
<text class="refund-amount-value">¥{{ refundQuote.refund_amount }}</text>
</view>
<view class="refund-tips">
<text>{{ refundQuote.applicable_tier_description || '当前不可取消' }}</text>
</view>
</view>
<view class="refund-popup-footer">
<button class="ghost-button refund-btn" @tap="closeRefundPopup">再想想</button>
<button class="primary-button refund-btn" @tap="confirmCancelBooking" :disabled="refundLoading">确认取消</button>
</view>
</view>
</view> </view>
</view> </view>
</template> </template>
@@ -94,6 +129,49 @@ onLoad(async (query) => {
const goPay = () => { const goPay = () => {
uni.navigateTo({ url: `/pages/comprehensive/pay/index?order_id=${booking.value.order_id}` }) uni.navigateTo({ url: `/pages/comprehensive/pay/index?order_id=${booking.value.order_id}` })
} }
const refundPopupVisible = ref(false)
const refundQuote = ref(null)
const refundLoading = ref(false)
const onCancelBooking = async () => {
try {
const data = await bookingApi.getPrivateBookingRefundQuote(bookingId.value)
refundQuote.value = data
refundPopupVisible.value = true
} catch {
uni.showToast({ title: '获取退款信息失败', icon: 'none' })
}
}
const closeRefundPopup = () => {
refundPopupVisible.value = false
refundQuote.value = null
}
const confirmCancelBooking = async () => {
if (refundLoading.value) return
refundLoading.value = true
try {
await bookingApi.cancelPrivateBooking(bookingId.value)
refundPopupVisible.value = false
const amount = refundQuote.value?.refund_amount || '0'
uni.showToast({ title: `取消成功,退款¥${amount}`, icon: 'none' })
booking.value = await bookingApi.getPrivateBookingDetail(bookingId.value)
} catch {
uni.showToast({ title: '取消失败,请稍后重试', icon: 'none' })
} finally {
refundLoading.value = false
}
}
const formatHoursBefore = (hours) => {
const h = Number(hours)
if (h >= 24) return '>24小时'
if (h >= 12) return '12-24小时'
if (h >= 0) return '0-12小时'
return `${h}小时`
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -198,4 +276,138 @@ const goPay = () => {
.action-btn { .action-btn {
flex: 1; flex: 1;
} }
.refund-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: flex-end;
}
.refund-popup {
width: 100%;
background: $bg-color-soft;
border-radius: $border-radius-lg $border-radius-lg 0 0;
overflow: hidden;
}
.refund-popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: $spacing-md $spacing-lg;
background: #fff;
border-bottom: 1rpx solid $bg-color-hover;
}
.refund-popup-title {
font-size: 32rpx;
font-weight: 900;
color: $text-color-main;
}
.refund-popup-close {
font-size: 40rpx;
color: $text-color-muted;
padding: 0 $spacing-sm;
}
.refund-popup-body {
padding: $spacing-md $spacing-lg;
}
.refund-rules {
margin-bottom: $spacing-md;
background: #fff;
border-radius: $border-radius-base;
overflow: hidden;
}
.refund-rules-header {
display: flex;
padding: $spacing-sm $spacing-md;
background: $bg-color-hover;
font-size: 22rpx;
font-weight: 800;
color: $text-color-muted;
text {
flex: 1;
text-align: center;
&:first-child { flex: 0.8; }
&:last-child { flex: 1.4; }
}
}
.refund-rule-row {
display: flex;
padding: $spacing-sm $spacing-md;
border-bottom: 1rpx solid $bg-color-hover;
font-size: 22rpx;
color: $text-color-muted;
text {
flex: 1;
text-align: center;
&:first-child { flex: 0.8; }
&:last-child { flex: 1.4; text-align: left; }
}
&.active {
background: rgba(64, 158, 255, 0.08);
color: $color-primary;
font-weight: 800;
}
}
.refund-rule-row:last-child {
border-bottom: none;
}
.refund-amount-box {
display: flex;
align-items: center;
justify-content: space-between;
padding: $spacing-md;
background: #fff;
border-radius: $border-radius-base;
margin-bottom: $spacing-sm;
}
.refund-amount-label {
font-size: 26rpx;
color: $text-color-muted;
}
.refund-amount-value {
font-size: 36rpx;
font-weight: 900;
color: $color-danger;
}
.refund-tips {
text-align: center;
font-size: 22rpx;
color: $text-color-muted;
margin-bottom: $spacing-md;
}
.refund-popup-footer {
display: flex;
gap: $spacing-md;
padding: $spacing-md $spacing-lg $spacing-lg;
padding-bottom: calc(#{$spacing-lg} + env(safe-area-inset-bottom));
background: #fff;
}
.refund-btn {
flex: 1;
}
</style> </style>