import { useState } from 'react'; import { Button, Descriptions, Drawer, Form, Image, Input, Modal, Select, Space, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { TICKET_STATUS_LABELS, TICKET_TYPE_LABELS, ticketStatusLabel, type TicketStatusDto, type TicketTypeDto, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; type Row = { id: string; ticketNo: string; ticketType: TicketTypeDto; status: string; refType: string; refId: string; remark?: string; extraJson?: { evidenceUrls?: string[] }; createdAt: string; }; const STATUS_COLOR: Partial> = { PENDING: 'orange', OPEN: 'blue', COLLABORATING: 'cyan', RESOLVED: 'green', REJECTED: 'red', COMPLETED: 'green', CLOSED: 'default', }; const STATUS_OPTIONS = (Object.keys(TICKET_STATUS_LABELS) as TicketStatusDto[]).map((value) => ({ value, label: TICKET_STATUS_LABELS[value], })); export default function TicketsPage() { const [filters, setFilters] = useState>({}); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/tickets', () => { const qs = new URLSearchParams(); if (filters.ticketType) qs.set('ticketType', filters.ticketType); if (filters.status) qs.set('status', filters.status); return qs; }, [filters], ); const [detail, setDetail] = useState(null); const [drawerOpen, setDrawerOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false); const [creating, setCreating] = useState(false); const [createForm] = Form.useForm<{ ticketType: TicketTypeDto; orderNo: string; remark?: string; }>(); async function approve(id: string) { await request(`/admin/tickets/${id}/approve`, { method: 'POST', body: JSON.stringify({}) }); message.success('已审批通过'); reload(); setDrawerOpen(false); } async function reject(id: string) { await request(`/admin/tickets/${id}/reject`, { method: 'POST', body: JSON.stringify({ remark: '驳回' }), }); message.success('已驳回'); reload(); setDrawerOpen(false); } async function submitCreate() { const values = await createForm.validateFields(); setCreating(true); try { await request('/admin/tickets', { method: 'POST', body: JSON.stringify({ ticketType: values.ticketType, orderNo: values.orderNo.trim(), remark: values.remark?.trim() || undefined, }), }); message.success('工单已创建'); setCreateOpen(false); createForm.resetFields(); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '创建失败'); } finally { setCreating(false); } } const columns: ColumnsType = [ { title: '工单号', dataIndex: 'ticketNo', width: 180 }, { title: '类型', dataIndex: 'ticketType', width: 110, render: (t: TicketTypeDto) => TICKET_TYPE_LABELS[t] ?? t, }, { title: '状态', dataIndex: 'status', width: 100, render: (s: string) => ( {ticketStatusLabel(s)} ), }, { title: '关联', width: 140, render: (_, r) => `${r.refType}#${r.refId}` }, { title: '备注', dataIndex: 'remark', ellipsis: true }, { title: '时间', dataIndex: 'createdAt', width: 160, render: fmtTime }, { title: '操作', width: 80, render: (_, row) => ( ), }, ]; const evidenceUrls = detail?.extraJson?.evidenceUrls ?? []; return (
工单中心
{ setFilters(v); setPage(1); }} >
{ setPage(p); setPageSize(ps); }, }} /> setDrawerOpen(false)} extra={ detail && (detail.status === 'PENDING' || detail.status === 'OPEN') ? ( ) : null } > {detail && ( {String(detail.ticketNo)} {TICKET_TYPE_LABELS[detail.ticketType] ?? detail.ticketType} {ticketStatusLabel(detail.status)} {String(detail.refType)} #{String(detail.refId)} {String(detail.remark ?? '—')} {evidenceUrls.length ? ( {evidenceUrls.map((url) => ( ))} ) : ( '—' )} )} setCreateOpen(false)} onOk={() => void submitCreate()} confirmLoading={creating} destroyOnClose okText="提交" >
); }