diff --git a/src/pages-service/tickets/components/TravelerManager.vue b/src/pages-service/tickets/components/TravelerManager.vue index 2f610bb..22e7336 100644 --- a/src/pages-service/tickets/components/TravelerManager.vue +++ b/src/pages-service/tickets/components/TravelerManager.vue @@ -1,42 +1,67 @@ @@ -49,11 +74,9 @@ const props = defineProps({ selectionMode: { type: Boolean, default: false }, }) -const emit = defineEmits(['toggle', 'save']) -const idTypes = ['身份证', '护照'] -const showForm = ref(false) +const emit = defineEmits(['toggle', 'save', 'delete']) const formError = ref('') -const form = reactive({ name: '', idType: '身份证', idNumber: '', phone: '' }) +const form = reactive({ name: '', idNumber: '', phone: '' }) const isSelected = (id) => props.selectedIds.includes(id) const selectTraveler = (id) => { @@ -74,22 +97,30 @@ const setFormField = (key, value) => { } const resetForm = () => { form.name = '' - form.idType = '身份证' form.idNumber = '' form.phone = '' formError.value = '' - showForm.value = false } const saveTraveler = () => { if (!form.name.trim() || !form.idNumber.trim()) { formError.value = '请填写姓名和证件号码' return } + if (!/^\d{17}[\dX]$/.test(form.idNumber.trim().toUpperCase())) { + formError.value = '请填写正确的18位身份证号码' + return + } if (form.phone && !/^1[3-9]\d{9}$/.test(form.phone)) { formError.value = '请填写正确的手机号' return } - emit('save', { id: `traveler-${Date.now()}`, name: form.name.trim(), idType: form.idType, idNumber: form.idNumber.trim(), phone: form.phone }) + emit('save', { + id: `traveler-${Date.now()}`, + name: form.name.trim(), + idType: '身份证', + idNumber: form.idNumber.trim().toUpperCase(), + phone: form.phone, + }) resetForm() } diff --git a/src/pages-service/tickets/store.js b/src/pages-service/tickets/store.js index ad12142..4f0161c 100644 --- a/src/pages-service/tickets/store.js +++ b/src/pages-service/tickets/store.js @@ -70,6 +70,17 @@ export const useTicketStore = defineStore("ticket-client", { }; } }, + deleteTraveler(travelerId) { + this.travelers = this.travelers.filter( + (traveler) => traveler.id !== travelerId + ); + this.bookingDraft = { + ...this.bookingDraft, + travelerIds: (this.bookingDraft.travelerIds || []).filter( + (id) => id !== travelerId + ), + }; + }, submitBooking() { const product = this.currentProduct; const draft = this.bookingDraft; diff --git a/src/pages-service/tickets/travelers.vue b/src/pages-service/tickets/travelers.vue index d93300f..f3d4d58 100644 --- a/src/pages-service/tickets/travelers.vue +++ b/src/pages-service/tickets/travelers.vue @@ -13,6 +13,7 @@ :selection-mode="selectionMode" @toggle="ticketStore.toggleTraveler" @save="saveTraveler" + @delete="ticketStore.deleteTraveler" /> diff --git a/src/pages-service/tickets/view-contract.test.mjs b/src/pages-service/tickets/view-contract.test.mjs index 1e7f6fe..99f6e23 100644 --- a/src/pages-service/tickets/view-contract.test.mjs +++ b/src/pages-service/tickets/view-contract.test.mjs @@ -53,8 +53,6 @@ test("TopNavBar exposes an optional subtitle", async () => { test("storefront synchronizes the left category from right-column scrolling", async () => { const source = await readTicketFile("components/TicketHome.vue"); - assert.match(source, /right-\[100px\]/); - assert.match(source, /z-30/); assert.match(source, /@tap\.stop="emit\('open-travelers'\)"/); assert.match(source, /@tap\.stop="emit\('open-orders'\)"/); assert.match(source, /@scroll="handleProductScroll"/); @@ -78,3 +76,28 @@ test("order content uses bounded box-model wrappers", async () => { assert.match(source, /min-w-0/); } }); + +test("traveler manager matches the approved prototype details", async () => { + const component = await readTicketFile("components/TravelerManager.vue"); + const page = await readTicketFile("travelers.vue"); + const store = await readTicketFile("store.js"); + + assert.match(component, /gap-\[12px\]/); + assert.match(component, /type="trash"/); + assert.match(component, /type="phone"/); + assert.match(component, /@tap\.stop="emit\('delete', traveler\.id\)"/); + assert.match(component, /身份证信息仅用于|出行人信息仅用于实名购票、第三方出票和身份核验。/); + assert.match(component, /18位身份证号码/); + assert.match(component, /placeholder="手机号(选填)"/); + assert.match(component, /请填写正确的手机号/); + assert.match(component, /bg-blue-600/); + assert.match(component, /已保存的出行人/); + assert.doesNotMatch(component, /showForm/); + assert.doesNotMatch(component, /取消/); + assert.equal((component.match(/新增出行人/g) || []).length, 1); + assert.ok( + component.indexOf("新增出行人") < component.indexOf('v-if="travelers.length"') + ); + assert.match(page, /@delete="ticketStore\.deleteTraveler"/); + assert.match(store, /deleteTraveler\(travelerId\)/); +});