perf(listings): 首页仅展示单张纯币截图,去除悬停轮播以降低OSS并发请求

This commit is contained in:
yml2213
2026-09-06 20:07:31 +08:00
parent 9ed63e9aec
commit 87aa0b0a2b
3 changed files with 26 additions and 49 deletions
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed } from 'vue'
import { RouterLink } from 'vue-router'
import type { Listing } from '@/features/listings'
import { formatCent, formatMoney } from '@/shared/utils/money'
@@ -22,6 +22,7 @@ import {
formatHafCoinM,
readAssetString,
isTopTierInsurance,
getCoinScreenshotURL,
type ListingDisplayResource,
} from '@/shared/utils/listingDisplay'
@@ -31,37 +32,8 @@ interface Props {
const props = defineProps<Props>()
// 多图悬浮轮播(Hover Carousel)
const activeImageIndex = ref(0)
const screenshotList = computed(() => {
const urls = props.listing.screenshot_urls
if (Array.isArray(urls) && urls.length > 0) {
return urls.filter(Boolean)
}
if (props.listing.cover_url) {
return [props.listing.cover_url]
}
return []
})
const activeCoverURL = computed(() => {
if (screenshotList.value.length === 0) return ''
return screenshotList.value[activeImageIndex.value] || screenshotList.value[0] || ''
})
function handleCoverMouseMove(e: MouseEvent) {
const total = screenshotList.value.length
if (total <= 1) return
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
const offsetX = Math.max(0, Math.min(e.clientX - rect.left, rect.width))
const ratio = offsetX / rect.width
const nextIdx = Math.min(total - 1, Math.floor(ratio * total))
activeImageIndex.value = nextIdx
}
function handleCoverMouseLeave() {
activeImageIndex.value = 0
}
// 封面图片:仅展示纯币截图单张,避免 OSS 爆发高频请求错误
const coverURL = computed(() => getCoinScreenshotURL(props.listing))
// 基础元信息
const listingCode = computed(() => formatListingCode(props.listing))
@@ -129,11 +101,11 @@ function formatStatNumber(value: number) {
<template>
<RouterLink class="hfb-listing-card" :to="`/listings/${listing.id}`">
<!-- 左侧:4:3 黄金比例封面 + 悬浮多图轮播 -->
<div class="card-media" @mousemove="handleCoverMouseMove" @mouseleave="handleCoverMouseLeave">
<!-- 左侧:4:3 黄金比例单张封面(展示纯币截图) -->
<div class="card-media">
<img
v-if="activeCoverURL"
:src="activeCoverURL"
v-if="coverURL"
:src="coverURL"
:alt="getListingTitle(listing)"
loading="lazy"
decoding="async"
@@ -148,17 +120,6 @@ function formatStatNumber(value: number) {
<!-- 左下角黑金比例浮层 -->
<div v-if="ratioValue > 0" class="media-ratio">比例 1:{{ ratioValue.toFixed(1) }}</div>
<!-- 多图小圆点指示器 -->
<div v-if="screenshotList.length > 1" class="media-indicators">
<span
v-for="(_, index) in screenshotList"
:key="index"
class="indicator-dot"
:class="{ active: index === activeImageIndex }"
@mouseenter.stop="activeImageIndex = index"
/>
</div>
</div>
<!-- 中间:双栏结构化信息区(左基础指标 + 右高光装备物资展台) -->
@@ -53,6 +53,7 @@ import {
hasAcceleratedSaleRatio,
hasGiftResources,
isTopTierInsurance,
getCoinScreenshotURL,
getCoinWan,
formatHafCoinM,
readAssetString,
@@ -1046,8 +1047,8 @@ syncMobileHomeQuery()
<!-- 左侧封面 -->
<div class="mobile-card-media">
<img
v-if="item.cover_url || item.screenshot_urls?.[0]"
:src="item.cover_url || item.screenshot_urls?.[0]"
v-if="getCoinScreenshotURL(item)"
:src="getCoinScreenshotURL(item)"
:alt="formatListingCode(item)"
loading="lazy"
decoding="async"
@@ -409,3 +409,18 @@ export function isTopTierInsurance(value: string | undefined | null): boolean {
s.includes('3*3') || s.includes('3x3') || s.includes('3×3') || s === '9格' || s.includes('9格')
)
}
/**
* 获取账号封面/纯币截图(仅展示一张,优先纯币截图,其次封面图,其次首张截图)
*/
export function getCoinScreenshotURL(item: Listing): string {
const groups = item.asset_summary?.screenshot_groups
if (typeof groups === 'object' && groups !== null) {
const coinUrls = (groups as Record<string, unknown>)['coin']
if (Array.isArray(coinUrls)) {
const firstCoin = coinUrls.find(url => typeof url === 'string' && Boolean(url))
if (firstCoin) return firstCoin
}
}
return item.cover_url || item.screenshot_urls?.[0] || ''
}