feat
parent
cc68fb3c9e
commit
17f193025b
|
|
@ -10,19 +10,17 @@ import {
|
|||
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 "~/components/SearchForm";
|
||||
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
|
||||
import PreviewUrlButton from "~/components/PreviewUrlButton";
|
||||
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
|
||||
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
|
||||
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||
|
|
@ -41,7 +39,25 @@ const { TextArea } = Input;
|
|||
|
||||
const UPLOAD_ACTION = `${window.process?.env?.app?.API_HOST || ""}/safetyEval/file/upload`;
|
||||
|
||||
/** 报告文件类型配置 */
|
||||
const FILE_TYPE_CONFIG = {
|
||||
main: {
|
||||
label: "报告正文",
|
||||
accept: ".pdf,.doc,.docx,.zip,.rar,.7z",
|
||||
acceptSet: new Set(["pdf", "doc", "docx", "zip", "rar", "7z"]),
|
||||
rule: "正式签字盖章版正文",
|
||||
format: "PDF、Word、ZIP、RAR、7Z,单个文件不超过100MB",
|
||||
},
|
||||
attachment: {
|
||||
label: "报告附件",
|
||||
accept: ".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png,.zip,.rar,.7z",
|
||||
acceptSet: new Set(["pdf", "doc", "docx", "xls", "xlsx", "jpg", "jpeg", "png", "zip", "rar", "7z"]),
|
||||
rule: "图纸、检测报告、签字页及支撑资料",
|
||||
format: "PDF、Office、图片、ZIP、RAR、7Z",
|
||||
},
|
||||
};
|
||||
|
||||
let fileInputCounter = 0;
|
||||
|
||||
/** 保密属性(OpenAPI 仅示例 NORMAL) */
|
||||
const SECRET_LEVEL_OPTIONS = [
|
||||
|
|
@ -85,30 +101,6 @@ const getYearOptions = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const getFileExt = (name = "") => {
|
||||
const parts = String(name).split(".");
|
||||
return parts.length > 1 ? parts.pop().toUpperCase() : "PDF";
|
||||
};
|
||||
|
||||
/** 解析 JSON 文件列表,使用 PreviewUrlButton 渲染预览链接 */
|
||||
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} style={{ marginBottom: 4 }}>
|
||||
|
||||
<PreviewUrlButton url={f.url} >
|
||||
<a>{f.name || "未命名文件"}</a>
|
||||
</PreviewUrlButton>
|
||||
</div>
|
||||
));
|
||||
} catch {
|
||||
return jsonStr;
|
||||
}
|
||||
};
|
||||
|
||||
const getOptionLabel = (options, value) =>
|
||||
options.find((item) => item.value === value)?.label || value || "-";
|
||||
|
||||
|
|
@ -131,19 +123,16 @@ const EvalReportLibrary = (props) => {
|
|||
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([]);
|
||||
// 变更时间:2026-08-11 变更原因:批量报送需记录勾选的报告主键
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
|
||||
const [batchSubmitting, setBatchSubmitting] = useState(false);
|
||||
const [reportFiles, setReportFiles] = useState([]);
|
||||
|
||||
const {
|
||||
evalReportPageLoading,
|
||||
|
|
@ -153,6 +142,9 @@ const EvalReportLibrary = (props) => {
|
|||
evalReportSpotcheckRectifyLoading,
|
||||
} = props.safetyEvalBusiness;
|
||||
|
||||
// 查看模式:复用上传弹窗展示详情,表单整体禁用
|
||||
const viewMode = detailData !== null;
|
||||
|
||||
const fetchSummary = async () => {
|
||||
const res = await props.evalReportSummary();
|
||||
if (res?.success !== false) {
|
||||
|
|
@ -200,122 +192,116 @@ const EvalReportLibrary = (props) => {
|
|||
};
|
||||
|
||||
const handleOpenAdd = () => {
|
||||
setUploadedFile(null);
|
||||
setContractFiles([]);
|
||||
setProcessDocFiles([]);
|
||||
setDetailData(null);
|
||||
uploadForm.resetFields();
|
||||
uploadForm.setFieldsValue({
|
||||
reportYear: dayjs().year(),
|
||||
evalTypeCode: "PRE",
|
||||
|
||||
secretLevelCode: "NORMAL",
|
||||
issueDate: dayjs(),
|
||||
submitToRegulator: 0,
|
||||
});
|
||||
fileInputCounter = 0;
|
||||
setReportFiles([
|
||||
{ id: ++fileInputCounter, type: "main", url: "", fileName: "" },
|
||||
{ id: ++fileInputCounter, type: "attachment", url: "", fileName: "" },
|
||||
]);
|
||||
setAddVisible(true);
|
||||
};
|
||||
|
||||
const handleFileUploadChange = (info) => {
|
||||
if (info.file.status === "uploading") return;
|
||||
const addReportFileRow = () => {
|
||||
setReportFiles((prev) => [
|
||||
...prev,
|
||||
{ id: ++fileInputCounter, type: "attachment", url: "", fileName: "" },
|
||||
]);
|
||||
message.success("已添加一条文件记录");
|
||||
};
|
||||
|
||||
const removeReportFileRow = (id) => {
|
||||
setReportFiles((prev) => {
|
||||
if (prev.length <= 1) {
|
||||
message.warning("至少保留一条文件记录");
|
||||
return prev;
|
||||
}
|
||||
return prev.filter((f) => f.id !== id);
|
||||
});
|
||||
};
|
||||
|
||||
const changeReportFileType = (id, type) => {
|
||||
setReportFiles((prev) =>
|
||||
prev.map((f) => (f.id === id ? { ...f, type, url: "", fileName: "", uploading: false } : f)),
|
||||
);
|
||||
};
|
||||
|
||||
const handleReportFileUpload = (id, info) => {
|
||||
if (info.file.status === "uploading") {
|
||||
setReportFiles((prev) =>
|
||||
prev.map((f) => (f.id === id ? { ...f, fileName: info.file.name, uploading: true } : f)),
|
||||
);
|
||||
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) {
|
||||
const data = info.file.response?.data;
|
||||
const url = data?.url || data?.fileUrl || "";
|
||||
const name = data?.name || data?.fileName || info.file.name;
|
||||
if (!url) {
|
||||
message.error("上传响应中未获取到文件地址");
|
||||
return;
|
||||
}
|
||||
setUploadedFile({ url: fileUrl, name: fileName });
|
||||
uploadForm.setFieldsValue({
|
||||
fileUrl,
|
||||
fileName,
|
||||
reportName:
|
||||
uploadForm.getFieldValue("reportName") ||
|
||||
fileName.replace(/\.[^.]+$/, ""),
|
||||
});
|
||||
message.success(`${fileName} 上传成功`);
|
||||
const { createdTime, size } = data || {};
|
||||
setReportFiles((prev) =>
|
||||
prev.map((f) =>
|
||||
f.id === id
|
||||
? { ...f, url, fileName: name, createdTime, size, uploading: false }
|
||||
: f,
|
||||
),
|
||||
);
|
||||
message.success(`${name} 上传成功`);
|
||||
} else if (info.file.status === "error") {
|
||||
setReportFiles((prev) =>
|
||||
prev.map((f) => (f.id === id ? { ...f, fileName: "", uploading: false } : f)),
|
||||
);
|
||||
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("请选择需要上传的正式报告文件");
|
||||
const uploading = reportFiles.some((r) => r.uploading);
|
||||
if (uploading) {
|
||||
message.warning("文件正在上传中,请稍候");
|
||||
return;
|
||||
}
|
||||
const doneFiles = reportFiles.filter((r) => r.url);
|
||||
const mainFiles = doneFiles.filter((r) => r.type === "main");
|
||||
if (mainFiles.length === 0) {
|
||||
message.warning("请至少上传1个报告正文");
|
||||
return;
|
||||
}
|
||||
const fileUrlArr = doneFiles.map((r) => ({
|
||||
type: r.type,
|
||||
url: r.url,
|
||||
name: r.fileName,
|
||||
createdTime: r.createdTime,
|
||||
size: r.size,
|
||||
}));
|
||||
const payload = {
|
||||
...values,
|
||||
reportNo: values.reportNo?.trim(),
|
||||
reportName: values.reportName?.trim(),
|
||||
reportYear: values.reportYear,
|
||||
evalTypeCode: values.evalTypeCode,
|
||||
evalTypeName: getOptionLabel(
|
||||
EVAL_TYPE_OPTIONS,
|
||||
values.evalTypeCode,
|
||||
),
|
||||
industryCode: values.industryCode,
|
||||
industryName: getOptionLabel(
|
||||
QUALIFICATION_INDUSTRY_OPTIONS,
|
||||
values.industryCode,
|
||||
),
|
||||
evalTypeName: getOptionLabel(EVAL_TYPE_OPTIONS, values.evalTypeCode),
|
||||
industryName: getOptionLabel(QUALIFICATION_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,
|
||||
fileUrl: JSON.stringify(fileUrlArr),
|
||||
secretLevelName: getOptionLabel(SECRET_LEVEL_OPTIONS, values.secretLevelCode),
|
||||
submitToRegulator: values.submitToRegulator === 1,
|
||||
};
|
||||
delete payload.reportMainFile;
|
||||
delete payload.reportAttachment;
|
||||
const res = await props.evalReportAdd(payload);
|
||||
if (res?.success !== false) {
|
||||
message.success(
|
||||
|
|
@ -330,22 +316,75 @@ const EvalReportLibrary = (props) => {
|
|||
};
|
||||
|
||||
const handleViewDetail = async (record) => {
|
||||
setDetailData(null);
|
||||
setDetailVisible(true);
|
||||
// 查看复用上传弹窗:清空表单并置为只读,接口返回后回填数据
|
||||
setDetailData({});
|
||||
uploadForm.resetFields();
|
||||
fileInputCounter = 0;
|
||||
setReportFiles([]);
|
||||
setAddVisible(true);
|
||||
const res = await props.evalReportDetail({ id: record.id });
|
||||
if (res?.success !== false) {
|
||||
setDetailData(res?.data || {});
|
||||
if (res?.success === false) {
|
||||
setAddVisible(false);
|
||||
return;
|
||||
}
|
||||
const data = res?.data || {};
|
||||
setDetailData(data);
|
||||
uploadForm.setFieldsValue({
|
||||
...data,
|
||||
issueDate: data.issueDate ? dayjs(data.issueDate) : undefined,
|
||||
});
|
||||
let files = [];
|
||||
try {
|
||||
const parsed = JSON.parse(data.fileUrl);
|
||||
if (Array.isArray(parsed)) files = parsed;
|
||||
} catch {
|
||||
if (data.fileUrl) {
|
||||
files = [{ type: "main", url: data.fileUrl, name: data.fileName || data.reportName }];
|
||||
}
|
||||
}
|
||||
setReportFiles(
|
||||
files.map((f) => ({
|
||||
id: ++fileInputCounter,
|
||||
type: f.type,
|
||||
url: f.url,
|
||||
fileName: f.name,
|
||||
})),
|
||||
);
|
||||
};
|
||||
|
||||
const handleDownload = async (record) => {
|
||||
let fileUrl = record.fileUrl;
|
||||
let fileName = record.fileName || record.reportName || "报告文件";
|
||||
// 解析 fileUrl:兼容新格式(JSON 数组)和旧格式(字符串)
|
||||
if (fileUrl) {
|
||||
try {
|
||||
const arr = JSON.parse(fileUrl);
|
||||
if (Array.isArray(arr) && arr.length > 0) {
|
||||
const mainFile = arr.find((f) => f.type === "main") || arr[0];
|
||||
fileUrl = mainFile.url;
|
||||
fileName = mainFile.name || fileName;
|
||||
}
|
||||
} catch {
|
||||
// 旧格式:直接使用字符串
|
||||
}
|
||||
}
|
||||
if (!fileUrl) {
|
||||
const res = await props.evalReportDetail({ id: record.id });
|
||||
if (res?.success === false) return;
|
||||
fileUrl = res?.data?.fileUrl;
|
||||
fileName = res?.data?.fileName || fileName;
|
||||
const detailFileUrl = res?.data?.fileUrl;
|
||||
if (detailFileUrl) {
|
||||
try {
|
||||
const arr = JSON.parse(detailFileUrl);
|
||||
if (Array.isArray(arr) && arr.length > 0) {
|
||||
const mainFile = arr.find((f) => f.type === "main") || arr[0];
|
||||
fileUrl = mainFile.url;
|
||||
fileName = mainFile.name || fileName;
|
||||
}
|
||||
} catch {
|
||||
fileUrl = detailFileUrl;
|
||||
fileName = res?.data?.fileName || fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fileUrl) {
|
||||
message.warning("文件地址不存在");
|
||||
|
|
@ -864,15 +903,19 @@ const EvalReportLibrary = (props) => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* 上传历史报告 */}
|
||||
{/* 上传历史报告 / 报告详情(查看复用同一弹窗,disabled 只读) */}
|
||||
<Modal
|
||||
title={
|
||||
<div className="erl-modal-title">
|
||||
<div>上传历史报告</div>
|
||||
<div className="erl-modal-subtitle">
|
||||
上传过去三年已完成并正式签发的安全评价报告
|
||||
viewMode ? (
|
||||
"报告详情"
|
||||
) : (
|
||||
<div className="erl-modal-title">
|
||||
<div>上传历史报告</div>
|
||||
<div className="erl-modal-subtitle">
|
||||
上传过去三年已完成并正式签发的安全评价报告
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
open={addVisible}
|
||||
onCancel={() => setAddVisible(false)}
|
||||
|
|
@ -880,6 +923,23 @@ const EvalReportLibrary = (props) => {
|
|||
confirmLoading={evalReportAddLoading}
|
||||
okText="确认上传"
|
||||
cancelText="取消"
|
||||
footer={
|
||||
viewMode ? (
|
||||
<Space>
|
||||
{detailData && isSpotcheckRelated(detailData) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setAddVisible(false);
|
||||
handleViewSpotcheck(detailData);
|
||||
}}
|
||||
>
|
||||
抽查记录
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={() => setAddVisible(false)}>关闭</Button>
|
||||
</Space>
|
||||
) : undefined
|
||||
}
|
||||
width={820}
|
||||
destroyOnHidden
|
||||
className="erl-upload-modal"
|
||||
|
|
@ -889,69 +949,8 @@ const EvalReportLibrary = (props) => {
|
|||
layout="vertical"
|
||||
preserve={false}
|
||||
scrollToFirstError
|
||||
disabled={viewMode}
|
||||
>
|
||||
<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
|
||||
|
|
@ -1031,49 +1030,114 @@ const EvalReportLibrary = (props) => {
|
|||
<Select options={SECRET_LEVEL_OPTIONS} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
{/* 报告文件列表(对齐原型 .report-upload-files Table 布局) */}
|
||||
<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>
|
||||
<div className="erl-report-files">
|
||||
<div className="erl-report-files-head">
|
||||
<h4>报告文件</h4>
|
||||
{!viewMode && (
|
||||
<Button type="link" size="small" icon={<PlusOutlined />} onClick={addReportFileRow}>
|
||||
添加文件
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Table
|
||||
dataSource={reportFiles}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
size="small"
|
||||
loading={viewMode && evalReportDetailLoading}
|
||||
columns={[
|
||||
{
|
||||
title: "文件类型",
|
||||
dataIndex: "type",
|
||||
width: 120,
|
||||
render: (_, row) => (
|
||||
<Select
|
||||
value={row.type}
|
||||
onChange={(val) => changeReportFileType(row.id, val)}
|
||||
size="small"
|
||||
style={{ width: "100%" }}
|
||||
options={[
|
||||
{ label: "报告正文", value: "main" },
|
||||
{ label: "报告附件", value: "attachment" },
|
||||
]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "上传要求",
|
||||
dataIndex: "type",
|
||||
render: (_, row) => {
|
||||
const config = FILE_TYPE_CONFIG[row.type];
|
||||
return (
|
||||
<div style={{ fontSize: 13, color: "#334155" }}>
|
||||
{row.type === "main" && <span style={{ color: "#dc2626" }}>* </span>}
|
||||
{config.rule}
|
||||
<div style={{ fontSize: 12, color: "#64748b", marginTop: 2 }}>
|
||||
{config.format}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "文件",
|
||||
dataIndex: "fileName",
|
||||
render: (_, row) => (
|
||||
<PreviewUrlButton url={row.url || ""}>
|
||||
{row.uploading
|
||||
? `正在上传 ${row.fileName}...`
|
||||
: row.fileName || "尚未选择文件"}
|
||||
</PreviewUrlButton>
|
||||
),
|
||||
},
|
||||
!viewMode && {
|
||||
title: "操作",
|
||||
fixed: "right",
|
||||
width: 140,
|
||||
render: (_, row) => {
|
||||
const config = FILE_TYPE_CONFIG[row.type];
|
||||
return (
|
||||
<TableAction>
|
||||
<Upload
|
||||
name="file"
|
||||
action={UPLOAD_ACTION}
|
||||
headers={{ token: sessionStorage.getItem("token") || "" }}
|
||||
accept={config.accept}
|
||||
showUploadList={false}
|
||||
beforeUpload={(file) => {
|
||||
if (file.size / 1024 / 1024 > 100) {
|
||||
message.error("单个文件不超过 100MB");
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
return true;
|
||||
}}
|
||||
onChange={(info) => handleReportFileUpload(row.id, info)}
|
||||
>
|
||||
<Button type="link" size="small" icon={<UploadOutlined />} loading={row.uploading}>
|
||||
选择文件
|
||||
</Button>
|
||||
</Upload>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
danger
|
||||
onClick={() => removeReportFileRow(row.id)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</TableAction>
|
||||
);
|
||||
},
|
||||
},
|
||||
].filter(Boolean)}
|
||||
/>
|
||||
</div>
|
||||
<div className="erl-report-files-note">
|
||||
<strong>附件材料提示:</strong>
|
||||
营业执照(副本)复印件、不动产权证书复印件、企业投资项目备案信息复印件、专家评审意见、修改说明、附图(地理位置示意图、周边关系及总平面布置图)。
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Item name="remarks" label="备注">
|
||||
|
|
@ -1088,192 +1152,12 @@ const EvalReportLibrary = (props) => {
|
|||
</Row>
|
||||
</Form>
|
||||
|
||||
<div className="erl-upload-tip">
|
||||
<strong>上传要求</strong>
|
||||
<span>报告编号不得重复</span>
|
||||
<span>报告须完成签字盖章</span>
|
||||
<span>分类信息用于监管抽查检索</span>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
|
||||
<Modal
|
||||
title={
|
||||
'报告详情'
|
||||
}
|
||||
open={detailVisible}
|
||||
onCancel={() => setDetailVisible(false)}
|
||||
footer={<Button onClick={() => setDetailVisible(false)}>关闭</Button>}
|
||||
width={820}
|
||||
destroyOnHidden
|
||||
className="erl-upload-modal"
|
||||
>
|
||||
{detailData ? (
|
||||
<>
|
||||
{/* 正式报告文件:预览优先,与上传弹窗主文件展示一致 */}
|
||||
<div className="erl-upload-box erl-upload-box-view">
|
||||
<div className="erl-upload-box-inner">
|
||||
<div className="erl-upload-icon">
|
||||
{getFileExt(detailData.fileName || detailData.fileUrl)}
|
||||
</div>
|
||||
<div className="erl-upload-text">
|
||||
<strong>{detailData.reportName || "未命名报告"}</strong>
|
||||
<p>
|
||||
{detailData.fileName ||
|
||||
detailData.fileUrl ||
|
||||
"暂无正式报告文件"}
|
||||
</p>
|
||||
{detailData.fileUrl ? (
|
||||
<PreviewUrlButton
|
||||
url={detailData.fileUrl}
|
||||
children={detailData.fileName || "预览报告"}
|
||||
/>
|
||||
) : (
|
||||
<span>尚未上传正式报告文件</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Row gutter={16} className="erl-detail-form">
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>报告编号</label>
|
||||
<span className="erl-report-no">
|
||||
{detailData.reportNo || "-"}
|
||||
</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>报告名称</label>
|
||||
<span>{detailData.reportName || "-"}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>报告年度</label>
|
||||
<span>{detailData.reportYear || "-"}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>评价类型</label>
|
||||
<span>
|
||||
{detailData.evalTypeName ||
|
||||
getOptionLabel(
|
||||
EVAL_TYPE_OPTIONS,
|
||||
detailData.evalTypeCode,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>所属行业</label>
|
||||
<span>
|
||||
{detailData.industryName ||
|
||||
getOptionLabel(
|
||||
QUALIFICATION_INDUSTRY_OPTIONS,
|
||||
detailData.industryCode,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>被评价单位</label>
|
||||
<span>{detailData.evaluatedUnitName || "-"}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>报告签发日期</label>
|
||||
<span>
|
||||
{detailData.issueDate
|
||||
? dayjs(detailData.issueDate).format("YYYY-MM-DD")
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>报告页数</label>
|
||||
<span>{detailData.pageCount ?? "-"}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>报送状态</label>
|
||||
<span>{renderDisplayStatus(detailData)}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>报告状态</label>
|
||||
<span>{detailData.reportStatusName || "-"}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>保密属性</label>
|
||||
<span>
|
||||
{detailData.secretLevelName ||
|
||||
getOptionLabel(
|
||||
SECRET_LEVEL_OPTIONS,
|
||||
detailData.secretLevelCode,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className="erl-detail-item">
|
||||
<label>归档时间</label>
|
||||
<span>
|
||||
{detailData.archiveTime
|
||||
? dayjs(detailData.archiveTime).format(
|
||||
"YYYY-MM-DD HH:mm",
|
||||
)
|
||||
: "-"}
|
||||
</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<div className="erl-detail-item">
|
||||
<label>合同文件</label>
|
||||
<span>{renderFileLinks(detailData.contractFileUrls)}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<div className="erl-detail-item">
|
||||
<label>其他过程文档</label>
|
||||
<span>{renderFileLinks(detailData.processDocUrls)}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<div className="erl-detail-item">
|
||||
<label>备注</label>
|
||||
<span>{detailData.remarks || "-"}</span>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div className="erl-detail-actions">
|
||||
{isSpotcheckRelated(detailData) && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setDetailVisible(false);
|
||||
handleViewSpotcheck(detailData);
|
||||
}}
|
||||
>
|
||||
抽查记录
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="erl-loading">
|
||||
{evalReportDetailLoading ? "加载中..." : "暂无数据"}
|
||||
{!viewMode && (
|
||||
<div className="erl-upload-tip">
|
||||
<strong>上传要求</strong>
|
||||
<span>报告编号不得重复</span>
|
||||
<span>报告须完成签字盖章</span>
|
||||
<span>分类信息用于监管抽查检索</span>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -46,11 +46,6 @@
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.erl-report-no {
|
||||
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.erl-status-link {
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
@ -225,70 +220,42 @@
|
|||
|
||||
}
|
||||
|
||||
.erl-detail-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 16px;
|
||||
}
|
||||
/* 报告文件列表(对齐原型 .report-upload-files) */
|
||||
.erl-report-files {
|
||||
border: 1px solid var(--micro-temp-color-border, #e8edf3);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
/* 变更时间:2026-08-11 查看详情改为与「上传历史报告」一致的排版样式 */
|
||||
.erl-detail-form {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.erl-report-files-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 14px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #e8edf3;
|
||||
|
||||
.erl-detail-item {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
padding: 8px 10px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #eef2f7;
|
||||
border-radius: 8px;
|
||||
min-height: 40px;
|
||||
|
||||
label {
|
||||
flex: 0 0 84px;
|
||||
color: #64748b;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
color: #1f2937;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #1e293b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 只读查看态的正式报告文件框:弱化边框,去掉上传交互 */
|
||||
.erl-upload-box-view {
|
||||
border-style: solid;
|
||||
border-color: #e2e8f0;
|
||||
background: #f8fafc;
|
||||
}
|
||||
.erl-report-files-note {
|
||||
margin-top: 10px;
|
||||
padding: 10px 14px;
|
||||
border-left: 3px solid #2563eb;
|
||||
border-radius: 4px;
|
||||
background: #eff6ff;
|
||||
color: #475569;
|
||||
font-size: 13px;
|
||||
line-height: 1.65;
|
||||
|
||||
.erl-detail-attachments {
|
||||
margin-top: 16px;
|
||||
|
||||
.erl-detail-attachments-block {
|
||||
margin-bottom: 12px;
|
||||
strong {
|
||||
color: #1e40af;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.erl-detail-attachments-title {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.ant-btn-link {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.erl-loading {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.erl-rectify-meta {
|
||||
|
|
|
|||
|
|
@ -225,6 +225,15 @@ const getFileExtLabel = (name = "") => {
|
|||
return ext && ext !== name ? ext.toUpperCase() : "PDF";
|
||||
};
|
||||
|
||||
/** 字节数转可读文件大小 */
|
||||
const formatFileSize = (bytes) => {
|
||||
const num = Number(bytes);
|
||||
if (!num || Number.isNaN(num)) return "-";
|
||||
if (num < 1024) return `${num} B`;
|
||||
if (num < 1024 * 1024) return `${(num / 1024).toFixed(1)} KB`;
|
||||
return `${(num / 1024 / 1024).toFixed(1)} MB`;
|
||||
};
|
||||
|
||||
/** 解析存储的 JSON 文件列表字符串,返回数组 */
|
||||
const parseFileList = (jsonStr) => {
|
||||
if (!jsonStr) return [];
|
||||
|
|
@ -506,11 +515,36 @@ const EvalReportDatabase = (props) => {
|
|||
const handleDownloadOne = async (record) => {
|
||||
let fileUrl = record.fileUrl;
|
||||
let fileName = record.fileName || record.reportName || "报告文件";
|
||||
// 解析 fileUrl:兼容新格式(JSON 数组)和旧格式(字符串)
|
||||
if (fileUrl) {
|
||||
try {
|
||||
const arr = JSON.parse(fileUrl);
|
||||
if (Array.isArray(arr) && arr.length > 0) {
|
||||
const mainFile = arr.find((f) => f.type === "main") || arr[0];
|
||||
fileUrl = mainFile.url;
|
||||
fileName = mainFile.name || fileName;
|
||||
}
|
||||
} catch {
|
||||
// 旧格式:直接使用字符串
|
||||
}
|
||||
}
|
||||
if (!fileUrl) {
|
||||
const res = await props.regulatorEvalReportDetail({ id: record.id });
|
||||
if (res?.success === false) return;
|
||||
fileUrl = res?.data?.fileUrl;
|
||||
fileName = res?.data?.fileName || fileName;
|
||||
const detailFileUrl = res?.data?.fileUrl;
|
||||
if (detailFileUrl) {
|
||||
try {
|
||||
const arr = JSON.parse(detailFileUrl);
|
||||
if (Array.isArray(arr) && arr.length > 0) {
|
||||
const mainFile = arr.find((f) => f.type === "main") || arr[0];
|
||||
fileUrl = mainFile.url;
|
||||
fileName = mainFile.name || fileName;
|
||||
}
|
||||
} catch {
|
||||
fileUrl = detailFileUrl;
|
||||
fileName = res?.data?.fileName || fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fileUrl) {
|
||||
message.warning("文件地址不存在");
|
||||
|
|
@ -1012,49 +1046,55 @@ const EvalReportDatabase = (props) => {
|
|||
]}
|
||||
/>
|
||||
</ModalSection>
|
||||
<ModalSection
|
||||
title="报告文件"
|
||||
desc="支持在线查看和下载正式报告。"
|
||||
>
|
||||
<ProtoTable
|
||||
headers={["文件名称", "文件类型", "版本"]}
|
||||
rows={[
|
||||
[
|
||||
<PreviewUrlButton
|
||||
url={detailData.fileUrl}
|
||||
>
|
||||
{detailData.fileName ||
|
||||
detailData.reportName ||
|
||||
"正式安全评价报告.pdf"}
|
||||
</PreviewUrlButton>,
|
||||
getFileExtLabel(detailData.fileName),
|
||||
"V1.0",
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</ModalSection>
|
||||
<ModalSection title="合同文件" desc="查看上传的合同文件。">
|
||||
<ProtoTable
|
||||
headers={["文件名称", "文件类型"]}
|
||||
rows={parseFileList(detailData.contractFileUrls).map((f) => [
|
||||
{(() => {
|
||||
let files = [];
|
||||
try {
|
||||
const parsed = JSON.parse(detailData.fileUrl);
|
||||
if (Array.isArray(parsed)) files = parsed;
|
||||
} catch {
|
||||
if (detailData.fileUrl) {
|
||||
files = [{ type: "main", url: detailData.fileUrl, name: detailData.fileName || detailData.reportName }];
|
||||
}
|
||||
}
|
||||
const mainFiles = files.filter((f) => f.type === "main");
|
||||
const attachmentFiles = files.filter((f) => f.type !== "main");
|
||||
const renderFileRows = (list) =>
|
||||
list.map((f) => [
|
||||
<PreviewUrlButton key={f.url} url={f.url}>
|
||||
{f.name || "未命名文件"}
|
||||
</PreviewUrlButton>,
|
||||
getFileExtLabel(f.name || f.url),
|
||||
])}
|
||||
/>
|
||||
</ModalSection>
|
||||
<ModalSection title="其他过程文档" desc="查看上传的过程文档。">
|
||||
<ProtoTable
|
||||
headers={["文件名称", "文件类型"]}
|
||||
rows={parseFileList(detailData.processDocUrls).map((f) => [
|
||||
<PreviewUrlButton key={f.url} url={f.url}>
|
||||
{f.name || "未命名文件"}
|
||||
formatFileSize(f.size),
|
||||
f.createdTime
|
||||
? dayjs(f.createdTime).format("YYYY-MM-DD HH:mm")
|
||||
: "-",
|
||||
<PreviewUrlButton key={`${f.url}-view`} url={f.url}>
|
||||
查看
|
||||
</PreviewUrlButton>,
|
||||
getFileExtLabel(f.name || f.url),
|
||||
])}
|
||||
/>
|
||||
</ModalSection>
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
<ModalSection
|
||||
title={`报告正文(${mainFiles.length}份)`}
|
||||
desc="评价机构上传的正式签字盖章版报告正文。"
|
||||
>
|
||||
<ProtoTable
|
||||
headers={["文件名称", "格式", "大小", "上传时间", "操作"]}
|
||||
rows={mainFiles.length ? renderFileRows(mainFiles) : [["-", "-", "-", "-", "-"]]}
|
||||
/>
|
||||
</ModalSection>
|
||||
<ModalSection
|
||||
title={`报告附件(${attachmentFiles.length}份)`}
|
||||
desc="附件按材料名称逐项展示,可在线查看或单独下载。"
|
||||
>
|
||||
<ProtoTable
|
||||
headers={["文件名称", "格式", "大小", "上传时间", "操作"]}
|
||||
rows={attachmentFiles.length ? renderFileRows(attachmentFiles) : [["-", "-", "-", "-", "-"]]}
|
||||
/>
|
||||
</ModalSection>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</>
|
||||
) : (
|
||||
<div className="redb-loading">
|
||||
|
|
|
|||
Loading…
Reference in New Issue