2026-07-28 16:07:00 +08:00
|
|
|
|
import React, { useEffect, useMemo, useState } from "react";
|
|
|
|
|
|
import {
|
|
|
|
|
|
Form,
|
|
|
|
|
|
Table,
|
|
|
|
|
|
Button,
|
|
|
|
|
|
Modal,
|
|
|
|
|
|
Input,
|
|
|
|
|
|
Select,
|
2026-08-12 14:37:45 +08:00
|
|
|
|
InputNumber,
|
|
|
|
|
|
Row,
|
|
|
|
|
|
Col,
|
2026-07-28 16:07:00 +08:00
|
|
|
|
message,
|
|
|
|
|
|
DatePicker,
|
|
|
|
|
|
Space,
|
|
|
|
|
|
ConfigProvider,
|
|
|
|
|
|
} from "antd";
|
2026-08-12 17:12:19 +08:00
|
|
|
|
import { DownloadOutlined, ExportOutlined } from "@ant-design/icons";
|
2026-08-12 14:37:45 +08:00
|
|
|
|
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
|
2026-07-28 16:07:00 +08:00
|
|
|
|
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
2026-08-13 10:26:12 +08:00
|
|
|
|
import SearchForm from "~/components/SearchForm";
|
2026-07-28 16:07:00 +08:00
|
|
|
|
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
|
|
|
|
|
|
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
|
2026-08-12 14:37:45 +08:00
|
|
|
|
import { NS_REGULATOR_EVAL_REPORT, NS_ORG_INFO } from "~/enumerate/namespace";
|
2026-07-28 16:07:00 +08:00
|
|
|
|
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
|
|
|
|
|
import { tools } from "@cqsjjb/jjb-common-lib";
|
2026-08-12 17:12:19 +08:00
|
|
|
|
import { renderDocxBlob, fillDocxTemplate } from "~/utils/fillDocxTemplate";
|
2026-08-12 17:26:27 +08:00
|
|
|
|
import SpotCheckDetailModal from "./components/SpotCheckDetailModal";
|
2026-08-13 10:08:53 +08:00
|
|
|
|
import PreviewUrlButton from "~/components/PreviewUrlButton";
|
2026-07-28 16:07:00 +08:00
|
|
|
|
import dayjs from "dayjs";
|
|
|
|
|
|
import "./index.less";
|
|
|
|
|
|
|
|
|
|
|
|
const { router } = tools;
|
|
|
|
|
|
const { TextArea } = Input;
|
|
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
|
|
|
|
|
|
|
|
const API_HOST = window.process?.env?.app?.API_HOST || "";
|
|
|
|
|
|
|
2026-08-12 17:12:19 +08:00
|
|
|
|
/** 监督检查单 docx 模板(线上) */
|
|
|
|
|
|
const CHECKLIST_TEMPLATE_URL =
|
|
|
|
|
|
"https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com/jjb/6a7c36c2e4b0435c62b76a34.docx";
|
|
|
|
|
|
|
2026-07-28 16:07:00 +08:00
|
|
|
|
/** 原型 CSS:--radius 8 / --radius-lg 12 */
|
|
|
|
|
|
const PROTO_THEME = {
|
|
|
|
|
|
token: { borderRadius: 8, borderRadiusLG: 12, borderRadiusSM: 6 },
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const EVAL_TYPE_OPTIONS = [
|
|
|
|
|
|
{ label: "安全预评价", value: "PRE" },
|
|
|
|
|
|
{ label: "安全验收评价", value: "ACCEPT" },
|
|
|
|
|
|
{ label: "安全现状评价", value: "STATUS" },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/** 监管端原型行业筛选项(编码对齐 OpenAPI 示例 HAZCHEM) */
|
|
|
|
|
|
const INDUSTRY_OPTIONS = [
|
|
|
|
|
|
{ label: "化工", value: "HAZCHEM" },
|
|
|
|
|
|
{ label: "矿山", value: "MINE" },
|
|
|
|
|
|
{ label: "工贸", value: "INDUSTRY_TRADE" },
|
|
|
|
|
|
{ label: "建筑施工", value: "CONSTRUCTION" },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-08-12 14:37:45 +08:00
|
|
|
|
/** 抽检-检查类型(对齐原型) */
|
|
|
|
|
|
const CHECK_TYPE_OPTIONS = [
|
|
|
|
|
|
{ label: "安全评价报告质量抽查", value: "QUALITY" },
|
|
|
|
|
|
{ label: "专项监督检查", value: "SPECIAL" },
|
|
|
|
|
|
{ label: "年度随机抽查", value: "ANNUAL" },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-07-28 16:07:00 +08:00
|
|
|
|
const getYearOptions = () => {
|
|
|
|
|
|
const year = dayjs().year();
|
|
|
|
|
|
return [0, 1, 2].map((offset) => {
|
|
|
|
|
|
const y = year - offset;
|
|
|
|
|
|
return { label: String(y), value: y };
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const getOptionLabel = (options, value) =>
|
|
|
|
|
|
options.find((item) => item.value === value)?.label || value || "-";
|
|
|
|
|
|
|
|
|
|
|
|
/** 文件流下载(批量 ZIP / 导出 CSV) */
|
|
|
|
|
|
const downloadBinaryPost = async (path, body, fallbackName) => {
|
|
|
|
|
|
const res = await fetch(`${API_HOST}${path}`, {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
|
token: sessionStorage.getItem("token") || "",
|
|
|
|
|
|
},
|
|
|
|
|
|
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 downloadFileByUrl = async (fileUrl, fileName) => {
|
|
|
|
|
|
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);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** OpenAPI:AI 分析为前端静态;文案对齐原型 reportAiAnalysis */
|
|
|
|
|
|
const buildStaticAiAnalysis = (record) => ({
|
|
|
|
|
|
overview:
|
|
|
|
|
|
"已完成报告全文结构化识别,共识别186页、32项法规标准、18类危险有害因素和46条对策措施。",
|
|
|
|
|
|
metrics: [
|
|
|
|
|
|
{ label: "综合关注度", value: "中" },
|
|
|
|
|
|
{ label: "质量参考分", value: "86" },
|
|
|
|
|
|
{ label: "重点提示", value: "2项" },
|
|
|
|
|
|
{ label: "一般建议", value: "4项" },
|
|
|
|
|
|
],
|
|
|
|
|
|
rows: [
|
|
|
|
|
|
{
|
|
|
|
|
|
dim: "法规标准",
|
|
|
|
|
|
result: "识别32项,2项版本建议核验",
|
|
|
|
|
|
level: "一般",
|
|
|
|
|
|
advice: "核对GB 50016引用版本及地方标准有效性",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dim: "危险辨识",
|
|
|
|
|
|
result: "主要危险因素覆盖,提示液氨装卸泄漏场景描述不足",
|
|
|
|
|
|
level: "重点",
|
|
|
|
|
|
advice: "复核危险因素章节与现场储存、装卸设施是否一致",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dim: "评价方法",
|
|
|
|
|
|
result: "方法与评价单元基本匹配",
|
|
|
|
|
|
level: "低",
|
|
|
|
|
|
advice: "核对定量评价参数来源和计算附件",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dim: "对策措施",
|
|
|
|
|
|
result: "4条措施表述较通用",
|
|
|
|
|
|
level: "一般",
|
|
|
|
|
|
advice: "补充责任主体、实施条件和可验证指标",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dim: "数据一致性",
|
|
|
|
|
|
result: "发现3处表格数据与正文表述差异",
|
|
|
|
|
|
level: "重点",
|
|
|
|
|
|
advice: "核查安全距离、设备数量和人员统计数据",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
dim: "评价结论",
|
|
|
|
|
|
result: "结论与章节分析基本一致",
|
|
|
|
|
|
level: "低",
|
|
|
|
|
|
advice: "结合重点提示完成最终人工判断",
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
conclusion:
|
|
|
|
|
|
"报告整体结构完整,建议重点复核法规版本、液氨装卸场景及3处数据一致性问题。本结果仅供辅助监管使用。",
|
|
|
|
|
|
reportLabel: record?.reportNo || record?.reportName || "",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/** 原型 .reg-modal-section */
|
|
|
|
|
|
const ModalSection = ({ title, desc, children }) => (
|
|
|
|
|
|
<div className="redb-modal-section">
|
|
|
|
|
|
<h4>{title}</h4>
|
|
|
|
|
|
{desc ? <p>{desc}</p> : null}
|
|
|
|
|
|
{children}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
/** 原型 tableHtml:字段表 / 数据表;kv=字段-内容两列表 */
|
|
|
|
|
|
const ProtoTable = ({ headers, rows, kv = false }) => (
|
|
|
|
|
|
<div className={`redb-data-table${kv ? " redb-data-table-kv" : ""}`}>
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
{headers.map((h) => (
|
|
|
|
|
|
<th key={h}>{h}</th>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
{rows.map((row, idx) => (
|
|
|
|
|
|
<tr key={idx}>
|
|
|
|
|
|
{row.map((cell, cIdx) => (
|
|
|
|
|
|
<td key={cIdx}>{cell}</td>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const getFileExtLabel = (name = "") => {
|
|
|
|
|
|
const ext = String(name).split(".").pop();
|
|
|
|
|
|
return ext && ext !== name ? ext.toUpperCase() : "PDF";
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-04 15:56:14 +08:00
|
|
|
|
/** 解析存储的 JSON 文件列表字符串,返回数组 */
|
|
|
|
|
|
const parseFileList = (jsonStr) => {
|
|
|
|
|
|
if (!jsonStr) return [];
|
|
|
|
|
|
try {
|
|
|
|
|
|
const arr = JSON.parse(jsonStr);
|
|
|
|
|
|
return Array.isArray(arr) ? arr : [];
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-28 16:07:00 +08:00
|
|
|
|
const EvalReportDatabase = (props) => {
|
|
|
|
|
|
const [searchForm] = Form.useForm();
|
|
|
|
|
|
const [exportForm] = Form.useForm();
|
|
|
|
|
|
const [spotcheckForm] = Form.useForm();
|
|
|
|
|
|
const [reviewForm] = Form.useForm();
|
|
|
|
|
|
const [batchForm] = Form.useForm();
|
|
|
|
|
|
|
|
|
|
|
|
const [dataSource, setDataSource] = useState([]);
|
|
|
|
|
|
const [total, setTotal] = useState(0);
|
|
|
|
|
|
const [statData, setStatData] = useState({});
|
|
|
|
|
|
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
|
|
|
|
|
|
|
|
|
|
|
const [detailVisible, setDetailVisible] = useState(false);
|
|
|
|
|
|
const [detailData, setDetailData] = useState(null);
|
|
|
|
|
|
const [aiVisible, setAiVisible] = useState(false);
|
|
|
|
|
|
const [aiData, setAiData] = useState(null);
|
|
|
|
|
|
const [exportVisible, setExportVisible] = useState(false);
|
|
|
|
|
|
const [batchVisible, setBatchVisible] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
const [spotcheckVisible, setSpotcheckVisible] = useState(false);
|
|
|
|
|
|
const [spotcheckList, setSpotcheckList] = useState([]);
|
|
|
|
|
|
const [spotcheckStartVisible, setSpotcheckStartVisible] = useState(false);
|
|
|
|
|
|
const [reviewVisible, setReviewVisible] = useState(false);
|
|
|
|
|
|
const [reviewRecord, setReviewRecord] = useState(null);
|
|
|
|
|
|
const [currentReport, setCurrentReport] = useState(null);
|
|
|
|
|
|
|
2026-08-12 14:37:45 +08:00
|
|
|
|
// 抽检
|
|
|
|
|
|
const [spotCheckVisible, setSpotCheckVisible] = useState(false);
|
|
|
|
|
|
const [spotCheckData, setSpotCheckData] = useState([]);
|
|
|
|
|
|
const [orgOptions, setOrgOptions] = useState([]);
|
|
|
|
|
|
const [spotCheckForm] = Form.useForm();
|
2026-08-12 17:12:19 +08:00
|
|
|
|
const [viewChecklistLoading, setViewChecklistLoading] = useState(false);
|
2026-08-12 17:26:27 +08:00
|
|
|
|
const [spotDetailVisible, setSpotDetailVisible] = useState(false);
|
|
|
|
|
|
const [spotDetailData, setSpotDetailData] = useState(null);
|
2026-08-12 14:37:45 +08:00
|
|
|
|
|
2026-07-28 16:07:00 +08:00
|
|
|
|
const {
|
|
|
|
|
|
regulatorEvalReportPageLoading,
|
|
|
|
|
|
regulatorEvalReportDetailLoading,
|
|
|
|
|
|
regulatorSpotcheckListLoading,
|
|
|
|
|
|
regulatorSpotcheckStartLoading,
|
|
|
|
|
|
regulatorSpotcheckReviewLoading,
|
2026-08-12 14:37:45 +08:00
|
|
|
|
regulatorSpotCheckReportLoading,
|
2026-07-28 16:07:00 +08:00
|
|
|
|
} = props.regulatorEvalReport || {};
|
|
|
|
|
|
|
2026-08-12 17:12:19 +08:00
|
|
|
|
const { registeredOrgListData } = props.orgInfo || {};
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-28 16:07:00 +08:00
|
|
|
|
const fetchSummary = async () => {
|
|
|
|
|
|
const res = await props.regulatorEvalReportSummary();
|
|
|
|
|
|
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 || 20,
|
|
|
|
|
|
};
|
|
|
|
|
|
// RangePicker 拆成接口字段
|
|
|
|
|
|
if (params.archiveTimeRange?.length === 2) {
|
2026-08-12 17:12:19 +08:00
|
|
|
|
params.archiveTimeStart = dayjs(params.archiveTimeRange[0]).format(
|
|
|
|
|
|
"YYYY-MM-DD",
|
|
|
|
|
|
);
|
|
|
|
|
|
params.archiveTimeEnd = dayjs(params.archiveTimeRange[1]).format(
|
|
|
|
|
|
"YYYY-MM-DD",
|
|
|
|
|
|
);
|
2026-07-28 16:07:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
delete params.archiveTimeRange;
|
|
|
|
|
|
|
|
|
|
|
|
const res = await props.regulatorEvalReportPage(params);
|
|
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
|
setDataSource(res?.data || []);
|
|
|
|
|
|
setTotal(res?.total || 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const q = { ...router.query };
|
|
|
|
|
|
if (q.archiveTimeStart && q.archiveTimeEnd) {
|
|
|
|
|
|
q.archiveTimeRange = [dayjs(q.archiveTimeStart), dayjs(q.archiveTimeEnd)];
|
|
|
|
|
|
}
|
|
|
|
|
|
searchForm.setFieldsValue(q);
|
|
|
|
|
|
fetchSummary();
|
|
|
|
|
|
getData();
|
2026-08-12 14:37:45 +08:00
|
|
|
|
loadOrgOptions();
|
2026-07-28 16:07:00 +08:00
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-08-12 14:37:45 +08:00
|
|
|
|
const loadOrgOptions = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await props.registeredOrgList({ current: 1, size: 200 });
|
|
|
|
|
|
const list = res?.data || [];
|
|
|
|
|
|
setOrgOptions(
|
|
|
|
|
|
list.map((item) => ({
|
|
|
|
|
|
label:
|
|
|
|
|
|
item.unitName ||
|
|
|
|
|
|
item.orgName ||
|
|
|
|
|
|
item.name ||
|
|
|
|
|
|
item.enterpriseName ||
|
|
|
|
|
|
String(item.id),
|
|
|
|
|
|
value: String(item.id),
|
|
|
|
|
|
})),
|
|
|
|
|
|
);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setOrgOptions([]);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleOpenSpotCheck = () => {
|
|
|
|
|
|
spotCheckForm.resetFields();
|
|
|
|
|
|
spotCheckForm.setFieldsValue({
|
|
|
|
|
|
randomNumber: 2,
|
|
|
|
|
|
checkType: "QUALITY",
|
|
|
|
|
|
planCheckDate: dayjs(),
|
|
|
|
|
|
});
|
|
|
|
|
|
setSpotCheckData([]);
|
|
|
|
|
|
setSpotCheckVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSpotCheck = async () => {
|
|
|
|
|
|
const values = await spotCheckForm.validateFields().catch(() => null);
|
|
|
|
|
|
if (!values) return;
|
|
|
|
|
|
const res = await props.regulatorSpotCheckReport({
|
|
|
|
|
|
orgId: values.orgId,
|
|
|
|
|
|
randomNumber: values.randomNumber,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
|
setSpotCheckData(res?.data || []);
|
|
|
|
|
|
message.success(`已随机抽取 ${res?.data?.length || 0} 份报告`);
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
document
|
|
|
|
|
|
.querySelector(".redb-modal .micro-temp-modal-body")
|
|
|
|
|
|
?.scrollTo({ top: 999999, behavior: "smooth" });
|
|
|
|
|
|
}, 100);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-12 17:12:19 +08:00
|
|
|
|
const getChecklistData = () => {
|
|
|
|
|
|
const data = registeredOrgListData.find(
|
|
|
|
|
|
(o) => o.id === spotCheckForm.getFieldValue("orgId"),
|
|
|
|
|
|
);
|
|
|
|
|
|
const unitName = data.unitName;
|
|
|
|
|
|
return {
|
|
|
|
|
|
unitName,
|
|
|
|
|
|
fileName: `安全评价报告监督检查单_${unitName}.docx`,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
unitName,
|
|
|
|
|
|
creditCode: data.creditCode || "",
|
|
|
|
|
|
qualificationCertNo: data.qualificationCertNo || "",
|
|
|
|
|
|
legalRepresentative: data.legalRepresentative || "",
|
|
|
|
|
|
businessAddress: data.businessAddress || "",
|
|
|
|
|
|
principalName: data.principalName || "",
|
|
|
|
|
|
principalPhone: data.principalPhone || "",
|
|
|
|
|
|
checkTypeName: getOptionLabel(
|
|
|
|
|
|
CHECK_TYPE_OPTIONS,
|
|
|
|
|
|
spotCheckForm.getFieldValue("checkType"),
|
|
|
|
|
|
),
|
|
|
|
|
|
planCheckDate: spotCheckForm.getFieldValue("planCheckDate")
|
|
|
|
|
|
? dayjs(spotCheckForm.getFieldValue("planCheckDate")).format(
|
|
|
|
|
|
"YYYY年M月D日",
|
|
|
|
|
|
)
|
|
|
|
|
|
: "",
|
|
|
|
|
|
checkLeader: spotCheckForm.getFieldValue("checkLeader") || "",
|
|
|
|
|
|
checkLocation: spotCheckForm.getFieldValue("checkLocation") || "",
|
|
|
|
|
|
checkMembers: spotCheckForm.getFieldValue("checkMembers") || "",
|
|
|
|
|
|
reports: spotCheckData.map((item,index) => ({
|
|
|
|
|
|
index: index + 1,
|
|
|
|
|
|
reportNo: item.reportNo || "",
|
|
|
|
|
|
reportName: item.reportName || "",
|
|
|
|
|
|
evalTypeName: item.evalTypeName || "",
|
|
|
|
|
|
industryName: item.industryName || "",
|
|
|
|
|
|
issueDate: item.issueDate || "",
|
|
|
|
|
|
})),
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const uploadDocx = async (blob, fileName) => {
|
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
|
formData.append("file", new File([blob], fileName), fileName);
|
|
|
|
|
|
const res = await fetch(
|
|
|
|
|
|
`${window.process.env.app.API_HOST}/safetyEval/file/upload`,
|
|
|
|
|
|
{
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
headers: { token: sessionStorage.getItem("token") },
|
|
|
|
|
|
body: formData,
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
const json = await res.json();
|
|
|
|
|
|
const url = json?.data?.url;
|
|
|
|
|
|
if (!url) throw new Error("上传失败");
|
|
|
|
|
|
return url;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleViewChecklist = async () => {
|
|
|
|
|
|
setViewChecklistLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const { fileName, data } = getChecklistData();
|
|
|
|
|
|
const blob = await renderDocxBlob(CHECKLIST_TEMPLATE_URL, data);
|
|
|
|
|
|
const url = await uploadDocx(blob, fileName);
|
|
|
|
|
|
window.open(url, "_blank");
|
|
|
|
|
|
message.success("检查单已生成");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setViewChecklistLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleDownloadChecklist = async () => {
|
|
|
|
|
|
const { fileName, data } = getChecklistData();
|
|
|
|
|
|
await fillDocxTemplate(
|
|
|
|
|
|
CHECKLIST_TEMPLATE_URL,
|
|
|
|
|
|
data,
|
|
|
|
|
|
fileName,
|
|
|
|
|
|
{ download: true },
|
|
|
|
|
|
);
|
|
|
|
|
|
message.success("检查单已下载");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-28 16:07:00 +08:00
|
|
|
|
const handleSearch = (values) => {
|
|
|
|
|
|
const next = { ...values, current: 1, size: router.query.size || 20 };
|
|
|
|
|
|
if (next.archiveTimeRange?.length === 2) {
|
2026-08-12 17:12:19 +08:00
|
|
|
|
next.archiveTimeStart = dayjs(next.archiveTimeRange[0]).format(
|
|
|
|
|
|
"YYYY-MM-DD",
|
|
|
|
|
|
);
|
|
|
|
|
|
next.archiveTimeEnd = dayjs(next.archiveTimeRange[1]).format(
|
|
|
|
|
|
"YYYY-MM-DD",
|
|
|
|
|
|
);
|
2026-07-28 16:07:00 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
next.archiveTimeStart = undefined;
|
|
|
|
|
|
next.archiveTimeEnd = undefined;
|
|
|
|
|
|
}
|
|
|
|
|
|
delete next.archiveTimeRange;
|
|
|
|
|
|
router.query = { ...router.query, ...next };
|
|
|
|
|
|
getData();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleReset = (values) => {
|
|
|
|
|
|
searchForm.resetFields();
|
|
|
|
|
|
router.query = { ...values, current: 1, size: 20 };
|
|
|
|
|
|
getData();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handlePageChange = (pagination) => {
|
|
|
|
|
|
router.query = {
|
|
|
|
|
|
...router.query,
|
|
|
|
|
|
current: pagination.current,
|
|
|
|
|
|
size: pagination.pageSize,
|
|
|
|
|
|
};
|
|
|
|
|
|
getData(pagination);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleViewDetail = async (record) => {
|
|
|
|
|
|
setDetailData(null);
|
|
|
|
|
|
setDetailVisible(true);
|
|
|
|
|
|
const res = await props.regulatorEvalReportDetail({ id: record.id });
|
|
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
|
setDetailData(res?.data || {});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-12 17:26:27 +08:00
|
|
|
|
/** 抽检报告详情:直接用行数据展示,不调接口 */
|
|
|
|
|
|
const handleViewSpotDetail = (record) => {
|
|
|
|
|
|
setSpotDetailData(record);
|
|
|
|
|
|
setSpotDetailVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-28 16:07:00 +08:00
|
|
|
|
const handleDownloadOne = async (record) => {
|
|
|
|
|
|
let fileUrl = record.fileUrl;
|
|
|
|
|
|
let fileName = record.fileName || record.reportName || "报告文件";
|
|
|
|
|
|
if (!fileUrl) {
|
|
|
|
|
|
const res = await props.regulatorEvalReportDetail({ id: record.id });
|
|
|
|
|
|
if (res?.success === false) return;
|
|
|
|
|
|
fileUrl = res?.data?.fileUrl;
|
|
|
|
|
|
fileName = res?.data?.fileName || fileName;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!fileUrl) {
|
|
|
|
|
|
message.warning("文件地址不存在");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const hide = message.loading("正在下载...", 0);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await downloadFileByUrl(fileUrl, fileName);
|
|
|
|
|
|
message.success("下载已开始");
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
message.error("下载失败,请稍后重试");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleOpenAi = (record) => {
|
|
|
|
|
|
setAiData({ record, analysis: buildStaticAiAnalysis(record) });
|
|
|
|
|
|
setAiVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleBatchDownload = () => {
|
|
|
|
|
|
if (!selectedRowKeys.length) {
|
|
|
|
|
|
message.warning("请先勾选需要下载的报告");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (selectedRowKeys.length > 50) {
|
|
|
|
|
|
message.warning("批量下载上限 50 份");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
batchForm.setFieldsValue({
|
|
|
|
|
|
scopeLabel: `批量下载(已选 ${selectedRowKeys.length} 份)`,
|
|
|
|
|
|
keepRecord: "是",
|
|
|
|
|
|
remark: "",
|
|
|
|
|
|
});
|
|
|
|
|
|
setBatchVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleBatchOk = async () => {
|
|
|
|
|
|
const hide = message.loading("正在打包下载...", 0);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await downloadBinaryPost(
|
|
|
|
|
|
"/safetyEval/regulator/eval-report/batch-download",
|
|
|
|
|
|
{ ids: selectedRowKeys },
|
|
|
|
|
|
`安评报告批量下载_${dayjs().format("YYYYMMDDHHmmss")}.zip`,
|
|
|
|
|
|
);
|
|
|
|
|
|
message.success("批量操作已提交");
|
|
|
|
|
|
setBatchVisible(false);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
message.error(err.message || "批量下载失败");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleOpenExport = () => {
|
|
|
|
|
|
exportForm.setFieldsValue({
|
|
|
|
|
|
scope: selectedRowKeys.length ? "SELECTED" : "FILTERED",
|
|
|
|
|
|
});
|
|
|
|
|
|
setExportVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleExportOk = async () => {
|
|
|
|
|
|
const values = await exportForm.validateFields().catch(() => null);
|
|
|
|
|
|
if (!values) return;
|
|
|
|
|
|
const body = {
|
|
|
|
|
|
scope: values.scope,
|
|
|
|
|
|
...router.query,
|
|
|
|
|
|
};
|
|
|
|
|
|
delete body.current;
|
|
|
|
|
|
delete body.size;
|
|
|
|
|
|
delete body.archiveTimeRange;
|
|
|
|
|
|
if (values.scope === "SELECTED") {
|
|
|
|
|
|
if (!selectedRowKeys.length) {
|
|
|
|
|
|
message.warning("请先勾选需要导出的报告");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
body.ids = selectedRowKeys;
|
|
|
|
|
|
}
|
|
|
|
|
|
const hide = message.loading("正在导出目录...", 0);
|
|
|
|
|
|
try {
|
|
|
|
|
|
await downloadBinaryPost(
|
|
|
|
|
|
"/safetyEval/regulator/eval-report/export-catalog",
|
|
|
|
|
|
body,
|
|
|
|
|
|
`安评报告数据库目录_${dayjs().format("YYYYMMDDHHmmss")}.csv`,
|
|
|
|
|
|
);
|
|
|
|
|
|
message.success("目录导出已开始");
|
|
|
|
|
|
setExportVisible(false);
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
message.error(err.message || "导出失败");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
hide();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleOpenSpotcheckStart = (record) => {
|
|
|
|
|
|
setCurrentReport(record);
|
|
|
|
|
|
spotcheckForm.resetFields();
|
|
|
|
|
|
spotcheckForm.setFieldsValue({ checkResultCode: 1 });
|
|
|
|
|
|
setSpotcheckStartVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSpotcheckStartOk = async () => {
|
|
|
|
|
|
const values = await spotcheckForm.validateFields().catch(() => null);
|
|
|
|
|
|
if (!values) return;
|
|
|
|
|
|
if (values.checkResultCode === 2 && !values.rectifyRequire?.trim()) {
|
|
|
|
|
|
message.warning("不合格时必须填写整改要求");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const res = await props.regulatorSpotcheckStart({
|
|
|
|
|
|
reportId: currentReport.id,
|
|
|
|
|
|
checkResultCode: values.checkResultCode,
|
|
|
|
|
|
checkResultName: values.checkResultCode === 1 ? "合格" : "不合格",
|
|
|
|
|
|
checkOpinion: values.checkOpinion,
|
|
|
|
|
|
rectifyRequire: values.rectifyRequire,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
|
message.success("抽查已发起");
|
|
|
|
|
|
setSpotcheckStartVisible(false);
|
|
|
|
|
|
getData();
|
|
|
|
|
|
if (spotcheckVisible && currentReport?.id) {
|
|
|
|
|
|
const listRes = await props.regulatorSpotcheckList({
|
|
|
|
|
|
reportId: currentReport.id,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (listRes?.success !== false) setSpotcheckList(listRes?.data || []);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleOpenReview = (record) => {
|
|
|
|
|
|
setReviewRecord(record);
|
|
|
|
|
|
reviewForm.resetFields();
|
|
|
|
|
|
reviewForm.setFieldsValue({ reviewResultCode: 1 });
|
|
|
|
|
|
setReviewVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleReviewOk = async () => {
|
|
|
|
|
|
const values = await reviewForm.validateFields().catch(() => null);
|
|
|
|
|
|
if (!values) return;
|
|
|
|
|
|
const res = await props.regulatorSpotcheckReview({
|
|
|
|
|
|
spotcheckId: reviewRecord.id,
|
|
|
|
|
|
reviewResultCode: values.reviewResultCode,
|
|
|
|
|
|
reviewResultName: values.reviewResultCode === 1 ? "通过" : "不通过",
|
|
|
|
|
|
});
|
|
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
|
message.success("复核结果已提交");
|
|
|
|
|
|
setReviewVisible(false);
|
|
|
|
|
|
getData();
|
|
|
|
|
|
if (currentReport?.id) {
|
|
|
|
|
|
const listRes = await props.regulatorSpotcheckList({
|
|
|
|
|
|
reportId: currentReport.id,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (listRes?.success !== false) setSpotcheckList(listRes?.data || []);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const columns = [
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "报告编号",
|
|
|
|
|
|
dataIndex: "reportNo",
|
|
|
|
|
|
width: 150,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "报告名称",
|
|
|
|
|
|
dataIndex: "reportName",
|
|
|
|
|
|
width: 240,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
render: (val) => val || "-",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "报送机构",
|
|
|
|
|
|
dataIndex: "orgName",
|
|
|
|
|
|
width: 200,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
render: (val) => val || "-",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "评价类型",
|
|
|
|
|
|
dataIndex: "evalTypeName",
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
render: (val, record) =>
|
|
|
|
|
|
val || getOptionLabel(EVAL_TYPE_OPTIONS, record.evalTypeCode),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "行业",
|
|
|
|
|
|
dataIndex: "industryName",
|
|
|
|
|
|
width: 100,
|
|
|
|
|
|
render: (val, record) =>
|
|
|
|
|
|
val || getOptionLabel(INDUSTRY_OPTIONS, record.industryCode),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "签发日期",
|
|
|
|
|
|
dataIndex: "issueDate",
|
|
|
|
|
|
width: 120,
|
|
|
|
|
|
render: (val) => (val ? dayjs(val).format("YYYY-MM-DD") : "-"),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "报送时间",
|
|
|
|
|
|
dataIndex: "archiveTime",
|
|
|
|
|
|
width: 160,
|
|
|
|
|
|
render: (val) => (val ? dayjs(val).format("YYYY-MM-DD HH:mm") : "-"),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "操作",
|
|
|
|
|
|
width: 200,
|
|
|
|
|
|
fixed: "right",
|
|
|
|
|
|
render: (_, record) => (
|
2026-08-12 14:37:45 +08:00
|
|
|
|
<TableAction size={4} wrap>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<a onClick={() => handleViewDetail(record)}>查看报告</a>
|
|
|
|
|
|
<a onClick={() => handleOpenAi(record)}>AI分析</a>
|
2026-08-12 14:37:45 +08:00
|
|
|
|
</TableAction>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
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: 90,
|
|
|
|
|
|
render: (val, record) => {
|
2026-08-12 17:12:19 +08:00
|
|
|
|
if (record.checkResultCode === 1)
|
|
|
|
|
|
return <span className="redb-tag redb-tag-success">合格</span>;
|
|
|
|
|
|
if (record.checkResultCode === 2)
|
|
|
|
|
|
return <span className="redb-tag redb-tag-danger">不合格</span>;
|
2026-07-28 16:07:00 +08:00
|
|
|
|
return val || "-";
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "抽查意见",
|
|
|
|
|
|
dataIndex: "checkOpinion",
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
render: (val) => val || "-",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "整改要求",
|
|
|
|
|
|
dataIndex: "rectifyRequire",
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
render: (val) => val || "-",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "整改反馈",
|
|
|
|
|
|
dataIndex: "rectifyFeedback",
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
render: (val) => val || "-",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "复核结果",
|
|
|
|
|
|
dataIndex: "reviewResultName",
|
|
|
|
|
|
width: 100,
|
|
|
|
|
|
render: (val, record) => {
|
2026-08-12 17:12:19 +08:00
|
|
|
|
if (record.reviewResultCode === 1)
|
|
|
|
|
|
return <span className="redb-tag redb-tag-success">通过</span>;
|
|
|
|
|
|
if (record.reviewResultCode === 2)
|
|
|
|
|
|
return <span className="redb-tag redb-tag-danger">不通过</span>;
|
2026-07-28 16:07:00 +08:00
|
|
|
|
return val || "-";
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "操作",
|
|
|
|
|
|
width: 100,
|
|
|
|
|
|
fixed: "right",
|
|
|
|
|
|
render: (_, record) => {
|
|
|
|
|
|
// 有整改反馈且尚未复核时可复核
|
|
|
|
|
|
if (record.rectifyFeedback && !record.reviewResultCode) {
|
2026-08-12 17:12:19 +08:00
|
|
|
|
return <a onClick={() => handleOpenReview(record)}>复核</a>;
|
2026-07-28 16:07:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
return "-";
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const currentYear = dayjs().year();
|
|
|
|
|
|
|
|
|
|
|
|
const levelClass = useMemo(
|
|
|
|
|
|
() => ({
|
|
|
|
|
|
重点: "redb-tag-danger",
|
|
|
|
|
|
一般: "redb-tag-warning",
|
|
|
|
|
|
低: "redb-tag-success",
|
|
|
|
|
|
}),
|
|
|
|
|
|
[],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<ConfigProvider theme={PROTO_THEME}>
|
|
|
|
|
|
<PageLayout title="安评报告数据库">
|
|
|
|
|
|
<div className="redb-page">
|
2026-08-17 14:43:35 +08:00
|
|
|
|
<div className="redb-summary-cards">
|
|
|
|
|
|
<div className="redb-summary-card">
|
|
|
|
|
|
<div className="label">近三年报告</div>
|
|
|
|
|
|
<div className="value">
|
|
|
|
|
|
<strong>{statData.recentThreeYearCount ?? "-"}份</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="redb-summary-card">
|
|
|
|
|
|
<div className="label">{currentYear}年度</div>
|
|
|
|
|
|
<div className="value">
|
|
|
|
|
|
<strong>{statData.currentYearCount ?? "-"}份</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
{statData.submittedTotalCount != null && (
|
2026-08-17 14:43:35 +08:00
|
|
|
|
<div className="redb-summary-card">
|
|
|
|
|
|
<div className="label">已报送合计</div>
|
|
|
|
|
|
<div className="value">
|
|
|
|
|
|
<strong>{statData.submittedTotalCount}份</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<SearchForm
|
|
|
|
|
|
form={searchForm}
|
|
|
|
|
|
loading={false}
|
|
|
|
|
|
style={{ marginBottom: 12 }}
|
|
|
|
|
|
formLine={[
|
|
|
|
|
|
<Form.Item key="keyword" name="keyword">
|
|
|
|
|
|
<ControlWrapper.Input
|
|
|
|
|
|
label="报告名称/编号"
|
|
|
|
|
|
placeholder="输入报告名称或编号"
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>,
|
|
|
|
|
|
<Form.Item key="orgName" name="orgName">
|
|
|
|
|
|
<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={EVAL_TYPE_OPTIONS}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>,
|
|
|
|
|
|
<Form.Item key="industryCode" name="industryCode">
|
|
|
|
|
|
<ControlWrapper.Select
|
|
|
|
|
|
label="行业类别"
|
|
|
|
|
|
placeholder="全部"
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
options={INDUSTRY_OPTIONS}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>,
|
|
|
|
|
|
<Form.Item key="archiveTimeRange" name="archiveTimeRange">
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<RangePicker
|
|
|
|
|
|
style={{ width: "100%" }}
|
|
|
|
|
|
placeholder={["报送时间起", "报送时间止"]}
|
|
|
|
|
|
/>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
</Form.Item>,
|
|
|
|
|
|
]}
|
|
|
|
|
|
onFinish={handleSearch}
|
|
|
|
|
|
onReset={handleReset}
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="redb-toolbar">
|
|
|
|
|
|
<div className="redb-toolbar-left">
|
|
|
|
|
|
<span className="redb-total">共 {total} 条报告记录</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="redb-toolbar-right">
|
2026-08-12 14:37:45 +08:00
|
|
|
|
<Button type="primary" size="small" onClick={handleOpenSpotCheck}>
|
|
|
|
|
|
抽检
|
|
|
|
|
|
</Button>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
icon={<DownloadOutlined />}
|
|
|
|
|
|
onClick={handleBatchDownload}
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
批量下载
|
|
|
|
|
|
</Button>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
icon={<ExportOutlined />}
|
|
|
|
|
|
onClick={handleOpenExport}
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
导出目录
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Table
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
columns={columns}
|
|
|
|
|
|
dataSource={dataSource}
|
|
|
|
|
|
loading={regulatorEvalReportPageLoading}
|
|
|
|
|
|
rowSelection={{
|
|
|
|
|
|
selectedRowKeys,
|
|
|
|
|
|
onChange: (keys) => {
|
|
|
|
|
|
setSelectedRowKeys(keys);
|
|
|
|
|
|
},
|
|
|
|
|
|
}}
|
|
|
|
|
|
scroll={{ y: props.scrollY, x: 1400 }}
|
|
|
|
|
|
pagination={{
|
|
|
|
|
|
total,
|
|
|
|
|
|
current: Number(router.query.current) || 1,
|
|
|
|
|
|
pageSize: Number(router.query.size) || 20,
|
|
|
|
|
|
showSizeChanger: true,
|
|
|
|
|
|
showQuickJumper: true,
|
|
|
|
|
|
pageSizeOptions: ["20", "50", "100"],
|
|
|
|
|
|
showTotal: (count) => `共 ${count} 条`,
|
|
|
|
|
|
}}
|
|
|
|
|
|
onChange={handlePageChange}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 查看报告 — 对齐原型 reportLibraryDetail */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title={
|
|
|
|
|
|
detailData
|
|
|
|
|
|
? `查看安全评价报告 - ${detailData.reportNo || ""}`
|
|
|
|
|
|
: "查看安全评价报告"
|
|
|
|
|
|
}
|
|
|
|
|
|
open={detailVisible}
|
|
|
|
|
|
onCancel={() => setDetailVisible(false)}
|
|
|
|
|
|
width={980}
|
2026-08-13 10:08:53 +08:00
|
|
|
|
destroyOnHide
|
2026-07-28 16:07:00 +08:00
|
|
|
|
className="redb-modal"
|
|
|
|
|
|
footer={
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button onClick={() => setDetailVisible(false)}>关闭</Button>
|
|
|
|
|
|
{detailData && (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
onClick={() => handleDownloadOne(detailData)}
|
|
|
|
|
|
disabled={!detailData.fileUrl && !detailData.id}
|
|
|
|
|
|
>
|
|
|
|
|
|
下载报告
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
{detailData ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<ModalSection
|
|
|
|
|
|
title="报告基本信息"
|
|
|
|
|
|
desc="展示评价机构上传的正式安全评价报告信息。"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ProtoTable
|
|
|
|
|
|
kv
|
|
|
|
|
|
headers={["字段", "内容"]}
|
|
|
|
|
|
rows={[
|
|
|
|
|
|
["报告编号", detailData.reportNo || "-"],
|
|
|
|
|
|
["报告名称", detailData.reportName || "-"],
|
|
|
|
|
|
["评价机构", detailData.orgName || "-"],
|
|
|
|
|
|
[
|
|
|
|
|
|
"评价类型",
|
|
|
|
|
|
detailData.evalTypeName ||
|
2026-08-12 17:12:19 +08:00
|
|
|
|
getOptionLabel(
|
|
|
|
|
|
EVAL_TYPE_OPTIONS,
|
|
|
|
|
|
detailData.evalTypeCode,
|
|
|
|
|
|
),
|
2026-07-28 16:07:00 +08:00
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
"所属行业",
|
|
|
|
|
|
detailData.industryName ||
|
2026-08-12 17:12:19 +08:00
|
|
|
|
getOptionLabel(
|
|
|
|
|
|
INDUSTRY_OPTIONS,
|
|
|
|
|
|
detailData.industryCode,
|
|
|
|
|
|
),
|
2026-07-28 16:07:00 +08:00
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
"签发日期",
|
|
|
|
|
|
detailData.issueDate
|
|
|
|
|
|
? dayjs(detailData.issueDate).format("YYYY-MM-DD")
|
|
|
|
|
|
: "-",
|
|
|
|
|
|
],
|
|
|
|
|
|
[
|
|
|
|
|
|
"报送时间",
|
|
|
|
|
|
detailData.archiveTime
|
2026-08-12 17:12:19 +08:00
|
|
|
|
? dayjs(detailData.archiveTime).format(
|
|
|
|
|
|
"YYYY-MM-DD HH:mm",
|
|
|
|
|
|
)
|
2026-07-28 16:07:00 +08:00
|
|
|
|
: "-",
|
|
|
|
|
|
],
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</ModalSection>
|
|
|
|
|
|
<ModalSection
|
|
|
|
|
|
title="报告文件"
|
|
|
|
|
|
desc="支持在线查看和下载正式报告。"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ProtoTable
|
|
|
|
|
|
headers={["文件名称", "文件类型", "版本"]}
|
|
|
|
|
|
rows={[
|
|
|
|
|
|
[
|
2026-08-13 10:08:53 +08:00
|
|
|
|
<PreviewUrlButton
|
|
|
|
|
|
url={detailData.fileUrl}
|
2026-08-04 15:56:14 +08:00
|
|
|
|
>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
{detailData.fileName ||
|
|
|
|
|
|
detailData.reportName ||
|
|
|
|
|
|
"正式安全评价报告.pdf"}
|
2026-08-13 10:08:53 +08:00
|
|
|
|
</PreviewUrlButton>,
|
2026-07-28 16:07:00 +08:00
|
|
|
|
getFileExtLabel(detailData.fileName),
|
|
|
|
|
|
"V1.0",
|
|
|
|
|
|
],
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</ModalSection>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<ModalSection title="合同文件" desc="查看上传的合同文件。">
|
2026-08-04 15:56:14 +08:00
|
|
|
|
<ProtoTable
|
|
|
|
|
|
headers={["文件名称", "文件类型"]}
|
|
|
|
|
|
rows={parseFileList(detailData.contractFileUrls).map((f) => [
|
2026-08-13 10:08:53 +08:00
|
|
|
|
<PreviewUrlButton key={f.url} url={f.url}>
|
2026-08-04 15:56:14 +08:00
|
|
|
|
{f.name || "未命名文件"}
|
2026-08-13 10:08:53 +08:00
|
|
|
|
</PreviewUrlButton>,
|
2026-08-04 15:56:14 +08:00
|
|
|
|
getFileExtLabel(f.name || f.url),
|
|
|
|
|
|
])}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</ModalSection>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<ModalSection title="其他过程文档" desc="查看上传的过程文档。">
|
2026-08-04 15:56:14 +08:00
|
|
|
|
<ProtoTable
|
|
|
|
|
|
headers={["文件名称", "文件类型"]}
|
|
|
|
|
|
rows={parseFileList(detailData.processDocUrls).map((f) => [
|
2026-08-13 10:08:53 +08:00
|
|
|
|
<PreviewUrlButton key={f.url} url={f.url}>
|
2026-08-04 15:56:14 +08:00
|
|
|
|
{f.name || "未命名文件"}
|
2026-08-13 10:08:53 +08:00
|
|
|
|
</PreviewUrlButton>,
|
2026-08-04 15:56:14 +08:00
|
|
|
|
getFileExtLabel(f.name || f.url),
|
|
|
|
|
|
])}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</ModalSection>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="redb-loading">
|
|
|
|
|
|
{regulatorEvalReportDetailLoading ? "加载中..." : "暂无数据"}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
{/* AI分析 — 对齐原型 reportAiAnalysis */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title={
|
|
|
|
|
|
aiData
|
|
|
|
|
|
? `AI报告分析 - ${aiData.record?.reportNo || aiData.record?.reportName || ""}`
|
|
|
|
|
|
: "AI报告分析"
|
|
|
|
|
|
}
|
|
|
|
|
|
open={aiVisible}
|
|
|
|
|
|
onCancel={() => setAiVisible(false)}
|
|
|
|
|
|
width={980}
|
|
|
|
|
|
destroyOnClose
|
|
|
|
|
|
className="redb-modal"
|
|
|
|
|
|
footer={
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button onClick={() => setAiVisible(false)}>关闭</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
message.success("AI分析结果文件已生成");
|
|
|
|
|
|
setAiVisible(false);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
生成AI分析报告
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
{aiData && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<ModalSection title="分析概览" desc={aiData.analysis.overview}>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<div
|
|
|
|
|
|
className="redb-summary-strip"
|
2026-08-17 11:22:53 +08:00
|
|
|
|
style={{ marginTop: "11.2px", marginBottom: 0 }}
|
2026-08-12 17:12:19 +08:00
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
{aiData.analysis.metrics.map((m) => (
|
|
|
|
|
|
<span key={m.label}>
|
|
|
|
|
|
{m.label}:<strong>{m.value}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</ModalSection>
|
|
|
|
|
|
<div className="redb-data-table">
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>分析维度</th>
|
|
|
|
|
|
<th>AI识别结果</th>
|
|
|
|
|
|
<th>风险等级</th>
|
|
|
|
|
|
<th>建议人工核查内容</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
{aiData.analysis.rows.map((row) => (
|
|
|
|
|
|
<tr key={row.dim}>
|
|
|
|
|
|
<td>{row.dim}</td>
|
|
|
|
|
|
<td>{row.result}</td>
|
|
|
|
|
|
<td>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<span
|
|
|
|
|
|
className={`redb-tag ${levelClass[row.level] || ""}`}
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
{row.level}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td>{row.advice}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="redb-ai-note">
|
|
|
|
|
|
<strong>AI结论:</strong>
|
|
|
|
|
|
{aiData.analysis.conclusion}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 导出目录 — 对齐原型 export */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="安评报告数据库目录"
|
|
|
|
|
|
open={exportVisible}
|
|
|
|
|
|
onCancel={() => setExportVisible(false)}
|
|
|
|
|
|
onOk={handleExportOk}
|
|
|
|
|
|
okText="生成文件"
|
|
|
|
|
|
cancelText="关闭"
|
|
|
|
|
|
width={720}
|
|
|
|
|
|
destroyOnClose
|
|
|
|
|
|
className="redb-modal"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ModalSection
|
|
|
|
|
|
title="导出设置"
|
|
|
|
|
|
desc="导出当前查询范围内的数据,系统将记录导出人和导出时间。"
|
|
|
|
|
|
>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Form
|
|
|
|
|
|
form={exportForm}
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
preserve={false}
|
|
|
|
|
|
className="redb-form-grid"
|
|
|
|
|
|
scrollToFirstError
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="scope"
|
|
|
|
|
|
label="导出范围"
|
|
|
|
|
|
rules={[{ required: true, message: "请选择导出范围" }]}
|
|
|
|
|
|
className="redb-field"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
options={[
|
|
|
|
|
|
{ label: "当前勾选", value: "SELECTED" },
|
|
|
|
|
|
{ label: "当前查询结果", value: "FILTERED" },
|
|
|
|
|
|
{ label: "全部数据", value: "ALL" },
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="fileFormat"
|
|
|
|
|
|
label="文件格式"
|
|
|
|
|
|
initialValue="CSV"
|
|
|
|
|
|
className="redb-field"
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
<Select
|
|
|
|
|
|
options={[
|
|
|
|
|
|
{ label: "CSV", value: "CSV" },
|
|
|
|
|
|
{ label: "Excel", value: "Excel" },
|
|
|
|
|
|
{ label: "PDF", value: "PDF" },
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="exportRemark"
|
|
|
|
|
|
label="导出说明"
|
|
|
|
|
|
className="redb-field redb-field-full"
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
<TextArea rows={3} placeholder="选填" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</ModalSection>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 批量下载 — 对齐原型 reportBatchAction */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="批量下载"
|
|
|
|
|
|
open={batchVisible}
|
|
|
|
|
|
onCancel={() => setBatchVisible(false)}
|
|
|
|
|
|
onOk={handleBatchOk}
|
|
|
|
|
|
okText="确认执行"
|
|
|
|
|
|
cancelText="关闭"
|
|
|
|
|
|
width={720}
|
|
|
|
|
|
destroyOnClose
|
|
|
|
|
|
className="redb-modal"
|
|
|
|
|
|
>
|
|
|
|
|
|
<ModalSection
|
|
|
|
|
|
title="批量操作确认"
|
|
|
|
|
|
desc="批量分类将按年度、行业和评价类型重新校验目录;批量下载将生成所选报告的压缩文件。"
|
|
|
|
|
|
>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Form
|
|
|
|
|
|
form={batchForm}
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
preserve={false}
|
|
|
|
|
|
className="redb-form-grid"
|
|
|
|
|
|
scrollToFirstError
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="scopeLabel"
|
|
|
|
|
|
label="操作范围"
|
|
|
|
|
|
className="redb-field"
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
<Input disabled />
|
|
|
|
|
|
</Form.Item>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="keepRecord"
|
|
|
|
|
|
label="保留原分类记录"
|
|
|
|
|
|
className="redb-field"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
options={[
|
|
|
|
|
|
{ label: "是", value: "是" },
|
|
|
|
|
|
{ label: "否", value: "否" },
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
</Form.Item>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="remark"
|
|
|
|
|
|
label="操作说明"
|
|
|
|
|
|
className="redb-field redb-field-full"
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
<TextArea rows={3} placeholder="选填" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</ModalSection>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 抽查记录 */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title={
|
|
|
|
|
|
currentReport
|
|
|
|
|
|
? `抽查记录 — ${currentReport.reportName || currentReport.reportNo || ""}`
|
|
|
|
|
|
: "抽查记录"
|
|
|
|
|
|
}
|
|
|
|
|
|
open={spotcheckVisible}
|
|
|
|
|
|
onCancel={() => setSpotcheckVisible(false)}
|
|
|
|
|
|
width={980}
|
|
|
|
|
|
destroyOnClose
|
|
|
|
|
|
className="redb-modal"
|
|
|
|
|
|
footer={
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button onClick={() => setSpotcheckVisible(false)}>关闭</Button>
|
|
|
|
|
|
{currentReport?.reportStatusCode === 1 && (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
onClick={() => handleOpenSpotcheckStart(currentReport)}
|
|
|
|
|
|
>
|
|
|
|
|
|
发起抽查
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Table
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
columns={spotcheckColumns}
|
|
|
|
|
|
dataSource={spotcheckList}
|
|
|
|
|
|
loading={regulatorSpotcheckListLoading}
|
|
|
|
|
|
pagination={false}
|
|
|
|
|
|
scroll={{ x: 1100 }}
|
|
|
|
|
|
locale={{ emptyText: "暂无抽查记录" }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 发起抽查 */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title={
|
|
|
|
|
|
currentReport
|
|
|
|
|
|
? `发起报告抽查 - ${currentReport.reportNo || ""}`
|
|
|
|
|
|
: "发起报告抽查"
|
|
|
|
|
|
}
|
|
|
|
|
|
open={spotcheckStartVisible}
|
|
|
|
|
|
onCancel={() => setSpotcheckStartVisible(false)}
|
|
|
|
|
|
onOk={handleSpotcheckStartOk}
|
|
|
|
|
|
confirmLoading={regulatorSpotcheckStartLoading}
|
|
|
|
|
|
okText="提交抽查"
|
|
|
|
|
|
width={640}
|
|
|
|
|
|
destroyOnClose
|
|
|
|
|
|
className="redb-modal"
|
|
|
|
|
|
>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Form
|
|
|
|
|
|
form={spotcheckForm}
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
preserve={false}
|
|
|
|
|
|
scrollToFirstError
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="checkResultCode"
|
|
|
|
|
|
label="抽查结果"
|
|
|
|
|
|
rules={[{ required: true, message: "请选择抽查结果" }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
options={[
|
|
|
|
|
|
{ label: "合格", value: 1 },
|
|
|
|
|
|
{ label: "不合格", value: 2 },
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item name="checkOpinion" label="抽查意见">
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<TextArea
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
placeholder="填写抽查意见"
|
|
|
|
|
|
maxLength={500}
|
|
|
|
|
|
showCount
|
|
|
|
|
|
/>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
noStyle
|
2026-08-12 17:12:19 +08:00
|
|
|
|
shouldUpdate={(prev, cur) =>
|
|
|
|
|
|
prev.checkResultCode !== cur.checkResultCode
|
|
|
|
|
|
}
|
2026-07-28 16:07:00 +08:00
|
|
|
|
>
|
|
|
|
|
|
{({ getFieldValue }) =>
|
|
|
|
|
|
getFieldValue("checkResultCode") === 2 ? (
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="rectifyRequire"
|
|
|
|
|
|
label="整改要求"
|
2026-08-12 17:12:19 +08:00
|
|
|
|
rules={[
|
|
|
|
|
|
{ required: true, message: "不合格须填写整改要求" },
|
|
|
|
|
|
]}
|
2026-07-28 16:07:00 +08:00
|
|
|
|
>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<TextArea
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
placeholder="请填写整改要求"
|
|
|
|
|
|
maxLength={500}
|
|
|
|
|
|
showCount
|
|
|
|
|
|
/>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
) : null
|
|
|
|
|
|
}
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 复核 */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="抽查复核"
|
|
|
|
|
|
open={reviewVisible}
|
|
|
|
|
|
onCancel={() => setReviewVisible(false)}
|
|
|
|
|
|
onOk={handleReviewOk}
|
|
|
|
|
|
confirmLoading={regulatorSpotcheckReviewLoading}
|
|
|
|
|
|
okText="提交复核结果"
|
|
|
|
|
|
width={520}
|
|
|
|
|
|
destroyOnClose
|
|
|
|
|
|
className="redb-modal"
|
|
|
|
|
|
>
|
|
|
|
|
|
{reviewRecord && (
|
|
|
|
|
|
<div className="redb-note" style={{ marginBottom: 12 }}>
|
|
|
|
|
|
<div>整改反馈:{reviewRecord.rectifyFeedback || "-"}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Form
|
|
|
|
|
|
form={reviewForm}
|
|
|
|
|
|
layout="vertical"
|
|
|
|
|
|
preserve={false}
|
|
|
|
|
|
scrollToFirstError
|
|
|
|
|
|
>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="reviewResultCode"
|
|
|
|
|
|
label="复核结果"
|
|
|
|
|
|
rules={[{ required: true, message: "请选择复核结果" }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
options={[
|
|
|
|
|
|
{ label: "通过", value: 1 },
|
|
|
|
|
|
{ label: "不通过", value: 2 },
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Modal>
|
2026-08-12 14:37:45 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 抽检 - 随机抽取并生成检查单 */}
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="安评报告随机抽检"
|
|
|
|
|
|
open={spotCheckVisible}
|
|
|
|
|
|
onCancel={() => setSpotCheckVisible(false)}
|
|
|
|
|
|
footer={
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button onClick={() => setSpotCheckVisible(false)}>关闭</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
loading={regulatorSpotCheckReportLoading}
|
|
|
|
|
|
onClick={handleSpotCheck}
|
|
|
|
|
|
>
|
|
|
|
|
|
随机抽取并生成检查单
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
}
|
|
|
|
|
|
width={980}
|
|
|
|
|
|
destroyOnHidden
|
|
|
|
|
|
className="redb-modal"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="redb-modal-section">
|
|
|
|
|
|
<h4>抽检设置</h4>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<p>
|
|
|
|
|
|
从选定评价机构已报送的安全评价报告中随机抽取,生成监督检查单。
|
|
|
|
|
|
</p>
|
2026-08-12 14:37:45 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<Form form={spotCheckForm} layout="vertical" preserve={false}>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={24}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="orgId"
|
|
|
|
|
|
label="评价机构"
|
|
|
|
|
|
rules={[{ required: true, message: "请选择评价机构" }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
showSearch
|
|
|
|
|
|
optionFilterProp="label"
|
|
|
|
|
|
placeholder="请选择评价机构"
|
|
|
|
|
|
options={orgOptions}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="randomNumber"
|
|
|
|
|
|
label="检查份数"
|
|
|
|
|
|
rules={[{ required: true, message: "请输入检查份数" }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<InputNumber min={1} max={10} style={{ width: "100%" }} />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="checkType" label="检查类型">
|
|
|
|
|
|
<Select options={CHECK_TYPE_OPTIONS} />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="planCheckDate" label="计划检查日期">
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<DatePicker
|
|
|
|
|
|
style={{ width: "100%" }}
|
|
|
|
|
|
placeholder="选择日期"
|
|
|
|
|
|
/>
|
2026-08-12 14:37:45 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Form.Item name="checkLeader" label="检查负责人">
|
|
|
|
|
|
<Input placeholder="请输入检查负责人" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={24}>
|
|
|
|
|
|
<Form.Item name="checkLocation" label="检查地点">
|
|
|
|
|
|
<Input placeholder="请输入检查地点" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={24}>
|
|
|
|
|
|
<Form.Item name="checkMembers" label="检查组成员">
|
|
|
|
|
|
<Input placeholder="请输入检查组成员" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={24}>
|
|
|
|
|
|
<Form.Item name="checkBasis" label="补充检查依据">
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<TextArea
|
|
|
|
|
|
rows={2}
|
|
|
|
|
|
placeholder="可填写本次专项检查、年度计划或属地监管文件依据"
|
|
|
|
|
|
/>
|
2026-08-12 14:37:45 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
|
|
|
|
|
|
{spotCheckData.length > 0 && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div className="redb-modal-section">
|
|
|
|
|
|
<h4>随机抽取结果</h4>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
本次从"
|
|
|
|
|
|
{orgOptions.find(
|
|
|
|
|
|
(o) => o.value === spotCheckForm.getFieldValue("orgId"),
|
|
|
|
|
|
)?.label || ""}
|
|
|
|
|
|
"报告库中随机抽取 {spotCheckData.length}{" "}
|
|
|
|
|
|
份,抽取结果已写入检查单。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="redb-modal-section">
|
|
|
|
|
|
<div className="redb-spotcheck-header">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h4 style={{ margin: 0 }}>随机抽取报告</h4>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Space>
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
loading={viewChecklistLoading}
|
|
|
|
|
|
onClick={handleViewChecklist}
|
|
|
|
|
|
>
|
|
|
|
|
|
查看检查单
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
onClick={handleDownloadChecklist}
|
|
|
|
|
|
>
|
|
|
|
|
|
下载检查单
|
|
|
|
|
|
</Button>
|
2026-08-12 14:37:45 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
loading={regulatorSpotCheckReportLoading}
|
|
|
|
|
|
onClick={handleSpotCheck}
|
|
|
|
|
|
>
|
|
|
|
|
|
重新抽取
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Table
|
2026-08-12 17:12:19 +08:00
|
|
|
|
rowKey="archiveTime"
|
2026-08-12 14:37:45 +08:00
|
|
|
|
size="small"
|
|
|
|
|
|
columns={[
|
2026-08-12 17:12:19 +08:00
|
|
|
|
{
|
|
|
|
|
|
title: "报告名称",
|
|
|
|
|
|
dataIndex: "reportName",
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "被检查机构",
|
|
|
|
|
|
dataIndex: "orgName",
|
|
|
|
|
|
width: 200,
|
|
|
|
|
|
ellipsis: true,
|
|
|
|
|
|
},
|
2026-08-12 14:37:45 +08:00
|
|
|
|
{
|
|
|
|
|
|
title: "操作",
|
|
|
|
|
|
width: 90,
|
|
|
|
|
|
render: (_, record) => (
|
2026-08-12 17:12:19 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="small"
|
2026-08-12 17:26:27 +08:00
|
|
|
|
onClick={() => handleViewSpotDetail(record)}
|
2026-08-12 17:12:19 +08:00
|
|
|
|
>
|
2026-08-12 14:37:45 +08:00
|
|
|
|
查看
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
]}
|
|
|
|
|
|
dataSource={spotCheckData}
|
|
|
|
|
|
pagination={false}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Modal>
|
2026-08-12 17:26:27 +08:00
|
|
|
|
<SpotCheckDetailModal
|
|
|
|
|
|
open={spotDetailVisible}
|
|
|
|
|
|
record={spotDetailData}
|
|
|
|
|
|
onCancel={() => setSpotDetailVisible(false)}
|
|
|
|
|
|
/>
|
2026-07-28 16:07:00 +08:00
|
|
|
|
</PageLayout>
|
|
|
|
|
|
</ConfigProvider>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default Connect(
|
2026-08-12 14:37:45 +08:00
|
|
|
|
[NS_REGULATOR_EVAL_REPORT, NS_ORG_INFO],
|
2026-07-28 16:07:00 +08:00
|
|
|
|
true,
|
|
|
|
|
|
)(AntdTableFuncControl(EvalReportDatabase));
|