Files
2026-06-29 10:54:33 +08:00

340 lines
9.5 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>