340 lines
9.5 KiB
Vue
340 lines
9.5 KiB
Vue
<template>
|
||
<view class="container">
|
||
<view class="card block">
|
||
<view class="row between">
|
||
<view class="name">{{ vm.project.name }}</view>
|
||
<view class="price">¥{{ vm.amount }}</view>
|
||
</view>
|
||
<view class="muted meta" v-if="vm.type === 'booking'">
|
||
{{ vm.date }} {{ vm.slot }} · {{ vm.techName }}
|
||
</view>
|
||
<view class="muted meta" v-else>{{ vm.planLabel }} · {{ vm.validText }}</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="title">订单信息</view>
|
||
<view class="line row between">
|
||
<view class="muted">类型</view>
|
||
<view>{{ vm.type === 'booking' ? '预约订单' : '购买卡券' }}</view>
|
||
</view>
|
||
<view class="line row between">
|
||
<view class="muted">单价</view>
|
||
<view>¥{{ vm.amount }}</view>
|
||
</view>
|
||
<view class="line row between">
|
||
<view class="muted">数量</view>
|
||
<view>1</view>
|
||
</view>
|
||
<view class="line row between">
|
||
<view class="muted">合计</view>
|
||
<view class="sum">¥{{ vm.amount }}</view>
|
||
</view>
|
||
<view class="line row between" v-if="vm.note">
|
||
<view class="muted">备注</view>
|
||
<view class="note">{{ vm.note }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="title">规则摘要</view>
|
||
<view class="muted rline" v-if="vm.type === 'booking'">改约:可在“我的预约”中发起改约(原型演示)。</view>
|
||
<view class="muted rline" v-if="vm.type === 'booking'">取消:支持取消预约,取消后订单状态变为已取消。</view>
|
||
<view class="muted rline" v-if="vm.type === 'booking'">爽约:商用版可按门店策略收取爽约金(此处占位)。</view>
|
||
<view class="muted rline" v-if="vm.type !== 'booking'">有效期:以卡券类型展示为准,过期后不可核销(原型演示)。</view>
|
||
<view class="muted rline" v-if="vm.type !== 'booking'">核销:到店出示核销码,核销后自动扣次。</view>
|
||
</view>
|
||
|
||
<view class="card block" v-if="vm.type !== 'booking'">
|
||
<view class="title">订单类型</view>
|
||
<view class="plans">
|
||
<view
|
||
v-for="p in plans"
|
||
:key="p.key"
|
||
class="plan"
|
||
:class="{ on: p.key === vm.planKey }"
|
||
@tap="selectPlan(p.key)"
|
||
>
|
||
<view class="p1">{{ p.label }}</view>
|
||
<view class="muted p2">{{ p.validText }}</view>
|
||
</view>
|
||
</view>
|
||
<view class="muted hint">购买后可在“我的卡券/次卡”里查看,预约后到店核销。</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="title">支付方式</view>
|
||
<view class="pay row between">
|
||
<view class="row">
|
||
<view class="p-icon">W</view>
|
||
<view class="p-text">微信支付(原型模拟)</view>
|
||
</view>
|
||
<view class="tag">默认</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="btn btn-primary submit" @tap="mockPay">去支付</view>
|
||
<view class="btn btn-ghost submit2" @tap="saveUnpaid">先保存为待付款</view>
|
||
<AiFloat />
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import AiFloat from '@/components/AiFloat.vue'
|
||
import { projects } from '@/common/mockData'
|
||
|
||
const fallbackProjects = [
|
||
{
|
||
id: 'p1',
|
||
categoryId: 'c1',
|
||
name: '水氧净透体验',
|
||
price: 99,
|
||
originPrice: 199,
|
||
durationMin: 60,
|
||
fitFor: '初次体验、暗沉、出油',
|
||
taboo: '近期激光/微针术后 7 天内不建议',
|
||
cover: '',
|
||
desc: '轻盈水氧 + 净透护理,适合快速提升肤感与通透度。'
|
||
}
|
||
]
|
||
|
||
function safeJsonParse(s) {
|
||
try {
|
||
return JSON.parse(s)
|
||
} catch (e) {
|
||
return null
|
||
}
|
||
}
|
||
|
||
export default {
|
||
components: { AiFloat },
|
||
data() {
|
||
const safeProjects = Array.isArray(projects) && projects.length ? projects : fallbackProjects
|
||
const initProject = safeProjects[0]
|
||
const initPlan = { key: 'single', label: '单次券', validText: '有效期 30 天', times: 1, mul: 1 }
|
||
return {
|
||
safeProjects,
|
||
vm: {
|
||
type: 'coupon',
|
||
project: initProject,
|
||
note: '',
|
||
planKey: initPlan.key,
|
||
planLabel: initPlan.label,
|
||
validText: initPlan.validText,
|
||
times: initPlan.times,
|
||
amount: initProject ? Math.round(initProject.price * initPlan.mul) : 99
|
||
},
|
||
plans: [
|
||
{ key: 'single', label: '单次券', validText: '有效期 30 天', times: 1, mul: 1 },
|
||
{ key: 'times5', label: '次卡 5 次', validText: '有效期 180 天', times: 5, mul: 4.2 },
|
||
{ key: 'package', label: '套餐', validText: '有效期 90 天', times: 3, mul: 2.6 }
|
||
]
|
||
}
|
||
},
|
||
onLoad(query) {
|
||
try {
|
||
const payload = query.payload ? safeJsonParse(decodeURIComponent(query.payload)) : null
|
||
const safeProjects = Array.isArray(this.safeProjects) && this.safeProjects.length ? this.safeProjects : fallbackProjects
|
||
|
||
if (payload && payload.type === 'booking') {
|
||
const p = safeProjects.find((x) => x.id === payload.projectId) || safeProjects[0]
|
||
const amount = p ? p.price : 99
|
||
this.vm = {
|
||
type: 'booking',
|
||
project: p || fallbackProjects[0],
|
||
amount,
|
||
planKey: '',
|
||
planLabel: '预约订单',
|
||
validText: '',
|
||
times: 0,
|
||
date: payload.date || '',
|
||
slot: payload.slot || '',
|
||
techId: payload.techId || '',
|
||
techName: payload.techName || '自动分配',
|
||
note: payload.note || ''
|
||
}
|
||
return
|
||
}
|
||
|
||
const projectId = query.projectId || ''
|
||
const p = safeProjects.find((x) => x.id === projectId) || safeProjects.find((x) => x.categoryId === 'c5') || safeProjects[0]
|
||
const basePlanKey = p && p.categoryId === 'c5' ? 'times5' : 'single'
|
||
const plan = this.plans.find((x) => x.key === basePlanKey) || this.plans[0]
|
||
const price = p ? p.price : 99
|
||
const amount = Math.round(price * plan.mul)
|
||
this.vm = {
|
||
type: 'coupon',
|
||
project: p || fallbackProjects[0],
|
||
note: '',
|
||
planKey: plan.key,
|
||
planLabel: plan.label,
|
||
validText: plan.validText,
|
||
times: plan.times,
|
||
amount
|
||
}
|
||
} catch (e) {
|
||
const p = (Array.isArray(this.safeProjects) && this.safeProjects[0]) || fallbackProjects[0]
|
||
const plan = this.plans[0]
|
||
this.vm = {
|
||
type: 'coupon',
|
||
project: p,
|
||
note: '',
|
||
planKey: plan.key,
|
||
planLabel: plan.label,
|
||
validText: plan.validText,
|
||
times: plan.times,
|
||
amount: Math.round((p ? p.price : 99) * plan.mul)
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
selectPlan(key) {
|
||
const p = this.plans.find((x) => x.key === key) || this.plans[0]
|
||
const amount = Math.round(this.vm.project.price * p.mul)
|
||
this.vm = {
|
||
...this.vm,
|
||
planKey: p.key,
|
||
planLabel: p.label,
|
||
validText: p.validText,
|
||
times: p.times,
|
||
amount
|
||
}
|
||
},
|
||
buildOrder(status) {
|
||
const now = Date.now()
|
||
const id = `ord_demo_${now}`
|
||
const base = {
|
||
id,
|
||
createdAt: now,
|
||
status,
|
||
amount: this.vm.amount,
|
||
projectId: this.vm.project.id,
|
||
projectName: this.vm.project.name,
|
||
durationMin: this.vm.project.durationMin
|
||
}
|
||
if (this.vm.type === 'booking') {
|
||
return {
|
||
...base,
|
||
orderType: 'booking',
|
||
appointmentDate: this.vm.date,
|
||
appointmentSlot: this.vm.slot,
|
||
technicianName: this.vm.techName,
|
||
note: this.vm.note || '',
|
||
verifyCode: `VC${now}`
|
||
}
|
||
}
|
||
return {
|
||
...base,
|
||
orderType: 'coupon',
|
||
couponTitle: this.vm.project.name,
|
||
couponPlanKey: this.vm.planKey,
|
||
couponPlanLabel: this.vm.planLabel,
|
||
validText: this.vm.validText,
|
||
remainingTimes: this.vm.times || 1,
|
||
verifyCode: `VC${now}`
|
||
}
|
||
},
|
||
mockPay() {
|
||
const order = this.buildOrder('待核销')
|
||
const payload = encodeURIComponent(JSON.stringify(order))
|
||
uni.redirectTo({ url: `/pages/verify/code?payload=${payload}` })
|
||
},
|
||
saveUnpaid() {
|
||
const order = this.buildOrder('待付款')
|
||
const payload = encodeURIComponent(JSON.stringify(order))
|
||
uni.redirectTo({ url: `/pages/orders/detail?payload=${payload}` })
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.block {
|
||
padding: 22rpx;
|
||
margin-bottom: 18rpx;
|
||
}
|
||
.name {
|
||
font-size: 34rpx;
|
||
font-weight: 950;
|
||
max-width: 520rpx;
|
||
}
|
||
.price {
|
||
font-size: 32rpx;
|
||
font-weight: 950;
|
||
}
|
||
.meta {
|
||
margin-top: 10rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
.title {
|
||
font-weight: 950;
|
||
font-size: 30rpx;
|
||
margin-bottom: 14rpx;
|
||
}
|
||
.line {
|
||
padding: 12rpx 0;
|
||
}
|
||
.sum {
|
||
font-weight: 950;
|
||
}
|
||
.note {
|
||
max-width: 520rpx;
|
||
text-align: right;
|
||
}
|
||
.plans {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 12rpx;
|
||
}
|
||
.plan {
|
||
padding: 14rpx 12rpx;
|
||
border-radius: 18rpx;
|
||
border: 1rpx solid rgba(17, 24, 39, 0.08);
|
||
background: rgba(17, 24, 39, 0.03);
|
||
}
|
||
.on {
|
||
background: rgba(59, 130, 246, 0.12);
|
||
border-color: rgba(59, 130, 246, 0.35);
|
||
}
|
||
.p1 {
|
||
font-size: 26rpx;
|
||
font-weight: 950;
|
||
}
|
||
.p2 {
|
||
margin-top: 8rpx;
|
||
font-size: 22rpx;
|
||
}
|
||
.hint {
|
||
margin-top: 14rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
.rline {
|
||
margin-top: 10rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
.pay {
|
||
padding: 14rpx 0;
|
||
}
|
||
.p-icon {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 16rpx;
|
||
background: rgba(16, 185, 129, 0.16);
|
||
color: #059669;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-weight: 900;
|
||
margin-right: 12rpx;
|
||
}
|
||
.p-text {
|
||
font-weight: 800;
|
||
}
|
||
.submit {
|
||
margin-top: 10rpx;
|
||
}
|
||
.submit2 {
|
||
margin-top: 16rpx;
|
||
}
|
||
</style>
|
||
|