feat: add report material workflows and gateway safeguards

This commit is contained in:
hectorzhao
2026-07-15 18:23:48 +08:00
parent cf9f4ce4cd
commit 7091a8bed4
41 changed files with 3606 additions and 71 deletions
+7 -18
View File
@@ -4,8 +4,9 @@ import { useNavigate, useParams } from 'react-router-dom';
import { adminApi, type AdminChannel, type ChannelReportField, type ClientSmsSignature, type DictionaryItem, type ReportRecord, type ReportTask } from '@/api/adminApi';
import { Breadcrumb, Button, Input, Modal, Select, Tag, Textarea } from '@/components/ui';
import { formatDateTime } from '@/utils/dateTime';
import { ReportFieldMappingModal } from './ReportFieldMappingModal';
type ReportType = 'signature' | 'drainage' | 'both';
type ReportType = 'signature' | 'drainage';
type DrainageItem = Record<string, unknown> & { id?: string; url?: string; siteName?: string; submittedAt?: string; remark?: string };
const statusMeta: Record<string, { label: string; tone: 'success' | 'danger' | 'warning' | 'neutral' }> = {
@@ -22,8 +23,6 @@ const statusMeta: Record<string, { label: string; tone: 'success' | 'danger' | '
filing: { label: '报备中', tone: 'warning' },
};
const fieldTypeLabel: Record<string, string> = { string: '字符串', image: '图片', file: '文件' };
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
}
@@ -81,9 +80,6 @@ export function AdminChannelReportPage() {
const [nextStatus, setNextStatus] = useState('approved');
const [statusReason, setStatusReason] = useState('');
const [configType, setConfigType] = useState<ReportType>();
const [drainageFieldId, setDrainageFieldId] = useState('');
const [required, setRequired] = useState(false);
const [description, setDescription] = useState('');
const [error, setError] = useState('');
function loadData() {
@@ -119,11 +115,10 @@ export function AdminChannelReportPage() {
return records.find((record) => record.taskId === taskId && record.action === action);
}
function createField() {
if (!configType || !drainageFieldId) return;
adminApi.createChannelReportField({ channelId, drainageFieldId, reportType: configType, required, description, status: 'active' })
.then(() => { setConfigType(undefined); setDrainageFieldId(''); setRequired(false); setDescription(''); loadData(); })
.catch((failure: Error) => setError(failure.message || '报备字段保存失败'));
async function saveFieldMapping(nextFields: Parameters<typeof adminApi.replaceChannelReportFields>[2]) {
if (!configType) return;
await adminApi.replaceChannelReportFields(channelId, configType, nextFields);
loadData();
}
function saveTaskStatus() {
@@ -180,13 +175,7 @@ export function AdminChannelReportPage() {
{detail ? <DetailModal {...detail} onClose={() => setDetail(undefined)} /> : null}
<Modal footer={<><Button onClick={() => setStatusTask(undefined)} variant="ghost"></Button><Button onClick={saveTaskStatus}></Button></>} onClose={() => setStatusTask(undefined)} open={Boolean(statusTask)} title="修改当前通道报备状态"><div className="admin-system-modal-form"><Select label="报备状态" onChange={(event) => setNextStatus(event.target.value)} options={[{label:'未报备',value:'pending'},{label:'资料待补充',value:'waiting_material'},{label:'报备中',value:'reporting'},{label:'报备通过',value:'approved'},{label:'报备失败',value:'failed'},{label:'放弃报备',value:'abandoned'}]} value={nextStatus}/><Textarea label="修改原因" onChange={(event) => setStatusReason(event.target.value)} rows={3} value={statusReason}/></div></Modal>
<Modal footer={<><Button onClick={() => setConfigType(undefined)} variant="ghost"></Button><Button disabled={!drainageFieldId} onClick={createField}></Button></>} onClose={() => setConfigType(undefined)} open={Boolean(configType)} title={configType === 'drainage' ? '配置引流信息报备字段' : '配置签名报备字段'}>
<div className="admin-system-modal-form">
<Select label="报备字段库字段" onChange={(event) => setDrainageFieldId(event.target.value)} options={[{ label: '请选择字段', value: '' }, ...libraryFields.map((field) => ({ label: `${field.name ?? field.code}${fieldTypeLabel[String(field.fieldType)] ?? field.fieldType}`, value: field.id }))]} value={drainageFieldId} />
<Select label="是否必填" onChange={(event) => setRequired(event.target.value === 'true')} options={[{ label: '选填', value: 'false' }, { label: '必填', value: 'true' }]} value={String(required)} />
<Textarea label="通道报备说明" onChange={(event) => setDescription(event.target.value)} rows={4} value={description} />
</div>
</Modal>
{configType ? <ReportFieldMappingModal fields={fields} libraryFields={libraryFields} onClose={() => setConfigType(undefined)} onSave={saveFieldMapping} reportType={configType} /> : null}
</section>
);
}