From ec1ff6ded0063160fb8ecf6ae38a07667a6c579b Mon Sep 17 00:00:00 2001 From: zoujing Date: Fri, 31 Jul 2026 10:38:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=88=91=E7=9A=84=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E7=AD=89=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tickets/components/CredentialsView.vue | 196 ++++++++++++++---- .../tickets/components/OrderDetail.vue | 180 +++++++++++----- .../tickets/components/RefundFlow.vue | 18 +- src/pages-service/tickets/credentials.vue | 2 +- src/pages-service/tickets/data.js | 7 + src/pages-service/tickets/model.test.mjs | 6 +- .../tickets/view-contract.test.mjs | 76 +++++++ 7 files changed, 388 insertions(+), 97 deletions(-) diff --git a/src/pages-service/tickets/components/CredentialsView.vue b/src/pages-service/tickets/components/CredentialsView.vue index f1d563e..943b809 100644 --- a/src/pages-service/tickets/components/CredentialsView.vue +++ b/src/pages-service/tickets/components/CredentialsView.vue @@ -1,54 +1,170 @@ + + diff --git a/src/pages-service/tickets/components/OrderDetail.vue b/src/pages-service/tickets/components/OrderDetail.vue index bdd1e34..b946350 100644 --- a/src/pages-service/tickets/components/OrderDetail.vue +++ b/src/pages-service/tickets/components/OrderDetail.vue @@ -1,77 +1,159 @@ + + diff --git a/src/pages-service/tickets/components/RefundFlow.vue b/src/pages-service/tickets/components/RefundFlow.vue index 77592ee..ff1496f 100644 --- a/src/pages-service/tickets/components/RefundFlow.vue +++ b/src/pages-service/tickets/components/RefundFlow.vue @@ -7,17 +7,22 @@ 订单号{{ order.orderNo || order.id || '-' }} 退款状态待审核 - + + + {{ isPackage ? '套餐整单退款' : '按未使用权益退款' }} + {{ isPackage ? '套餐不支持拆项退款,本次将退回全部未使用权益对应的整单实付金额。' : '请选择需要退款的未使用权益,退款金额按购买时实际分摊金额计算。' }} + + 选择退款权益 - - + + {{ right.name }}{{ right.quantity || 0 }}份,可退 ¥{{ money((right.refundAllocation || 0) * (right.quantity || 0)) }} 当前没有可申请退款的权益 @@ -26,19 +31,19 @@ 退款原因 - + {{ reason }} - 预计退款金额¥{{ money(refundAmount) }} + 预计退款金额¥{{ money(refundAmount) }} 实际到账金额以审核结果为准 - + @@ -54,6 +59,7 @@ const props = defineProps({ const emit = defineEmits(['submit', 'detail', 'home']) const selectedIds = ref([]) const selectedReason = ref('') +const isPackage = computed(() => props.order?.refundMode === 'all_or_nothing') const pendingRights = computed(() => (props.order?.entitlements || []).filter((right) => right.status === 'pending')) const refundAmount = computed(() => calculateRefundAmount(props.order, selectedIds.value)) const money = (value) => Number(value || 0).toFixed(2) diff --git a/src/pages-service/tickets/credentials.vue b/src/pages-service/tickets/credentials.vue index 726f00e..631f9bc 100644 --- a/src/pages-service/tickets/credentials.vue +++ b/src/pages-service/tickets/credentials.vue @@ -2,7 +2,7 @@ diff --git a/src/pages-service/tickets/data.js b/src/pages-service/tickets/data.js index 527e337..57d613a 100644 --- a/src/pages-service/tickets/data.js +++ b/src/pages-service/tickets/data.js @@ -13,6 +13,13 @@ export const ORDER_STATUS_META = { exception: { label: '异常', className: 'border-rose-200 bg-rose-50 text-rose-700' }, } +export const ENTITLEMENT_STATUS_META = { + pending: { label: '待使用', className: 'bg-blue-50 text-blue-700' }, + used: { label: '已使用', className: 'bg-emerald-50 text-emerald-700' }, + refunding: { label: '退款中', className: 'bg-orange-50 text-orange-700' }, + refunded: { label: '已退款', className: 'bg-slate-100 text-slate-600' }, +} + export const SCENIC_SPOT = { id: 'libo-xiaoqikong', name: '荔波小七孔景区', diff --git a/src/pages-service/tickets/model.test.mjs b/src/pages-service/tickets/model.test.mjs index d477ad6..8afcbbd 100644 --- a/src/pages-service/tickets/model.test.mjs +++ b/src/pages-service/tickets/model.test.mjs @@ -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, ORDER_STATUS_META, PLACEHOLDER_IMAGE, PRODUCTS, REFUND_REASONS, SCENIC_SPOT } = data + const { AVAILABLE_DATES, ENTITLEMENT_STATUS_META, 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') @@ -86,6 +86,10 @@ test('provides coherent default visitor ticket data', async () => { exception: '异常', }, ) + assert.deepEqual( + Object.fromEntries(Object.entries(ENTITLEMENT_STATUS_META).map(([status, meta]) => [status, meta.label])), + { pending: '待使用', used: '已使用', refunding: '退款中', refunded: '已退款' }, + ) assert.equal(INITIAL_ORDERS.length, 11) assert.deepEqual( INITIAL_ORDERS.map(({ orderNo, productName, status, amount }) => [orderNo, productName, status, amount]), diff --git a/src/pages-service/tickets/view-contract.test.mjs b/src/pages-service/tickets/view-contract.test.mjs index a7d6180..3a68ace 100644 --- a/src/pages-service/tickets/view-contract.test.mjs +++ b/src/pages-service/tickets/view-contract.test.mjs @@ -94,6 +94,82 @@ test("order list matches the visitor prototype card layout and fields", async () assert.match(component, /type="right"/); }); +test("order detail matches the visitor prototype sections and actions", async () => { + const component = await readTicketFile("components/OrderDetail.vue"); + const page = await readTicketFile("order-detail.vue"); + + assert.match(component, /box-border flex w-full min-w-0 flex-col gap-3 p-4 pb-28/); + assert.match(component, /rounded-2xl bg-white p-4/); + assert.match(component, /ORDER_STATUS_META/); + assert.match(component, /rounded-full border px-2\.5 py-1 text-xs font-semibold/); + assert.match(component, /:class="statusMeta\.className"/); + assert.match(component, /order\.orderNo/); + assert.match(component, /使用信息/); + assert.match(component, /使用日期/); + assert.match(component, /使用时段/); + assert.match(component, /出行人/); + assert.match(component, /travelerIdType/); + assert.match(component, /联系手机/); + assert.match(component, /order\.customerName/); + assert.match(component, /order\.phone/); + assert.doesNotMatch(component, /当前状态|statusDescription| { + const component = await readTicketFile("components/CredentialsView.vue"); + const page = await readTicketFile("credentials.vue"); + + assert.match(component, /import Qrcode from "@\/components\/Qrcode\/index\.vue"/); + assert.equal((component.match(/ { + const component = await readTicketFile("components/RefundFlow.vue"); + + assert.match(component, /套餐整单退款/); + assert.match(component, /套餐不支持拆项退款,本次将退回全部未使用权益对应的整单实付金额。/); + assert.match(component, /refundMode === 'all_or_nothing'/); + assert.match(component, /bg-orange-50/); + assert.match(component, /border-orange-400/); + assert.match(component, /border-orange-500 bg-orange-500/); + assert.match(component, /text-orange-600/); + assert.match(component, /bg-orange-500/); + assert.doesNotMatch(component, /#7c3aed|#faf5ff/); +}); + test("traveler manager matches the approved prototype details", async () => { const component = await readTicketFile("components/TravelerManager.vue"); const page = await readTicketFile("travelers.vue");