优化首页账号展示体验

This commit is contained in:
yml2213
2026-06-04 12:57:34 +08:00
parent 75e849a91a
commit e41d3a923a
6 changed files with 157 additions and 23 deletions
+2
View File
@@ -17,6 +17,8 @@ declare module 'vue' {
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb'] ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup'] ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Bell } from '@element-plus/icons-vue' import { Bell } from '@element-plus/icons-vue'
import { ElCarousel, ElCarouselItem, ElIcon } from 'element-plus'
interface Props { interface Props {
announcements: string[] announcements: string[]
@@ -32,6 +31,7 @@ defineProps<Props>()
align-items: center; align-items: center;
gap: 12px; gap: 12px;
padding: 10px 20px; padding: 10px 20px;
min-width: 0;
color: #854d0e; color: #854d0e;
background: #fefce8; background: #fefce8;
border: 1px solid #fef08a; border: 1px solid #fef08a;
@@ -41,5 +41,18 @@ defineProps<Props>()
.home-announcement :deep(.el-carousel) { .home-announcement :deep(.el-carousel) {
flex: 1; flex: 1;
min-width: 0;
}
.home-announcement :deep(.el-carousel__container) {
overflow: hidden;
}
.home-announcement span {
display: block;
overflow: hidden;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
} }
</style> </style>
@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { ElCarousel, ElCarouselItem } from 'element-plus'
interface HomeBannerSlide { interface HomeBannerSlide {
title?: string title?: string
eyebrow?: string eyebrow?: string
@@ -47,6 +45,20 @@ defineProps<Props>()
min-width: 0; min-width: 0;
border-radius: 16px; border-radius: 16px;
overflow: hidden; overflow: hidden;
background: #ffffff;
}
.hero-board :deep(.el-carousel) {
min-width: 0;
}
.hero-board :deep(.el-carousel__container) {
height: 240px;
overflow: hidden;
}
.hero-board :deep(.el-carousel__item) {
height: 100%;
} }
.hero-slide { .hero-slide {
@@ -55,7 +67,13 @@ defineProps<Props>()
display: flex; display: flex;
align-items: center; align-items: center;
padding: 40px; padding: 40px;
background: #f8fafc; overflow: hidden;
color: #17233d;
background: linear-gradient(135deg, #eff6ff 0%, #ffffff 58%, #fff7ed 100%);
}
.hero-slide.tone-blue {
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 56%, #fff7ed 100%);
} }
.hero-slide.tone-orange { .hero-slide.tone-orange {
@@ -77,7 +95,7 @@ defineProps<Props>()
content: ''; content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
background: linear-gradient(90deg, rgba(0, 0, 0, 0.6) 0%, transparent 60%); background: linear-gradient(90deg, rgba(15, 23, 42, 0.74) 0%, rgba(15, 23, 42, 0.24) 62%, transparent 100%);
} }
.hero-copy { .hero-copy {
@@ -86,22 +104,50 @@ defineProps<Props>()
max-width: 480px; max-width: 480px;
} }
.hero-copy span {
color: #ff6a00;
font-size: 13px;
font-weight: 900;
}
.hero-slide.has-image .hero-copy { .hero-slide.has-image .hero-copy {
color: #ffffff; color: #ffffff;
} }
.hero-slide.has-image .hero-copy span {
color: #fed7aa;
}
.hero-copy h1 { .hero-copy h1 {
margin: 12px 0; margin: 12px 0;
font-size: 32px; font-size: 32px;
font-weight: 900;
line-height: 1.2; line-height: 1.2;
} }
.hero-copy p { .hero-copy p {
display: inline-block; display: inline-block;
margin: 0;
padding: 6px 12px; padding: 6px 12px;
border-radius: 999px; border-radius: 999px;
background: rgba(255, 255, 255, 0.2); color: #1e293b;
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
font-size: 14px; font-size: 14px;
font-weight: 800;
}
.hero-slide em {
position: absolute;
top: 22px;
right: 24px;
z-index: 1;
padding: 6px 10px;
border-radius: 999px;
color: #ffffff;
background: #ff6a00;
font-size: 12px;
font-style: normal;
font-weight: 900;
} }
</style> </style>
@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { ElRadioGroup, ElRadioButton } from 'element-plus'
interface ZoneOption { interface ZoneOption {
key: string key: string
label: string label: string
@@ -24,6 +22,10 @@ const emit = defineEmits<{
function selectZone(key: string) { function selectZone(key: string) {
emit('update:activeZone', key) emit('update:activeZone', key)
} }
function updateSort(value: string | number | boolean) {
emit('update:sortBy', String(value))
}
</script> </script>
<template> <template>
@@ -45,7 +47,7 @@ function selectZone(key: string) {
</button> </button>
</div> </div>
<div class="list-actions"> <div class="list-actions">
<el-radio-group :model-value="sortBy" size="small" @update:model-value="(val) => emit('update:sortBy', val as string)"> <el-radio-group :model-value="sortBy" size="small" @update:model-value="updateSort">
<el-radio-button label="recommended">综合推荐</el-radio-button> <el-radio-button label="recommended">综合推荐</el-radio-button>
<el-radio-button label="coinDesc">哈夫币</el-radio-button> <el-radio-button label="coinDesc">哈夫币</el-radio-button>
<el-radio-button label="awmDesc">AWM数量</el-radio-button> <el-radio-button label="awmDesc">AWM数量</el-radio-button>
@@ -58,22 +60,27 @@ function selectZone(key: string) {
<style scoped> <style scoped>
.zones-and-sort { .zones-and-sort {
display: flex; display: grid;
flex-direction: column; grid-template-columns: minmax(0, 1fr) max-content;
gap: 16px; gap: 14px;
align-items: end;
min-width: 0;
} }
.zone-tabs { .zone-tabs {
display: grid; display: grid;
grid-template-columns: repeat(6, 1fr); grid-template-columns: repeat(6, minmax(108px, 1fr));
gap: 12px; gap: 10px;
min-width: 0;
} }
.zone-tab { .zone-tab {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 6px;
padding: 14px; min-width: 0;
min-height: 72px;
padding: 12px 14px;
border: 2px solid #e2e8f0; border: 2px solid #e2e8f0;
border-radius: 12px; border-radius: 12px;
background: #ffffff; background: #ffffff;
@@ -99,9 +106,12 @@ function selectZone(key: string) {
} }
.zone-tab strong { .zone-tab strong {
overflow: hidden;
font-size: 15px; font-size: 15px;
font-weight: 700; font-weight: 700;
color: #17233d; color: #17233d;
text-overflow: ellipsis;
white-space: nowrap;
} }
.zone-count { .zone-count {
@@ -111,12 +121,39 @@ function selectZone(key: string) {
} }
.zone-tab small { .zone-tab small {
overflow: hidden;
font-size: 11px; font-size: 11px;
color: #7b8798; color: #7b8798;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
} }
.list-actions { .list-actions {
display: flex; display: flex;
justify-content: flex-end; 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> </style>
@@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import type { Listing } from '@/features/listings' import type { Listing } from '@/features/listings'
import { import {
@@ -24,18 +25,19 @@ interface Props {
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const coverURL = computed(() => props.listing.cover_url || props.listing.screenshot_urls?.[0] || '')
</script> </script>
<template> <template>
<RouterLink class="listing-card-v2" :to="`/listings/${listing.id}`"> <RouterLink class="listing-card-v2" :to="`/listings/${listing.id}`">
<div class="card-cover"> <div class="card-cover">
<img <img
v-if="listing.cover_url" v-if="coverURL"
:data-src="listing.cover_url" :src="coverURL"
:alt="getListingTitle(listing)" :alt="getListingTitle(listing)"
loading="lazy" loading="lazy"
decoding="async" decoding="async"
class="lazy-image"
/> />
<div v-else class="empty-cover">HFB</div> <div v-else class="empty-cover">HFB</div>
<div class="cover-badges"> <div class="cover-badges">
@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { ElEmpty } from 'element-plus'
import { import {
defaultHomeAnnouncements, defaultHomeAnnouncements,
defaultHomeBanners, defaultHomeBanners,
@@ -174,10 +173,11 @@ loadHome()
@update:sort-by="sortBy = $event" @update:sort-by="sortBy = $event"
/> />
<el-empty <div v-if="!loading && listings.length === 0" class="home-empty-state">
v-if="!loading && listings.length === 0" <strong>没有符合条件的账号</strong>
description="没有符合条件的账号" <span>可以放宽筛选条件或切回全部专区</span>
/> <button type="button" @click="handleResetFilters">重置条件</button>
</div>
<div v-else v-loading="loading" class="enhanced-desktop-list"> <div v-else v-loading="loading" class="enhanced-desktop-list">
<ListingCard <ListingCard
v-for="item in listings" v-for="item in listings"
@@ -259,4 +259,38 @@ loadHome()
gap: 16px; gap: 16px;
min-width: 0; 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> </style>