Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c22f774d8 | |||
| 26b1adb769 | |||
| c4b3fcfedb | |||
| 0040c5795d | |||
| 5acfb05c57 |
@@ -30,7 +30,7 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import { api, errMsg } from '@/lib/api';
|
||||
import { formatUtcTime, percentile } from '@/lib/format';
|
||||
import { formatUtcTime, nearestRankPercentile } from '@/lib/format';
|
||||
import type {
|
||||
AdRevenueDaily,
|
||||
AdRevenueHourly,
|
||||
@@ -758,9 +758,9 @@ export default function AdRevenueReportPage() {
|
||||
.filter((item) => item.feed_scene === sceneName)
|
||||
.map((item) => item.sub_count ?? 1);
|
||||
return {
|
||||
p5: percentile(counts, 0.05, false),
|
||||
p50: percentile(counts, 0.5, false),
|
||||
p95: percentile(counts, 0.95, false),
|
||||
p5: nearestRankPercentile(counts, 0.05),
|
||||
p50: nearestRankPercentile(counts, 0.5),
|
||||
p95: nearestRankPercentile(counts, 0.95),
|
||||
};
|
||||
};
|
||||
return { coupon: summarize('coupon'), comparison: summarize('comparison') };
|
||||
@@ -1083,12 +1083,12 @@ export default function AdRevenueReportPage() {
|
||||
</Typography.Text>
|
||||
</Divider>
|
||||
<Row gutter={[16, 12]}>
|
||||
<Col span={4}><Statistic title="单次领券广告数 P5" value={sessionAdCounts.coupon.p5 ?? '-'} precision={1} /></Col>
|
||||
<Col span={4}><Statistic title="单次领券广告数 P50" value={sessionAdCounts.coupon.p50 ?? '-'} precision={1} /></Col>
|
||||
<Col span={4}><Statistic title="单次领券广告数 P95" value={sessionAdCounts.coupon.p95 ?? '-'} precision={1} /></Col>
|
||||
<Col span={4}><Statistic title="单次比价广告数 P5" value={sessionAdCounts.comparison.p5 ?? '-'} precision={1} /></Col>
|
||||
<Col span={4}><Statistic title="单次比价广告数 P50" value={sessionAdCounts.comparison.p50 ?? '-'} precision={1} /></Col>
|
||||
<Col span={4}><Statistic title="单次比价广告数 P95" value={sessionAdCounts.comparison.p95 ?? '-'} precision={1} /></Col>
|
||||
<Col span={4}><Statistic title="单次领券广告数 P5" value={sessionAdCounts.coupon.p5 ?? '-'} precision={0} /></Col>
|
||||
<Col span={4}><Statistic title="单次领券广告数 P50" value={sessionAdCounts.coupon.p50 ?? '-'} precision={0} /></Col>
|
||||
<Col span={4}><Statistic title="单次领券广告数 P95" value={sessionAdCounts.coupon.p95 ?? '-'} precision={0} /></Col>
|
||||
<Col span={4}><Statistic title="单次比价广告数 P5" value={sessionAdCounts.comparison.p5 ?? '-'} precision={0} /></Col>
|
||||
<Col span={4}><Statistic title="单次比价广告数 P50" value={sessionAdCounts.comparison.p50 ?? '-'} precision={0} /></Col>
|
||||
<Col span={4}><Statistic title="单次比价广告数 P95" value={sessionAdCounts.comparison.p95 ?? '-'} precision={0} /></Col>
|
||||
</Row>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -710,7 +710,7 @@ export default function DashboardPage() {
|
||||
<div>
|
||||
<h1>数据大盘</h1>
|
||||
<p>
|
||||
数据更新于 {updatedAt ?? '--'} · 北京时间 · 日期窗口按 00:00 自然日切分,不包含今日
|
||||
数据更新于 {updatedAt ?? '--'} · 北京时间 · 日期窗口按 00:00 自然日切分,自定义日期可选择今日
|
||||
</p>
|
||||
</div>
|
||||
<div className="controls">
|
||||
@@ -734,7 +734,7 @@ export default function DashboardPage() {
|
||||
format="YYYY-MM-DD"
|
||||
allowClear={false}
|
||||
open={datePickerOpen}
|
||||
disabledDate={(date) => date.isAfter(dayjs().subtract(1, 'day'), 'day')}
|
||||
disabledDate={(date) => date.isAfter(dayjs(), 'day')}
|
||||
suffixIcon={<CalendarOutlined />}
|
||||
onClick={() => setDatePickerOpen(true)}
|
||||
onOpenChange={setDatePickerOpen}
|
||||
|
||||
@@ -570,6 +570,7 @@ export function WithdrawReviewPage({ mode = 'other' }: { mode?: WithdrawReviewMo
|
||||
},
|
||||
{
|
||||
title: '失败/拒绝原因',
|
||||
key: 'fail_reason',
|
||||
dataIndex: 'fail_reason',
|
||||
ellipsis: true,
|
||||
render: (v: string | null) => v || <Text type="secondary">-</Text>,
|
||||
@@ -647,6 +648,10 @@ export function WithdrawReviewPage({ mode = 'other' }: { mode?: WithdrawReviewMo
|
||||
},
|
||||
},
|
||||
];
|
||||
const visibleColumns =
|
||||
activeStatus === 'pending' || activeStatus === 'success'
|
||||
? columns.filter((column) => column.key !== 'fail_reason')
|
||||
: columns;
|
||||
|
||||
const inviteeColumns: ColumnsType<InviteeDetail> = [
|
||||
{ title: '手机号', dataIndex: 'phone', width: 130 },
|
||||
@@ -945,7 +950,7 @@ export function WithdrawReviewPage({ mode = 'other' }: { mode?: WithdrawReviewMo
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
columns={columns}
|
||||
columns={visibleColumns}
|
||||
dataSource={items}
|
||||
loading={loading}
|
||||
pagination={{
|
||||
@@ -989,12 +994,6 @@ export function WithdrawReviewPage({ mode = 'other' }: { mode?: WithdrawReviewMo
|
||||
<Descriptions.Item label="金额">
|
||||
<Text strong>{yuan(detail.order.amount_cents)}</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="提现类型">
|
||||
{detail.order.source === 'invite_cash' ? '邀请奖励提现' : '福利页提现'}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="累计成功提现">
|
||||
{yuan(detail.cumulative_success_cents)}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="提交时间">
|
||||
{dt(detail.order.created_at)}
|
||||
</Descriptions.Item>
|
||||
|
||||
@@ -37,6 +37,18 @@ export function percentile(values: readonly number[], q: number, round = true):
|
||||
return round ? Math.round(result) : result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 离散计数分位数(nearest-rank)。
|
||||
* 广告条数等不可拆分的计数不做线性插值,结果始终取自真实样本。
|
||||
*/
|
||||
export function nearestRankPercentile(values: readonly number[], q: number): number | null {
|
||||
if (!values.length) return null;
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const quantile = Math.min(1, Math.max(0, q));
|
||||
const rank = Math.max(1, Math.ceil(quantile * sorted.length));
|
||||
return sorted[rank - 1];
|
||||
}
|
||||
|
||||
const hasTz = (v: string) => /(?:Z|[+-]\d{2}:?\d{2})$/i.test(v);
|
||||
|
||||
/** 把 UTC 口径字符串解析为带时区的 dayjs(无时区后缀的补 Z 当 UTC)。 */
|
||||
|
||||
Reference in New Issue
Block a user