diff --git a/src/pages/hotel/detail.vue b/src/pages/hotel/detail.vue
index 85fb088..542447d 100644
--- a/src/pages/hotel/detail.vue
+++ b/src/pages/hotel/detail.vue
@@ -21,7 +21,7 @@
-
+
{{ hotel.hotelName }}
@@ -87,8 +87,8 @@
-
-
+
+
起价
¥
@@ -112,6 +112,7 @@ const STAR_MAP: Record = { 1: "民宿", 2: "经济", 3: "舒适"
const placeholderCover = "/static/page/blue-bg.png"
const statusBarHeight = ref(20)
+const safeBottomPx = ref(0)
const scrollHeight = ref("100vh")
const hotelId = ref("")
const MAX_STAY_NIGHTS = 30
@@ -123,6 +124,13 @@ const hotel = ref(null)
const products = ref([])
const rooms = ref([])
+/** 底部栏内容高度(上内边距 + 按钮 + 下内边距),不含安全区 */
+const BOTTOM_BAR_INNER_RPX = 16 + 84 + 16
+/** 无底栏时内容区额外留白 */
+const BODY_PAD_BASE_RPX = 24
+/** 有底栏时内容区额外缓冲,避免最后一张房型贴边 */
+const BODY_PAD_EXTRA_RPX = 24
+
const nightCount = computed(() => {
const n = dayjs(checkOut.value).diff(dayjs(checkIn.value), "day")
return n > 0 ? n : 1
@@ -212,6 +220,26 @@ const minPrice = computed(() => {
return nums.length ? Math.min(...nums) : null
})
+const showBottomBar = computed(() => !!(hotel.value && roomCards.value.length))
+
+const rpx2px = (rpx: number) => {
+ try {
+ return uni.upx2px(rpx)
+ } catch {
+ return Math.round(rpx / 2)
+ }
+}
+
+/** 滚动区底部留白:底栏高度 + 安全区,避免 iOS Home 条 / Android 手势条遮住房型 */
+const bodyPadBottom = computed(() => {
+ if (showBottomBar.value) {
+ return `${rpx2px(BOTTOM_BAR_INNER_RPX + BODY_PAD_EXTRA_RPX) + safeBottomPx.value}px`
+ }
+ return `${rpx2px(BODY_PAD_BASE_RPX) + safeBottomPx.value}px`
+})
+
+const bottomBarPadBottom = computed(() => `${rpx2px(16) + safeBottomPx.value}px`)
+
const formatMd = (ymd: string) => dayjs(ymd).format("M月D日")
const formatPrice = (v: any) => {
const n = Number(v)
@@ -226,9 +254,21 @@ const facilityIcon = (name: string) => {
return "checkbox-circle-line"
}
+const readSafeBottom = (sys: any) => {
+ const inset = sys?.safeAreaInsets?.bottom
+ if (typeof inset === "number" && inset >= 0) return inset
+ const area = sys?.safeArea
+ if (area && typeof sys?.screenHeight === "number") {
+ const bottom = sys.screenHeight - (area.bottom || 0)
+ if (bottom > 0 && bottom < 80) return bottom
+ }
+ return 0
+}
+
const layout = () => {
const sys = uni.getSystemInfoSync()
statusBarHeight.value = sys.statusBarHeight || 20
+ safeBottomPx.value = readSafeBottom(sys)
scrollHeight.value = `${sys.windowHeight || 667}px`
}
@@ -255,7 +295,6 @@ const load = async () => {
const book = (p: any) => {
if (!p?.productCode) return
- // 页面跳转 query 只带关键键;名称/地址/封面等进确认页后用详情接口补齐,避免整单信息挂在 URL 上
router.jump("/pages/hotel/confirm", {
hotelId: hotelId.value,
productCode: p.productCode,
@@ -272,7 +311,6 @@ onLoad((opt: any) => {
checkIn.value = opt?.checkIn || checkIn.value
checkOut.value = opt?.checkOut || checkOut.value
roomsCount.value = Number(opt?.rooms || 1) || 1
- // 连住最多 30 晚,避免超长区间带入确认页
const nights = dayjs(checkOut.value).diff(dayjs(checkIn.value), "day")
if (nights > MAX_STAY_NIGHTS) {
checkOut.value = dayjs(checkIn.value).add(MAX_STAY_NIGHTS, "day").format("YYYY-MM-DD")
@@ -320,9 +358,12 @@ onLoad((opt: any) => {
}
.ht-body {
margin-top: -36rpx;
- padding: 0 24rpx 24rpx;
+ padding-left: 24rpx;
+ padding-right: 24rpx;
+ /* padding-bottom 由 bodyPadBottom 动态计算(底栏 + iOS/Android 安全区) */
position: relative;
z-index: 2;
+ box-sizing: border-box;
}
.ht-card {
background: #fff;
@@ -492,11 +533,16 @@ onLoad((opt: any) => {
right: 0;
bottom: 0;
z-index: 20;
- padding: 16rpx 28rpx calc(16rpx + env(safe-area-inset-bottom));
+ padding-top: 16rpx;
+ padding-left: 28rpx;
+ padding-right: 28rpx;
+ padding-bottom: calc(16rpx + constant(safe-area-inset-bottom));
+ padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
background: #fff;
display: flex;
align-items: center;
gap: 20rpx;
+ box-sizing: border-box;
box-shadow: 0 -4rpx 20rpx rgba(15, 23, 42, 0.06);
}
.ht-bottom__price {