forked from wangziqi/gongxue-base
feat: add referral conversion report
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
loadPointExchangeItems,
|
||||
loadPointExchangeOrders,
|
||||
loadPointsRiskReport,
|
||||
loadReferralConversionReport,
|
||||
loadTenantMembers,
|
||||
loadUserNotifications,
|
||||
updateCommissionSettings,
|
||||
@@ -53,6 +54,7 @@ import {
|
||||
type PointActivityTaskItem,
|
||||
type PointExchangeItem,
|
||||
type PointsRiskReport,
|
||||
type ReferralConversionReport,
|
||||
type TenantMemberItem,
|
||||
type UserNotificationAdminItem,
|
||||
} from '@/services/tenantAdmin';
|
||||
@@ -305,6 +307,7 @@ export default function TenantMarketingPage() {
|
||||
const [crmLogs, setCrmLogs] = useState<Record<string, CrmQueueLogItem[]>>({});
|
||||
const [commissionSettings, setCommissionSettings] = useState<CommissionSettingsItem | null>(null);
|
||||
const [commission, setCommission] = useState<CommissionSummaryItem | null>(null);
|
||||
const [conversionReport, setConversionReport] = useState<ReferralConversionReport | null>(null);
|
||||
const [commissionOrders, setCommissionOrders] = useState<CommissionOrderItem[]>([]);
|
||||
const [settlements, setSettlements] = useState<CommissionSettlementItem[]>([]);
|
||||
const [settlementProofs, setSettlementProofs] = useState<Record<string, CommissionSettlementProofItem[]>>({});
|
||||
@@ -380,6 +383,7 @@ export default function TenantMarketingPage() {
|
||||
crmDeadLettersPayload,
|
||||
commissionSettingsPayload,
|
||||
commissionPayload,
|
||||
conversionReportPayload,
|
||||
orderPayload,
|
||||
settlementPayload,
|
||||
memberPayload,
|
||||
@@ -410,6 +414,7 @@ export default function TenantMarketingPage() {
|
||||
loadCrmDeadLetters({ limit: 20 }).catch(() => ({ items: [], summary: {} })),
|
||||
loadCommissionSettings().catch(() => ({ item: null })),
|
||||
loadCommissionSummary(period).catch(() => ({ item: null })),
|
||||
loadReferralConversionReport({ ...period, limit: 20 }).catch(() => ({ item: null })),
|
||||
loadCommissionOrders({ ...period, limit: 20 }).catch(() => ({ items: [] })),
|
||||
loadCommissionSettlements({ limit: 20 }).catch(() => ({ items: [] })),
|
||||
loadTenantMembers({ limit: 100 }).catch(() => ({ items: [] })),
|
||||
@@ -438,6 +443,7 @@ export default function TenantMarketingPage() {
|
||||
setCrmDeadLetters(crmDeadLettersPayload || {});
|
||||
setCommissionSettings(nextSettings);
|
||||
setCommission(commissionPayload.item || null);
|
||||
setConversionReport(conversionReportPayload.item || null);
|
||||
setCommissionOrders(orderPayload.items || []);
|
||||
setSettlements(settlementPayload.items || []);
|
||||
setMembers(memberPayload.items || []);
|
||||
@@ -882,12 +888,14 @@ export default function TenantMarketingPage() {
|
||||
setBusy('commission');
|
||||
setError('');
|
||||
try {
|
||||
const [summaryPayload, orderPayload, settlementPayload] = await Promise.all([
|
||||
const [summaryPayload, conversionPayload, orderPayload, settlementPayload] = await Promise.all([
|
||||
loadCommissionSummary(period),
|
||||
loadReferralConversionReport({ ...period, limit: 30 }),
|
||||
loadCommissionOrders({ ...period, limit: 30 }),
|
||||
loadCommissionSettlements({ limit: 30 }),
|
||||
]);
|
||||
setCommission(summaryPayload.item || null);
|
||||
setConversionReport(conversionPayload.item || null);
|
||||
setCommissionOrders(orderPayload.items || []);
|
||||
setSettlements(settlementPayload.items || []);
|
||||
} catch (nextError) {
|
||||
@@ -1544,6 +1552,67 @@ export default function TenantMarketingPage() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>销售/代理转化</Text>
|
||||
<View className='admin-grid'>
|
||||
<View className='admin-metric'><Text className='admin-metric-label'>新增客资</Text><Text className='admin-metric-value'>{String(conversionReport?.summary?.leadCount || 0)}</Text></View>
|
||||
<View className='admin-metric'><Text className='admin-metric-label'>有效成交</Text><Text className='admin-metric-value'>{String(conversionReport?.summary?.convertedLeadCount || 0)}</Text></View>
|
||||
<View className='admin-metric'><Text className='admin-metric-label'>转化率</Text><Text className='admin-metric-value'>{percentLabel(conversionReport?.summary?.conversionRate)}</Text></View>
|
||||
<View className='admin-metric'><Text className='admin-metric-label'>成交金额</Text><Text className='admin-metric-value'>{money(conversionReport?.summary?.grossAmountCents)}</Text></View>
|
||||
</View>
|
||||
<View className='admin-grid'>
|
||||
<View className='admin-metric'><Text className='admin-metric-label'>预计佣金</Text><Text className='admin-metric-value'>{money(conversionReport?.summary?.commissionAmountCents)}</Text></View>
|
||||
<View className='admin-metric'><Text className='admin-metric-label'>CRM 失败</Text><Text className='admin-metric-value'>{String(conversionReport?.summary?.crmFailedCount || 0)}</Text></View>
|
||||
<View className='admin-metric'><Text className='admin-metric-label'>跟进积压</Text><Text className='admin-metric-value'>{String(conversionReport?.summary?.openFollowupCount || 0)}</Text></View>
|
||||
<View className='admin-metric'><Text className='admin-metric-label'>首付均时</Text><Text className='admin-metric-value'>{String(conversionReport?.summary?.avgFirstPayHours || 0)}h</Text></View>
|
||||
</View>
|
||||
<View className='admin-list compact'>
|
||||
{(conversionReport?.funnel || []).map(item => (
|
||||
<View className='admin-row' key={String(item.key || item.label)}>
|
||||
<Text className='admin-row-main'>{String(item.label || item.key || '漏斗')}</Text>
|
||||
<Text className='admin-row-meta'>{String(item.count || 0)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<View className='admin-list'>
|
||||
{(conversionReport?.byReferrer || []).slice(0, 8).map(item => (
|
||||
<View className='admin-row' key={item.referrerUserId}>
|
||||
<Text className='admin-row-main'>{item.name || item.phone || item.username || item.referrerUserId} · {money(item.grossAmountCents)}</Text>
|
||||
<Text className='admin-row-meta'>
|
||||
{item.role || 'member'} · 客资 {String(item.leadCount || 0)} · 成交 {String(item.convertedLeadCount || 0)} · 转化 {percentLabel(item.conversionRate)} · 佣金 {money(item.commissionAmountCents)}
|
||||
</Text>
|
||||
<Text className='admin-row-meta'>
|
||||
CRM 失败 {String(item.crmFailedCount || 0)} · 待跟进 {String(item.openFollowupCount || 0)} · 超期 {String(item.overdueFollowupCount || 0)} · 首付均时 {String(item.avgFirstPayHours || 0)}h
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
{(conversionReport?.dailyTrend || []).length ? (
|
||||
<View className='admin-list compact'>
|
||||
{(conversionReport?.dailyTrend || []).slice(-7).map(item => (
|
||||
<View className='admin-row' key={String(item.date)}>
|
||||
<Text className='admin-row-main'>{String(item.date || '-')}</Text>
|
||||
<Text className='admin-row-meta'>客资 {String(item.leadCount || 0)} · 成交来源 {String(item.paidSourceCount || 0)} · 金额 {money(item.grossAmountCents)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
{(conversionReport?.recentUnconvertedLeads || []).length ? (
|
||||
<View className='admin-list'>
|
||||
<Text className='admin-section-title'>近期未成交客资</Text>
|
||||
{(conversionReport?.recentUnconvertedLeads || []).slice(0, 6).map(item => (
|
||||
<View className='admin-row' key={String(item.id)}>
|
||||
<Text className='admin-row-main'>{item.studentName || item.studentPhone || item.studentUserId || '学生'} · {item.referrerName || item.referrerPhone || item.referrerUserId || '归属人'}</Text>
|
||||
<Text className='admin-row-meta'>跟进 {item.assignedToName || item.assignedToUserId || '-'} · 来源 {item.source || '-'} · {shortDate(item.boundAt)}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
) : null}
|
||||
{!(conversionReport?.byReferrer?.length || conversionReport?.recentUnconvertedLeads?.length) ? (
|
||||
<View className='admin-empty'>当前筛选范围暂无转化数据,或当前角色只能查看本人客资。</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<View className='admin-section'>
|
||||
<Text className='admin-section-title'>分佣订单明细</Text>
|
||||
<View className='admin-list'>
|
||||
|
||||
Reference in New Issue
Block a user