|
|
|
@@ -54,19 +54,18 @@ const emptyMetric = (
|
|
|
|
|
key: string,
|
|
|
|
|
label: string,
|
|
|
|
|
tone: DashboardMetric['tone'],
|
|
|
|
|
route: string,
|
|
|
|
|
): DashboardMetric => ({ key, label, value: 0, trendText: '暂无授权数据', tone, available: false, route })
|
|
|
|
|
): DashboardMetric => ({ key, label, value: 0, trendText: '暂无授权数据', tone, available: false, status: 'forbidden', 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'),
|
|
|
|
|
emptyMetric('pending_reviews', '待审核资料', 'rose'),
|
|
|
|
|
emptyMetric('matchmaker_todos', '红娘服务待办', 'amber'),
|
|
|
|
|
emptyMetric('counseling_todos', '咨询服务待办', 'blue'),
|
|
|
|
|
emptyMetric('station_appointments', '服务站预约', 'green'),
|
|
|
|
|
emptyMetric('effective_matches', '有效匹配', 'purple'),
|
|
|
|
|
emptyMetric('activity_registrations', '活动报名', 'red'),
|
|
|
|
|
],
|
|
|
|
|
todos: [],
|
|
|
|
|
activities: [],
|
|
|
|
@@ -84,7 +83,8 @@ export async function loadDashboard(permissions: string[]): Promise<DashboardMod
|
|
|
|
|
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([
|
|
|
|
|
const [workbenchResult, coreResult, serviceResult, activityResult, matchmakerResult, reviewResult, managementResult] = await Promise.allSettled([
|
|
|
|
|
client.get('/operator/workbench/dashboard', { timeout: REQUEST_TIMEOUT }),
|
|
|
|
|
canViewAnalytics
|
|
|
|
|
? client.get('/operator/analytics/dashboard', { timeout: REQUEST_TIMEOUT })
|
|
|
|
|
: Promise.resolve(null),
|
|
|
|
@@ -101,6 +101,18 @@ export async function loadDashboard(permissions: string[]): Promise<DashboardMod
|
|
|
|
|
canManageMatchmaker ? client.get('/operator/matchmaker/management/services', { timeout: REQUEST_TIMEOUT }) : Promise.resolve(null),
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const workbench = fulfilledData<JsonRecord>(workbenchResult)
|
|
|
|
|
const workbenchMetrics = recordOf(workbench || undefined, 'metrics')
|
|
|
|
|
const workbenchValue = (key: string) => numberOf(recordOf(workbenchMetrics, key), 'value')
|
|
|
|
|
const hasWorkbenchMetric = (key: string) => Object.prototype.hasOwnProperty.call(workbenchMetrics, key)
|
|
|
|
|
const workbenchAvailable = (key: string) => Boolean(recordOf(workbenchMetrics, key).available)
|
|
|
|
|
const metricStatus = (key: string, permission: string, fallbackAvailable: boolean): DashboardMetric['status'] => {
|
|
|
|
|
if (hasWorkbenchMetric(key)) return workbenchAvailable(key) ? 'ready' : 'forbidden'
|
|
|
|
|
if (!permissions.includes(permission)) return 'forbidden'
|
|
|
|
|
return fallbackAvailable ? 'ready' : 'error'
|
|
|
|
|
}
|
|
|
|
|
const metricAvailable = (key: string, permission: string, fallbackAvailable: boolean) => metricStatus(key, permission, fallbackAvailable) === 'ready'
|
|
|
|
|
const metricRoute = (status: DashboardMetric['status'], route: string) => status === 'ready' ? route : ''
|
|
|
|
|
const core = fulfilledData<JsonRecord>(coreResult)
|
|
|
|
|
const service = fulfilledData<JsonRecord>(serviceResult)
|
|
|
|
|
const activityRows = fulfilledData<JsonRecord[]>(activityResult)
|
|
|
|
@@ -129,39 +141,41 @@ export async function loadDashboard(permissions: string[]): Promise<DashboardMod
|
|
|
|
|
|
|
|
|
|
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: 'pending_reviews', label: '待审核资料', value: hasWorkbenchMetric('pendingReviews') ? workbenchValue('pendingReviews') : numberOf(reviews || undefined, 'pending') + numberOf(reviews || undefined, 'processing'),
|
|
|
|
|
trendText: workbenchAvailable('pendingReviews') ? '当前数据范围内待审核资料' : (reviews ? '待领取与审核中资料' : '暂无授权数据'), tone: 'rose', available: metricAvailable('pendingReviews', 'review.view', Boolean(reviews)), status: metricStatus('pendingReviews', 'review.view', Boolean(reviews)), route: metricRoute(metricStatus('pendingReviews', 'review.view', Boolean(reviews)), '/review'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'matchmaker_todos', label: '红娘服务待办',
|
|
|
|
|
value: matchmakerPending,
|
|
|
|
|
trendText: matchmakerAvailable ? (canManageMatchmaker ? '管理范围内待完成服务' : '本人服务对象待跟进') : '暂无授权数据', tone: 'amber', available: matchmakerAvailable, route: '/matchmaker',
|
|
|
|
|
value: hasWorkbenchMetric('matchmakerTodos') ? workbenchValue('matchmakerTodos') : matchmakerPending,
|
|
|
|
|
trendText: workbenchAvailable('matchmakerTodos') ? `${workbench?.scopeLabel || '当前范围'}待完成服务` : (matchmakerAvailable ? (canManageMatchmaker ? '管理范围内待完成服务' : '本人服务对象待跟进') : '暂无授权数据'), tone: 'amber', available: metricAvailable('matchmakerTodos', 'matchmaker.view', matchmakerAvailable), status: metricStatus('matchmakerTodos', 'matchmaker.view', matchmakerAvailable), route: metricRoute(metricStatus('matchmakerTodos', 'matchmaker.view', matchmakerAvailable), permissions.includes('permission.manage') ? '' : '/matchmaker'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'counseling_todos', label: '咨询服务待办',
|
|
|
|
|
value: numberOf(counseling, 'pending') + numberOf(counseling, 'assigned'),
|
|
|
|
|
trendText: service ? '待受理与已分配' : '暂无授权数据', tone: 'blue', available: Boolean(service), route: '/counseling',
|
|
|
|
|
value: hasWorkbenchMetric('counselingTodos') ? workbenchValue('counselingTodos') : numberOf(counseling, 'pending') + numberOf(counseling, 'assigned'),
|
|
|
|
|
trendText: workbenchAvailable('counselingTodos') ? `${workbench?.scopeLabel || '当前范围'}咨询待办` : (service ? '待受理与已分配' : '暂无授权数据'), tone: 'blue', available: metricAvailable('counselingTodos', 'counseling.service.view', Boolean(service)), status: metricStatus('counselingTodos', 'counseling.service.view', Boolean(service)), route: metricRoute(metricStatus('counselingTodos', 'counseling.service.view', Boolean(service)), '/counseling'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'station_appointments', label: '服务站预约',
|
|
|
|
|
value: numberOf(appointments, 'submitted') + numberOf(appointments, 'confirmed'),
|
|
|
|
|
trendText: service ? '待确认与已确认' : '暂无授权数据', tone: 'green', available: Boolean(service), route: '/service-appointment',
|
|
|
|
|
value: hasWorkbenchMetric('serviceStationAppointments') ? workbenchValue('serviceStationAppointments') : numberOf(appointments, 'submitted') + numberOf(appointments, 'confirmed'),
|
|
|
|
|
trendText: workbenchAvailable('serviceStationAppointments') ? `${workbench?.scopeLabel || '当前范围'}服务站预约` : (service ? '待确认与已确认' : '暂无授权数据'), tone: 'green', available: metricAvailable('serviceStationAppointments', 'service_appointment.view', Boolean(service)), status: metricStatus('serviceStationAppointments', 'service_appointment.view', Boolean(service)), route: metricRoute(metricStatus('serviceStationAppointments', 'service_appointment.view', Boolean(service)), '/service-appointment'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'effective_matches', label: '有效匹配', value: numberOf(effectiveConnections, 'activeMatches'),
|
|
|
|
|
trendText: service ? '当前活跃匹配' : '暂无授权数据', tone: 'purple', available: Boolean(service), route: '/analytics',
|
|
|
|
|
key: 'effective_matches', label: '有效匹配', value: hasWorkbenchMetric('effectiveMatches') ? workbenchValue('effectiveMatches') : numberOf(effectiveConnections, 'activeMatches'),
|
|
|
|
|
trendText: workbenchAvailable('effectiveMatches') ? `${workbench?.scopeLabel || '当前范围'}有效匹配` : (service ? '当前活跃匹配' : '暂无授权数据'), tone: 'purple', available: metricAvailable('effectiveMatches', 'analytics.view', Boolean(service)), status: metricStatus('effectiveMatches', 'analytics.view', Boolean(service)), route: metricRoute(metricStatus('effectiveMatches', 'analytics.view', Boolean(service)), '/analytics'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
key: 'activity_registrations', label: '活动报名', value: numberOf(registrationSources, 'totalRegistrations'),
|
|
|
|
|
trendText: service ? '累计有效报名' : '暂无授权数据', tone: 'red', available: Boolean(service), route: '/activities/interests',
|
|
|
|
|
key: 'activity_registrations', label: '活动报名', value: hasWorkbenchMetric('activityRegistrations') ? workbenchValue('activityRegistrations') : numberOf(registrationSources, 'totalRegistrations'),
|
|
|
|
|
trendText: workbenchAvailable('activityRegistrations') ? `${workbench?.scopeLabel || '当前范围'}活动报名` : (service ? '累计有效报名' : '暂无授权数据'), tone: 'red', available: metricAvailable('activityRegistrations', 'activity.review_registration', Boolean(service)), status: metricStatus('activityRegistrations', 'activity.review_registration', Boolean(service)), route: metricRoute(metricStatus('activityRegistrations', 'activity.review_registration', Boolean(service)), '/activities/interests'),
|
|
|
|
|
},
|
|
|
|
|
// 保留 forbidden 卡片以明确展示“无权限”;其数值只在 available 时渲染,route 由 metricRoute 置空。
|
|
|
|
|
]
|
|
|
|
|
const metricByKey = (key: string) => metrics.find(metric => metric.key === key)
|
|
|
|
|
|
|
|
|
|
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: 'review', title: '资料审核', description: '待领取与审核中资料', count: metricByKey('pending_reviews')?.value || 0, actionText: '立即审核', route: '/review', tone: 'rose', available: metricByKey('pending_reviews')?.available || false },
|
|
|
|
|
{ key: 'counseling', title: '咨询预约', description: '咨询申请等待受理或跟进', count: metricByKey('counseling_todos')?.value || 0, actionText: '查看服务', route: '/counseling', tone: 'blue', available: metricByKey('counseling_todos')?.available || false },
|
|
|
|
|
{ key: 'appointment', title: '到站预约', description: '线下服务预约等待确认', count: metricByKey('station_appointments')?.value || 0, actionText: '管理预约', route: '/service-appointment', tone: 'green', available: metricByKey('station_appointments')?.available || false },
|
|
|
|
|
{ key: 'matchmaker', title: '红娘服务', description: metricByKey('matchmaker_todos')?.trendText || '暂无授权数据', count: metricByKey('matchmaker_todos')?.value || 0, actionText: permissions.includes('permission.manage') ? '统计' : '查看服务', route: permissions.includes('permission.manage') ? '' : '/matchmaker', tone: 'amber', available: metricByKey('matchmaker_todos')?.available || false },
|
|
|
|
|
{ key: 'risk', title: '举报处理', description: '风险举报等待工作人员处理', count: numberOf(core || undefined, 'pendingReports'), actionText: '去处理', route: '/risk/reports', tone: 'amber', available: Boolean(core) },
|
|
|
|
|
] as DashboardTodo[]).filter((item) => item.available)
|
|
|
|
|
|
|
|
|
@@ -192,8 +206,8 @@ export async function loadDashboard(permissions: string[]): Promise<DashboardMod
|
|
|
|
|
{ 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 rejectedCount = [workbenchResult, coreResult, serviceResult, activityResult, matchmakerResult, reviewResult, managementResult].filter((item) => item.status === 'rejected').length
|
|
|
|
|
const anyData = Boolean(workbench) || analyticsAvailable || Boolean(activityRows) || matchmakerAvailable || Boolean(reviews)
|
|
|
|
|
const notice = !anyData
|
|
|
|
|
? (rejectedCount ? '工作台数据加载失败,请稍后重试' : '当前账号暂无工作台统计权限')
|
|
|
|
|
: (rejectedCount ? '部分模块暂时不可用,其余数据已正常加载' : '')
|
|
|
|
|