feat: unify account environments and work collection
This commit is contained in:
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user