feat(M08-A): 接入顾客端选店下单入口
This commit is contained in:
@@ -1,7 +1,32 @@
|
||||
const {
|
||||
request,
|
||||
ensureLogin,
|
||||
cents,
|
||||
clientRequestId,
|
||||
} = require('../../utils/api.js')
|
||||
|
||||
function defaultStartAt() {
|
||||
const date = new Date(Date.now() + 30 * 60 * 1000)
|
||||
date.setSeconds(0, 0)
|
||||
return date
|
||||
}
|
||||
|
||||
function isoMinutesAfter(date, minutes) {
|
||||
return new Date(date.getTime() + minutes * 60 * 1000).toISOString()
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
storeId: '',
|
||||
roomId: '',
|
||||
loading: false,
|
||||
errorMessage: '',
|
||||
successMessage: '',
|
||||
durationMinutes: 120,
|
||||
pricingMode: 'HOURLY',
|
||||
quote: null,
|
||||
order: null,
|
||||
payment: null,
|
||||
},
|
||||
onLoad(options) {
|
||||
this.setData({
|
||||
@@ -9,4 +34,104 @@ Page({
|
||||
roomId: options.roomId || '',
|
||||
})
|
||||
},
|
||||
|
||||
setDuration(event) {
|
||||
this.setData({
|
||||
durationMinutes: Number(event.currentTarget.dataset.minutes),
|
||||
quote: null,
|
||||
order: null,
|
||||
payment: null,
|
||||
errorMessage: '',
|
||||
successMessage: '',
|
||||
})
|
||||
},
|
||||
|
||||
async quote() {
|
||||
await this.withRequest(async () => {
|
||||
const payload = this.pricingPayload()
|
||||
const response = await request('/pricing/quote', { method: 'POST', data: payload })
|
||||
this.setData({
|
||||
quote: {
|
||||
...response.data,
|
||||
totalText: cents(response.data.totalCents),
|
||||
depositText: cents(response.data.depositCents),
|
||||
},
|
||||
successMessage: '报价已生成',
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async reserve() {
|
||||
await this.withRequest(async () => {
|
||||
const payload = this.pricingPayload()
|
||||
const response = await request('/orders/reserve', { method: 'POST', data: payload })
|
||||
this.setData({
|
||||
order: response.data,
|
||||
successMessage: '时段已预占,请继续支付',
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async createTestPayment() {
|
||||
if (!this.data.order || !this.data.order.orderId) {
|
||||
this.setData({ errorMessage: '请先预占订单' })
|
||||
return
|
||||
}
|
||||
await this.withRequest(async () => {
|
||||
const response = await request('/payments', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
orderId: this.data.order.orderId,
|
||||
provider: 'TEST',
|
||||
clientRequestId: clientRequestId('miniapp-pay'),
|
||||
},
|
||||
})
|
||||
this.setData({
|
||||
payment: {
|
||||
...response.data,
|
||||
amountText: cents(response.data.amountCents),
|
||||
},
|
||||
successMessage: '测试支付单已创建',
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async completeTestPayment() {
|
||||
if (!this.data.payment || !this.data.payment.paymentId) {
|
||||
this.setData({ errorMessage: '请先创建测试支付单' })
|
||||
return
|
||||
}
|
||||
await this.withRequest(async () => {
|
||||
await request(`/payments/${encodeURIComponent(this.data.payment.paymentId)}/test-complete`, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
callbackId: clientRequestId('miniapp-callback'),
|
||||
amountCents: this.data.payment.amountCents,
|
||||
},
|
||||
})
|
||||
this.setData({ successMessage: '测试支付已完成,生产环境将切换微信支付' })
|
||||
})
|
||||
},
|
||||
|
||||
pricingPayload() {
|
||||
const start = defaultStartAt()
|
||||
return {
|
||||
roomId: this.data.roomId,
|
||||
startAt: start.toISOString(),
|
||||
endAt: isoMinutesAfter(start, this.data.durationMinutes),
|
||||
pricingMode: this.data.pricingMode,
|
||||
}
|
||||
},
|
||||
|
||||
async withRequest(work) {
|
||||
this.setData({ loading: true, errorMessage: '', successMessage: '' })
|
||||
try {
|
||||
await ensureLogin()
|
||||
await work()
|
||||
} catch (error) {
|
||||
this.setData({ errorMessage: error.message || '操作失败' })
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -2,5 +2,41 @@
|
||||
<view class="title">房间详情</view>
|
||||
<view>门店编号:{{storeId}}</view>
|
||||
<view>房间编号:{{roomId}}</view>
|
||||
<view class="notice">扫码或 NFC 只打开此页面,开门权限仍由有效订单单独校验。</view>
|
||||
<view class="section-title">选择时长</view>
|
||||
<view class="duration-row">
|
||||
<button size="mini" class="{{durationMinutes === 60 ? 'selected' : ''}}" data-minutes="60" bindtap="setDuration">1小时</button>
|
||||
<button size="mini" class="{{durationMinutes === 120 ? 'selected' : ''}}" data-minutes="120" bindtap="setDuration">2小时</button>
|
||||
<button size="mini" class="{{durationMinutes === 180 ? 'selected' : ''}}" data-minutes="180" bindtap="setDuration">3小时</button>
|
||||
</view>
|
||||
|
||||
<view class="action-grid">
|
||||
<button loading="{{loading}}" bindtap="quote">报价</button>
|
||||
<button loading="{{loading}}" bindtap="reserve">预占</button>
|
||||
<button loading="{{loading}}" bindtap="createTestPayment">测试支付</button>
|
||||
<button loading="{{loading}}" bindtap="completeTestPayment">完成测试支付</button>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{errorMessage}}" class="error">{{errorMessage}}</view>
|
||||
<view wx:if="{{successMessage}}" class="success">{{successMessage}}</view>
|
||||
|
||||
<view wx:if="{{quote}}" class="result-card">
|
||||
<view class="result-title">报价</view>
|
||||
<view>总价:{{quote.totalText}}</view>
|
||||
<view>押金:{{quote.depositText}}</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{order}}" class="result-card">
|
||||
<view class="result-title">订单</view>
|
||||
<view>订单编号:{{order.orderId}}</view>
|
||||
<view wx:if="{{order.reservationId}}">预占编号:{{order.reservationId}}</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{payment}}" class="result-card">
|
||||
<view class="result-title">支付</view>
|
||||
<view>支付编号:{{payment.paymentId}}</view>
|
||||
<view>金额:{{payment.amountText}}</view>
|
||||
<view>状态:{{payment.status}}</view>
|
||||
</view>
|
||||
|
||||
<view class="notice">扫码或 NFC 只打开此页面,开门权限仍由有效订单单独校验;生产支付以微信预支付为准。</view>
|
||||
</view>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
.page {
|
||||
padding: 32rpx;
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.title {
|
||||
@@ -12,3 +14,49 @@
|
||||
margin-top: 32rpx;
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 32rpx 0 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.duration-row,
|
||||
.action-grid {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.action-grid button {
|
||||
flex: 1 1 280rpx;
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: #ffffff;
|
||||
background: #1f6feb;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
margin-top: 20rpx;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.success {
|
||||
margin: 16rpx 0;
|
||||
color: #17823b;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 16rpx 0;
|
||||
color: #c73535;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user