import React, { useState, useEffect } from "react"; import { Form, Table, Button, Modal, Input, Select, InputNumber, Row, Col, message, Descriptions, Upload, DatePicker, Space, ConfigProvider, Tag, Tooltip, } from "antd"; import { PlusOutlined, UploadOutlined, SendOutlined, } from "@ant-design/icons"; import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout"; import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm"; import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper"; import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd"; import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace"; import { Connect } from "@cqsjjb/jjb-dva-runtime"; import { tools } from "@cqsjjb/jjb-common-lib"; import dayjs from "dayjs"; import "./index.less"; const { router } = tools; const { TextArea } = Input; const UPLOAD_ACTION = `${window.process?.env?.app?.API_HOST || ""}/safetyEval/file/upload`; /** 评价类型(对齐原型文案;编码对齐 OpenAPI PRE/ACCEPT/STATUS) */ const REPORT_EVAL_TYPE_OPTIONS = [ { label: "安全预评价", value: "PRE" }, { label: "安全验收评价", value: "ACCEPT" }, { label: "安全现状评价", value: "STATUS" }, ]; /** 所属行业(原型选项;编码仅 OpenAPI 示例给出 HAZCHEM,其余按命名约定) */ const REPORT_INDUSTRY_OPTIONS = [ { label: "危险化学品", value: "HAZCHEM" }, { label: "金属非金属矿山", value: "METAL_NONMETAL_MINE" }, { label: "金属冶炼", value: "METAL_SMELTING" }, { label: "烟花爆竹", value: "FIREWORKS" }, { label: "城镇燃气", value: "URBAN_GAS" }, { label: "工贸", value: "INDUSTRY_TRADE" }, { label: "其他", value: "OTHER" }, ]; /** 保密属性(OpenAPI 仅示例 NORMAL) */ const SECRET_LEVEL_OPTIONS = [ { label: "普通", value: "NORMAL" }, { label: "含商业秘密", value: "TRADE_SECRET" }, { label: "涉个人隐私", value: "PERSONAL_PRIVACY" }, { label: "涉密资料", value: "CLASSIFIED" }, ]; /** 报送状态筛选(对齐原型 + OpenAPI displayStatus) */ const DISPLAY_STATUS_OPTIONS = [ { label: "未报送", value: "UN_SUBMITTED" }, { label: "已报送", value: "SUBMITTED" }, { label: "抽查中", value: "SPOTCHECKING" }, { label: "整改中", value: "RECTIFYING" }, ]; /** 对齐原型 .tag / .tag-*(胶囊圆角 + 色值) */ const DISPLAY_STATUS_CLASS = { 未报送: "erl-tag-warning", 已报送: "erl-tag-success", 抽查中: "erl-tag-info", 整改中: "erl-tag-danger", 已复核: "erl-tag-success", }; /** 原型 CSS 变量:--radius 8px / --radius-lg 12px */ const PROTO_THEME = { token: { borderRadius: 8, borderRadiusLG: 12, borderRadiusSM: 6, }, }; const getYearOptions = () => { const year = dayjs().year(); return [0, 1, 2, 3].map((offset) => { const y = year - offset; return { label: String(y), value: y }; }); }; const getFileExt = (name = "") => { const parts = String(name).split("."); return parts.length > 1 ? parts.pop().toUpperCase() : "PDF"; }; /** 解析 JSON 文件列表,渲染为可下载链接列表 */ const renderFileLinks = (jsonStr) => { if (!jsonStr) return "-"; try { const files = JSON.parse(jsonStr); if (!Array.isArray(files) || files.length === 0) return "-"; return files.map((f, i) => (
{getFileExt(f.name || f.url)} {f.name || "未命名文件"}
)); } catch { return jsonStr; } }; const getOptionLabel = (options, value) => options.find((item) => item.value === value)?.label || value || "-"; const isUnsubmitted = (record) => record?.submitFlag === 2 || record?.displayStatusName === "未报送" || record?.displayStatus === "UN_SUBMITTED"; const isSpotcheckRelated = (record) => { const name = record?.displayStatusName || ""; return ["抽查中", "整改中", "已复核"].includes(name); }; const EvalReportLibrary = (props) => { const [searchForm] = Form.useForm(); const [uploadForm] = Form.useForm(); const [rectifyForm] = Form.useForm(); const [dataSource, setDataSource] = useState([]); const [total, setTotal] = useState(0); const [statData, setStatData] = useState({}); const [addVisible, setAddVisible] = useState(false); const [detailVisible, setDetailVisible] = useState(false); const [detailData, setDetailData] = useState(null); const [spotcheckVisible, setSpotcheckVisible] = useState(false); const [spotcheckData, setSpotcheckData] = useState([]); const [currentReport, setCurrentReport] = useState(null); const [rectifyVisible, setRectifyVisible] = useState(false); const [rectifyRecord, setRectifyRecord] = useState(null); const [uploadedFile, setUploadedFile] = useState(null); const [contractFiles, setContractFiles] = useState([]); const [processDocFiles, setProcessDocFiles] = useState([]); const { evalReportPageLoading, evalReportAddLoading, evalReportDetailLoading, evalReportSpotcheckListLoading, evalReportSpotcheckRectifyLoading, } = props.safetyEvalBusiness; const fetchSummary = async () => { const res = await props.evalReportSummary(); if (res?.success !== false) { setStatData(res?.data || {}); } }; const getData = async (pagination) => { const params = { ...router.query, current: pagination?.current || router.query.current || 1, size: pagination?.size || router.query.size || 10, }; const res = await props.evalReportPage(params); if (res?.success !== false) { setDataSource(res?.data || []); setTotal(res?.total || 0); } }; useEffect(() => { searchForm.setFieldsValue(router.query); fetchSummary(); getData(); }, []); const handleSearch = (values) => { router.query = { ...router.query, ...values, current: 1, size: 10 }; getData(); }; const handleReset = (values) => { searchForm.resetFields(); router.query = { ...values, current: 1, size: 10 }; getData(); }; const handlePageChange = (pagination) => { router.query = { ...router.query, current: pagination.current, size: pagination.pageSize, }; getData(pagination); }; const handleOpenAdd = () => { setUploadedFile(null); setContractFiles([]); setProcessDocFiles([]); uploadForm.resetFields(); uploadForm.setFieldsValue({ reportYear: dayjs().year(), evalTypeCode: "PRE", industryCode: "HAZCHEM", secretLevelCode: "NORMAL", issueDate: dayjs(), submitToRegulator: 0, }); setAddVisible(true); }; const handleFileUploadChange = (info) => { if (info.file.status === "uploading") return; if (info.file.status === "done") { const responseData = info.file.response?.data; const fileUrl = responseData?.url || responseData?.fileUrl || ""; const fileName = responseData?.name || responseData?.fileName || info.file.name; if (!fileUrl) { message.error("上传响应中未获取到文件地址"); return; } setUploadedFile({ url: fileUrl, name: fileName }); uploadForm.setFieldsValue({ fileUrl, fileName, reportName: uploadForm.getFieldValue("reportName") || fileName.replace(/\.[^.]+$/, ""), }); message.success(`${fileName} 上传成功`); } else if (info.file.status === "error") { message.error(`${info.file.name} 上传失败`); } }; // 合同文件上传 const handleContractUploadChange = (info) => { if (info.file.status === "error") { message.error(`${info.file.name} 上传失败`); return; } const doneFiles = info.fileList .filter((f) => f.status === "done" && f.response?.data) .map((f) => ({ uid: f.uid, url: f.response.data.url || f.response.data.fileUrl || "", name: f.response.data.name || f.response.data.fileName || f.name, })) .filter((f) => f.url); setContractFiles(doneFiles); }; // 其他过程文档上传 const handleProcessDocUploadChange = (info) => { if (info.file.status === "error") { message.error(`${info.file.name} 上传失败`); return; } const doneFiles = info.fileList .filter((f) => f.status === "done" && f.response?.data) .map((f) => ({ uid: f.uid, url: f.response.data.url || f.response.data.fileUrl || "", name: f.response.data.name || f.response.data.fileName || f.name, })) .filter((f) => f.url); setProcessDocFiles(doneFiles); }; const handleAddOk = async () => { const values = await uploadForm.validateFields().catch(() => null); if (!values) return; if (!values.fileUrl) { message.warning("请选择需要上传的正式报告文件"); return; } const payload = { reportNo: values.reportNo?.trim(), reportName: values.reportName?.trim(), reportYear: values.reportYear, evalTypeCode: values.evalTypeCode, evalTypeName: getOptionLabel(REPORT_EVAL_TYPE_OPTIONS, values.evalTypeCode), industryCode: values.industryCode, industryName: getOptionLabel(REPORT_INDUSTRY_OPTIONS, values.industryCode), evaluatedUnitName: values.evaluatedUnitName?.trim(), issueDate: values.issueDate ? dayjs(values.issueDate).format("YYYY-MM-DD") : undefined, pageCount: values.pageCount, fileUrl: values.fileUrl, fileName: values.fileName, secretLevelCode: values.secretLevelCode, secretLevelName: getOptionLabel( SECRET_LEVEL_OPTIONS, values.secretLevelCode, ), contractFileUrls: contractFiles.length > 0 ? JSON.stringify(contractFiles) : undefined, processDocUrls: processDocFiles.length > 0 ? JSON.stringify(processDocFiles) : undefined, remarks: values.remarks, submitToRegulator: values.submitToRegulator === 1, }; const res = await props.evalReportAdd(payload); if (res?.success !== false) { message.success( payload.submitToRegulator ? "历史报告已上传并同步报送监管端" : "历史报告已上传到机构报告库", ); setAddVisible(false); fetchSummary(); getData(); } }; const handleViewDetail = async (record) => { setDetailData(null); setDetailVisible(true); const res = await props.evalReportDetail({ id: record.id }); if (res?.success !== false) { setDetailData(res?.data || {}); } }; const handleDownload = async (record) => { let fileUrl = record.fileUrl; let fileName = record.fileName || record.reportName || "报告文件"; if (!fileUrl) { const res = await props.evalReportDetail({ id: record.id }); if (res?.success === false) return; fileUrl = res?.data?.fileUrl; fileName = res?.data?.fileName || fileName; } if (!fileUrl) { message.warning("文件地址不存在"); return; } // 跨域时 会被忽略并在线打开,改为拉取 blob 再触发本地下载 const hide = message.loading("正在下载...", 0); try { const response = await fetch(fileUrl, { headers: { token: sessionStorage.getItem("token") || "", }, }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const blob = await response.blob(); 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); message.success("下载已开始"); } catch (err) { message.error("下载失败,请稍后重试"); } finally { hide(); } }; const handleSubmitToRegulator = (record) => { Modal.confirm({ title: "确认报送", icon: , content: (
确定要将报告「{record.reportName}」报送至监管端吗?报送后监管端可进行抽查审核。
), okText: "确认报送", cancelText: "取消", onOk: async () => { const res = await props.evalReportSubmit({ id: record.id }); if (res?.success !== false) { message.success("报告已报送监管端报告数据库"); fetchSummary(); getData(); } }, }); }; const handleViewSpotcheck = async (record) => { setCurrentReport(record); setSpotcheckVisible(true); setSpotcheckData([]); const res = await props.evalReportSpotcheckList({ reportId: record.id }); if (res?.success !== false) { setSpotcheckData(res?.data || []); } }; const handleOpenRectify = (record) => { setRectifyRecord(record); rectifyForm.resetFields(); setRectifyVisible(true); }; const handleRectifyOk = async () => { const values = await rectifyForm.validateFields().catch(() => null); if (!values) return; const res = await props.evalReportSpotcheckRectify({ spotcheckId: rectifyRecord.id, rectifyFeedback: values.rectifyFeedback, }); if (res?.success !== false) { message.success("整改反馈提交成功"); setRectifyVisible(false); if (currentReport?.id) { const listRes = await props.evalReportSpotcheckList({ reportId: currentReport.id, }); if (listRes?.success !== false) { setSpotcheckData(listRes?.data || []); } } fetchSummary(); getData(); } }; const renderDisplayStatus = (record) => { const name = record.displayStatusName || record.reportStatusName || "-"; const cls = DISPLAY_STATUS_CLASS[name] || ""; const tag = {name}; if (isSpotcheckRelated(record)) { return (
handleViewSpotcheck(record)} className="erl-status-link"> {tag} ); } return tag; }; const columns = [ { title: "报告编号", dataIndex: "reportNo", width: 160, render: (val) => ( {val || "-"} ), }, { title: "报告名称", dataIndex: "reportName", width: 260, ellipsis: true, render: (text, record) => (
{getFileExt(record.fileName || record.fileUrl || text)} handleViewDetail(record)} title={text}> {text || "-"}
), }, { title: "年度", dataIndex: "reportYear", width: 80, render: (val) => val || "-", }, { title: "评价类型", dataIndex: "evalTypeName", width: 120, render: (val, record) => val || getOptionLabel(REPORT_EVAL_TYPE_OPTIONS, record.evalTypeCode), }, { title: "所属行业", dataIndex: "industryName", width: 120, render: (val, record) => val || getOptionLabel(REPORT_INDUSTRY_OPTIONS, record.industryCode), }, { title: "被评价单位", dataIndex: "evaluatedUnitName", width: 180, ellipsis: true, render: (val) => val || "-", }, { title: "签发日期", dataIndex: "issueDate", width: 120, render: (val) => (val ? dayjs(val).format("YYYY-MM-DD") : "-"), }, { title: "报送状态", dataIndex: "displayStatusName", width: 110, render: (_, record) => renderDisplayStatus(record), }, { title: "操作", width: 210, fixed: "right", render: (_, record) => ( {isUnsubmitted(record) && ( )} ), }, ]; const spotcheckColumns = [ { title: "抽查时间", dataIndex: "checkTime", width: 160, render: (val) => (val ? dayjs(val).format("YYYY-MM-DD HH:mm") : "-"), }, { title: "抽查人", dataIndex: "checkerName", width: 100, render: (val) => val || "-", }, { title: "抽查结果", dataIndex: "checkResultName", width: 100, render: (val, record) => { if (record.checkResultCode === 1) { return 合格; } if (record.checkResultCode === 2) { return 不合格; } return {val || "-"}; }, }, { title: "抽查意见", dataIndex: "checkOpinion", width: 200, ellipsis: true, render: (val) => val || "-", }, { title: "整改要求", dataIndex: "rectifyRequire", width: 180, ellipsis: true, render: (val) => val || "-", }, { title: "整改反馈", dataIndex: "rectifyFeedback", width: 180, ellipsis: true, render: (val) => val || "-", }, { title: "复核结果", dataIndex: "reviewResultName", width: 100, render: (val, record) => { if (record.reviewResultCode === 1) { return 通过; } if (record.reviewResultCode === 2) { return 不通过; } return val || "-"; }, }, { title: "操作", width: 110, fixed: "right", render: (_, record) => { const needRectify = record.checkResultCode === 2 && !record.rectifyFeedback; if (!needRectify) return "-"; return ( ); }, }, ]; const summaryItems = [ { key: "totalCount", label: "报告总数", value: statData.totalCount }, { key: "recentThreeYearCount", label: "近三年报告", value: statData.recentThreeYearCount, }, { key: "submittedCount", label: "已报送监管", value: statData.submittedCount, color: "#059669", }, { key: "incompleteCount", label: "待完善分类", value: statData.incompleteCount, color: "#d97706", }, { key: "spotcheckingCount", label: "监管抽查中", value: statData.spotcheckingCount, color: "#2563eb", }, ]; return ( } onClick={handleOpenAdd}> 上传历史报告 } >
上传过去三年已执行项目的正式报告,按年度、评价类型、行业和企业分类管理,并可报送监管端抽查。
{summaryItems.map((item) => (
{item.label} {item.value ?? "-"}
))}
, , , , , ]} onFinish={handleSearch} onReset={handleReset} /> `共 ${count} 条`, }} onChange={handlePageChange} /> {/* 上传历史报告 */}
上传历史报告
上传过去三年已完成并正式签发的安全评价报告
} open={addVisible} onCancel={() => setAddVisible(false)} onOk={handleAddOk} confirmLoading={evalReportAddLoading} okText="确认上传" cancelText="取消" width={820} destroyOnClose className="erl-upload-modal" >
{ if (!uploadForm.getFieldValue("fileUrl")) { throw new Error("请选择正式报告文件"); } }, }, ]} >
{ const isLt100M = file.size / 1024 / 1024 <= 100; if (!isLt100M) { message.error("单个文件不超过 100MB"); return Upload.LIST_IGNORE; } return true; }} className="erl-upload-trigger" >
PDF
选择正式报告文件

支持 PDF、Word,建议上传签字盖章后的正式 PDF 文件,单个文件不超过100MB。

{uploadedFile ? `${uploadedFile.name}` : "尚未选择文件"}
{ const isLt100M = file.size / 1024 / 1024 <= 100; if (!isLt100M) { message.error("单个文件不超过 100MB"); return Upload.LIST_IGNORE; } return true; }} > { const isLt100M = file.size / 1024 / 1024 <= 100; if (!isLt100M) { message.error("单个文件不超过 100MB"); return Upload.LIST_IGNORE; } return true; }} >