diff --git a/frontend/src/views/public/HomeView.vue b/frontend/src/views/public/HomeView.vue index 4ac00c0..9fb74c5 100644 --- a/frontend/src/views/public/HomeView.vue +++ b/frontend/src/views/public/HomeView.vue @@ -68,6 +68,7 @@ const filters = reactive({ maxFireLevel: undefined as number | undefined, }); const sortBy = ref("recommended"); +const activeZone = ref("all"); const coinRangeOptions = [ { label: "全部区间", min: undefined, max: undefined }, @@ -128,6 +129,8 @@ const skinChipLabel = computed(() => { const visibleListings = computed(() => { const keyword = filters.keyword.trim().toLowerCase(); const filtered = listings.value.filter((item) => { + if (!matchesZone(item, activeZone.value)) return false; + const text = [ item.title, item.description, @@ -206,6 +209,45 @@ const statCards = computed(() => [ }, ]); +const zoneOptions = computed(() => [ + { + key: "all", + label: "全部专区", + hint: "当前可租账号", + count: listings.value.length, + }, + { + key: "sale", + label: "特惠专区", + hint: "价格更划算", + count: listings.value.filter((item) => matchesZone(item, "sale")).length, + }, + { + key: "gift", + label: "赠送专区", + hint: "含赠送物品", + count: listings.value.filter((item) => matchesZone(item, "gift")).length, + }, + { + key: "night", + label: "夜间专区", + hint: "夜间也好上号", + count: listings.value.filter((item) => matchesZone(item, "night")).length, + }, + { + key: "password", + label: "账密专区", + hint: "交接更快", + count: listings.value.filter((item) => matchesZone(item, "password")).length, + }, + { + key: "highCoin", + label: "高币专区", + hint: "100M 以上", + count: listings.value.filter((item) => matchesZone(item, "highCoin")).length, + }, +]); + onMounted(loadHome); async function loadHome() { @@ -249,9 +291,14 @@ function resetFilters() { filters.maxTotal = undefined; filters.minFireLevel = undefined; filters.maxFireLevel = undefined; + activeZone.value = "all"; sortBy.value = "recommended"; } +function setActiveZone(zoneKey: string) { + activeZone.value = zoneKey; +} + function uniqueOptions(values: string[]) { return [...new Set(values.map((item) => item.trim()).filter(Boolean))]; } @@ -306,6 +353,40 @@ function resetSkinFilter() { filters.skinGroup = ""; filters.skinName = ""; } + +function matchesZone(item: Listing, zoneKey: string) { + if (zoneKey === "all") return true; + if (zoneKey === "sale") return hasAcceleratedSaleRatio(item); + if (zoneKey === "gift") return hasGiftResources(item); + if (zoneKey === "night") return isNightAvailable(item); + if (zoneKey === "password") return /账密|账号密码/.test(getLoginMethod(item)); + if (zoneKey === "highCoin") return getCoinM(item) >= 100; + return true; +} + +function isNightAvailable(item: Listing) { + const onlineTime = item.asset_summary?.online_time; + if (typeof onlineTime !== "object" || onlineTime === null) return false; + const row = onlineTime as Record; + const start = parseTimeHour(row.start); + const end = parseTimeHour(row.end); + if (start === undefined || end === undefined) return false; + return timeRangeCoversHour(start, end, 22) || timeRangeCoversHour(start, end, 23) || timeRangeCoversHour(start, end, 0); +} + +function parseTimeHour(value: unknown) { + if (typeof value !== "string") return undefined; + const [hourText] = value.split(":"); + const hour = Number(hourText); + if (!Number.isFinite(hour) || hour < 0 || hour > 23) return undefined; + return hour; +} + +function timeRangeCoversHour(start: number, end: number, hour: number) { + if (start === end) return true; + if (start < end) return hour >= start && hour <= end; + return hour >= start || hour <= end; +}