diff --git a/src/pages-service/tickets/components/TicketHome.vue b/src/pages-service/tickets/components/TicketHome.vue index 47d4b14..f404799 100644 --- a/src/pages-service/tickets/components/TicketHome.vue +++ b/src/pages-service/tickets/components/TicketHome.vue @@ -95,7 +95,7 @@ - 已售 {{ product.sold || 0 }} + 已售 {{ product.sold }} ¥{{ product.salePrice }} @@ -106,7 +106,7 @@ - 暂无可售商品 + {{ loading ? '加载中...' : '暂无可售商品' }} @@ -124,6 +124,7 @@ const props = defineProps({ scenicSpot: { type: Object, required: true }, groups: { type: Array, default: () => [] }, products: { type: Array, default: () => [] }, + loading: { type: Boolean, default: false }, }) const emit = defineEmits(['back', 'open-travelers', 'open-orders', 'open-detail', 'buy']) diff --git a/src/pages-service/tickets/data.js b/src/pages-service/tickets/data.js index 61d4543..33f9d0a 100644 --- a/src/pages-service/tickets/data.js +++ b/src/pages-service/tickets/data.js @@ -49,12 +49,9 @@ export const SCENIC_SPOT = { } export const GROUPS = [ - { id: 'ticket', name: '景点门票', subtitle: '累计销量 5000+', description: '一票通玩,核销后门票7日内有效', tag: '官方', productIds: ['adult', 'elderly', 'minor', 'free'], sortOrder: 1, enabled: true }, - { id: 'vip', name: 'VIP速通', subtitle: '专属服务', description: '减少排队等待时间,按预约场次使用', tag: '推荐', productIds: ['vip-fast'], sortOrder: 2, enabled: true }, - { id: 'bus', name: '直通车', subtitle: '往返直达', description: '贵阳市区集合,直达小七孔景区', tag: '省心', productIds: ['direct-bus'], sortOrder: 3, enabled: true }, - { id: 'package', name: '门票+观光车', subtitle: '累计销量 1.5万+', description: '门票与观光车由第三方出票,园内项目使用自营权益码', tag: '热卖', productIds: ['classic'], sortOrder: 4, enabled: true }, - { id: 'tour', name: '一日游', subtitle: '精选线路', description: '轻松游览小七孔代表性景观', tag: '品质', productIds: ['day-tour'], sortOrder: 5, enabled: true }, - { id: 'play', name: '玩乐体验', subtitle: '园内自营项目', description: '工作人员扫描园内权益码后选择项目核销', tag: '自营', productIds: ['boat', 'guide'], sortOrder: 6, enabled: true }, + { id: 'ticket', name: '门票', commodityTypeCode: '1', productIds: [], sortOrder: 1, enabled: true }, + { id: 'maolan', name: '茂兰景区', commodityTypeCode: '67d07734e0af4caa918309f5a346a86e', productIds: [], sortOrder: 2, enabled: true }, + { id: 'heritage-tribe', name: '世遗部落', commodityTypeCode: 'd5766c63028e46f1bd9aaa25d1dc7482', productIds: [], sortOrder: 3, enabled: true }, ] const TIME_SLOTS = ['08:00—09:30', '09:30—11:30', '11:30—13:30', '13:30—15:00', '15:00—16:00'] diff --git a/src/pages-service/tickets/index.vue b/src/pages-service/tickets/index.vue index 7240d02..7f0c48f 100644 --- a/src/pages-service/tickets/index.vue +++ b/src/pages-service/tickets/index.vue @@ -3,7 +3,8 @@ diff --git a/src/pages-service/tickets/model.test.mjs b/src/pages-service/tickets/model.test.mjs index 7882514..91d9cdc 100644 --- a/src/pages-service/tickets/model.test.mjs +++ b/src/pages-service/tickets/model.test.mjs @@ -23,18 +23,13 @@ test('provides coherent default visitor ticket data', async () => { assert.deepEqual(SCENIC_SPOT, { id: 'libo-xiaoqikong', name: '荔波小七孔景区', level: '国家5A级景区', heritage: '世界自然遗产', address: '贵州省黔南布依族苗族自治州荔波县', phone: '0854-3619810', latitude: 25.2534, longitude: 107.7255, heroImage: SCENIC_HERO_IMAGE, }) - assert.deepEqual(GROUPS.map(({ id, name, sortOrder, enabled, productIds }) => ({ id, name, sortOrder, enabled, productIds })), [ - { id: 'ticket', name: '景点门票', sortOrder: 1, enabled: true, productIds: ['adult', 'elderly', 'minor', 'free'] }, - { id: 'vip', name: 'VIP速通', sortOrder: 2, enabled: true, productIds: ['vip-fast'] }, - { id: 'bus', name: '直通车', sortOrder: 3, enabled: true, productIds: ['direct-bus'] }, - { id: 'package', name: '门票+观光车', sortOrder: 4, enabled: true, productIds: ['classic'] }, - { id: 'tour', name: '一日游', sortOrder: 5, enabled: true, productIds: ['day-tour'] }, - { id: 'play', name: '玩乐体验', sortOrder: 6, enabled: true, productIds: ['boat', 'guide'] }, + assert.deepEqual(GROUPS.map(({ id, name, commodityTypeCode, sortOrder, enabled, productIds }) => ({ id, name, commodityTypeCode, sortOrder, enabled, productIds })), [ + { id: 'ticket', name: '门票', commodityTypeCode: '1', sortOrder: 1, enabled: true, productIds: [] }, + { id: 'maolan', name: '茂兰景区', commodityTypeCode: '67d07734e0af4caa918309f5a346a86e', sortOrder: 2, enabled: true, productIds: [] }, + { id: 'heritage-tribe', name: '世遗部落', commodityTypeCode: 'd5766c63028e46f1bd9aaa25d1dc7482', sortOrder: 3, enabled: true, productIds: [] }, ]) assert.deepEqual(PRODUCTS.map(({ id }) => id), ['adult', 'elderly', 'minor', 'free', 'vip-fast', 'direct-bus', 'classic', 'day-tour', 'boat', 'guide']) const productsById = new Map(PRODUCTS.map((product) => [product.id, product])) - assert.ok(GROUPS.every((group) => group.productIds.every((productId) => productsById.has(productId)))) - assert.deepEqual(GROUPS.flatMap((group) => group.productIds).sort(), PRODUCTS.map(({ id }) => id).sort()) const productKeys = ['id', 'name', 'shortTitle', 'category', 'description', 'salePrice', 'marketPrice', 'stock', 'sold', 'status', 'clientVisible', 'highlights', 'ageRule', 'included', 'excluded', 'detail', 'purchaseNotice', 'images', 'reservation', 'entitlements'] const reservationKeys = ['requiresDate', 'advanceDays', 'requiresTimeSlot', 'timeSlots', 'realNameRequired', 'idTypes', 'maxQuantity', 'contactPhoneRequired'] const entitlementKeys = ['id', 'name', 'fulfillment', 'quantity', 'unit', 'refundAllocation'] diff --git a/src/pages-service/tickets/view-contract.test.mjs b/src/pages-service/tickets/view-contract.test.mjs index fd02aa2..4b8dd00 100644 --- a/src/pages-service/tickets/view-contract.test.mjs +++ b/src/pages-service/tickets/view-contract.test.mjs @@ -63,13 +63,17 @@ test("storefront synchronizes the left category from right-column scrolling", as assert.match(source, /TopNavBar/); }); -test("product detail is an independent prototype-aligned page", async () => { +test("storefront products open the API-backed goods detail page", async () => { const indexPage = await readTicketFile("index.vue"); const detailPage = await readTicketFile("product-detail.vue"); const component = await readTicketFile("components/ProductDetail.vue"); const data = await readTicketFile("data.js"); - assert.match(indexPage, /uni\.navigateTo\(\{\s*url: `\/pages-service\/tickets\/product-detail\?productId=\$\{productId\}`/); + assert.match(indexPage, /quickBookingList/); + assert.match(indexPage, /commodityTypeCode: group\.commodityTypeCode/); + assert.match(indexPage, /current: 1/); + assert.match(indexPage, /size: 10/); + assert.match(indexPage, /url: `\/pages\/goods\/index\?commodityId=\$\{encodeURIComponent\(productId\)\}`/); assert.doesNotMatch(indexPage, /detailProduct|