@@ -66,21 +66,89 @@ const APP_TAG: Record<string, { color: string; label: string }> = {
test : { color : 'default' , label : '测试应用' } ,
} ;
// 发奖状态 → 颜色 + 中文(含「不发金币的原因」:提前关闭/未满10秒/缺eCPM/次数超限)
const STATUS_TAG : Record < string , { color : string ; label : string } > = {
granted : { color : 'green' , label : '已发' } ,
capped : { color : 'orange' , label : '次数超限' } ,
ecpm_missing : { color : 'red' , label : '缺 eCPM' } ,
const REWARD_STATUS_HINT : Record < string , string > = {
granted : '已完成金币发放' ,
capped : '次数超限,未发金币' ,
ecpm_missing : '缺少有效 eCPM,未发金币' ,
too_short : '播放时长未达到发奖条件,未发金币' ,
closed_early : '用户提前关闭广告,未发金币' ,
} ;
const PLAYBACK_STATUS_TAG : Record < string , { color : string ; label : string } > = {
completed : { color : 'green' , label : '已完成' } ,
too_short : { color : 'gold' , label : '未满10秒' } ,
closed_early : { color : 'default' , label : '提前关闭' } ,
unknown : { color : 'default' , label : '未知' } ,
} ;
const STATUS_HINT : Record < string , string > = {
granted : '已满足当前客户端发奖条件并完成金币发放。' ,
too_short : '旧版 Draw 信息流观看不足 10 秒,不发金币;新版按观看比例发放时会记为已发。' ,
closed_early : '用户在达到发奖条件前主动关闭,不发金币。' ,
capped : '已达到次数上限,不再发金币。' ,
ecpm_missing : '缺少有效 eCPM,无法计算金币。' ,
} ;
function rewardStatuses ( row : AdRevenueRow ) : string [ ] {
if ( row . sub_rewards ? . length ) return row . sub_rewards . map ( ( reward ) = > reward . status ) ;
return row . status ? [ row . status ] : [ ] ;
}
function rewardStatusTag ( row : AdRevenueRow ) {
const statuses = rewardStatuses ( row ) ;
if ( ! row . has_reward || statuses . length === 0 ) {
return { color : 'default' , label : '无记录' , hint : '仅记录到广告展示,没有对应发奖记录。' } ;
}
const grantedCount = statuses . filter ( ( status ) = > status === 'granted' ) . length ;
const reasonSummary = Object . entries (
statuses
. filter ( ( status ) = > status !== 'granted' )
. reduce < Record < string , number > > ( ( counts , status ) = > {
counts [ status ] = ( counts [ status ] ? ? 0 ) + 1 ;
return counts ;
} , { } ) ,
)
. map ( ( [ status , count ] ) = > ` ${ REWARD_STATUS_HINT [ status ] ? ? status } ${ count } 条 ` )
. join ( '; ' ) ;
if ( grantedCount === statuses . length ) {
return { color : 'green' , label : '已发' , hint : ` 已完成金币发放 ${ statuses . length > 1 ? ` ${ statuses . length } 条 ` : '' } 。 ` } ;
}
if ( grantedCount > 0 ) {
return {
color : 'blue' ,
label : '部分已发' ,
hint : ` 已发 ${ grantedCount } 条,未发 ${ statuses . length - grantedCount } 条 ${ reasonSummary ? ` ; ${ reasonSummary } ` : '' } 。 ` ,
} ;
}
return { color : 'default' , label : '未发' , hint : reasonSummary ? ` ${ reasonSummary } 。 ` : '未发放金币。' } ;
}
function playbackStatusTag ( row : AdRevenueRow ) {
const statuses = rewardStatuses ( row ) ;
if ( statuses . length === 0 ) {
return row . has_impression
? { color : 'blue' , label : '仅展示' , hint : '记录到广告展示,但没有对应的播放结果。' }
: { color : 'default' , label : '无记录' , hint : '没有可用的广告播放记录。' } ;
}
const playbackCounts = statuses . reduce < Record < string , number > > ( ( counts , status ) = > {
const playbackStatus =
status === 'too_short' || status === 'closed_early'
? status
: status === 'granted' || status === 'capped' || status === 'ecpm_missing'
? 'completed'
: 'unknown' ;
counts [ playbackStatus ] = ( counts [ playbackStatus ] ? ? 0 ) + 1 ;
return counts ;
} , { } ) ;
const entries = Object . entries ( playbackCounts ) ;
if ( entries . length === 1 ) {
const [ status , count ] = entries [ 0 ] ;
const tag = PLAYBACK_STATUS_TAG [ status ] ;
return {
. . . tag ,
hint : ` ${ tag . label } ${ count > 1 ? ` ${ count } 条 ` : '' } 。 ` ,
} ;
}
const hint = entries
. map ( ( [ status , count ] ) = > ` ${ PLAYBACK_STATUS_TAG [ status ] . label } ${ count } 条 ` )
. join ( '; ' ) ;
return { color : 'purple' , label : '混合状态' , hint : ` ${ hint } 。 ` } ;
}
const fmtFactorRange = ( a : number | null , b : number | null ) = > {
if ( a == null ) return '-' ;
@@ -485,12 +553,20 @@ export default function AdRevenueReportPage() {
} ,
{
title : '发奖状态' ,
dataIndex : 'status' ,
key : 'reward_ status' ,
width : 100 ,
render : ( s : string | null ) = > {
if ( ! s ) return < Tooltip title = "仅记录广告展示,没有对应发奖事件。" > < Tag > 仅 展 示 < / Tag > < / Tooltip > ;
const t = STATUS_TAG [ s ] ? ? { color : 'default' , label : s } ;
return < Tooltip title = { STATUS_HINT [ s ] } > < Tag color = { t . color } > { t . label } < / Tag > < / Tooltip > ;
render : ( _ : unknown , row : AdRevenueRow ) = > {
const tag = rewardStatusTag ( row ) ;
return < Tooltip title = { tag . hint } > < Tag color = { tag . color } > { tag . label } < / Tag > < / Tooltip > ;
} ,
} ,
{
title : '广告播放状态' ,
key : 'playback_status' ,
width : 120 ,
render : ( _ : unknown , row : AdRevenueRow ) = > {
const tag = playbackStatusTag ( row ) ;
return < Tooltip title = { tag . hint } > < Tag color = { tag . color } > { tag . label } < / Tag > < / Tooltip > ;
} ,
} ,
{
@@ -532,7 +608,8 @@ export default function AdRevenueReportPage() {
'ecpm_yuan' ,
'revenue_yuan' ,
'actual_coin' ,
'status' ,
'reward_ status' ,
'playback_status' ,
'ad_type' ,
'app_env' ,
'our_code_id' ,