feat: rebuild support workspace frontend

This commit is contained in:
hailin
2026-07-16 17:20:45 +08:00
parent fcb8121f0a
commit e90648e129
95 changed files with 4353 additions and 2165 deletions

View File

@@ -11,6 +11,7 @@ import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Textarea } from '@/components/ui/textarea'
import { toast } from 'sonner'
import { createTicket } from './api'
import { TICKET_CATEGORIES } from './ticketCategories'
@@ -60,9 +61,11 @@ export function TicketCreatePage() {
}
return (
<AppPage maxWidth="6xl" contentClassName="grid gap-5">
<AppPageHeader title="创建工单" />
<form className="grid gap-4 xl:grid-cols-[minmax(0,1fr)_340px]" onSubmit={handleSubmit}>
<AppPage
contentClassName="grid gap-5"
header={<AppPageHeader title="创建工单" description="按提出方、问题描述和影响范围记录完整上下文" actions={<><Button type="button" variant="ghost" onClick={() => (window.location.hash = '/tickets')}>取消</Button><Button type="button" variant="outline" onClick={() => toast('暂存入口为视觉预留,当前不会保存数据')}>暂存</Button></>} />}
>
<form id="ticket-create-form" className="grid gap-4 xl:grid-cols-[minmax(0,1fr)_320px]" onSubmit={handleSubmit}>
<div className="grid gap-4">
{error ? (
<Alert variant="destructive">
@@ -72,72 +75,7 @@ export function TicketCreatePage() {
</Alert>
) : null}
<AppSection title="基础信息">
<div className="grid gap-4 md:grid-cols-2">
<AppField label="问题分类" required>
<Select
value={form.category_code}
onValueChange={(value) => updateField('category_code', value as TicketInput['category_code'])}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="请选择问题分类" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{TICKET_CATEGORIES.map((category) => (
<SelectItem key={category.code} value={category.code}>
{category.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</AppField>
<AppField label="工单标题" htmlFor="ticket-title" required>
<Input
id="ticket-title"
required
placeholder="例如:查询客户采集进度"
value={form.title}
onChange={(event) => updateField('title', event.target.value)}
/>
</AppField>
</div>
</AppSection>
<AppSection title="问题信息">
<div className="grid gap-4 md:grid-cols-2">
<AppField label="问题表现" htmlFor="ticket-problem-performance" required>
<Input
id="ticket-problem-performance"
required
placeholder="例如:采集进度长时间未更新"
value={form.problem_performance}
onChange={(event) => updateField('problem_performance', event.target.value)}
/>
</AppField>
<AppField label="问题对象" htmlFor="ticket-problem-object" required>
<Input
id="ticket-problem-object"
required
placeholder="例如:客户名称、账号、机构或任务编号"
value={form.problem_object}
onChange={(event) => updateField('problem_object', event.target.value)}
/>
</AppField>
</div>
<AppField label="问题描述" htmlFor="ticket-description" required>
<Textarea
id="ticket-description"
required
placeholder="请填写问题背景、期望结果、已知线索或需要支持团队确认的内容。"
value={form.description}
onChange={(event) => updateField('description', event.target.value)}
/>
</AppField>
</AppSection>
<AppSection title="提出信息">
<AppSection variant="card" title="提出方与对象" description="先确认由谁提出,以及具体影响到哪个业务对象。">
<div className="grid gap-4 md:grid-cols-2">
<AppField label="提出人" htmlFor="ticket-submitter-name" required>
<Input
@@ -157,26 +95,33 @@ export function TicketCreatePage() {
onChange={(event) => updateField('submitter_org', event.target.value)}
/>
</AppField>
<AppField label="问题对象" htmlFor="ticket-problem-object" required>
<Input
id="ticket-problem-object"
required
placeholder="客户、账号、机构或任务编号"
value={form.problem_object}
onChange={(event) => updateField('problem_object', event.target.value)}
/>
</AppField>
<AppField label="环境" required>
<Select value={form.environment} onValueChange={(value) => updateField('environment', value)}><SelectTrigger className="w-full"><SelectValue placeholder="请选择问题环境" /></SelectTrigger><SelectContent><SelectGroup>{ENVIRONMENT_OPTIONS.map((environment) => <SelectItem key={environment} value={environment}>{environment}</SelectItem>)}</SelectGroup></SelectContent></Select>
</AppField>
</div>
</AppSection>
<AppSection title="影响与环境">
<AppSection variant="card" title="问题描述" description="描述用户看到的现象、发生时间与期望结果。">
<div className="grid gap-4 md:grid-cols-2">
<AppField label="优先级" required>
<Select value={form.priority} onValueChange={(value) => updateField('priority', value as TicketInput['priority'])}>
<SelectTrigger className="w-full">
<SelectValue placeholder="请选择优先级" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="low">低</SelectItem>
<SelectItem value="medium">中</SelectItem>
<SelectItem value="high">高</SelectItem>
<SelectItem value="urgent">紧急</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</AppField>
<AppField label="工单标题" htmlFor="ticket-title" required><Input id="ticket-title" required placeholder="例如:查询客户采集进度" value={form.title} onChange={(event) => updateField('title', event.target.value)} /></AppField>
<AppField label="问题表现" htmlFor="ticket-problem-performance" required><Input id="ticket-problem-performance" required placeholder="例如:采集进度长时间未更新" value={form.problem_performance} onChange={(event) => updateField('problem_performance', event.target.value)} /></AppField>
</div>
<AppField label="问题描述" htmlFor="ticket-description" required><Textarea id="ticket-description" required placeholder="请填写问题背景、期望结果、已知线索或需要支持团队确认的内容。" value={form.description} onChange={(event) => updateField('description', event.target.value)} /></AppField>
</AppSection>
<AppSection variant="card" title="分类与影响" description="用于确定处理队列、响应顺序和影响范围。">
<div className="grid gap-4 md:grid-cols-2">
<AppField label="问题分类" required><Select value={form.category_code} onValueChange={(value) => updateField('category_code', value as TicketInput['category_code'])}><SelectTrigger className="w-full"><SelectValue placeholder="请选择问题分类" /></SelectTrigger><SelectContent><SelectGroup>{TICKET_CATEGORIES.map((category) => <SelectItem key={category.code} value={category.code}>{category.label}</SelectItem>)}</SelectGroup></SelectContent></Select></AppField>
<AppField label="优先级" required><Select value={form.priority} onValueChange={(value) => updateField('priority', value as TicketInput['priority'])}><SelectTrigger className="w-full"><SelectValue placeholder="请选择优先级" /></SelectTrigger><SelectContent><SelectGroup><SelectItem value="low">低</SelectItem><SelectItem value="medium">中</SelectItem><SelectItem value="high">高</SelectItem><SelectItem value="urgent">紧急</SelectItem></SelectGroup></SelectContent></Select></AppField>
<AppField label="影响范围" htmlFor="ticket-impact-scope">
<Input
id="ticket-impact-scope"
@@ -195,41 +140,32 @@ export function TicketCreatePage() {
onChange={(event) => updateField('related_system', event.target.value)}
/>
</AppField>
<AppField label="环境" required>
<Select value={form.environment} onValueChange={(value) => updateField('environment', value)}>
<SelectTrigger className="w-full">
<SelectValue placeholder="请选择问题环境" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{ENVIRONMENT_OPTIONS.map((environment) => (
<SelectItem key={environment} value={environment}>
{environment}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</AppField>
</div>
</AppSection>
<AppSection title="提交确认">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div className="flex items-start gap-2 text-sm text-muted-foreground">
<CheckCircle2 className="mt-0.5 size-4" />
<span>请确认分类、对象和描述已足够支持处理人员判断下一步动作。</span>
</div>
<Button type="submit" disabled={submitting}>
<Send data-icon="inline-start" />
{submitting ? '提交中...' : '提交工单'}
</Button>
<AppSection
variant="card"
title="提交确认"
description="提交后工单将进入对应的支持处理队列。"
footer={
<>
<span className="text-xs text-muted-foreground">确认无误后提交</span>
<Button className="hidden md:inline-flex" type="submit" disabled={submitting}>
<Send data-icon="inline-start" />
{submitting ? '提交中...' : '提交工单'}
</Button>
</>
}
>
<div className="flex items-start gap-2 text-sm text-muted-foreground">
<CheckCircle2 className="mt-0.5 size-4" />
<span>请确认分类、对象和描述已足够支持处理人员判断下一步动作。</span>
</div>
</AppSection>
</div>
<div className="grid content-start gap-4">
<AppSection title="建单提示">
<AppSection variant="card" title="建单提示">
<div className="grid gap-3 text-sm text-muted-foreground">
<div className="flex gap-2">
<Lightbulb className="mt-0.5 size-4" />
@@ -246,16 +182,10 @@ export function TicketCreatePage() {
</div>
</AppSection>
<AppSection
title="AI 解决方案"
action={<Badge variant="ai">即将接入</Badge>}
>
<p className="text-sm text-muted-foreground">
后续阶段将由 AI 识别问题后自动回填,当前不作为人工必填项。
</p>
</AppSection>
<AppSection variant="card" title="Agent 辅助" action={<Badge variant="ai">演示</Badge>}><p className="text-sm text-muted-foreground">右侧助手会根据当前页面字段提供排查结构,但不会提交或保存工单。</p></AppSection>
</div>
</form>
<div className="sticky bottom-3 rounded-lg border bg-card/95 p-3 shadow-lg backdrop-blur md:hidden"><Button form="ticket-create-form" type="submit" className="w-full" disabled={submitting}><Send data-icon="inline-start" />{submitting ? '提交中...' : '提交工单'}</Button></div>
</AppPage>
)
}