fix(web): 官方 antd lint 清零——Alert title、Flex vertical 替代弃用 Space/List、App.useApp 包裹

This commit is contained in:
2026-09-23 16:14:56 +08:00
parent d0a252f3dd
commit 837fe41865
19 changed files with 125 additions and 107 deletions
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { history } from '@umijs/max';
import {
Flex,
Alert,
Button,
Card,
@@ -172,12 +173,12 @@ export default function AccountManagementList({ mode }: { mode: 'owned' | 'monit
dataIndex: 'status',
render: (_, account) =>
account.source_type === 'owned' ? (
<Space direction="vertical" size={0}>
<Flex>
<Tag color={account.runtime_status === 'active' ? 'success' : 'default'}>
{account.runtime_status === 'active' ? '启用' : '暂停'}
</Tag>
<Typography.Text type="secondary">{account.authorization_status === 'authorized' ? '已授权' : '已撤销'}</Typography.Text>
</Space>
</Flex>
) : (
<Tag color={account.enabled ? 'success' : 'default'}>{account.enabled ? '监控中' : '已暂停'}</Tag>
),
@@ -244,7 +245,7 @@ export default function AccountManagementList({ mode }: { mode: 'owned' | 'monit
共 {rows.length} 个{isMonitoring ? '监控' : '自有'}账号
</Typography.Text>
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} message={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
) : null}
<Table
rowKey={(row) => `${row.source_type}:${row.id}`}
+4 -1
View File
@@ -2,7 +2,7 @@ import { useState } from 'react';
import type { ReactNode } from 'react';
import { history, Outlet, useLocation } from '@umijs/max';
import { PageContainer, ProLayout } from '@ant-design/pro-components';
import { Dropdown, Typography } from 'antd';
import { App, Dropdown, Typography } from 'antd';
import { LogoutOutlined } from '@ant-design/icons';
import { PageActionsContext } from '@/components/PageActions';
import { menu, menuSelectedKey, pageMetadata } from '@/utils/metadata';
@@ -59,6 +59,8 @@ export default function Layout() {
}}
>
{/* 页面标题在卡片外(内容区左上角),动作按钮与标题同一行(右侧);卡片内不再重复渲染标题。 */}
{/* App 包裹:App.useApp() 的 message 等实例需要此上下文(component=false 不增加 DOM,避免破坏 ProLayout 布局)。 */}
<App component={false}>
<PageContainer
ghost
title={
@@ -73,6 +75,7 @@ export default function Layout() {
>
<Outlet />
</PageContainer>
</App>
</ProLayout>
</PageActionsContext.Provider>
);
+9 -8
View File
@@ -3,6 +3,7 @@
import { useCallback, useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import {
Flex,
Alert,
Button,
Card,
@@ -262,13 +263,13 @@ export default function Page() {
if (error && !selected)
return (
<div>
<Alert type="error" showIcon message={error.message || '账号不存在'} />
<Alert type="error" showIcon title={error.message || '账号不存在'} />
<Button style={{ marginTop: 16 }} onClick={() => history.push('/accounts')}>
返回社媒账号
</Button>
</div>
);
if (!selected) return <Alert type="warning" showIcon message="账号不存在。" />;
if (!selected) return <Alert type="warning" showIcon title="账号不存在。" />;
const smallAccounts = profiles.filter(
(profile) => profile.id !== selected.id && profile.authorization_kind === 'owned' && profile.platform === selected.platform,
@@ -276,7 +277,7 @@ export default function Page() {
const profileTab = (
<div>
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 12 }}>
<Descriptions column={1} size="small" style={{ flex: 1, minWidth: 260 }}>
<Descriptions.Item label="登录核验">
@@ -371,13 +372,13 @@ export default function Page() {
</Button>
</div>
</Form>
</Space>
</Flex>
</div>
);
const strategyTab = selected.big_account ? (
<div>
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Typography.Text type="secondary">先绑定同平台小号,再保存单动作策略;没有平台执行证据时只记录为不确定。</Typography.Text>
<Form form={strategyForm} layout="vertical" onFinish={createStrategy} style={{ background: '#fafafa', padding: 16, borderRadius: 8 }}>
<Space size={16} wrap style={{ display: 'flex' }}>
@@ -418,8 +419,8 @@ export default function Page() {
) : null}
</Space>
</Form>
{strategyError ? <Alert type="warning" showIcon message={`策略读取失败:${conflictMessage(strategyError, '请只读重试')}`} /> : null}
{relationError ? <Alert type="warning" showIcon message={`账号关系读取失败:${conflictMessage(relationError, '请只读重试')}`} /> : null}
{strategyError ? <Alert type="warning" showIcon title={`策略读取失败:${conflictMessage(strategyError, '请只读重试')}`} /> : null}
{relationError ? <Alert type="warning" showIcon title={`账号关系读取失败:${conflictMessage(relationError, '请只读重试')}`} /> : null}
{relations.map((relation) => (
<Card key={`${relation.big_account_id}:${relation.small_account_id}`} size="small">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
@@ -470,7 +471,7 @@ export default function Page() {
</Card>
))}
{!strategies.length && !strategyError ? <Typography.Text type="secondary">尚未配置策略。</Typography.Text> : null}
</Space>
</Flex>
</div>
) : (
<Typography.Text type="secondary">开启大号模式后可配置有序自动响应策略。</Typography.Text>
+8 -7
View File
@@ -3,6 +3,7 @@
import { useCallback, useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import {
Flex,
Alert,
Button,
Card,
@@ -103,7 +104,7 @@ export default function Page() {
}
if (pending) return <Card loading />;
if (error || !account) return <Alert type="error" showIcon message={error?.message || '账号不存在'} />;
if (error || !account) return <Alert type="error" showIcon title={error?.message || '账号不存在'} />;
const statusTag = (
<Tag
@@ -119,7 +120,7 @@ export default function Page() {
label: '账号状态',
children: (
<div>
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<div>{statusTag}</div>
<Descriptions column={1} bordered size="small">
<Descriptions.Item label="授权">{account.authorization_status === 'authorized' ? '已授权' : '已撤销'}</Descriptions.Item>
@@ -134,8 +135,8 @@ export default function Page() {
恢复账号
</Button>
</Space>
<Alert type="info" showIcon message="暂停会把待领取任务置为 hold;恢复账号只恢复账号可用性,不会自动恢复既有 hold。" />
</Space>
<Alert type="info" showIcon title="暂停会把待领取任务置为 hold;恢复账号只恢复账号可用性,不会自动恢复既有 hold。" />
</Flex>
</div>
),
},
@@ -146,7 +147,7 @@ export default function Page() {
<Alert
type="error"
showIcon
message="环境不可用:运行环境与网络出口状态未知,依赖资源状态的操作暂不可用。"
title="环境不可用:运行环境与网络出口状态未知,依赖资源状态的操作暂不可用。"
action={
<Button size="small" onClick={loadAll}>
重试环境状态
@@ -181,7 +182,7 @@ export default function Page() {
label: '文本草稿',
children: (
<div>
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Typography.Text type="secondary">仅支持单用户 Phase A Mock 文本;内部 ID 与版本由系统生成。</Typography.Text>
<Form form={draftForm} layout="vertical">
<Form.Item
@@ -216,7 +217,7 @@ export default function Page() {
</div>
</Card>
))}
</Space>
</Flex>
</div>
),
},
+1 -1
View File
@@ -177,7 +177,7 @@ export default function Page() {
</Button>
</div>
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} message={error.message} action={<Button size="small" onClick={loadShareJobs}>重试</Button>} />
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={loadShareJobs}>重试</Button>} />
) : null}
<Table rowKey="id" columns={columns} dataSource={jobs} loading={pending} pagination={false} locale={{ emptyText: '暂无分享链接解析任务。' }} />
<Modal open={createOpen} title="创建账号导入任务" onCancel={closeCreate} onOk={createJob} confirmLoading={busy} okText={busy ? '加入中…' : '加入解析队列'} width={560}>
+1 -1
View File
@@ -45,7 +45,7 @@ export default function Page() {
return (
<Card variant="outlined">
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} message={conflictMessage(error, '该平台的账号 ID 已存在;表单内容已保留。')} />
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={conflictMessage(error, '该平台的账号 ID 已存在;表单内容已保留。')} />
) : null}
<Form form={form} layout="vertical" onFinish={onFinish} style={{ maxWidth: 720 }}>
<Form.Item name="name" label="账号名称" required rules={[{ required: true, message: '请输入账号名称' }]} extra="展示名称,支持中文">
+4 -4
View File
@@ -1,7 +1,7 @@
// Attempt 详情:语义对齐 web.archived TracePages.jsx 的 AttemptDetail。
import { useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import { Alert, Button, Card, Descriptions, Space, Typography } from 'antd';
import { Flex, Alert, Button, Card, Descriptions, Space, Typography } from 'antd';
import { getOne } from '@/services/api';
import { dateTime } from '@/utils/helpers';
@@ -31,7 +31,7 @@ export default function Page() {
}, [id]);
if (pending) return <Card loading />;
if (error) return <Alert type="error" showIcon message={error.message} />;
if (error) return <Alert type="error" showIcon title={error.message} />;
if (!attempt) return null;
return (
@@ -42,7 +42,7 @@ export default function Page() {
</Typography.Text>
</div>
<Card>
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Descriptions column={1} bordered size="small">
<Descriptions.Item label="结果">{attempt.outcome || '执行中'}</Descriptions.Item>
<Descriptions.Item label="开始">{dateTime(attempt.started_at)}</Descriptions.Item>
@@ -61,7 +61,7 @@ export default function Page() {
<Button onClick={() => history.push(`/network-exits/${encodeURIComponent(attempt.network_exit_id)}`)}>查看出口</Button>
) : null}
</Space>
</Space>
</Flex>
</Card>
</div>
);
+6 -4
View File
@@ -1,7 +1,7 @@
// 运行环境详情:语义对齐 web.archived BrowsersPage.jsx 的 BrowserDetail。
import { useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import { Alert, Button, Card, Descriptions, Space, Tag, Typography, message } from 'antd';
import { Flex, Alert, App, Button, Card, Descriptions, Space, Tag, Typography } from 'antd';
import { CopyOutlined } from '@ant-design/icons';
import { getOne } from '@/services/api';
@@ -19,6 +19,8 @@ const stateText: Record<string, string> = {
};
export default function Page() {
// 官方规范:静态 message 吃不到 ConfigProvider 上下文,用 App.useApp() 取实例。
const { message } = App.useApp();
const { id = '' } = useParams<{ id: string }>();
const [runtime, setRuntime] = useState<any>(null);
const [pending, setPending] = useState(true);
@@ -44,7 +46,7 @@ export default function Page() {
}, [id]);
if (pending) return <Card loading />;
if (error || !runtime) return <Alert type="error" showIcon message={error?.message || '运行环境不存在'} />;
if (error || !runtime) return <Alert type="error" showIcon title={error?.message || '运行环境不存在'} />;
return (
<div>
@@ -55,7 +57,7 @@ export default function Page() {
<Button onClick={() => history.push('/browsers')}>返回运行环境</Button>
</div>
<Card>
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Tag color={runtime.state === 'running' ? 'success' : runtime.state === 'stopped' ? 'warning' : 'default'}>
{stateText[runtime.state] ?? `未知状态:${runtime.state}`}
</Tag>
@@ -104,7 +106,7 @@ export default function Page() {
<Button onClick={() => history.push(`/network-exits/${encodeURIComponent(runtime.network_exit_id)}`)}>查看出口</Button>
) : null}
</Space>
</Space>
</Flex>
</Card>
</div>
);
+1 -1
View File
@@ -170,7 +170,7 @@ export default function Page() {
<Card variant="outlined">
{contextHolder}
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} message={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
) : null}
<Table
rowKey="alias"
+11 -11
View File
@@ -1,7 +1,7 @@
// 竞品分析:语义对齐 web.archived/src/features/creator/competitors/CreatorCompetitorsPage.jsx(提交 d97cade)。
// 仅用 antd 默认组件;筛选自动刷新、手动上一页/下一页(后端 has_next)、素材三步处理与仿写流程原样保留。
import { useEffect, useState } from 'react';
import { Alert, Button, Card, Input, InputNumber, Modal, Select, Space, Table, Tag, Typography, message } from 'antd';
import { Flex, Alert, Button, Card, Input, InputNumber, Modal, Select, Space, Table, Tag, Typography, message } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { creatorAction, creatorGet, creatorUpdate, getList } from '@/services/api';
import { conflictMessage, dateTime } from '@/utils/helpers';
@@ -264,7 +264,7 @@ export default function Page() {
return (
<div>
{contextHolder}
<Space direction="vertical" size={16} style={{ width: '100%' }}>
<Flex>
<Card title="筛选作品" extra={<Typography.Text type="secondary" style={{ fontSize: 12 }}>调整条件后自动刷新</Typography.Text>}>
<Space size={12} wrap style={{ display: 'flex' }}>
<Select
@@ -292,7 +292,7 @@ export default function Page() {
</Card>
<Card title="作品与指标">
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} message={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
) : null}
<Table rowKey="id" columns={columns} dataSource={works} loading={pending} pagination={false} locale={{ emptyText: '暂无符合条件的作品。' }} />
{workPageInfo.total > works.length ? (
@@ -314,7 +314,7 @@ export default function Page() {
title={<span>作品详情 <Typography.Text type="secondary" style={{ fontSize: 12, fontWeight: 'normal' }}>{workDetail.work_key || workDetail.id}</Typography.Text></span>}
extra={<Button size="small" onClick={() => setWorkDetail(null)}>关闭</Button>}
>
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
{workDetail.cover_url ? <img src={workDetail.cover_url} alt="作品封面" style={{ maxHeight: 256, borderRadius: 4, border: '1px solid #f0f0f0' }} /> : null}
<Typography.Paragraph style={{ whiteSpace: 'pre-wrap', marginBottom: 0 }}>{workDetail.body || workDetail.title || '暂无正文'}</Typography.Paragraph>
<Space wrap>
@@ -339,7 +339,7 @@ export default function Page() {
]}
locale={{ emptyText: '暂无指标记录。' }}
/>
</Space>
</Flex>
</Card>
) : null}
{material ? (
@@ -348,13 +348,13 @@ export default function Page() {
<Button type="primary" onClick={processMaterial} loading={materialPending || material.download_status === 'running'} style={{ marginBottom: 16 }}>
开始处理素材
</Button>
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Space wrap>
{statusTag(material.download_status === 'succeeded' ? 'success' : material.download_status === 'failed' ? 'error' : 'default', `下载:${material.download_status}`)}
{statusTag(material.audio_status === 'succeeded' ? 'success' : material.audio_status === 'failed' ? 'error' : 'default', `音轨:${material.audio_status}`)}
{statusTag(material.transcription_status === 'succeeded' ? 'success' : material.transcription_status === 'failed' ? 'error' : 'default', `转写:${material.transcription_status}`)}
</Space>
{material.failure_reason ? <Alert type="error" showIcon message={`失败步骤:${material.failed_step || '未记录'};${material.failure_reason}`} /> : null}
{material.failure_reason ? <Alert type="error" showIcon title={`失败步骤:${material.failed_step || '未记录'};${material.failure_reason}`} /> : null}
<div>
<Typography.Text strong>仿写要求</Typography.Text>
<Typography.Paragraph type="secondary" style={{ fontSize: 12, marginBottom: 4 }}>必须先确认要求,才允许保存可编辑结果。</Typography.Paragraph>
@@ -366,7 +366,7 @@ export default function Page() {
style={{ marginBottom: 12 }}
/>
{material.rewrite_confirmed_at ? (
<Space direction="vertical" style={{ width: '100%' }} size={12}>
<Flex>
<Space>
<Button onClick={generateRewrite} disabled={materialPending}>
生成仿写草稿
@@ -384,17 +384,17 @@ export default function Page() {
<Button type="primary" onClick={saveRewrite} disabled={materialPending}>
保存仿写结果
</Button>
</Space>
</Flex>
) : (
<Button type="primary" onClick={confirmRewrite} disabled={materialPending || !materialReadyForConfirm}>
确认仿写要求
</Button>
)}
</div>
</Space>
</Flex>
</Card>
) : null}
</Space>
</Flex>
<Modal
open={Boolean(pendingSwitchWork)}
title="放弃未保存的素材文稿"
+1 -1
View File
@@ -71,7 +71,7 @@ export default function Page() {
}
if (pending) return <Card loading />;
if (error) return <Alert type="error" showIcon message={error.message} action={<Button size="small" onClick={load}>重试</Button>} />;
if (error) return <Alert type="error" showIcon title={error.message} action={<Button size="small" onClick={load}>重试</Button>} />;
return (
<div>
+33 -24
View File
@@ -2,7 +2,7 @@
// 六个页签:评论/线索/规则/事件监听/私信/操作记录;实时更新走 /creator/updates SSE;仅用 antd 默认组件。
import { useEffect, useRef, useState } from 'react';
import type { ReactNode } from 'react';
import { Alert, Button, Card, Checkbox, Col, Empty, Input, List, Modal, Row, Select, Space, Spin, Table, Tabs, Tag, Typography, message } from 'antd';
import { Flex, Alert, Button, Card, Checkbox, Col, Empty, Input, Modal, Row, Select, Space, Spin, Table, Tabs, Tag, Typography, message } 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';
@@ -82,7 +82,7 @@ function TabShell({ pending, error, onRetry, children }: { pending: boolean; err
return (
<div>
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} message={error.message} action={<Button size="small" onClick={onRetry}>重试</Button>} />
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={onRetry}>重试</Button>} />
) : null}
<Spin spinning={pending}>{children}</Spin>
</div>
@@ -556,7 +556,7 @@ export default function Page() {
const replyFormCard =
reply.target_comment_id && replyTarget ? (
<Card size="small" style={{ marginBottom: 16 }}>
<Space direction="vertical" style={{ width: '100%' }} size={8}>
<Flex>
<Space size={12} wrap style={{ display: 'flex', alignItems: 'flex-end' }}>
<div>
<Typography.Text>发送账号</Typography.Text>
@@ -585,7 +585,7 @@ export default function Page() {
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
{tab === 'comments' ? '回复' : ''}目标 UID:{reply.target_uid || '不可用,无法发送'}
</Typography.Text>
</Space>
</Flex>
</Card>
) : null;
@@ -609,7 +609,7 @@ export default function Page() {
<Empty description="暂无一级评论。" />
) : (
<>
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Flex>
{visibleData.map((comment: any) => (
<Card key={comment.id} size="small">
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 12 }}>
@@ -655,7 +655,7 @@ export default function Page() {
</div>
</Card>
))}
</Space>
</Flex>
<PageNav page={listPage} total={listPageInfo.total} count={visibleData.length} hasNext={listPageInfo.hasNext} onPageChange={setListPage} />
</>
)}
@@ -668,7 +668,7 @@ export default function Page() {
{!pending && !error && !visibleData.length ? (
<Empty description="暂无命中线索。" />
) : (
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Flex>
{visibleData.map((lead: any) => (
<Card key={lead.comment.id} size="small">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
@@ -702,7 +702,7 @@ export default function Page() {
</div>
</Card>
))}
</Space>
</Flex>
)}
</TabShell>
);
@@ -713,7 +713,7 @@ export default function Page() {
<Empty description="暂无监听事件。" />
) : (
<>
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Flex>
{visibleData.map((event: any) => (
<Card key={event.id} size="small">
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: 12 }}>
@@ -739,7 +739,7 @@ export default function Page() {
</Typography.Paragraph>
</Card>
))}
</Space>
</Flex>
<PageNav page={listPage} total={listPageInfo.total} count={visibleData.length} hasNext={listPageInfo.hasNext} onPageChange={setListPage} />
</>
)}
@@ -750,7 +750,7 @@ export default function Page() {
<Row gutter={16}>
<Col xs={24} lg={8}>
<Card size="small" title={editingRuleID ? '编辑规则' : '新建规则'} style={{ marginBottom: 16 }}>
<Space direction="vertical" style={{ width: '100%' }} size={12}>
<Flex>
<div>
<Typography.Text>规则名称</Typography.Text>
<RequiredMark />
@@ -800,7 +800,7 @@ export default function Page() {
</Button>
) : null}
</Space>
</Space>
</Flex>
</Card>
</Col>
<Col xs={24} lg={16}>
@@ -866,12 +866,21 @@ export default function Page() {
<Row gutter={16}>
<Col xs={24} lg={7}>
<Card size="small" style={{ marginBottom: 16 }}>
<List
dataSource={visibleConversations}
locale={{ emptyText: '暂无私信会话。' }}
renderItem={(conversation: any) => (
<List.Item
style={{ cursor: 'pointer', padding: '8px 8px', background: conversation.id === conversationID ? '#e6f4ff' : undefined, borderRadius: 4 }}
{/* List 已在 antd 6 弃用:会话切换用按钮列表(Flex vertical + Button block)等价实现。 */}
<Flex vertical gap={4}>
{visibleConversations.map((conversation: any) => (
<Button
key={conversation.id}
block
style={{
height: 'auto',
padding: '8px 8px',
textAlign: 'left',
whiteSpace: 'normal',
background: conversation.id === conversationID ? '#e6f4ff' : undefined,
}}
type={conversation.id === conversationID ? 'primary' : 'default'}
ghost={conversation.id === conversationID}
onClick={() => switchConversation(conversation.id)}
>
<div style={{ minWidth: 0 }}>
@@ -882,9 +891,9 @@ export default function Page() {
{conversation.peer_uid}
</Typography.Paragraph>
</div>
</List.Item>
)}
/>
</Button>
))}
</Flex>
</Card>
</Col>
<Col xs={24} lg={17}>
@@ -910,14 +919,14 @@ export default function Page() {
</Space>
</div>
{messageError ? (
<Alert type="error" showIcon style={{ marginBottom: 12 }} message={messageError.message} action={<Button size="small" onClick={() => setMessageRetry((value) => value + 1)}>重试</Button>} />
<Alert type="error" showIcon style={{ marginBottom: 12 }} title={messageError.message} action={<Button size="small" onClick={() => setMessageRetry((value) => value + 1)}>重试</Button>} />
) : null}
<Spin spinning={messagePending}>
{!messagePending && !messageError && !messages.length ? (
<Empty description="暂无消息记录。" />
) : (
<>
<Space direction="vertical" size={12} style={{ width: '100%' }}>
<Flex>
{messages.map((msg: any) => (
<div
key={msg.id}
@@ -935,7 +944,7 @@ export default function Page() {
</Typography.Text>
</div>
))}
</Space>
</Flex>
<PageNav page={messagePage} total={messagePageInfo.total} count={messages.length} hasNext={messagePageInfo.hasNext} onPageChange={setMessagePage} />
</>
)}
+14 -13
View File
@@ -2,6 +2,7 @@
import { useCallback, useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import {
Flex,
Alert,
Button,
Card,
@@ -64,7 +65,7 @@ export default function Page() {
}, [load]);
if (pending) return <Card loading />;
if (error || !draft) return <Alert type="error" showIcon message={error?.message || '草稿不存在'} />;
if (error || !draft) return <Alert type="error" showIcon title={error?.message || '草稿不存在'} />;
const versions = draft.versions || [];
const confirmations = draft.confirmations || [];
@@ -119,14 +120,14 @@ export default function Page() {
<Button onClick={() => history.push(`/accounts/${draft.account_id}`)}>返回账号详情</Button>
</div>
{browsersError ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} message="环境不可用:当前运行环境与网络出口状态未知,不能确认或排队。" />
<Alert type="error" showIcon style={{ marginBottom: 16 }} title="环境不可用:当前运行环境与网络出口状态未知,不能确认或排队。" />
) : null}
{!isLatest ? (
<Alert
type="info"
showIcon
style={{ marginBottom: 16 }}
message={`当前只读快照已不是最新草稿版本,请刷新到版本 ${latestVersion} 后重新核对。`}
title={`当前只读快照已不是最新草稿版本,请刷新到版本 ${latestVersion} 后重新核对。`}
action={
<Button size="small" onClick={() => history.push(`/drafts/${versions[0]?.id}`)}>
打开最新版本
@@ -134,12 +135,12 @@ export default function Page() {
}
/>
) : null}
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Card title="只读内容快照">
<Typography.Paragraph style={{ whiteSpace: 'pre-wrap', marginBottom: 0 }}>{draft.content}</Typography.Paragraph>
</Card>
<Card title="版本与当前资源">
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Descriptions column={1} bordered size="small">
<Descriptions.Item label="账号版本">{draft.account.version}</Descriptions.Item>
<Descriptions.Item label="草稿版本">{draft.version}</Descriptions.Item>
@@ -147,10 +148,10 @@ export default function Page() {
<Descriptions.Item label="网络出口">{browsersError ? '状态未知' : binding?.network_exit_id || '当前机器直连'}</Descriptions.Item>
</Descriptions>
<Tag color={readiness.ready ? 'success' : 'warning'}>{readiness.label}</Tag>
</Space>
</Flex>
</Card>
<Card title="版本链">
<Space direction="vertical" size={4}>
<Flex>
{versions.map((item: any) =>
item.id === draft.id ? (
<Typography.Text key={item.id} strong>
@@ -162,10 +163,10 @@ export default function Page() {
</Button>
),
)}
</Space>
</Flex>
</Card>
<Card title="显式核对与入队">
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Checkbox checked={reviewed} onChange={(e) => setReviewed(e.target.checked)}>
我已核对当前账号、草稿内容、运行环境和网络出口
</Checkbox>
@@ -186,23 +187,23 @@ export default function Page() {
) : (
<Typography.Text type="secondary">保存有效确认后才可加入队列。</Typography.Text>
)}
</Space>
</Flex>
</Card>
<Card title="关联任务">
{tasks.length === 0 ? (
<Typography.Text type="secondary">尚未入队。</Typography.Text>
) : (
<Space direction="vertical" style={{ width: '100%' }} size={8}>
<Flex>
{tasks.map((task: any) => (
<Card key={task.id} size="small">
<Typography.Text strong>{task.id}</Typography.Text>
<Typography.Paragraph style={{ marginBottom: 0 }}>任务状态:{task.state}</Typography.Paragraph>
</Card>
))}
</Space>
</Flex>
)}
</Card>
</Space>
</Flex>
<Modal
open={confirmOpen}
title="确认草稿版本"
+3 -3
View File
@@ -33,7 +33,7 @@ function GatewayFormModal({
return (
<Modal open={open} title={editing ? '编辑网关' : '注册网关'} onCancel={onCancel} onOk={() => form.submit()} confirmLoading={busy} okText={busy ? (editing ? '保存中…' : '注册中…') : editing ? '保存' : '注册网关'}>
{error ? <Alert type="error" showIcon style={{ marginBottom: 16 }} message={error.message} /> : null}
{error ? <Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} /> : null}
<Form form={form} layout="vertical" onFinish={onSubmit}>
<Form.Item
name="name"
@@ -184,7 +184,7 @@ export default function Page() {
type="success"
showIcon
style={{ marginBottom: 16 }}
message={
title={
<Space size={4}>
<span>网关 {created.name} 已注册。令牌</span>
<Typography.Text code>{created.token}</Typography.Text>
@@ -203,7 +203,7 @@ export default function Page() {
/>
) : null}
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} message={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
) : null}
<Table rowKey="name" columns={columns} dataSource={gateways} loading={pending} pagination={false} locale={{ emptyText: '暂无网关;注册后环境即可调度到该网关。' }} />
<GatewayFormModal
+1 -1
View File
@@ -45,7 +45,7 @@ export default function Page() {
<Typography.Title level={4} style={{ marginBottom: 24, textAlign: 'center' }}>
CreatorHub
</Typography.Title>
{error ? <Alert type="error" showIcon style={{ marginBottom: 16 }} message={error} /> : null}
{error ? <Alert type="error" showIcon style={{ marginBottom: 16 }} title={error} /> : null}
<Form form={form} layout="vertical" onFinish={onFinish} requiredMark={false}>
<Form.Item name="username" label="用户名" rules={[{ required: true, message: '请输入用户名' }]}>
<Input autoComplete="username" />
+4 -4
View File
@@ -1,7 +1,7 @@
// 网络出口详情:语义对齐 web.archived NetworkExitsPage.jsx 的 NetworkExitDetail。
import { useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import { Alert, Button, Card, Descriptions, Space, Tag, Typography } from 'antd';
import { Flex, Alert, Button, Card, Descriptions, Space, Tag, Typography } from 'antd';
import { getOne } from '@/services/api';
import { dateTime } from '@/utils/helpers';
@@ -38,7 +38,7 @@ export default function Page() {
}, [id]);
if (pending) return <Card loading />;
if (error || !exit) return <Alert type="error" showIcon message={error?.message || '网络出口不存在'} />;
if (error || !exit) return <Alert type="error" showIcon title={error?.message || '网络出口不存在'} />;
const known = Object.hasOwn(healthText, exit.health_status ?? '');
return (
@@ -52,7 +52,7 @@ export default function Page() {
<Button onClick={() => history.push('/network-exits')}>返回出口列表</Button>
</div>
<Card>
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Tag color={exit.health_status === 'healthy' ? 'success' : exit.health_status === 'disabled' ? 'default' : exit.health_status === 'unchecked' ? 'warning' : 'error'}>
{known ? healthText[exit.health_status] : `未知状态:${exit.health_status}`}
</Tag>
@@ -68,7 +68,7 @@ export default function Page() {
<Descriptions.Item label="出口IP">{exit.observed_public_ip || '尚无观测'}</Descriptions.Item>
<Descriptions.Item label="最近检测">{exit.last_checked_at ? dateTime(exit.last_checked_at) : '未检测'}</Descriptions.Item>
</Descriptions>
</Space>
</Flex>
</Card>
</div>
);
+3 -3
View File
@@ -64,7 +64,7 @@ function ExitFormModal({
return (
<Modal open={open} title={title} onCancel={onCancel} onOk={() => form.submit()} confirmLoading={busy} okText={busy ? '保存中…' : submitLabel} width={640}>
{error ? <Alert type="error" showIcon style={{ marginBottom: 16 }} message={conflictMessage(error, '出口地址或认证信息与现有资源冲突;表单内容已保留。')} /> : null}
{error ? <Alert type="error" showIcon style={{ marginBottom: 16 }} title={conflictMessage(error, '出口地址或认证信息与现有资源冲突;表单内容已保留。')} /> : null}
<Form form={form} layout="vertical" onFinish={onSubmit}>
<Space size={16} wrap style={{ display: 'flex' }}>
<Form.Item name="protocol" label="协议" required rules={[{ required: true }]} extra="代理协议">
@@ -288,14 +288,14 @@ export default function Page() {
<Card variant="outlined">
{contextHolder}
{error ? (
<Alert type="error" showIcon style={{ marginBottom: 16 }} message={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
<Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} />
) : null}
{bindingsError ? (
<Alert
type="error"
showIcon
style={{ marginBottom: 16 }}
message={`环境不可用${bindingsError.status ? `(${bindingsError.status})` : ''}:无法读取账号绑定,状态暂时未知。`}
title={`环境不可用${bindingsError.status ? `(${bindingsError.status})` : ''}:无法读取账号绑定,状态暂时未知。`}
action={
<Button size="small" onClick={loadBindings}>
重试环境状态
+16 -16
View File
@@ -2,7 +2,7 @@
// 四卡改用 Tabs(当前状态 / 确认快照 / 执行关联 / 唯一安全动作)——antd 默认组件的信息组织方式。
import { useCallback, useEffect, useState } from 'react';
import { history, useParams } from '@umijs/max';
import { Alert, Button, Card, Descriptions, Select, Space, Tabs, Tag, Typography, message } from 'antd';
import { Flex, Alert, Button, Card, Descriptions, Select, Space, Tabs, Tag, Typography, message } from 'antd';
import { getOne, taskAction } from '@/services/api';
import { dateTime } from '@/utils/helpers';
@@ -100,7 +100,7 @@ export default function Page() {
}
if (pending) return <Card loading />;
if (error || !task) return <Alert type="error" showIcon message={error?.message || '任务不存在'} />;
if (error || !task) return <Alert type="error" showIcon title={error?.message || '任务不存在'} />;
const confirmation = task.confirmation || {};
const unknownHold = task.hold_reason && !holdText[task.hold_reason];
@@ -110,20 +110,20 @@ export default function Page() {
key: 'status',
label: '当前状态',
children: (
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Tag color={stateColor[task.state] || 'default'}>{stateText[task.state] || `未知状态:${task.state}`}</Tag>
<Descriptions column={1} bordered size="small">
<Descriptions.Item label="停止原因">{holdLabel(task.hold_reason)}</Descriptions.Item>
<Descriptions.Item label="人工核验">{task.verification_result || '尚未记录'}</Descriptions.Item>
</Descriptions>
</Space>
</Flex>
),
},
{
key: 'confirmation',
label: '确认快照',
children: (
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Descriptions column={1} bordered size="small">
<Descriptions.Item label="确认">
<Typography.Text copyable>{confirmation.id || '未记录'}</Typography.Text> · v{confirmation.version || '—'}
@@ -141,14 +141,14 @@ export default function Page() {
查看草稿
</Button>
</Space>
</Space>
</Flex>
),
},
{
key: 'execution',
label: '执行关联',
children: (
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<Flex>
<Descriptions column={1} bordered size="small">
<Descriptions.Item label="环境">{task.browser_env_alias || '未记录'}</Descriptions.Item>
<Descriptions.Item label="固定出口">{task.network_exit_id || '未记录'}</Descriptions.Item>
@@ -168,18 +168,18 @@ export default function Page() {
</Button>
) : null}
</Space>
</Space>
</Flex>
),
},
{
key: 'action',
label: '唯一安全动作',
children: (
<Space direction="vertical" style={{ width: '100%' }} size={16}>
{task.readiness_reason ? <Alert type="info" showIcon message={`当前仍被阻断:${holdLabel(task.readiness_reason)}。修复原资源并刷新后,才会开放恢复。`} /> : null}
<Flex>
{task.readiness_reason ? <Alert type="info" showIcon title={`当前仍被阻断:${holdLabel(task.readiness_reason)}。修复原资源并刷新后,才会开放恢复。`} /> : null}
{task.allowed_action === 'verify' ? (
<>
<Alert type="info" showIcon message='先在原环境和原出口核验真实结果。选择"未执行"不会立即重试,仍需下一步显式恢复。' />
<Alert type="info" showIcon title='先在原环境和原出口核验真实结果。选择"未执行"不会立即重试,仍需下一步显式恢复。' />
<Select
style={{ width: 280 }}
value={verification}
@@ -209,14 +209,14 @@ export default function Page() {
) : null}
{task.allowed_action === 'reconfirm' ? (
<>
<Alert type="info" showIcon message="版本已变化,旧确认不可继续使用。" />
<Alert type="info" showIcon title="版本已变化,旧确认不可继续使用。" />
<Button type="primary" onClick={() => history.push(`/drafts/${task.draft_id}`)}>
重新核对并确认草稿
</Button>
</>
) : null}
{task.allowed_action ? null : <Typography.Text type="secondary">当前没有可安全执行的动作。</Typography.Text>}
</Space>
</Flex>
),
},
];
@@ -233,11 +233,11 @@ export default function Page() {
</div>
<Button onClick={() => history.push('/tasks')}>返回任务中心</Button>
</div>
{unknownHold ? <Alert type="warning" showIcon style={{ marginBottom: 16 }} message={`后端返回了未知停止原因"${task.hold_reason}";为避免不安全操作,本页不提供恢复动作。`} /> : null}
{unknownHold ? <Alert type="warning" showIcon style={{ marginBottom: 16 }} title={`后端返回了未知停止原因"${task.hold_reason}";为避免不安全操作,本页不提供恢复动作。`} /> : null}
<Tabs defaultActiveKey="status" items={items} />
<Typography.Title level={5} style={{ marginTop: 24, marginBottom: 12 }}>执行尝试</Typography.Title>
{task.attempts?.length ? (
<Space direction="vertical" style={{ width: '100%' }} size={12}>
<Flex>
{task.attempts.map((attempt: any) => (
<Card key={attempt.id} size="small">
<Typography.Link onClick={() => history.push(`/attempts/${encodeURIComponent(attempt.id)}`)}>{attempt.id}</Typography.Link>
@@ -247,7 +247,7 @@ export default function Page() {
<Typography.Text type="secondary">脱敏证据:{attempt.evidence?.mock_outcome || '无可公开证据'}</Typography.Text>
</Card>
))}
</Space>
</Flex>
) : (
<Card size="small">
<Typography.Text type="secondary">尚未产生执行尝试。</Typography.Text>
+1 -1
View File
@@ -131,7 +131,7 @@ export default function Page() {
</Space>
</Card>
<Card variant="outlined">
{error ? <Alert type="error" showIcon style={{ marginBottom: 16 }} message={error.message} action={<Button size="small" onClick={load}>重试</Button>} /> : null}
{error ? <Alert type="error" showIcon style={{ marginBottom: 16 }} title={error.message} action={<Button size="small" onClick={load}>重试</Button>} /> : null}
<Table
rowKey="id"
columns={columns}