Files
thebet365/apps/player/src/composables/usePlayerHome.ts
Mars 567ec9ec8a feat: 管理端 RBAC 权限体系与员工管理
新增多角色权限控制(赛事/财务/客服管理员),支持员工 CRUD、路由菜单按权限显隐、审计日志范围过滤;登录返回角色与权限列表。玩家端赛事列表增加静默刷新避免图片闪烁。Seed 补充演示员工账号与充值相关权限。附带 RBAC/审计范围单元测试及 UAT 文档更新。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 17:52:39 +08:00

112 lines
3.5 KiB
TypeScript

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';
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;
}
interface HomePayload {
banners?: BannerItem[];
announcements?: Array<{ translation?: { title?: string; body?: string } }>;
ticker?: Array<{ translation?: { title?: string; body?: string } }>;
notices?: Array<{ translation?: { title?: string; body?: string } }>;
hotMatches?: PlayerHomeMatch[];
}
const homeRaw = ref<HomePayload | null>(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 text = item.translation?.title || item.translation?.body;
if (text) lines.push(text);
}
return lines;
}
/** 管理端公共内容 → 玩家端首页/跑马灯(单例,避免重复请求) */
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;
if (fresh.hotMatches && existing.hotMatches) {
const freshMap = new Map(fresh.hotMatches.map((m) => [m.id, m]));
for (const m of existing.hotMatches) {
const f = freshMap.get(m.id);
if (f) Object.assign(m, f);
}
// 处理新增或删除的比赛
const existingIds = new Set(existing.hotMatches.map((m) => m.id));
for (const fm of fresh.hotMatches) {
if (!existingIds.has(fm.id)) existing.hotMatches.push(fm);
}
for (let i = existing.hotMatches.length - 1; i >= 0; i--) {
if (!freshMap.has(existing.hotMatches[i].id)) {
existing.hotMatches.splice(i, 1);
}
}
} else {
existing.hotMatches = fresh.hotMatches;
}
} else {
homeRaw.value = fresh;
}
} catch {
homeRaw.value = null;
} finally {
loading.value = false;
}
}
const banners = computed(() => resolveBanners(homeRaw.value?.banners));
const announcements = computed(() =>
resolveAnnouncements(collectAnnouncementLines(homeRaw.value), t('home.announcement_default')),
);
const hotMatches = computed(() => homeRaw.value?.hotMatches ?? []);
return {
homeRaw,
loading,
load,
banners,
announcements,
hotMatches,
};
}