feat: 替换了图片

This commit is contained in:
2026-07-31 12:58:54 +08:00
parent 91567851ac
commit b7a9d61c37
2 changed files with 38 additions and 5 deletions

View File

@@ -1,5 +1,21 @@
export const PLACEHOLDER_IMAGE = 'https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com/oneFeel/2082673896938569729.jpg'
const XIAOQIKONG_IMAGE_ROOT = 'https://one-feel-config-images-bucket.oss-cn-chengdu.aliyuncs.com/xiaoqi/tickets'
export const SCENIC_HERO_IMAGE = `${XIAOQIKONG_IMAGE_ROOT}/hero-bridge.jpg`
export const PRODUCT_IMAGES = {
adult: `${XIAOQIKONG_IMAGE_ROOT}/product-adult-laya-waterfall.jpg`,
elderly: `${XIAOQIKONG_IMAGE_ROOT}/product-elderly-water-forest.jpg`,
minor: `${XIAOQIKONG_IMAGE_ROOT}/product-minor-cascade.jpg`,
free: `${XIAOQIKONG_IMAGE_ROOT}/product-free-scenery.jpg`,
'vip-fast': `${XIAOQIKONG_IMAGE_ROOT}/product-vip-tree.jpg`,
'direct-bus': `${XIAOQIKONG_IMAGE_ROOT}/product-direct-bus-gate.jpg`,
classic: `${XIAOQIKONG_IMAGE_ROOT}/product-classic-forest-path.jpg`,
'day-tour': `${XIAOQIKONG_IMAGE_ROOT}/product-day-tour-waterfall.jpg`,
boat: `${XIAOQIKONG_IMAGE_ROOT}/product-boat-rafting.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' },
@@ -29,7 +45,7 @@ export const SCENIC_SPOT = {
phone: '0854-3619810',
latitude: 25.2534,
longitude: 107.7255,
heroImage: PLACEHOLDER_IMAGE,
heroImage: SCENIC_HERO_IMAGE,
}
export const GROUPS = [
@@ -82,6 +98,7 @@ const createProduct = ({
purchaseNotice,
useRule = '请按预约日期和场次使用;实名商品需携带下单时填写的有效证件,具体核验方式以出票凭证及现场指引为准。',
refundRule = '未使用且符合商品退改规则时可申请退款;已使用、已过有效期或超过退改时间的权益不可退款,审核结果以订单页面为准。',
image = PLACEHOLDER_IMAGE,
reservation,
entitlements,
status = 'on_sale',
@@ -106,7 +123,7 @@ const createProduct = ({
purchaseNotice,
useRule,
refundRule,
images: [PLACEHOLDER_IMAGE],
images: [image],
reservation: createReservation(reservation),
entitlements: entitlements.map((entitlement) => ({ ...entitlement })),
})
@@ -116,46 +133,55 @@ const ticketEntitlement = (id, name, fulfillment, refundAllocation, unit = '张'
export const PRODUCTS = [
createProduct({
id: 'adult', name: '荔波小七孔成人票+观光车', shortTitle: '成人门票+观光车', category: '景区门票', description: '成人门票与观光车固定组合,指定日期和场次使用,由第三方票务系统统一出票。', salePrice: 160, marketPrice: 160, stock: 500, sold: 2386,
image: PRODUCT_IMAGES.adult,
highlights: ['官方', '可订明日', '随时退', '无需取票'], included: ['荔波小七孔景区成人门票 1 张、观光车票 1 张。'], excluded: ['不含景区保险及其他园内自费项目。'], detail: '门票与观光车为固定销售组合,由第三方票务系统统一出票。出票成功后,可凭本人身份证或入园二维码通过景区闸机。', purchaseNotice: '实名预约,一人一证;请按照预约日期和入园时段使用。',
reservation: { advanceDays: 30 }, entitlements: [ticketEntitlement('adult-ticket', '小七孔成人门票', 'third_party', 120), ticketEntitlement('shuttle', '小七孔观光车票', 'third_party', 40)],
}),
createProduct({
id: 'elderly', name: '荔波小七孔老年人票+观光车', shortTitle: '老年人门票+观光车', category: '景区门票', description: '老年人门票与观光车固定组合,系统按使用日期和身份证出生日期校验年龄。', salePrice: 100, marketPrice: 100, stock: 1800, sold: 816,
image: PRODUCT_IMAGES.elderly,
highlights: ['官方', '实名预约', '年龄校验', '第三方出票'], ageRule: { min: 60, max: 999 }, included: ['荔波小七孔景区老年人票 1 张、观光车票 1 张。'], excluded: ['不含景区保险及其他园内项目。'], detail: '系统会根据使用日期和身份证中的出生日期计算年龄,符合条件后提交第三方票务系统出票。', purchaseNotice: '限使用日年满60周岁的游客购买一人一证证件信息须与使用人一致。',
reservation: { advanceDays: 30, idTypes: ['身份证'] }, entitlements: [ticketEntitlement('elderly-ticket', '小七孔老年人票', 'third_party', 60), ticketEntitlement('shuttle', '小七孔观光车票', 'third_party', 40)],
}),
createProduct({
id: 'minor', name: '荔波小七孔未成年票+观光车', shortTitle: '未成年门票+观光车', category: '景区门票', description: '未成年人门票与观光车固定组合,系统按使用日期和身份证出生日期校验年龄。', salePrice: 100, marketPrice: 100, stock: 1800, sold: 629,
image: PRODUCT_IMAGES.minor,
highlights: ['官方', '实名预约', '年龄校验', '第三方出票'], ageRule: { min: 6, max: 17 }, included: ['荔波小七孔景区未成年票 1 张、观光车票 1 张。'], excluded: ['不含景区保险及其他园内项目。'], detail: '系统会根据使用日期和身份证中的出生日期计算年龄符合6—17周岁条件后提交第三方票务系统出票。', purchaseNotice: '限使用日6—17周岁的游客购买一人一证证件信息须与使用人一致。',
reservation: { advanceDays: 30, idTypes: ['身份证'] }, entitlements: [ticketEntitlement('minor-ticket', '小七孔未成年票', 'third_party', 60), ticketEntitlement('shuttle', '小七孔观光车票', 'third_party', 40)],
}),
createProduct({
id: 'free', name: '荔波小七孔免门票+观光车', shortTitle: '免门票+观光车实名预约', category: '景区门票', description: '特殊政策期门票免费、观光车正常计费,仍需实名校验并由第三方票务系统出票。', salePrice: 40, marketPrice: 40, stock: 1200, sold: 346,
image: PRODUCT_IMAGES.free,
highlights: ['政策免票', '实名预约', '限期开放', '第三方出票'], ageRule: { min: 0, max: 120 }, included: ['特殊政策期小七孔景区入园预约资格 1 份、观光车票 1 张。'], excluded: ['不含景区保险及所有园内自费项目。'], detail: '免票商品仍需选择日期与场次并填写实名信息。系统校验预约日期处于政策期后,向第三方票务系统请求出票。', purchaseNotice: '仅在后台配置的政策日期内开放;游客还须符合当期景区公告的适用条件。',
reservation: { advanceDays: 30, idTypes: ['身份证'] }, entitlements: [ticketEntitlement('free-ticket', '小七孔特殊时期免门票', 'third_party', 0), ticketEntitlement('shuttle', '小七孔观光车票', 'third_party', 40)],
}),
createProduct({
id: 'vip-fast', name: '小七孔景区 VIP 快速通行服务', shortTitle: 'VIP 快速通行', category: '园内项目', description: '专属通道与现场引导服务,减少排队等待时间。', salePrice: 98, marketPrice: 98, stock: 200, sold: 126,
image: PRODUCT_IMAGES['vip-fast'],
highlights: ['专属通道', '快速通行', '无需取票'], included: ['小七孔景区 VIP 快速通行服务 1 次。'], excluded: ['不含景区门票、观光车及其他园内项目。'], detail: '购买后生成园内权益码,请按预约时间到指定服务区域联系工作人员。', purchaseNotice: '请提前完成预约并按时到场,具体通行安排以现场工作人员指引为准。',
reservation: { advanceDays: 15, realNameRequired: false, idTypes: ['身份证'], timeSlots: ['08:00—10:00', '10:00—12:00', '12:00—14:00', '14:00—16:00'] }, entitlements: [ticketEntitlement('vip-fast-pass', 'VIP 快速通行服务', 'self', 98, '次')],
}),
createProduct({
id: 'direct-bus', name: '贵阳往返小七孔景区直通车', shortTitle: '贵阳往返小七孔直通车', category: '交通票', description: '贵阳市区集合,往返直达小七孔景区,省去中转换乘。', salePrice: 68, marketPrice: 68, stock: 180, sold: 92,
image: PRODUCT_IMAGES['direct-bus'],
highlights: ['往返直达', '定点发车', '第三方出票'], included: ['贵阳至小七孔景区往返直通车乘车权益 1 份。'], excluded: ['不含景区门票、餐饮及其他个人消费。'], detail: '下单时选择出行日期和班次,出票成功后凭乘车凭证按时到达集合地点。', purchaseNotice: '请至少提前20分钟到达集合地点迟到或错过班次按商品退改规则处理。',
reservation: { advanceDays: 30, timeSlots: ['06:30—07:00 去程', '18:00—18:30 返程'] }, entitlements: [ticketEntitlement('direct-bus-seat', '景区直通车', 'third_party', 68, '位')],
}),
createProduct({
id: 'classic', name: '小七孔经典畅游套餐', shortTitle: '门票 + 观光车 + 鸳鸯湖游船(成人套餐)', category: '组合套餐', description: '门票 + 观光车 + 鸳鸯湖游船,一次购买,两类凭证独立履约。', salePrice: 188, marketPrice: 210, stock: 480, sold: 184,
image: PRODUCT_IMAGES.classic,
highlights: ['官方', '实名预约', '整包退', '双凭证'], included: ['成人门票 1 张、观光车票 1 张、鸳鸯湖游船 1 次。'], excluded: ['不含景区保险、餐饮及套餐外个人消费。'], detail: '一个订单包含两类凭证:第三方票务凭证包含景区门票和观光车票;自营权益码包含鸳鸯湖游船,由工作人员扫码核销。', purchaseNotice: '套餐统一选择使用日期和入园时段。全部权益未使用可整单全额退;任一权益使用后整单不可退,不支持拆项退款。', refundRule: '套餐内全部权益均未使用时支持整单全额退款;任一权益使用后整单不可退款,不支持拆项退款。',
reservation: { advanceDays: 30 }, entitlements: [ticketEntitlement('classic-ticket', '小七孔成人门票', 'third_party', 120), ticketEntitlement('classic-shuttle', '观光车票', 'third_party', 40), ticketEntitlement('classic-boat', '鸳鸯湖游船', 'self', 28, '次')],
}),
createProduct({
id: 'day-tour', name: '荔波小七孔精品一日游', shortTitle: '小七孔精品一日游', category: '园内项目', description: '精选游览路线与随行服务,一天轻松游览小七孔代表性景观。', salePrice: 188, marketPrice: 188, stock: 120, sold: 68,
image: PRODUCT_IMAGES['day-tour'],
highlights: ['精品小团', '行程讲解', '可订明日'], included: ['小七孔精品一日游随行服务 1 份。'], excluded: ['不含未在商品说明中列明的个人消费。'], detail: '覆盖小七孔古桥、拉雅瀑布、水上森林等代表性游览区域,具体线路以当天安排为准。', purchaseNotice: '下单时请选择出行日期和集合场次,并准确填写联系人与出行人信息。',
reservation: { advanceDays: 20, timeSlots: ['08:00—08:30 集合', '09:00—09:30 集合'] }, entitlements: [ticketEntitlement('day-tour-service', '小七孔精品一日游', 'self', 188, '份')],
}),
createProduct({
id: 'boat', name: '鸳鸯湖游船票', shortTitle: '鸳鸯湖游船票', category: '园内项目', description: '鸳鸯湖游船体验,凭园内权益码由工作人员扫码核销。', salePrice: 50, marketPrice: 60, stock: 480, sold: 326,
image: PRODUCT_IMAGES.boat,
highlights: ['官方自营', '当日可订', '未用可退'], included: ['鸳鸯湖游船体验 1 次。'], excluded: ['不含景区门票、观光车及个人消费。'], detail: '乘船游览鸳鸯湖水上森林景观,凭园内权益码由工作人员核销。', purchaseNotice: '受天气和现场运营情况影响,具体以景区通知为准。',
reservation: { requiresTimeSlot: false, realNameRequired: false, advanceDays: 15, idTypes: ['身份证'] }, entitlements: [ticketEntitlement('boat-experience', '鸳鸯湖游船', 'self', 50, '次')],
}),

View File

@@ -16,12 +16,12 @@ import {
test('provides coherent default visitor ticket data', async () => {
const data = await import('./data.js')
const { AVAILABLE_DATES, ENTITLEMENT_STATUS_META, 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, PRODUCT_IMAGES, PRODUCTS, REFUND_REASONS, SCENIC_HERO_IMAGE, SCENIC_SPOT } = data
const { INITIAL_ORDERS } = data
assert.equal(PLACEHOLDER_IMAGE, 'https://one-feel-bucket.oss-cn-guangzhou.aliyuncs.com/oneFeel/2082673896938569729.jpg')
assert.deepEqual(SCENIC_SPOT, {
id: 'libo-xiaoqikong', name: '荔波小七孔景区', level: '国家5A级景区', heritage: '世界自然遗产', address: '贵州省黔南布依族苗族自治州荔波县', phone: '0854-3619810', latitude: 25.2534, longitude: 107.7255, heroImage: PLACEHOLDER_IMAGE,
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'] },
@@ -38,8 +38,15 @@ test('provides coherent default visitor ticket data', async () => {
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']
assert.ok(PRODUCTS.every((product) => productKeys.every((key) => key in product) && ['on_sale', 'draft'].includes(product.status) && Number.isFinite(product.salePrice) && Number.isFinite(product.stock) && Number.isFinite(product.sold) && product.images.length === 1 && product.images[0] === PLACEHOLDER_IMAGE && reservationKeys.every((key) => key in product.reservation) && product.entitlements.length && product.entitlements.every((entitlement) => entitlementKeys.every((key) => key in entitlement))))
assert.ok(PRODUCTS.every((product) => productKeys.every((key) => key in product) && ['on_sale', 'draft'].includes(product.status) && Number.isFinite(product.salePrice) && Number.isFinite(product.stock) && Number.isFinite(product.sold) && product.images.length === 1 && reservationKeys.every((key) => key in product.reservation) && product.entitlements.length && product.entitlements.every((entitlement) => entitlementKeys.every((key) => key in entitlement))))
assert.deepEqual(PRODUCTS.filter(({ status, clientVisible }) => status !== 'on_sale' || clientVisible === false).map(({ id }) => id), ['guide'])
const visibleProducts = PRODUCTS.filter(({ status, clientVisible }) => status === 'on_sale' && clientVisible !== false)
assert.deepEqual(Object.keys(PRODUCT_IMAGES), visibleProducts.map(({ id }) => id))
assert.ok(SCENIC_HERO_IMAGE.endsWith('/hero-bridge.jpg'))
assert.notEqual(SCENIC_HERO_IMAGE, PLACEHOLDER_IMAGE)
assert.ok(visibleProducts.every((product) => product.images[0] === PRODUCT_IMAGES[product.id] && product.images[0].startsWith('/static/images/tickets/xiaoqikong/')))
assert.equal(new Set([SCENIC_HERO_IMAGE, ...visibleProducts.map(({ images }) => images[0])]).size, 10)
assert.equal(productsById.get('guide').images[0], PLACEHOLDER_IMAGE)
assert.deepEqual(PRODUCTS.map(({ id, salePrice, marketPrice, sold }) => [id, salePrice, marketPrice, sold]), [
['adult', 160, 160, 2386], ['elderly', 100, 100, 816], ['minor', 100, 100, 629], ['free', 40, 40, 346], ['vip-fast', 98, 98, 126], ['direct-bus', 68, 68, 92], ['classic', 188, 210, 184], ['day-tour', 188, 188, 68], ['boat', 50, 60, 326], ['guide', 68, 88, 0],
])