feat
parent
0bc60e970e
commit
273cb8d6ec
|
|
@ -301,10 +301,10 @@ export const evalProcessControlFiles = declareRequest(
|
|||
"processControlFiles: [] | res.data || []",
|
||||
);
|
||||
|
||||
/** 项目档案文件列表-机构端分页(分页参数 pageIndex/pageSize,总数 totalCount) */
|
||||
/** 项目档案文件列表-机构端 */
|
||||
export const evalProjectArchiveFileOrgPage = declareRequest(
|
||||
"evalProjectArchiveFileOrgLoading",
|
||||
"Get > /safetyEval/eval-project-archive-file/org/page",
|
||||
"Get > /safetyEval/eval-project-archive-file/org/list",
|
||||
);
|
||||
|
||||
/** 新增项目档案文件(机构补充上传) */
|
||||
|
|
|
|||
|
|
@ -444,11 +444,11 @@ export const WARING_STATUS_OPTIONS = [
|
|||
|
||||
export const NODE_LABELS = {
|
||||
RISK_ANALYSIS: "风险分析",
|
||||
BUSINESS_CONTRACT: "商务合同",
|
||||
BUSINESS_CONTRACT: "业务合同",
|
||||
PROJECT_GROUP: "项目组",
|
||||
SITE_SURVEY: "现场勘查",
|
||||
SITE_SURVEY: "现场踏勘",
|
||||
REPORT_PREPARATION: "报告编制",
|
||||
INTERNAL_REVIEW: "内审",
|
||||
INTERNAL_REVIEW: "内部审核",
|
||||
TECHNICAL_REVIEW: "技术审核",
|
||||
PROCESS_CONTROL: "过程控制",
|
||||
ARCHIVE: "项目归档",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useMemo } from "react";
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
Col,
|
||||
DatePicker,
|
||||
Form,
|
||||
Modal,
|
||||
message,
|
||||
} from "antd";
|
||||
import dayjs from "dayjs";
|
||||
|
|
@ -20,7 +21,7 @@ import LayoutFooterPortal from "~/components/LayoutFooterPortal";
|
|||
import PreviewUrlButton from "~/components/PreviewUrlButton";
|
||||
import ArchiveSupplementModal from "./ArchiveSupplementModal";
|
||||
import {
|
||||
|
||||
NODE_LABELS,
|
||||
ARCHIVE_RECORD_SOURCE_OPTIONS,
|
||||
ARCHIVE_RECORD_SOURCE_BADGE_MAP,
|
||||
} from "~/enumerate/constant";
|
||||
|
|
@ -35,13 +36,10 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
|||
|
||||
const [archiveForm] = Form.useForm();
|
||||
const [archiveFiles, setArchiveFiles] = useState([]);
|
||||
const [archiveTotal, setArchiveTotal] = useState(0);
|
||||
const [archiveQuery, setArchiveQuery] = useState({
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
recordSource: "",
|
||||
});
|
||||
const [sourceFilter, setSourceFilter] = useState("");
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [supplementOpen, setSupplementOpen] = useState(false);
|
||||
const [viewRecord, setViewRecord] = useState(null);
|
||||
|
||||
const fetchDetail = () => {
|
||||
props.evalProcessControlDetail({ projectId });
|
||||
|
|
@ -67,25 +65,32 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
|||
}, [processControlDetail]);
|
||||
|
||||
const fetchArchiveFiles = () => {
|
||||
props
|
||||
.evalProjectArchiveFileOrgPage({
|
||||
projectId,
|
||||
pageIndex: archiveQuery.pageIndex,
|
||||
pageSize: archiveQuery.pageSize,
|
||||
recordSource: archiveQuery.recordSource || undefined,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res?.success !== false) {
|
||||
setArchiveFiles(res?.data || []);
|
||||
setArchiveTotal(res?.totalCount || 0);
|
||||
}
|
||||
});
|
||||
props.evalProjectArchiveFileOrgPage({ projectId }).then((res) => {
|
||||
if (res?.success !== false) {
|
||||
setArchiveFiles(res?.data || []);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId) return;
|
||||
fetchArchiveFiles();
|
||||
}, [projectId, archiveQuery]);
|
||||
}, [projectId]);
|
||||
|
||||
/** 本地筛选:来源 + 文件名称/档案类型关键字 */
|
||||
const filteredArchiveFiles = useMemo(() => {
|
||||
const kw = keyword.trim().toLowerCase();
|
||||
return archiveFiles.filter((item) => {
|
||||
const matchSource = !sourceFilter || item.recordSource === sourceFilter;
|
||||
const matchKeyword =
|
||||
!kw ||
|
||||
(item.files || []).some((f) =>
|
||||
(f.name || "").toLowerCase().includes(kw),
|
||||
) ||
|
||||
(item.archiveType || "").toLowerCase().includes(kw);
|
||||
return matchSource && matchKeyword;
|
||||
});
|
||||
}, [archiveFiles, sourceFilter, keyword]);
|
||||
|
||||
const isSignatureCompleted =
|
||||
processControl?.reviewerSignatureTaskStatus === "completed";
|
||||
|
|
@ -109,35 +114,54 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
|||
return /^https?:\/\//i.test(u) ? u : `${window.fileUrl || ""}${u}`;
|
||||
};
|
||||
|
||||
const handleDownload = (file) => {
|
||||
const url = resolveFileUrl(file.fileUrl);
|
||||
/** 单文件下载:跨域地址下 a.download 无效,fetch 转 blob 后再触发下载 */
|
||||
const downloadArchiveFile = async (f) => {
|
||||
const url = resolveFileUrl(f.url);
|
||||
if (!url) return;
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = file.fileName || "";
|
||||
a.target = "_blank";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
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 archiveColumns = [
|
||||
{
|
||||
title: "序号",
|
||||
width: 60,
|
||||
render: (_, __, index) =>
|
||||
(archiveQuery.pageIndex - 1) * archiveQuery.pageSize + index + 1,
|
||||
render: (_, __, index) => index + 1,
|
||||
},
|
||||
{
|
||||
title: "文件名称",
|
||||
dataIndex: "fileName",
|
||||
dataIndex: "files",
|
||||
ellipsis: true,
|
||||
render: (v) => v || "-",
|
||||
render: (v) => {
|
||||
if(Array.isArray(v)){
|
||||
return v.map(v=>v.name).join(',')
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "档案类型",
|
||||
dataIndex: "archiveType",
|
||||
width: 130,
|
||||
width: 180,
|
||||
|
||||
},
|
||||
{
|
||||
|
|
@ -152,7 +176,7 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
|||
>
|
||||
{ARCHIVE_RECORD_SOURCE_BADGE_MAP[v] || v || "-"}
|
||||
</span>
|
||||
<span className="pf-archive__node">{r.businessNode || "-"}</span>
|
||||
<span className="pf-archive__node">{NODE_LABELS[r.businessNode] || "-"}</span>
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
|
|
@ -165,8 +189,7 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
|||
{
|
||||
title: "形成时间",
|
||||
dataIndex: "createTime",
|
||||
width: 160,
|
||||
align: "right",
|
||||
width: 200,
|
||||
render: (v) => v || "-",
|
||||
},
|
||||
{
|
||||
|
|
@ -174,12 +197,19 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
|||
width: 110,
|
||||
render: (_, r) => (
|
||||
<TableAction>
|
||||
<PreviewUrlButton url={r.fileUrl}>查看</PreviewUrlButton>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
disabled={!r.fileUrl}
|
||||
onClick={() => handleDownload(r)}
|
||||
disabled={!r.files?.length}
|
||||
onClick={() => setViewRecord(r)}
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
disabled={!r.files?.length}
|
||||
onClick={() => handleBatchDownload(r.files)}
|
||||
>
|
||||
下载
|
||||
</Button>
|
||||
|
|
@ -194,12 +224,17 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
|||
>
|
||||
|
||||
|
||||
<Flex justify="flex-end" className="pf-archive__toolbar">
|
||||
<Flex justify="space-between" className="pf-archive__toolbar">
|
||||
<Input.Search
|
||||
placeholder="搜索文件名称或档案类型"
|
||||
allowClear
|
||||
onChange={(e) => setKeyword(e.target.value)}
|
||||
onSearch={setKeyword}
|
||||
className="pf-archive__keyword-input"
|
||||
/>
|
||||
<Select
|
||||
value={archiveQuery.recordSource}
|
||||
onChange={(v) =>
|
||||
setArchiveQuery((q) => ({ ...q, recordSource: v, pageIndex: 1 }))
|
||||
}
|
||||
value={sourceFilter}
|
||||
onChange={setSourceFilter}
|
||||
options={[
|
||||
{ label: "全部来源", value: "" },
|
||||
...ARCHIVE_RECORD_SOURCE_OPTIONS,
|
||||
|
|
@ -211,18 +246,10 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
|||
<Table
|
||||
rowKey="id"
|
||||
size="small"
|
||||
dataSource={archiveFiles}
|
||||
dataSource={filteredArchiveFiles}
|
||||
columns={archiveColumns}
|
||||
loading={props.safetyEvalBusiness?.evalProjectArchiveFileOrgLoading}
|
||||
pagination={{
|
||||
total: archiveTotal,
|
||||
current: archiveQuery.pageIndex,
|
||||
pageSize: archiveQuery.pageSize,
|
||||
showQuickJumper: true,
|
||||
showTotal: (t) => `共 ${t} 条`,
|
||||
onChange: (page, pageSize) =>
|
||||
setArchiveQuery((q) => ({ ...q, pageIndex: page, pageSize })),
|
||||
}}
|
||||
pagination={false}
|
||||
/>
|
||||
|
||||
<Form form={archiveForm} layout="vertical" disabled={readOnly} scrollToFirstError>
|
||||
|
|
@ -281,6 +308,23 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
|||
fetchArchiveFiles();
|
||||
}}
|
||||
/>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function ArchiveSupplementModal({
|
|||
|
||||
const handleOk = async () => {
|
||||
const values = await form.validateFields();
|
||||
const fileList = values.file || [];
|
||||
const fileList = values.files || [];
|
||||
if (fileList.some((f) => f.status === "uploading")) {
|
||||
message.warning("文件正在上传中,请等待上传完成");
|
||||
return;
|
||||
|
|
@ -34,7 +34,7 @@ function ArchiveSupplementModal({
|
|||
businessNode: "文档上传",
|
||||
recordSource: "upload_additional_files",
|
||||
remark: values.remark,
|
||||
file: doneFiles,
|
||||
files: doneFiles,
|
||||
});
|
||||
if (res?.success === false) return;
|
||||
message.success("补充档案已上传");
|
||||
|
|
@ -72,7 +72,7 @@ function ArchiveSupplementModal({
|
|||
<TextArea rows={3} placeholder="填写资料内容或补充说明" maxLength={500} />
|
||||
</Form.Item>
|
||||
<AttachmentUpload
|
||||
name="file"
|
||||
name="files"
|
||||
label="资料上传"
|
||||
rules={[{ required: true, message: "请选择需要上传的文件" }]}
|
||||
extra="支持上传多个文件"
|
||||
|
|
|
|||
|
|
@ -298,6 +298,10 @@
|
|||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__keyword-input {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
&__source-select {
|
||||
width: 140px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Input, Modal, Table, Tag } from "antd";
|
||||
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 {
|
||||
NODE_LABELS,
|
||||
ARCHIVE_TYPE_MAP,
|
||||
ARCHIVE_RECORD_SOURCE_MAP,
|
||||
} from "~/enumerate/constant";
|
||||
|
|
@ -19,6 +20,7 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
|
|||
const [query, setQuery] = useState({ pageIndex: 1, pageSize: 10, fileName: "" });
|
||||
const [list, setList] = useState([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [viewRecord, setViewRecord] = useState(null);
|
||||
|
||||
|
||||
|
||||
|
|
@ -32,16 +34,32 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
|
|||
});
|
||||
}, [record?.id, query]);
|
||||
|
||||
const handleDownload = (file) => {
|
||||
const url = resolveFileUrl(file.fileUrl);
|
||||
/** 单文件下载:跨域地址下 a.download 无效,fetch 转 blob 后再触发下载 */
|
||||
const downloadArchiveFile = async (f) => {
|
||||
const url = resolveFileUrl(f.url);
|
||||
if (!url) return;
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = file.fileName || "";
|
||||
a.target = "_blank";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
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 = [
|
||||
|
|
@ -53,17 +71,17 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
|
|||
},
|
||||
{
|
||||
title: "文件名称",
|
||||
dataIndex: "fileName",
|
||||
dataIndex: "files",
|
||||
ellipsis: true,
|
||||
render: (v, r) => (
|
||||
|
||||
<div>
|
||||
<div>{v || "-"}</div>
|
||||
<div className="epd-archive__file-source">
|
||||
{r.recordSource === "upload_additional_files"
|
||||
? "评价机构补充上传"
|
||||
: "业务节点自动归集"}
|
||||
</div>
|
||||
{Array.isArray(v) && v.length
|
||||
? v.map((f) => f.name).join(",")
|
||||
: "-"}
|
||||
</div>
|
||||
|
||||
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
@ -89,19 +107,26 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
|
|||
title: "业务节点",
|
||||
dataIndex: "businessNode",
|
||||
width: 130,
|
||||
render: (v) => v || "-",
|
||||
render: (v) => NODE_LABELS[v] || v || "-",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 120,
|
||||
render: (_, r) => (
|
||||
<TableAction>
|
||||
<PreviewUrlButton url={r.fileUrl}>查看</PreviewUrlButton>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
disabled={!r.fileUrl}
|
||||
onClick={() => handleDownload(r)}
|
||||
disabled={!r.files?.length}
|
||||
onClick={() => setViewRecord(r)}
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
disabled={!r.files?.length}
|
||||
onClick={() => handleBatchDownload(r.files)}
|
||||
>
|
||||
下载
|
||||
</Button>
|
||||
|
|
@ -178,6 +203,23 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
|
|||
setQuery((q) => ({ ...q, pageIndex: page, pageSize })),
|
||||
}}
|
||||
/>
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue