Files
hfb_sys/frontend/src/features/listings/views/HomeView.vue
T
yml2213 e010f2ce7e chore: 批量更新导入路径到 features 架构
- 更新所有 admin API 文件导入路径
- 更新所有 features 中 Vue 文件的导入路径
- 修复 ChatAttachmentImage 组件导入
- 修复 auth 模块部分导入路径

剩余 ~190 个类型错误待修复(主要是缺失的模块)
2026-06-04 09:23:15 +08:00

263 lines
5.9 KiB
Vue

<script setup lang="ts">
import { computed, ref } from 'vue'
import { ElEmpty } from 'element-plus'
import {
defaultHomeAnnouncements,
defaultHomeBanners,
fetchMobileHomeConfig,
type HomeBannerSlide,
} from '@/api/homeConfig'
import {
emptyListingPublishOptions,
type ListingPublishOptions,
} from '@/features/listings/api/listingOptions'
import { useHomeFilters } from '@/composables/home/useHomeFilters'
import { useListingQuery } from '@/composables/home/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"
/>
<el-empty
v-if="!loading && listings.length === 0"
description="没有符合条件的账号"
/>
<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;
}
</style>