refactor: use original dashboard UI primitives

This commit is contained in:
2026-09-20 17:14:13 +08:00
parent a25e84ac14
commit 59f170c191
32 changed files with 1981 additions and 2179 deletions
+50 -32
View File
@@ -1,9 +1,12 @@
import { useState } from 'react'
import { Link, useNavigate, useParams } from '../../shared/router.jsx'
import { useDataProvider, useList, useOne } from '../../shared/hooks/dataHooks.js'
import {
Alert, Button, Card, CardContent, DetailList, Field, PageHeader, PageState, Select, StatusPill,
} from '../../shared/ui/ui.jsx'
import { Alert } from '../../components/ui/alert'
import { Button } from '../../components/ui/button'
import { Card, CardContent } from '../../components/ui/card'
import { Input } from '../../components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../components/ui/select'
import { DetailList, Field, PageHeader, PageState, StatusPill } from '../../shared/ui/ui.jsx'
import { useTitle } from '../../shared/hooks/hooks.js'
const stateTone = { queued: 'neutral', executing: 'info', succeeded: 'success', failed: 'danger', needs_confirmation: 'warning', policy_hold: 'warning', cancelled: 'neutral' }
@@ -34,10 +37,10 @@ function TaskCard({ task }) {
<Link to={`/tasks/${task.id}`} className="anywhere font-semibold text-primary hover:underline">{task.id}</Link>
<StatusPill tone={stateTone[task.state] || 'neutral'} label={stateLabel(task.state)} />
</div>
<div className="text-sm"><span className="text-muted">账号:</span><Link to={`/accounts/${task.account_id}`} className="anywhere text-primary hover:underline">{task.account_id}</Link></div>
<div className="text-sm"><span className="text-muted">确认:</span><span className="anywhere">{task.confirmation_id || '未确认'}</span></div>
<div className="anywhere text-xs text-muted">{holdLabel(task.hold_reason)}</div>
<p className="text-xs text-muted">{new Date(task.updated_at || task.created_at).toLocaleString('zh-CN')}</p>
<div className="text-sm"><span className="text-muted-foreground">账号:</span><Link to={`/accounts/${task.account_id}`} className="anywhere text-primary hover:underline">{task.account_id}</Link></div>
<div className="text-sm"><span className="text-muted-foreground">确认:</span><span className="anywhere">{task.confirmation_id || '未确认'}</span></div>
<div className="anywhere text-xs text-muted-foreground">{holdLabel(task.hold_reason)}</div>
<p className="text-xs text-muted-foreground">{new Date(task.updated_at || task.created_at).toLocaleString('zh-CN')}</p>
</CardContent>
</Card>
)
@@ -63,11 +66,20 @@ export function TaskList() {
<PageHeader icon="ri-task-line" title="任务中心" description="逐条处理暂停、版本变化与未知执行结果;不会自动重试或更换出口" />
<div className="mb-5 flex flex-col gap-3 sm:flex-row">
<Field id="task-account-filter" label="按账号筛选" className="sm:w-72">
<input id="task-account-filter" value={accountFilter} onChange={event => setAccountFilter(event.target.value)} placeholder="账号 ID" className="h-9 w-full rounded-md border border-[#c4ccd9] bg-white px-3 text-sm focus:border-primary focus:outline-none" />
<Input id="task-account-filter" value={accountFilter} onChange={event => setAccountFilter(event.target.value)} placeholder="账号 ID" />
</Field>
<Field id="task-state-filter" label="按状态筛选" className="sm:w-56">
<Select id="task-state-filter" value={stateFilter} onChange={event => setStateFilter(event.target.value)}
options={Object.entries(stateText).map(([value, label]) => ({ value, label }))} placeholder="全部状态" emptyOption={{ value: '', label: '全部状态' }} />
<Select value={stateFilter || "__all__"} onValueChange={value => setStateFilter(value === "__all__" ? "" : value)}>
<SelectTrigger id="task-state-filter">
<SelectValue placeholder="全部状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__all__">全部状态</SelectItem>
{Object.entries(stateText).map(([value, label]) => (
<SelectItem key={value} value={value}>{label}</SelectItem>
))}
</SelectContent>
</Select>
</Field>
</div>
{error ? <Alert variant="destructive" className="mb-4">{error.message}</Alert> : null}
@@ -114,10 +126,13 @@ export function TaskDetail() {
return (
<div>
<PageHeader title="任务详情" description={<span className="anywhere">{task.id}</span>}>
<Button icon="ri-arrow-left-line" onClick={() => navigate('/tasks')}>返回任务中心</Button>
<Button onClick={() => navigate('/tasks')}>
<i className="ri-arrow-left-line" aria-hidden="true" />
返回任务中心
</Button>
</PageHeader>
{notice ? <Alert variant={notice.variant} className="mb-4">{notice.text}</Alert> : null}
{unknownHold ? <Alert variant="warning" className="mb-4">后端返回了未知停止原因“{task.hold_reason}”;为避免不安全操作,本页不提供恢复动作。</Alert> : null}
{notice ? <Alert variant={notice.variant === "destructive" ? "destructive" : "default"} className="mb-4">{notice.text}</Alert> : null}
{unknownHold ? <Alert variant="default" className="mb-4">后端返回了未知停止原因“{task.hold_reason}”;为避免不安全操作,本页不提供恢复动作。</Alert> : null}
<div className="grid gap-4 lg:grid-cols-2">
<Card>
<CardContent className="space-y-3">
@@ -128,7 +143,7 @@ export function TaskDetail() {
['人工核验', task.verification_result || '尚未记录'],
]} />
<div>
<Button size="sm" as={Link} to={`/audit?task_id=${encodeURIComponent(task.id)}`}>查看关联审计</Button>
<Button size="sm" asChild><Link to={`/audit?task_id=${encodeURIComponent(task.id)}`}>查看关联审计</Link></Button>
</div>
</CardContent>
</Card>
@@ -141,8 +156,8 @@ export function TaskDetail() {
['环境与出口', <span className="anywhere">{confirmation.browser_env_alias || '未记录'} · {confirmation.network_exit_id || '未记录'} · 绑定版本:{confirmation.binding_version || '未记录'}</span>],
]} />
<div className="flex flex-wrap gap-2">
<Button size="sm" as={Link} to={`/accounts/${task.account_id}`}>查看账号</Button>
<Button size="sm" as={Link} to={`/drafts/${task.draft_id}`}>查看草稿</Button>
<Button size="sm" asChild><Link to={`/accounts/${task.account_id}`}>查看账号</Link></Button>
<Button size="sm" asChild><Link to={`/drafts/${task.draft_id}`}>查看草稿</Link></Button>
</div>
</CardContent>
</Card>
@@ -155,37 +170,40 @@ export function TaskDetail() {
['运行实例 / 绑定版本', <span className="anywhere">{task.runtime_instance_id || '未记录'} · {task.binding_version || '未记录'}</span>],
]} />
<div className="flex flex-wrap gap-2">
{task.browser_env_alias ? <Button size="sm" as={Link} to={`/browsers/${encodeURIComponent(task.browser_env_alias)}`}>查看原环境</Button> : null}
{task.network_exit_id ? <Button size="sm" as={Link} to={`/network-exits/${encodeURIComponent(task.network_exit_id)}`}>查看原出口</Button> : null}
{task.browser_env_alias ? <Button size="sm" asChild><Link to={`/browsers/${encodeURIComponent(task.browser_env_alias)}`}>查看原环境</Link></Button> : null}
{task.network_exit_id ? <Button size="sm" asChild><Link to={`/network-exits/${encodeURIComponent(task.network_exit_id)}`}>查看原出口</Link></Button> : null}
</div>
</CardContent>
</Card>
<Card>
<CardContent className="space-y-3">
<h2 className="text-base font-semibold">唯一安全动作</h2>
{task.readiness_reason ? <Alert variant="warning">当前仍被阻断:{holdLabel(task.readiness_reason)}。修复原资源并刷新后,才会开放恢复。</Alert> : null}
{task.readiness_reason ? <Alert variant="default">当前仍被阻断:{holdLabel(task.readiness_reason)}。修复原资源并刷新后,才会开放恢复。</Alert> : null}
{task.allowed_action === 'verify' ? (
<>
<Alert variant="warning">先在原环境和原出口核验真实结果。选择“未执行”不会立即重试,仍需下一步显式恢复。</Alert>
<Alert variant="default">先在原环境和原出口核验真实结果。选择“未执行”不会立即重试,仍需下一步显式恢复。</Alert>
<Field id="task-verification" label="人工核验结论">
<Select id="task-verification" value={verification} onChange={event => setVerification(event.target.value)} options={[
{ value: 'not_executed', label: '确认未执行,可评估恢复' },
{ value: 'succeeded', label: '确认已成功' },
{ value: 'failed', label: '确认已失败' },
]} />
<Select value={verification} onValueChange={setVerification}>
<SelectTrigger id="task-verification"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="not_executed">确认未执行,可评估恢复</SelectItem>
<SelectItem value="succeeded">确认已成功</SelectItem>
<SelectItem value="failed">确认已失败</SelectItem>
</SelectContent>
</Select>
</Field>
<Button variant="primary" disabled={busy} onClick={() => runAction('verify', { result: verification })}>记录人工核验</Button>
<Button variant="default" disabled={busy} onClick={() => runAction('verify', { result: verification })}>记录人工核验</Button>
</>
) : null}
{task.allowed_action === 'resume' ? <Button variant="primary" disabled={busy} onClick={() => runAction('resume')}>按核验结论恢复排队</Button> : null}
{task.allowed_action === 'finish' ? <Button variant="primary" disabled={busy} onClick={() => runAction('finish')}>按核验结论结束任务</Button> : null}
{task.allowed_action === 'resume' ? <Button variant="default" disabled={busy} onClick={() => runAction('resume')}>按核验结论恢复排队</Button> : null}
{task.allowed_action === 'finish' ? <Button variant="default" disabled={busy} onClick={() => runAction('finish')}>按核验结论结束任务</Button> : null}
{task.allowed_action === 'reconfirm' ? (
<>
<Alert variant="warning">版本已变化,旧确认不可继续使用。</Alert>
<Button variant="primary" as={Link} to={`/drafts/${task.draft_id}`}>重新核对并确认草稿</Button>
<Alert variant="default">版本已变化,旧确认不可继续使用。</Alert>
<Button variant="default" asChild><Link to={`/drafts/${task.draft_id}`}>重新核对并确认草稿</Link></Button>
</>
) : null}
{task.allowed_action ? null : <p className="text-sm text-muted">当前没有可安全执行的动作。</p>}
{task.allowed_action ? null : <p className="text-sm text-muted-foreground">当前没有可安全执行的动作。</p>}
</CardContent>
</Card>
</div>
@@ -204,7 +222,7 @@ export function TaskDetail() {
))}
</div>
) : (
<Card><CardContent><p className="text-sm text-muted">尚未产生执行尝试。</p></CardContent></Card>
<Card><CardContent><p className="text-sm text-muted-foreground">尚未产生执行尝试。</p></CardContent></Card>
)}
</section>
</div>