初始化

This commit is contained in:
leiking
2026-06-29 10:54:33 +08:00
parent 761cee968e
commit 4983006317
156 changed files with 25687 additions and 0 deletions
+339
View File
@@ -0,0 +1,339 @@
<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>