dev-tmp1
tangjie 2026-08-26 10:43:20 +08:00
parent 0bc60e970e
commit 273cb8d6ec
6 changed files with 176 additions and 86 deletions

View File

@ -301,10 +301,10 @@ export const evalProcessControlFiles = declareRequest(
"processControlFiles: [] | res.data || []", "processControlFiles: [] | res.data || []",
); );
/** 项目档案文件列表-机构端分页(分页参数 pageIndex/pageSize总数 totalCount */ /** 项目档案文件列表-机构端 */
export const evalProjectArchiveFileOrgPage = declareRequest( export const evalProjectArchiveFileOrgPage = declareRequest(
"evalProjectArchiveFileOrgLoading", "evalProjectArchiveFileOrgLoading",
"Get > /safetyEval/eval-project-archive-file/org/page", "Get > /safetyEval/eval-project-archive-file/org/list",
); );
/** 新增项目档案文件(机构补充上传) */ /** 新增项目档案文件(机构补充上传) */

View File

@ -444,11 +444,11 @@ export const WARING_STATUS_OPTIONS = [
export const NODE_LABELS = { export const NODE_LABELS = {
RISK_ANALYSIS: "风险分析", RISK_ANALYSIS: "风险分析",
BUSINESS_CONTRACT: "务合同", BUSINESS_CONTRACT: "务合同",
PROJECT_GROUP: "项目组", PROJECT_GROUP: "项目组",
SITE_SURVEY: "现场", SITE_SURVEY: "现场勘",
REPORT_PREPARATION: "报告编制", REPORT_PREPARATION: "报告编制",
INTERNAL_REVIEW: "内审", INTERNAL_REVIEW: "内",
TECHNICAL_REVIEW: "技术审核", TECHNICAL_REVIEW: "技术审核",
PROCESS_CONTROL: "过程控制", PROCESS_CONTROL: "过程控制",
ARCHIVE: "项目归档", ARCHIVE: "项目归档",

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useMemo } from "react";
import { import {
Button, Button,
Table, Table,
@ -9,6 +9,7 @@ import {
Col, Col,
DatePicker, DatePicker,
Form, Form,
Modal,
message, message,
} from "antd"; } from "antd";
import dayjs from "dayjs"; import dayjs from "dayjs";
@ -20,7 +21,7 @@ import LayoutFooterPortal from "~/components/LayoutFooterPortal";
import PreviewUrlButton from "~/components/PreviewUrlButton"; import PreviewUrlButton from "~/components/PreviewUrlButton";
import ArchiveSupplementModal from "./ArchiveSupplementModal"; import ArchiveSupplementModal from "./ArchiveSupplementModal";
import { import {
NODE_LABELS,
ARCHIVE_RECORD_SOURCE_OPTIONS, ARCHIVE_RECORD_SOURCE_OPTIONS,
ARCHIVE_RECORD_SOURCE_BADGE_MAP, ARCHIVE_RECORD_SOURCE_BADGE_MAP,
} from "~/enumerate/constant"; } from "~/enumerate/constant";
@ -35,13 +36,10 @@ const ArchiveContent = ({ readOnly, ...props }) => {
const [archiveForm] = Form.useForm(); const [archiveForm] = Form.useForm();
const [archiveFiles, setArchiveFiles] = useState([]); const [archiveFiles, setArchiveFiles] = useState([]);
const [archiveTotal, setArchiveTotal] = useState(0); const [sourceFilter, setSourceFilter] = useState("");
const [archiveQuery, setArchiveQuery] = useState({ const [keyword, setKeyword] = useState("");
pageIndex: 1,
pageSize: 10,
recordSource: "",
});
const [supplementOpen, setSupplementOpen] = useState(false); const [supplementOpen, setSupplementOpen] = useState(false);
const [viewRecord, setViewRecord] = useState(null);
const fetchDetail = () => { const fetchDetail = () => {
props.evalProcessControlDetail({ projectId }); props.evalProcessControlDetail({ projectId });
@ -67,25 +65,32 @@ const ArchiveContent = ({ readOnly, ...props }) => {
}, [processControlDetail]); }, [processControlDetail]);
const fetchArchiveFiles = () => { const fetchArchiveFiles = () => {
props props.evalProjectArchiveFileOrgPage({ projectId }).then((res) => {
.evalProjectArchiveFileOrgPage({ if (res?.success !== false) {
projectId, setArchiveFiles(res?.data || []);
pageIndex: archiveQuery.pageIndex, }
pageSize: archiveQuery.pageSize, });
recordSource: archiveQuery.recordSource || undefined,
})
.then((res) => {
if (res?.success !== false) {
setArchiveFiles(res?.data || []);
setArchiveTotal(res?.totalCount || 0);
}
});
}; };
useEffect(() => { useEffect(() => {
if (!projectId) return; if (!projectId) return;
fetchArchiveFiles(); 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 = const isSignatureCompleted =
processControl?.reviewerSignatureTaskStatus === "completed"; processControl?.reviewerSignatureTaskStatus === "completed";
@ -109,35 +114,54 @@ const ArchiveContent = ({ readOnly, ...props }) => {
return /^https?:\/\//i.test(u) ? u : `${window.fileUrl || ""}${u}`; return /^https?:\/\//i.test(u) ? u : `${window.fileUrl || ""}${u}`;
}; };
const handleDownload = (file) => { /** 单文件下载:跨域地址下 a.download 无效fetch 转 blob 后再触发下载 */
const url = resolveFileUrl(file.fileUrl); const downloadArchiveFile = async (f) => {
const url = resolveFileUrl(f.url);
if (!url) return; if (!url) return;
const a = document.createElement("a"); const res = await fetch(url);
a.href = url; if (!res.ok) throw new Error(`HTTP ${res.status}`);
a.download = file.fileName || ""; const blobUrl = window.URL.createObjectURL(await res.blob());
a.target = "_blank"; const link = document.createElement("a");
document.body.appendChild(a); link.href = blobUrl;
a.click(); link.download = f.name || "";
document.body.removeChild(a); 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 = [ const archiveColumns = [
{ {
title: "序号", title: "序号",
width: 60, width: 60,
render: (_, __, index) => render: (_, __, index) => index + 1,
(archiveQuery.pageIndex - 1) * archiveQuery.pageSize + index + 1,
}, },
{ {
title: "文件名称", title: "文件名称",
dataIndex: "fileName", dataIndex: "files",
ellipsis: true, ellipsis: true,
render: (v) => v || "-", render: (v) => {
if(Array.isArray(v)){
return v.map(v=>v.name).join(',')
}
},
}, },
{ {
title: "档案类型", title: "档案类型",
dataIndex: "archiveType", dataIndex: "archiveType",
width: 130, width: 180,
}, },
{ {
@ -152,7 +176,7 @@ const ArchiveContent = ({ readOnly, ...props }) => {
> >
{ARCHIVE_RECORD_SOURCE_BADGE_MAP[v] || v || "-"} {ARCHIVE_RECORD_SOURCE_BADGE_MAP[v] || v || "-"}
</span> </span>
<span className="pf-archive__node">{r.businessNode || "-"}</span> <span className="pf-archive__node">{NODE_LABELS[r.businessNode] || "-"}</span>
</Flex> </Flex>
), ),
}, },
@ -165,8 +189,7 @@ const ArchiveContent = ({ readOnly, ...props }) => {
{ {
title: "形成时间", title: "形成时间",
dataIndex: "createTime", dataIndex: "createTime",
width: 160, width: 200,
align: "right",
render: (v) => v || "-", render: (v) => v || "-",
}, },
{ {
@ -174,12 +197,19 @@ const ArchiveContent = ({ readOnly, ...props }) => {
width: 110, width: 110,
render: (_, r) => ( render: (_, r) => (
<TableAction> <TableAction>
<PreviewUrlButton url={r.fileUrl}>查看</PreviewUrlButton>
<Button <Button
type="link" type="link"
size="small" size="small"
disabled={!r.fileUrl} disabled={!r.files?.length}
onClick={() => handleDownload(r)} onClick={() => setViewRecord(r)}
>
查看
</Button>
<Button
type="link"
size="small"
disabled={!r.files?.length}
onClick={() => handleBatchDownload(r.files)}
> >
下载 下载
</Button> </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 <Select
value={archiveQuery.recordSource} value={sourceFilter}
onChange={(v) => onChange={setSourceFilter}
setArchiveQuery((q) => ({ ...q, recordSource: v, pageIndex: 1 }))
}
options={[ options={[
{ label: "全部来源", value: "" }, { label: "全部来源", value: "" },
...ARCHIVE_RECORD_SOURCE_OPTIONS, ...ARCHIVE_RECORD_SOURCE_OPTIONS,
@ -211,18 +246,10 @@ const ArchiveContent = ({ readOnly, ...props }) => {
<Table <Table
rowKey="id" rowKey="id"
size="small" size="small"
dataSource={archiveFiles} dataSource={filteredArchiveFiles}
columns={archiveColumns} columns={archiveColumns}
loading={props.safetyEvalBusiness?.evalProjectArchiveFileOrgLoading} loading={props.safetyEvalBusiness?.evalProjectArchiveFileOrgLoading}
pagination={{ pagination={false}
total: archiveTotal,
current: archiveQuery.pageIndex,
pageSize: archiveQuery.pageSize,
showQuickJumper: true,
showTotal: (t) => `${t}`,
onChange: (page, pageSize) =>
setArchiveQuery((q) => ({ ...q, pageIndex: page, pageSize })),
}}
/> />
<Form form={archiveForm} layout="vertical" disabled={readOnly} scrollToFirstError> <Form form={archiveForm} layout="vertical" disabled={readOnly} scrollToFirstError>
@ -281,6 +308,23 @@ const ArchiveContent = ({ readOnly, ...props }) => {
fetchArchiveFiles(); 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> </div>
); );
}; };

View File

@ -17,7 +17,7 @@ function ArchiveSupplementModal({
const handleOk = async () => { const handleOk = async () => {
const values = await form.validateFields(); const values = await form.validateFields();
const fileList = values.file || []; const fileList = values.files || [];
if (fileList.some((f) => f.status === "uploading")) { if (fileList.some((f) => f.status === "uploading")) {
message.warning("文件正在上传中,请等待上传完成"); message.warning("文件正在上传中,请等待上传完成");
return; return;
@ -34,7 +34,7 @@ function ArchiveSupplementModal({
businessNode: "文档上传", businessNode: "文档上传",
recordSource: "upload_additional_files", recordSource: "upload_additional_files",
remark: values.remark, remark: values.remark,
file: doneFiles, files: doneFiles,
}); });
if (res?.success === false) return; if (res?.success === false) return;
message.success("补充档案已上传"); message.success("补充档案已上传");
@ -72,7 +72,7 @@ function ArchiveSupplementModal({
<TextArea rows={3} placeholder="填写资料内容或补充说明" maxLength={500} /> <TextArea rows={3} placeholder="填写资料内容或补充说明" maxLength={500} />
</Form.Item> </Form.Item>
<AttachmentUpload <AttachmentUpload
name="file" name="files"
label="资料上传" label="资料上传"
rules={[{ required: true, message: "请选择需要上传的文件" }]} rules={[{ required: true, message: "请选择需要上传的文件" }]}
extra="支持上传多个文件" extra="支持上传多个文件"

View File

@ -298,6 +298,10 @@
margin-bottom: 12px; margin-bottom: 12px;
} }
&__keyword-input {
width: 260px;
}
&__source-select { &__source-select {
width: 140px; width: 140px;
} }

View File

@ -1,8 +1,9 @@
import React, { useEffect, useState } from "react"; 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 TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import PreviewUrlButton from "~/components/PreviewUrlButton"; import PreviewUrlButton from "~/components/PreviewUrlButton";
import { import {
NODE_LABELS,
ARCHIVE_TYPE_MAP, ARCHIVE_TYPE_MAP,
ARCHIVE_RECORD_SOURCE_MAP, ARCHIVE_RECORD_SOURCE_MAP,
} from "~/enumerate/constant"; } from "~/enumerate/constant";
@ -19,6 +20,7 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
const [query, setQuery] = useState({ pageIndex: 1, pageSize: 10, fileName: "" }); const [query, setQuery] = useState({ pageIndex: 1, pageSize: 10, fileName: "" });
const [list, setList] = useState([]); const [list, setList] = useState([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [viewRecord, setViewRecord] = useState(null);
@ -32,16 +34,32 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
}); });
}, [record?.id, query]); }, [record?.id, query]);
const handleDownload = (file) => { /** 单文件下载:跨域地址下 a.download 无效fetch 转 blob 后再触发下载 */
const url = resolveFileUrl(file.fileUrl); const downloadArchiveFile = async (f) => {
const url = resolveFileUrl(f.url);
if (!url) return; if (!url) return;
const a = document.createElement("a"); const res = await fetch(url);
a.href = url; if (!res.ok) throw new Error(`HTTP ${res.status}`);
a.download = file.fileName || ""; const blobUrl = window.URL.createObjectURL(await res.blob());
a.target = "_blank"; const link = document.createElement("a");
document.body.appendChild(a); link.href = blobUrl;
a.click(); link.download = f.name || "";
document.body.removeChild(a); 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 = [ const columns = [
@ -53,17 +71,17 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
}, },
{ {
title: "文件名称", title: "文件名称",
dataIndex: "fileName", dataIndex: "files",
ellipsis: true, ellipsis: true,
render: (v, r) => ( render: (v, r) => (
<div> <div>
<div>{v || "-"}</div> {Array.isArray(v) && v.length
<div className="epd-archive__file-source"> ? v.map((f) => f.name).join(",")
{r.recordSource === "upload_additional_files" : "-"}
? "评价机构补充上传"
: "业务节点自动归集"}
</div>
</div> </div>
), ),
}, },
{ {
@ -89,19 +107,26 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
title: "业务节点", title: "业务节点",
dataIndex: "businessNode", dataIndex: "businessNode",
width: 130, width: 130,
render: (v) => v || "-", render: (v) => NODE_LABELS[v] || v || "-",
}, },
{ {
title: "操作", title: "操作",
width: 120, width: 120,
render: (_, r) => ( render: (_, r) => (
<TableAction> <TableAction>
<PreviewUrlButton url={r.fileUrl}>查看</PreviewUrlButton>
<Button <Button
type="link" type="link"
size="small" size="small"
disabled={!r.fileUrl} disabled={!r.files?.length}
onClick={() => handleDownload(r)} onClick={() => setViewRecord(r)}
>
查看
</Button>
<Button
type="link"
size="small"
disabled={!r.files?.length}
onClick={() => handleBatchDownload(r.files)}
> >
下载 下载
</Button> </Button>
@ -178,6 +203,23 @@ function ArchiveModal({ record, loading, fetchFiles, onClose }) {
setQuery((q) => ({ ...q, pageIndex: page, pageSize })), 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> </Modal>
); );
} }