256 lines
6.7 KiB
Vue
256 lines
6.7 KiB
Vue
<template>
|
||
<view class="container">
|
||
<view class="card head">
|
||
<view class="row between">
|
||
<view
|
||
class="st"
|
||
:class="{
|
||
w: o.status === '待付款',
|
||
g: o.status === '已完成',
|
||
d: o.status === '已取消',
|
||
p: o.status !== '待付款' && o.status !== '已完成' && o.status !== '已取消'
|
||
}"
|
||
>
|
||
{{ o.status }}
|
||
</view>
|
||
<view class="muted">订单号 {{ o.id }}</view>
|
||
</view>
|
||
<view class="name">{{ o.projectName }}</view>
|
||
<view class="muted meta">{{ typeLabel(o.orderType) }} · ¥{{ o.amount }}</view>
|
||
<view class="muted meta" v-if="o.orderType === 'booking'">
|
||
{{ o.appointmentDate }} {{ o.appointmentSlot }} · {{ o.technicianName }}
|
||
</view>
|
||
<view class="muted meta" v-else>剩余次数:{{ o.remainingTimes }}</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="title">核销码</view>
|
||
<view class="code row between">
|
||
<view class="c">{{ o.verifyCode }}</view>
|
||
<view class="tag" @tap="copy(o.verifyCode)">复制</view>
|
||
</view>
|
||
<view class="muted hint">到店出示核销码,由门店扫码/输码核销(此处为原型演示)。</view>
|
||
<view class="btn btn-ghost more" @tap="openCode">查看大码</view>
|
||
</view>
|
||
|
||
<view class="card block" v-if="o.note">
|
||
<view class="title">备注</view>
|
||
<view class="muted">{{ o.note }}</view>
|
||
</view>
|
||
|
||
<view class="actions">
|
||
<view v-if="o.status === '待付款'" class="btn btn-primary" @tap="pay">模拟支付</view>
|
||
<view v-if="o.status === '待核销'" class="btn btn-primary" @tap="verify">模拟核销</view>
|
||
<view v-if="o.orderType === 'booking' && o.status === '待核销'" class="btn btn-ghost" @tap="reschedule">改约</view>
|
||
<view v-if="o.status !== '已取消' && o.status !== '已完成'" class="btn btn-ghost" @tap="cancel">取消订单</view>
|
||
</view>
|
||
|
||
<AiFloat />
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import AiFloat from '@/components/AiFloat.vue'
|
||
import { demoOrders } from '@/common/demoOrders'
|
||
|
||
function safeJsonParse(s) {
|
||
try {
|
||
return JSON.parse(s)
|
||
} catch (e) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
const __DBG_URL = 'http://127.0.0.1:7777/event'
|
||
|
||
export default {
|
||
components: { AiFloat },
|
||
data() {
|
||
return {
|
||
order: null
|
||
}
|
||
},
|
||
onLoad(query) {
|
||
//#region debug-point orders-detail-load
|
||
try {
|
||
uni.request({
|
||
url: __DBG_URL,
|
||
method: 'POST',
|
||
timeout: 2000,
|
||
data: {
|
||
sessionId: 'orders-detail-blank',
|
||
runId: 'pre-fix',
|
||
hypothesisId: 'H2',
|
||
msg: 'orders/detail onLoad',
|
||
queryKeys: query ? Object.keys(query) : [],
|
||
payloadLen: query && query.payload ? String(query.payload).length : 0
|
||
}
|
||
})
|
||
} catch (e) {}
|
||
//#endregion debug-point orders-detail-load
|
||
|
||
const raw = query.payload ? safeJsonParse(decodeURIComponent(query.payload)) : null
|
||
if (raw) this.order = raw
|
||
},
|
||
computed: {
|
||
o() {
|
||
return this.order || demoOrders[0] || {}
|
||
}
|
||
},
|
||
onReady() {
|
||
//#region debug-point orders-detail-ready
|
||
try {
|
||
uni.request({
|
||
url: __DBG_URL,
|
||
method: 'POST',
|
||
timeout: 2000,
|
||
data: {
|
||
sessionId: 'orders-detail-blank',
|
||
runId: 'pre-fix',
|
||
hypothesisId: 'H2',
|
||
msg: 'orders/detail onReady',
|
||
hasOrder: !!this.order,
|
||
oKeys: this.o ? Object.keys(this.o) : []
|
||
}
|
||
})
|
||
} catch (e) {}
|
||
//#endregion debug-point orders-detail-ready
|
||
},
|
||
onError(err) {
|
||
//#region debug-point orders-detail-error
|
||
try {
|
||
uni.request({
|
||
url: __DBG_URL,
|
||
method: 'POST',
|
||
timeout: 2000,
|
||
data: {
|
||
sessionId: 'orders-detail-blank',
|
||
runId: 'pre-fix',
|
||
hypothesisId: 'H2',
|
||
msg: 'orders/detail onError',
|
||
err: String(err || '')
|
||
}
|
||
})
|
||
} catch (e) {}
|
||
//#endregion debug-point orders-detail-error
|
||
},
|
||
methods: {
|
||
typeLabel(t) {
|
||
return t === 'booking' ? '预约订单' : '购买卡券'
|
||
},
|
||
openCode() {
|
||
const payload = encodeURIComponent(JSON.stringify(this.o))
|
||
uni.navigateTo({ url: `/pages/verify/code?payload=${payload}` })
|
||
},
|
||
copy(text) {
|
||
uni.setClipboardData({ data: text })
|
||
},
|
||
pay() {
|
||
this.order = { ...this.o, status: '待核销', paidAt: Date.now() }
|
||
uni.showToast({ title: '支付成功(模拟)', icon: 'none' })
|
||
setTimeout(() => {
|
||
const payload = encodeURIComponent(JSON.stringify(this.o))
|
||
uni.navigateTo({ url: `/pages/verify/code?payload=${payload}` })
|
||
}, 200)
|
||
},
|
||
verify() {
|
||
if (this.o.orderType === 'coupon') {
|
||
const left = Math.max(0, (this.o.remainingTimes || 0) - 1)
|
||
this.order = { ...this.o, remainingTimes: left, status: left === 0 ? '已完成' : '待核销' }
|
||
uni.showToast({ title: left === 0 ? '已核销完成' : '核销成功,已扣次', icon: 'none' })
|
||
return
|
||
}
|
||
this.order = { ...this.o, status: '已完成', verifiedAt: Date.now() }
|
||
uni.showToast({ title: '已核销完成(模拟)', icon: 'none' })
|
||
},
|
||
reschedule() {
|
||
uni.navigateTo({ url: `/pages/booking/create?projectId=${this.o.projectId}` })
|
||
},
|
||
cancel() {
|
||
uni.showModal({
|
||
title: '确认取消',
|
||
content: '原型演示:取消后订单状态将变为已取消。',
|
||
success: (res) => {
|
||
if (!res.confirm) return
|
||
this.order = { ...this.o, status: '已取消', canceledAt: Date.now() }
|
||
}
|
||
})
|
||
},
|
||
goMember() {
|
||
uni.switchTab({ url: '/pages/member/index' })
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.head {
|
||
padding: 24rpx;
|
||
}
|
||
.st {
|
||
padding: 10rpx 14rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 900;
|
||
}
|
||
.w {
|
||
background: rgba(245, 158, 11, 0.16);
|
||
color: #b45309;
|
||
}
|
||
.p {
|
||
background: rgba(59, 130, 246, 0.16);
|
||
color: #1d4ed8;
|
||
}
|
||
.g {
|
||
background: rgba(16, 185, 129, 0.16);
|
||
color: #047857;
|
||
}
|
||
.d {
|
||
background: rgba(239, 68, 68, 0.12);
|
||
color: #b91c1c;
|
||
}
|
||
.name {
|
||
margin-top: 14rpx;
|
||
font-size: 38rpx;
|
||
font-weight: 950;
|
||
}
|
||
.meta {
|
||
margin-top: 10rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
.block {
|
||
padding: 22rpx;
|
||
margin-top: 18rpx;
|
||
}
|
||
.title {
|
||
font-weight: 950;
|
||
font-size: 30rpx;
|
||
margin-bottom: 14rpx;
|
||
}
|
||
.code {
|
||
padding: 16rpx;
|
||
border-radius: 18rpx;
|
||
background: rgba(17, 24, 39, 0.04);
|
||
border: 1rpx dashed rgba(17, 24, 39, 0.18);
|
||
}
|
||
.c {
|
||
font-size: 36rpx;
|
||
font-weight: 900;
|
||
letter-spacing: 1rpx;
|
||
}
|
||
.hint {
|
||
margin-top: 12rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
.more {
|
||
margin-top: 16rpx;
|
||
}
|
||
.actions {
|
||
margin-top: 24rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
}
|
||
</style>
|
||
|