import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import api from '../api'; import type { BannerItem } from '../components/BannerCarousel.vue'; import { resolveBanners } from '../constants/defaultBanner'; import { resolveAnnouncements } from '../constants/defaultAnnouncement'; import { stripHtml } from '../utils/html'; export interface PlayerHomeMatch { id: string; leagueName?: string; leagueLogoUrl?: string | null; homeTeamName: string; awayTeamName: string; homeTeamCode?: string; awayTeamCode?: string; homeTeamLogoUrl?: string | null; awayTeamLogoUrl?: string | null; startTime: string; isHot?: boolean; displayOrder?: number; } export interface PlayerContentItem { id: string; contentType?: string; sortOrder?: number; createdAt?: string; linkType?: string | null; linkTarget?: string | null; translation?: { title?: string | null; body?: string | null; imageUrl?: string | null; }; } export type PlayerAnnouncementItem = PlayerContentItem; interface HomePayload { banners?: PlayerContentItem[]; announcements?: PlayerAnnouncementItem[]; ticker?: PlayerAnnouncementItem[]; notices?: PlayerAnnouncementItem[]; hotMatches?: PlayerHomeMatch[]; upcomingMatches?: PlayerHomeMatch[]; inboxEnabled?: boolean; } function mergeMatchList( existing: PlayerHomeMatch[] | undefined, fresh: PlayerHomeMatch[] | undefined, ): PlayerHomeMatch[] | undefined { if (!fresh) return existing; if (!existing) return fresh; const freshMap = new Map(fresh.map((m) => [m.id, m])); for (const m of existing) { const f = freshMap.get(m.id); if (f) Object.assign(m, f); } const existingIds = new Set(existing.map((m) => m.id)); for (const fm of fresh) { if (!existingIds.has(fm.id)) existing.push(fm); } for (let i = existing.length - 1; i >= 0; i--) { if (!freshMap.has(existing[i].id)) existing.splice(i, 1); } return existing; } const homeRaw = ref(null); const loading = ref(false); function collectAnnouncementLines(data: HomePayload | null): string[] { if (!data) return []; const source = data.announcements && data.announcements.length > 0 ? data.announcements : [...(data.ticker ?? []), ...(data.notices ?? [])]; const lines: string[] = []; for (const item of source) { const title = item.translation?.title?.trim(); const text = title || stripHtml(item.translation?.body ?? ''); if (text) lines.push(text); } return lines; } function collectAnnouncementItems(data: HomePayload | null): PlayerAnnouncementItem[] { if (!data) return []; const source = data.announcements && data.announcements.length > 0 ? data.announcements : [...(data.ticker ?? []), ...(data.notices ?? [])]; return source.filter((item) => item.translation?.title || item.translation?.body); } function collectHubContentItems(data: HomePayload | null): PlayerContentItem[] { if (!data) return []; const seen = new Set(); const merged: PlayerContentItem[] = []; const pushItems = (items: PlayerContentItem[] | undefined, fallbackType?: string) => { for (const item of items ?? []) { if (!item?.id || seen.has(item.id)) continue; const hasContent = item.translation?.title?.trim() || item.translation?.body?.trim() || item.translation?.imageUrl?.trim(); if (!hasContent) continue; seen.add(item.id); merged.push({ ...item, contentType: item.contentType || fallbackType, }); } }; pushItems(data.banners, 'BANNER'); pushItems(data.ticker, 'TICKER'); pushItems(data.notices, 'NOTICE'); pushItems(data.announcements); merged.sort((a, b) => { const timeA = a.createdAt ? Date.parse(a.createdAt) : 0; const timeB = b.createdAt ? Date.parse(b.createdAt) : 0; if (timeB !== timeA) return timeB - timeA; return (a.sortOrder ?? 0) - (b.sortOrder ?? 0); }); return merged; } /** 管理端公共内容 → 玩家端首页/跑马灯(单例,避免重复请求) */ export function usePlayerHome() { const { t } = useI18n(); async function load(force = false) { if (!force && homeRaw.value) return; loading.value = true; try { const { data } = await api.get('/player/home'); const fresh = (data.data ?? null) as HomePayload | null; if (fresh && homeRaw.value) { // 已有数据 → 原地更新,保留对象引用,避免图片重新加载 const existing = homeRaw.value; existing.banners = fresh.banners; existing.announcements = fresh.announcements; existing.ticker = fresh.ticker; existing.notices = fresh.notices; existing.inboxEnabled = fresh.inboxEnabled; existing.hotMatches = mergeMatchList(existing.hotMatches, fresh.hotMatches); existing.upcomingMatches = mergeMatchList(existing.upcomingMatches, fresh.upcomingMatches); } else { homeRaw.value = fresh; } } catch { homeRaw.value = null; } finally { loading.value = false; } } const banners = computed(() => resolveBanners(homeRaw.value?.banners as BannerItem[] | undefined)); const bannerItems = computed(() => homeRaw.value?.banners ?? []); const announcements = computed(() => resolveAnnouncements(collectAnnouncementLines(homeRaw.value), t('home.announcement_default')), ); const announcementItems = computed(() => collectAnnouncementItems(homeRaw.value)); const hubContentItems = computed(() => collectHubContentItems(homeRaw.value)); const hotMatches = computed(() => homeRaw.value?.hotMatches ?? []); const upcomingMatches = computed(() => homeRaw.value?.upcomingMatches ?? []); return { homeRaw, loading, load, banners, bannerItems, announcements, announcementItems, hubContentItems, hotMatches, upcomingMatches, }; }