// 我的账号列表(owned):语义对齐 web.archived AccountManagementList(mode="owned")。 // 监控账号列表已拆分至 components/accounts/MonitoringAccountList.tsx(card list)。 // 数据走 GET /creator/accounts/monitor-views(画像 + 作品聚合 + 最新画像指标快照)。 // 「启动」= POST /phase-a/accounts/:id/start(恢复账号+启动环境),成功后弹窗展示网关浏览器 // 登录弹窗(POST /creator/accounts/:id/login-qr),自动核验身份、刷新过期二维码。 import { useCallback, useEffect, useState } from 'react'; import { history } from '@umijs/max'; import { Alert, App, Avatar, Button, Card, Flex, Form, Modal, Popconfirm, Select, Space, Table, Tag, Typography } from 'antd'; 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, 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; runtime_id?: string; runtime_cleanup_pending?: boolean; network_exit_id?: string; 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'; environment?: EnvironmentView; work_count?: number; latest_published_at?: string | null; follower_count?: number | null; following_count?: number | null; total_favorited?: number | null; aweme_count?: number | null; friend_count?: number | null; comment_total?: number | null; } // 对齐监控列表 formatCount:>=1w 显示 x.xw,空值显示 —。 function formatCount(value?: number | null): string { if (value === null || value === undefined) return '—'; return value >= 10000 ? `${(value / 10000).toFixed(1)}w` : `${value}`; } export default function AccountManagementList() { const [rows, setRows] = useState([]); const [createOpen, setCreateOpen] = useState(false); const [environmentTarget, setEnvironmentTarget] = useState(null); const [pending, setPending] = useState(true); const [error, setError] = useState(null); const [tagTarget, setTagTarget] = useState(null); const [tagForm] = Form.useForm<{ tags: string[] }>(); const [tagBusy, setTagBusy] = useState(false); const [actionBusy, setActionBusy] = useState(''); const [loginTarget, setLoginTarget] = useState(null); const { message: messageApi } = App.useApp(); const { vertical } = useOverflowGrid(rows.length); const load = useCallback(async () => { setPending(true); setError(null); try { const [rows, environments] = await Promise.all([ creatorGet('/creator/accounts/monitor-views'), creatorGet('/creator/environments'), ]); 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 { setPending(false); } }, []); useEffect(() => { load(); }, [load]); async function deleteAccount(row: Row) { setActionBusy(row.id); try { await remove('accounts', row.id); messageApi.success('自有账号已删除。'); await load(); } catch (deleteError) { messageApi.error(conflictMessage(deleteError, '自有账号删除失败')); } finally { setActionBusy(''); } } function openTags(row: Row) { setTagTarget(row); tagForm.setFieldsValue({ tags: Array.isArray(row.tags) ? row.tags : [] }); } // 启动:恢复账号并启动环境;成功后直接弹登录画面(环境已就绪,重复点击幂等)。 async function startAccount(row: Row) { setActionBusy(row.id); try { await creatorAction(`/phase-a/accounts/${encodeURIComponent(row.id)}/start`); messageApi.success('运行环境已启动。'); setLoginTarget(row); } catch (startError) { messageApi.error(conflictMessage(startError, '启动未完成:账号或环境不满足就绪条件,请按不可运行原因处理后重试。')); } finally { setActionBusy(''); } } async function saveTags() { if (!tagTarget) return; const { tags } = await tagForm.validateFields(); setTagBusy(true); try { await creatorUpdate(`/creator/accounts/${encodeURIComponent(tagTarget.id)}/tags`, { tags }); setRows((current) => current.map((row) => (row.id === tagTarget.id ? { ...row, tags } : row))); setTagTarget(null); messageApi.success('标签已保存。'); } catch (saveError) { messageApi.error(conflictMessage(saveError, '标签保存失败')); } finally { setTagBusy(false); } } const columns: ColumnsType = [ { title: '账号', dataIndex: 'name', ...fixedLeft({ width: 240 }), render: (_, account) => ( {(account.name || '账').slice(0, 1).toUpperCase()} {account.pending_environment ? ( 待登录 ) : ( history.push(`/accounts/${account.id}`)}> {account.name || '待登录'} )} ) }, { title: '粉丝', render: (_, account) => formatCount(account.follower_count), }, { title: '关注', render: (_, account) => formatCount(account.following_count), }, { title: '获赞', render: (_, account) => formatCount(account.total_favorited), }, { title: '互关', render: (_, account) => formatCount(account.friend_count), }, { title: '评论', render: (_, account) => formatCount(account.comment_total), }, { title: '作品数据', dataIndex: 'work_count', render: (_, account) => account.pending_environment ? ( 登录后显示作品数据 ) : ( {account.aweme_count == null ? '作品总数未知' : `${account.aweme_count} 个作品`} 已采集 {account.work_count ?? '未知'} / 总计 {account.aweme_count ?? '未知'} 最近发布 {account.latest_published_at ? dateTime(account.latest_published_at) : '—'} ) }, { title: '标签', dataIndex: 'tags', render: (tags?: string[]) => (tags?.length ? {tags.map((tag) => {tag})} : 未设置) }, { title: '运行环境', render: (_, account) => { const env = account.environment; if (!env) return 未绑定运行环境; return ( {env.name || env.alias || '—'} {env.runtime_cleanup_pending ? '停止待确认' : env.runtime_id ? '运行中' : '未启动'} 网关 {env.gateway || '—'} {env.network_exit_id ? `出口 ${env.network_exit_id} · ${env.network_exit_health === 'healthy' ? '健康' : env.network_exit_health || '状态未知'}` : '机器直连'} ); } }, { title: '状态', dataIndex: 'status', render: (_, account) => { if (account.pending_environment) return 待登录; // 与详情页同款三层判定(绑定/出口/账号),环境未绑定时退化为账号启停状态。 const readiness = accountReadiness(account, account.environment ?? null); return ( {readiness.label} ); } }, { title: '业务状态', dataIndex: 'business_status', render: (status: Row['business_status']) => { if (!status) return —; const statuses = { normal: { label: '正常', color: 'success' }, muted: { label: '禁言', color: 'warning' }, banned: { label: '封禁', color: 'error' }, deleted: { label: '注销', color: 'default' }, }; const { label, color } = statuses[status]; return {label}; }, }, { title: '操作', key: 'actions', align: 'right', ...fixedRight({ width: 300 }), render: (_, account) => { const environmentAlias = account.pending_environment; if (environmentAlias) { return ( ); } return ( deleteAccount(account)} disabled={actionBusy === account.id} > ); }, }, ]; return ( 共 {rows.length} 项 {createOpen ? setCreateOpen(false)} onSuccess={() => { setCreateOpen(false); void load(); }} /> : null} {environmentTarget ? ( setEnvironmentTarget(null)} onSuccess={() => { setEnvironmentTarget(null); void load(); }} /> ) : null} {error ? ( 重试} /> ) : null} { if (!tagBusy) setTagTarget(null); }} onOk={saveTags} confirmLoading={tagBusy} okText={tagBusy ? '保存中…' : '保存标签'} >