feat: build reporting workbench workflow
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Clock3, Eye, Search } from 'lucide-react';
|
||||
import { adminApi, type ReportRecord } from '@/api/adminApi';
|
||||
import { Breadcrumb, Button, DateRangeInput, Input, Modal, Pagination, Select, Table, Tag, type DateRangeValue, type TableColumn } from '@/components/ui';
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
DateRangeInput,
|
||||
Input,
|
||||
Modal,
|
||||
Pagination,
|
||||
Select,
|
||||
Table,
|
||||
Tag,
|
||||
type DateRangeValue,
|
||||
type TableColumn,
|
||||
} from '@/components/ui';
|
||||
|
||||
const statusTone: Record<string, 'neutral' | 'info' | 'success' | 'warning' | 'danger'> = {
|
||||
pending: 'neutral',
|
||||
@@ -17,15 +29,40 @@ const statusTone: Record<string, 'neutral' | 'info' | 'success' | 'warning' | 'd
|
||||
};
|
||||
|
||||
const statusLabel: Record<string, string> = {
|
||||
pending: '待报备', waiting_material: '待补充资料', waiting_review: '待重新审核', reporting: '报备中', exporting: '导出中', partial: '部分通过', approved: '已通过', success: '成功', completed: '已完成', failed: '失败', rejected: '已驳回', abandoned: '已废弃', imported: '已导入', deleted: '已删除',
|
||||
pending: '待报备',
|
||||
waiting_material: '待补充资料',
|
||||
waiting_review: '待重新审核',
|
||||
reporting: '报备中',
|
||||
exporting: '导出中',
|
||||
partial: '部分通过',
|
||||
approved: '已通过',
|
||||
success: '成功',
|
||||
completed: '已完成',
|
||||
failed: '失败',
|
||||
rejected: '已驳回',
|
||||
abandoned: '已废弃',
|
||||
imported: '已导入',
|
||||
deleted: '已删除',
|
||||
};
|
||||
|
||||
const actionLabel: Record<string, string> = {
|
||||
create: '创建报备任务', manual_status_change: '人工修改状态', export: '导出报备资料', receipt_import: '导入回执', audit_approved_create: '引流审核通过后创建', audit_approved_reset: '引流审核通过后重置', audit_resubmit_freeze: '引流修改后冻结', audit_rejected_freeze: '引流审核驳回后冻结', drainage_deleted: '引流信息删除',
|
||||
create: '创建报备任务',
|
||||
manual_status_change: '人工修改状态',
|
||||
export: '导出报备资料',
|
||||
receipt_import: '导入回执',
|
||||
audit_approved_create: '引流审核通过后创建',
|
||||
audit_approved_reset: '引流审核通过后重置',
|
||||
audit_resubmit_freeze: '引流修改后冻结',
|
||||
audit_rejected_freeze: '引流审核驳回后冻结',
|
||||
drainage_deleted: '引流信息删除',
|
||||
};
|
||||
|
||||
const sourceEntryLabel: Record<string, string> = {
|
||||
enterprise_signature: '企业签名修改', report_task: '报备任务修改', channel_report: '通道信息修改', system: '系统自动处理', legacy: '历史记录(入口未记录)',
|
||||
enterprise_signature: '企业签名修改',
|
||||
report_task: '报备任务修改',
|
||||
channel_report: '通道信息修改',
|
||||
system: '系统自动处理',
|
||||
legacy: '历史记录(入口未记录)',
|
||||
};
|
||||
|
||||
function translateStatus(value?: string | null) {
|
||||
@@ -42,22 +79,67 @@ function RecordDetailModal({ record, onClose }: { record: ReportRecord; onClose:
|
||||
const isDrainage = record.task?.reportType === 'drainage';
|
||||
const target = isDrainage ? record.task?.drainageInfo?.url : record.task?.signature?.name;
|
||||
return (
|
||||
<Modal footer={<Button onClick={onClose}>关闭</Button>} onClose={onClose} open size="xl" title={<div className="template-modal-title"><h2>报备记录详情</h2><p>{record.id}</p></div>}>
|
||||
<Modal
|
||||
footer={<Button onClick={onClose}>关闭</Button>}
|
||||
onClose={onClose}
|
||||
open
|
||||
size="xl"
|
||||
title={
|
||||
<div className="template-modal-title">
|
||||
<h2>报备记录详情</h2>
|
||||
<p>{record.id}</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="report-record-detail">
|
||||
<div className="detail-grid">
|
||||
<div><span>报备任务号</span><strong>{record.taskId}</strong></div>
|
||||
<div><span>通道</span><strong>{record.channel?.name ?? '-'}</strong></div>
|
||||
<div><span>报备类型</span><strong>{isDrainage ? '引流信息' : '签名'}</strong></div>
|
||||
<div><span>报备对象</span><strong>{target ?? '-'}</strong></div>
|
||||
<div><span>动作</span><strong>{actionLabel[record.action] ?? record.action}</strong></div>
|
||||
<div><span>修改入口</span><strong>{recordSource(record)}</strong></div>
|
||||
<div><span>状态前</span><strong>{translateStatus(record.statusBefore)}</strong></div>
|
||||
<div><span>状态后</span><strong>{translateStatus(record.statusAfter)}</strong></div>
|
||||
<div className="detail-grid__wide"><span>失败/备注原因</span><strong>{record.reason ?? '-'}</strong></div>
|
||||
<div>
|
||||
<span>报备任务号</span>
|
||||
<strong>{record.taskId}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>通道</span>
|
||||
<strong>{record.channel?.name ?? '-'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>报备类型</span>
|
||||
<strong>{isDrainage ? '引流信息' : '签名'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>报备对象</span>
|
||||
<strong>{target ?? '-'}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>动作</span>
|
||||
<strong>{actionLabel[record.action] ?? record.action}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>修改入口</span>
|
||||
<strong>{recordSource(record)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>状态前</span>
|
||||
<strong>{translateStatus(record.statusBefore)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>状态后</span>
|
||||
<strong>{translateStatus(record.statusAfter)}</strong>
|
||||
</div>
|
||||
<div className="detail-grid__wide">
|
||||
<span>失败/备注原因</span>
|
||||
<strong>{record.reason ?? '-'}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<section className="report-history">
|
||||
<h3><Clock3 size={17} />状态历史</h3>
|
||||
<div><span>{record.createdAt ?? '-'}</span><strong>{actionLabel[record.action] ?? record.action}</strong><em>{record.reason ?? `修改入口:${recordSource(record)}`}</em></div>
|
||||
<h3>
|
||||
<Clock3 size={17} />
|
||||
状态历史
|
||||
</h3>
|
||||
<div>
|
||||
<span>{record.createdAt ?? '-'}</span>
|
||||
<strong>{actionLabel[record.action] ?? record.action}</strong>
|
||||
<em>{record.reason ?? `修改入口:${recordSource(record)}`}</em>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -69,6 +151,10 @@ export function AdminReportRecordsPage() {
|
||||
const [keyword, setKeyword] = useState('');
|
||||
const [dateRange, setDateRange] = useState<DateRangeValue>({});
|
||||
const [reportType, setReportType] = useState('all');
|
||||
const [batchNo, setBatchNo] = useState('');
|
||||
const [operatorKeyword, setOperatorKeyword] = useState('');
|
||||
const [statusAfter, setStatusAfter] = useState('all');
|
||||
const [sourceEntry, setSourceEntry] = useState('all');
|
||||
const [detail, setDetail] = useState<ReportRecord | null>(null);
|
||||
const [error, setError] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
@@ -76,14 +162,19 @@ export function AdminReportRecordsPage() {
|
||||
const pageSize = 10;
|
||||
|
||||
function loadData(targetPage = page) {
|
||||
adminApi.listReportRecordsPage({
|
||||
keyword: keyword || undefined,
|
||||
reportType: reportType === 'all' ? undefined : reportType as 'signature' | 'drainage',
|
||||
createdAtFrom: dateRange.start || undefined,
|
||||
createdAtTo: dateRange.end || undefined,
|
||||
page: targetPage,
|
||||
pageSize,
|
||||
})
|
||||
adminApi
|
||||
.listReportRecordsPage({
|
||||
keyword: keyword || undefined,
|
||||
reportType: reportType === 'all' ? undefined : (reportType as 'signature' | 'drainage'),
|
||||
batchNo: batchNo.trim() || undefined,
|
||||
operatorKeyword: operatorKeyword.trim() || undefined,
|
||||
statusAfter: statusAfter === 'all' ? undefined : statusAfter,
|
||||
sourceEntry: sourceEntry === 'all' ? undefined : sourceEntry,
|
||||
createdAtFrom: dateRange.start || undefined,
|
||||
createdAtTo: dateRange.end || undefined,
|
||||
page: targetPage,
|
||||
pageSize,
|
||||
})
|
||||
.then((result) => {
|
||||
setRecords(result.items);
|
||||
setTotal(result.total);
|
||||
@@ -97,35 +188,168 @@ export function AdminReportRecordsPage() {
|
||||
}, [page]);
|
||||
|
||||
const columns: Array<TableColumn<ReportRecord>> = [
|
||||
{ key: 'task', title: '报备任务号', width: '190px', render: (record) => <strong className="admin-task-id">{record.taskId}</strong> },
|
||||
{
|
||||
key: 'task',
|
||||
title: '报备任务号',
|
||||
width: '190px',
|
||||
render: (record) => <strong className="admin-task-id">{record.taskId}</strong>,
|
||||
},
|
||||
{ key: 'channel', title: '通道名称', width: '180px', render: (record) => record.channel?.name ?? '-' },
|
||||
{ key: 'targetType', title: '变更主体', width: '110px', render: (record) => <Tag tone={record.task?.reportType === 'drainage' ? 'info' : 'neutral'}>{record.task?.reportType === 'drainage' ? '引流信息' : '签名'}</Tag> },
|
||||
{ key: 'target', title: '主体内容', width: '260px', render: (record) => record.task?.reportType === 'drainage' ? <div className="admin-task-enterprise"><strong>{record.task?.signature?.name ?? '-'}</strong><span>{record.task?.drainageInfo?.url ?? '-'}</span>{record.task?.drainageInfo?.remark ? <span>{record.task.drainageInfo.remark}</span> : null}</div> : <div className="admin-task-enterprise"><strong>{record.task?.signature?.name ?? '-'}</strong>{record.task?.signature?.purpose ? <span>{record.task.signature.purpose}</span> : null}</div> },
|
||||
{
|
||||
key: 'targetType',
|
||||
title: '变更主体',
|
||||
width: '110px',
|
||||
render: (record) => (
|
||||
<Tag tone={record.task?.reportType === 'drainage' ? 'info' : 'neutral'}>
|
||||
{record.task?.reportType === 'drainage' ? '引流信息' : '签名'}
|
||||
</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'target',
|
||||
title: '主体内容',
|
||||
width: '260px',
|
||||
render: (record) =>
|
||||
record.task?.reportType === 'drainage' ? (
|
||||
<div className="admin-task-enterprise">
|
||||
<strong>{record.task?.signature?.name ?? '-'}</strong>
|
||||
<span>{record.task?.drainageInfo?.url ?? '-'}</span>
|
||||
{record.task?.drainageInfo?.remark ? <span>{record.task.drainageInfo.remark}</span> : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="admin-task-enterprise">
|
||||
<strong>{record.task?.signature?.name ?? '-'}</strong>
|
||||
{record.task?.signature?.purpose ? <span>{record.task.signature.purpose}</span> : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{ key: 'source', title: '修改入口', width: '150px', render: (record) => recordSource(record) },
|
||||
{
|
||||
key: 'operator',
|
||||
title: '操作人',
|
||||
width: '140px',
|
||||
render: (record) => record.operator?.displayName ?? record.operator?.username ?? '系统',
|
||||
},
|
||||
{ key: 'action', title: '动作', width: '170px', render: (record) => actionLabel[record.action] ?? record.action },
|
||||
{ key: 'status', title: '状态变化', width: '210px', render: (record) => <Tag tone={statusTone[record.statusAfter ?? 'pending'] ?? 'info'}>{`${translateStatus(record.statusBefore)} → ${translateStatus(record.statusAfter)}`}</Tag> },
|
||||
{
|
||||
key: 'status',
|
||||
title: '状态变化',
|
||||
width: '210px',
|
||||
render: (record) => (
|
||||
<Tag
|
||||
tone={statusTone[record.statusAfter ?? 'pending'] ?? 'info'}
|
||||
>{`${translateStatus(record.statusBefore)} → ${translateStatus(record.statusAfter)}`}</Tag>
|
||||
),
|
||||
},
|
||||
{ key: 'time', title: '记录时间', width: '190px', render: (record) => record.createdAt ?? '-' },
|
||||
{ key: 'reason', title: '备注', width: '320px', render: (record) => <span className="ui-table__long-text">{record.reason ?? '-'}</span> },
|
||||
{ key: 'actions', title: '操作', align: 'right', width: '120px', render: (record) => <Button icon={<Eye size={14} />} onClick={() => setDetail(record)} size="sm" variant="ghost">详情</Button> },
|
||||
{
|
||||
key: 'reason',
|
||||
title: '备注',
|
||||
width: '320px',
|
||||
render: (record) => <span className="ui-table__long-text">{record.reason ?? '-'}</span>,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
align: 'right',
|
||||
width: '120px',
|
||||
render: (record) => (
|
||||
<Button icon={<Eye size={14} />} onClick={() => setDetail(record)} size="sm" variant="ghost">
|
||||
详情
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="page-stack admin-sms-task-page report-record-page">
|
||||
<div className="page-heading">
|
||||
<div>
|
||||
<Breadcrumb items={['报备任务', '报备记录']} />
|
||||
<h1>报备记录</h1>
|
||||
<Breadcrumb items={['报备工作台', '状态记录']} />
|
||||
<h1>状态记录</h1>
|
||||
</div>
|
||||
</div>
|
||||
{error ? <p className="form-error">{error}</p> : null}
|
||||
|
||||
<div className="surface admin-task-filter">
|
||||
<Input label="报备任务号/通道/动作/备注" onChange={(event) => setKeyword(event.target.value)} placeholder="请输入报备任务号、通道、动作或备注" value={keyword} />
|
||||
<Select label="报备类型" onChange={(event) => setReportType(event.target.value)} options={[{ label: '全部类型', value: 'all' }, { label: '签名报备', value: 'signature' }, { label: '引流信息报备', value: 'drainage' }]} value={reportType} />
|
||||
<Input
|
||||
label="报备任务号/通道/动作/备注"
|
||||
onChange={(event) => setKeyword(event.target.value)}
|
||||
placeholder="请输入报备任务号、通道、动作或备注"
|
||||
value={keyword}
|
||||
/>
|
||||
<Select
|
||||
label="报备类型"
|
||||
onChange={(event) => setReportType(event.target.value)}
|
||||
options={[
|
||||
{ label: '全部类型', value: 'all' },
|
||||
{ label: '签名报备', value: 'signature' },
|
||||
{ label: '引流信息报备', value: 'drainage' },
|
||||
]}
|
||||
value={reportType}
|
||||
/>
|
||||
<Input
|
||||
label="报备批次号"
|
||||
onChange={(event) => setBatchNo(event.target.value)}
|
||||
placeholder="输入批次号"
|
||||
value={batchNo}
|
||||
/>
|
||||
<Input
|
||||
label="操作人"
|
||||
onChange={(event) => setOperatorKeyword(event.target.value)}
|
||||
placeholder="姓名或账号"
|
||||
value={operatorKeyword}
|
||||
/>
|
||||
<Select
|
||||
label="变更后状态"
|
||||
onChange={(event) => setStatusAfter(event.target.value)}
|
||||
options={[
|
||||
{ label: '全部状态', value: 'all' },
|
||||
{ label: '未报备', value: 'pending' },
|
||||
{ label: '报备中', value: 'reporting' },
|
||||
{ label: '报备通过', value: 'approved' },
|
||||
{ label: '报备失败', value: 'failed' },
|
||||
{ label: '已放弃', value: 'abandoned' },
|
||||
]}
|
||||
value={statusAfter}
|
||||
/>
|
||||
<Select
|
||||
label="修改入口"
|
||||
onChange={(event) => setSourceEntry(event.target.value)}
|
||||
options={[
|
||||
{ label: '全部入口', value: 'all' },
|
||||
{ label: '企业签名修改', value: 'enterprise_signature' },
|
||||
{ label: '通道报备明细', value: 'report_task' },
|
||||
{ label: '通道详情', value: 'channel_report' },
|
||||
{ label: '系统自动处理', value: 'system' },
|
||||
]}
|
||||
value={sourceEntry}
|
||||
/>
|
||||
<DateRangeInput label="提交时间" onChange={setDateRange} value={dateRange} />
|
||||
<div className="admin-task-filter__actions">
|
||||
<Button icon={<Search size={16} />} onClick={() => { if (page !== 1) setPage(1); else loadData(1); }}>查询</Button>
|
||||
<Button onClick={() => { setKeyword(''); setDateRange({}); setReportType('all'); }} variant="ghost">重置</Button>
|
||||
<Button
|
||||
icon={<Search size={16} />}
|
||||
onClick={() => {
|
||||
if (page !== 1) setPage(1);
|
||||
else loadData(1);
|
||||
}}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setKeyword('');
|
||||
setDateRange({});
|
||||
setReportType('all');
|
||||
setBatchNo('');
|
||||
setOperatorKeyword('');
|
||||
setStatusAfter('all');
|
||||
setSourceEntry('all');
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user