1149 lines
36 KiB
JavaScript
1149 lines
36 KiB
JavaScript
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) => (
|
||
<div key={i} className="erl-file-cell" style={{ marginBottom: 4 }}>
|
||
<i>{getFileExt(f.name || f.url)}</i>
|
||
<a
|
||
href={f.url}
|
||
download={f.name}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
>
|
||
{f.name || "未命名文件"}
|
||
</a>
|
||
</div>
|
||
));
|
||
} 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;
|
||
}
|
||
// 跨域时 <a download> 会被忽略并在线打开,改为拉取 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: <SendOutlined style={{ color: "#1677ff" }} />,
|
||
content: (
|
||
<div style={{ fontSize: 14, color: "#333", lineHeight: 1.8, padding: "8px 0" }}>
|
||
确定要将报告「{record.reportName}」报送至监管端吗?报送后监管端可进行抽查审核。
|
||
</div>
|
||
),
|
||
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 = <span className={`erl-tag ${cls}`}>{name}</span>;
|
||
if (isSpotcheckRelated(record)) {
|
||
return (
|
||
<a onClick={() => handleViewSpotcheck(record)} className="erl-status-link">
|
||
{tag}
|
||
</a>
|
||
);
|
||
}
|
||
return tag;
|
||
};
|
||
|
||
const columns = [
|
||
{
|
||
title: "报告编号",
|
||
dataIndex: "reportNo",
|
||
width: 160,
|
||
render: (val) => (
|
||
<span className="erl-report-no">{val || "-"}</span>
|
||
),
|
||
},
|
||
{
|
||
title: "报告名称",
|
||
dataIndex: "reportName",
|
||
width: 260,
|
||
ellipsis: true,
|
||
render: (text, record) => (
|
||
<div className="erl-file-cell">
|
||
<i>{getFileExt(record.fileName || record.fileUrl || text)}</i>
|
||
<a onClick={() => handleViewDetail(record)} title={text}>
|
||
{text || "-"}
|
||
</a>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
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) => (
|
||
<Space size={4} wrap className="erl-action-btns">
|
||
<Button type="primary" size="small" onClick={() => handleViewDetail(record)}>
|
||
查看
|
||
</Button>
|
||
<Button size="small" onClick={() => handleDownload(record)}>
|
||
下载
|
||
</Button>
|
||
{isUnsubmitted(record) && (
|
||
<Button
|
||
type="primary"
|
||
size="small"
|
||
ghost
|
||
onClick={() => handleSubmitToRegulator(record)}
|
||
>
|
||
报送
|
||
</Button>
|
||
)}
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
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 <span className="erl-tag erl-tag-success">合格</span>;
|
||
}
|
||
if (record.checkResultCode === 2) {
|
||
return <span className="erl-tag erl-tag-danger">不合格</span>;
|
||
}
|
||
return <span className="erl-tag">{val || "-"}</span>;
|
||
},
|
||
},
|
||
{
|
||
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 <span className="erl-tag erl-tag-success">通过</span>;
|
||
}
|
||
if (record.reviewResultCode === 2) {
|
||
return <span className="erl-tag erl-tag-danger">不通过</span>;
|
||
}
|
||
return val || "-";
|
||
},
|
||
},
|
||
{
|
||
title: "操作",
|
||
width: 110,
|
||
fixed: "right",
|
||
render: (_, record) => {
|
||
const needRectify =
|
||
record.checkResultCode === 2 && !record.rectifyFeedback;
|
||
if (!needRectify) return "-";
|
||
return (
|
||
<Button type="link" size="small" onClick={() => handleOpenRectify(record)}>
|
||
提交整改
|
||
</Button>
|
||
);
|
||
},
|
||
},
|
||
];
|
||
|
||
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 (
|
||
<ConfigProvider theme={PROTO_THEME}>
|
||
<PageLayout
|
||
title="报告库管理"
|
||
extra={
|
||
<Button type="primary" icon={<PlusOutlined />} onClick={handleOpenAdd}>
|
||
上传历史报告
|
||
</Button>
|
||
}
|
||
>
|
||
<div className="erl-page">
|
||
<div className="erl-page-desc">
|
||
上传过去三年已执行项目的正式报告,按年度、评价类型、行业和企业分类管理,并可报送监管端抽查。
|
||
</div>
|
||
|
||
<div className="erl-summary">
|
||
{summaryItems.map((item) => (
|
||
<div key={item.key} className="erl-summary-item">
|
||
<span>{item.label}</span>
|
||
<strong style={item.color ? { color: item.color } : undefined}>
|
||
{item.value ?? "-"}
|
||
</strong>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<SearchForm
|
||
form={searchForm}
|
||
loading={false}
|
||
style={{ marginBottom: 16 }}
|
||
formLine={[
|
||
<Form.Item key="keyword" name="keyword">
|
||
<ControlWrapper.Input
|
||
label="报告名称"
|
||
placeholder="报告名称 / 编号"
|
||
allowClear
|
||
/>
|
||
</Form.Item>,
|
||
<Form.Item key="reportYear" name="reportYear">
|
||
<ControlWrapper.Select
|
||
label="报告年度"
|
||
placeholder="全部"
|
||
allowClear
|
||
options={getYearOptions()}
|
||
/>
|
||
</Form.Item>,
|
||
<Form.Item key="evalTypeCode" name="evalTypeCode">
|
||
<ControlWrapper.Select
|
||
label="评价类型"
|
||
placeholder="全部"
|
||
allowClear
|
||
options={REPORT_EVAL_TYPE_OPTIONS}
|
||
/>
|
||
</Form.Item>,
|
||
<Form.Item key="industryCode" name="industryCode">
|
||
<ControlWrapper.Select
|
||
label="所属行业"
|
||
placeholder="全部"
|
||
allowClear
|
||
options={REPORT_INDUSTRY_OPTIONS}
|
||
/>
|
||
</Form.Item>,
|
||
<Form.Item key="displayStatus" name="displayStatus">
|
||
<ControlWrapper.Select
|
||
label="报送状态"
|
||
placeholder="全部"
|
||
allowClear
|
||
options={DISPLAY_STATUS_OPTIONS}
|
||
/>
|
||
</Form.Item>,
|
||
]}
|
||
onFinish={handleSearch}
|
||
onReset={handleReset}
|
||
/>
|
||
|
||
<Table
|
||
rowKey="id"
|
||
columns={columns}
|
||
dataSource={dataSource}
|
||
loading={evalReportPageLoading}
|
||
scroll={{ y: props.scrollY, x: 1400 }}
|
||
pagination={{
|
||
total,
|
||
current: Number(router.query.current) || 1,
|
||
pageSize: Number(router.query.size) || 10,
|
||
showSizeChanger: true,
|
||
showQuickJumper: true,
|
||
showTotal: (count) => `共 ${count} 条`,
|
||
}}
|
||
onChange={handlePageChange}
|
||
/>
|
||
</div>
|
||
|
||
{/* 上传历史报告 */}
|
||
<Modal
|
||
title={
|
||
<div className="erl-modal-title">
|
||
<div>上传历史报告</div>
|
||
<div className="erl-modal-subtitle">
|
||
上传过去三年已完成并正式签发的安全评价报告
|
||
</div>
|
||
</div>
|
||
}
|
||
open={addVisible}
|
||
onCancel={() => setAddVisible(false)}
|
||
onOk={handleAddOk}
|
||
confirmLoading={evalReportAddLoading}
|
||
okText="确认上传"
|
||
cancelText="取消"
|
||
width={820}
|
||
destroyOnClose
|
||
className="erl-upload-modal"
|
||
>
|
||
<Form form={uploadForm} layout="vertical" preserve={false}>
|
||
<Form.Item name="fileUrl" hidden>
|
||
<Input />
|
||
</Form.Item>
|
||
<Form.Item name="fileName" hidden>
|
||
<Input />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="file"
|
||
rules={[
|
||
{
|
||
validator: async () => {
|
||
if (!uploadForm.getFieldValue("fileUrl")) {
|
||
throw new Error("请选择正式报告文件");
|
||
}
|
||
},
|
||
},
|
||
]}
|
||
>
|
||
<div className="erl-upload-box">
|
||
<Upload
|
||
name="file"
|
||
action={UPLOAD_ACTION}
|
||
headers={{ token: sessionStorage.getItem("token") || "" }}
|
||
onChange={handleFileUploadChange}
|
||
accept=".pdf,.doc,.docx"
|
||
maxCount={1}
|
||
showUploadList={false}
|
||
beforeUpload={(file) => {
|
||
const isLt100M = file.size / 1024 / 1024 <= 100;
|
||
if (!isLt100M) {
|
||
message.error("单个文件不超过 100MB");
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
return true;
|
||
}}
|
||
className="erl-upload-trigger"
|
||
>
|
||
<div className="erl-upload-box-inner">
|
||
<div className="erl-upload-icon">PDF</div>
|
||
<div className="erl-upload-text">
|
||
<strong>选择正式报告文件</strong>
|
||
<p>
|
||
支持 PDF、Word,建议上传签字盖章后的正式 PDF 文件,单个文件不超过100MB。
|
||
</p>
|
||
<span>
|
||
{uploadedFile
|
||
? `${uploadedFile.name}`
|
||
: "尚未选择文件"}
|
||
</span>
|
||
</div>
|
||
<Button type="primary" size="small" icon={<UploadOutlined />}>
|
||
选择文件
|
||
</Button>
|
||
</div>
|
||
</Upload>
|
||
</div>
|
||
</Form.Item>
|
||
|
||
<Row gutter={16}>
|
||
<Col span={12}>
|
||
<Form.Item
|
||
name="reportNo"
|
||
label="报告编号"
|
||
rules={[{ required: true, message: "请输入报告编号" }]}
|
||
>
|
||
<Input placeholder="例如:CQAP-XZ-2026-001" maxLength={64} />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item
|
||
name="reportName"
|
||
label="报告名称"
|
||
rules={[{ required: true, message: "请输入报告名称" }]}
|
||
>
|
||
<Input placeholder="请输入正式报告名称" maxLength={200} />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item name="reportYear" label="报告年度">
|
||
<Select options={getYearOptions()} />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item name="evalTypeCode" label="评价类型">
|
||
<Select options={REPORT_EVAL_TYPE_OPTIONS} />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item name="industryCode" label="所属行业">
|
||
<Select options={REPORT_INDUSTRY_OPTIONS} />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item
|
||
name="evaluatedUnitName"
|
||
label="被评价单位"
|
||
rules={[{ required: true, message: "请输入被评价单位" }]}
|
||
>
|
||
<Input placeholder="请输入被评价单位名称" maxLength={200} />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item
|
||
name="issueDate"
|
||
label="报告签发日期"
|
||
rules={[{ required: true, message: "请选择签发日期" }]}
|
||
>
|
||
<DatePicker style={{ width: "100%" }} placeholder="选择签发日期" />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item name="pageCount" label="报告页数">
|
||
<InputNumber
|
||
style={{ width: "100%" }}
|
||
min={1}
|
||
placeholder="例如:186"
|
||
/>
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item name="submitToRegulator" label="入库后处理">
|
||
<Select
|
||
options={[
|
||
{ label: "仅存入机构报告库", value: 0 },
|
||
{ label: "同步报送监管端抽查", value: 1 },
|
||
]}
|
||
/>
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={12}>
|
||
<Form.Item name="secretLevelCode" label="保密属性">
|
||
<Select options={SECRET_LEVEL_OPTIONS} />
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={24}>
|
||
<Form.Item label="合同上传">
|
||
<Upload
|
||
name="file"
|
||
action={UPLOAD_ACTION}
|
||
headers={{ token: sessionStorage.getItem("token") || "" }}
|
||
onChange={handleContractUploadChange}
|
||
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png,.zip,.rar"
|
||
maxCount={20}
|
||
beforeUpload={(file) => {
|
||
const isLt100M = file.size / 1024 / 1024 <= 100;
|
||
if (!isLt100M) {
|
||
message.error("单个文件不超过 100MB");
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
return true;
|
||
}}
|
||
>
|
||
<Button icon={<UploadOutlined />}>选择合同文件</Button>
|
||
</Upload>
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={24}>
|
||
<Form.Item label="其他过程文档">
|
||
<Upload
|
||
name="file"
|
||
action={UPLOAD_ACTION}
|
||
headers={{ token: sessionStorage.getItem("token") || "" }}
|
||
onChange={handleProcessDocUploadChange}
|
||
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png,.zip,.rar"
|
||
maxCount={20}
|
||
beforeUpload={(file) => {
|
||
const isLt100M = file.size / 1024 / 1024 <= 100;
|
||
if (!isLt100M) {
|
||
message.error("单个文件不超过 100MB");
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
return true;
|
||
}}
|
||
>
|
||
<Button icon={<UploadOutlined />}>选择过程文档</Button>
|
||
</Upload>
|
||
</Form.Item>
|
||
</Col>
|
||
<Col span={24}>
|
||
<Form.Item name="remarks" label="备注">
|
||
<TextArea
|
||
rows={3}
|
||
placeholder="填写报告修订版次、附件情况或脱密说明"
|
||
maxLength={500}
|
||
showCount
|
||
/>
|
||
</Form.Item>
|
||
</Col>
|
||
</Row>
|
||
</Form>
|
||
|
||
<div className="erl-upload-tip">
|
||
<strong>上传要求</strong>
|
||
<span>报告编号不得重复</span>
|
||
<span>报告须完成签字盖章</span>
|
||
<span>分类信息用于监管抽查检索</span>
|
||
<span>涉密内容须先完成脱密处理</span>
|
||
</div>
|
||
</Modal>
|
||
|
||
{/* 报告详情 */}
|
||
<Modal
|
||
title="报告详情"
|
||
open={detailVisible}
|
||
onCancel={() => setDetailVisible(false)}
|
||
footer={<Button onClick={() => setDetailVisible(false)}>关闭</Button>}
|
||
width={760}
|
||
destroyOnClose
|
||
>
|
||
{detailData ? (
|
||
<>
|
||
<Descriptions bordered column={{ xs: 1, sm: 1, md: 2 }} size="small">
|
||
<Descriptions.Item label="报告编号">
|
||
<span className="erl-report-no">{detailData.reportNo || "-"}</span>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="报告名称">
|
||
{detailData.reportName || "-"}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="报告年度">
|
||
{detailData.reportYear || "-"}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="评价类型">
|
||
{detailData.evalTypeName ||
|
||
getOptionLabel(REPORT_EVAL_TYPE_OPTIONS, detailData.evalTypeCode)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="所属行业">
|
||
{detailData.industryName ||
|
||
getOptionLabel(REPORT_INDUSTRY_OPTIONS, detailData.industryCode)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="被评价单位">
|
||
{detailData.evaluatedUnitName || "-"}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="签发日期">
|
||
{detailData.issueDate
|
||
? dayjs(detailData.issueDate).format("YYYY-MM-DD")
|
||
: "-"}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="报告页数">
|
||
{detailData.pageCount ?? "-"}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="报送状态">
|
||
{renderDisplayStatus(detailData)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="报告状态">
|
||
{detailData.reportStatusName || "-"}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="保密属性">
|
||
{detailData.secretLevelName ||
|
||
getOptionLabel(SECRET_LEVEL_OPTIONS, detailData.secretLevelCode)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="归档时间">
|
||
{detailData.archiveTime
|
||
? dayjs(detailData.archiveTime).format("YYYY-MM-DD HH:mm")
|
||
: "-"}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="文件名称" span={2}>
|
||
<div className="erl-file-cell">
|
||
<i>{getFileExt(detailData.fileName || detailData.fileUrl)}</i>
|
||
<span>{detailData.fileName || "-"}</span>
|
||
</div>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="合同文件" span={2}>
|
||
{renderFileLinks(detailData.contractFileUrls)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="过程文档" span={2}>
|
||
{renderFileLinks(detailData.processDocUrls)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="备注" span={2}>
|
||
{detailData.remarks || "-"}
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
<div className="erl-detail-actions">
|
||
<Button
|
||
type="primary"
|
||
onClick={() => handleDownload(detailData)}
|
||
disabled={!detailData.fileUrl}
|
||
>
|
||
下载报告
|
||
</Button>
|
||
{isUnsubmitted(detailData) && (
|
||
<Button onClick={() => handleSubmitToRegulator(detailData)}>
|
||
报送监管
|
||
</Button>
|
||
)}
|
||
{isSpotcheckRelated(detailData) && (
|
||
<Button
|
||
onClick={() => {
|
||
setDetailVisible(false);
|
||
handleViewSpotcheck(detailData);
|
||
}}
|
||
>
|
||
抽查记录
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</>
|
||
) : (
|
||
<div className="erl-loading">
|
||
{evalReportDetailLoading ? "加载中..." : "暂无数据"}
|
||
</div>
|
||
)}
|
||
</Modal>
|
||
|
||
{/* 抽查记录 */}
|
||
<Modal
|
||
title={
|
||
currentReport
|
||
? `抽查记录 — ${currentReport.reportName || currentReport.reportNo || ""}`
|
||
: "抽查记录"
|
||
}
|
||
open={spotcheckVisible}
|
||
onCancel={() => setSpotcheckVisible(false)}
|
||
footer={<Button onClick={() => setSpotcheckVisible(false)}>关闭</Button>}
|
||
width={980}
|
||
destroyOnClose
|
||
>
|
||
<Table
|
||
rowKey="id"
|
||
columns={spotcheckColumns}
|
||
dataSource={spotcheckData}
|
||
loading={evalReportSpotcheckListLoading}
|
||
pagination={false}
|
||
size="small"
|
||
scroll={{ x: 1100 }}
|
||
locale={{ emptyText: "暂无抽查记录" }}
|
||
/>
|
||
</Modal>
|
||
|
||
{/* 整改反馈 */}
|
||
<Modal
|
||
title="提交整改反馈"
|
||
open={rectifyVisible}
|
||
onCancel={() => setRectifyVisible(false)}
|
||
onOk={handleRectifyOk}
|
||
confirmLoading={evalReportSpotcheckRectifyLoading}
|
||
okText="提交整改"
|
||
cancelText="取消"
|
||
width={560}
|
||
destroyOnClose
|
||
>
|
||
{rectifyRecord && (
|
||
<div className="erl-rectify-meta">
|
||
<div>
|
||
<label>抽查意见:</label>
|
||
<span>{rectifyRecord.checkOpinion || "-"}</span>
|
||
</div>
|
||
<div>
|
||
<label>整改要求:</label>
|
||
<span>{rectifyRecord.rectifyRequire || "-"}</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
<Form form={rectifyForm} layout="vertical" preserve={false}>
|
||
<Form.Item
|
||
name="rectifyFeedback"
|
||
label="整改反馈"
|
||
rules={[{ required: true, message: "请填写整改反馈" }]}
|
||
>
|
||
<TextArea
|
||
rows={4}
|
||
placeholder="请详细说明整改措施和完成情况..."
|
||
showCount
|
||
maxLength={500}
|
||
/>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
</PageLayout>
|
||
</ConfigProvider>
|
||
);
|
||
};
|
||
|
||
export default Connect(
|
||
[NS_SAFETY_EVAL_BUSINESS],
|
||
true,
|
||
)(AntdTableFuncControl(EvalReportLibrary));
|