完善运营端报备流程与页面标题
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
ChevronUp,
|
||||
Eye,
|
||||
FileSliders,
|
||||
FileUp,
|
||||
GripVertical,
|
||||
Pencil,
|
||||
Plus,
|
||||
@@ -14,7 +15,7 @@ import {
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Button, DateRangeInput, Input, Modal, Select, Tag, Textarea } from '@/components/ui';
|
||||
import { Breadcrumb, Button, DateRangeInput, Input, Modal, Select, Tag, Textarea } from '@/components/ui';
|
||||
import type { DateRangeValue } from '@/components/ui';
|
||||
|
||||
type ReportStatus = 'success' | 'failed' | 'reporting' | 'unreported' | 'withdrawn' | 'abandoned';
|
||||
@@ -129,11 +130,34 @@ const drainageFieldPool: ReportField[] = [
|
||||
{ id: 'authorization', label: '授权证明', type: '文件' },
|
||||
];
|
||||
|
||||
const signatureFieldPool: ReportField[] = [
|
||||
{ id: 'signatureName', label: '短信签名', type: '文本' },
|
||||
{ id: 'signatureBasis', label: '签名依据', type: '文本' },
|
||||
{ id: 'qualificationFile', label: '资质凭证', type: '文件' },
|
||||
{ id: 'companyName', label: '公司名称', type: '文本' },
|
||||
{ id: 'creditCode', label: '统一社会信用代码', type: '文本' },
|
||||
{ id: 'legalName', label: '法人姓名', type: '文本' },
|
||||
{ id: 'legalId', label: '法人身份证号', type: '文本' },
|
||||
{ id: 'legalIdFront', label: '法人身份证人像面', type: '图片' },
|
||||
{ id: 'legalIdBack', label: '法人身份证国徽面', type: '图片' },
|
||||
{ id: 'managerName', label: '责任人姓名', type: '文本' },
|
||||
{ id: 'managerPhone', label: '责任人手机号', type: '文本' },
|
||||
{ id: 'managerId', label: '责任人身份证号', type: '文本' },
|
||||
{ id: 'authorization', label: '授权委托书', type: '文件' },
|
||||
];
|
||||
|
||||
const initialSelectedDrainageFields: SelectedReportField[] = [
|
||||
{ id: 'companyName', label: '公司名称', type: '文本', required: false, mapping: '' },
|
||||
{ id: 'legalId', label: '法人身份证号', type: '文本', required: false, mapping: '' },
|
||||
];
|
||||
|
||||
const initialSelectedSignatureFields: SelectedReportField[] = [
|
||||
{ id: 'signatureName', label: '短信签名', type: '文本', required: true, mapping: 'sign_name' },
|
||||
{ id: 'qualificationFile', label: '资质凭证', type: '文件', required: true, mapping: 'license_file' },
|
||||
{ id: 'companyName', label: '公司名称', type: '文本', required: true, mapping: 'enterprise_name' },
|
||||
{ id: 'creditCode', label: '统一社会信用代码', type: '文本', required: true, mapping: 'credit_code' },
|
||||
];
|
||||
|
||||
const emptyStats: DeliveryStats = {
|
||||
successRate: 0,
|
||||
successCount: 0,
|
||||
@@ -269,19 +293,25 @@ function SignatureDetailModal({ report, onClose }: { report: SignatureReport; on
|
||||
|
||||
function DrainageFieldConfigModal({
|
||||
fields,
|
||||
fieldPool,
|
||||
onChange,
|
||||
onClose,
|
||||
title = '配置报备字段',
|
||||
description = '从字段池中选择字段,并配置是否必填',
|
||||
}: {
|
||||
fields: SelectedReportField[];
|
||||
fieldPool: ReportField[];
|
||||
onChange: (fields: SelectedReportField[]) => void;
|
||||
onClose: () => void;
|
||||
title?: string;
|
||||
description?: string;
|
||||
}) {
|
||||
const [draft, setDraft] = useState(fields);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const availableFields = useMemo(() => drainageFieldPool.filter((field) => (
|
||||
const availableFields = useMemo(() => fieldPool.filter((field) => (
|
||||
!draft.some((item) => item.id === field.id)
|
||||
&& (!searchText || field.label.includes(searchText))
|
||||
)), [draft, searchText]);
|
||||
)), [draft, fieldPool, searchText]);
|
||||
|
||||
function addField(field: ReportField) {
|
||||
setDraft((items) => [...items, { ...field, required: false, mapping: '' }]);
|
||||
@@ -312,7 +342,7 @@ function DrainageFieldConfigModal({
|
||||
onClose={onClose}
|
||||
open
|
||||
size="xl"
|
||||
title={<div className="channel-field-config-title"><h2>配置报备字段</h2><p>从字段池中选择字段,并配置是否必填</p></div>}
|
||||
title={<div className="channel-field-config-title"><h2>{title}</h2><p>{description}</p></div>}
|
||||
>
|
||||
<div className="channel-field-config">
|
||||
<section className="channel-field-pool">
|
||||
@@ -366,6 +396,33 @@ function DrainageFieldConfigModal({
|
||||
);
|
||||
}
|
||||
|
||||
function ReceiptImportModal({ onClose, onSubmit }: { onClose: () => void; onSubmit: () => void }) {
|
||||
return (
|
||||
<Modal
|
||||
footer={<><Button onClick={onClose} variant="ghost">取消</Button><Button onClick={onSubmit}>确认导入</Button></>}
|
||||
onClose={onClose}
|
||||
open
|
||||
size="xl"
|
||||
title={<div className="template-modal-title"><h2>导入报备回执</h2><p>同步运营商返回的签名和引流信息报备状态。</p></div>}
|
||||
>
|
||||
<div className="report-receipt-modal">
|
||||
<div className="report-upload-drop">
|
||||
<FileUp size={38} />
|
||||
<strong>选择回执文件</strong>
|
||||
<span>支持 Excel、CSV。导入后会按签名、引流内容和通道匹配当前报备记录。</span>
|
||||
</div>
|
||||
<div className="report-receipt-preview">
|
||||
<h3>匹配预览</h3>
|
||||
<div><span>可更新签名</span><strong>12 条</strong></div>
|
||||
<div><span>可更新引流信息</span><strong>5 条</strong></div>
|
||||
<div><span>需人工确认</span><strong>2 条</strong></div>
|
||||
</div>
|
||||
<Textarea label="导入备注" placeholder="记录运营商工单号、回执来源或人工说明" rows={4} />
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function AdminChannelReportPage() {
|
||||
const navigate = useNavigate();
|
||||
const { channelId = '88827' } = useParams();
|
||||
@@ -380,7 +437,10 @@ export function AdminChannelReportPage() {
|
||||
const [nextRemark, setNextRemark] = useState('');
|
||||
const [detail, setDetail] = useState<ReportDetail | null>(null);
|
||||
const [fieldConfigOpen, setFieldConfigOpen] = useState(false);
|
||||
const [signatureFieldConfigOpen, setSignatureFieldConfigOpen] = useState(false);
|
||||
const [receiptOpen, setReceiptOpen] = useState(false);
|
||||
const [drainageFields, setDrainageFields] = useState(initialSelectedDrainageFields);
|
||||
const [signatureFields, setSignatureFields] = useState(initialSelectedSignatureFields);
|
||||
|
||||
const filteredReports = useMemo(() => reports.filter((report) => {
|
||||
const matchesKeyword = !keyword || report.name.includes(keyword) || report.drainage.some((item) => item.value.includes(keyword));
|
||||
@@ -433,14 +493,15 @@ export function AdminChannelReportPage() {
|
||||
return (
|
||||
<section className="page-stack channel-report-page">
|
||||
<div className="surface channel-report-hero">
|
||||
<div className="breadcrumb-line"><span>短信通道管理</span><span>/</span><strong>通道报备详情</strong></div>
|
||||
<Breadcrumb items={[channelNames[channelId] ?? `短信通道 ${channelId}`]} />
|
||||
<div className="channel-report-heading">
|
||||
<Button icon={<ChevronLeft size={16} />} onClick={() => navigate('/admin/channels')} variant="ghost">返回列表</Button>
|
||||
<h1>{channelNames[channelId] ?? `短信通道 ${channelId}`}</h1>
|
||||
<Button icon={<ChevronRight size={16} />} variant="ghost">下一个</Button>
|
||||
<div className="channel-report-config-actions">
|
||||
<Button icon={<FileUp size={16} />} onClick={() => setReceiptOpen(true)} variant="secondary">导入回执</Button>
|
||||
<Button icon={<Settings2 size={16} />} onClick={() => setFieldConfigOpen(true)} variant="ghost">个性化引流信息报备字段</Button>
|
||||
<Button icon={<FileSliders size={16} />} variant="ghost">个性化签名报备字段</Button>
|
||||
<Button icon={<FileSliders size={16} />} onClick={() => setSignatureFieldConfigOpen(true)} variant="ghost">个性化签名报备字段</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -543,12 +604,28 @@ export function AdminChannelReportPage() {
|
||||
|
||||
{fieldConfigOpen ? (
|
||||
<DrainageFieldConfigModal
|
||||
description="这些字段会驱动客户端引流资料补充,并映射到当前通道的导出模板。"
|
||||
fieldPool={drainageFieldPool}
|
||||
fields={drainageFields}
|
||||
onChange={setDrainageFields}
|
||||
onClose={() => setFieldConfigOpen(false)}
|
||||
title="个性化引流信息报备字段"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{signatureFieldConfigOpen ? (
|
||||
<DrainageFieldConfigModal
|
||||
description="这些字段会驱动客户端签名资料补充,并映射到当前通道的签名报备导出模板。"
|
||||
fieldPool={signatureFieldPool}
|
||||
fields={signatureFields}
|
||||
onChange={setSignatureFields}
|
||||
onClose={() => setSignatureFieldConfigOpen(false)}
|
||||
title="个性化签名报备字段"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{receiptOpen ? <ReceiptImportModal onClose={() => setReceiptOpen(false)} onSubmit={() => setReceiptOpen(false)} /> : null}
|
||||
|
||||
{detail?.kind === 'drainage' ? (
|
||||
<Modal footer={<Button onClick={() => setDetail(null)} variant="ghost">关闭</Button>} onClose={() => setDetail(null)} open size="md" title="引流信息报备详情">
|
||||
<div className="channel-report-detail">
|
||||
|
||||
Reference in New Issue
Block a user