import client from '@/api/client' import type { DashboardActivity, DashboardEffectivenessItem, DashboardMetric, DashboardModel, DashboardOverviewItem, DashboardTodo, } from '@/types/dashboard' type JsonRecord = Record interface ApiEnvelope { success: boolean data: T message?: string } const REQUEST_TIMEOUT = 8000 export function summarizeMatchmakerServices(rows: JsonRecord[]) { const day = new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit' }) const today = day.format(new Date()) return { pending: rows.filter(row => ['submitted', 'assigned', 'in_service', 'waiting_user'].includes(String(row.status))).length, completed: rows.filter(row => row.status === 'completed').length, total: rows.filter(row => ['createdAt', 'assignedAt', 'completedAt', 'latestFollowupAt'].some(key => { if (!row[key]) return false const date = new Date(String(row[key])) return !Number.isNaN(date.getTime()) && day.format(date) === today })).length, } } const numberOf = (source: JsonRecord | undefined, key: string) => { const value = source?.[key] return typeof value === 'number' && Number.isFinite(value) ? value : 0 } const recordOf = (source: JsonRecord | undefined, key: string): JsonRecord => { const value = source?.[key] return value && typeof value === 'object' && !Array.isArray(value) ? value as JsonRecord : {} } const dataOf = (response: unknown): T | null => { const envelope = response as ApiEnvelope | null return envelope?.success && envelope.data != null ? envelope.data : null } const fulfilledData = (result: PromiseSettledResult): T | null => result.status === 'fulfilled' && result.value ? dataOf(result.value) : null const emptyMetric = ( key: string, label: string, tone: DashboardMetric['tone'], route: string, ): DashboardMetric => ({ key, label, value: 0, trendText: '暂无授权数据', tone, available: false, route }) export function createEmptyDashboard(notice = '当前账号暂无可展示的工作台数据'): DashboardModel { return { followups: [], metrics: [ emptyMetric('pending_reviews', '待审核资料', 'rose', '/review'), emptyMetric('matchmaker_todos', '红娘服务待办', 'amber', '/matchmaker'), emptyMetric('counseling_todos', '咨询服务待办', 'blue', '/counseling'), emptyMetric('station_appointments', '服务站预约', 'green', '/service-appointment'), emptyMetric('effective_matches', '有效匹配', 'purple', '/analytics'), emptyMetric('activity_registrations', '活动报名', 'red', '/activities/interests'), ], todos: [], activities: [], serviceOverview: [], effectiveness: [], activitiesAvailable: false, notice, } } export async function loadDashboard(permissions: string[]): Promise { const canViewAnalytics = permissions.includes('analytics.view') const canViewActivities = permissions.includes('activity.view') const canViewMatchmaker = permissions.includes('matchmaker.view') const canManageMatchmaker = permissions.includes('matchmaker.assign') || permissions.includes('matchmaker.team.assign') const canViewReviews = permissions.includes('review.view') const [coreResult, serviceResult, activityResult, matchmakerResult, reviewResult, managementResult] = await Promise.allSettled([ canViewAnalytics ? client.get('/operator/analytics/dashboard', { timeout: REQUEST_TIMEOUT }) : Promise.resolve(null), canViewAnalytics ? client.get('/operator/analytics/v11-dashboard', { timeout: REQUEST_TIMEOUT }) : Promise.resolve(null), canViewActivities ? client.get('/operator/activities', { timeout: REQUEST_TIMEOUT }) : Promise.resolve(null), canViewMatchmaker ? client.get('/operator/matchmaker/dashboard', { timeout: REQUEST_TIMEOUT }) : Promise.resolve(null), canViewReviews ? client.get('/operator/reviews/stats', { timeout: REQUEST_TIMEOUT }) : Promise.resolve(null), canManageMatchmaker ? client.get('/operator/matchmaker/management/services', { timeout: REQUEST_TIMEOUT }) : Promise.resolve(null), ]) const core = fulfilledData(coreResult) const service = fulfilledData(serviceResult) const activityRows = fulfilledData(activityResult) const matchmaker = fulfilledData(matchmakerResult) const reviews = fulfilledData(reviewResult) const managedServices = fulfilledData(managementResult) const managedStats = summarizeMatchmakerServices(managedServices || []) const matchmakerAvailable = canManageMatchmaker ? Boolean(managedServices) : Boolean(matchmaker) const matchmakerPending = canManageMatchmaker ? managedStats.pending : numberOf(matchmaker || undefined, 'pendingCount') + numberOf(matchmaker || undefined, 'inProgressCount') const matchmakerCompleted = canManageMatchmaker ? managedStats.completed : numberOf(matchmaker || undefined, 'completedCount') const followups = (managedServices || []).filter(row => row.latestFollowupContent) .sort((a, b) => String(b.latestFollowupAt).localeCompare(String(a.latestFollowupAt))) .slice(0, 5).map(row => ({ serviceId: String(row.serviceId), userDisplayNumber: String(row.userDisplayNumber || '未生成编号'), content: String(row.latestFollowupContent), createdAt: String(row.latestFollowupAt || '') })) const analyticsAvailable = Boolean(core || service) const counseling = recordOf(service || undefined, 'counseling') const appointments = recordOf(service || undefined, 'serviceAppointments') const effectiveConnections = recordOf(service || undefined, 'effectiveConnections') const registrationSources = recordOf(service || undefined, 'registrationSources') const uniqueUsers = recordOf(service || undefined, 'uniqueServiceUsers') const metrics: DashboardMetric[] = [ { key: 'pending_reviews', label: '待审核资料', value: numberOf(reviews || undefined, 'pending') + numberOf(reviews || undefined, 'processing'), trendText: reviews ? '待领取与审核中资料' : '暂无授权数据', tone: 'rose', available: Boolean(reviews), route: '/review', }, { key: 'matchmaker_todos', label: '红娘服务待办', value: matchmakerPending, trendText: matchmakerAvailable ? (canManageMatchmaker ? '管理范围内待完成服务' : '本人服务对象待跟进') : '暂无授权数据', tone: 'amber', available: matchmakerAvailable, route: '/matchmaker', }, { key: 'counseling_todos', label: '咨询服务待办', value: numberOf(counseling, 'pending') + numberOf(counseling, 'assigned'), trendText: service ? '待受理与已分配' : '暂无授权数据', tone: 'blue', available: Boolean(service), route: '/counseling', }, { key: 'station_appointments', label: '服务站预约', value: numberOf(appointments, 'submitted') + numberOf(appointments, 'confirmed'), trendText: service ? '待确认与已确认' : '暂无授权数据', tone: 'green', available: Boolean(service), route: '/service-appointment', }, { key: 'effective_matches', label: '有效匹配', value: numberOf(effectiveConnections, 'activeMatches'), trendText: service ? '当前活跃匹配' : '暂无授权数据', tone: 'purple', available: Boolean(service), route: '/analytics', }, { key: 'activity_registrations', label: '活动报名', value: numberOf(registrationSources, 'totalRegistrations'), trendText: service ? '累计有效报名' : '暂无授权数据', tone: 'red', available: Boolean(service), route: '/activities/interests', }, ] const todos = ([ { key: 'review', title: '资料审核', description: '待领取与审核中资料', count: metrics[0].value, actionText: '立即审核', route: '/review', tone: 'rose', available: metrics[0].available }, { key: 'counseling', title: '咨询预约', description: '咨询申请等待受理或跟进', count: metrics[2].value, actionText: '查看服务', route: '/counseling', tone: 'blue', available: metrics[2].available }, { key: 'appointment', title: '到站预约', description: '线下服务预约等待确认', count: metrics[3].value, actionText: '管理预约', route: '/service-appointment', tone: 'green', available: metrics[3].available }, { key: 'matchmaker', title: '红娘服务', description: metrics[1].trendText, count: metrics[1].value, actionText: '查看服务', route: '/matchmaker', tone: 'amber', available: metrics[1].available }, { key: 'risk', title: '举报处理', description: '风险举报等待工作人员处理', count: numberOf(core || undefined, 'pendingReports'), actionText: '去处理', route: '/risk/reports', tone: 'amber', available: Boolean(core) }, ] as DashboardTodo[]).filter((item) => item.available) const activities: DashboardActivity[] = (activityRows || []).slice(0, 4).map((item) => ({ id: String(item.id || ''), title: String(item.title || '未命名活动'), coverUrl: String(item.coverUrl || ''), startAt: String(item.startAt || ''), locationName: String(item.locationName || '地点待确认'), registered: typeof item.registered === 'number' ? item.registered : null, capacity: typeof item.totalLimit === 'number' ? item.totalLimit : null, statusText: String(item.status || '状态待确认'), })) const serviceOverview = ([ { key: 'registered', label: '平台注册用户', value: numberOf(core || undefined, 'totalUsers'), compareText: '累计注册', tone: 'rose', available: Boolean(core) }, { key: 'approved', label: '资料审核通过', value: numberOf(core || undefined, 'approvedProfiles'), compareText: '已通过资料', tone: 'blue', available: Boolean(core) }, { key: 'served', label: '公益服务用户', value: numberOf(uniqueUsers, 'totalUniqueServiceUsers'), compareText: '去重服务人数', tone: 'green', available: Boolean(service) }, { key: 'success', label: '成功牵手', value: numberOf(core || undefined, 'successPairs'), compareText: '双方确认成功', tone: 'amber', available: Boolean(core) }, { key: 'matchmaker_completed', label: '红娘服务完成', value: matchmakerCompleted, compareText: canManageMatchmaker ? '管理范围内已完成服务' : '本人已完成服务', tone: 'amber', available: matchmakerAvailable }, ] as DashboardOverviewItem[]).filter((item) => item.available) const effectiveness = ([ { key: 'profile_review', label: '资料审核通过', value: numberOf(core || undefined, 'approvedProfiles'), max: numberOf(core || undefined, 'totalUsers'), unit: '人', available: Boolean(core) }, { key: 'successful_match', label: '成功匹配用户', value: numberOf(core || undefined, 'totalMatches'), max: numberOf(core || undefined, 'totalLikes'), unit: '对', available: Boolean(core) }, { key: 'counseling_completed', label: '咨询服务完成', value: numberOf(counseling, 'completed'), max: numberOf(counseling, 'total'), unit: '人次', available: Boolean(service) }, { key: 'activity_participation', label: '活动参与人数', value: numberOf(registrationSources, 'totalRegistrations'), max: 0, unit: '人次', available: Boolean(service) }, { key: 'offline_success', label: '线下牵手成功', value: numberOf(effectiveConnections, 'successConnections'), max: numberOf(effectiveConnections, 'activeMatches'), unit: '对', available: Boolean(service) }, ] as DashboardEffectivenessItem[]).filter((item) => item.available) const rejectedCount = [coreResult, serviceResult, activityResult, matchmakerResult, reviewResult, managementResult].filter((item) => item.status === 'rejected').length const anyData = analyticsAvailable || Boolean(activityRows) || matchmakerAvailable || Boolean(reviews) const notice = !anyData ? (rejectedCount ? '工作台数据加载失败,请稍后重试' : '当前账号暂无工作台统计权限') : (rejectedCount ? '部分模块暂时不可用,其余数据已正常加载' : '') return { followups, metrics, todos, activities, serviceOverview, effectiveness, activitiesAvailable: Boolean(activityRows), notice, } }