feat(M08-A): 接入顾客端权益明细
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
"pages/orders/list",
|
||||
"pages/orders/detail",
|
||||
"pages/profile/index",
|
||||
"pages/benefits/index",
|
||||
"pages/recharge/index",
|
||||
"pages/logs/logs"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
const { request, ensureLogin, cents } = require('../../utils/api.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loading: false,
|
||||
errorMessage: '',
|
||||
coupons: [],
|
||||
packages: [],
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadBenefits()
|
||||
},
|
||||
|
||||
async loadBenefits() {
|
||||
this.setData({ loading: true, errorMessage: '' })
|
||||
try {
|
||||
await ensureLogin()
|
||||
const response = await request('/profile/benefits')
|
||||
this.setData({
|
||||
coupons: (response.data.coupons || []).map(formatCoupon),
|
||||
packages: (response.data.packages || []).map(formatPackage),
|
||||
})
|
||||
} catch (error) {
|
||||
this.setData({ errorMessage: error.message || '权益明细加载失败' })
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function formatCoupon(item) {
|
||||
const isTime = item.couponType === 'TIME'
|
||||
return {
|
||||
...item,
|
||||
statusText: benefitStatus(item.status),
|
||||
valueText: isTime ? `${item.timeMinutes || 0}分钟` : cents(item.discountAmountCents),
|
||||
thresholdText: Number(item.minOrderAmountCents || 0) > 0
|
||||
? `满${cents(item.minOrderAmountCents)}可用`
|
||||
: '无门槛',
|
||||
validText: `${formatDate(item.validFrom)} 至 ${formatDate(item.validTo)}`,
|
||||
scopeText: scopeText(item),
|
||||
}
|
||||
}
|
||||
|
||||
function formatPackage(item) {
|
||||
return {
|
||||
...item,
|
||||
statusText: benefitStatus(item.status),
|
||||
priceText: cents(item.priceCents),
|
||||
remainingAmountText: cents(item.remainingAmountCents),
|
||||
totalAmountText: cents(item.amountCentsTotal),
|
||||
validText: `${formatDate(item.validFrom)} 至 ${formatDate(item.validTo)}`,
|
||||
scopeText: scopeText(item),
|
||||
}
|
||||
}
|
||||
|
||||
function benefitStatus(status) {
|
||||
return ({
|
||||
AVAILABLE: '可用',
|
||||
ACTIVE: '可用',
|
||||
FROZEN: '锁定中',
|
||||
USED: '已使用',
|
||||
EXHAUSTED: '已用尽',
|
||||
EXPIRED: '已过期',
|
||||
})[status] || status
|
||||
}
|
||||
|
||||
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 formatDate(value) {
|
||||
const date = new Date(value)
|
||||
const pad = (input) => String(input).padStart(2, '0')
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "权益明细"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<scroll-view class="scrollarea" scroll-y type="list">
|
||||
<view class="page">
|
||||
<view class="title">权益明细</view>
|
||||
<view wx:if="{{errorMessage}}" class="error">{{errorMessage}}</view>
|
||||
|
||||
<view class="section-title">优惠券</view>
|
||||
<view wx:if="{{!loading && coupons.length === 0}}" class="empty">暂无优惠券</view>
|
||||
<view wx:for="{{coupons}}" wx:key="couponGrantId" class="benefit-card">
|
||||
<view class="card-head">
|
||||
<view class="benefit-name">{{item.name}}</view>
|
||||
<view class="status">{{item.statusText}}</view>
|
||||
</view>
|
||||
<view class="benefit-value">{{item.valueText}}</view>
|
||||
<view class="muted">{{item.thresholdText}}</view>
|
||||
<view class="muted">{{item.scopeText}}</view>
|
||||
<view class="muted">{{item.validText}}</view>
|
||||
<view class="muted">剩余次数:{{item.remainingUses}}</view>
|
||||
</view>
|
||||
|
||||
<view class="section-title">套餐</view>
|
||||
<view wx:if="{{!loading && packages.length === 0}}" class="empty">暂无套餐</view>
|
||||
<view wx:for="{{packages}}" wx:key="packageHoldingId" class="benefit-card">
|
||||
<view class="card-head">
|
||||
<view class="benefit-name">{{item.name}}</view>
|
||||
<view class="status">{{item.statusText}}</view>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text>剩余分钟</text>
|
||||
<text>{{item.remainingMinutes}} / {{item.minutesTotal}}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text>剩余金额</text>
|
||||
<text>{{item.remainingAmountText}} / {{item.totalAmountText}}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text>购买价</text>
|
||||
<text>{{item.priceText}}</text>
|
||||
</view>
|
||||
<view class="muted">{{item.scopeText}}</view>
|
||||
<view class="muted">{{item.validText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -0,0 +1,72 @@
|
||||
.scrollarea {
|
||||
height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 24rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 28rpx 0 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.benefit-card {
|
||||
margin-top: 20rpx;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.card-head,
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.benefit-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #1f6feb;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.benefit-value {
|
||||
margin-top: 16rpx;
|
||||
color: #b42318;
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-top: 16rpx;
|
||||
color: #475467;
|
||||
}
|
||||
|
||||
.muted {
|
||||
margin-top: 8rpx;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 60rpx 0;
|
||||
color: #888888;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 16rpx 0;
|
||||
color: #c73535;
|
||||
}
|
||||
@@ -36,6 +36,10 @@ Page({
|
||||
openRecharge() {
|
||||
wx.navigateTo({ url: '/pages/recharge/index' })
|
||||
},
|
||||
|
||||
openBenefits() {
|
||||
wx.navigateTo({ url: '/pages/benefits/index' })
|
||||
},
|
||||
})
|
||||
|
||||
function formatProfile(profile) {
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
<text>套餐余额</text>
|
||||
<text>{{profile.benefits.packageAmountText}}</text>
|
||||
</view>
|
||||
<button bindtap="openBenefits">查看权益明细</button>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{profile}}" class="card">
|
||||
|
||||
@@ -3,6 +3,7 @@ const {
|
||||
ensureLogin,
|
||||
cents,
|
||||
clientRequestId,
|
||||
requestWechatPayment,
|
||||
} = require('../../utils/api.js')
|
||||
|
||||
function defaultStartAt() {
|
||||
@@ -96,6 +97,35 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
async createWechatPayment() {
|
||||
if (!this.data.order || !this.data.order.orderId) {
|
||||
this.setData({ errorMessage: '请先预占订单' })
|
||||
return
|
||||
}
|
||||
await this.withRequest(async () => {
|
||||
const paymentResponse = await request('/payments', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
orderId: this.data.order.orderId,
|
||||
provider: 'WECHAT',
|
||||
clientRequestId: clientRequestId('miniapp-wxpay'),
|
||||
},
|
||||
})
|
||||
const prepayResponse = await request('/pay/wechat/prepay', {
|
||||
method: 'POST',
|
||||
data: { paymentId: paymentResponse.data.paymentId },
|
||||
})
|
||||
await requestWechatPayment(prepayResponse.data)
|
||||
this.setData({
|
||||
payment: {
|
||||
...paymentResponse.data,
|
||||
amountText: cents(paymentResponse.data.amountCents),
|
||||
},
|
||||
successMessage: '微信支付已提交,请稍后查看订单状态',
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async completeTestPayment() {
|
||||
if (!this.data.payment || !this.data.payment.paymentId) {
|
||||
this.setData({ errorMessage: '请先创建测试支付单' })
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<view class="action-grid">
|
||||
<button loading="{{loading}}" bindtap="quote">报价</button>
|
||||
<button loading="{{loading}}" bindtap="reserve">预占</button>
|
||||
<button loading="{{loading}}" bindtap="createWechatPayment">微信支付</button>
|
||||
<button loading="{{loading}}" bindtap="createTestPayment">测试支付</button>
|
||||
<button loading="{{loading}}" bindtap="completeTestPayment">完成测试支付</button>
|
||||
</view>
|
||||
|
||||
@@ -64,6 +64,24 @@ function clientRequestId(prefix) {
|
||||
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||
}
|
||||
|
||||
function requestWechatPayment(params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!params || !params.timeStamp || !params.nonceStr || !params.package || !params.paySign) {
|
||||
reject(new Error('微信支付参数不完整'))
|
||||
return
|
||||
}
|
||||
wx.requestPayment({
|
||||
timeStamp: String(params.timeStamp),
|
||||
nonceStr: params.nonceStr,
|
||||
package: params.package,
|
||||
signType: params.signType || 'RSA',
|
||||
paySign: params.paySign,
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
request,
|
||||
login,
|
||||
@@ -71,4 +89,5 @@ module.exports = {
|
||||
clearSession,
|
||||
cents,
|
||||
clientRequestId,
|
||||
requestWechatPayment,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user