import { ref, onMounted, onBeforeUnmount, watch, type Ref } from 'vue' import { fetchListingsPage, type Listing, type PublicListingQuery } from '../api/listings' import type { HomeFilters } from './useHomeFilters' const homePageSize = 12 export function useListingQuery( filters: HomeFilters, sortBy: Ref, activeZone: Ref ) { const loading = ref(false) const loadingMore = ref(false) const listings = ref([]) const totalListings = ref(0) const zoneCounts = ref>({}) const currentPage = ref(1) const hasMoreListings = ref(true) let listingRequestSeq = 0 function joinCSV(values: string[]) { return values.filter(Boolean).join(',') || undefined } function buildListingQuery(page: number): PublicListingQuery { return { page, page_size: homePageSize, keyword: filters.keyword.trim(), sort: sortBy.value, zone: activeZone.value, server: filters.server, region: joinCSV(filters.region), login_method: joinCSV(filters.loginMethod), rank: joinCSV(filters.rank), insurance: joinCSV(filters.insurance), stamina: joinCSV(filters.stamina), load: joinCSV(filters.load), // 多选皮肤名;后端 skin_name CSV 为 AND 匹配(账号需包含所有选中皮肤) skin_name: joinCSV(filters.skinNames), min_coin: filters.minCoin, max_coin: filters.maxCoin, min_ratio: filters.minRatio, max_ratio: filters.maxRatio, min_price: filters.minPrice, max_price: filters.maxPrice, min_deposit: filters.minDeposit, max_deposit: filters.maxDeposit, min_total: filters.minTotal, max_total: filters.maxTotal, min_fire_level: filters.minFireLevel, max_fire_level: filters.maxFireLevel, resource_awmAmmo_min: filters.minAwmAmmo, resource_awmAmmo_max: filters.maxAwmAmmo, } } function listingQuerySignature() { return JSON.stringify(buildListingQuery(1)) } async function loadListingsPage(reset = false) { if ((loadingMore.value && !reset) || (!hasMoreListings.value && !reset)) return const requestSeq = ++listingRequestSeq if (reset) { currentPage.value = 1 hasMoreListings.value = true } loadingMore.value = true try { const page = await fetchListingsPage(buildListingQuery(currentPage.value)) if (requestSeq !== listingRequestSeq) return listings.value = reset ? page.items : [...listings.value, ...page.items] totalListings.value = page.total zoneCounts.value = page.zone_counts hasMoreListings.value = listings.value.length < page.total currentPage.value = page.page + 1 requestAnimationFrame(handleWindowScroll) } catch { if (reset) { listings.value = [] totalListings.value = 0 zoneCounts.value = {} hasMoreListings.value = false } } finally { if (requestSeq === listingRequestSeq) { loadingMore.value = false } } } function handleWindowScroll() { if (window.innerHeight + window.scrollY < document.documentElement.scrollHeight - 480) return loadListingsPage(false) } function zoneCount(key: string) { if (key === 'all') return zoneCounts.value.all ?? totalListings.value return zoneCounts.value[key] ?? 0 } onMounted(() => { window.addEventListener('scroll', handleWindowScroll, { passive: true }) }) onBeforeUnmount(() => { window.removeEventListener('scroll', handleWindowScroll) }) // 使用防抖优化搜索 let debounceTimer: ReturnType | null = null watch( () => listingQuerySignature(), () => { if (debounceTimer) clearTimeout(debounceTimer) debounceTimer = setTimeout(() => { loadListingsPage(true) }, 300) } ) return { loading, loadingMore, listings, totalListings, zoneCounts, hasMoreListings, loadListingsPage, zoneCount, } }