feat(distribution): 分站首页改为联名标题并同步手机轮播

首页主标题显示为站名×大锤商行,手机端使用与电脑端相同的分站自定义海报。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-10-05 14:46:28 +08:00
co-authored by Cursor
parent 1d21b3f548
commit 4f69657017
5 changed files with 96 additions and 28 deletions
@@ -40,7 +40,7 @@ import HomeBanner from '../components/HomeBanner.vue'
import HomeFilters from '../components/HomeFilters.vue'
import HomeZones from '../components/HomeZones.vue'
import ListingCard from '../components/ListingCard.vue'
import { useDistributionSiteStore } from '@/stores/distributionSite'
import { mapDistributionHomeBanners, useDistributionSiteStore } from '@/stores/distributionSite'
const siteStore = useDistributionSiteStore()
@@ -59,28 +59,8 @@ const displayAnnouncements = computed(() => {
})
const banners = ref<HomeBannerSlide[]>(defaultHomeBanners)
const displayBanners = computed<HomeBannerSlide[]>(() => {
if (siteStore.isDistribution && siteStore.banners && siteStore.banners.length > 0) {
return siteStore.banners.map(b => {
const imageUrl = b.image_url?.trim() || ''
// 整张海报已含文案。再叠标题会触发整幅压暗遮罩,左右两侧颜色会被压暗。
if (imageUrl) {
return {
eyebrow: '',
title: '',
badge: '本店精选',
pill: '',
tone: 'gold',
image_url: imageUrl,
}
}
return {
eyebrow: siteStore.siteName,
title: b.title || '精选游戏账号 · 随租随玩',
badge: '本店精选',
pill: '官方保障',
tone: 'gold',
}
})
if (siteStore.isDistribution && siteStore.banners.length > 0) {
return mapDistributionHomeBanners(siteStore.banners, siteStore.siteName)
}
return banners.value
})
@@ -461,7 +441,7 @@ if (!restored) {
<span class="dist-pulse"></span>
{{ siteStore.storefrontText.hero_badge }}
</div>
<h2 class="dist-portal-title">{{ siteStore.siteName }}</h2>
<h2 class="dist-portal-title">{{ siteStore.coBrandName }}</h2>
<p class="dist-portal-desc">{{ siteStore.storefrontText.hero_desc }}</p>
</div>
<div class="dist-portal-badges">
@@ -328,6 +328,15 @@
object-fit: cover;
}
.mobile-banner.has-image:not(.has-copy) {
background: #0f172a;
}
.mobile-banner.has-image:not(.has-copy) .banner-image {
object-fit: contain;
background: #0f172a;
}
.mobile-banner.has-image.has-copy::after {
position: absolute;
inset: 0;
@@ -60,7 +60,7 @@ import {
getListingResources,
} from '@/shared/utils/listingDisplay'
import { buildMobileHomeListSignature, useMobileHomeCacheStore } from '@/stores/mobileHomeCache'
import { useDistributionSiteStore } from '@/stores/distributionSite'
import { mapDistributionHomeBanners, useDistributionSiteStore } from '@/stores/distributionSite'
import { useSessionStore } from '@/stores/session'
const router = useRouter()
@@ -97,6 +97,12 @@ const displayAnnouncements = computed(() => {
return announcements.value
})
const bannerSlides = ref<HomeBannerSlide[]>(defaultHomeBanners)
const displayBanners = computed<HomeBannerSlide[]>(() => {
if (siteStore.isDistribution && siteStore.banners.length > 0) {
return mapDistributionHomeBanners(siteStore.banners, siteStore.siteName)
}
return bannerSlides.value
})
const awRecycle = ref<AWRecycleConfig>({ ...defaultAWRecycleConfig })
const awRecyclePreviewVisible = ref(false)
const cooperationFeedback = ref<CooperationFeedbackConfig>({ ...defaultCooperationFeedbackConfig })
@@ -507,7 +513,7 @@ function openCooperationFeedback() {
function openBannerPreview(slide: HomeBannerSlide) {
const url = slide.image_url?.trim()
if (!url) return
const images = bannerSlides.value
const images = displayBanners.value
.map(item => item.image_url?.trim() || '')
.filter((item): item is string => Boolean(item))
const startPosition = images.indexOf(url)
@@ -903,14 +909,14 @@ if (!restoreHomeConfigFromCache()) {
<div v-if="siteStore.isDistribution" class="mobile-dist-card">
<div class="dist-card-top">
<span class="dist-card-badge">{{ siteStore.storefrontText.hero_badge }}</span>
<strong class="dist-card-name">{{ siteStore.siteName }}</strong>
<strong class="dist-card-name">{{ siteStore.coBrandName }}</strong>
</div>
<p class="dist-card-sub">{{ siteStore.storefrontText.card_sub }}</p>
</div>
<!-- Banner 轮播 -->
<van-swipe class="banner-swipe" :autoplay="3600" lazy-render>
<van-swipe-item v-for="slide in bannerSlides" :key="slide.title || slide.image_url">
<van-swipe-item v-for="slide in displayBanners" :key="slide.image_url || slide.title">
<div
class="mobile-banner"
:class="[
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest'
import { formatDistributionCoBrandName, mapDistributionHomeBanners } from './distributionSite'
describe('formatDistributionCoBrandName', () => {
it('joins a distribution site with the main brand', () => {
expect(formatDistributionCoBrandName('天域商行', true)).toBe('天域商行×大锤商行')
})
it('keeps the main site name', () => {
expect(formatDistributionCoBrandName('大锤商行', false)).toBe('大锤商行')
})
})
describe('mapDistributionHomeBanners', () => {
it('uses the custom image without overlay copy', () => {
const [slide] = mapDistributionHomeBanners(
[{ image_url: 'https://cdn.example/poster.webp', title: '海报标题' }],
'天域商行'
)
expect(slide?.image_url).toBe('https://cdn.example/poster.webp')
expect(slide?.title).toBe('')
expect(slide?.eyebrow).toBe('')
expect(slide?.pill).toBe('')
})
})
+48
View File
@@ -14,6 +14,50 @@ export interface DistributionBanner {
title?: string
}
export interface DistributionHomeBanner {
eyebrow: string
title: string
badge: string
pill: string
tone: string
image_url?: string
}
/** 分站首页主标题:站名与主站联名。主站或站名已含主站名时保持原样。 */
export function formatDistributionCoBrandName(siteName: string, isDistribution: boolean): string {
const name = siteName.trim()
if (!isDistribution) return name || '大锤商行'
if (!name || name.includes('大锤商行')) return name || '大锤商行'
return `${name}×大锤商行`
}
/** 分站首页轮播:有图时不再叠标题,避免整幅压暗。 */
export function mapDistributionHomeBanners(
banners: DistributionBanner[],
siteName: string
): DistributionHomeBanner[] {
return banners.map(banner => {
const imageUrl = banner.image_url?.trim() || ''
if (imageUrl) {
return {
eyebrow: '',
title: '',
badge: '本店精选',
pill: '',
tone: 'gold',
image_url: imageUrl,
}
}
return {
eyebrow: siteName,
title: banner.title?.trim() || '精选游戏账号 · 随租随玩',
badge: '本店精选',
pill: '官方保障',
tone: 'gold',
}
})
}
export interface DistributionFooterInfo {
icp?: string
copyright?: string
@@ -92,6 +136,10 @@ export const useDistributionSiteStore = defineStore('distributionSite', {
brandMark(state): string {
return state.isDistribution && state.siteName ? state.siteName.slice(0, 1) : '锤'
},
/** 分站首页主标题:站名与主站联名。 */
coBrandName(state): string {
return formatDistributionCoBrandName(state.siteName, state.isDistribution)
},
},
actions: {