From c2bccba352e6f6122d5444df5b5c47834b3054e8 Mon Sep 17 00:00:00 2001 From: Rogee Date: Mon, 28 Sep 2026 15:54:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor(web):=20=E5=88=A0=E9=99=A4=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E7=A9=BA=E8=BD=AC=E9=93=BE=E8=B7=AF=EF=BC=88=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E4=B8=AD=E5=BF=83/=E8=8D=89=E7=A8=BF/attempt/?= =?UTF-8?q?=E8=BF=90=E8=90=A5=E5=B7=A5=E4=BD=9C=E5=8F=B0/=E7=B4=A0?= =?UTF-8?q?=E6=9D=90=E9=9D=A2=E6=9D=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删页面目录 tasks/ drafts/ attempts/ creator/workbench/ - 竞品页移除素材处理 Drawer 与素材操作 - metadata 菜单与页头规则同步收敛 - api.ts/listQuery.ts 移除对应 resource 与函数 - 固定表头测试文件清单同步 --- web/src/pages/accounts/$id/index.tsx | 59 +- web/src/pages/attempts/$id/index.tsx | 68 -- web/src/pages/creator/competitors/index.tsx | 207 +--- web/src/pages/creator/workbench/index.tsx | 1066 ------------------- web/src/pages/drafts/$id/index.tsx | 212 ---- web/src/pages/tasks/$id/index.tsx | 250 ----- web/src/pages/tasks/index.tsx | 185 ---- web/src/services/api.ts | 34 +- web/src/utils/listQuery.ts | 6 - web/src/utils/metadata.tsx | 7 - web/tests/table-fixed-header.test.cjs | 1 - 11 files changed, 11 insertions(+), 2084 deletions(-) delete mode 100644 web/src/pages/attempts/$id/index.tsx delete mode 100644 web/src/pages/creator/workbench/index.tsx delete mode 100644 web/src/pages/drafts/$id/index.tsx delete mode 100644 web/src/pages/tasks/$id/index.tsx delete mode 100644 web/src/pages/tasks/index.tsx diff --git a/web/src/pages/accounts/$id/index.tsx b/web/src/pages/accounts/$id/index.tsx index bd2e9fd..bffc296 100644 --- a/web/src/pages/accounts/$id/index.tsx +++ b/web/src/pages/accounts/$id/index.tsx @@ -6,8 +6,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { history, useParams } from '@umijs/max'; import { Line } from '@ant-design/charts'; -import { Alert, App, Button, Card, Descriptions, Flex, Form, Input, Modal, Space, Tag, Typography } from 'antd'; -import { accountAction, createDraft, getOne, getList, creatorGet, creatorAction } from '@/services/api'; +import { Alert, App, Button, Card, Descriptions, Flex, Modal, Space, Tag, Typography } from 'antd'; +import { accountAction, getOne, getList, creatorGet, creatorAction } from '@/services/api'; import { accountReadiness, conflictMessage, dateTime, platformLabel } from '@/utils/helpers'; import { usePageActions, usePageInfo } from '@/components/PageActions'; import WorksGallery from '@/components/creator/WorksGallery'; @@ -65,13 +65,10 @@ export default function Page() { const [account, setAccount] = useState(null); const [binding, setBinding] = useState(null); const [browsersError, setBrowsersError] = useState(null); - const [drafts, setDrafts] = useState([]); const [pending, setPending] = useState(true); const [error, setError] = useState(null); const [pauseOpen, setPauseOpen] = useState(false); const [actionBusy, setActionBusy] = useState(false); - const [draftForm] = Form.useForm<{ content: string }>(); - const [draftBusy, setDraftBusy] = useState(false); const [collectionStatus, setCollectionStatus] = useState(null); const [accountMetrics, setAccountMetrics] = useState([]); const { message: messageApi } = App.useApp(); @@ -124,12 +121,6 @@ export default function Page() { setBrowsersError(browsersResult.reason); setBinding(undefined); } - try { - const draftResult = await getList({ resource: 'drafts', filters: { account_id: id } }); - setDrafts(draftResult.data ?? []); - } catch { - setDrafts([]); - } // 监控数据:采集失败不阻塞页面主体,采集状态为空时按待采集展示。 try { setCollectionStatus(await creatorGet(`/creator/accounts/${encodeURIComponent(id)}/collection-status`)); @@ -184,7 +175,7 @@ export default function Page() { try { await accountAction(account.id, action); messageApi.success( - action === 'pause' ? '账号已暂停;既有 hold 任务需后续逐条核验。' : '账号已恢复;既有 hold 任务没有自动恢复。', + action === 'pause' ? '账号已暂停,运行环境已停止。' : '账号已恢复。', ); await loadAll(); } catch (reason) { @@ -194,22 +185,6 @@ export default function Page() { } } - async function onCreateDraft() { - if (!readiness?.ready) return; - const { content } = await draftForm.validateFields(); - setDraftBusy(true); - try { - const draft = await createDraft(account.id, content); - await loadAll(); - draftForm.resetFields(); - messageApi.success(`草稿版本 ${draft.version} 已创建,请进入只读快照核对。`); - } catch (reason) { - messageApi.error(conflictMessage(reason)); - } finally { - setDraftBusy(false); - } - } - if (pending) return ; if (error || !account) return ; @@ -256,7 +231,7 @@ export default function Page() { ) : null} - + @@ -306,35 +281,11 @@ export default function Page() { ) : ( - 尚未绑定运行环境。账号暂停且资源就绪后,可在运行环境页创建绑定。 + 尚未绑定运行环境。账号创建时若绑定失败,可联系管理员排查后重试。 - history.push('/browsers')}>前往运行环境 )} - - - 仅支持单用户 Phase A Mock 文本;内部 ID 与版本由系统生成。 -
- - - - -
- {drafts.length ? ( - - {drafts.map((draft: any) => ( - - {draft.content} - v{draft.version} - - ))} - - ) : null} -
-
(); - const [attempt, setAttempt] = useState(null); - const [pending, setPending] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - let cancelled = false; - setPending(true); - setError(null); - getOne('attempts', id) - .then((result) => { - if (!cancelled) setAttempt(result); - }) - .catch((loadError) => { - if (!cancelled) setError(loadError); - }) - .finally(() => { - if (!cancelled) setPending(false); - }); - return () => { - cancelled = true; - }; - }, [id]); - - if (pending) return ; - if (error) return ; - if (!attempt) return null; - - return ( -
-
- - {attempt.id} - -
- - - - {attempt.outcome || '执行中'} - {dateTime(attempt.started_at)} - - {`${attempt.browser_env_alias || '未记录'} · ${attempt.network_exit_id || '未记录'} · ${attempt.binding_version || '未记录'}`} - - - - - {attempt.browser_env_alias ? ( - - ) : null} - {attempt.network_exit_id ? ( - - ) : null} - - - -
- ); -} diff --git a/web/src/pages/creator/competitors/index.tsx b/web/src/pages/creator/competitors/index.tsx index cc8ff11..2490ca2 100644 --- a/web/src/pages/creator/competitors/index.tsx +++ b/web/src/pages/creator/competitors/index.tsx @@ -1,11 +1,11 @@ // 竞品分析:作品列表对齐 ant-design-pro card-list——antd Row/Col 响应式栅格 + Card 原样组合; -// 作品详情与素材处理均走 Drawer;日期筛选用 antd DatePicker;筛选自动刷新、手动翻页不变。 +// 作品详情走 Drawer;日期筛选用 antd DatePicker;筛选自动刷新、手动翻页不变。 // 封面读本地缓存(/works/:id/cover,认证 fetch + blob);发布时间状态详情见 publishedAtStatusMeta 注释。 import { useEffect, useState } from 'react'; import { CommentOutlined, LikeOutlined, ShareAltOutlined } from '@ant-design/icons'; -import { Alert, App, Button, Card, Col, DatePicker, Drawer, Empty, Flex, Input, InputNumber, Modal, Row, Select, Space, Spin, Table, Tag, Tooltip, Typography } from 'antd'; +import { Alert, App, Button, Card, Col, DatePicker, Drawer, Empty, Flex, InputNumber, Row, Select, Space, Spin, Table, Tag, Tooltip, Typography } from 'antd'; import type { Dayjs } from 'dayjs'; -import { creatorAction, creatorGet, creatorUpdate, fetchCreatorBinary, getList } from '@/services/api'; +import { creatorGet, fetchCreatorBinary, getList } from '@/services/api'; import { conflictMessage, dateTime } from '@/utils/helpers'; const publishedAtStatusOptions = [ @@ -65,10 +65,6 @@ function WorkCover({ workID, title }: { workID: string; title?: string }) { ); } -function stepTag(status: string, label: string) { - return {label}; -} - export default function Page() { const [works, setWorks] = useState([]); const [workPage, setWorkPage] = useState(1); @@ -79,12 +75,6 @@ export default function Page() { const [workDetail, setWorkDetail] = useState(null); const [workMetrics, setWorkMetrics] = useState([]); const [workDetailPending, setWorkDetailPending] = useState(false); - const [material, setMaterial] = useState(null); - const [materialPending, setMaterialPending] = useState(false); - const [rewriteRequirement, setRewriteRequirement] = useState(''); - const [rewriteTitle, setRewriteTitle] = useState(''); - const [rewriteScript, setRewriteScript] = useState(''); - const [pendingSwitchWork, setPendingSwitchWork] = useState(null); const { message: messageApi } = App.useApp(); async function load() { @@ -119,12 +109,6 @@ export default function Page() { setWorkPage(1); }, [filters]); - const materialDirty = - material && - (rewriteRequirement !== (material.rewrite_requirement || '') || - rewriteTitle !== (material.generated_title || '') || - rewriteScript !== (material.generated_script || '')); - async function openWorkDetail(workID: string) { setWorkDetailPending(true); try { @@ -141,104 +125,6 @@ export default function Page() { } } - function selectMaterial(workID: string) { - if (materialDirty) { - setPendingSwitchWork(workID); - return; - } - doSelectMaterial(workID); - } - - async function doSelectMaterial(workID: string) { - setPendingSwitchWork(null); - setMaterialPending(true); - try { - const result = await creatorAction(`/creator/works/${encodeURIComponent(workID)}/material/select`); - setMaterial(result); - setRewriteRequirement(result.rewrite_requirement || ''); - setRewriteTitle(result.generated_title || ''); - setRewriteScript(result.generated_script || ''); - messageApi.info('素材已选择;点击开始处理后才会执行真实下载、音轨提取和转写。'); - } catch (actionError) { - messageApi.error(conflictMessage(actionError, '素材选择失败')); - } finally { - setMaterialPending(false); - } - } - - async function processMaterial() { - if (!material) return; - setMaterialPending(true); - try { - const result = await creatorAction(`/creator/works/${encodeURIComponent(material.work_id)}/material/process`); - setMaterial(result); - const failedStep = - result.failed_step || ['download', 'audio', 'transcription'].find((step) => result[`${step}_status`] === 'failed'); - if (failedStep) { - messageApi.warning(`素材处理未完成(${failedStep}):${result.failure_reason || '请查看步骤状态后重试。'}`); - } else { - messageApi.success('素材处理完成。'); - } - } catch (actionError) { - messageApi.error(conflictMessage(actionError, '素材处理失败')); - } finally { - setMaterialPending(false); - } - } - - async function confirmRewrite() { - if (!material) return; - setMaterialPending(true); - try { - const result = await creatorAction(`/creator/works/${encodeURIComponent(material.work_id)}/material/rewrite/confirm`, { - requirement: rewriteRequirement }); - setMaterial(result); - messageApi.info('已确认仿写要求,请在可编辑字段中复核并保存结果。'); - } catch (actionError) { - messageApi.error(conflictMessage(actionError, '仿写确认失败')); - } finally { - setMaterialPending(false); - } - } - - async function generateRewrite() { - if (!material) return; - setMaterialPending(true); - try { - const result = await creatorAction(`/creator/works/${encodeURIComponent(material.work_id)}/material/rewrite/generate`); - setMaterial(result); - setRewriteTitle(result.generated_title || ''); - setRewriteScript(result.generated_script || ''); - messageApi.info('仿写草稿已生成,请人工复核后保存。'); - } catch (actionError) { - messageApi.error(conflictMessage(actionError, '仿写草稿生成失败')); - } finally { - setMaterialPending(false); - } - } - - async function saveRewrite() { - if (!material) return; - setMaterialPending(true); - try { - const result = await creatorUpdate(`/creator/works/${encodeURIComponent(material.work_id)}/material/rewrite`, { - title: rewriteTitle, - script: rewriteScript }); - setMaterial(result); - messageApi.info('仿写标题和口播已保存,可继续人工复核。'); - } catch (actionError) { - messageApi.error(conflictMessage(actionError, '仿写结果保存失败')); - } finally { - setMaterialPending(false); - } - } - - const materialReadyForConfirm = - material && - material.download_status === 'succeeded' && - !['not_started', 'running', 'failed'].includes(material.audio_status) && - !['not_started', 'running', 'failed'].includes(material.transcription_status); - return ( <> @@ -288,14 +174,6 @@ export default function Page() { , - , ...(work.original_url ? [原文] : []), ]} > @@ -342,73 +220,7 @@ export default function Page() { ) : null}
- setMaterial(null)} - size="large" - title="素材处理" - destroyOnHidden - > - {material ? ( - - 每一步都保留真实状态;失败不会被显示为成功。 - -
- -
- - {stepTag(material.download_status, `下载:${material.download_status}`)} - {stepTag(material.audio_status, `音轨:${material.audio_status}`)} - {stepTag(material.transcription_status, `转写:${material.transcription_status}`)} - - {material.failure_reason ? ( - - ) : null} -
- 仿写要求 - 必须先确认要求,才允许保存可编辑结果。 - setRewriteRequirement(event.target.value)} - disabled={Boolean(material.rewrite_confirmed_at) || materialPending} - /> -
- {material.rewrite_confirmed_at ? ( - <> - - - 生成结果不会自动发布,保存前必须人工复核。 - -
- 可编辑标题 - setRewriteTitle(event.target.value)} style={{ marginTop: 4 }} /> -
-
- 可编辑口播 - setRewriteScript(event.target.value)} style={{ marginTop: 4 }} /> -
-
- -
- - ) : ( -
- -
- )} -
-
- ) : null} -
+ setWorkDetail(null)} size="large" title="作品详情"> {workDetail ? ( @@ -441,16 +253,7 @@ export default function Page() { ) : null} - pendingSwitchWork && doSelectMaterial(pendingSwitchWork)} - onCancel={() => setPendingSwitchWork(null)} - > - 当前素材文稿尚未保存,确定放弃并切换吗? - + ); } diff --git a/web/src/pages/creator/workbench/index.tsx b/web/src/pages/creator/workbench/index.tsx deleted file mode 100644 index 9efd404..0000000 --- a/web/src/pages/creator/workbench/index.tsx +++ /dev/null @@ -1,1066 +0,0 @@ -// 运营工作台:语义对齐 web.archived/src/features/creator/workbench/CreatorWorkbenchPage.jsx(提交 d97cade)。 -// 六个页签:评论/线索/规则/事件监听/私信/操作记录;实时更新走 /creator/updates SSE;仅用 antd 默认组件。 -import { useEffect, useRef, useState } from 'react'; -import type { ReactNode } from 'react'; -import { Alert, App, Button, Card, Checkbox, Col, Empty, Flex, Input, Modal, Row, Select, Space, Spin, Table, Tabs, Tag, Typography } from 'antd'; -import type { ColumnsType } from 'antd/es/table'; -import { create, creatorAction, creatorCreate, creatorGet, creatorSubscribe, creatorSyncConversation, creatorUpdate, getList } from '@/services/api'; -import { conflictMessage, dateTime } from '@/utils/helpers'; - -const tabs = [ - ['comments', '评论'], - ['leads', '线索'], - ['rules', '规则'], - ['events', '事件监听'], - ['dms', '私信'], - ['operations', '操作记录'], -] as const; -type TabKey = (typeof tabs)[number][0]; - -function parseKeywords(value: string): string[] { - if (!value.trim()) return []; - const values = value.split(',').map((item) => item.replace(/^[ \t\r\n]+|[ \t\r\n]+$/g, '')); - if (values.some((item) => !item)) throw new Error('关键词不能为空'); - return values; -} - -function newOperationKey(): string { - const crypto = globalThis.crypto; - if (typeof crypto?.randomUUID === 'function') return `manual-${crypto.randomUUID()}`; - if (typeof crypto?.getRandomValues !== 'function') { - throw new Error('当前浏览器不支持安全操作标识,请使用 HTTPS 或受支持的浏览器'); - } - const bytes = crypto.getRandomValues(new Uint8Array(16)); - return `manual-${Array.from(bytes, (value) => value.toString(16).padStart(2, '0')).join('')}`; -} - -const replyDraftKey = (commentID: string) => `creatorhub.reply.${commentID}`; -const dmDraftKey = (accountID: string, conversationID: string) => `creatorhub.dm.${accountID}.${conversationID}`; -const readDraft = (key: string): any => { - try { - const value = JSON.parse(localStorage.getItem(key) || 'null'); - return value && typeof value === 'object' ? value : null; - } catch { - return null; - } -}; -const writeDraft = (key: string, value: unknown) => localStorage.setItem(key, JSON.stringify(value)); -const removeDraft = (key: string) => localStorage.removeItem(key); - -function operationStatus(state?: string, reason?: string): string { - const labels: Record = { - succeeded: '成功(已获得平台证据)', - failed: '失败(平台明确拒绝或执行前失败)', - blocked: '已阻止(未发送)', - processing: '处理中(尚未获得最终结果)', - uncertain: '结果不明(禁止自动重试)' }; - return `${labels[state || ''] || state || '未知状态'}${reason ? `:${reason}` : ''}`; -} - -function statusTag(tone: 'success' | 'warning' | 'error' | 'default', label: string) { - return {label}; -} - -function PageNav({ page, total, count, hasNext, onPageChange }: { page: number; total: number; count: number; hasNext: boolean; onPageChange: (page: number) => void }) { - return ( -
- 当前页 {count} 条 · 共 {total} 条 - - - - -
- ); -} - -function TabShell({ pending, error, onRetry, children }: { pending: boolean; error: any; onRetry: () => void; children: ReactNode }) { - return ( -
- {error ? ( - 重试} /> - ) : null} - {children} -
- ); -} - -function RequiredMark() { - return *; -} - -export default function Page() { - const [tab, setTab] = useState('comments'); - const [data, setData] = useState([]); - const [dataTab, setDataTab] = useState(''); - const [listPage, setListPage] = useState(1); - const [listPageInfo, setListPageInfo] = useState({ total: 0, hasNext: false }); - const [rules, setRules] = useState([]); - const [accounts, setAccounts] = useState([]); - const [conversations, setConversations] = useState([]); - const [conversationAccountID, setConversationAccountID] = useState(''); - const [messages, setMessages] = useState([]); - const [messagePage, setMessagePage] = useState(1); - const [messagePageInfo, setMessagePageInfo] = useState({ total: 0, hasNext: false }); - const [messagePending, setMessagePending] = useState(false); - const [messageError, setMessageError] = useState(null); - const [messageRetry, setMessageRetry] = useState(0); - const [messageSyncing, setMessageSyncing] = useState(false); - const [updateStatus, setUpdateStatus] = useState<'connected' | 'connecting' | 'disconnected'>('disconnected'); - const [conversationID, setConversationID] = useState(''); - const [dmAccountID, setDmAccountID] = useState(''); - const [dmText, setDmText] = useState(''); - const [dmConfirm, setDmConfirm] = useState(null); - const [pending, setPending] = useState(true); - const [error, setError] = useState(null); - const [analyzing, setAnalyzing] = useState(''); - const [selectedCommentIDs, setSelectedCommentIDs] = useState([]); - const [ruleForm, setRuleForm] = useState({ name: '', source_type: 'all', topic: '', include_keywords: '', exclude_keywords: '', ai_requirement: '' }); - const [editingRuleID, setEditingRuleID] = useState(''); - const [replyMode, setReplyMode] = useState<'comment' | 'dm'>('comment'); - const [reply, setReply] = useState({ account_id: '', text: '', target_uid: '', target_comment_id: '', operation_key: '' }); - const [confirm, setConfirm] = useState(null); - const [busy, setBusy] = useState(false); - const [dmOperationKey, setDmOperationKey] = useState(''); - const [pendingDMSwitch, setPendingDMSwitch] = useState<{ kind: 'account' | 'conversation'; id: string } | null>(null); - const loadSequence = useRef(0); - const messageSequence = useRef(0); - const { message: messageApi } = App.useApp(); - - const visibleData = dataTab === tab ? data : []; - const visibleConversations = conversationAccountID === dmAccountID ? conversations : []; - const replyTarget = - visibleData.find((item: any) => item.id === reply.target_comment_id) || - visibleData.find((item: any) => item.comment?.id === reply.target_comment_id)?.comment; - - async function load() { - const sequence = ++loadSequence.current; - setPending(true); - setError(null); - setDataTab(''); - try { - if (tab === 'comments') { - const [comments, ruleList] = await Promise.all([ - getList({ resource: 'creator-comments', page: listPage, pageSize: 25 }), - getList({ resource: 'creator-rules' }), - ]); - if (sequence !== loadSequence.current) return; - setData(comments.data); - setListPageInfo({ total: comments.total, hasNext: Boolean(comments.hasNext) }); - setDataTab('comments'); - setRules(ruleList.data); - } else if (tab === 'leads') { - const result = await getList({ resource: 'creator-leads' }); - if (sequence !== loadSequence.current) return; - setData(result.data); - setDataTab('leads'); - } else if (tab === 'events') { - const result = await getList({ resource: 'creator-events', page: listPage, pageSize: 25 }); - if (sequence !== loadSequence.current) return; - setData(result.data); - setListPageInfo({ total: result.total, hasNext: Boolean(result.hasNext) }); - setDataTab('events'); - } else if (tab === 'rules') { - const result = await getList({ resource: 'creator-rules' }); - if (sequence !== loadSequence.current) return; - setData(result.data); - setDataTab('rules'); - } else if (tab === 'dms') { - if (!dmAccountID) { - setPending(false); - return; - } - const result = await getList({ resource: 'creator-conversations', filters: { account_id: dmAccountID } }); - if (sequence !== loadSequence.current) return; - setConversations(result.data); - setConversationAccountID(dmAccountID); - setData(result.data); - setDataTab('dms'); - setConversationID((current) => (result.data.some((item: any) => item.id === current) ? current : result.data[0]?.id || '')); - } else { - const result = await getList({ resource: 'creator-operations' }); - if (sequence !== loadSequence.current) return; - setData(result.data); - setDataTab('operations'); - } - } catch (loadError) { - if (sequence === loadSequence.current) setError(loadError); - } finally { - if (sequence === loadSequence.current) setPending(false); - } - } - - // 发送账号只拉一次,供回复与私信共用。 - useEffect(() => { - let active = true; - getList({ resource: 'creator-accounts' }) - .then((result) => { - if (!active) return; - setAccounts(result.data); - setDmAccountID((current) => current || result.data[0]?.id || ''); - }) - .catch((loadError) => { - if (active) setError(loadError); - }); - return () => { - active = false; - }; - }, []); - - useEffect(() => { - if (tab !== 'dms' || dmAccountID) load(); - }, [tab, dmAccountID, listPage]); - - function switchTab(nextTab: string) { - if (nextTab === tab) return; - loadSequence.current += 1; - setData([]); - setDataTab(''); - setListPage(1); - setListPageInfo({ total: 0, hasNext: false }); - setError(null); - setTab(nextTab as TabKey); - } - - // 私信消息记录:按会话与页码加载。 - useEffect(() => { - if (!conversationID || tab !== 'dms') { - setMessages([]); - setMessagePending(false); - setMessageError(null); - return undefined; - } - const sequence = ++messageSequence.current; - setMessages([]); - setMessagePending(true); - setMessageError(null); - creatorGet(`/creator/conversations/${encodeURIComponent(conversationID)}/messages?page=${messagePage}&page_size=25`) - .then((result) => { - if (sequence !== messageSequence.current) return; - const items = Array.isArray(result) ? result : result.data || []; - setMessages(items); - setMessagePageInfo({ - total: Array.isArray(result) ? items.length : (result.total ?? items.length), - hasNext: Array.isArray(result) ? false : Boolean(result.has_next) }); - }) - .catch((loadError) => { - if (sequence === messageSequence.current) setMessageError(loadError); - }) - .finally(() => { - if (sequence === messageSequence.current) setMessagePending(false); - }); - return () => { - if (sequence === messageSequence.current) setMessagePending(false); - }; - }, [conversationID, tab, messageRetry, messagePage]); - - async function syncMessages() { - if (!conversationID || messageSyncing) return; - setMessageSyncing(true); - setMessageError(null); - try { - const result = await creatorSyncConversation(conversationID); - messageApi[result?.history_has_more ? 'warning' : 'success']( - result?.history_has_more - ? `已同步 ${result?.messages ?? 0} 条平台私信历史,仍有更多内容待同步。` - : `已同步 ${result?.messages ?? 0} 条平台私信历史。`, - ); - setMessageRetry((value) => value + 1); - } catch (syncError) { - setMessageError(syncError); - } finally { - setMessageSyncing(false); - } - } - - // 回复草稿:按评论持久化到 localStorage。 - useEffect(() => { - if (reply.target_comment_id) { - writeDraft(replyDraftKey(reply.target_comment_id), reply); - } - }, [reply]); - - // 实时更新:SSE 订阅 /creator/updates,断开后 5 秒重连(规则页签为静态配置,不订阅)。 - useEffect(() => { - const realtimeTabs = new Set(['comments', 'leads', 'events', 'dms', 'operations']); - if (!realtimeTabs.has(tab)) { - setUpdateStatus('disconnected'); - return undefined; - } - const controller = new AbortController(); - let stopped = false; - let retryTimer = 0; - const connect = () => { - if (stopped) return; - setUpdateStatus('connecting'); - creatorSubscribe( - '/creator/updates', - () => { - load(); - if (tab === 'dms') { - setMessageRetry((value) => value + 1); - } - }, - controller.signal, - (status) => setUpdateStatus(status), - ) - .then(() => { - if (!stopped) retryTimer = window.setTimeout(connect, 5000); - }) - .catch((streamError) => { - if (stopped || streamError.name === 'AbortError') return; - setUpdateStatus('disconnected'); - setMessageError(streamError); - retryTimer = window.setTimeout(connect, 5000); - }); - }; - connect(); - return () => { - stopped = true; - controller.abort(); - window.clearTimeout(retryTimer); - setUpdateStatus('disconnected'); - }; - }, [tab]); - - // 私信草稿:按发送账号+会话持久化。 - useEffect(() => { - if (!dmAccountID || !conversationID) return; - const draft = readDraft(dmDraftKey(dmAccountID, conversationID)); - setDmText(draft?.text || ''); - setDmOperationKey(draft?.operation_key || ''); - }, [dmAccountID, conversationID]); - useEffect(() => { - if (dmAccountID && conversationID) { - writeDraft(dmDraftKey(dmAccountID, conversationID), { operation_key: dmOperationKey, text: dmText }); - } - }, [dmAccountID, conversationID, dmOperationKey, dmText]); - - async function analyzeSelected(ruleID: string) { - if (!ruleID || !selectedCommentIDs.length) return; - setAnalyzing(`batch:${ruleID}`); - try { - const response = await creatorCreate('/creator/comments/analyze', { comment_ids: selectedCommentIDs, rule_id: ruleID }); - const failed = (response.items || []).filter((item: any) => item.error).length; - messageApi[failed ? 'warning' : 'success'](`已分析 ${selectedCommentIDs.length - failed} 条评论${failed ? `,${failed} 条失败` : ''}。`); - setSelectedCommentIDs([]); - } catch (actionError) { - messageApi.info(conflictMessage(actionError, '批量 AI 分析不可用,未生成虚假结果')); - } finally { - setAnalyzing(''); - } - } - - async function analyze(commentID: string, ruleID: string) { - setAnalyzing(`${commentID}:${ruleID}`); - try { - const result = await creatorCreate(`/creator/comments/${encodeURIComponent(commentID)}/analyze`, { rule_id: ruleID }); - messageApi.info(`分析结果:${result.status}。${result.reason || ''}`); - } catch (actionError) { - messageApi.info(conflictMessage(actionError, 'AI 分析不可用,未生成虚假结果')); - } finally { - setAnalyzing(''); - } - } - - async function createRule() { - setBusy(true); - try { - const variables = { - name: ruleForm.name, - enabled: true, - source_type: ruleForm.source_type, - topic: ruleForm.topic, - include_keywords: parseKeywords(ruleForm.include_keywords), - exclude_keywords: parseKeywords(ruleForm.exclude_keywords), - ai_requirement: ruleForm.ai_requirement }; - const result = editingRuleID - ? await creatorUpdate(`/creator/rules/${encodeURIComponent(editingRuleID)}`, variables) - : (await create('creator-rules', variables)).data; - const update = (items: any[]) => (editingRuleID ? items.map((item) => (item.id === editingRuleID ? result : item)) : [result, ...items]); - setData(update); - setDataTab('rules'); - setRules(update); - setEditingRuleID(''); - setRuleForm({ name: '', source_type: 'all', topic: '', include_keywords: '', exclude_keywords: '', ai_requirement: '' }); - messageApi.info('线索规则已保存。'); - } catch (createError) { - messageApi.error(conflictMessage(createError, '规则保存失败')); - } finally { - setBusy(false); - } - } - - async function toggleRule(rule: any) { - setBusy(true); - try { - const updated = await creatorAction(`/creator/rules/${encodeURIComponent(rule.id)}/${rule.enabled ? 'disable' : 'enable'}`); - setData((items) => items.map((item) => (item.id === rule.id ? updated : item))); - setRules((items) => items.map((item) => (item.id === rule.id ? updated : item))); - } catch (ruleError) { - messageApi.error(conflictMessage(ruleError, '规则状态更新失败')); - } finally { - setBusy(false); - } - } - - function requestReply(comment: any, mode: 'comment' | 'dm' = 'comment') { - setReplyMode(mode); - const draft = readDraft(replyDraftKey(comment.id)); - setReply({ - account_id: draft?.account_id || '', - text: draft?.text || '', - target_uid: comment.author_uid || '', - target_comment_id: comment.id, - operation_key: draft?.operation_key || '' }); - messageApi.info(mode === 'dm' ? '请填写发送账号和私信文案,再进行逐次确认。' : '请填写发送账号和文案,再进行逐次确认。'); - } - - function askReplyConfirm(comment: any) { - if (!comment || !reply.account_id || !reply.target_uid || !reply.text.trim()) { - messageApi.info('人工回复需要发送账号、可用的目标 UID 和文案。'); - return; - } - try { - setReply((value) => ({ ...value, operation_key: value.operation_key || newOperationKey() })); - setConfirm(comment); - } catch (error) { - messageApi.info((error as Error).message); - } - } - - async function sendReply() { - if (!confirm) return; - setBusy(true); - try { - const created = await creatorCreate('/creator/operations', { - idempotency_key: reply.operation_key, - source: 'manual', - action: replyMode === 'dm' ? 'dm' : 'reply_comment', - platform: confirm.platform, - account_id: reply.account_id, - target_uid: reply.target_uid, - ...(replyMode === 'comment' ? { target_comment_id: reply.target_comment_id } : {}), - text: reply.text }); - const result = await creatorAction(`/creator/operations/${encodeURIComponent(created.id)}/execute`); - messageApi[result.state === 'succeeded' ? 'success' : 'warning'](operationStatus(result.state, result.reason)); - if (result.state === 'succeeded') { - removeDraft(replyDraftKey(reply.target_comment_id)); - setReply((value) => ({ ...value, operation_key: '' })); - } - } catch (sendError) { - messageApi.error(conflictMessage(sendError, '人工回复未发送')); - } finally { - setBusy(false); - setConfirm(null); - } - } - - const canSwitchDM = () => !dmText.trim(); - - function switchDMAccount(accountID: string) { - if (accountID === dmAccountID) return; - if (!canSwitchDM()) { - setPendingDMSwitch({ kind: 'account', id: accountID }); - return; - } - doSwitchDMAccount(accountID); - } - - function doSwitchDMAccount(accountID: string) { - setPendingDMSwitch(null); - setDmAccountID(accountID); - setConversationAccountID(''); - setConversations([]); - setConversationID(''); - setMessagePage(1); - setMessagePageInfo({ total: 0, hasNext: false }); - setDmText(''); - setDmOperationKey(''); - } - - function switchConversation(nextID: string) { - if (nextID === conversationID) return; - if (!canSwitchDM()) { - setPendingDMSwitch({ kind: 'conversation', id: nextID }); - return; - } - doSwitchConversation(nextID); - } - - function doSwitchConversation(nextID: string) { - setPendingDMSwitch(null); - setConversationID(nextID); - setMessagePage(1); - setMessagePageInfo({ total: 0, hasNext: false }); - setDmText(''); - setDmOperationKey(''); - } - - function askDMConfirm(conversation: any) { - if (!conversation || !dmAccountID || !dmText.trim()) { - messageApi.info('人工私信需要发送账号、会话和文案。'); - return; - } - let operationKey: string; - try { - operationKey = dmOperationKey || newOperationKey(); - } catch (error) { - messageApi.info((error as Error).message); - return; - } - setDmOperationKey(operationKey); - setDmConfirm({ ...conversation, operation_key: operationKey }); - } - - async function sendDM() { - if (!dmConfirm) return; - setBusy(true); - try { - const created = await creatorCreate('/creator/operations', { - idempotency_key: dmConfirm.operation_key || dmOperationKey, - source: 'manual', - action: 'dm', - platform: dmConfirm.platform, - account_id: dmAccountID, - target_uid: dmConfirm.peer_uid, - text: dmText }); - const result = await creatorAction(`/creator/operations/${encodeURIComponent(created.id)}/execute`); - messageApi[result.state === 'succeeded' ? 'success' : 'warning'](operationStatus(result.state, result.reason)); - if (result.state === 'succeeded') { - removeDraft(dmDraftKey(dmAccountID, dmConfirm.id)); - setDmText(''); - setDmOperationKey(''); - } - } catch (sendError) { - messageApi.error(conflictMessage(sendError, '人工私信未发送')); - } finally { - setBusy(false); - setDmConfirm(null); - } - } - - const accountOptions = accounts.map((account: any) => ({ value: account.id, label: account.name || account.id })); - const ruleOptions = rules.map((rule: any) => ({ value: rule.id, label: rule.name })); - - // 回复表单卡片:评论页签与线索页签共用。 - const replyFormCard = - reply.target_comment_id && replyTarget ? ( - - - -
- 发送账号 - - setReply((value) => ({ ...value, text: event.target.value, operation_key: '' }))} - /> -
- -
- - {tab === 'comments' ? '回复' : ''}目标 UID:{reply.target_uid || '不可用,无法发送'} - -
-
- ) : null; - - const commentsTab = ( - - {replyFormCard} -
- - value && analyze(comment.id, value)} - disabled={!rules.length || Boolean(analyzing)} - options={ruleOptions} - /> - 仅分析,不会自动发送消息。 -
- - ))} - - - - )} -
- ); - - const leadsTab = ( - - {replyFormCard} - {!pending && !error && !visibleData.length ? ( - - ) : ( - - {visibleData.map((lead: any) => ( - -
- - {lead.comment.author_name || '未知用户'} · {lead.comment.author_uid || 'UID 不可用'} - - - {statusTag('success', '线索')} - - - -
- {lead.comment.content} - 规则:{lead.rule_ids.join('、')} -
- {lead.results?.length ? ( - lead.results.map((result: any) => ( - - {result.status} - {result.reason ? `:${result.reason}` : ''} - {result.matched_keywords?.length ? `(关键词:${result.matched_keywords.join('、')})` : ''} - - )) - ) : ( - 尚未分析 - )} -
-
- ))} -
- )} -
- ); - - const eventsTab = ( - - {!pending && !error && !visibleData.length ? ( - - ) : ( - <> - - {visibleData.map((event: any) => ( - -
-
- - {event.event_type} · {event.interactor_uid || 'UID 不可用'} - - - 作品:{event.work_id || '—'} · 事件键:{event.event_key} - - {event.message_text ? ( - {event.message_text} - ) : null} -
- {statusTag( - event.baseline || event.state === 'blocked' ? 'warning' : event.state === 'succeeded' ? 'success' : event.state === 'uncertain' ? 'warning' : 'default', - event.baseline ? `基线/不触发:${event.reason || '需重新建立边界'}` : event.state || '未知', - )} -
- - 平台时间:{event.platform_event_at ? dateTime(event.platform_event_at) : '不可用'} · 网关接收: - {event.gateway_received_at ? dateTime(event.gateway_received_at) : '不可用'} · 控制面接收:{dateTime(event.received_at)} - -
- ))} -
- - - )} -
- ); - - const rulesTab = ( - - - - -
- 规则名称 - - setRuleForm({ ...ruleForm, name: event.target.value })} /> -
-
- 来源范围 - - setRuleForm({ ...ruleForm, topic: event.target.value })} /> -
-
- 包含关键词 - - setRuleForm({ ...ruleForm, include_keywords: event.target.value })} /> - 逗号分隔;只去除边缘空白,保留大小写和内部空格。 -
-
- 排除关键词 - setRuleForm({ ...ruleForm, exclude_keywords: event.target.value })} /> -
-
- AI 判断要求 - - setRuleForm({ ...ruleForm, ai_requirement: event.target.value })} /> -
- - - {editingRuleID ? ( - - ) : null} - -
-
- - - - {visibleData.map((rule: any) => ( -
-
- {rule.name} - {statusTag(rule.enabled ? 'success' : 'default', rule.enabled ? '启用' : '停用')} -
- - 来源:{rule.source_type || 'all'} · 主题:{rule.topic} - - 包含:{(rule.include_keywords || []).join('、')} - - - - -
- ))} -
- -
- ); - - const dmsTab = ( -
- -
- 发送账号 - - -
- -
- - ) : null} - {task.allowed_action === 'resume' ? ( - - ) : null} - {task.allowed_action === 'finish' ? ( - - ) : null} - {task.allowed_action === 'reconfirm' ? ( - <> - - - - ) : null} - {task.allowed_action ? null : 当前没有可安全执行的动作。} - - ) }, - ]; - - return ( - -
-
- 任务详情 - - {task.id} - -
- -
- {unknownHold ? : null} - - 执行尝试 - {task.attempts?.length ? ( - - {task.attempts.map((attempt: any) => ( - - history.push(`/attempts/${encodeURIComponent(attempt.id)}`)}>{attempt.id} - - {attempt.outcome || '执行中'} · {dateTime(attempt.started_at)} - - 脱敏证据:{attempt.evidence?.mock_outcome || '无可公开证据'} - - ))} - - ) : ( - - 尚未产生执行尝试。 - - )} -
- ); -} diff --git a/web/src/pages/tasks/index.tsx b/web/src/pages/tasks/index.tsx deleted file mode 100644 index b3f9bef..0000000 --- a/web/src/pages/tasks/index.tsx +++ /dev/null @@ -1,185 +0,0 @@ -// 任务中心列表:语义对齐 web.archived TasksPage.jsx 的 TaskList。 -import { useCallback, useEffect, useState } from 'react'; -import { history } from '@umijs/max'; -import { Alert, Button, Card, Flex, Select, Space, Table, Tag, Typography } from 'antd'; -import type { ColumnsType } from 'antd/es/table'; -import { getList } from '@/services/api'; -import { dateTime } from '@/utils/helpers'; -import { fixedLeft, fixedRight, tablePagination, tableScroll, useOverflowGrid } from '@/utils/table'; - -const stateText: Record = { - queued: '已排队', - executing: '执行中', - succeeded: '已成功', - failed: '失败', - needs_confirmation: '需要人工确认', - policy_hold: '策略暂停', - cancelled: '已取消', -}; -const stateColor: Record = { - queued: 'default', - executing: 'processing', - succeeded: 'success', - failed: 'error', - needs_confirmation: 'warning', - policy_hold: 'warning', - cancelled: 'default', -}; - -const holdText: Record = { - account_paused: '账号已暂停', - account_revoked: '账号授权已撤销', - binding_missing: '运行环境绑定缺失', - environment_missing: '原运行环境不可用', - exit_missing: '固定出口缺失', - exit_unhealthy: '固定出口失败或发生漂移', - runtime_stop_pending: '停止结果未知,等待人工核验', - runtime_missing: '原运行实例不可用', - runtime_lease_expired: '运行实例租约已过期', - execution_lease_expired: '执行租约过期,结果未知', - task_result_uncertain: '执行结果不确定', - task_policy_hold: '执行器请求策略暂停', - account_version_changed: '账号版本已变化', - draft_version_changed: '草稿版本已变化', - confirmation_missing: '确认快照缺失', - confirmation_version_changed: '确认版本已变化', - binding_version_changed: '环境或出口版本已变化', - legacy_confirmation_required: '历史任务结果未知,等待人工核验', -}; -export const holdLabel = (reason?: string) => (reason ? holdText[reason] || `未知停止原因:${reason}` : '无'); - -export default function Page() { - const [accountFilter, setAccountFilter] = useState(undefined); - const [stateFilter, setStateFilter] = useState(undefined); - const [rows, setRows] = useState([]); - const [accountOptions, setAccountOptions] = useState<{ value: string; label: string }[]>([]); - const [accountsPending, setAccountsPending] = useState(false); - const [pending, setPending] = useState(true); - const [error, setError] = useState(null); - const { vertical, horizontal } = useOverflowGrid(rows.length); - - const load = useCallback(async () => { - setPending(true); - setError(null); - try { - const result = await getList({ - resource: 'tasks', - filters: { account_id: accountFilter, state: stateFilter || undefined }, - }); - setRows(result.data ?? []); - } catch (loadError) { - setError(loadError); - } finally { - setPending(false); - } - }, [accountFilter, stateFilter]); - - useEffect(() => { - load(); - }, [load]); - - // 账号筛选下拉:一次性拉取账号列表,无需手输 UUID。 - useEffect(() => { - setAccountsPending(true); - getList({ resource: 'accounts' }) - .then((result) => - setAccountOptions( - (result.data ?? []).map((account: any) => ({ - value: account.id, - label: account.name ? `${account.name}(${account.id})` : account.id, - })), - ), - ) - .catch(() => setAccountOptions([])) - .finally(() => setAccountsPending(false)); - }, []); - - const columns: ColumnsType = [ - { - title: '任务', - dataIndex: 'id', - ...fixedLeft({}), - render: (value: string) => ( - - history.push(`/tasks/${value}`)}>{value} - - - ), - }, - { - title: '账号', - dataIndex: 'account_id', - render: (value: string) => ( - - history.push(`/accounts/${value}`)}>{value} - - - ), - }, - { title: '确认', dataIndex: 'confirmation_id', render: (value?: string) => value || '未确认' }, - { - title: '状态', - dataIndex: 'state', - render: (value: string) => {stateText[value] || `未知状态:${value}`}, - }, - { title: '停止原因', dataIndex: 'hold_reason', render: (value?: string) => {holdLabel(value)} }, - { - title: '更新时间', - dataIndex: 'updated_at', - render: (_, task) => {dateTime(task.updated_at || task.created_at)}, - }, - { - title: '操作', - key: 'actions', - align: 'right', - ...fixedRight({}), - render: (_, task) => ( - - ), - }, - ]; - - return ( -
- - - 共 {rows.length} 个任务 - - ({ value, label }))} - /> - - - - - {error ? 重试} /> : null} - - - - ); -} diff --git a/web/src/services/api.ts b/web/src/services/api.ts index a2218fd..87fb5c9 100644 --- a/web/src/services/api.ts +++ b/web/src/services/api.ts @@ -6,10 +6,6 @@ export const resourcePaths = { browsers: '/browsers', gateways: '/gateways', accounts: '/phase-a/accounts', - drafts: '/phase-a/drafts', - confirmations: '/phase-a/confirmations', - tasks: '/phase-a/tasks', - attempts: '/phase-a/attempts', 'network-exits': '/network-exits', 'creator-accounts': '/creator/accounts', 'creator-competitors': '/creator/competitors', @@ -18,27 +14,17 @@ export const resourcePaths = { 'creator-comments': '/creator/comments', 'creator-leads': '/creator/leads', 'creator-rules': '/creator/rules', - 'creator-operations': '/creator/operations', - 'creator-conversations': '/creator/conversations', - 'creator-events': '/creator/events', - 'creator-listeners': '/creator/listeners', } as const; export type Resource = keyof typeof resourcePaths; // 与归档版 filterKeys 一致:资源 → 允许透传的查询字段。 const filterKeys: Partial> = { - drafts: ['account_id'], - confirmations: ['draft_id'], - tasks: ['account_id', 'draft_id', 'state'], 'creator-competitors': ['platform'], 'creator-competitor-share-jobs': ['platform', 'status'], 'creator-works': ['platform', 'source_id', 'source_type', 'published_at_status', 'published_after', 'published_before', 'min_likes', 'min_comments', 'min_shares', 'growth_hours', 'min_likes_growth'], 'creator-comments': ['platform', 'work_id'], 'creator-leads': ['platform'], - 'creator-operations': ['account_id'], - 'creator-conversations': ['account_id'], - 'creator-events': ['account_id'], }; export interface ListParams { @@ -70,7 +56,7 @@ export async function getList({ resource, page, pageSize, filters = {} }; } -const GETONE_ALLOWED: Resource[] = ['accounts', 'network-exits', 'browsers', 'drafts', 'confirmations', 'tasks', 'attempts', 'creator-accounts', 'creator-competitors', 'creator-competitor-share-jobs', 'creator-works', 'creator-comments', 'creator-rules', 'creator-operations']; +const GETONE_ALLOWED: Resource[] = ['accounts', 'network-exits', 'browsers', 'creator-accounts', 'creator-competitors', 'creator-competitor-share-jobs', 'creator-works', 'creator-comments', 'creator-rules']; export async function getOne(resource: Resource, id: string | number): Promise { if (!GETONE_ALLOWED.includes(resource)) throw new Error(`${resource} 不支持 getOne`); @@ -109,24 +95,6 @@ export async function accountAction(id: string, action: 'pause' | 'resume'): Pro await request(`/phase-a/accounts/${encodeURIComponent(id)}/${action}`, { method: 'POST' }); } -export function createDraft(accountID: string, content: string) { - return request('/phase-a/drafts', jsonOptions('POST', { account_id: accountID, content })); -} - -export function confirmDraft(draftID: string, accountVersion: number, draftVersion: number) { - return request('/phase-a/confirmations', jsonOptions('POST', { draft_id: draftID, account_version: accountVersion, draft_version: draftVersion })); -} - -export function enqueueConfirmation(confirmationID: string) { - return request('/phase-a/tasks', jsonOptions('POST', { confirmation_id: confirmationID })); -} - -export async function taskAction(id: string, action: 'verify' | 'resume' | 'finish' | 'cancel', data?: unknown): Promise { - if (!['verify', 'resume', 'finish', 'cancel'].includes(action)) throw new Error(`未知任务操作: ${action}`); - const options = data ? jsonOptions('POST', data) : { method: 'POST' }; - await request(`/phase-a/tasks/${encodeURIComponent(id)}/${action}`, options); -} - export async function networkExitAction(id: string, action: 'check' | 'disable' | 'enable'): Promise { if (!['check', 'disable', 'enable'].includes(action)) throw new Error(`未知网络出口操作: ${action}`); return request(`/network-exits/${encodeURIComponent(id)}/${action}`, { method: 'POST' }); diff --git a/web/src/utils/listQuery.ts b/web/src/utils/listQuery.ts index 68b1e6e..2e6aa80 100644 --- a/web/src/utils/listQuery.ts +++ b/web/src/utils/listQuery.ts @@ -1,17 +1,11 @@ // 列表页共享工具:资源 → 查询字段白名单,对齐 web.archived/src/shared/api/dataProvider.js 的 filterKeys。 // creator-* 资源走 page/page_size 分页;其余资源后端返回全量或自带 total。 export const filterKeys: Record = { - drafts: ['account_id'], - confirmations: ['draft_id'], - tasks: ['account_id', 'draft_id', 'state'], 'creator-competitors': ['platform'], 'creator-competitor-share-jobs': ['platform', 'status'], 'creator-works': ['platform', 'source_id', 'source_type', 'published_after', 'published_before', 'min_likes', 'min_comments', 'min_shares'], 'creator-comments': ['platform', 'work_id'], 'creator-leads': ['platform'], - 'creator-operations': ['account_id'], - 'creator-conversations': ['account_id'], - 'creator-events': ['account_id'], }; export function isCreatorResource(resource: string): boolean { diff --git a/web/src/utils/metadata.tsx b/web/src/utils/metadata.tsx index b5d5b08..e5ebf9d 100644 --- a/web/src/utils/metadata.tsx +++ b/web/src/utils/metadata.tsx @@ -24,13 +24,8 @@ const rules: Array<{ test: (path: string) => boolean; title: string; subtitle: s { test: (p) => /\/edit$/.test(p) && p.startsWith('/accounts/'), title: '编辑社媒账号', subtitle: '维护账号资料、登录核验与自动响应策略。' }, { test: (p) => p.startsWith('/accounts/'), title: '账号详情', subtitle: '查看账号状态、登录身份与运行环境绑定。' }, { test: (p) => p === '/creator/competitors', title: '竞品分析', subtitle: '查看竞品作品与指标,分享链接导入请前往账号导入。' }, - { test: (p) => p === '/creator/workbench', title: '运营工作台', subtitle: '评论、线索、私信与写操作均保留来源和明确结果。' }, { test: (p) => p === '/creator/settings', title: '采集设置', subtitle: '统一配置采集窗口、指标采集和已批准的服务。' }, { test: (p) => p.startsWith('/creator/competitors/'), title: '竞品账号详情', subtitle: '查看竞品画像与作品列表,点击作品可追溯指标统计。' }, - { test: (p) => p.startsWith('/drafts/'), title: '草稿核对', subtitle: '核对版本化草稿,确认后再将操作加入队列。' }, - { test: (p) => p.startsWith('/attempts/'), title: 'Attempt 详情', subtitle: '查看执行结果、运行环境与网络出口。' }, - { test: (p) => p === '/tasks', title: '任务中心', subtitle: '逐条处理暂停、版本变化与未知执行结果。' }, - { test: (p) => p.startsWith('/tasks/'), title: '任务详情', subtitle: '查看任务状态、执行结果与允许的下一步操作。' }, { test: (p) => p === '/browsers', title: '运行环境', subtitle: '启动、停止并回收隔离的指纹浏览器运行环境。' }, { test: (p) => p === '/browsers/new', title: '创建运行环境', subtitle: '为已授权且暂停的账号绑定网关、指纹与网络出口。' }, { test: (p) => p.startsWith('/browsers/'), title: '运行环境详情', subtitle: '查看环境状态、账号绑定和运行实例信息。' }, @@ -50,9 +45,7 @@ export const menu: MenuDataItem[] = [ { path: '/accounts', name: '我的账号', icon: }, { path: '/accounts/monitoring', name: '监控账号', icon: }, { path: '/creator/competitors', name: '竞品分析', icon: }, - { path: '/creator/workbench', name: '运营工作台', icon: }, { path: '/creator/settings', name: '采集设置', icon: }, - { path: '/tasks', name: '任务中心', icon: }, ], }, { diff --git a/web/tests/table-fixed-header.test.cjs b/web/tests/table-fixed-header.test.cjs index aaabf74..5139030 100644 --- a/web/tests/table-fixed-header.test.cjs +++ b/web/tests/table-fixed-header.test.cjs @@ -8,7 +8,6 @@ const files = [ 'src/components/accounts/MonitoringAccountList.tsx', 'src/components/accounts/AccountManagementList.tsx', 'src/pages/accounts/import.tsx', - 'src/pages/tasks/index.tsx', 'src/pages/browsers/index.tsx', 'src/pages/network-exits/index.tsx', 'src/pages/gateways/index.tsx',