后端(bobo)已上线独立「酒店住宿域」,与通用套餐模型 /api/miniapp/orders 完全解耦。
本文件给出 miniprogram-v2/pages/hotel-booking/hotel-booking.ts 改为调用独立端点的可套用代码。
端点详细契约见 docs/hotel-c-end-contract.md(本文件只给落地代码,不重复列字段)。
请求层:apiGet(path, query)/apiPost(path, data),base 已含/api/miniapp,调用写全路径即可。
独立酒店域按「商户」组织房型,C端必须带 merchantId。
category-rental(或酒店入口页)wx.navigateTo 跳 hotel-booking 时,带上 merchantId 参数。onLoad(options) 里 this.setData({ merchantId: options.merchantId })。merchantId 缺失 → 页面提示「该酒店暂未接入预订」,不请求。(也可从全局选中的酒店商户上下文读取,按小齐现有导航结构定。)
data: {
toast: '', toastShow: false, toastYielding: false,
t: {} as Record<string, string>,
acting: false,
form: { name: '', phone: '', note: '' }, // 联系人/电话(不变)
merchantId: 0, // 新增:酒店商户 ID
roomTypes: [] as Array<{ // 新增:房型列表
id: number; name: string; bed_type: string;
max_guests: number; total_rooms: number;
base_price: number; weekend_price: number; holiday_price: number
}>,
selectedRoomId: 0, // 新增:选中房型
roomsCount: 1, // 新增:间数
checkIn: '', // 入住日(原 stayDate 改名,语义更准)
checkOut: '', // 离店日(新增)
availability: null as any, // 新增:逐晚余量+总价
cfg: {} as Record<string, string>
}
删除原room: '山景木屋'、roomPrice、stayDate等硬编码字段。
onLoad(options: any) {
this.setData({ t: pageTexts('hotel-booking') })
if (options && options.merchantId) this.setData({ merchantId: Number(options.merchantId) })
try { this.revealCtx = revealInPage(this) } catch (e) {}
// 拉该商户在售房型(替代原 loadContent 硬编码 A端配置价)
if (this.data.merchantId) {
apiGet('/api/miniapp/hotel/room-types', { merchantId: this.data.merchantId })
.then((d: any) => this.setData({ roomTypes: (d && d.room_types) || [] }))
.catch(() => this.notify('房型加载失败'))
} else {
this.notify('该酒店暂未接入预订')
}
const stop = skeletonDelay(this, 600)
setTimeout(() => { stop() }, 900)
}
移除原 loadContent(['hotel.room.mountain.price', ...]) 调用(金额不再写死,取自接口)。
onPickRoom(e: any) {
const id = Number((e.currentTarget.dataset as any).id || 0)
this.setData({ selectedRoomId: id, availability: null })
}
incRooms() { const c = (this.data as any).roomsCount; if (c < 9) this.setData({ roomsCount: c + 1, availability: null }) }
decRooms() { const c = (this.data as any).roomsCount; if (c > 1) this.setData({ roomsCount: c - 1, availability: null }) }
onPickDate(e: any) {
const ds: any = e.currentTarget.dataset || {}
if (ds.k === 'in') this.setData({ checkIn: ds.date, availability: null })
else if (ds.k === 'out') this.setData({ checkOut: ds.date, availability: null })
}
async loadAvailability(): Promise<boolean> {
const d: any = this.data
if (!d.selectedRoomId || !d.checkIn || !d.checkOut) { this.notify('请选择房型与日期'); return false }
try {
const r: any = await apiGet('/api/miniapp/hotel/availability', {
roomTypeId: d.selectedRoomId, checkIn: d.checkIn, checkOut: d.checkOut, roomsCount: d.roomsCount,
})
const nights = (r && r.nights) || []
if (nights.some((n: any) => !n.available)) { this.notify('所选日期含满房/维护日,请调整'); return false }
this.setData({ availability: r })
return true
} catch (err: any) {
this.notify((err && err.message) || '余量查询失败'); return false
}
}
submitAction() {
const c: any = this as any
if (c._submitting || this.data.acting) return
c._submitting = true
const f: any = this.data.form
if (!isLoggedIn()) { c._submitting = false; this.notify('请先登录后预订'); return }
if (!f.name || !f.phone) { c._submitting = false; this.notify('请填写联系人与电话'); return }
this.setData({ confirmOpen: false, acting: true })
this.loadAvailability().then((ok) => {
if (!ok) { c._submitting = false; this.setData({ acting: false }); return }
const d: any = this.data
apiPost('/api/miniapp/hotel/bookings', {
roomTypeId: d.selectedRoomId,
checkIn: d.checkIn,
checkOut: d.checkOut,
roomsCount: d.roomsCount,
guests: d.roomsCount, // 住客人数:按间数或另加选择器,按业务定
guestName: f.name,
guestPhone: f.phone,
contactName: f.name,
contactPhone: f.phone,
paymentType: 'offline', // offline=到店付;online=线上支付(走现有支付链路)
})
.then((r: any) => {
c._submitting = false
this.setData({ acting: false, submitted: true })
const bid = (r && r.bookingId) || ''
this.notify('已提交,等待主理人确认')
setTimeout(() => {
if (bid) wx.redirectTo({ url: '/pages/order-detail/order-detail?id=' + String(bid) })
else wx.redirectTo({ url: '/pages/orders/orders' })
}, 800)
})
.catch((err: any) => {
c._submitting = false
this.setData({ acting: false })
this.notify((err && err.message) || '提交失败,请稍后再试')
})
})
}
关键变更:apiPost('/api/miniapp/orders', {productId,...})→apiPost('/api/miniapp/hotel/bookings', {...独立字段})。
订单详情/状态时间线复用现有orders域(order_type='room'),无需新页面。
<radio-group> / 卡片列表改成 wx:for="{{roomTypes}}",data-id="{{item.id}}" bindtap="onPickRoom",显示 item.base_price 等三价。picker mode="date"(入住/离店),data-k="in|out" bindchange="onPickDate"。bindtap="incRooms"/"decRooms",显示 roomsCount。availability.total_price 与逐晚价,提交按钮 bindtap="submitAction"。availability 返回逐晚 available:true 且 total_price 正确(元,无需×100)。HT... 订单 + 房量 sold +N(B端房态日历实时减余量)。POST /api/miniapp/hotel/bookings/{bookingId}/cancel(房量回补)—— 订单详情页的取消入口接此端点。接口已全部在 prod 验证可用(房型列表/余量/下单/取消)。B端后台(b.kailashi.cn)房型与房价、房态、预订管理、入住台均已上线,可作对照。