feat: 我的订单调整

This commit is contained in:
2026-07-31 09:50:31 +08:00
parent 6a9135770b
commit a36183d4d8
4 changed files with 390 additions and 52 deletions

View File

@@ -1,34 +1,47 @@
<template>
<view class="flex h-full min-w-0 flex-col overflow-hidden bg-[#f8fafc]">
<view class="flex h-full min-w-0 flex-col overflow-hidden bg-slate-100">
<scroll-view scroll-y class="min-h-0 min-w-0 flex-1">
<view class="box-border w-full min-w-0 px-[16px] py-[16px]">
<view v-if="orders.length" class="flex min-w-0 flex-col gap-[12px]">
<view v-for="order in orders" :key="order.id || order.orderNo" class="box-border w-full min-w-0 overflow-hidden rounded-[16px] bg-white p-[16px]" @click="emit('open', order.id)">
<view class="flex min-w-0 items-start justify-between gap-[12px]">
<text class="min-w-0 flex-1 break-words text-[16px] font-medium leading-[23px] text-[#171717]">{{ order.productName || '景区产品' }}</text>
<text v-if="orderStatus(order) === 'paid'" class="shrink-0 rounded-[12px] bg-[#ecfdf3] px-[8px] py-[3px] text-[12px] text-[#15803d]">待使用</text>
<text v-else-if="orderStatus(order) === 'refunding'" class="shrink-0 rounded-[12px] bg-[#fff7ed] px-[8px] py-[3px] text-[12px] text-[#c2410c]">退款中</text>
<text v-else-if="orderStatus(order) === 'completed'" class="shrink-0 rounded-[12px] bg-[#eff6ff] px-[8px] py-[3px] text-[12px] text-[#2563eb]">已完成</text>
<text v-else class="shrink-0 rounded-[12px] bg-[#f2f4f7] px-[8px] py-[3px] text-[12px] text-[#667085]">已退款</text>
<view class="box-border w-full min-w-0 p-4">
<view v-if="orders.length" class="flex min-w-0 flex-col gap-3">
<view v-for="order in orders" :key="order.id || order.orderNo" class="box-border w-full min-w-0 rounded-2xl bg-white p-4 text-left shadow-sm" @tap="emit('open', order.id)">
<view class="flex min-w-0 items-start justify-between gap-3">
<view class="min-w-0 flex-1">
<text class="block truncate font-bold text-slate-900">{{ order.productName }}</text>
<view class="mt-1 flex min-w-0 items-center gap-2">
<text class="min-w-0 truncate text-xs text-slate-400">{{ order.orderNo }}</text>
<text v-if="order.insurance" class="shrink-0 rounded-full bg-blue-50 px-2 py-0.5 text-[9px] font-semibold text-blue-600">含保险</text>
</view>
</view>
<text class="inline-flex shrink-0 items-center rounded-full border px-2.5 py-1 text-xs font-semibold" :class="statusMeta(order.status).className">{{ statusMeta(order.status).label }}</text>
</view>
<text class="mt-[12px] block text-[14px] text-[#475467]">{{ order.visitDate || '-' }} {{ order.timeSlot || '全天' }}</text>
<view class="mt-[10px] flex min-w-0 flex-wrap items-center gap-[8px] text-[13px] text-[#667085]">
<text>{{ order.quantity || 0 }}</text>
<text>{{ firstTraveler(order) }}</text>
<text v-if="order.insurance" class="rounded-[4px] bg-[#eff6ff] px-[6px] py-[2px] text-[11px] text-[#2563eb]">含保险</text>
<view class="mt-3 rounded-xl bg-slate-50 p-3 text-xs text-slate-500">
<view class="flex items-center justify-between">
<text>使用日期</text>
<text class="font-semibold text-slate-700">{{ order.visitDate }}</text>
</view>
<view class="mt-2 flex items-center justify-between">
<text>数量</text>
<text class="font-semibold text-slate-700">{{ order.quantity }} </text>
</view>
</view>
<view class="mt-[14px] flex min-w-0 items-center justify-between gap-[10px] border-t border-[#f2f4f7] pt-[12px]">
<text class="min-w-0 flex-1 truncate text-[12px] text-[#98a2b3]">订单号 {{ order.orderNo || order.id || '-' }}</text>
<text class="shrink-0 text-[17px] font-semibold text-[#ef4444]">¥{{ money(order.paidAmount) }}</text>
<view class="mt-3 flex min-w-0 items-end justify-between gap-3 border-t border-slate-100 pt-3">
<text class="min-w-0 flex-1 truncate text-xs text-slate-400">{{ order.entitlements.length ? `${order.entitlements.length} 项权益` : '尚未出票' }}</text>
<view class="flex shrink-0 items-center">
<text class="text-xs text-slate-400">实付 </text>
<text class="font-bold text-orange-600">¥{{ money(order.amount ?? order.paidAmount) }}</text>
<uni-icons class="ml-1" type="right" size="16" color="#cbd5e1" />
</view>
</view>
</view>
</view>
<view v-else class="flex min-h-[420px] min-w-0 flex-col items-center justify-center text-center">
<view class="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-[#eef2ff]">
<uni-icons type="list" size="34" color="#7c3aed" />
<view class="flex h-[72px] w-[72px] items-center justify-center rounded-full bg-blue-50">
<uni-icons type="list" size="34" color="#2563eb" />
</view>
<text class="mt-[16px] text-[16px] font-medium text-[#344054]">暂无订单</text>
<text class="mt-[8px] text-[13px] text-[#98a2b3]">购买后可在这里查看凭证和退款进度</text>
<text class="mt-4 text-base font-semibold text-slate-700">暂无订单</text>
<text class="mt-2 text-xs text-slate-400">购买后可在这里查看凭证和退款进度</text>
</view>
</view>
</scroll-view>
@@ -36,15 +49,12 @@
</template>
<script setup>
import { ORDER_STATUS_META } from '../data.js'
defineProps({ orders: { type: Array, default: () => [] } })
const emit = defineEmits(['open'])
const money = (value) => Number(value || 0).toFixed(2)
const firstTraveler = (order) => order?.travelers?.[0]?.name || '未填写出行人'
const orderStatus = (order) => {
if (order?.status === 'refunded' || order?.refundStatus === 'approved') return 'refunded'
if (order?.status === 'refunding' || order?.refundStatus === 'pending') return 'refunding'
if (order?.status === 'completed') return 'completed'
return 'paid'
}
const fallbackStatus = { label: '未知状态', className: 'border-slate-200 bg-slate-50 text-slate-600' }
const statusMeta = (status) => ORDER_STATUS_META[status] || fallbackStatus
const money = (value) => Number(value || 0).toLocaleString('zh-CN')
</script>

View File

@@ -1,7 +1,18 @@
import { createPaidOrder } from './model.mjs'
export const PLACEHOLDER_IMAGE = 'https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com/oneFeel/2082673896938569729.jpg'
export const ORDER_STATUS_META = {
pending_payment: { label: '待支付', className: 'border-amber-200 bg-amber-50 text-amber-700' },
closed: { label: '已关闭', className: 'border-slate-200 bg-slate-100 text-slate-500' },
paid: { label: '待使用', className: 'border-blue-200 bg-blue-50 text-blue-700' },
fulfilling: { label: '出票中', className: 'border-indigo-200 bg-indigo-50 text-indigo-700' },
partial_used: { label: '部分使用', className: 'border-amber-200 bg-amber-50 text-amber-700' },
completed: { label: '已完成', className: 'border-emerald-200 bg-emerald-50 text-emerald-700' },
refunding: { label: '退款中', className: 'border-orange-200 bg-orange-50 text-orange-700' },
partial_refunded: { label: '部分退款', className: 'border-cyan-200 bg-cyan-50 text-cyan-700' },
refunded: { label: '已退款', className: 'border-slate-200 bg-slate-100 text-slate-600' },
exception: { label: '异常', className: 'border-rose-200 bg-rose-50 text-rose-700' },
}
export const SCENIC_SPOT = {
id: 'libo-xiaoqikong',
name: '荔波小七孔景区',
@@ -161,18 +172,288 @@ export const INITIAL_TRAVELERS = [
{ id: 'traveler-zhou-ning', name: '周宁', idType: '身份证', idNumber: '110101196001020017', phone: '13900139000' },
]
const createDemoOrder = (productId, draft, now) => createPaidOrder({
product: PRODUCTS.find((product) => product.id === productId),
draft,
travelers: INITIAL_TRAVELERS.map((traveler) => ({ ...traveler })),
now,
const createPrototypeOrder = (order) => ({
insurance: null,
refundStatus: order.status === 'refunding'
? 'pending'
: ['partial_refunded', 'refunded'].includes(order.status) ? 'approved' : '',
productAmount: order.amount,
insuranceAmount: 0,
paidAmount: order.amount,
contactName: order.customerName,
travelers: [{
id: `${order.id}-traveler`,
name: order.customerName,
idType: '身份证',
idNumber: order.idCard,
phone: order.phone,
}],
...order,
})
export const INITIAL_ORDERS = [
createDemoOrder('adult', {
visitDate: '2026-08-01', timeSlot: '08:00—09:30', quantity: 1, travelerIds: ['traveler-lin-xiao'], contactName: '林晓', phone: '13800138000', insuranceSelected: true, insurancePrice: INSURANCE.price, maxQuantity: 5,
}, new Date('2026-07-30T09:00:00+08:00')),
createDemoOrder('classic', {
visitDate: '2026-08-02', timeSlot: '09:30—11:30', quantity: 1, travelerIds: ['traveler-lin-xiao'], contactName: '林晓', phone: '13800138000', insuranceSelected: false, insurancePrice: INSURANCE.price, maxQuantity: 5,
}, new Date('2026-07-30T09:05:00+08:00')),
createPrototypeOrder({
id: 'o-001',
orderNo: 'LB202607260001',
productId: 'p-package-classic',
productName: '小七孔经典畅游套餐',
customerName: '张晓云',
phone: '13800138088',
idCard: '52272219930418XXXX',
visitDate: '2026-07-27',
timeSlot: '08:00—10:00',
quantity: 1,
amount: 188,
paidAt: '2026-07-26 09:18:32',
status: 'partial_used',
refundMode: 'all_or_nothing',
gateCredential: { status: 'used', provider: '景区票务平台', providerOrderNo: 'TP20260726093821', ticketNo: 'XQK88261001', qrToken: 'GATE-XQK-88261001', issuedAt: '2026-07-26 09:18:36' },
selfCredential: { status: 'issued', credentialNo: 'BEN202607260001', qrToken: 'BENEFIT-LB202607260001', createdAt: '2026-07-26 09:18:37' },
entitlements: [
{ id: 'oe-001-ticket', name: '小七孔成人门票', fulfillment: 'third_party', quantity: 1, usedQuantity: 1, unit: '张', status: 'used', refundAllocation: 120, voucherNo: 'XQK88261001', usedAt: '2026-07-27 08:46:12' },
{ id: 'oe-001-shuttle', name: '观光车票', fulfillment: 'third_party', quantity: 1, usedQuantity: 1, unit: '张', status: 'used', refundAllocation: 30, voucherNo: 'XQK88261001-CAR', usedAt: '2026-07-27 09:02:18' },
{ id: 'oe-001-boat', name: '鸳鸯湖游船', fulfillment: 'self', quantity: 1, usedQuantity: 0, unit: '次', status: 'pending', refundAllocation: 38, voucherNo: 'BEN202607260001-02' },
],
logs: [
{ time: '2026-07-26 09:18:32', text: '小程序支付成功,实付 ¥188', operator: '系统' },
{ time: '2026-07-26 09:18:36', text: '第三方门票与观光车票出票成功', operator: '出票服务' },
{ time: '2026-07-26 09:18:37', text: '自营园内权益包生成成功', operator: '系统' },
{ time: '2026-07-27 08:46:12', text: '门票已通过闸机,状态由第三方同步', operator: '景区票务平台' },
{ time: '2026-07-27 09:02:18', text: '观光车票已使用,状态由第三方同步', operator: '景区票务平台' },
],
}),
createPrototypeOrder({
id: 'o-002',
orderNo: 'LB202607260002',
productId: 'p-package-classic',
productName: '小七孔经典畅游套餐',
customerName: '周一航',
phone: '18682131666',
idCard: '52010219880902XXXX',
visitDate: '2026-07-28',
timeSlot: '10:00—12:00',
quantity: 2,
amount: 376,
paidAt: '2026-07-26 10:05:09',
status: 'paid',
refundMode: 'all_or_nothing',
gateCredential: { status: 'issued', provider: '景区票务平台', providerOrderNo: 'TP20260726102977', ticketNo: 'XQK88261002', qrToken: 'GATE-XQK-88261002', issuedAt: '2026-07-26 10:05:13' },
selfCredential: { status: 'issued', credentialNo: 'BEN202607260002', qrToken: 'BENEFIT-LB202607260002', createdAt: '2026-07-26 10:05:14' },
entitlements: [
{ id: 'oe-002-ticket', name: '小七孔成人门票', fulfillment: 'third_party', quantity: 2, usedQuantity: 0, unit: '张', status: 'pending', refundAllocation: 240, voucherNo: 'XQK88261002' },
{ id: 'oe-002-shuttle', name: '观光车票', fulfillment: 'third_party', quantity: 2, usedQuantity: 0, unit: '张', status: 'pending', refundAllocation: 60, voucherNo: 'XQK88261002-CAR' },
{ id: 'oe-002-boat', name: '鸳鸯湖游船', fulfillment: 'self', quantity: 2, usedQuantity: 0, unit: '次', status: 'pending', refundAllocation: 76, voucherNo: 'BEN202607260002-02' },
],
logs: [
{ time: '2026-07-26 10:05:09', text: '小程序支付成功,实付 ¥376', operator: '系统' },
{ time: '2026-07-26 10:05:13', text: '第三方门票与观光车票出票成功各2张', operator: '出票服务' },
{ time: '2026-07-26 10:05:14', text: '自营园内权益包生成成功', operator: '系统' },
],
}),
createPrototypeOrder({
id: 'o-003',
orderNo: 'LB202607260003',
productId: 'p-ticket-adult',
productName: '荔波小七孔景区成人票',
customerName: '陈思齐',
phone: '17730208821',
idCard: '52270119991221XXXX',
visitDate: '2026-07-29',
timeSlot: '12:00—14:00',
quantity: 1,
amount: 120,
paidAt: '2026-07-26 10:42:51',
status: 'exception',
gateCredential: { status: 'failed', provider: '景区票务平台', providerOrderNo: '—', failureReason: '第三方票种库存校验超时' },
entitlements: [
{ id: 'oe-003-ticket', name: '小七孔成人门票', fulfillment: 'third_party', quantity: 1, usedQuantity: 0, unit: '张', status: 'pending', refundAllocation: 120, voucherNo: '待出票' },
],
logs: [
{ time: '2026-07-26 10:42:51', text: '小程序支付成功,实付 ¥120', operator: '系统' },
{ time: '2026-07-26 10:42:56', text: '第三方出票失败:库存校验超时', operator: '出票服务' },
],
}),
createPrototypeOrder({
id: 'o-004',
orderNo: 'LB202607260004',
productId: 'p-boat',
productName: '鸳鸯湖游船票',
customerName: '吴嘉树',
phone: '13985271349',
idCard: '52272220010307XXXX',
visitDate: '2026-07-27',
timeSlot: '不限时段',
quantity: 2,
amount: 100,
paidAt: '2026-07-26 11:16:24',
status: 'paid',
selfCredential: { status: 'issued', credentialNo: 'BEN202607260004', qrToken: 'BENEFIT-LB202607260004', createdAt: '2026-07-26 11:16:25' },
entitlements: [
{ id: 'oe-004-boat', name: '鸳鸯湖游船', fulfillment: 'self', quantity: 2, usedQuantity: 0, unit: '次', status: 'pending', refundAllocation: 100, voucherNo: 'BEN202607260004-01' },
],
logs: [
{ time: '2026-07-26 11:16:24', text: '小程序支付成功,实付 ¥100', operator: '系统' },
{ time: '2026-07-26 11:16:25', text: '自营园内权益包生成成功', operator: '系统' },
],
}),
createPrototypeOrder({
id: 'o-005',
orderNo: 'LB202607260005',
productId: 'p-ticket-adult',
productName: '荔波小七孔景区成人票',
customerName: '罗雨桐',
phone: '18585321206',
idCard: '52272519960816XXXX',
visitDate: '2026-07-30',
timeSlot: '08:00—09:30',
quantity: 1,
amount: 120,
paidAt: '2026-07-26 11:42:18',
status: 'fulfilling',
gateCredential: { status: 'issuing', provider: '景区票务平台', providerOrderNo: 'TP20260726114218' },
entitlements: [
{ id: 'oe-005-ticket', name: '小七孔成人门票', fulfillment: 'third_party', quantity: 1, usedQuantity: 0, unit: '张', status: 'pending', refundAllocation: 120, voucherNo: '出票中' },
],
logs: [
{ time: '2026-07-26 11:42:18', text: '小程序支付成功,实付 ¥120', operator: '系统' },
{ time: '2026-07-26 11:42:19', text: '已提交第三方出票,等待出票结果', operator: '出票服务' },
],
}),
createPrototypeOrder({
id: 'o-006',
orderNo: 'LB202607260006',
productId: 'p-boat',
productName: '鸳鸯湖游船票',
customerName: '高子涵',
phone: '18786650218',
idCard: '52272619941122XXXX',
visitDate: '2026-07-26',
timeSlot: '13:30—15:00',
quantity: 1,
amount: 50,
paidAt: '2026-07-25 18:20:06',
status: 'completed',
selfCredential: { status: 'issued', credentialNo: 'BEN202607260006', qrToken: 'BENEFIT-LB202607260006', createdAt: '2026-07-25 18:20:07' },
entitlements: [
{ id: 'oe-006-boat', name: '鸳鸯湖游船', fulfillment: 'self', quantity: 1, usedQuantity: 1, unit: '次', status: 'used', refundAllocation: 50, voucherNo: 'BEN202607260006-01', usedAt: '2026-07-26 14:06:32' },
],
logs: [
{ time: '2026-07-25 18:20:06', text: '小程序支付成功,实付 ¥50', operator: '系统' },
{ time: '2026-07-25 18:20:07', text: '自营园内权益包生成成功', operator: '系统' },
{ time: '2026-07-26 14:06:32', text: '鸳鸯湖游船核销成功,订单完成', operator: '核销员·王超' },
],
}),
createPrototypeOrder({
id: 'o-007',
orderNo: 'LB202607260007',
productId: 'p-shuttle',
productName: '小七孔观光车票',
customerName: '何静怡',
phone: '15285940731',
idCard: '52272319870709XXXX',
visitDate: '2026-07-31',
timeSlot: '不限时段',
quantity: 2,
amount: 80,
paidAt: '2026-07-26 12:08:40',
status: 'refunding',
refundMode: 'per_item',
gateCredential: { status: 'issued', provider: '景区票务平台', providerOrderNo: 'TP20260726120840', ticketNo: 'XQKCAR261207', qrToken: 'GATE-CAR-XQKCAR261207', issuedAt: '2026-07-26 12:08:41' },
entitlements: [
{ id: 'oe-007-shuttle', name: '观光车票', fulfillment: 'third_party', quantity: 2, usedQuantity: 0, unit: '张', status: 'refunding', refundAllocation: 80, voucherNo: 'XQKCAR261207' },
],
logs: [
{ time: '2026-07-26 12:08:40', text: '小程序支付成功,实付 ¥80第三方观光车票出票成功', operator: '出票服务' },
{ time: '2026-07-27 10:15:22', text: '游客申请整单退款,等待审核', operator: '系统' },
],
}),
createPrototypeOrder({
id: 'o-008',
orderNo: 'LB202607260008',
productId: 'p-guide',
productName: '精品讲解服务',
customerName: '彭浩然',
phone: '15086031257',
idCard: '52270119920511XXXX',
visitDate: '2026-08-01',
timeSlot: '09:00—10:30',
quantity: 1,
amount: 68,
paidAt: '2026-07-26 13:26:11',
status: 'refunded',
selfCredential: { status: 'issued', credentialNo: 'BEN202607260008', qrToken: 'BENEFIT-LB202607260008', createdAt: '2026-07-26 13:26:12' },
entitlements: [
{ id: 'oe-008-guide', name: '精品讲解服务', fulfillment: 'self', quantity: 1, usedQuantity: 0, unit: '次', status: 'refunded', refundAllocation: 68, voucherNo: 'BEN202607260008-01' },
],
logs: [
{ time: '2026-07-26 13:26:11', text: '小程序支付成功,实付 ¥68', operator: '系统' },
{ time: '2026-07-27 09:30:16', text: '退款审核通过,原路退回 ¥68', operator: '客服·宋妍' },
],
}),
createPrototypeOrder({
id: 'o-009',
orderNo: 'LB202607260009',
productId: 'p-package-classic',
productName: '小七孔经典畅游套餐',
customerName: '叶嘉宁',
phone: '18188040712',
idCard: '52270219950915XXXX',
visitDate: '2026-08-02',
timeSlot: '08:00—09:30',
quantity: 1,
amount: 188,
paidAt: '—',
status: 'pending_payment',
refundMode: 'all_or_nothing',
entitlements: [],
logs: [
{ time: '2026-07-27 14:02:33', text: '订单创建成功库存已锁定15分钟', operator: '系统' },
],
}),
createPrototypeOrder({
id: 'o-010',
orderNo: 'LB202607260010',
productId: 'p-ticket-adult',
productName: '荔波小七孔景区成人票',
customerName: '田思远',
phone: '18984510366',
idCard: '52272819881203XXXX',
visitDate: '2026-08-02',
timeSlot: '09:30—11:30',
quantity: 1,
amount: 120,
paidAt: '—',
status: 'closed',
entitlements: [],
logs: [
{ time: '2026-07-27 13:30:04', text: '订单创建成功,等待支付', operator: '系统' },
{ time: '2026-07-27 13:45:04', text: '超过15分钟未支付订单自动关闭并释放库存', operator: '系统' },
],
}),
createPrototypeOrder({
id: 'o-011',
orderNo: 'LB202607260011',
productId: 'p-boat',
productName: '鸳鸯湖游船票',
customerName: '蒋语晨',
phone: '15329760845',
idCard: '52272419910208XXXX',
visitDate: '2026-07-25',
timeSlot: '不限时段',
quantity: 2,
amount: 100,
paidAt: '2026-07-24 16:18:28',
status: 'partial_refunded',
refundMode: 'per_item',
selfCredential: { status: 'issued', credentialNo: 'BEN202607260011', qrToken: 'BENEFIT-LB202607260011', createdAt: '2026-07-24 16:18:32' },
entitlements: [
{ id: 'oe-011-boat-used', name: '鸳鸯湖游船', fulfillment: 'self', quantity: 1, usedQuantity: 1, unit: '次', status: 'used', refundAllocation: 50, voucherNo: 'BEN202607260011-01', usedAt: '2026-07-25 10:26:18' },
{ id: 'oe-011-boat-refunded', name: '鸳鸯湖游船', fulfillment: 'self', quantity: 1, usedQuantity: 0, unit: '次', status: 'refunded', refundAllocation: 50, voucherNo: 'BEN202607260011-02' },
],
logs: [
{ time: '2026-07-24 16:18:28', text: '小程序支付成功,实付 ¥100', operator: '系统' },
{ time: '2026-07-25 10:26:18', text: '第1份游船权益核销成功', operator: '核销员·王超' },
{ time: '2026-07-25 15:48:06', text: '第2份未使用游船权益退款成功退款 ¥50', operator: '客服·宋妍' },
],
}),
]

View File

@@ -16,7 +16,7 @@ import {
test('provides coherent default visitor ticket data', async () => {
const data = await import('./data.js')
const { AVAILABLE_DATES, GROUPS, INITIAL_TRAVELERS, INSURANCE, PLACEHOLDER_IMAGE, PRODUCTS, REFUND_REASONS, SCENIC_SPOT } = data
const { AVAILABLE_DATES, GROUPS, INITIAL_TRAVELERS, INSURANCE, ORDER_STATUS_META, PLACEHOLDER_IMAGE, PRODUCTS, REFUND_REASONS, SCENIC_SPOT } = data
const { INITIAL_ORDERS } = data
assert.equal(PLACEHOLDER_IMAGE, 'https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com/oneFeel/2082673896938569729.jpg')
@@ -71,15 +71,45 @@ test('provides coherent default visitor ticket data', async () => {
assert.deepEqual(productsById.get('classic').reservation, { requiresDate: true, advanceDays: 30, requiresTimeSlot: true, timeSlots: standardSlots, realNameRequired: true, idTypes: ['身份证', '护照'], maxQuantity: 5, contactPhoneRequired: true })
assert.deepEqual(productsById.get('classic').highlights, ['官方', '实名预约', '整包退', '双凭证'])
assert.deepEqual(INITIAL_TRAVELERS[0], { id: 'traveler-lin-xiao', name: '林晓', idType: '身份证', idNumber: '110101199001020010', phone: '13800138000' })
assert.equal(INITIAL_ORDERS.length, 2)
assert.deepEqual(INITIAL_ORDERS.map(({ productId, visitDate, timeSlot, quantity, travelers, insurance, gateCredential, selfCredential, entitlements }) => ({ productId, visitDate, timeSlot, quantity, travelers, insurance, hasGateCredential: Boolean(gateCredential), hasSelfCredential: Boolean(selfCredential), fulfillment: entitlements.map(({ fulfillment }) => fulfillment) })), [
{ productId: 'adult', visitDate: '2026-08-01', timeSlot: '08:00—09:30', quantity: 1, travelers: [{ id: 'traveler-lin-xiao', name: '林晓', idType: '身份证', idNumber: '110101199001020010', phone: '13800138000' }], insurance: { name: '出行意外险', price: 5, quantity: 1, status: 'active' }, hasGateCredential: true, hasSelfCredential: false, fulfillment: ['third_party', 'third_party'] },
{ productId: 'classic', visitDate: '2026-08-02', timeSlot: '09:30—11:30', quantity: 1, travelers: [{ id: 'traveler-lin-xiao', name: '林晓', idType: '身份证', idNumber: '110101199001020010', phone: '13800138000' }], insurance: null, hasGateCredential: true, hasSelfCredential: true, fulfillment: ['third_party', 'third_party', 'self'] },
])
assert.deepEqual(
Object.fromEntries(Object.entries(ORDER_STATUS_META).map(([status, meta]) => [status, meta.label])),
{
pending_payment: '待支付',
closed: '已关闭',
paid: '待使用',
fulfilling: '出票中',
partial_used: '部分使用',
completed: '已完成',
refunding: '退款中',
partial_refunded: '部分退款',
refunded: '已退款',
exception: '异常',
},
)
assert.equal(INITIAL_ORDERS.length, 11)
assert.deepEqual(
INITIAL_ORDERS.map(({ orderNo, productName, status, amount }) => [orderNo, productName, status, amount]),
[
['LB202607260001', '小七孔经典畅游套餐', 'partial_used', 188],
['LB202607260002', '小七孔经典畅游套餐', 'paid', 376],
['LB202607260003', '荔波小七孔景区成人票', 'exception', 120],
['LB202607260004', '鸳鸯湖游船票', 'paid', 100],
['LB202607260005', '荔波小七孔景区成人票', 'fulfilling', 120],
['LB202607260006', '鸳鸯湖游船票', 'completed', 50],
['LB202607260007', '小七孔观光车票', 'refunding', 80],
['LB202607260008', '精品讲解服务', 'refunded', 68],
['LB202607260009', '小七孔经典畅游套餐', 'pending_payment', 188],
['LB202607260010', '荔波小七孔景区成人票', 'closed', 120],
['LB202607260011', '鸳鸯湖游船票', 'partial_refunded', 100],
],
)
assert.deepEqual(new Set(INITIAL_ORDERS.map(({ status }) => status)), new Set(Object.keys(ORDER_STATUS_META)))
for (const order of INITIAL_ORDERS) {
assert.notEqual(order.travelers[0], INITIAL_TRAVELERS[0])
const sourceProduct = productsById.get(order.productId)
for (const entitlement of order.entitlements) assert.notEqual(entitlement, sourceProduct.entitlements.find((source) => source.id === entitlement.sourceId))
assert.equal(order.paidAmount, order.amount)
assert.equal(order.productAmount, order.amount)
assert.equal(order.insuranceAmount, 0)
assert.equal(order.travelers[0].name, order.customerName)
assert.ok(order.entitlements.every((entitlement) => ['pending', 'used', 'refunding', 'refunded'].includes(entitlement.status)))
}
})

View File

@@ -77,6 +77,23 @@ test("order content uses bounded box-model wrappers", async () => {
}
});
test("order list matches the visitor prototype card layout and fields", async () => {
const component = await readTicketFile("components/OrderList.vue");
assert.match(component, /rounded-2xl bg-white p-4 text-left shadow-sm/);
assert.match(component, /truncate font-bold text-slate-900/);
assert.match(component, /order\.orderNo/);
assert.match(component, /含保险/);
assert.match(component, /rounded-xl bg-slate-50 p-3/);
assert.match(component, /使用日期/);
assert.match(component, /order\.quantity/);
assert.match(component, /order\.entitlements\.length/);
assert.match(component, /尚未出票/);
assert.match(component, /实付/);
assert.match(component, /ORDER_STATUS_META/);
assert.match(component, /type="right"/);
});
test("traveler manager matches the approved prototype details", async () => {
const component = await readTicketFile("components/TravelerManager.vue");
const page = await readTicketFile("travelers.vue");