safety-eval-service-frontend/src/pages/Container/Supervision/EvalProjectDatabase/ArchiveModal.js

228 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import React, { useEffect, useMemo, useState } from "react";
import { Button, Flex, Input, Modal, Table, Tag, message } from "antd";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import PreviewUrlButton from "~/components/PreviewUrlButton";
import { useDebounce } from "~/utils";
import {
NODE_LABELS,
ARCHIVE_TYPE_MAP,
ARCHIVE_RECORD_SOURCE_MAP,
} from "~/enumerate/constant";
/** 解析文件地址:相对路径拼接 window.fileUrl与 PreviewUrlButton 保持一致) */
const resolveFileUrl = (raw) => {
if (!raw) return "";
const u = String(raw);
return /^https?:\/\//i.test(u) ? u : `${window.fileUrl || ""}${u}`;
};
/** 项目档案查看弹窗(原型 modal-reg-project-archive */
function ArchiveModal({ record, loading, fetchFiles, onClose }) {
const [list, setList] = useState([]);
const [keyword, setKeyword] = useState("");
const [viewRecord, setViewRecord] = useState(null);
useEffect(() => {
if (!record?.id) return;
fetchFiles({ projectId: record.id, pageSize: 500 }).then((res) => {
if (res?.success !== false) {
setList(res?.data || []);
}
});
}, [record?.id]);
const debouncedKeyword = useDebounce(keyword, 400);
const filteredList = useMemo(() => {
const kw = debouncedKeyword.trim().toLowerCase();
if (!kw) return list;
return list.filter(
(item) =>
(item.files || []).some((f) =>
(f.name || "").toLowerCase().includes(kw),
) || (item.archiveType || "").toLowerCase().includes(kw),
);
}, [list, debouncedKeyword]);
/** 单文件下载:跨域地址下 a.download 无效fetch 转 blob 后再触发下载 */
const downloadArchiveFile = async (f) => {
const url = resolveFileUrl(f.url);
if (!url) return;
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const blobUrl = window.URL.createObjectURL(await res.blob());
const link = document.createElement("a");
link.href = blobUrl;
link.download = f.name || "";
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
};
/** 批量下载 files 数组(逐个串行下载,失败不影响其余文件) */
const handleBatchDownload = async (files) => {
for (const f of files || []) {
try {
await downloadArchiveFile(f);
} catch {
message.error(`文件下载失败:${f.name || f.url}`);
}
}
};
const columns = [
{
title: "序号",
width: 60,
render: (_, __, index) => index + 1,
},
{
title: "文件名称",
dataIndex: "files",
ellipsis: true,
render: (v, r) => (
<div>
{Array.isArray(v) && v.length
? v.map((f) => f.name).join(",")
: "-"}
</div>
),
},
{
title: "档案类型",
dataIndex: "archiveType",
width: 130,
},
{
title: "来源",
dataIndex: "recordSource",
width: 120,
render: (v) =>
v ? (
<Tag color={v === "upload_additional_files" ? "warning" : "processing"}>
{ARCHIVE_RECORD_SOURCE_MAP[v]}
</Tag>
) : (
"-"
),
},
{
title: "业务节点",
dataIndex: "businessNode",
width: 130,
render: (v) => NODE_LABELS[v] || v || "-",
},
{
title: "操作",
width: 120,
render: (_, r) => (
<TableAction>
<Button
type="link"
size="small"
disabled={!r.files?.length}
onClick={() => setViewRecord(r)}
>
查看
</Button>
<Button
type="link"
size="small"
disabled={!r.files?.length}
onClick={() => handleBatchDownload(r.files)}
>
下载
</Button>
</TableAction>
),
},
];
const processCount = record?.processFileCount ?? 0;
const additionalCount = record?.additionalFileCount ?? 0;
return (
<Modal
open={!!record}
title={
<div>
<div>项目档案查看</div>
<div className="epd-archive__subtitle">
查看项目执行过程中由系统形成及机构补充上传的档案资料
</div>
</div>
}
width={1000}
footer={<Button onClick={onClose}>关闭</Button>}
destroyOnHidden
onCancel={onClose}
className="epd-archive"
>
<div className="epd-archive__meta">
<div>
<span>项目名称</span>
<strong>{record?.projectName || "-"}</strong>
<small>{record?.projectNo}</small>
</div>
<div>
<span>评价机构</span>
<strong>{record?.orgName || "-"}</strong>
</div>
<div>
<span>评价类型</span>
<strong>{record?.evalTypeName || "-"}</strong>
</div>
<div>
<span>档案数量</span>
<strong>{processCount + additionalCount} </strong>
<small>
流程文档 {processCount} / 补充文件 {additionalCount}
</small>
</div>
</div>
<Input.Search
placeholder="搜索文件名称或档案类型"
allowClear
onChange={(e) => setKeyword(e.target.value)}
onSearch={setKeyword}
className="epd-archive__search"
/>
<Table
rowKey="id"
size="small"
columns={columns}
dataSource={filteredList}
loading={loading}
scroll={{ x: 800, y: 400 }}
pagination={false}
/>
<Modal
open={!!viewRecord}
title="查看档案文件"
width={640}
footer={null}
destroyOnHidden
onCancel={() => setViewRecord(null)}
>
<Flex gap={8}>
{(viewRecord?.files || []).map((f, i) => (
<PreviewUrlButton key={f.url || i} url={f.url}>
{f.name || "查看文件"}
</PreviewUrlButton>
))}
</Flex>
</Modal>
</Modal>
);
}
export default ArchiveModal;