bug
parent
96a70d39c3
commit
8e35c0f289
1928
pnpm-lock.yaml
1928
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -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,11 +491,16 @@ const InstitutionInspectionNotice = (props) => {
|
|||
width: 90,
|
||||
align: "center",
|
||||
fixed: "right",
|
||||
render: (_, record) => (
|
||||
<Button type="primary" size="small" onClick={() => openProcess(record)}>
|
||||
处理
|
||||
</Button>
|
||||
),
|
||||
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={
|
||||
<Button type="primary" loading={submitting} onClick={handleSubmit}>
|
||||
提交
|
||||
</Button>
|
||||
processViewOnly ? (
|
||||
<Button onClick={() => setProcessOpen(false)}>关闭</Button>
|
||||
) : (
|
||||
<Button type="primary" loading={submitting} onClick={handleSubmit}>
|
||||
提交
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
>
|
||||
{institutionInspNoticeGetLoading || !detail ? (
|
||||
|
|
@ -613,35 +676,72 @@ 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">
|
||||
<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
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -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%" }} />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue