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); } /** 管理端公共内容 → 玩家端首页/跑马灯(单例,避免重复请求) */ 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 hotMatches = computed(() => homeRaw.value?.hotMatches ?? []); const upcomingMatches = computed(() => homeRaw.value?.upcomingMatches ?? []); return { homeRaw, loading, load, banners, bannerItems, announcements, announcementItems, hotMatches, upcomingMatches, }; }