diff --git a/src/pages/hotel/list.vue b/src/pages/hotel/list.vue index 713e6ae..4885a1f 100644 --- a/src/pages/hotel/list.vue +++ b/src/pages/hotel/list.vue @@ -52,13 +52,21 @@ {{ formatMd(query.checkOut) }} - - 共住 - {{ nightCount }}晚 + + {{ nightCount }}晚 · 间数人数 + {{ guestSummaryShort }} + + 当前目的地 @@ -276,7 +284,11 @@ @scrolltolower="loadMore" > - 加载中… + + + 正在努力查找酒店中 + 请稍候… + 暂无符合条件的酒店 @@ -319,7 +331,10 @@ - 加载更多… + + + 正在努力查找更多酒店… + 没有更多了 @@ -333,10 +348,12 @@ import dayjs from "dayjs" import { hotelApi } from "@@/api" import { router } from "@@/index" import { filePath } from "@@/utils/file" +import HotelGuestSheet from "@@/components/hotel/hotel-guest-sheet.vue" const statusBarHeight = ref(20) const scrollHeight = ref("100vh") const refreshing = ref(false) +const guestVisible = ref(false) const panel = ref<"" | "area" | "price" | "score" | "more">("") const filterTabs = [ @@ -366,10 +383,12 @@ const query = reactive({ checkOut: dayjs().add(1, "day").format("YYYY-MM-DD"), rooms: 1, adults: 1, + children: 0, }) const listCheckOutMin = computed(() => dayjs(query.checkIn).add(1, "day").format("YYYY-MM-DD")) const listCheckOutMax = computed(() => dayjs(query.checkIn).add(MAX_STAY_NIGHTS, "day").format("YYYY-MM-DD")) +const guestSummaryShort = computed(() => `${query.rooms}间${query.adults + query.children}人`) const normalizeListDate = (value: string) => dayjs(String(value || "").replace(/\//g, "-")).format("YYYY-MM-DD") @@ -391,6 +410,7 @@ const onListCheckInConfirm = (value: string) => { query.checkOut = dayjs(query.checkIn).add(1, "day").format("YYYY-MM-DD") } clampListStayRange(true) + reload() } const onListCheckOutConfirm = (value: string) => { @@ -403,6 +423,26 @@ const onListCheckOutConfirm = (value: string) => { } query.checkOut = d clampListStayRange(true) + reload() +} + +const onGuestConfirm = (v: { rooms: number; adults: number; children: number }) => { + query.rooms = v.rooms + query.adults = v.adults + query.children = v.children + hotelApi + .saveSearchPref({ + rooms: query.rooms, + adults: query.adults, + children: query.children, + nights: nightCount.value, + city: query.city, + keyword: query.keyword, + checkIn: query.checkIn, + checkOut: query.checkOut, + }) + .catch(() => {}) + reload() } const filter = reactive({ @@ -895,6 +935,11 @@ const fetchPage = async (reset = false) => { minPrice: filter.minPrice != null ? filter.minPrice : undefined, maxPrice: filter.maxPrice != null ? filter.maxPrice : undefined, facilityTags: filter.facilityTags.length ? filter.facilityTags.join(",") : undefined, + checkIn: query.checkIn || undefined, + checkOut: query.checkOut || undefined, + rooms: query.rooms || undefined, + adults: query.adults || undefined, + children: query.children ?? undefined, }) const data = res?.data || {} const rows = data.records || [] @@ -930,6 +975,8 @@ const goDetail = (item: any) => { checkIn: query.checkIn, checkOut: query.checkOut, rooms: String(query.rooms), + adults: String(query.adults), + children: String(query.children), }) } @@ -942,6 +989,7 @@ onLoad((opt: any) => { query.checkOut = opt?.checkOut || query.checkOut query.rooms = Number(opt?.rooms || 1) || 1 query.adults = Number(opt?.adults || 1) || 1 + query.children = Math.max(0, Number(opt?.children || 0) || 0) clampListStayRange(false) layout() loadMeta() @@ -1278,6 +1326,29 @@ onLoad((opt: any) => { font-size: 26rpx; color: #94a3b8; } +.ht-loading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 120rpx 32rpx 80rpx; + gap: 16rpx; +} +.ht-loading--more { + padding: 32rpx 0 48rpx; + flex-direction: row; + gap: 12rpx; +} +.ht-loading__title { + margin-top: 8rpx; + font-size: 30rpx; + font-weight: 600; + color: #334155; +} +.ht-loading__sub { + font-size: 24rpx; + color: #94a3b8; +} .ht-hint { width: 100%; font-size: 22rpx; diff --git a/src/pages/tabbar/travel.vue b/src/pages/tabbar/travel.vue index a14cac2..897f092 100644 --- a/src/pages/tabbar/travel.vue +++ b/src/pages/tabbar/travel.vue @@ -56,6 +56,14 @@ + + + 间数人数 + {{ guestSummary }} + + + + + + @@ -121,7 +137,9 @@ import { onShow } from "@dcloudio/uni-app" import dayjs from "dayjs" import { router } from "@@/index" import { useTabbarStore } from "@@/stores/tabbar" +import hotelApi from "@@/api/hotel" import TIndexAddress from "@@/components/address/t-index-address/t-index-address.vue" +import HotelGuestSheet from "@@/components/hotel/hotel-guest-sheet.vue" const DATE_PICKER_MIN = dayjs().format("YYYY-MM-DD") const DATE_PICKER_MAX = dayjs().add(365, "day").format("YYYY-MM-DD") @@ -133,12 +151,17 @@ const hotCities = ["北京", "上海", "广州", "深圳", "杭州", "成都", " const cityVisible = ref(false) const cityKeyword = ref("") +const guestVisible = ref(false) +const prefLoaded = ref(false) const state = reactive({ city: "", keyword: "", checkIn: dayjs().format("YYYY-MM-DD"), checkOut: dayjs().add(1, "day").format("YYYY-MM-DD"), + rooms: 1, + adults: 1, + children: 0, }) const nightCount = computed(() => { @@ -146,6 +169,10 @@ const nightCount = computed(() => { return n > 0 ? n : 1 }) +const guestSummary = computed( + () => `${state.rooms}间 ${state.adults}成人 ${state.children}儿童`, +) + const checkOutPickerMin = computed(() => dayjs(state.checkIn).add(1, "day").format("YYYY-MM-DD")) const checkOutPickerMax = computed(() => dayjs(state.checkIn).add(MAX_STAY_NIGHTS, "day").format("YYYY-MM-DD")) @@ -213,13 +240,63 @@ const clearCity = () => { state.city = "" } +const onGuestConfirm = (v: { rooms: number; adults: number; children: number }) => { + state.rooms = v.rooms + state.adults = v.adults + state.children = v.children +} + +const persistSearchPref = async () => { + try { + await hotelApi.saveSearchPref({ + rooms: state.rooms, + adults: state.adults, + children: state.children, + nights: nightCount.value, + city: state.city, + keyword: state.keyword, + checkIn: state.checkIn, + checkOut: state.checkOut, + }) + } catch { + /* 未登录或接口失败不阻断搜索 */ + } +} + +const loadSearchPref = async () => { + if (prefLoaded.value) return + try { + const res: any = await hotelApi.getSearchPref() + const d = res?.data + if (!d) return + state.rooms = Math.max(1, Number(d.rooms) || 1) + state.adults = Math.max(1, Number(d.adults) || 1) + state.children = Math.max(0, Number(d.children) || 0) + if (d.city) state.city = String(d.city) + // 晚数习惯:若库里有 nights,且当前仍是默认 1 晚,则按习惯回填离店 + const nights = Math.max(1, Math.min(MAX_STAY_NIGHTS, Number(d.nights) || 1)) + const today = dayjs().format("YYYY-MM-DD") + if (state.checkIn === today && nightCount.value === 1 && nights > 1) { + state.checkOut = dayjs(state.checkIn).add(nights, "day").format("YYYY-MM-DD") + } + prefLoaded.value = true + } catch { + prefLoaded.value = true + } +} + const goSearch = () => { + clampStayRange(true) + // 习惯落库异步,不阻塞跳转与列表请求 + persistSearchPref() router.jump("/pages/hotel/list", { city: state.city, keyword: state.keyword, checkIn: state.checkIn, checkOut: state.checkOut, - rooms: "1", + rooms: String(state.rooms), + adults: String(state.adults), + children: String(state.children), }) } @@ -230,6 +307,7 @@ const goOrders = () => { onShow(() => { // 从酒店列表等业务页返回时,确保原生底栏重新显示 useTabbarStore().showTabbar("/pages/tabbar/travel") + loadSearchPref() }) @@ -347,6 +425,25 @@ onShow(() => { color: #1570ef; font-size: 22rpx; } +.ht-guest-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20rpx 0; + border-bottom: 1rpx solid #f1f5f9; +} +.ht-guest-row__lab { + display: block; + font-size: 22rpx; + color: #94a3b8; +} +.ht-guest-row__val { + display: block; + margin-top: 6rpx; + font-size: 30rpx; + font-weight: 600; + color: #0f172a; +} .ht-keyword-wrap { position: relative; margin-top: 20rpx; diff --git a/src/sheep/api/hotel.ts b/src/sheep/api/hotel.ts index a05646f..1bab888 100644 --- a/src/sheep/api/hotel.ts +++ b/src/sheep/api/hotel.ts @@ -33,6 +33,21 @@ export default { params, }) }, + /** 当前用户酒店搜索习惯 */ + getSearchPref: (): Promise => { + return request({ + url: "/supermarket/app/hotel/search-pref", + method: "GET", + }) + }, + /** 保存酒店搜索习惯 */ + saveSearchPref: (data: any): Promise => { + return request({ + url: "/supermarket/app/hotel/search-pref", + method: "POST", + data, + }) + }, /** 酒店详情 */ detail: (id: string | number): Promise => { return request({ diff --git a/src/sheep/components/hotel/hotel-guest-sheet.vue b/src/sheep/components/hotel/hotel-guest-sheet.vue new file mode 100644 index 0000000..9621d9b --- /dev/null +++ b/src/sheep/components/hotel/hotel-guest-sheet.vue @@ -0,0 +1,278 @@ + + + + +