feat: unify account environments and work collection

This commit is contained in:
2026-10-06 13:00:29 +08:00
parent b72727451d
commit 950b23da32
22 changed files with 935 additions and 93 deletions
+53 -12
View File
@@ -4,7 +4,7 @@
// 指标 GET /creator/works/:id/metrics,评论 GET /creator/comments?work_id=(定时采集由后端调度器维护)。
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Line } from '@ant-design/charts';
import { Alert, App, Button, Card, Col, Drawer, Empty, Flex, InputNumber, Row, Space, Spin, Table, Typography } from 'antd';
import { Alert, App, Button, Card, Col, Drawer, Empty, Flex, InputNumber, Pagination, Row, Space, Spin, Table, Typography } from 'antd';
import {
CommentOutlined,
EyeOutlined,
@@ -104,6 +104,12 @@ function GrowthChart({ metrics }: { metrics: any[] }) {
);
}
interface CollectionProgress {
work_count: number;
aweme_count: number | null;
works: { status: string; last_error?: string };
}
interface WorksGalleryProps {
/** 不传则不过滤来源(如竞品分析页跨全部竞品);监控/自有账号详情传具体账号 ID。 */
sourceId?: string;
@@ -116,7 +122,9 @@ interface WorksGalleryProps {
export default function WorksGallery({ sourceId, sourceType, emptyHint, extraFilters }: WorksGalleryProps) {
const [works, setWorks] = useState<any[]>([]);
const [workPage, setWorkPage] = useState(1);
const [workPageInfo, setWorkPageInfo] = useState({ total: 0, hasNext: false });
const [workPageInfo, setWorkPageInfo] = useState({ total: 0 });
const [collectionProgress, setCollectionProgress] = useState<CollectionProgress | null>(null);
const [progressError, setProgressError] = useState<any>(null);
const [worksPending, setWorksPending] = useState(true);
const [worksError, setWorksError] = useState<any>(null);
const [minGrowth, setMinGrowth] = useState<number | null>(null);
@@ -133,6 +141,16 @@ export default function WorksGallery({ sourceId, sourceType, emptyHint, extraFil
const loadWorks = useCallback(async () => {
setWorksPending(true);
setWorksError(null);
setProgressError(null);
setCollectionProgress(null);
const progressRequest = sourceType === 'owned' && sourceId
? creatorGet(`/creator/accounts/${encodeURIComponent(sourceId)}/collection-status`)
.then((progress) => setCollectionProgress(progress))
.catch((error) => {
console.error('读取账号作品采集进度失败', { sourceId, sourceType, error });
setProgressError(error);
})
: Promise.resolve();
try {
const filters: Record<string, string | number | undefined> = { ...extraFilters };
if (sourceId) filters.source_id = sourceId;
@@ -144,10 +162,12 @@ export default function WorksGallery({ sourceId, sourceType, emptyHint, extraFil
}
const result = await getList({ resource: 'creator-works', page: workPage, pageSize: 25, filters });
setWorks(result.data);
setWorkPageInfo({ total: result.total, hasNext: Boolean(result.hasNext) });
setWorkPageInfo({ total: result.total });
} catch (loadError) {
console.error('读取作品列表失败', { sourceId, sourceType, page: workPage, minGrowth, extraFilters, error: loadError });
setWorksError(loadError);
} finally {
await progressRequest;
setWorksPending(false);
}
}, [sourceId, sourceType, workPage, minGrowth, extraFilters]);
@@ -188,6 +208,18 @@ export default function WorksGallery({ sourceId, sourceType, emptyHint, extraFil
loadComments(work.id, 1);
}
let progressHint = '采集进度暂不可用';
if (collectionProgress) {
const { work_count: collected, aweme_count: total, works: checkpoint } = collectionProgress;
progressHint = `已采集 ${collected} / 总计 ${total ?? '未知'}`;
if (total == null) progressHint += ';抖音作品总数未知,暂无法判断是否完整。';
else if (collected < total) progressHint += ';尚未采集完整,当前仅展示已采集作品。';
else if (collected > total) progressHint += ';已采集数量与抖音总数不一致,请重新同步确认。';
else progressHint += ';当前已采集数量与抖音总数一致。';
if (checkpoint.status === 'running') progressHint += ' 正在采集。';
if (checkpoint.status === 'failed' || checkpoint.status === 'blocked') progressHint += ` 采集未完成:${checkpoint.last_error || '请查看账号采集状态。'}`;
}
return (
<>
<Card
@@ -197,7 +229,12 @@ export default function WorksGallery({ sourceId, sourceType, emptyHint, extraFil
}
>
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
<Typography.Text type="secondary">共 {workPageInfo.total} 个作品;作品由系统按采集间隔自动同步。</Typography.Text>
<Flex vertical gap={4}>
{sourceType === 'owned' ? <Typography.Text>{progressHint}</Typography.Text> : null}
<Typography.Text type="secondary">
{worksError ? '作品列表暂不可用' : `${minGrowth !== null || extraFilters ? '符合筛选' : sourceType === 'owned' ? '当前列表' : '共'} ${workPageInfo.total} 个作品`};作品由系统按采集间隔自动同步。
</Typography.Text>
</Flex>
<Flex align="center" gap={8}>
<FireOutlined style={{ color: 'rgba(0,0,0,0.45)' }} />
<Typography.Text type="secondary">24h 点赞增量 ≥</Typography.Text>
@@ -214,6 +251,9 @@ export default function WorksGallery({ sourceId, sourceType, emptyHint, extraFil
/>
</Flex>
</Flex>
{progressError ? (
<Alert type="error" showIcon title={progressError.message} style={{ marginBottom: 16 }} action={<Button size="small" onClick={loadWorks}>重试</Button>} />
) : null}
{worksError ? (
<Alert
type="error"
@@ -269,14 +309,15 @@ export default function WorksGallery({ sourceId, sourceType, emptyHint, extraFil
</Spin>
{workPageInfo.total > works.length ? (
<Flex justify="flex-end" style={{ marginTop: 12 }}>
<Space>
<Button size="small" onClick={() => setWorkPage((page) => Math.max(1, page - 1))} disabled={workPage === 1 || worksPending}>
上一页
</Button>
<Button size="small" onClick={() => setWorkPage((page) => page + 1)} disabled={!workPageInfo.hasNext || worksPending}>
下一页
</Button>
</Space>
<Pagination
current={workPage}
pageSize={25}
total={workPageInfo.total}
showSizeChanger={false}
showTotal={(total) => `${sourceType === 'owned' ? '当前列表共' : '共'} ${total} 个作品`}
disabled={worksPending}
onChange={(page) => setWorkPage(page)}
/>
</Flex>
) : null}
</Card>