赛事报名使用优惠劵

This commit is contained in:
马梓彭 2026-04-13 18:15:37 +08:00
parent 70763194b4
commit 5294f3a077
4 changed files with 198 additions and 40 deletions

View File

@ -216,6 +216,9 @@ Page({
projectGroups: [], // 分组后的展示数据
couponCode: '',
totalPrice: 0,
originalPrice: 0,
discountPrice: 0,
usedCouponId: null,
submitting: false,
openidLoading: false,
myAuth: null
@ -503,7 +506,12 @@ Page({
_recalcAmountNow() {
const items = buildCalculateAmountItems(this.data.projectGroups);
if (!items.length) {
this.setData({ totalPrice: 0 });
this.setData({
totalPrice: 0,
originalPrice: 0,
discountPrice: 0,
usedCouponId: null
});
return;
}
@ -514,34 +522,58 @@ Page({
needAuth: true,
data: {
eventId: Number(this.data.eventId),
registrationMode: 2,
registrationMode: 1,
items
},
contentType: 'application/json'
})
.then((res) => {
// 兼容:后端可能返回 number / string / {amount}
if (seq !== this._calcSeq) return;
let amount = null;
if (typeof res === 'number') amount = res;
else if (typeof res === 'string') {
let totalAmount = null;
let originalPrice = 0;
let discountPrice = 0;
if (typeof res === 'number') {
totalAmount = res;
originalPrice = res;
} else if (typeof res === 'string') {
const n = Number(res);
amount = Number.isNaN(n) ? null : n;
totalAmount = Number.isNaN(n) ? null : n;
originalPrice = totalAmount || 0;
} else if (res && typeof res === 'object') {
const v = res.amount != null ? res.amount : res.payAmount != null ? res.payAmount : res.totalAmount;
const n = Number(v);
amount = Number.isNaN(n) ? null : n;
// 从统一的 CalculateAmountOutput 中获取数据
totalAmount = res.totalAmount != null ? Number(res.totalAmount) :
(res.amount != null ? Number(res.amount) :
(res.payAmount != null ? Number(res.payAmount) : null));
originalPrice = res.originalAmount != null ? Number(res.originalAmount) : totalAmount || 0;
discountPrice = res.discountAmount != null ? Number(res.discountAmount) : 0;
}
if (amount == null) {
if (totalAmount == null) {
// fallback:仍然展示前端计算的费用,避免空白
this.setData({ totalPrice: calcTotalPrice(this.data.projectGroups) });
const fallbackPrice = calcTotalPrice(this.data.projectGroups);
this.setData({
totalPrice: fallbackPrice,
originalPrice: fallbackPrice,
discountPrice: 0
});
} else {
this.setData({ totalPrice: amount });
this.setData({
totalPrice: totalAmount,
originalPrice: originalPrice || totalAmount,
discountPrice: discountPrice || 0
});
}
})
.catch(() => {
if (seq !== this._calcSeq) return;
this.setData({ totalPrice: calcTotalPrice(this.data.projectGroups) });
const fallbackPrice = calcTotalPrice(this.data.projectGroups);
this.setData({
totalPrice: fallbackPrice,
originalPrice: fallbackPrice,
discountPrice: 0
});
});
},
@ -549,6 +581,51 @@ Page({
this.setData({ couponCode: (e.detail.value || '').trim() });
},
/**
* 验证并应用优惠券
*/
async onValidateCoupon() {
const couponCode = this.data.couponCode;
if (!couponCode) {
wx.showToast({ title: '请输入优惠券兑换码', icon: 'none' });
return;
}
wx.showLoading({ title: '验证中', mask: true });
try {
const res = await request({
path: API.couponValidate,
method: 'POST',
needAuth: true,
data: {
eventId: Number(this.data.eventId),
couponCode: couponCode,
originalAmount: this.data.originalPrice || this.data.totalPrice
},
contentType: 'application/json'
});
if (res && res.isValid) {
this.setData({
usedCouponId: res.couponId,
totalPrice: res.finalAmount,
discountPrice: res.discountAmount || 0,
originalPrice: res.originalAmount || this.data.originalPrice
});
wx.showToast({ title: '优惠券应用成功', icon: 'success' });
} else {
wx.showToast({ title: res.invalidReason || '优惠券无效', icon: 'none' });
}
} catch (error) {
wx.showToast({
title: error && error.message ? error.message : '验证失败,请稍后重试',
icon: 'none'
});
} finally {
wx.hideLoading();
}
},
/** 收集已选的 projectId + groupId */
_getSelectedItems() {
const items = [];
@ -717,12 +794,16 @@ Page({
},
/** 获取支付信息 */
_getPaymentInfo(registrationId, openId) {
_getPaymentInfo(registrationId, openid) {
return request({
path: '/api/eventclient/payment-info',
method: 'POST',
needAuth: true,
data: { registrationId, openId },
data: {
registrationId,
openId: openid,
couponId: this.data.usedCouponId
},
contentType: 'application/json'
});
},

View File

@ -29,10 +29,25 @@
<!-- 优惠券 -->
<view class="coupon-row">
<input class="coupon-input" placeholder="请输入优惠卷兑换码" placeholder-class="coupon-placeholder" value="{{couponCode}}" bindinput="onCouponInput" />
<view class="coupon-icon">
<text class="coupon-icon-text">⊙</text>
<button class="coupon-btn" bindtap="onValidateCoupon">验证</button>
</view>
<!-- 金额显示 -->
<view class="price-section">
<view wx:if="{{discountPrice > 0}}" class="price-row">
<text class="price-label">原价:</text>
<text class="original-price">¥{{originalPrice}}</text>
</view>
<view wx:if="{{discountPrice > 0}}" class="price-row">
<text class="price-label">优惠:</text>
<text class="discount-price">-¥{{discountPrice}}</text>
</view>
<view class="price-row total">
<text class="price-label">合计:</text>
<text class="total-price">¥{{totalPrice}}</text>
</view>
</view>
<!-- 底部占位,避免被 bottom-bar 遮挡 -->
<view style="height: 200rpx;"></view>
</scroll-view>
@ -40,7 +55,14 @@
<view class="bottom-bar">
<button class="btn-offline" bindtap="onPayOffline" loading="{{submitting}}">线下支付</button>
<button class="btn-online" bindtap="onPayOnline" loading="{{submitting}}">
¥{{totalPrice}} 线上支付
<view wx:if="{{discountPrice > 0}}" class="price-display">
<text class="small-price">¥{{originalPrice}}</text>
<text class="main-price">¥{{totalPrice}}</text>
</view>
<view wx:else class="price-display">
<text class="main-price">¥{{totalPrice}}</text>
</view>
<text class="btn-text">线上支付</text>
</button>
</view>
</block>

View File

@ -140,18 +140,64 @@
font-size: 28rpx;
}
.coupon-icon {
.coupon-btn {
flex-shrink: 0;
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
background: #1a56db;
color: #ffffff;
font-size: 26rpx;
padding: 0 32rpx;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
margin-left: 24rpx;
}
.coupon-icon-text {
font-size: 44rpx;
color: #c0c4cc;
.coupon-btn::after {
border: none;
}
/* ===== 金额显示区域 ===== */
.price-section {
background: #ffffff;
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 24rpx;
}
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.price-row.total {
margin-bottom: 0;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
}
.price-label {
font-size: 28rpx;
color: #666;
}
.original-price {
font-size: 28rpx;
color: #999;
text-decoration: line-through;
}
.discount-price {
font-size: 28rpx;
color: #e53935;
font-weight: 600;
}
.total-price {
font-size: 36rpx;
color: #1a56db;
font-weight: 700;
}
/* ===== 底部按钮栏 ===== */
@ -177,6 +223,10 @@
font-weight: 600;
border: none;
letter-spacing: 2rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
}
.btn-offline {
@ -197,19 +247,23 @@
border: none;
}
.bottom-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 18rpx 16rpx 24rpx;
background: rgba(255, 255, 255, 0.95);
border-top: 1rpx solid rgba(0, 0, 0, 0.06);
.price-display {
display: flex;
align-items: baseline;
gap: 8rpx;
}
.submit-btn {
width: 100%;
height: 86rpx;
.small-price {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.7);
text-decoration: line-through;
}
.main-price {
font-size: 34rpx;
font-weight: 700;
}
.btn-text {
font-size: 28rpx;
}

View File

@ -13,6 +13,7 @@ const API = {
apply: '/api/eventclient/apply',
calculateAmount: '/api/eventclient/calculate-amount',
cancelRegistrationRefund: '/api/eventclient/cancel-registration-refund',
couponValidate: '/api/eventclient/coupon/validate',
wechatLogin: '/api/oauth/login',
loginByCode: '/api/oauth/login',