feat: redesign ticket workbench ui

This commit is contained in:
hailin
2026-07-01 11:44:23 +08:00
parent df8684f37d
commit cba807befb
6 changed files with 262 additions and 110 deletions

View File

@@ -1,12 +1,14 @@
import * as React from 'react'
import { AppEmptyState } from '@/components/app/AppEmptyState'
import { AppDetailLayout } from '@/components/app/AppDetailLayout'
import { AppField } from '@/components/app/AppField'
import { AppPageHeader } from '@/components/app/AppPageHeader'
import { AppPriorityBadge } from '@/components/app/AppPriorityBadge'
import { AppSection } from '@/components/app/AppSection'
import { AppStatusBadge } from '@/components/app/AppStatusBadge'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { SolutionLinkPanel } from '@/features/solutions/SolutionLinkPanel'
import { getTicket, transitionTicket } from './api'
@@ -15,6 +17,14 @@ import { TicketStatusTimeline } from './TicketStatusTimeline'
import type { Ticket, TicketStatus } from './types'
const NEXT_STATUSES: TicketStatus[] = ['in_progress', 'pending_confirm', 'resolved', 'closed']
const STATUS_ACTION_LABELS: Record<TicketStatus, string> = {
pending_info: '退回补充',
pending: '转为待处理',
in_progress: '开始处理',
pending_confirm: '提交确认',
resolved: '标记解决',
closed: '关闭工单'
}
export function TicketDetailPage({ ticketId }: { ticketId: number }) {
const [ticket, setTicket] = React.useState<Ticket | null>(null)
@@ -28,7 +38,7 @@ export function TicketDetailPage({ ticketId }: { ticketId: number }) {
async function handleTransition(status: TicketStatus) {
if (!ticket) return
const updated = await transitionTicket(ticket.id, status, `Move to ${status}`)
const updated = await transitionTicket(ticket.id, status, `状态调整为:${STATUS_ACTION_LABELS[status]}`)
setTicket(updated)
}
@@ -54,41 +64,57 @@ export function TicketDetailPage({ ticketId }: { ticketId: number }) {
<AppPageHeader
title={ticket.title}
description={ticket.ticket_no}
meta={
<>
<AppStatusBadge status={ticket.status} />
<AppPriorityBadge priority={ticket.priority} />
</>
}
actions={
<div className="flex flex-wrap gap-2">
{NEXT_STATUSES.map((status) => (
<Button key={status} variant="secondary" onClick={() => void handleTransition(status)}>
{status}
{STATUS_ACTION_LABELS[status]}
</Button>
))}
</div>
}
/>
<Card>
<CardContent className="grid gap-4 md:grid-cols-3">
<AppField label="Status">
<AppStatusBadge status={ticket.status} />
</AppField>
<AppField label="Priority">
<AppPriorityBadge priority={ticket.priority} />
</AppField>
<AppField label="System">
<span className="text-sm">{ticket.related_system || '-'}</span>
</AppField>
<AppField label="Environment">
<span className="text-sm">{ticket.environment || '-'}</span>
</AppField>
<AppField label="Impact">
<span className="text-sm">{ticket.impact_scope || '-'}</span>
</AppField>
<AppField label="Description">
<span className="text-sm">{ticket.description}</span>
</AppField>
</CardContent>
</Card>
<TicketStatusTimeline ticket={ticket} />
<TicketComments ticketId={ticket.id} />
<SolutionLinkPanel ticketId={ticket.id} />
<AppDetailLayout
side={
<>
<AppSection title="关键字段">
<div className="grid gap-3">
<AppField label="状态">
<AppStatusBadge status={ticket.status} />
</AppField>
<AppField label="优先级">
<AppPriorityBadge priority={ticket.priority} />
</AppField>
<AppField label="关联系统">
<span className="text-sm">{ticket.related_system || '-'}</span>
</AppField>
<AppField label="运行环境">
<span className="text-sm">{ticket.environment || '-'}</span>
</AppField>
<AppField label="影响范围">
<span className="text-sm">{ticket.impact_scope || '-'}</span>
</AppField>
</div>
</AppSection>
<SolutionLinkPanel ticketId={ticket.id} />
<AppSection title="AI 支持分析" action={<Badge variant="ai">后续接入</Badge>}>
<p className="text-sm text-muted-foreground">后续阶段将在这里展示排查建议、相似工单和引用来源。</p>
</AppSection>
</>
}
>
<AppSection title="工单概览" description="当前工单的核心问题和处理上下文。">
<p className="whitespace-pre-wrap text-sm leading-6">{ticket.description}</p>
</AppSection>
<TicketStatusTimeline ticket={ticket} />
<TicketComments ticketId={ticket.id} />
</AppDetailLayout>
</div>
</main>
)