feat: add category-driven ticket workflows
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { AlertCircle, Bot, CheckCircle2, Lightbulb, Send } from 'lucide-react'
|
||||
import { AlertCircle, CheckCircle2, Lightbulb, Send } from 'lucide-react'
|
||||
|
||||
import { AppField } from '@/components/app/AppField'
|
||||
import { AppPage } from '@/components/app/AppPage'
|
||||
@@ -13,17 +13,25 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectVa
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
import { createTicket } from './api'
|
||||
import { TICKET_CATEGORIES } from './ticketCategories'
|
||||
import type { TicketInput } from './types'
|
||||
|
||||
const initialForm: TicketInput = {
|
||||
title: '',
|
||||
description: '',
|
||||
category_code: 'business_issue',
|
||||
priority: 'medium',
|
||||
problem_performance: '',
|
||||
problem_object: '',
|
||||
submitter_name: '',
|
||||
submitter_org: '',
|
||||
impact_scope: '',
|
||||
environment: '',
|
||||
related_system: ''
|
||||
}
|
||||
|
||||
const ENVIRONMENT_OPTIONS = ['生产环境', '测试环境', '预发布环境', '本地环境']
|
||||
|
||||
export function TicketCreatePage() {
|
||||
const [form, setForm] = React.useState<TicketInput>(initialForm)
|
||||
const [error, setError] = React.useState('')
|
||||
@@ -36,6 +44,10 @@ export function TicketCreatePage() {
|
||||
async function handleSubmit(event: React.FormEvent) {
|
||||
event.preventDefault()
|
||||
setError('')
|
||||
if (!form.environment || !form.problem_performance || !form.problem_object || !form.submitter_name || !form.submitter_org) {
|
||||
setError('请完整填写问题表现、问题对象、提出人、提出机构和环境。')
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
const ticket = await createTicket(form)
|
||||
@@ -61,18 +73,94 @@ export function TicketCreatePage() {
|
||||
) : null}
|
||||
|
||||
<AppSection title="基础信息">
|
||||
<AppField label="工单标题" htmlFor="ticket-title" required>
|
||||
<Input
|
||||
id="ticket-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.title}
|
||||
onChange={(event) => updateField('title', event.target.value)}
|
||||
placeholder="请填写问题背景、期望结果、已知线索或需要支持团队确认的内容。"
|
||||
value={form.description}
|
||||
onChange={(event) => updateField('description', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
</AppSection>
|
||||
|
||||
<AppSection title="影响与优先级">
|
||||
<AppSection title="提出信息">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<AppField label="提出人" htmlFor="ticket-submitter-name" required>
|
||||
<Input
|
||||
id="ticket-submitter-name"
|
||||
required
|
||||
placeholder="请输入提出人"
|
||||
value={form.submitter_name}
|
||||
onChange={(event) => updateField('submitter_name', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
<AppField label="提出机构" htmlFor="ticket-submitter-org" required>
|
||||
<Input
|
||||
id="ticket-submitter-org"
|
||||
required
|
||||
placeholder="请输入提出机构"
|
||||
value={form.submitter_org}
|
||||
onChange={(event) => updateField('submitter_org', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
</div>
|
||||
</AppSection>
|
||||
|
||||
<AppSection title="影响与环境">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<AppField label="优先级" required>
|
||||
<Select value={form.priority} onValueChange={(value) => updateField('priority', value as TicketInput['priority'])}>
|
||||
@@ -92,15 +180,12 @@ export function TicketCreatePage() {
|
||||
<AppField label="影响范围" htmlFor="ticket-impact-scope">
|
||||
<Input
|
||||
id="ticket-impact-scope"
|
||||
placeholder="例如:单个用户、某部门、全部用户"
|
||||
placeholder="例如:单个客户、某部门、全部用户"
|
||||
value={form.impact_scope}
|
||||
onChange={(event) => updateField('impact_scope', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
</div>
|
||||
</AppSection>
|
||||
|
||||
<AppSection title="环境与系统">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<AppField label="关联系统" htmlFor="ticket-related-system">
|
||||
<Input
|
||||
@@ -110,34 +195,30 @@ export function TicketCreatePage() {
|
||||
onChange={(event) => updateField('related_system', event.target.value)}
|
||||
/>
|
||||
</AppField>
|
||||
<AppField label="运行环境" htmlFor="ticket-environment">
|
||||
<Input
|
||||
id="ticket-environment"
|
||||
placeholder="例如:生产环境、测试环境、Windows 终端"
|
||||
value={form.environment}
|
||||
onChange={(event) => updateField('environment', event.target.value)}
|
||||
/>
|
||||
<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="问题描述">
|
||||
<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="提交确认">
|
||||
<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>
|
||||
<span>请确认分类、对象和描述已足够支持处理人员判断下一步动作。</span>
|
||||
</div>
|
||||
<Button type="submit" disabled={submitting}>
|
||||
<Send data-icon="inline-start" />
|
||||
@@ -152,25 +233,25 @@ export function TicketCreatePage() {
|
||||
<div className="grid gap-3 text-sm text-muted-foreground">
|
||||
<div className="flex gap-2">
|
||||
<Lightbulb className="mt-0.5 size-4" />
|
||||
<span>标题建议包含系统、场景和异常现象。</span>
|
||||
<span>业务、操作类问题可在详情页直接填写解决方案并完成。</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Lightbulb className="mt-0.5 size-4" />
|
||||
<span>描述中写清楚复现步骤、报错信息和影响范围。</span>
|
||||
<span>技术类问题会保留开始处理、提交确认、解决和关闭流程。</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Lightbulb className="mt-0.5 size-4" />
|
||||
<span>紧急优先级应只用于阻断生产或大范围影响问题。</span>
|
||||
<span>数据类问题支持处理后解决,也支持明确后直接解决。</span>
|
||||
</div>
|
||||
</div>
|
||||
</AppSection>
|
||||
|
||||
<AppSection
|
||||
title="AI 建单辅助"
|
||||
title="AI 解决方案"
|
||||
action={<Badge variant="ai">即将接入</Badge>}
|
||||
>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
后续阶段将接入建单 Agent,用于补全字段、追问缺失信息和推荐相似问题。
|
||||
后续阶段将由 AI 识别问题后自动回填,当前不作为人工必填项。
|
||||
</p>
|
||||
</AppSection>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user