dev-tmp1
zhanglei 2026-08-17 14:17:45 +08:00
parent 96a70d39c3
commit 8e35c0f289
4 changed files with 1262 additions and 1016 deletions

File diff suppressed because it is too large Load Diff

View File

@ -76,12 +76,11 @@ const PROTO_THEME = {
token: { borderRadius: 8, borderRadiusLG: 12, borderRadiusSM: 6 },
};
/** 原型通知类型筛选项 */
const MSG_TYPE_OPTIONS = [
{ label: "整改通知", value: "整改通知" },
{ label: "核查函", value: "核查函" },
{ label: "补正通知", value: "补正通知" },
{ label: "办结通知", value: "办结通知" },
/** 通知类型与监管端监督检查告知一致 */
const INSP_TYPE_OPTIONS = [
{ label: "项目过程检查", value: "PROJECT_PROCESS" },
{ label: "资质保持检查", value: "QUAL_KEEP" },
{ label: "专项检查", value: "SPECIAL" },
];
const TAB_STATUS = {
@ -149,6 +148,53 @@ const formatDate = (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>
@ -175,6 +221,7 @@ const InstitutionInspectionNotice = (props) => {
});
const [processOpen, setProcessOpen] = useState(false);
const [processViewOnly, setProcessViewOnly] = useState(false);
const [detail, setDetail] = useState(null);
const [materials, setMaterials] = useState([]);
const [uploadUrls, setUploadUrls] = useState([]);
@ -192,9 +239,9 @@ const InstitutionInspectionNotice = (props) => {
const buildQuery = (tab = activeTab) => {
const values = searchForm.getFieldsValue();
const tabStatus = TAB_STATUS[tab];
const keywordParts = [values.keyword, values.msgType].filter(Boolean);
return {
keyword: keywordParts.length ? keywordParts.join(" ") : undefined,
keyword: values.keyword || undefined,
inspTypeCode: values.inspTypeCode || undefined,
statusCode: values.statusCode ?? tabStatus,
};
};
@ -243,7 +290,8 @@ const InstitutionInspectionNotice = (props) => {
fetchData(1, pageSize, key);
};
const openProcess = async (record) => {
const openProcess = async (record, viewOnly = false) => {
setProcessViewOnly(viewOnly);
setProcessOpen(true);
setDetail(null);
setMaterials([]);
@ -443,10 +491,15 @@ const InstitutionInspectionNotice = (props) => {
width: 90,
align: "center",
fixed: "right",
render: (_, record) => (
render: (_, record) =>
record.statusCode === 1 ? (
<Button type="primary" size="small" onClick={() => openProcess(record)}>
处理
</Button>
) : (
<Button type="primary" size="small" onClick={() => openProcess(record, true)}>
查看
</Button>
),
},
];
@ -530,12 +583,12 @@ const InstitutionInspectionNotice = (props) => {
allowClear
/>
</Form.Item>,
<Form.Item key="msgType" name="msgType">
<Form.Item key="inspTypeCode" name="inspTypeCode">
<ControlWrapper.Select
label="通知类型"
placeholder="全部"
allowClear
options={MSG_TYPE_OPTIONS}
options={INSP_TYPE_OPTIONS}
/>
</Form.Item>,
<Form.Item key="statusCode" name="statusCode">
@ -593,16 +646,26 @@ const InstitutionInspectionNotice = (props) => {
{/* 处理 — 对齐原型 regMessageProcess */}
<Modal
title={detail ? `监管消息处理 - ${detail.noticeNo}` : "监管消息处理"}
title={
detail
? `${processViewOnly ? "监管消息查看" : "监管消息处理"} - ${detail.noticeNo}`
: processViewOnly
? "监管消息查看"
: "监管消息处理"
}
open={processOpen}
onCancel={() => setProcessOpen(false)}
width={820}
destroyOnClose
className="inst-modal"
footer={
processViewOnly ? (
<Button onClick={() => setProcessOpen(false)}>关闭</Button>
) : (
<Button type="primary" loading={submitting} onClick={handleSubmit}>
提交
</Button>
)
}
>
{institutionInspNoticeGetLoading || !detail ? (
@ -613,26 +676,61 @@ const InstitutionInspectionNotice = (props) => {
title="处理要求"
desc="机构收到消息后直接填写说明并上传证明材料,完成后提交监管复核。"
/>
<Form form={processForm} layout="vertical" preserve={false} scrollToFirstError>
<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} />
<Select options={PROCESS_TYPE_OPTIONS} disabled={processViewOnly} />
</Form.Item>
<Form.Item
name="processRemark"
label="处理说明"
className="inst-form-full"
>
<TextArea rows={3} />
<TextArea rows={3} disabled={processViewOnly} />
</Form.Item>
<Form.Item label="上传附件" className="inst-form-full">
<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") || "" }}
headers={{
token: sessionStorage.getItem("token") || "",
}}
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png"
multiple
onChange={onFileUpload}
@ -642,6 +740,8 @@ const InstitutionInspectionNotice = (props) => {
<div className="inst-upload-tip">
支持 PDFWordExcel图片可选择多个文件
</div>
</>
)}
</Form.Item>
</div>
</Form>

View File

@ -67,6 +67,13 @@ const RESULT_OPTIONS = [
{ label: "转入风险预警", value: 3 },
];
const PROCESS_TYPE_OPTIONS = [
{ label: "提交整改材料", value: "提交整改材料" },
{ label: "提交核查说明", value: "提交核查说明" },
{ label: "补充证明材料", value: "补充证明材料" },
{ label: "查看办结结果", value: "查看办结结果" },
];
const SOURCE_WARN_OPTIONS = [
{ label: "无", value: "" },
{ label: "W-PROJ-2026-021", value: "W-PROJ-2026-021" },
@ -92,9 +99,25 @@ const formatDateTime = (val) => {
return d.isValid() ? d.format("YYYY-MM-DD HH:mm") : 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 materialFileName = (m) =>
m.fileName || m.materialName || m.name || String(m.fileUrl || "").split("/").pop() || "附件";
/** 雪花 ID 须保持字符串,避免 Number() 精度丢失 */
const toId = (id) => (id == null ? id : String(id));
const urlToFileItem = (url) => ({
fileUrl: url,
fileName: String(url).split("?")[0].split("/").pop() || "附件",
});
const parseUrlList = (val) => {
if (!val) return [];
if (Array.isArray(val)) return val.filter(Boolean);
@ -113,6 +136,19 @@ const parseUrlList = (val) => {
return [];
};
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 downloadBinary = async (method, path, bodyOrQuery, fallbackName) => {
const headers = { token: sessionStorage.getItem("token") || "" };
let url = `${API_HOST}${path}`;
@ -190,6 +226,7 @@ const InspectionNotice = (props) => {
const [resultForm] = Form.useForm();
const [remarkForm] = Form.useForm();
const [exportForm] = Form.useForm();
const [processViewForm] = Form.useForm();
const [dataSource, setDataSource] = useState([]);
const [total, setTotal] = useState(0);
@ -204,6 +241,9 @@ const InspectionNotice = (props) => {
const [detailOpen, setDetailOpen] = useState(false);
const [detail, setDetail] = useState(null);
const [processViewOpen, setProcessViewOpen] = useState(false);
const [processViewDetail, setProcessViewDetail] = useState(null);
const [processViewMaterials, setProcessViewMaterials] = useState([]);
const [resultOpen, setResultOpen] = useState(false);
const [resultRecord, setResultRecord] = useState(null);
@ -368,6 +408,34 @@ const InspectionNotice = (props) => {
}
};
const openProcessView = async (record) => {
setProcessViewOpen(true);
setProcessViewDetail(null);
setProcessViewMaterials([]);
processViewForm.resetFields();
processViewForm.setFieldsValue({
noticeNo: record.noticeNo,
processType: "提交整改材料",
processRemark: "请根据监管要求填写问题原因、处理措施和完成情况。",
});
const [detailRes, matRes] = await Promise.all([
props.regulatorInspNoticeGet({ id: String(record.id) }),
props.regulatorInspNoticeMaterials({ noticeId: toId(record.id) }),
]);
if (detailRes?.success !== false) {
const data = detailRes?.data || null;
setProcessViewDetail(data);
processViewForm.setFieldsValue({
noticeNo: data?.noticeNo || record.noticeNo,
processType:
data?.statusCode === 4 ? "查看办结结果" : "提交整改材料",
});
}
if (matRes?.success !== false) {
setProcessViewMaterials(matRes?.data || []);
}
};
const openDetail = async (record) => {
setDetailOpen(true);
setDetail(null);
@ -514,7 +582,6 @@ const InspectionNotice = (props) => {
</div>
),
},
{ title: "检查负责人", dataIndex: "leaderName", width: 100 },
{
title: "机构确认",
dataIndex: "confirmFlag",
@ -547,9 +614,15 @@ const InspectionNotice = (props) => {
title: "操作",
key: "action",
fixed: "right",
width: 180,
width: 240,
render: (_, record) => {
const code = record.statusCode;
const processViewBtn =
record.confirmFlag === 1 ? (
<Button size="small" onClick={() => openProcessView(record)}>
处理查看
</Button>
) : null;
if (code === 0) {
return (
<span className="insp-actions">
@ -559,6 +632,7 @@ const InspectionNotice = (props) => {
<Button size="small" onClick={() => handleIssueRow(record)}>
下发
</Button>
{processViewBtn}
</span>
);
}
@ -571,6 +645,7 @@ const InspectionNotice = (props) => {
<Button size="small" onClick={() => handleUrge(record)}>
提醒
</Button>
{processViewBtn}
</span>
);
}
@ -583,6 +658,7 @@ const InspectionNotice = (props) => {
<Button size="small" onClick={() => openResult(record)}>
登记结果
</Button>
{processViewBtn}
</span>
);
}
@ -594,6 +670,7 @@ const InspectionNotice = (props) => {
<Button size="small" onClick={() => handleDownloadRecord(record)}>
下载记录
</Button>
{processViewBtn}
</span>
);
},
@ -885,6 +962,81 @@ const InspectionNotice = (props) => {
)}
</Modal>
{/* 处理查看 — 与机构端监管消息查看一致 */}
<Modal
title={
processViewDetail
? `监管消息查看 - ${processViewDetail.noticeNo}`
: "监管消息查看"
}
open={processViewOpen}
onCancel={() => setProcessViewOpen(false)}
width={820}
destroyOnClose
className="insp-modal"
footer={<Button onClick={() => setProcessViewOpen(false)}>关闭</Button>}
>
{regulatorInspNoticeGetLoading || !processViewDetail ? (
<div className="insp-loading">加载中...</div>
) : (
<>
<ModalSection
title="处理要求"
desc="机构收到消息后直接填写说明并上传证明材料,完成后提交监管复核。"
/>
<Form
form={processViewForm}
layout="vertical"
preserve={false}
scrollToFirstError
>
<div className="insp-form-grid">
<Form.Item name="noticeNo" label="关联消息编号">
<Input readOnly />
</Form.Item>
<Form.Item name="processType" label="处理方式">
<Select options={PROCESS_TYPE_OPTIONS} disabled />
</Form.Item>
<Form.Item
name="processRemark"
label="处理说明"
className="insp-form-full"
>
<TextArea rows={3} disabled />
</Form.Item>
<Form.Item label="附件" className="insp-form-full">
{(() => {
const viewFiles = collectViewFiles(
processViewDetail,
processViewMaterials,
);
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>
);
})()}
</Form.Item>
</div>
</Form>
</>
)}
</Modal>
{/* 登记结果 — 对齐原型 inspectionResult */}
<Modal
title={
@ -924,10 +1076,20 @@ const InspectionNotice = (props) => {
<Select options={RESULT_OPTIONS} />
</Form.Item>
<Form.Item name="resultSituation" label="检查情况" className="insp-form-full">
<TextArea rows={3} placeholder="填写现场检查情况和发现问题" />
<TextArea
rows={3}
maxLength={500}
showCount
placeholder="填写现场检查情况和发现问题"
/>
</Form.Item>
<Form.Item name="rectifyRequire" label="整改要求" className="insp-form-full">
<TextArea rows={2} placeholder="需要整改时填写整改事项和期限" />
<TextArea
rows={2}
maxLength={500}
showCount
placeholder="需要整改时填写整改事项和期限"
/>
</Form.Item>
<Form.Item name="rectifyDeadline" label="整改期限">
<DatePicker style={{ width: "100%" }} />

View File

@ -173,6 +173,12 @@
grid-column: 1 / -1;
}
.insp-upload-tip {
font-size: 14px;
color: #64748b;
margin-top: 4.2px;
}
.insp-actions {
display: inline-flex;
gap: 6px;