重构首页账号专区组件,拆分排序控件

将 HomeZonesAndSort 拆分为独立的 HomeZones 专区组件,
排序控件内联至列表头部,专区改为响应式网格 tab 布局。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
yml2213
2026-06-17 07:43:51 +08:00
co-authored by Claude Opus 4.8
parent 4d54f1b03a
commit fa2d245788
3 changed files with 179 additions and 163 deletions
@@ -0,0 +1,168 @@
<script setup lang="ts">
import type { Component } from 'vue'
import { Coin, Discount, Grid, Key, Moon, Present } from '@element-plus/icons-vue'
interface ZoneOption {
key: string
label: string
hint: string
count: number
}
interface Props {
zones: ZoneOption[]
activeZone: string
}
defineProps<Props>()
const emit = defineEmits<{
'update:activeZone': [value: string]
}>()
const zoneIcons: Record<string, Component> = {
all: Grid,
sale: Discount,
gift: Present,
night: Moon,
password: Key,
highCoin: Coin,
}
function selectZone(key: string) {
emit('update:activeZone', key)
}
</script>
<template>
<div class="zone-tabs">
<button
v-for="zone in zones"
:key="zone.key"
type="button"
class="zone-tab"
:class="{ active: activeZone === zone.key }"
@click="selectZone(zone.key)"
>
<span class="zone-icon" aria-hidden="true">
<el-icon><component :is="zoneIcons[zone.key] || Grid" /></el-icon>
</span>
<span class="zone-title">
<strong>{{ zone.label }}</strong>
<span class="zone-count" :class="{ 'is-empty': zone.count === 0 }">{{ zone.count }}</span>
</span>
<small>{{ zone.hint }}</small>
</button>
</div>
</template>
<style scoped>
.zone-tabs {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 12px;
min-width: 0;
}
.zone-tab {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 0;
padding: 14px;
border: 1px solid #eef1f5;
border-radius: 14px;
background: #ffffff;
cursor: pointer;
text-align: left;
transition:
border-color 0.18s ease,
background-color 0.18s ease,
box-shadow 0.18s ease,
transform 0.18s ease;
}
.zone-tab:hover {
border-color: #ffd4b0;
background: #fffaf5;
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(255, 106, 0, 0.08);
}
.zone-tab.active {
border-color: #ff6a00;
background: #fff7ed;
box-shadow: 0 6px 16px rgba(255, 106, 0, 0.1);
}
.zone-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 9px;
background: #f3f5f9;
color: #94a3b8;
font-size: 18px;
transition:
background-color 0.18s ease,
color 0.18s ease;
}
.zone-tab:hover .zone-icon,
.zone-tab.active .zone-icon {
background: #ffe6d2;
color: #ff6a00;
}
.zone-title {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
min-width: 0;
}
.zone-title strong {
overflow: hidden;
font-size: 15px;
font-weight: 700;
color: #17233d;
text-overflow: ellipsis;
white-space: nowrap;
}
.zone-count {
flex-shrink: 0;
font-size: 15px;
font-weight: 800;
color: #ff6a00;
font-variant-numeric: tabular-nums;
}
.zone-count.is-empty {
color: #c0c8d4;
font-weight: 700;
}
.zone-tab small {
overflow: hidden;
font-size: 12px;
color: #7b8798;
text-overflow: ellipsis;
white-space: nowrap;
}
@media (max-width: 900px) {
.zone-tabs {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 560px) {
.zone-tabs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>
@@ -1,159 +0,0 @@
<script setup lang="ts">
interface ZoneOption {
key: string
label: string
hint: string
count: number
}
interface Props {
zones: ZoneOption[]
activeZone: string
sortBy: string
}
defineProps<Props>()
const emit = defineEmits<{
'update:activeZone': [value: string]
'update:sortBy': [value: string]
}>()
function selectZone(key: string) {
emit('update:activeZone', key)
}
function updateSort(value: string | number | boolean) {
emit('update:sortBy', String(value))
}
</script>
<template>
<div class="zones-and-sort">
<div class="zone-tabs">
<button
v-for="zone in zones"
:key="zone.key"
type="button"
class="zone-tab"
:class="{ active: activeZone === zone.key }"
@click="selectZone(zone.key)"
>
<div class="zone-main">
<strong>{{ zone.label }}</strong>
<span class="zone-count">{{ zone.count }}</span>
</div>
<small>{{ zone.hint }}</small>
</button>
</div>
<div class="list-actions">
<el-radio-group :model-value="sortBy" size="small" @update:model-value="updateSort">
<el-radio-button label="recommended">综合推荐</el-radio-button>
<el-radio-button label="coinDesc">哈夫币</el-radio-button>
<el-radio-button label="awmDesc">AWM数量</el-radio-button>
<el-radio-button label="priceAsc">价格最低</el-radio-button>
<el-radio-button label="priceDesc">价格最高</el-radio-button>
</el-radio-group>
</div>
</div>
</template>
<style scoped>
.zones-and-sort {
display: grid;
grid-template-columns: minmax(0, 1fr) max-content;
gap: 14px;
align-items: end;
min-width: 0;
}
.zone-tabs {
display: grid;
grid-template-columns: repeat(6, minmax(108px, 1fr));
gap: 10px;
min-width: 0;
}
.zone-tab {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
min-height: 72px;
padding: 12px 14px;
border: 2px solid #e2e8f0;
border-radius: 12px;
background: #ffffff;
cursor: pointer;
transition: all 0.2s;
}
.zone-tab:hover {
border-color: #ff6a00;
background: #fff7ed;
}
.zone-tab.active {
border-color: #ff6a00;
background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
}
.zone-main {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 8px;
}
.zone-tab strong {
overflow: hidden;
font-size: 15px;
font-weight: 700;
color: #17233d;
text-overflow: ellipsis;
white-space: nowrap;
}
.zone-count {
font-size: 16px;
font-weight: 900;
color: #ff6a00;
}
.zone-tab small {
overflow: hidden;
font-size: 11px;
color: #7b8798;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
}
.list-actions {
display: flex;
justify-content: flex-end;
min-width: max-content;
padding-bottom: 2px;
}
.list-actions :deep(.el-radio-group) {
white-space: nowrap;
}
@media (max-width: 1380px) {
.zones-and-sort {
grid-template-columns: 1fr;
align-items: stretch;
}
.list-actions {
justify-content: flex-start;
}
}
@media (max-width: 900px) {
.zone-tabs {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>
@@ -30,7 +30,7 @@ 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 HomeZones from '../components/HomeZones.vue'
import ListingCard from '../components/ListingCard.vue'
const announcements = ref<string[]>(defaultHomeAnnouncements)
@@ -270,14 +270,21 @@ loadHome()
<p class="eyebrow">Account Zone</p>
<h2>账号专区</h2>
</div>
<div class="list-actions">
<el-radio-group v-model="sortBy" size="small">
<el-radio-button label="recommended">综合推荐</el-radio-button>
<el-radio-button label="coinDesc">哈夫币</el-radio-button>
<el-radio-button label="awmDesc">AWM数量</el-radio-button>
<el-radio-button label="priceAsc">价格最低</el-radio-button>
<el-radio-button label="priceDesc">价格最高</el-radio-button>
</el-radio-group>
</div>
</div>
<HomeZonesAndSort
<HomeZones
: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">