- 指标采集不再依赖已登录自有账号:竞品作品到期后经匿名浏览器拉取 抖音作品详情接口(detail 返回完整点赞/收藏/转发/评论快照) - 补采收藏数:creator_work 与 creator_work_metric 新增 collect_count (迁移 039),works 列表与 detail 解析均映射该字段 - 新增点赞增速:LoadWorksGrowth 按时间窗计算基线增量(full/partial 覆盖标注),works API 支持 growth_hours/min_likes_growth 筛选排序 - 前端监控详情页:作品卡片展示 24h 点赞增量、支持按增量筛选爆品; 作品 Drawer 新增增长曲线折线图(@ant-design/charts)与收藏列
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
// 监控账号详情:形态对齐账号详情页(页头槽 + 卡片流)——
|
||||
// 页头 info 为账号标识(头像 + 昵称 + 同步状态 + 平台),actions 为返回/同步/暂停恢复;
|
||||
// 主体两张 Card:账号画像(Descriptions)与作品列表(Row/Col card 网格,分页)。
|
||||
// 点击作品弹出 Drawer 展示统计信息:作品详情 + 指标采集记录表。
|
||||
// 主体两张 Card:账号画像(Descriptions)与作品列表(Row/Col card 网格,分页,支持按点赞增速筛选爆品)。
|
||||
// 点击作品弹出 Drawer 展示统计信息:作品详情 + 增长曲线折线图 + 指标采集记录表。
|
||||
// 作品列表走 GET /creator/works?source_id=<competitor_id>,定时采集由后端调度器维护。
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { history, useParams } from '@umijs/max';
|
||||
import { Line } from '@ant-design/charts';
|
||||
import {
|
||||
Alert,
|
||||
Avatar,
|
||||
@@ -15,6 +16,7 @@ import {
|
||||
Drawer,
|
||||
Empty,
|
||||
Flex,
|
||||
InputNumber,
|
||||
Row,
|
||||
Space,
|
||||
Spin,
|
||||
@@ -23,7 +25,7 @@ import {
|
||||
Typography,
|
||||
message,
|
||||
} from 'antd';
|
||||
import { CommentOutlined, LikeOutlined, ReloadOutlined, ShareAltOutlined } from '@ant-design/icons';
|
||||
import { CommentOutlined, FireOutlined, LikeOutlined, ReloadOutlined, ShareAltOutlined } from '@ant-design/icons';
|
||||
import { creatorAction, creatorGet, getOne, getList } from '@/services/api';
|
||||
import { conflictMessage, dateTime, platformLabel } from '@/utils/helpers';
|
||||
import { usePageActions, usePageInfo } from '@/components/PageActions';
|
||||
@@ -60,6 +62,42 @@ function formatCount(value?: number | null): string {
|
||||
return value >= 10000 ? `${(value / 10000).toFixed(1)}w` : `${value}`;
|
||||
}
|
||||
|
||||
const metricSeriesMeta = [
|
||||
{ key: 'likes', label: '点赞' },
|
||||
{ key: 'collect_count', label: '收藏' },
|
||||
{ key: 'comments_count', label: '评论' },
|
||||
{ key: 'shares', label: '转发' },
|
||||
] as const;
|
||||
|
||||
// 增长曲线:把指标时序点展开成长表(时间 × 指标 × 数值)供折线图渲染。
|
||||
function GrowthChart({ metrics }: { metrics: any[] }) {
|
||||
const data = useMemo(
|
||||
() =>
|
||||
metrics.flatMap((point) =>
|
||||
metricSeriesMeta
|
||||
.filter((series) => point[series.key] != null)
|
||||
.map((series) => ({
|
||||
time: dateTime(point.collected_at),
|
||||
metric: series.label,
|
||||
value: point[series.key],
|
||||
})),
|
||||
),
|
||||
[metrics],
|
||||
);
|
||||
if (!data.length) return null;
|
||||
return (
|
||||
<Line
|
||||
data={data}
|
||||
xField="time"
|
||||
yField="value"
|
||||
seriesField="metric"
|
||||
height={240}
|
||||
yAxis={{ labelFormatter: (v: number) => (v >= 10000 ? `${(v / 10000).toFixed(1)}w` : `${v}`) }}
|
||||
legend={{ position: 'top' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const { id = '' } = useParams<{ id: string }>();
|
||||
const [competitor, setCompetitor] = useState<CompetitorDetail | null>(null);
|
||||
@@ -74,6 +112,7 @@ export default function Page() {
|
||||
const [detailWork, setDetailWork] = useState<any>(null);
|
||||
const [workMetrics, setWorkMetrics] = useState<any[]>([]);
|
||||
const [metricsPending, setMetricsPending] = useState(false);
|
||||
const [minGrowth, setMinGrowth] = useState<number | null>(null);
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
|
||||
const loadCompetitor = useCallback(async () => {
|
||||
@@ -92,7 +131,13 @@ export default function Page() {
|
||||
setWorksPending(true);
|
||||
setWorksError(null);
|
||||
try {
|
||||
const result = await getList({ resource: 'creator-works', page: workPage, pageSize: 25, filters: { source_id: id } });
|
||||
const filters: Record<string, string | number> = { source_id: id };
|
||||
if (minGrowth !== null) {
|
||||
// 爆品筛选:growth_hours 开启后端 24h 点赞增量计算,min_likes_growth 设下限。
|
||||
filters.growth_hours = 24;
|
||||
filters.min_likes_growth = minGrowth;
|
||||
}
|
||||
const result = await getList({ resource: 'creator-works', page: workPage, pageSize: 25, filters });
|
||||
setWorks(result.data);
|
||||
setWorkPageInfo({ total: result.total, hasNext: Boolean(result.hasNext) });
|
||||
} catch (loadError) {
|
||||
@@ -100,7 +145,7 @@ export default function Page() {
|
||||
} finally {
|
||||
setWorksPending(false);
|
||||
}
|
||||
}, [id, workPage]);
|
||||
}, [id, workPage, minGrowth]);
|
||||
|
||||
useEffect(() => {
|
||||
loadCompetitor();
|
||||
@@ -229,6 +274,21 @@ export default function Page() {
|
||||
>
|
||||
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
|
||||
<Typography.Text type="secondary">共 {workPageInfo.total} 个作品;作品由系统按采集间隔自动同步。</Typography.Text>
|
||||
<Flex align="center" gap={8}>
|
||||
<FireOutlined style={{ color: 'rgba(0,0,0,0.45)' }} />
|
||||
<Typography.Text type="secondary">24h 点赞增量 ≥</Typography.Text>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={0}
|
||||
placeholder="不限"
|
||||
style={{ width: 96 }}
|
||||
value={minGrowth}
|
||||
onChange={(value) => {
|
||||
setMinGrowth(value ?? null);
|
||||
setWorkPage(1);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{worksError ? (
|
||||
<Alert
|
||||
@@ -270,6 +330,11 @@ export default function Page() {
|
||||
<Typography.Text style={{ fontSize: 12 }}><CommentOutlined /> {work.comments_count ?? '—'}</Typography.Text>
|
||||
<Typography.Text style={{ fontSize: 12 }}><ShareAltOutlined /> {work.shares ?? '—'}</Typography.Text>
|
||||
</Flex>
|
||||
{work.likes_growth != null && work.likes_growth > 0 ? (
|
||||
<Typography.Text type="warning" style={{ fontSize: 12 }}>
|
||||
<FireOutlined /> 24h +{work.likes_growth}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</Flex>
|
||||
</Card>
|
||||
</Col>
|
||||
@@ -309,6 +374,7 @@ export default function Page() {
|
||||
<Typography.Link href={detailWork.original_url} target="_blank">打开原文</Typography.Link>
|
||||
) : null}
|
||||
</Space>
|
||||
{workMetrics.length >= 2 ? <GrowthChart metrics={workMetrics} /> : null}
|
||||
<Table
|
||||
rowKey="collected_at"
|
||||
size="small"
|
||||
@@ -318,6 +384,7 @@ export default function Page() {
|
||||
columns={[
|
||||
{ title: '采集时间', dataIndex: 'collected_at', render: (value: string) => dateTime(value) },
|
||||
{ title: '点赞', dataIndex: 'likes', render: (v) => v ?? '—' },
|
||||
{ title: '收藏', dataIndex: 'collect_count', render: (v) => v ?? '—' },
|
||||
{ title: '评论', dataIndex: 'comments_count', render: (v) => v ?? '—' },
|
||||
{ title: '转发', dataIndex: 'shares', render: (v) => v ?? '—' },
|
||||
]}
|
||||
|
||||
@@ -33,7 +33,7 @@ const filterKeys: Partial<Record<Resource, string[]>> = {
|
||||
tasks: ['account_id', 'draft_id', 'state'],
|
||||
'creator-competitors': ['platform'],
|
||||
'creator-competitor-share-jobs': ['platform', 'status'],
|
||||
'creator-works': ['platform', 'source_id', 'source_type', 'published_at_status', 'published_after', 'published_before', 'min_likes', 'min_comments', 'min_shares'],
|
||||
'creator-works': ['platform', 'source_id', 'source_type', 'published_at_status', 'published_after', 'published_before', 'min_likes', 'min_comments', 'min_shares', 'growth_hours', 'min_likes_growth'],
|
||||
'creator-comments': ['platform', 'work_id'],
|
||||
'creator-leads': ['platform'],
|
||||
'creator-operations': ['account_id'],
|
||||
|
||||
Reference in New Issue
Block a user