feat(M08-A): 增加权益可视化选择器

This commit is contained in:
Codex
2026-06-25 23:05:11 +08:00
parent 59c4ef330e
commit 47016744d4
11 changed files with 249 additions and 17 deletions
+114 -2
View File
@@ -32,15 +32,82 @@ Page({
packageHoldingId: '',
packageMinutes: '',
packageCreditYuan: '',
benefitLoading: false,
availableCoupons: [],
availablePackages: [],
selectedCouponName: '',
selectedPackageName: '',
},
onLoad(options) {
this.setData({
storeId: options.storeId || '',
roomId: options.roomId || '',
})
this.loadBenefitOptions()
},
updateBenefitField(event) {
async loadBenefitOptions() {
this.setData({ benefitLoading: true })
try {
await ensureLogin()
const response = await request('/profile/benefits')
this.setData({
availableCoupons: (response.data.coupons || [])
.filter((item) => canUseCoupon(item, this.data.storeId, this.data.roomId))
.map(formatCouponOption),
availablePackages: (response.data.packages || [])
.filter((item) => canUsePackage(item, this.data.storeId, this.data.roomId))
.map(formatPackageOption),
})
} catch (error) {
this.setData({ errorMessage: error.message || '权益加载失败' })
} finally {
this.setData({ benefitLoading: false })
}
},
selectCoupon(event) {
const couponGrantId = event.currentTarget.dataset.id || ''
const current = this.data.couponGrantId === couponGrantId
const selected = current
? null
: this.data.availableCoupons.find((item) => item.couponGrantId === couponGrantId)
this.setData({
couponGrantId: current ? '' : couponGrantId,
selectedCouponName: selected ? selected.name : '',
order: null,
payment: null,
successMessage: '',
errorMessage: '',
})
},
selectPackage(event) {
const packageHoldingId = event.currentTarget.dataset.id || ''
const current = this.data.packageHoldingId === packageHoldingId
const selected = current
? null
: this.data.availablePackages.find((item) => item.packageHoldingId === packageHoldingId)
this.setData({
packageHoldingId: current ? '' : packageHoldingId,
selectedPackageName: selected ? selected.name : '',
packageMinutes: selected && selected.remainingMinutes > 0
? String(Math.min(selected.remainingMinutes, this.data.durationMinutes))
: '',
packageCreditYuan: selected && selected.remainingAmountCents > 0
? yuanInput(Math.min(
selected.remainingAmountCents,
this.data.quote?.totalCents || selected.remainingAmountCents
))
: '',
order: null,
payment: null,
successMessage: '',
errorMessage: '',
})
},
updatePackageUsage(event) {
const field = event.currentTarget.dataset.field
if (!field) return
this.setData({
@@ -95,7 +162,52 @@ Page({
if (!this.data.order || !this.data.order.orderId) {
this.setData({ errorMessage: '请先预占订单' })
return
}
}
function canUseCoupon(item, storeId, roomId) {
return item.status === 'AVAILABLE'
&& Number(item.remainingUses || 0) > 0
&& (!item.storeId || String(item.storeId) === String(storeId))
&& (!item.roomId || String(item.roomId) === String(roomId))
}
function canUsePackage(item, storeId, roomId) {
return item.status === 'ACTIVE'
&& (Number(item.remainingMinutes || 0) > 0 || Number(item.remainingAmountCents || 0) > 0)
&& (!item.storeId || String(item.storeId) === String(storeId))
&& (!item.roomId || String(item.roomId) === String(roomId))
}
function formatCouponOption(item) {
const isTime = item.couponType === 'TIME'
return {
...item,
valueText: isTime ? `${item.timeMinutes || 0}分钟` : cents(item.discountAmountCents),
thresholdText: Number(item.minOrderAmountCents || 0) > 0
? `${cents(item.minOrderAmountCents)}可用`
: '无门槛',
scopeText: scopeText(item),
}
}
function formatPackageOption(item) {
return {
...item,
remainingAmountText: cents(item.remainingAmountCents),
scopeText: scopeText(item),
}
}
function scopeText(item) {
if (item.roomId) return `房间 ${item.roomId}`
if (item.roomCategoryId) return `房型 ${item.roomCategoryId}`
if (item.storeId) return `门店 ${item.storeId}`
return item.holidayOnly ? '节假日' : '通用'
}
function yuanInput(centsValue) {
return (Number(centsValue || 0) / 100).toFixed(2)
}
await this.withRequest(async () => {
const response = await request('/payments', {
method: 'POST',
+37 -5
View File
@@ -10,11 +10,41 @@
</view>
<view class="section-title">权益抵扣</view>
<view class="benefit-grid">
<input placeholder="优惠券ID" value="{{couponGrantId}}" data-field="couponGrantId" bindinput="updateBenefitField" />
<input placeholder="套餐ID" value="{{packageHoldingId}}" data-field="packageHoldingId" bindinput="updateBenefitField" />
<input placeholder="套餐分钟" type="number" value="{{packageMinutes}}" data-field="packageMinutes" bindinput="updateBenefitField" />
<input placeholder="套餐抵扣元" type="digit" value="{{packageCreditYuan}}" data-field="packageCreditYuan" bindinput="updateBenefitField" />
<view wx:if="{{benefitLoading}}" class="muted">权益加载中</view>
<view wx:if="{{!benefitLoading && availableCoupons.length === 0 && availablePackages.length === 0}}" class="muted">暂无可用权益</view>
<view wx:if="{{availableCoupons.length}}" class="benefit-list">
<view
wx:for="{{availableCoupons}}"
wx:key="couponGrantId"
class="benefit-option {{couponGrantId === item.couponGrantId ? 'active' : ''}}"
data-id="{{item.couponGrantId}}"
bindtap="selectCoupon"
>
<view class="option-head">
<view class="option-title">{{item.name}}</view>
<view class="option-value">{{item.valueText}}</view>
</view>
<view class="muted">{{item.thresholdText}} · {{item.scopeText}}</view>
</view>
</view>
<view wx:if="{{availablePackages.length}}" class="benefit-list">
<view
wx:for="{{availablePackages}}"
wx:key="packageHoldingId"
class="benefit-option {{packageHoldingId === item.packageHoldingId ? 'active' : ''}}"
data-id="{{item.packageHoldingId}}"
bindtap="selectPackage"
>
<view class="option-head">
<view class="option-title">{{item.name}}</view>
<view class="option-value">{{item.remainingAmountText}}</view>
</view>
<view class="muted">剩余 {{item.remainingMinutes}} 分钟 · {{item.scopeText}}</view>
</view>
</view>
<view wx:if="{{packageHoldingId}}" class="benefit-grid">
<input placeholder="套餐分钟" type="number" value="{{packageMinutes}}" data-field="packageMinutes" bindinput="updatePackageUsage" />
<input placeholder="套餐抵扣元" type="digit" value="{{packageCreditYuan}}" data-field="packageCreditYuan" bindinput="updatePackageUsage" />
</view>
<view class="action-grid">
@@ -39,6 +69,8 @@
<view class="result-title">订单</view>
<view>订单编号:{{order.orderId}}</view>
<view wx:if="{{order.reservationId}}">预占编号:{{order.reservationId}}</view>
<view wx:if="{{selectedCouponName}}">已选优惠券:{{selectedCouponName}}</view>
<view wx:if="{{selectedPackageName}}">已选套餐:{{selectedPackageName}}</view>
<view wx:if="{{order.quote.discountCents}}">优惠抵扣:{{order.quote.discountCents}}分</view>
<view wx:if="{{order.quote.packageCreditCents}}">套餐抵扣:{{order.quote.packageCreditCents}}分</view>
<button size="mini" data-order-id="{{order.orderId}}" bindtap="openOrder">查看订单</button>
+45
View File
@@ -40,6 +40,46 @@
background: #ffffff;
}
.benefit-list {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-bottom: 20rpx;
}
.benefit-option {
padding: 22rpx;
border: 2rpx solid #d9dde5;
border-radius: 12rpx;
background: #ffffff;
}
.benefit-option.active {
border-color: #1f6feb;
background: #eef5ff;
}
.option-head {
display: flex;
gap: 16rpx;
align-items: flex-start;
justify-content: space-between;
}
.option-title {
flex: 1;
min-width: 0;
font-size: 30rpx;
font-weight: 600;
word-break: break-all;
}
.option-value {
flex: 0 0 auto;
color: #1f6feb;
font-weight: 600;
}
.action-grid button {
flex: 1 1 280rpx;
}
@@ -67,6 +107,11 @@
color: #17823b;
}
.muted {
color: #777777;
line-height: 1.6;
}
.error {
margin: 16rpx 0;
color: #c73535;