Files
hfb_sys/frontend/src/features/listings/views/HomeView.vue
T

297 lines
6.6 KiB
Vue

<script setup lang="ts">
import { computed, ref } from 'vue'
import {
defaultHomeAnnouncements,
defaultHomeBanners,
fetchMobileHomeConfig,
type HomeBannerSlide,
} from '@/features/listings/api/homeConfig'
import {
emptyListingPublishOptions,
type ListingPublishOptions,
} from '@/features/listings/api/listingOptions'
import { useHomeFilters } from '@/features/listings/composables/useHomeFilters'
import { useListingQuery } from '@/features/listings/composables/useListingQuery'
import HomeAnnouncement from '../components/HomeAnnouncement.vue'
import HomeBanner from '../components/HomeBanner.vue'
import HomeStats from '../components/HomeStats.vue'
import HomeFilters from '../components/HomeFilters.vue'
import HomeZonesAndSort from '../components/HomeZonesAndSort.vue'
import ListingCard from '../components/ListingCard.vue'
const announcements = ref<string[]>(defaultHomeAnnouncements)
const banners = ref<HomeBannerSlide[]>(defaultHomeBanners)
const publishOptions = ref<ListingPublishOptions>(emptyListingPublishOptions)
const sortBy = ref('recommended')
const activeZone = ref('all')
const {
filters,
activeFilterPopover,
regionOptions,
loginMethodOptions,
skinFilterGroups,
skinChipLabel,
resetFilters,
setFilterPopover,
closeFilterPopover,
} = useHomeFilters(publishOptions, computed(() => listings.value))
const {
loading,
loadingMore,
listings,
totalListings,
zoneCounts,
hasMoreListings,
loadListingsPage,
zoneCount,
} = useListingQuery(filters, sortBy, activeZone)
const statCards = computed(() => [
{ label: '可租账号', value: `${totalListings.value}`, hint: '当前筛选结果' },
{
label: '高哈夫币',
value: `${zoneCount('highCoin')}`,
hint: '100M 以上',
},
{
label: '账密登录',
value: `${zoneCount('password')}`,
hint: '交接更快',
},
])
const zoneOptions = computed(() => [
{
key: 'all',
label: '全部专区',
hint: '当前可租账号',
count: zoneCount('all'),
},
{
key: 'sale',
label: '特惠专区',
hint: '价格更划算',
count: zoneCount('sale'),
},
{
key: 'gift',
label: '赠送专区',
hint: '含赠送物品',
count: zoneCount('gift'),
},
{
key: 'night',
label: '夜间专区',
hint: '夜间也好上号',
count: zoneCount('night'),
},
{
key: 'password',
label: '账密专区',
hint: '交接更快',
count: zoneCount('password'),
},
{
key: 'highCoin',
label: '高币专区',
hint: '100M 以上',
count: zoneCount('highCoin'),
},
])
async function loadHome() {
loading.value = true
try {
const [, config] = await Promise.all([
loadListingsPage(true),
fetchMobileHomeConfig(),
])
announcements.value = config.announcements
banners.value = config.banners
publishOptions.value = config.publish_options
} catch {
announcements.value = defaultHomeAnnouncements
banners.value = defaultHomeBanners
publishOptions.value = emptyListingPublishOptions
} finally {
loading.value = false
}
}
function updateFilters(partial: Partial<typeof filters>) {
Object.assign(filters, partial)
}
function handleResetFilters() {
resetFilters()
activeZone.value = 'all'
sortBy.value = 'recommended'
}
loadHome()
</script>
<template>
<section class="pc-home-redesign">
<HomeAnnouncement :announcements="announcements" />
<main class="home-content">
<div class="hero-section">
<HomeBanner :banners="banners" />
<HomeStats :stats="statCards" />
</div>
<HomeFilters
:filters="filters"
:total-listings="totalListings"
:publish-options="publishOptions"
:region-options="regionOptions"
:login-method-options="loginMethodOptions"
:skin-filter-groups="skinFilterGroups"
:skin-chip-label="skinChipLabel"
:active-filter-popover="activeFilterPopover"
@update:filters="updateFilters"
@reset="handleResetFilters"
@set-filter-popover="setFilterPopover"
@close-filter-popover="closeFilterPopover"
/>
<div class="list-head">
<div class="zone-head">
<p class="eyebrow">Account Zone</p>
<h2>账号专区</h2>
</div>
</div>
<HomeZonesAndSort
:zones="zoneOptions"
:active-zone="activeZone"
:sort-by="sortBy"
@update:active-zone="activeZone = $event"
@update:sort-by="sortBy = $event"
/>
<div v-if="!loading && listings.length === 0" class="home-empty-state">
<strong>没有符合条件的账号</strong>
<span>可以放宽筛选条件或切回全部专区。</span>
<button type="button" @click="handleResetFilters">重置条件</button>
</div>
<div v-else v-loading="loading" class="enhanced-desktop-list">
<ListingCard
v-for="item in listings"
:key="item.id"
:listing="item"
/>
</div>
<div v-if="!loading && listings.length" class="infinite-load-state">
<span v-if="loadingMore">正在加载更多账号...</span>
<span v-else-if="!hasMoreListings">已经到底了</span>
</div>
</main>
</section>
</template>
<style scoped>
.pc-home-redesign {
display: grid;
gap: 20px;
width: 100%;
max-width: 1720px;
min-width: 0;
margin: 0 auto;
overflow-x: clip;
}
.home-content {
display: grid;
gap: 20px;
min-width: 0;
}
.infinite-load-state {
padding: 6px 0 18px;
color: #94a3b8;
font-size: 13px;
font-weight: 700;
text-align: center;
}
.hero-section {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 20px;
min-width: 0;
}
.list-head {
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.zone-head {
display: flex;
flex-direction: column;
gap: 4px;
}
.eyebrow {
margin: 0;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #ff6a00;
}
.zone-head h2 {
margin: 0;
font-size: 28px;
font-weight: 900;
color: #17233d;
}
.enhanced-desktop-list {
display: grid;
gap: 16px;
min-width: 0;
}
.home-empty-state {
display: grid;
place-items: center;
gap: 8px;
min-height: 148px;
padding: 28px;
border: 1px dashed #d8e1ee;
border-radius: 16px;
background: #ffffff;
color: #7b8798;
text-align: center;
}
.home-empty-state strong {
color: #17233d;
font-size: 18px;
}
.home-empty-state span {
font-size: 13px;
}
.home-empty-state button {
height: 34px;
margin-top: 4px;
padding: 0 16px;
border: 1px solid #ff6a00;
border-radius: 8px;
background: #fff7ed;
color: #ff6a00;
font-weight: 800;
cursor: pointer;
}
</style>