105 lines
4.2 KiB
TypeScript
105 lines
4.2 KiB
TypeScript
import { useState } from 'react';
|
|
import { Button, Descriptions, Drawer, Form, Select, Table, Typography, message } from 'antd';
|
|
import type { ColumnsType } from 'antd/es/table';
|
|
import { request } from '../lib/api';
|
|
import { fmtTime } from '../lib/constants';
|
|
import { useAdminList } from '../lib/useAdminList';
|
|
|
|
type Row = {
|
|
id: string;
|
|
ticketNo: string;
|
|
ticketType: string;
|
|
status: string;
|
|
refType: string;
|
|
refId: string;
|
|
remark?: string;
|
|
createdAt: string;
|
|
};
|
|
|
|
export default function TicketsPage() {
|
|
const [filters, setFilters] = useState<Record<string, string>>({});
|
|
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList<Row>(
|
|
'/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<Record<string, unknown> | null>(null);
|
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
|
|
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);
|
|
}
|
|
|
|
const columns: ColumnsType<Row> = [
|
|
{ title: '工单号', dataIndex: 'ticketNo', width: 180 },
|
|
{ title: '类型', dataIndex: 'ticketType', width: 100 },
|
|
{ title: '状态', dataIndex: 'status', width: 90 },
|
|
{ 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) => (
|
|
<Button type="link" size="small" onClick={async () => {
|
|
setDetail(await request(`/admin/tickets/${row.id}`));
|
|
setDrawerOpen(true);
|
|
}}>详情</Button>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<Typography.Title level={4}>工单中心</Typography.Title>
|
|
<Form layout="inline" style={{ marginBottom: 16 }} onFinish={(v) => { setFilters(v); setPage(1); }}>
|
|
<Form.Item name="ticketType" label="类型">
|
|
<Select allowClear style={{ width: 120 }} options={[
|
|
{ value: 'REFUND', label: '退款' },
|
|
{ value: 'RESHIPMENT', label: '补发' },
|
|
{ value: 'ALERT', label: '异常' },
|
|
]} />
|
|
</Form.Item>
|
|
<Form.Item name="status" label="状态"><Input allowClear placeholder="PENDING" /></Form.Item>
|
|
<Button type="primary" htmlType="submit">筛选</Button>
|
|
</Form>
|
|
<Table rowKey="id" loading={loading} columns={columns} dataSource={data?.items ?? []} scroll={{ x: 900 }}
|
|
pagination={{ current: page, pageSize, total: data?.total ?? 0, showSizeChanger: true, onChange: (p, ps) => { setPage(p); setPageSize(ps); } }} />
|
|
<Drawer title="工单详情" width={480} open={drawerOpen} onClose={() => setDrawerOpen(false)}
|
|
extra={detail && (detail.status === 'PENDING' || detail.status === 'OPEN') ? (
|
|
<>
|
|
<Button type="primary" onClick={() => approve(String(detail.id))} style={{ marginRight: 8 }}>通过</Button>
|
|
<Button danger onClick={() => reject(String(detail.id))}>驳回</Button>
|
|
</>
|
|
) : null}>
|
|
{detail && (
|
|
<Descriptions column={1} bordered size="small">
|
|
<Descriptions.Item label="工单号">{String(detail.ticketNo)}</Descriptions.Item>
|
|
<Descriptions.Item label="类型">{String(detail.ticketType)}</Descriptions.Item>
|
|
<Descriptions.Item label="状态">{String(detail.status)}</Descriptions.Item>
|
|
<Descriptions.Item label="关联">{String(detail.refType)} #{String(detail.refId)}</Descriptions.Item>
|
|
<Descriptions.Item label="备注">{String(detail.remark ?? '—')}</Descriptions.Item>
|
|
</Descriptions>
|
|
)}
|
|
</Drawer>
|
|
</div>
|
|
);
|
|
}
|