780 lines
25 KiB
JavaScript
780 lines
25 KiB
JavaScript
import React, { useEffect, useState } from "react";
|
||
import {
|
||
Form,
|
||
Table,
|
||
Button,
|
||
Modal,
|
||
Input,
|
||
Select,
|
||
ConfigProvider,
|
||
message,
|
||
Upload,
|
||
} from "antd";
|
||
import { UploadOutlined } from "@ant-design/icons";
|
||
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
||
import SearchForm from "~/components/SearchForm";
|
||
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
|
||
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
|
||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||
import { NS_INSP_NOTICE } from "~/enumerate/namespace";
|
||
import dayjs from "dayjs";
|
||
import "./index.less";
|
||
|
||
const { TextArea } = Input;
|
||
|
||
const API_HOST = window.process?.env?.app?.API_HOST || "";
|
||
const UPLOAD_ACTION = `${API_HOST}/safetyEval/file/upload`;
|
||
|
||
/** 雪花 ID 须保持字符串,避免 Number() 精度丢失 */
|
||
const toId = (id) => (id == null ? id : String(id));
|
||
|
||
const downloadBinaryPost = async (path, body, fallbackName) => {
|
||
const headers = {
|
||
"Content-Type": "application/json",
|
||
token: sessionStorage.getItem("token") || "",
|
||
};
|
||
const orgInfoId = sessionStorage.getItem("orgInfoId");
|
||
if (orgInfoId) {
|
||
headers.orgInfoId = String(orgInfoId);
|
||
}
|
||
const res = await fetch(`${API_HOST}${path}`, {
|
||
method: "POST",
|
||
headers,
|
||
body: JSON.stringify(body || {}),
|
||
});
|
||
const contentType = res.headers.get("content-type") || "";
|
||
if (contentType.includes("application/json")) {
|
||
const json = await res.json();
|
||
throw new Error(json.errMessage || json.message || "导出失败");
|
||
}
|
||
if (!res.ok) {
|
||
throw new Error(`请求失败(${res.status})`);
|
||
}
|
||
const blob = await res.blob();
|
||
let fileName = fallbackName;
|
||
const disposition = res.headers.get("content-disposition") || "";
|
||
const matched = disposition.match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);
|
||
if (matched?.[1]) {
|
||
try {
|
||
fileName = decodeURIComponent(matched[1].replace(/"/g, ""));
|
||
} catch {
|
||
fileName = matched[1].replace(/"/g, "");
|
||
}
|
||
}
|
||
const blobUrl = window.URL.createObjectURL(blob);
|
||
const link = document.createElement("a");
|
||
link.href = blobUrl;
|
||
link.download = fileName;
|
||
link.style.display = "none";
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
window.URL.revokeObjectURL(blobUrl);
|
||
};
|
||
|
||
const PROTO_THEME = {
|
||
token: { borderRadius: 8, borderRadiusLG: 12, borderRadiusSM: 6 },
|
||
};
|
||
|
||
/** 通知类型与监管端监督检查告知一致 */
|
||
const INSP_TYPE_OPTIONS = [
|
||
{ label: "项目过程检查", value: "PROJECT_PROCESS" },
|
||
{ label: "资质保持检查", value: "QUAL_KEEP" },
|
||
{ label: "专项检查", value: "SPECIAL" },
|
||
];
|
||
|
||
const TAB_STATUS = {
|
||
all: undefined,
|
||
unread: 1,
|
||
processing: 2,
|
||
feedback: 3,
|
||
closed: 4,
|
||
};
|
||
|
||
const TAB_ITEMS = [
|
||
{ key: "all", label: "全部通知" },
|
||
{ key: "unread", label: "待处理" },
|
||
{ key: "processing", label: "处理中" },
|
||
{ key: "feedback", label: "待监管复核" },
|
||
{ key: "closed", label: "已办结" },
|
||
];
|
||
|
||
const STATUS_FILTER_OPTIONS = [
|
||
{ label: "待处理", value: 1 },
|
||
{ label: "处理中", value: 2 },
|
||
{ label: "待监管复核", value: 3 },
|
||
{ label: "已办结", value: 4 },
|
||
];
|
||
|
||
const PROCESS_TYPE_OPTIONS = [
|
||
{ label: "提交整改材料", value: "提交整改材料" },
|
||
{ label: "提交核查说明", value: "提交核查说明" },
|
||
{ label: "补充证明材料", value: "补充证明材料" },
|
||
{ label: "查看办结结果", value: "查看办结结果" },
|
||
];
|
||
|
||
const statusTagClass = (code) => {
|
||
if (code === 1) return "inst-tag-danger";
|
||
if (code === 2) return "inst-tag-warning";
|
||
if (code === 3) return "inst-tag-info";
|
||
if (code === 4) return "inst-tag-success";
|
||
return "";
|
||
};
|
||
|
||
const statusDisplayName = (code, name) => {
|
||
if (code === 1) return "待处理";
|
||
if (code === 2) return "处理中";
|
||
if (code === 3) return "待监管复核";
|
||
if (code === 4) return "已办结";
|
||
return name || "-";
|
||
};
|
||
|
||
/** 列表「通知类型」展示:办结用办结通知,其余用检查类型名 */
|
||
const noticeTypeLabel = (record) => {
|
||
if (record.statusCode === 4) return "办结通知";
|
||
return record.inspTypeName || "监督检查通知";
|
||
};
|
||
|
||
const noticeTypeTagClass = (record) => {
|
||
if (record.statusCode === 4) return "inst-tag-success";
|
||
if (record.statusCode === 1) return "inst-tag-danger";
|
||
if (record.statusCode === 3) return "inst-tag-info";
|
||
return "inst-tag-warning";
|
||
};
|
||
|
||
const formatDate = (val) => {
|
||
if (!val) return "-";
|
||
const d = dayjs(val);
|
||
return d.isValid() ? d.format("YYYY-MM-DD") : val;
|
||
};
|
||
|
||
const resolveFileUrl = (raw) => {
|
||
if (!raw) return "";
|
||
const u = String(raw);
|
||
if (/^https?:\/\//i.test(u)) return u;
|
||
const base = window.fileUrl || "";
|
||
return base ? `${base}${u}` : u;
|
||
};
|
||
|
||
const parseUrlList = (val) => {
|
||
if (!val) return [];
|
||
if (Array.isArray(val)) return val.filter(Boolean);
|
||
if (typeof val === "string") {
|
||
try {
|
||
const parsed = JSON.parse(val);
|
||
if (Array.isArray(parsed)) return parsed.filter(Boolean);
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
return val
|
||
.split(/[,;\n]/)
|
||
.map((s) => s.trim())
|
||
.filter(Boolean);
|
||
}
|
||
return [];
|
||
};
|
||
|
||
const urlToFileItem = (url) => ({
|
||
fileUrl: url,
|
||
fileName: String(url).split("?")[0].split("/").pop() || "附件",
|
||
});
|
||
|
||
const collectViewFiles = (detail, materials = []) => {
|
||
const fromMaterials = (materials || []).filter((m) => m.fileUrl);
|
||
const fromNotice = parseUrlList(detail?.noticeUrls).map(urlToFileItem);
|
||
const fromResult = parseUrlList(detail?.resultUrls).map(urlToFileItem);
|
||
const seen = new Set();
|
||
return [...fromMaterials, ...fromNotice, ...fromResult].filter((item) => {
|
||
const key = resolveFileUrl(item.fileUrl);
|
||
if (!key || seen.has(key)) return false;
|
||
seen.add(key);
|
||
return true;
|
||
});
|
||
};
|
||
|
||
const materialFileName = (m) =>
|
||
m.fileName || m.materialName || m.name || String(m.fileUrl || "").split("?")[0].split("/").pop() || "附件";
|
||
|
||
const ModalSection = ({ title, desc, children }) => (
|
||
<div className="inst-modal-section">
|
||
<h4>{title}</h4>
|
||
{desc ? <p>{desc}</p> : null}
|
||
{children}
|
||
</div>
|
||
);
|
||
|
||
const InstitutionInspectionNotice = (props) => {
|
||
const [searchForm] = Form.useForm();
|
||
const [processForm] = Form.useForm();
|
||
|
||
const [dataSource, setDataSource] = useState([]);
|
||
const [total, setTotal] = useState(0);
|
||
const [pageIndex, setPageIndex] = useState(1);
|
||
const [pageSize, setPageSize] = useState(10);
|
||
const [activeTab, setActiveTab] = useState("all");
|
||
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
||
const [metrics, setMetrics] = useState({
|
||
total: 0,
|
||
pending: 0,
|
||
feedback: 0,
|
||
closed: 0,
|
||
});
|
||
|
||
const [processOpen, setProcessOpen] = useState(false);
|
||
const [processViewOnly, setProcessViewOnly] = useState(false);
|
||
const [detail, setDetail] = useState(null);
|
||
const [materials, setMaterials] = useState([]);
|
||
const [uploadUrls, setUploadUrls] = useState([]);
|
||
const [exportOpen, setExportOpen] = useState(false);
|
||
const [exportLoading, setExportLoading] = useState(false);
|
||
|
||
const {
|
||
institutionInspNoticePageLoading,
|
||
institutionInspNoticeGetLoading,
|
||
institutionInspNoticeConfirmLoading,
|
||
institutionInspNoticeFeedbackLoading,
|
||
institutionInspNoticeMaterialUploadLoading,
|
||
} = props;
|
||
|
||
const buildQuery = (tab = activeTab) => {
|
||
const values = searchForm.getFieldsValue();
|
||
const tabStatus = TAB_STATUS[tab];
|
||
return {
|
||
keyword: values.keyword || undefined,
|
||
inspTypeCode: values.inspTypeCode || undefined,
|
||
statusCode: values.statusCode ?? tabStatus,
|
||
};
|
||
};
|
||
|
||
const fetchMetrics = async () => {
|
||
try {
|
||
const [allRes, pendingRes, feedbackRes, closedRes] = await Promise.all([
|
||
props.institutionInspNoticePage({ current: 1, size: 1 }),
|
||
props.institutionInspNoticePage({ current: 1, size: 1, statusCode: 1 }),
|
||
props.institutionInspNoticePage({ current: 1, size: 1, statusCode: 3 }),
|
||
props.institutionInspNoticePage({ current: 1, size: 1, statusCode: 4 }),
|
||
]);
|
||
setMetrics({
|
||
total: allRes?.total || 0,
|
||
pending: pendingRes?.total || 0,
|
||
feedback: feedbackRes?.total || 0,
|
||
closed: closedRes?.total || 0,
|
||
});
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
};
|
||
|
||
const fetchData = async (page = pageIndex, size = pageSize, tab = activeTab) => {
|
||
const res = await props.institutionInspNoticePage({
|
||
...buildQuery(tab),
|
||
current: page,
|
||
size,
|
||
});
|
||
if (res?.success !== false) {
|
||
setDataSource(res?.data || []);
|
||
setTotal(res?.total || 0);
|
||
setPageIndex(page);
|
||
setPageSize(size);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
fetchData(1, 10, "all");
|
||
fetchMetrics();
|
||
}, []);
|
||
|
||
const switchTab = (key) => {
|
||
setActiveTab(key);
|
||
searchForm.setFieldsValue({ statusCode: undefined });
|
||
fetchData(1, pageSize, key);
|
||
};
|
||
|
||
const openProcess = async (record, viewOnly = false) => {
|
||
setProcessViewOnly(viewOnly);
|
||
setProcessOpen(true);
|
||
setDetail(null);
|
||
setMaterials([]);
|
||
setUploadUrls([]);
|
||
processForm.resetFields();
|
||
processForm.setFieldsValue({
|
||
noticeNo: record.noticeNo,
|
||
processType: "提交整改材料",
|
||
processRemark: "请根据监管要求填写问题原因、处理措施和完成情况。",
|
||
});
|
||
const [detailRes, matRes] = await Promise.all([
|
||
props.institutionInspNoticeGet({ id: String(record.id) }),
|
||
props.institutionInspNoticeMaterials({ noticeId: toId(record.id) }),
|
||
]);
|
||
if (detailRes?.success !== false) {
|
||
const data = detailRes?.data || null;
|
||
setDetail(data);
|
||
processForm.setFieldsValue({ noticeNo: data?.noticeNo || record.noticeNo });
|
||
if (data?.statusCode === 4) {
|
||
processForm.setFieldsValue({ processType: "查看办结结果" });
|
||
}
|
||
} else {
|
||
|
||
}
|
||
if (matRes?.success !== false) {
|
||
setMaterials(matRes?.data || []);
|
||
}
|
||
};
|
||
|
||
const onFileUpload = (info) => {
|
||
if (info.file.status === "done") {
|
||
const responseData = info.file.response?.data || info.file.response || {};
|
||
const fileUrl = responseData?.url || responseData?.fileUrl || "";
|
||
if (!fileUrl) {
|
||
message.error("上传成功但未返回文件地址");
|
||
return;
|
||
}
|
||
setUploadUrls((prev) => [...prev, fileUrl]);
|
||
} else if (info.file.status === "error") {
|
||
message.error("上传失败");
|
||
}
|
||
};
|
||
|
||
const handleSubmit = async () => {
|
||
if (!detail?.id) return;
|
||
const values = await processForm.validateFields();
|
||
|
||
if (values.processType === "查看办结结果" || detail.statusCode === 4) {
|
||
setProcessOpen(false);
|
||
return;
|
||
}
|
||
|
||
let current = detail;
|
||
if (current.statusCode === 1) {
|
||
const confirmRes = await props.institutionInspNoticeConfirm({
|
||
id: toId(current.id),
|
||
});
|
||
if (confirmRes?.success === false) {
|
||
|
||
return;
|
||
}
|
||
current = confirmRes?.data || { ...current, statusCode: 2 };
|
||
}
|
||
|
||
const pendingMaterials = materials.filter(
|
||
(m) => m.uploadStatusCode !== 2 && !m.fileUrl,
|
||
);
|
||
const urls = [...uploadUrls];
|
||
for (let i = 0; i < Math.min(urls.length, pendingMaterials.length); i += 1) {
|
||
const mat = pendingMaterials[i];
|
||
const upRes = await props.institutionInspNoticeMaterialUpload({
|
||
materialId: toId(mat.id),
|
||
fileUrl: urls[i],
|
||
});
|
||
if (upRes?.success === false) {
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (current.statusCode === 2 || detail.statusCode === 2 || detail.statusCode === 1) {
|
||
const matRes = await props.institutionInspNoticeMaterials({
|
||
noticeId: toId(detail.id),
|
||
});
|
||
const latest = matRes?.data || materials;
|
||
const feedbackRes = await props.institutionInspNoticeFeedback({
|
||
id: toId(detail.id),
|
||
materials: latest
|
||
.filter((m) => m.fileUrl || urls.length)
|
||
.map((m, idx) => ({
|
||
materialId: toId(m.id),
|
||
fileUrl: m.fileUrl || urls[idx] || urls[0],
|
||
}))
|
||
.filter((m) => m.fileUrl),
|
||
});
|
||
if (feedbackRes?.success === false) {
|
||
|
||
return;
|
||
}
|
||
}
|
||
|
||
message.success("处理材料已提交监管复核");
|
||
setProcessOpen(false);
|
||
fetchData(pageIndex, pageSize);
|
||
fetchMetrics();
|
||
};
|
||
|
||
const handleExport = async () => {
|
||
setExportLoading(true);
|
||
try {
|
||
const query = buildQuery();
|
||
const hasSelected = selectedRowKeys.length > 0;
|
||
const body = {
|
||
...query,
|
||
scope: hasSelected ? "SELECTED" : "FILTERED",
|
||
ids: hasSelected ? selectedRowKeys.map(toId) : undefined,
|
||
};
|
||
await downloadBinaryPost(
|
||
"/safetyEval/institution/insp-notice/export-messages",
|
||
body,
|
||
"监管消息清单.csv",
|
||
);
|
||
message.success("监管消息清单已导出");
|
||
setExportOpen(false);
|
||
} catch (e) {
|
||
message.error(e.message || "导出失败");
|
||
} finally {
|
||
setExportLoading(false);
|
||
}
|
||
};
|
||
|
||
const columns = [
|
||
{
|
||
title: "通知编号",
|
||
dataIndex: "noticeNo",
|
||
width: 140,
|
||
render: (t) => <span className="inst-mono">{t || "-"}</span>,
|
||
},
|
||
{
|
||
title: "通知类型",
|
||
dataIndex: "inspTypeName",
|
||
width: 110,
|
||
render: (_, record) => (
|
||
<span className={`inst-tag ${noticeTypeTagClass(record)}`}>
|
||
{noticeTypeLabel(record)}
|
||
</span>
|
||
),
|
||
},
|
||
{
|
||
title: "来源预警",
|
||
dataIndex: "sourceRefNo",
|
||
width: 140,
|
||
render: (t) => t || "-",
|
||
},
|
||
{
|
||
title: "来源字段",
|
||
key: "sourceFields",
|
||
width: 180,
|
||
render: (_, record) => (
|
||
<div className="inst-source-list">
|
||
{record.sourceTypeName ? (
|
||
<span className="inst-source-pill">{record.sourceTypeName}</span>
|
||
) : (
|
||
<span className="inst-source-pill">监督检查</span>
|
||
)}
|
||
{record.inspTypeName ? (
|
||
<span className="inst-source-pill">{record.inspTypeName}</span>
|
||
) : null}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
title: "监管要求",
|
||
dataIndex: "noticeContent",
|
||
ellipsis: true,
|
||
width: 240,
|
||
},
|
||
{
|
||
title: "办理期限",
|
||
dataIndex: "planInspTime",
|
||
width: 120,
|
||
render: formatDate,
|
||
},
|
||
{
|
||
title: "状态",
|
||
dataIndex: "statusCode",
|
||
width: 110,
|
||
render: (code, record) => (
|
||
<span className={`inst-tag ${statusTagClass(code)}`}>
|
||
{statusDisplayName(code, record.statusName)}
|
||
</span>
|
||
),
|
||
},
|
||
{
|
||
title: "操作",
|
||
key: "action",
|
||
width: 90,
|
||
align: "center",
|
||
fixed: "right",
|
||
render: (_, record) =>
|
||
record.statusCode === 1 ? (
|
||
<Button type="primary" size="small" onClick={() => openProcess(record)}>
|
||
处理
|
||
</Button>
|
||
) : (
|
||
<Button type="primary" size="small" onClick={() => openProcess(record, true)}>
|
||
查看
|
||
</Button>
|
||
),
|
||
},
|
||
];
|
||
|
||
const submitting =
|
||
institutionInspNoticeConfirmLoading ||
|
||
institutionInspNoticeFeedbackLoading ||
|
||
institutionInspNoticeMaterialUploadLoading;
|
||
|
||
return (
|
||
<ConfigProvider theme={PROTO_THEME}>
|
||
<PageLayout title="监管通知管理">
|
||
<div className="inst-page">
|
||
<div className="inst-page-header">
|
||
<div className="inst-sub">
|
||
接收监管端下发的整改通知、监督检查通知、核查函及其他通知文件,机构在线反馈结果、上传证明材料并查看监管复核意见。
|
||
</div>
|
||
<div className="inst-tabs">
|
||
{TAB_ITEMS.map((tab) => (
|
||
<button
|
||
key={tab.key}
|
||
type="button"
|
||
className={`inst-tab${activeTab === tab.key ? " active" : ""}`}
|
||
onClick={() => switchTab(tab.key)}
|
||
>
|
||
{tab.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="inst-metrics">
|
||
<div className="inst-metric-card">
|
||
<div>
|
||
<div className="label">监管消息总数</div>
|
||
<div className="value">{metrics.total}</div>
|
||
<div className="hint">来源:监管端风险预警中心</div>
|
||
</div>
|
||
<div className="icon">✉️</div>
|
||
</div>
|
||
<div className="inst-metric-card">
|
||
<div>
|
||
<div className="label">待处理</div>
|
||
<div className="value danger">{metrics.pending}</div>
|
||
<div className="hint">需机构直接处理</div>
|
||
</div>
|
||
<div className="icon">待</div>
|
||
</div>
|
||
<div className="inst-metric-card">
|
||
<div>
|
||
<div className="label">待监管复核</div>
|
||
<div className="value warning">{metrics.feedback}</div>
|
||
<div className="hint">材料已提交</div>
|
||
</div>
|
||
<div className="icon">审</div>
|
||
</div>
|
||
<div className="inst-metric-card">
|
||
<div>
|
||
<div className="label">已办结</div>
|
||
<div className="value success">{metrics.closed}</div>
|
||
<div className="hint">办结消息可归档</div>
|
||
</div>
|
||
<div className="icon">结</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="inst-note">
|
||
<strong>消息来源:</strong>
|
||
监管端风险预警中心。监管人员在风险预警中心发出整改通知、核查函、补正通知或办结结果后,机构端在此接收并处理;资质保持监控页面只作为监管信息来源。
|
||
</div>
|
||
|
||
<SearchForm
|
||
form={searchForm}
|
||
loading={institutionInspNoticePageLoading}
|
||
style={{ marginBottom: 16 }}
|
||
formLine={[
|
||
<Form.Item key="keyword" name="keyword">
|
||
<ControlWrapper.Input
|
||
label="通知关键词"
|
||
placeholder="通知编号/预警编号/事项"
|
||
allowClear
|
||
/>
|
||
</Form.Item>,
|
||
<Form.Item key="inspTypeCode" name="inspTypeCode">
|
||
<ControlWrapper.Select
|
||
label="通知类型"
|
||
placeholder="全部"
|
||
allowClear
|
||
options={INSP_TYPE_OPTIONS}
|
||
/>
|
||
</Form.Item>,
|
||
<Form.Item key="statusCode" name="statusCode">
|
||
<ControlWrapper.Select
|
||
label="处理状态"
|
||
placeholder="全部"
|
||
allowClear
|
||
options={STATUS_FILTER_OPTIONS}
|
||
/>
|
||
</Form.Item>,
|
||
]}
|
||
onFinish={() => fetchData(1, pageSize)}
|
||
onReset={() => {
|
||
searchForm.resetFields();
|
||
setActiveTab("all");
|
||
fetchData(1, pageSize, "all");
|
||
}}
|
||
/>
|
||
|
||
<div className="inst-toolbar">
|
||
<div className="inst-toolbar-left">
|
||
<span className="inst-hint">
|
||
监管消息列表:展示来源预警、来源字段、办理期限和机构处理状态。
|
||
</span>
|
||
</div>
|
||
<div className="inst-toolbar-right">
|
||
<Button size="small" onClick={() => setExportOpen(true)}>
|
||
导出消息
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<Table
|
||
rowKey="id"
|
||
columns={columns}
|
||
dataSource={dataSource}
|
||
loading={institutionInspNoticePageLoading}
|
||
scroll={{ y: props.scrollY, x: 1200 }}
|
||
rowSelection={{
|
||
selectedRowKeys,
|
||
onChange: setSelectedRowKeys,
|
||
columnWidth: 34,
|
||
}}
|
||
pagination={{
|
||
total,
|
||
current: pageIndex,
|
||
pageSize,
|
||
showSizeChanger: true,
|
||
showQuickJumper: true,
|
||
showTotal: (t) => `共 ${t} 条`,
|
||
}}
|
||
onChange={(pag) => fetchData(pag.current, pag.pageSize)}
|
||
/>
|
||
</div>
|
||
|
||
{/* 处理 — 对齐原型 regMessageProcess */}
|
||
<Modal
|
||
title={
|
||
detail
|
||
? `${processViewOnly ? "监管消息查看" : "监管消息处理"} - ${detail.noticeNo}`
|
||
: processViewOnly
|
||
? "监管消息查看"
|
||
: "监管消息处理"
|
||
}
|
||
open={processOpen}
|
||
onCancel={() => setProcessOpen(false)}
|
||
width={820}
|
||
destroyOnHidden
|
||
className="inst-modal"
|
||
footer={
|
||
processViewOnly ? (
|
||
<Button onClick={() => setProcessOpen(false)}>关闭</Button>
|
||
) : (
|
||
<Button type="primary" loading={submitting} onClick={handleSubmit}>
|
||
提交
|
||
</Button>
|
||
)
|
||
}
|
||
>
|
||
{institutionInspNoticeGetLoading || !detail ? (
|
||
<div className="inst-loading">加载中...</div>
|
||
) : (
|
||
<>
|
||
<ModalSection
|
||
title="处理要求"
|
||
desc="机构收到消息后直接填写说明并上传证明材料,完成后提交监管复核。"
|
||
/>
|
||
<Form
|
||
form={processForm}
|
||
layout="vertical"
|
||
preserve={false}
|
||
scrollToFirstError
|
||
>
|
||
<div className="inst-form-grid">
|
||
<Form.Item name="noticeNo" label="关联消息编号">
|
||
<Input readOnly />
|
||
</Form.Item>
|
||
<Form.Item name="processType" label="处理方式">
|
||
<Select options={PROCESS_TYPE_OPTIONS} disabled={processViewOnly} />
|
||
</Form.Item>
|
||
<Form.Item
|
||
name="processRemark"
|
||
label="处理说明"
|
||
className="inst-form-full"
|
||
>
|
||
<TextArea rows={3} disabled={processViewOnly} />
|
||
</Form.Item>
|
||
<Form.Item
|
||
label={processViewOnly ? "附件" : "上传附件"}
|
||
className="inst-form-full"
|
||
>
|
||
{processViewOnly ? (
|
||
(() => {
|
||
const viewFiles = collectViewFiles(detail, materials);
|
||
return viewFiles.length ? (
|
||
viewFiles.map((m) => (
|
||
<a
|
||
key={m.id || m.fileUrl}
|
||
onClick={() =>
|
||
window.open(resolveFileUrl(m.fileUrl), "_blank")
|
||
}
|
||
style={{
|
||
display: "block",
|
||
color: "#1677ff",
|
||
cursor: "pointer",
|
||
}}
|
||
>
|
||
{materialFileName(m)}
|
||
</a>
|
||
))
|
||
) : (
|
||
<span>暂无附件</span>
|
||
);
|
||
})()
|
||
) : (
|
||
<>
|
||
<Upload
|
||
name="file"
|
||
action={UPLOAD_ACTION}
|
||
headers={{
|
||
token: sessionStorage.getItem("token") || "",
|
||
}}
|
||
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png"
|
||
multiple
|
||
onChange={onFileUpload}
|
||
>
|
||
<Button icon={<UploadOutlined />}>选择文件</Button>
|
||
</Upload>
|
||
<div className="inst-upload-tip">
|
||
支持 PDF、Word、Excel、图片,可选择多个文件。
|
||
</div>
|
||
</>
|
||
)}
|
||
</Form.Item>
|
||
</div>
|
||
</Form>
|
||
</>
|
||
)}
|
||
</Modal>
|
||
|
||
{/* 导出 — 对齐原型 regMessageExport */}
|
||
<Modal
|
||
title="监管消息清单"
|
||
open={exportOpen}
|
||
onCancel={() => setExportOpen(false)}
|
||
width={560}
|
||
destroyOnHidden
|
||
className="inst-modal"
|
||
footer={
|
||
<Button type="primary" loading={exportLoading} onClick={handleExport}>
|
||
确认导出
|
||
</Button>
|
||
}
|
||
>
|
||
<ModalSection
|
||
title="导出内容"
|
||
desc="包含消息编号、来源预警、来源字段、监管要求、办理期限、处理状态和办结结果。"
|
||
/>
|
||
</Modal>
|
||
</PageLayout>
|
||
</ConfigProvider>
|
||
);
|
||
};
|
||
|
||
export default Connect(
|
||
[NS_INSP_NOTICE],
|
||
true,
|
||
)(AntdTableFuncControl(InstitutionInspectionNotice));
|