/** * OmniCard AI (CRMmp) - 展会团队数据业务驱动层 * 并行汇总线索分页与雷达足迹,产出数字摘要 */ import { LeadEntity, OwnerPageData, OwnerPageQuery, RadarTrack, TeamRankItem, } from '../../../common/types.uts'; import { fetchCurrentEvent, fetchOwnerLeadPage, fetchOwnerRadarPage, fetchTeamLiveRank, } from '../../../common/api.uts'; import { getCurrentLocale, getOmniText } from '../../../common/locale.uts'; export type OverviewCopyKey = | 'title' | 'desc' | 'totalLeads' | 'hotCount' | 'totalTracks' | 'topCountry' | 'recent' | 'refresh' | 'empty' | 'error' | 'loading' | 'unknownCountry' | 'rankTitle' | 'liveBadge' | 'syncTime' | 'leadsUnit' | 'touchesUnit' | 'scoreUnit' | 'championBadge' | 'emptyLeads'; export type OverviewSummary = { totalLeads: number; hotCount: number; totalTracks: number; topCountry: string; }; export type LoadOverviewInput = { page?: number; size?: number; cardId?: string | number; }; export type LoadOverviewApi = { fetchOwnerLeadPage?: ( query: OwnerPageQuery ) => Promise>; fetchOwnerRadarPage?: ( query: OwnerPageQuery ) => Promise>; fetchTeamLiveRank?: (eventId?: string) => Promise; fetchCurrentEvent?: () => Promise<{ code?: string } | null>; }; export type LoadOverviewResult = { leads: LeadEntity[]; tracks: RadarTrack[]; ranks: TeamRankItem[]; summary: OverviewSummary; empty: boolean; errorMessage: string; }; export type OverviewState = { leads: LeadEntity[]; tracks: RadarTrack[]; ranks: TeamRankItem[]; summary: OverviewSummary; loading: boolean; empty: boolean; errorMessage: string; }; const DEFAULT_PAGE = 1; const DEFAULT_SIZE = 20; function isZhLocale(): boolean { return getCurrentLocale() === 'zh-CN'; } /** * 页面文案:线索 / 足迹走词典,其余中英本地词典 */ export function resolveOverviewText(key: OverviewCopyKey): string { const locale = getCurrentLocale(); if (key === 'totalLeads') { return getOmniText(locale, 'crm.leads'); } if (key === 'totalTracks') { return getOmniText(locale, 'radar.visitor_trace'); } if (key === 'recent') { return getOmniText(locale, 'crm.follow_up'); } if (key === 'empty') { return getOmniText(locale, 'common.empty'); } if (key === 'error') { return getOmniText(locale, 'common.error'); } if (key === 'loading') { return getOmniText(locale, 'common.loading'); } const zh = isZhLocale(); if (key === 'title') { return zh ? '展会数据' : 'Expo Data'; } if (key === 'desc') { return zh ? '汇总线索与足迹' : 'Leads and visitor trail at a glance'; } if (key === 'hotCount') { return zh ? '高意向' : 'Hot leads'; } if (key === 'topCountry') { return zh ? '主要国家' : 'Top country'; } if (key === 'refresh') { return zh ? '刷新数据' : 'Refresh Data'; } if (key === 'unknownCountry') { return zh ? '未标注' : 'Unspecified'; } if (key === 'rankTitle') { return zh ? '现场排行' : 'Floor ranking'; } if (key === 'liveBadge') { return zh ? '实时展会数据' : 'LIVE EXPO'; } if (key === 'syncTime') { return zh ? '已同步' : 'Synced'; } if (key === 'leadsUnit') { return zh ? '转化线索' : 'Leads'; } if (key === 'touchesUnit') { return zh ? '交互足迹' : 'Touches'; } if (key === 'scoreUnit') { return zh ? '分' : 'pts'; } if (key === 'championBadge') { return zh ? '冠军' : 'TOP 1'; } if (key === 'emptyLeads') { return zh ? '暂无新线索' : 'No new leads yet'; } return ''; } function normalizePage(raw: number | null | undefined): number { if (raw == null || !Number.isFinite(raw) || raw < 1) { return DEFAULT_PAGE; } return Math.floor(raw); } function normalizeSize(raw: number | null | undefined): number { if (raw == null || !Number.isFinite(raw) || raw < 1) { return DEFAULT_SIZE; } return Math.floor(raw); } function parseList(payload: OwnerPageData | null | undefined): T[] { if (payload == null || payload.list == null) { return []; } if (!Array.isArray(payload.list)) { return []; } return payload.list; } function readErrorMessage(err: unknown): string { if (err instanceof Error && err.message !== '') { return err.message; } return resolveOverviewText('error'); } function emptySummary(): OverviewSummary { return { totalLeads: 0, hotCount: 0, totalTracks: 0, topCountry: '', }; } function pickTopCountry(tracks: RadarTrack[]): string { const names: string[] = []; const counts: number[] = []; for (let i = 0; i < tracks.length; i++) { const raw = tracks[i].country; const country = raw != null ? String(raw).trim() : ''; if (country === '') { continue; } let idx = -1; for (let j = 0; j < names.length; j++) { if (names[j] === country) { idx = j; break; } } if (idx < 0) { names.push(country); counts.push(1); } else { counts[idx] = counts[idx] + 1; } } let topCountry = ''; let topCount = 0; for (let i = 0; i < names.length; i++) { if (counts[i] > topCount) { topCount = counts[i]; topCountry = names[i]; } } return topCountry; } /** * 按线索与足迹列表汇总战报数字 */ export function summarize( leads: LeadEntity[], tracks: RadarTrack[] ): OverviewSummary { const safeLeads = leads != null ? leads : []; const safeTracks = tracks != null ? tracks : []; let hotCount = 0; for (let i = 0; i < safeLeads.length; i++) { if (safeLeads[i].status === 'hot') { hotCount += 1; } } return { totalLeads: safeLeads.length, hotCount, totalTracks: safeTracks.length, topCountry: pickTopCountry(safeTracks), }; } function buildQuery(input?: LoadOverviewInput): OwnerPageQuery { const query: OwnerPageQuery = { page: normalizePage(input != null ? input.page : null), size: normalizeSize(input != null ? input.size : null), }; if (input != null && input.cardId != null) { query.cardId = input.cardId; } return query; } /** * 并行拉取线索分页与雷达足迹。可注入两个 page API */ export async function loadOverview( input?: LoadOverviewInput, api?: LoadOverviewApi ): Promise { const query = buildQuery(input); const fetchLead = api != null && api.fetchOwnerLeadPage != null ? api.fetchOwnerLeadPage : fetchOwnerLeadPage; const fetchRadar = api != null && api.fetchOwnerRadarPage != null ? api.fetchOwnerRadarPage : fetchOwnerRadarPage; try { const pages = await Promise.all([fetchLead(query), fetchRadar(query)]); const leads = parseList(pages[0]); const tracks = parseList(pages[1]); const summary = summarize(leads, tracks); const ranks = await loadEventRanks(api); return { leads, tracks, ranks, summary, empty: leads.length === 0 && tracks.length === 0, errorMessage: '', }; } catch (err) { return { leads: [], tracks: [], ranks: [], summary: emptySummary(), empty: false, errorMessage: readErrorMessage(err), }; } } async function loadEventRanks(api?: LoadOverviewApi): Promise { const fetchRank = api != null && api.fetchTeamLiveRank != null ? api.fetchTeamLiveRank : api == null ? fetchTeamLiveRank : null; if (fetchRank == null) { return []; } try { let eventId = ''; const fetchEvent = api != null && api.fetchCurrentEvent != null ? api.fetchCurrentEvent : api == null ? fetchCurrentEvent : null; if (fetchEvent != null) { const current = await fetchEvent(); if (current != null && current.code != null) { eventId = current.code; } } const list = await fetchRank(eventId); return list != null ? list : []; } catch (_err) { return []; } } export function createOverviewState(): OverviewState { return { leads: [], tracks: [], ranks: [], summary: emptySummary(), loading: false, empty: true, errorMessage: '', }; } export function applyOverviewResult( state: OverviewState, result: LoadOverviewResult ): void { state.leads = result.leads; state.tracks = result.tracks; state.ranks = result.ranks; state.summary = result.summary; state.empty = result.empty; state.errorMessage = result.errorMessage; } export function formatTopCountry(country: string): string { if (country != null && country.trim() !== '') { return country.trim(); } return resolveOverviewText('unknownCountry'); } /** * 页面主 CTA:刷新战报 */ export async function refreshOverview( state: OverviewState, api?: LoadOverviewApi ): Promise { if (state.loading) { return { leads: state.leads, tracks: state.tracks, ranks: state.ranks, summary: state.summary, empty: state.empty, errorMessage: state.errorMessage, }; } state.loading = true; state.errorMessage = ''; const result = await loadOverview({ page: DEFAULT_PAGE, size: DEFAULT_SIZE }, api); applyOverviewResult(state, result); state.loading = false; if (result.errorMessage === '') { try { uni.vibrateShort({ type: 'light' }); } catch (_e) { // 兼容不支持振动的端环境 } } return result; }