228 lines
7.4 KiB
JavaScript
228 lines
7.4 KiB
JavaScript
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;
|