feat(M08-A): 接入顾客端订单查询
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
"pages/index/index",
|
||||
"pages/store/detail",
|
||||
"pages/room/detail",
|
||||
"pages/orders/list",
|
||||
"pages/orders/detail",
|
||||
"pages/logs/logs"
|
||||
],
|
||||
"window": {
|
||||
|
||||
@@ -65,6 +65,10 @@ Page({
|
||||
wx.navigateTo({ url: `/pages/store/detail?storeId=${encodeURIComponent(storeId)}` })
|
||||
},
|
||||
|
||||
openOrders() {
|
||||
wx.navigateTo({ url: '/pages/orders/list' })
|
||||
},
|
||||
|
||||
async resolveScene(code, sourceType) {
|
||||
this.setData({ loading: true, errorMessage: '' })
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<scroll-view class="scrollarea" scroll-y type="list">
|
||||
<view class="container">
|
||||
<view class="title">选择门店</view>
|
||||
<button bindtap="openOrders">我的订单</button>
|
||||
<button loading="{{locating}}" bindtap="locateNearby">定位附近门店</button>
|
||||
<view class="city-search">
|
||||
<input value="{{city}}" placeholder="拒绝定位时输入城市" bindinput="onCityInput" />
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
const { request, ensureLogin, cents } = require('../../utils/api.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
orderId: '',
|
||||
loading: false,
|
||||
errorMessage: '',
|
||||
successMessage: '',
|
||||
order: null,
|
||||
history: [],
|
||||
cancellation: null,
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this.setData({ orderId: options.orderId || '' })
|
||||
if (options.orderId) this.loadOrder(options.orderId)
|
||||
},
|
||||
|
||||
async loadOrder(orderId = this.data.orderId) {
|
||||
const targetOrderId = typeof orderId === 'string' ? orderId : this.data.orderId
|
||||
this.setData({ loading: true, errorMessage: '', successMessage: '' })
|
||||
try {
|
||||
await ensureLogin()
|
||||
const [orderResponse, historyResponse] = await Promise.all([
|
||||
request(`/orders/${encodeURIComponent(targetOrderId)}`),
|
||||
request(`/orders/${encodeURIComponent(targetOrderId)}/history`),
|
||||
])
|
||||
this.setData({
|
||||
order: formatOrder(orderResponse.data),
|
||||
history: (historyResponse.data || []).map((item) => ({
|
||||
...item,
|
||||
createdText: formatTime(item.createdAt),
|
||||
})),
|
||||
})
|
||||
} catch (error) {
|
||||
this.setData({ errorMessage: error.message || '订单加载失败' })
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
|
||||
async loadCancellationQuote() {
|
||||
if (!this.data.orderId) return
|
||||
await this.withOrderRequest(async () => {
|
||||
const response = await request(`/orders/${encodeURIComponent(this.data.orderId)}/cancellation-quote`)
|
||||
this.setData({
|
||||
cancellation: {
|
||||
...response.data,
|
||||
feeText: cents(response.data.feeCents),
|
||||
refundableText: cents(response.data.refundableCents),
|
||||
},
|
||||
successMessage: '取消测算已更新',
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async cancelOrder() {
|
||||
if (!this.data.orderId) return
|
||||
await this.withOrderRequest(async () => {
|
||||
await request(`/orders/${encodeURIComponent(this.data.orderId)}/cancel`, {
|
||||
method: 'POST',
|
||||
data: { reason: '用户小程序取消' },
|
||||
})
|
||||
this.setData({ successMessage: '订单已提交取消' })
|
||||
await this.loadOrder(this.data.orderId)
|
||||
})
|
||||
},
|
||||
|
||||
async withOrderRequest(work) {
|
||||
this.setData({ loading: true, errorMessage: '', successMessage: '' })
|
||||
try {
|
||||
await ensureLogin()
|
||||
await work()
|
||||
} catch (error) {
|
||||
this.setData({ errorMessage: error.message || '订单操作失败' })
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function formatOrder(order) {
|
||||
return {
|
||||
...order,
|
||||
totalText: cents(order.totalAmountCents),
|
||||
paidText: cents(order.paidAmountCents),
|
||||
startText: formatTime(order.startAt),
|
||||
endText: formatTime(order.endAt),
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(value) {
|
||||
const date = new Date(value)
|
||||
const pad = (input) => String(input).padStart(2, '0')
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "订单详情"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<scroll-view class="scrollarea" scroll-y type="list">
|
||||
<view class="page">
|
||||
<view class="title">订单详情</view>
|
||||
<view wx:if="{{errorMessage}}" class="error">{{errorMessage}}</view>
|
||||
<view wx:if="{{successMessage}}" class="success">{{successMessage}}</view>
|
||||
|
||||
<view wx:if="{{order}}" class="card">
|
||||
<view class="order-title">{{order.storeName}} · {{order.roomName}}</view>
|
||||
<view class="muted">订单号:{{order.orderNo}}</view>
|
||||
<view class="muted">房间:{{order.roomNo}}</view>
|
||||
<view class="muted">开始:{{order.startText}}</view>
|
||||
<view class="muted">结束:{{order.endText}}</view>
|
||||
<view class="row">
|
||||
<text class="status">{{order.status}}</text>
|
||||
<text class="amount">{{order.totalText}}</text>
|
||||
</view>
|
||||
<view wx:if="{{order.latestPayment}}" class="muted">
|
||||
最近支付:{{order.latestPayment.provider}} / {{order.latestPayment.status}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-grid">
|
||||
<button loading="{{loading}}" bindtap="loadOrder">刷新</button>
|
||||
<button loading="{{loading}}" bindtap="loadCancellationQuote">取消测算</button>
|
||||
<button loading="{{loading}}" bindtap="cancelOrder">取消订单</button>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{cancellation}}" class="card">
|
||||
<view class="section-title">取消测算</view>
|
||||
<view class="muted">是否允许:{{cancellation.allowed ? '允许' : '不允许'}}</view>
|
||||
<view class="muted">取消费用:{{cancellation.feeText}}</view>
|
||||
<view class="muted">预计退回:{{cancellation.refundableText}}</view>
|
||||
<view class="muted">免费取消分钟线:{{cancellation.cutoffMinutes}}</view>
|
||||
</view>
|
||||
|
||||
<view class="section-title">状态历史</view>
|
||||
<view wx:for="{{history}}" wx:key="id" class="history-item">
|
||||
<view>{{item.action}}:{{item.fromStatus || '-'}} → {{item.toStatus}}</view>
|
||||
<view class="muted">{{item.createdText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -0,0 +1,70 @@
|
||||
.scrollarea {
|
||||
height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 24rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card,
|
||||
.history-item {
|
||||
margin-top: 20rpx;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.order-title,
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.muted {
|
||||
margin-top: 8rpx;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #1f6feb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.amount {
|
||||
color: #b42318;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.action-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.action-grid button {
|
||||
flex: 1 1 220rpx;
|
||||
}
|
||||
|
||||
.success {
|
||||
margin: 16rpx 0;
|
||||
color: #17823b;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 16rpx 0;
|
||||
color: #c73535;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
const { request, ensureLogin, cents } = require('../../utils/api.js')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loading: false,
|
||||
errorMessage: '',
|
||||
orders: [],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadOrders()
|
||||
},
|
||||
|
||||
async loadOrders() {
|
||||
this.setData({ loading: true, errorMessage: '' })
|
||||
try {
|
||||
await ensureLogin()
|
||||
const response = await request(`/orders?page=${this.data.page}&pageSize=${this.data.pageSize}`)
|
||||
this.setData({
|
||||
orders: (response.data.items || []).map(formatOrder),
|
||||
total: response.data.total || 0,
|
||||
})
|
||||
} catch (error) {
|
||||
this.setData({ errorMessage: error.message || '订单加载失败' })
|
||||
} finally {
|
||||
this.setData({ loading: false })
|
||||
}
|
||||
},
|
||||
|
||||
openOrder(event) {
|
||||
const orderId = event.currentTarget.dataset.orderId
|
||||
if (!orderId) return
|
||||
wx.navigateTo({ url: `/pages/orders/detail?orderId=${encodeURIComponent(orderId)}` })
|
||||
},
|
||||
})
|
||||
|
||||
function formatOrder(order) {
|
||||
return {
|
||||
...order,
|
||||
totalText: cents(order.totalAmountCents),
|
||||
paidText: cents(order.paidAmountCents),
|
||||
timeText: `${formatTime(order.startAt)} 至 ${formatTime(order.endAt)}`,
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(value) {
|
||||
const date = new Date(value)
|
||||
const pad = (input) => String(input).padStart(2, '0')
|
||||
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的订单"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<scroll-view class="scrollarea" scroll-y type="list">
|
||||
<view class="page">
|
||||
<view class="title">我的订单</view>
|
||||
<button loading="{{loading}}" bindtap="loadOrders">刷新</button>
|
||||
<view wx:if="{{errorMessage}}" class="error">{{errorMessage}}</view>
|
||||
<view wx:if="{{!loading && orders.length === 0}}" class="empty">暂无订单</view>
|
||||
<view wx:for="{{orders}}" wx:key="id" class="order-card" bindtap="openOrder" data-order-id="{{item.id}}">
|
||||
<view class="order-title">{{item.storeName}} · {{item.roomName}}</view>
|
||||
<view class="muted">{{item.timeText}}</view>
|
||||
<view class="muted">订单号:{{item.orderNo}}</view>
|
||||
<view class="row">
|
||||
<text class="status">{{item.status}}</text>
|
||||
<text class="amount">{{item.totalText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -0,0 +1,62 @@
|
||||
.scrollarea {
|
||||
height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 24rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
margin-top: 20rpx;
|
||||
padding: 28rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.order-card:active {
|
||||
background: #eef4ff;
|
||||
}
|
||||
|
||||
.order-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.muted {
|
||||
margin-top: 8rpx;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #1f6feb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.amount {
|
||||
color: #b42318;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 20rpx 0;
|
||||
color: #c73535;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 80rpx 0;
|
||||
color: #888888;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -113,6 +113,12 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
openOrder(event) {
|
||||
const orderId = event.currentTarget.dataset.orderId
|
||||
if (!orderId) return
|
||||
wx.navigateTo({ url: `/pages/orders/detail?orderId=${encodeURIComponent(orderId)}` })
|
||||
},
|
||||
|
||||
pricingPayload() {
|
||||
const start = defaultStartAt()
|
||||
return {
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<view class="result-title">订单</view>
|
||||
<view>订单编号:{{order.orderId}}</view>
|
||||
<view wx:if="{{order.reservationId}}">预占编号:{{order.reservationId}}</view>
|
||||
<button size="mini" data-order-id="{{order.orderId}}" bindtap="openOrder">查看订单</button>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{payment}}" class="result-card">
|
||||
|
||||
Reference in New Issue
Block a user