tangjie 2026-08-04 16:15:18 +08:00
commit 2d08e2e9e5
4 changed files with 194 additions and 6 deletions

View File

@ -15,7 +15,7 @@ module.exports = {
API_HOST: "http://192.168.0.134", API_HOST: "http://192.168.0.134",
//API_HOST: "http://192.168.0.150", //太浅 //API_HOST: "http://192.168.0.150", //太浅
// API_HOST: "http://192.168.0.152", // API_HOST: "http://192.168.0.152",
//API_HOST: "http://192.168.0.103", //huwei // API_HOST: "http://192.168.0.103", //huwei
}, },
production: { production: {
// 应用后端分支名称,部署上线需要 // 应用后端分支名称,部署上线需要

View File

@ -222,12 +222,12 @@ function PersonnelInfoPage(props) {
ellipsis: true, ellipsis: true,
render: (text) => text || "-", render: (text) => text || "-",
}, },
{ {
title: "能力", title: "能力",
width: 80, width: 260,
dataIndex: "abilityNames", dataIndex: "abilityNames",
ellipsis: true, ellipsis: true,
render: (text) => "暂无", render: (text) => text || '暂无',
}, },
{ {
title: "操作", title: "操作",
@ -394,7 +394,8 @@ function StaffFormModal({
}; };
const loadBasicDisciplineMajors = async () => { const loadBasicDisciplineMajors = async () => {
await queryBasicDisciplineMajor({ pageSize: 999 });
await queryBasicDisciplineMajor({ pageSize: 999 });
}; };
useEffect(() => { useEffect(() => {
@ -449,6 +450,7 @@ function StaffFormModal({
} }
form.setFieldsValue(setFields); form.setFieldsValue(setFields);
}) })
.finally(() => { .finally(() => {
if (!cancelled) setDetailLoading(false); if (!cancelled) setDetailLoading(false);

View File

@ -15,6 +15,8 @@ import {
DatePicker, DatePicker,
Space, Space,
ConfigProvider, ConfigProvider,
Tag,
Tooltip,
} from "antd"; } from "antd";
import { import {
PlusOutlined, PlusOutlined,
@ -101,6 +103,30 @@ const getFileExt = (name = "") => {
return parts.length > 1 ? parts.pop().toUpperCase() : "PDF"; 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) => const getOptionLabel = (options, value) =>
options.find((item) => item.value === value)?.label || value || "-"; options.find((item) => item.value === value)?.label || value || "-";
@ -131,6 +157,8 @@ const EvalReportLibrary = (props) => {
const [rectifyVisible, setRectifyVisible] = useState(false); const [rectifyVisible, setRectifyVisible] = useState(false);
const [rectifyRecord, setRectifyRecord] = useState(null); const [rectifyRecord, setRectifyRecord] = useState(null);
const [uploadedFile, setUploadedFile] = useState(null); const [uploadedFile, setUploadedFile] = useState(null);
const [contractFiles, setContractFiles] = useState([]);
const [processDocFiles, setProcessDocFiles] = useState([]);
const { const {
evalReportPageLoading, evalReportPageLoading,
@ -188,6 +216,8 @@ const EvalReportLibrary = (props) => {
const handleOpenAdd = () => { const handleOpenAdd = () => {
setUploadedFile(null); setUploadedFile(null);
setContractFiles([]);
setProcessDocFiles([]);
uploadForm.resetFields(); uploadForm.resetFields();
uploadForm.setFieldsValue({ uploadForm.setFieldsValue({
reportYear: dayjs().year(), reportYear: dayjs().year(),
@ -225,6 +255,40 @@ const EvalReportLibrary = (props) => {
} }
}; };
// 合同文件上传
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 handleAddOk = async () => {
const values = await uploadForm.validateFields().catch(() => null); const values = await uploadForm.validateFields().catch(() => null);
if (!values) return; if (!values) return;
@ -252,6 +316,8 @@ const EvalReportLibrary = (props) => {
SECRET_LEVEL_OPTIONS, SECRET_LEVEL_OPTIONS,
values.secretLevelCode, values.secretLevelCode,
), ),
contractFileUrls: contractFiles.length > 0 ? JSON.stringify(contractFiles) : undefined,
processDocUrls: processDocFiles.length > 0 ? JSON.stringify(processDocFiles) : undefined,
remarks: values.remarks, remarks: values.remarks,
submitToRegulator: values.submitToRegulator === 1, submitToRegulator: values.submitToRegulator === 1,
}; };
@ -838,6 +904,50 @@ const EvalReportLibrary = (props) => {
<Select options={SECRET_LEVEL_OPTIONS} /> <Select options={SECRET_LEVEL_OPTIONS} />
</Form.Item> </Form.Item>
</Col> </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}> <Col span={24}>
<Form.Item name="remarks" label="备注"> <Form.Item name="remarks" label="备注">
<TextArea <TextArea
@ -921,6 +1031,12 @@ const EvalReportLibrary = (props) => {
<span>{detailData.fileName || "-"}</span> <span>{detailData.fileName || "-"}</span>
</div> </div>
</Descriptions.Item> </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}> <Descriptions.Item label="备注" span={2}>
{detailData.remarks || "-"} {detailData.remarks || "-"}
</Descriptions.Item> </Descriptions.Item>

View File

@ -210,6 +210,17 @@ const getFileExtLabel = (name = "") => {
return ext && ext !== name ? ext.toUpperCase() : "PDF"; return ext && ext !== name ? ext.toUpperCase() : "PDF";
}; };
/** 解析存储的 JSON 文件列表字符串,返回数组 */
const parseFileList = (jsonStr) => {
if (!jsonStr) return [];
try {
const arr = JSON.parse(jsonStr);
return Array.isArray(arr) ? arr : [];
} catch {
return [];
}
};
const EvalReportDatabase = (props) => { const EvalReportDatabase = (props) => {
const [searchForm] = Form.useForm(); const [searchForm] = Form.useForm();
const [exportForm] = Form.useForm(); const [exportForm] = Form.useForm();
@ -310,6 +321,19 @@ const EvalReportDatabase = (props) => {
getData(pagination); getData(pagination);
}; };
/** 下载指定 URL 的文件 */
const handleFileDownload = (fileUrl, fileName) => {
if (!fileUrl) {
message.warning("文件地址不存在");
return;
}
const hide = message.loading("正在下载...", 0);
downloadFileByUrl(fileUrl, fileName)
.then(() => message.success("下载已开始"))
.catch(() => message.error("下载失败,请稍后重试"))
.finally(() => hide());
};
const handleViewDetail = async (record) => { const handleViewDetail = async (record) => {
setDetailData(null); setDetailData(null);
setDetailVisible(true); setDetailVisible(true);
@ -822,13 +846,59 @@ const EvalReportDatabase = (props) => {
headers={["文件名称", "文件类型", "版本"]} headers={["文件名称", "文件类型", "版本"]}
rows={[ rows={[
[ [
detailData.fileName || detailData.reportName || "正式安全评价报告.pdf", <span
style={{ color: "#1677ff", cursor: "pointer" }}
onClick={() =>
handleFileDownload(
detailData.fileUrl,
detailData.fileName || detailData.reportName || "正式安全评价报告.pdf"
)
}
>
{detailData.fileName || detailData.reportName || "正式安全评价报告.pdf"}
</span>,
getFileExtLabel(detailData.fileName), getFileExtLabel(detailData.fileName),
"V1.0", "V1.0",
], ],
]} ]}
/> />
</ModalSection> </ModalSection>
<ModalSection
title="合同文件"
desc="查看上传的合同文件。"
>
<ProtoTable
headers={["文件名称", "文件类型"]}
rows={parseFileList(detailData.contractFileUrls).map((f) => [
<span
key={f.url}
style={{ color: "#1677ff", cursor: "pointer" }}
onClick={() => handleFileDownload(f.url, f.name || "未命名文件")}
>
{f.name || "未命名文件"}
</span>,
getFileExtLabel(f.name || f.url),
])}
/>
</ModalSection>
<ModalSection
title="其他过程文档"
desc="查看上传的过程文档。"
>
<ProtoTable
headers={["文件名称", "文件类型"]}
rows={parseFileList(detailData.processDocUrls).map((f) => [
<span
key={f.url}
style={{ color: "#1677ff", cursor: "pointer" }}
onClick={() => handleFileDownload(f.url, f.name || "未命名文件")}
>
{f.name || "未命名文件"}
</span>,
getFileExtLabel(f.name || f.url),
])}
/>
</ModalSection>
</> </>
) : ( ) : (
<div className="redb-loading"> <div className="redb-loading">