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
@@ -10,12 +10,13 @@ import { PlusOutlined, ReloadOutlined } from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import { remove, creatorUpdate, creatorGet, creatorAction } from '@/services/api';
import { accountReadiness, conflictMessage, dateTime } from '@/utils/helpers';
import { fixedLeft, fixedRight, tablePagination, tableScroll, useOverflowGrid } from '@/utils/table';
import { fixedLeft, fixedRight, tablePagination, useOverflowGrid } from '@/utils/table';
import AccountLoginModal from '@/components/accounts/AccountLoginModal';
import AccountCreateModal from '@/components/accounts/AccountCreateModal';
// 仅用 antd 默认组件:Table/Tag/Modal/Popconfirm/Select(tags)。
interface EnvironmentView {
id?: number;
gateway?: string;
name?: string;
alias?: string;
@@ -25,15 +26,21 @@ interface EnvironmentView {
network_exit_health?: string;
}
interface PendingEnvironment extends EnvironmentView {
alias: string;
network_exit?: { id: string; health_status: string };
}
interface Row {
id: string;
pending_environment?: string;
name: string;
platform_account_key: string;
avatar_url?: string;
douyin_number?: string;
tags?: string[];
runtime_status?: string;
business_status: 'normal' | 'muted' | 'banned' | 'deleted';
business_status?: 'normal' | 'muted' | 'banned' | 'deleted';
environment?: EnvironmentView;
work_count?: number;
latest_published_at?: string | null;
@@ -54,7 +61,6 @@ function formatCount(value?: number | null): string {
export default function AccountManagementList() {
const [rows, setRows] = useState<Row[]>([]);
const [createOpen, setCreateOpen] = useState(false);
const [environments, setEnvironments] = useState<{ alias: string; gateway: string; runtime_id?: string }[]>([]);
const [environmentTarget, setEnvironmentTarget] = useState<string | null>(null);
const [pending, setPending] = useState(true);
const [error, setError] = useState<any>(null);
@@ -64,7 +70,7 @@ export default function AccountManagementList() {
const [actionBusy, setActionBusy] = useState('');
const [loginTarget, setLoginTarget] = useState<Row | null>(null);
const { message: messageApi } = App.useApp();
const { vertical, horizontal } = useOverflowGrid(rows.length);
const { vertical } = useOverflowGrid(rows.length);
const load = useCallback(async () => {
setPending(true);
@@ -73,13 +79,28 @@ export default function AccountManagementList() {
const [rows, environments] = await Promise.all([
creatorGet('/creator/accounts/monitor-views'), creatorGet('/creator/environments'),
]);
setEnvironments(environments);
setRows(
(rows ?? []).map((account: any) => ({
...account,
name: account.name,
tags: account.tags ?? [] })),
);
setRows([
...(rows ?? []).map((account: Row) => ({ ...account, tags: account.tags ?? [] })),
...environments.map((environment: PendingEnvironment): Row => ({
id: `environment:${environment.alias}`,
pending_environment: environment.alias,
name: '',
platform_account_key: '',
tags: [],
follower_count: 0,
following_count: 0,
total_favorited: 0,
friend_count: 0,
comment_total: 0,
aweme_count: 0,
work_count: 0,
environment: {
...environment,
network_exit_id: environment.network_exit?.id,
network_exit_health: environment.network_exit?.health_status,
},
})),
].sort((a, b) => (b.environment?.id ?? 0) - (a.environment?.id ?? 0)));
} catch (loadError) {
setError(loadError);
} finally {
@@ -143,14 +164,18 @@ export default function AccountManagementList() {
{
title: '账号',
dataIndex: 'name',
...fixedLeft<Row>({}),
...fixedLeft<Row>({ width: 240 }),
render: (_, account) => (
<div>
<Space><Avatar size="small" src={account.avatar_url} /><Typography.Link onClick={() => history.push(`/accounts/${account.id}`)}>{account.name || '待登录'}</Typography.Link></Space>
<Typography.Paragraph type="secondary" style={{ marginBottom: 0 }} ellipsis={{ tooltip: true }}>
{account.platform_account_key}
</Typography.Paragraph>
</div>
<Flex align="center" gap={12}>
<Avatar size={40} shape="square" src={account.avatar_url || undefined}>{(account.name || '账').slice(0, 1).toUpperCase()}</Avatar>
{account.pending_environment ? (
<Typography.Text>待登录</Typography.Text>
) : (
<Typography.Link onClick={() => history.push(`/accounts/${account.id}`)}>
{account.name || '待登录'}
</Typography.Link>
)}
</Flex>
) },
{
title: '粉丝',
@@ -175,9 +200,14 @@ export default function AccountManagementList() {
{
title: '作品数据',
dataIndex: 'work_count',
render: (_, account) => (
render: (_, account) => account.pending_environment ? (
<Typography.Text type="secondary">登录后显示作品数据</Typography.Text>
) : (
<Flex vertical>
<Typography.Text>{formatCount(account.work_count ?? account.aweme_count)} 个作品</Typography.Text>
<Typography.Text>{account.aweme_count == null ? '作品总数未知' : `${account.aweme_count} 个作品`}</Typography.Text>
<Typography.Text type="secondary">
已采集 {account.work_count ?? '未知'} / 总计 {account.aweme_count ?? '未知'}
</Typography.Text>
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
最近发布 {account.latest_published_at ? dateTime(account.latest_published_at) : '—'}
</Typography.Text>
@@ -215,6 +245,7 @@ export default function AccountManagementList() {
title: '状态',
dataIndex: 'status',
render: (_, account) => {
if (account.pending_environment) return <Tag>待登录</Tag>;
// 与详情页同款三层判定(绑定/出口/账号),环境未绑定时退化为账号启停状态。
const readiness = accountReadiness(account, account.environment ?? null);
return (
@@ -227,6 +258,7 @@ export default function AccountManagementList() {
title: '业务状态',
dataIndex: 'business_status',
render: (status: Row['business_status']) => {
if (!status) return <Typography.Text type="secondary">—</Typography.Text>;
const statuses = {
normal: { label: '正常', color: 'success' },
muted: { label: '禁言', color: 'warning' },
@@ -241,35 +273,46 @@ export default function AccountManagementList() {
title: '操作',
key: 'actions',
align: 'right',
...fixedRight<Row>({}),
render: (_, account) => (
<Space wrap>
<Button type="text" size="small" onClick={() => startAccount(account)} loading={actionBusy === account.id}>
启动
</Button>
<Button type="text" size="small" onClick={() => openTags(account)}>
编辑标签
</Button>
<Button type="text" size="small" onClick={() => history.push(`/accounts/${account.id}/edit`)}>
编辑账号
</Button>
<Popconfirm
title={`确定删除自有账号“${account.name || '待登录'}”吗?相关专属资源也会被清理。`}
onConfirm={() => deleteAccount(account)}
disabled={actionBusy === account.id}
>
<Button type="text" size="small" danger disabled={actionBusy === account.id}>
删除
...fixedRight<Row>({ width: 300 }),
render: (_, account) => {
const environmentAlias = account.pending_environment;
if (environmentAlias) {
return (
<Button type="link" size="small" onClick={() => setEnvironmentTarget(environmentAlias)}>
登录并同步账号
</Button>
</Popconfirm>
</Space>
) },
);
}
return (
<Space>
<Button type="text" size="small" onClick={() => startAccount(account)} loading={actionBusy === account.id}>
启动
</Button>
<Button type="text" size="small" onClick={() => openTags(account)}>
编辑标签
</Button>
<Button type="text" size="small" onClick={() => history.push(`/accounts/${account.id}/edit`)}>
编辑账号
</Button>
<Popconfirm
title={`确定删除自有账号“${account.name || '待登录'}”吗?相关专属资源也会被清理。`}
onConfirm={() => deleteAccount(account)}
disabled={actionBusy === account.id}
>
<Button type="text" size="small" danger disabled={actionBusy === account.id}>
删除
</Button>
</Popconfirm>
</Space>
);
},
},
];
return (
<Card variant="outlined">
<Flex justify="space-between" align="center" style={{ marginBottom: 16 }}>
<Typography.Text type="secondary">共 {rows.length} 个自有账号</Typography.Text>
<Typography.Text type="secondary">共 {rows.length} 项</Typography.Text>
<Space>
<Button icon={<ReloadOutlined />} onClick={load} disabled={pending} aria-label="刷新" />
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
@@ -283,17 +326,6 @@ export default function AccountManagementList() {
<AccountLoginModal accountID={environmentTarget} accountName="待登录环境" resource="environments"
onClose={() => setEnvironmentTarget(null)} onSuccess={() => { setEnvironmentTarget(null); void load(); }} />
) : null}
{environments.length > 0 ? (
<Card title="待登录环境">
<Table rowKey="alias" dataSource={environments} pagination={tablePagination()} sticky columns={[
{ title: '账号', render: () => <Typography.Text>待登录</Typography.Text> },
{ title: '网关', dataIndex: 'gateway' },
{ title: '环境标识', dataIndex: 'alias', render: (alias: string) => <Typography.Text copyable>{alias}</Typography.Text> },
{ title: '状态', render: () => <Tag>待登录</Tag> },
{ title: '操作', render: (_, env) => <Button type="link" onClick={() => setEnvironmentTarget(env.alias)}>登录并同步账号</Button> },
]} />
</Card>
) : null}
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
) : null}
@@ -304,8 +336,8 @@ export default function AccountManagementList() {
loading={pending}
sticky
pagination={tablePagination()}
scroll={tableScroll(horizontal ? 1320 : undefined, vertical ? 'calc(100vh - 300px)' : undefined)}
locale={{ emptyText: '暂无自有账号。' }}
scroll={{ x: 'max-content', y: vertical ? 'calc(100vh - 300px)' : undefined }}
locale={{ emptyText: '暂无账号或待登录环境。' }}
/>
<Modal
open={Boolean(tagTarget)}