feat(web): 监控作品卡片展示描述,观看/点赞/评论/分享全宽 GRID 贴封面下方
douyin-release-gate / verify (push) Failing after 3m20s

- WorksGallery 卡片封面改走本地缓存 /works/:id/cover(WorkCover 与竞品分析页同链路,
  替换会过期的 douyinpic 签名 URL)
- 卡片新增作品描述(body,两行省略)
- 指标从标题下横排小字改为封面下方全宽四列 GRID(观看/点赞/评论/分享,上值下图标行)
- WorksGallery 为监控账号详情与自有账号详情共用,两处同步生效
- 测试:works-gallery-card 3 例 + 既有 1 例全绿,tsc/antd lint 通过
This commit is contained in:
2026-09-28 11:19:52 +08:00
parent 733b2d4b95
commit 8748f899d4
2 changed files with 118 additions and 28 deletions
+76 -28
View File
@@ -12,9 +12,62 @@ import {
LikeOutlined,
ReloadOutlined,
ShareAltOutlined } from '@ant-design/icons';
import { creatorGet, getList } from '@/services/api';
import { creatorAction, creatorGet, fetchCreatorBinary, getList } from '@/services/api';
import { conflictMessage, dateTime } from '@/utils/helpers';
// 作品封面:从本地缓存接口读取(认证 fetch + blob URL);缓存缺失时展示占位。
// 原始 douyinpic URL 带签名会过期,与竞品分析页共用同一条 /works/:id/cover 链路。
export function WorkCover({ workID, title }: { workID: string; title?: string }) {
const [src, setSrc] = useState<string | null>(null);
const [missing, setMissing] = useState(false);
useEffect(() => {
let objectURL: string | null = null;
setSrc(null);
setMissing(false);
fetchCreatorBinary(`/creator/works/${encodeURIComponent(workID)}/cover`)
.then((blob) => {
objectURL = URL.createObjectURL(blob);
setSrc(objectURL);
})
.catch(() => setMissing(true));
return () => {
if (objectURL) URL.revokeObjectURL(objectURL);
};
}, [workID]);
if (src) {
return <img src={src} alt={title || '作品封面'} style={{ width: '100%', aspectRatio: '16 / 9', objectFit: 'cover' }} />;
}
return (
<Flex align="center" justify="center" style={{ aspectRatio: '16 / 9', background: '#f5f5f5' }}>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>{missing ? '封面未缓存' : '封面加载中'}</Typography.Text>
</Flex>
);
}
// 观看/点赞/评论/分享全宽四列 GRID,贴在封面下方(body padding 置 0,本行自带上下边框)。
function WorkStatsGrid({ work }: { work: any }) {
const items = [
{ key: 'play_count', label: '观看', icon: EyeOutlined },
{ key: 'likes', label: '点赞', icon: LikeOutlined },
{ key: 'comments_count', label: '评论', icon: CommentOutlined },
{ key: 'shares', label: '分享', icon: ShareAltOutlined },
];
return (
<Flex style={{ margin: '10px 0 0', padding: '8px 0', borderTop: '1px solid #f0f0f0', borderBottom: '1px solid #f0f0f0' }}>
{items.map(({ key, label, icon: Icon }) => (
<Flex key={key} vertical align="center" gap={2} style={{ flex: 1, minWidth: 0 }}>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
<Icon /> {label}
</Typography.Text>
<Typography.Text strong style={{ fontSize: 12 }}>
{work[key] ?? '—'}
</Typography.Text>
</Flex>
))}
</Flex>
);
}
const metricSeriesMeta = [
{ key: 'play_count', label: '播放' },
{ key: 'likes', label: '点赞' },
@@ -174,36 +227,31 @@ export default function WorksGallery({ sourceId, sourceType, emptyHint }: WorksG
<Card
hoverable
style={{ height: '100%' }}
styles={{ body: { padding: '0 0 12px' } }}
onClick={() => openWorkDetail(work)}
cover={
work.cover_url ? (
<img src={work.cover_url} alt={work.title || '作品封面'} style={{ width: '100%', aspectRatio: '16 / 9', objectFit: 'cover' }} />
) : (
<Flex align="center" justify="center" style={{ aspectRatio: '16 / 9', background: '#f5f5f5' }}>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>无封面</Typography.Text>
</Flex>
)
}
cover={<WorkCover workID={work.id} title={work.title} />}
>
<Flex vertical gap={8} style={{ minWidth: 0 }}>
<Typography.Paragraph strong ellipsis={{ rows: 2 }} style={{ marginBottom: 0 }}>
{work.title || '无标题'}
</Typography.Paragraph>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{work.published_at ? dateTime(work.published_at) : '发布时间待核验'}
</Typography.Text>
<Flex gap={12} wrap="wrap">
<Typography.Text style={{ fontSize: 12 }}><EyeOutlined /> {work.play_count ?? '—'}</Typography.Text>
<Typography.Text style={{ fontSize: 12 }}><LikeOutlined /> {work.likes ?? '—'}</Typography.Text>
<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}
<div style={{ padding: '12px 16px 0' }}>
<Flex vertical gap={8} style={{ minWidth: 0 }}>
<Typography.Paragraph strong ellipsis={{ rows: 2 }} style={{ marginBottom: 0 }}>
{work.title || '无标题'}
</Typography.Paragraph>
{work.body ? (
<Typography.Paragraph type="secondary" ellipsis={{ rows: 2 }} style={{ fontSize: 12, marginBottom: 0 }}>
{work.body}
</Typography.Paragraph>
) : null}
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{work.published_at ? dateTime(work.published_at) : '发布时间待核验'}
</Typography.Text>
) : null}
</Flex>
</Flex>
</div>
<WorkStatsGrid work={work} />
{work.likes_growth != null && work.likes_growth > 0 ? (
<Typography.Text type="warning" style={{ display: 'block', padding: '8px 16px 0', fontSize: 12 }}>
<FireOutlined /> 24h +{work.likes_growth}
</Typography.Text>
) : null}
</Card>
</Col>
))}