Compare commits

..

3 Commits

Author SHA1 Message Date
linkeyu ecf8150cfc 修复:优化广告播放状态文案 (#65)
## 修改内容
- 广告播放状态“未满10秒”调整为“观看时长不足”
- 聚合播放状态“混合状态”调整为“部分完成”
- 保持后端状态码、接口结构和判定逻辑不变

## 影响范围
- 仅修改后台广告收益页的前端展示文案

## 验证
- npm run build

---------

Co-authored-by: linkeyu <798648091@qq.com>
Reviewed-on: #65
Co-authored-by: linkeyu <linkeyu@wonderable.ai>
Co-committed-by: linkeyu <linkeyu@wonderable.ai>
2026-07-22 18:21:39 +08:00
linkeyu 1559d4faf0 修复:激励视频未完成时预估收益显示为零 (#64)
## 问题
后台明细需要与后端有效收益口径一致:激励视频提前关闭或播放时长不足时显示 0,不能继续展示原始 eCPM 折算金额。

## 修改
- 对 reward_video + closed_early/too_short 做防御性归零显示
- 更新收益口径说明和类型注释
- 顶部合计、趋势和分类统计使用后端修正后的有效收益

## 关联后端
WonderableAI/shaguabijia-app-server#160

## 验证
- npm ci --legacy-peer-deps
- npm run build:通过(包含 TypeScript 与页面构建检查)

---------

Co-authored-by: linkeyu <798648091@qq.com>
Reviewed-on: #64
Co-authored-by: linkeyu <linkeyu@wonderable.ai>
Co-committed-by: linkeyu <linkeyu@wonderable.ai>
2026-07-22 17:38:47 +08:00
linkeyu 9d32234015 后台前端:新增“监控审计”一级目录 (#63)
## 变更
- 左侧导航新增一级目录“监控审计”。
- 将设备存活、埋点成功率、埋点日志和审计日志统一迁入该目录。
- 从“看板”移除技术监控项,并取消埋点日志、审计日志的独立一级入口。
- 保留现有页面权限过滤逻辑:无可见子项时不会展示该目录。

## 验证
- `npm run build` 通过,Next.js 编译、lint 和 TypeScript 检查均成功。

## 后端配套
- WonderableAI/shaguabijia-app-server#159

---------

Co-authored-by: linkeyu <798648091@qq.com>
Reviewed-on: #63
Co-authored-by: linkeyu <linkeyu@wonderable.ai>
Co-committed-by: linkeyu <linkeyu@wonderable.ai>
2026-07-22 17:22:23 +08:00
2 changed files with 18 additions and 8 deletions
+17 -7
View File
@@ -76,16 +76,28 @@ const REWARD_STATUS_HINT: Record<string, string> = {
const PLAYBACK_STATUS_TAG: Record<string, { color: string; label: string }> = { const PLAYBACK_STATUS_TAG: Record<string, { color: string; label: string }> = {
completed: { color: 'green', label: '已完成' }, completed: { color: 'green', label: '已完成' },
too_short: { color: 'gold', label: '未满10秒' }, too_short: { color: 'gold', label: '观看时长不足' },
closed_early: { color: 'default', label: '提前关闭' }, closed_early: { color: 'default', label: '提前关闭' },
unknown: { color: 'default', label: '未知' }, unknown: { color: 'default', label: '未知' },
}; };
const ZERO_REVENUE_REWARD_VIDEO_STATUSES = new Set(['closed_early', 'too_short']);
function rewardStatuses(row: AdRevenueRow): string[] { function rewardStatuses(row: AdRevenueRow): string[] {
if (row.sub_rewards?.length) return row.sub_rewards.map((reward) => reward.status); if (row.sub_rewards?.length) return row.sub_rewards.map((reward) => reward.status);
return row.status ? [row.status] : []; return row.status ? [row.status] : [];
} }
function effectiveRevenueYuan(row: AdRevenueRow): number {
if (
row.ad_type === 'reward_video'
&& rewardStatuses(row).some((status) => ZERO_REVENUE_REWARD_VIDEO_STATUSES.has(status))
) {
return 0;
}
return row.row_revenue_yuan ?? row.revenue_yuan;
}
function rewardStatusTag(row: AdRevenueRow) { function rewardStatusTag(row: AdRevenueRow) {
const statuses = rewardStatuses(row); const statuses = rewardStatuses(row);
if (!row.has_reward || statuses.length === 0) { if (!row.has_reward || statuses.length === 0) {
@@ -147,7 +159,7 @@ function playbackStatusTag(row: AdRevenueRow) {
const hint = entries const hint = entries
.map(([status, count]) => `${PLAYBACK_STATUS_TAG[status].label} ${count}`) .map(([status, count]) => `${PLAYBACK_STATUS_TAG[status].label} ${count}`)
.join(''); .join('');
return { color: 'purple', label: '混合状态', hint: `${hint}` }; return { color: 'purple', label: '部分完成', hint: `${hint}` };
} }
const fmtFactorRange = (a: number | null, b: number | null) => { const fmtFactorRange = (a: number | null, b: number | null) => {
@@ -546,10 +558,8 @@ export default function AdRevenueReportPage() {
width: 110, width: 110,
align: 'right', align: 'right',
// 一次比价/领券聚合行用 row_revenue_yuan(该次发奖广告 eCPM 折算之和),其它行用展示侧 revenue_yuan // 一次比价/领券聚合行用 row_revenue_yuan(该次发奖广告 eCPM 折算之和),其它行用展示侧 revenue_yuan
render: (v: number, r: AdRevenueRow) => { // 激励视频提前关闭/时长不足时防御性显示 0,与后端有效收益口径一致。
const rev = r.row_revenue_yuan ?? v; render: (_v: number, r: AdRevenueRow) => effectiveRevenueYuan(r).toFixed(4),
return rev.toFixed(4);
},
}, },
{ {
title: '发奖状态', title: '发奖状态',
@@ -695,7 +705,7 @@ export default function AdRevenueReportPage() {
<br /> <br />
<br /> <br />
广(onAdShow) eCPM ( ÷1000 广(onAdShow) eCPM ( ÷1000
),<b>广</b>;穿/,, );<b> 0 </b>, eCPM 穿,
<b>穿</b> 0广ID / , <b>穿</b> 0广ID / ,
<br /> <br />
<br /> <br />
+1 -1
View File
@@ -488,7 +488,7 @@ export interface AdRevenueRow {
has_impression: boolean; // 是否有广告展示(信息流逐条展示=true,纯发奖行=false) has_impression: boolean; // 是否有广告展示(信息流逐条展示=true,纯发奖行=false)
impressions: number; // 本行展示条数 1/0(供日汇总、趋势图复用) impressions: number; // 本行展示条数 1/0(供日汇总、趋势图复用)
ecpm: string | null; // 分/千次;展示行取展示值,纯发奖行取发奖采用值 ecpm: string | null; // 分/千次;展示行取展示值,纯发奖行取发奖采用值
revenue_yuan: number; // 本次展示预估收益(元);纯发奖=0 revenue_yuan: number; // 本次有效展示预估收益(元);纯发奖、激励视频提前关闭/时长不足=0
row_revenue_yuan?: number | null; // 主表逐行显示用:一次比价/领券聚合行=该次发奖广告 eCPM 折算之和;其它行空(回退 revenue_yuan) row_revenue_yuan?: number | null; // 主表逐行显示用:一次比价/领券聚合行=该次发奖广告 eCPM 折算之和;其它行空(回退 revenue_yuan)
adn: string | null; // 实际填充 ADN;纯发奖行为空 adn: string | null; // 实际填充 ADN;纯发奖行为空
slot_id: string | null; // 底层 mediation rit;纯发奖行为空 slot_id: string | null; // 底层 mediation rit;纯发奖行为空