fix
parent
5991b95195
commit
a874c2cb0b
|
|
@ -301,6 +301,18 @@ export const evalProcessControlFiles = declareRequest(
|
||||||
"processControlFiles: [] | res.data || []",
|
"processControlFiles: [] | res.data || []",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 项目档案文件列表-机构端分页(分页参数 pageIndex/pageSize,总数 totalCount) */
|
||||||
|
export const evalProjectArchiveFileOrgPage = declareRequest(
|
||||||
|
"evalProjectArchiveFileOrgLoading",
|
||||||
|
"Get > /safetyEval/eval-project-archive-file/org/page",
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 新增项目档案文件(机构补充上传) */
|
||||||
|
export const evalProjectArchiveFileAdd = declareRequest(
|
||||||
|
"evalProjectArchiveFileAddLoading",
|
||||||
|
"Post > @/safetyEval/eval-project-archive-file",
|
||||||
|
);
|
||||||
|
|
||||||
// ===== 监管端-安评项目实时监控 =====
|
// ===== 监管端-安评项目实时监控 =====
|
||||||
|
|
||||||
export const evalProjectMonitorStat = declareRequest(
|
export const evalProjectMonitorStat = declareRequest(
|
||||||
|
|
@ -346,6 +358,12 @@ export const evalProjectArchivePage = declareRequest(
|
||||||
"Get > /safetyEval/institution/eval-project/archive-supervision-page",
|
"Get > /safetyEval/institution/eval-project/archive-supervision-page",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 项目档案文件列表(监管端,分页参数为 pageIndex/pageSize,总数 totalCount) */
|
||||||
|
export const evalProjectArchiveFilePage = declareRequest(
|
||||||
|
"evalProjectArchiveFileLoading",
|
||||||
|
"Get > /safetyEval/eval-project-archive-file/monitor/page",
|
||||||
|
);
|
||||||
|
|
||||||
// ===== 机构端-安评报告库 =====
|
// ===== 机构端-安评报告库 =====
|
||||||
|
|
||||||
export const evalReportSummary = declareRequest(
|
export const evalReportSummary = declareRequest(
|
||||||
|
|
|
||||||
|
|
@ -784,3 +784,47 @@ export const QUESTION_TYPE_MAP = QUESTION_TYPE_OPTIONS.reduce(
|
||||||
(acc, cur) => ({ ...acc, [cur.value]: cur.label }),
|
(acc, cur) => ({ ...acc, [cur.value]: cur.label }),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** 项目档案类型(eval_project_archive_file.archive_type) */
|
||||||
|
export const ARCHIVE_TYPE_OPTIONS = [
|
||||||
|
{ label: "项目风险分析", value: "RISK_ANALYSIS" },
|
||||||
|
{ label: "技术服务合同", value: "TECH_SERVICE_CONTRACT" },
|
||||||
|
{ label: "项目组织文件", value: "PROJECT_ORG_FILE" },
|
||||||
|
{ label: "实施计划", value: "IMPLEMENT_PLAN" },
|
||||||
|
{ label: "从业告知", value: "PRACTICE_NOTICE" },
|
||||||
|
{ label: "现场记录", value: "SITE_RECORD" },
|
||||||
|
{ label: "现场勘察资料", value: "SITE_SURVEY" },
|
||||||
|
{ label: "现场检查资料", value: "SITE_CHECK" },
|
||||||
|
{ label: "整改复查资料", value: "RECTIFICATION_REVIEW" },
|
||||||
|
{ label: "评价报告", value: "EVALUATION_REPORT" },
|
||||||
|
{ label: "审核记录", value: "REVIEW_RECORD" },
|
||||||
|
{ label: "过程控制文件", value: "PROCESS_CONTROL" },
|
||||||
|
{ label: "签发文件", value: "SIGN_ISSUE" },
|
||||||
|
{ label: "专家评审材料", value: "EXPERT_REVIEW" },
|
||||||
|
{ label: "项目附图", value: "PROJECT_ATTACHMENT" },
|
||||||
|
{ label: "报告修改材料", value: "REPORT_MODIFICATION" },
|
||||||
|
{ label: "往来文件", value: "CORRESPONDENCE" },
|
||||||
|
{ label: "其他", value: "OTHER" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ARCHIVE_TYPE_MAP = ARCHIVE_TYPE_OPTIONS.reduce(
|
||||||
|
(acc, cur) => ({ ...acc, [cur.value]: cur.label }),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
/** 档案文件来源(record_source:auto_process_record流程自动记录 / upload_additional_files补充上传) */
|
||||||
|
export const ARCHIVE_RECORD_SOURCE_MAP = {
|
||||||
|
auto_process_record: "系统形成",
|
||||||
|
upload_additional_files: "机构补充上传",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 档案文件来源选项与徽标文案(机构端档案内容列表) */
|
||||||
|
export const ARCHIVE_RECORD_SOURCE_OPTIONS = [
|
||||||
|
{ label: "流程自动记录", value: "auto_process_record" },
|
||||||
|
{ label: "补充上传", value: "upload_additional_files" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ARCHIVE_RECORD_SOURCE_BADGE_MAP = ARCHIVE_RECORD_SOURCE_OPTIONS.reduce(
|
||||||
|
(acc, cur) => ({ ...acc, [cur.value]: cur.label }),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,7 @@
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: #eff6ff;
|
background: #eff6ff;
|
||||||
color: #475569;
|
color: #475569;
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Tag,
|
|
||||||
Button,
|
Button,
|
||||||
Card,
|
|
||||||
Table,
|
Table,
|
||||||
Input,
|
Input,
|
||||||
Flex,
|
Flex,
|
||||||
Popover,
|
Select,
|
||||||
Image,
|
|
||||||
Row,
|
Row,
|
||||||
Col,
|
Col,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
|
|
@ -16,33 +13,43 @@ import {
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||||||
|
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
|
||||||
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||||
import { tools } from "@cqsjjb/jjb-common-lib";
|
import { tools } from "@cqsjjb/jjb-common-lib";
|
||||||
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
|
import LayoutFooterPortal from "~/components/LayoutFooterPortal";
|
||||||
|
import PreviewUrlButton from "~/components/PreviewUrlButton";
|
||||||
|
import ArchiveSupplementModal from "./ArchiveSupplementModal";
|
||||||
|
import {
|
||||||
|
|
||||||
|
ARCHIVE_RECORD_SOURCE_OPTIONS,
|
||||||
|
ARCHIVE_RECORD_SOURCE_BADGE_MAP,
|
||||||
|
} from "~/enumerate/constant";
|
||||||
|
|
||||||
const { router } = tools;
|
const { router } = tools;
|
||||||
|
|
||||||
const ArchiveContent = ({ readOnly, ...props }) => {
|
const ArchiveContent = ({ readOnly, ...props }) => {
|
||||||
const projectId = router.query?.id;
|
const projectId = router.query?.id;
|
||||||
const { processControlDetail, processControlFiles, evalProjectDetailData } =
|
const { processControlDetail, evalProjectDetailData } =
|
||||||
props.safetyEvalBusiness || {};
|
props.safetyEvalBusiness || {};
|
||||||
const { processControl, archiveFlag } = processControlDetail || {};
|
const { processControl, archiveFlag } = processControlDetail || {};
|
||||||
|
|
||||||
const [files, setFiles] = useState([]);
|
|
||||||
const [archiveForm] = Form.useForm();
|
const [archiveForm] = Form.useForm();
|
||||||
|
const [archiveFiles, setArchiveFiles] = useState([]);
|
||||||
|
const [archiveTotal, setArchiveTotal] = useState(0);
|
||||||
|
const [archiveQuery, setArchiveQuery] = useState({
|
||||||
|
pageIndex: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
recordSource: "",
|
||||||
|
});
|
||||||
|
const [supplementOpen, setSupplementOpen] = useState(false);
|
||||||
|
|
||||||
const fetchDetail = () => {
|
const fetchDetail = () => {
|
||||||
props.evalProcessControlDetail({ projectId });
|
props.evalProcessControlDetail({ projectId });
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchFiles = () => {
|
|
||||||
props.evalProcessControlFiles({ projectId });
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
fetchDetail();
|
fetchDetail();
|
||||||
fetchFiles();
|
|
||||||
}
|
}
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
|
|
@ -59,11 +66,26 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
||||||
}
|
}
|
||||||
}, [processControlDetail]);
|
}, [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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (processControlFiles) {
|
if (!projectId) return;
|
||||||
setFiles(processControlFiles);
|
fetchArchiveFiles();
|
||||||
}
|
}, [projectId, archiveQuery]);
|
||||||
}, [processControlFiles]);
|
|
||||||
|
|
||||||
const isSignatureCompleted =
|
const isSignatureCompleted =
|
||||||
processControl?.reviewerSignatureTaskStatus === "completed";
|
processControl?.reviewerSignatureTaskStatus === "completed";
|
||||||
|
|
@ -80,91 +102,127 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isImage = (name) =>
|
/** 解析文件地址:相对路径拼接 window.fileUrl */
|
||||||
/\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i.test(name || "");
|
const resolveFileUrl = (raw) => {
|
||||||
|
if (!raw) return "";
|
||||||
|
const u = String(raw);
|
||||||
|
return /^https?:\/\//i.test(u) ? u : `${window.fileUrl || ""}${u}`;
|
||||||
|
};
|
||||||
|
|
||||||
const fileColumns = [
|
const handleDownload = (file) => {
|
||||||
{ title: "流程名称", dataIndex: "processName", ellipsis: true },
|
const url = resolveFileUrl(file.fileUrl);
|
||||||
{ title: "条目名称", dataIndex: "itemName", ellipsis: true },
|
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 archiveColumns = [
|
||||||
{
|
{
|
||||||
title: "文件",
|
title: "序号",
|
||||||
dataIndex: "projectDocUrl",
|
width: 60,
|
||||||
render: (val) => {
|
render: (_, __, index) =>
|
||||||
let fileList = [];
|
(archiveQuery.pageIndex - 1) * archiveQuery.pageSize + index + 1,
|
||||||
if (typeof val === "string") {
|
},
|
||||||
try {
|
{
|
||||||
fileList = JSON.parse(val);
|
title: "文件名称",
|
||||||
} catch {
|
dataIndex: "fileName",
|
||||||
fileList = [];
|
ellipsis: true,
|
||||||
}
|
render: (v) => v || "-",
|
||||||
} else if (Array.isArray(val)) {
|
},
|
||||||
fileList = val;
|
{
|
||||||
}
|
title: "档案类型",
|
||||||
if (!fileList.length) return "-";
|
dataIndex: "archiveType",
|
||||||
return (
|
width: 130,
|
||||||
<Flex vertical gap={2}>
|
|
||||||
<Popover
|
},
|
||||||
title="附件列表"
|
{
|
||||||
content={
|
title: "来源/业务节点",
|
||||||
<Flex vertical gap={4}>
|
dataIndex: "recordSource",
|
||||||
{fileList.map((f, i) => {
|
width: 150,
|
||||||
const url = f.url || f.response?.url;
|
render: (v, r) => (
|
||||||
return isImage(f.name) ? (
|
<Flex vertical gap={4}>
|
||||||
<Flex key={f.uid || i} align="center" gap={4}>
|
<span
|
||||||
{<Image src={url} width={80} />}
|
className={`pf-archive__source-tag${v === "upload_additional_files" ? " pf-archive__source-tag--upload" : ""
|
||||||
</Flex>
|
}`}
|
||||||
) : (
|
>
|
||||||
<a
|
{ARCHIVE_RECORD_SOURCE_BADGE_MAP[v] || v || "-"}
|
||||||
key={f.uid || i}
|
</span>
|
||||||
style={{ cursor: "pointer" }}
|
<span className="pf-archive__node">{r.businessNode || "-"}</span>
|
||||||
onClick={() => window.open(url)}
|
</Flex>
|
||||||
>
|
),
|
||||||
{f.name || "查看文件"}
|
},
|
||||||
</a>
|
{
|
||||||
);
|
title: "形成/上传人员",
|
||||||
})}
|
dataIndex: "createName",
|
||||||
</Flex>
|
width: 120,
|
||||||
}
|
render: (v) => v || "-",
|
||||||
trigger="hover"
|
},
|
||||||
placement="topLeft"
|
{
|
||||||
>
|
title: "形成时间",
|
||||||
<a
|
dataIndex: "createTime",
|
||||||
onClick={(e) => e.preventDefault()}
|
width: 160,
|
||||||
style={{ cursor: "pointer" }}
|
align: "right",
|
||||||
>
|
render: (v) => v || "-",
|
||||||
{fileList.length}个附件
|
},
|
||||||
</a>
|
{
|
||||||
</Popover>
|
title: "操作",
|
||||||
</Flex>
|
width: 110,
|
||||||
);
|
render: (_, r) => (
|
||||||
},
|
<TableAction>
|
||||||
|
<PreviewUrlButton url={r.fileUrl}>查看</PreviewUrlButton>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
disabled={!r.fileUrl}
|
||||||
|
onClick={() => handleDownload(r)}
|
||||||
|
>
|
||||||
|
下载
|
||||||
|
</Button>
|
||||||
|
</TableAction>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<div
|
||||||
title="项目归档"
|
|
||||||
style={{ marginBottom: 16 }}
|
|
||||||
styles={{ body: { paddingBottom: 0 } }}
|
|
||||||
>
|
>
|
||||||
<div className="pf-permission">
|
|
||||||
<Tag color="blue" className="pf-tag">
|
|
||||||
项目归档
|
<Flex justify="flex-end" className="pf-archive__toolbar">
|
||||||
</Tag>
|
<Select
|
||||||
<span className="pf-desc">
|
value={archiveQuery.recordSource}
|
||||||
项目过程文件清单,确认签字完成后归档入库
|
onChange={(v) =>
|
||||||
</span>
|
setArchiveQuery((q) => ({ ...q, recordSource: v, pageIndex: 1 }))
|
||||||
</div>
|
}
|
||||||
|
options={[
|
||||||
|
{ label: "全部来源", value: "" },
|
||||||
|
...ARCHIVE_RECORD_SOURCE_OPTIONS,
|
||||||
|
]}
|
||||||
|
className="pf-archive__source-select"
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
dataSource={files}
|
rowKey="id"
|
||||||
columns={fileColumns}
|
|
||||||
rowKey={(record) =>
|
|
||||||
`${record.processName}-${record.materialName}-${record.itemName}`
|
|
||||||
}
|
|
||||||
pagination={false}
|
|
||||||
size="small"
|
size="small"
|
||||||
|
dataSource={archiveFiles}
|
||||||
|
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 })),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Form form={archiveForm} layout="vertical" disabled={readOnly} scrollToFirstError>
|
<Form form={archiveForm} layout="vertical" disabled={readOnly} scrollToFirstError>
|
||||||
|
|
@ -197,18 +255,33 @@ const ArchiveContent = ({ readOnly, ...props }) => {
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<LayoutFooterPortal slotId="pf-footer-slot">
|
<LayoutFooterPortal slotId="pf-footer-slot">
|
||||||
<Flex justify="flex-end" gap={8}>
|
<Flex justify="flex-end" gap={8}>
|
||||||
|
<Button disabled={archiveFlag} onClick={() => setSupplementOpen(true)}>
|
||||||
|
补充上传
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={handleArchive}
|
onClick={handleArchive}
|
||||||
loading={props.safetyEvalBusiness?.processControlArchiveLoading}
|
loading={props.safetyEvalBusiness?.processControlArchiveLoading}
|
||||||
disabled={!canArchive || archiveFlag}
|
disabled={!canArchive || archiveFlag}
|
||||||
>
|
>
|
||||||
{archiveFlag ? '已归档' : '双方签字并确认入库'}
|
{archiveFlag ? '已归档' : '确认归档入库'}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</LayoutFooterPortal>
|
</LayoutFooterPortal>
|
||||||
)}
|
)}
|
||||||
</Card>
|
|
||||||
|
<ArchiveSupplementModal
|
||||||
|
open={supplementOpen}
|
||||||
|
projectId={projectId}
|
||||||
|
loading={props.safetyEvalBusiness?.evalProjectArchiveFileAddLoading}
|
||||||
|
onAdd={props.evalProjectArchiveFileAdd}
|
||||||
|
onClose={() => setSupplementOpen(false)}
|
||||||
|
onSuccess={() => {
|
||||||
|
setSupplementOpen(false);
|
||||||
|
fetchArchiveFiles();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Form, Input, Modal, message } from "antd";
|
||||||
|
import AttachmentUpload from "~/components/AttachmentUpload";
|
||||||
|
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
/** 补充上传项目档案弹窗(原型 modal-archive-supplement) */
|
||||||
|
function ArchiveSupplementModal({
|
||||||
|
open,
|
||||||
|
projectId,
|
||||||
|
loading,
|
||||||
|
onAdd,
|
||||||
|
onClose,
|
||||||
|
onSuccess,
|
||||||
|
}) {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const handleOk = async () => {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
const fileList = values.file || [];
|
||||||
|
if (fileList.some((f) => f.status === "uploading")) {
|
||||||
|
message.warning("文件正在上传中,请等待上传完成");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const doneFiles = fileList.filter((f) => f.status === "done" && f.url);
|
||||||
|
if (!doneFiles.length) {
|
||||||
|
message.warning("请先上传资料文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const total = doneFiles.length;
|
||||||
|
const res = await onAdd({
|
||||||
|
projectId,
|
||||||
|
archiveType: values.archiveType,
|
||||||
|
businessNode: "文档上传",
|
||||||
|
recordSource: "upload_additional_files",
|
||||||
|
remark: values.remark,
|
||||||
|
file: doneFiles,
|
||||||
|
});
|
||||||
|
if (res?.success === false) return;
|
||||||
|
message.success("补充档案已上传");
|
||||||
|
onSuccess?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
title="补充上传项目档案"
|
||||||
|
width={680}
|
||||||
|
okText="确认上传"
|
||||||
|
cancelText="取消"
|
||||||
|
confirmLoading={loading}
|
||||||
|
destroyOnHidden
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={onClose}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical" preserve={false}>
|
||||||
|
<Form.Item
|
||||||
|
label="资料名称"
|
||||||
|
name="fileName"
|
||||||
|
rules={[{ required: true, message: "请输入资料名称" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入资料名称" allowClear maxLength={100} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="档案类型"
|
||||||
|
name="archiveType"
|
||||||
|
rules={[{ required: true, message: "请填写档案类型" }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="自行填写档案类型" allowClear maxLength={50} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="资料说明" name="remark">
|
||||||
|
<TextArea rows={3} placeholder="填写资料内容或补充说明" maxLength={500} />
|
||||||
|
</Form.Item>
|
||||||
|
<AttachmentUpload
|
||||||
|
name="file"
|
||||||
|
label="资料上传"
|
||||||
|
rules={[{ required: true, message: "请选择需要上传的文件" }]}
|
||||||
|
extra="支持上传多个文件"
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArchiveSupplementModal;
|
||||||
|
|
@ -173,7 +173,6 @@ const ProjectFlow = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const openReportEditor = async () => {
|
const openReportEditor = async () => {
|
||||||
// 2026-08-18 前置拦截:报告文档 fileId 缺省(文档尚未生成/加载完成)时提示,不打开编辑器,避免 SDK 报错
|
|
||||||
if (!wpsDoc?.fileId) {
|
if (!wpsDoc?.fileId) {
|
||||||
message.warning("报告文档尚未生成,请先创建报告后再查看");
|
message.warning("报告文档尚未生成,请先创建报告后再查看");
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -291,3 +291,34 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 档案内容 - 档案文件列表
|
||||||
|
.pf-archive {
|
||||||
|
&__toolbar {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__source-select {
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__source-tag {
|
||||||
|
display: inline-block;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 2px 10px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
background: #52c41a;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&--upload {
|
||||||
|
background: #1677ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__node {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Button, Input, Modal, Table, Tag } from "antd";
|
||||||
|
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
|
||||||
|
import PreviewUrlButton from "~/components/PreviewUrlButton";
|
||||||
|
import {
|
||||||
|
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 [query, setQuery] = useState({ pageIndex: 1, pageSize: 10, fileName: "" });
|
||||||
|
const [list, setList] = useState([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!record?.id) return;
|
||||||
|
fetchFiles({ projectId: record.id, ...query }).then((res) => {
|
||||||
|
if (res?.success !== false) {
|
||||||
|
setList(res?.data || []);
|
||||||
|
setTotal(res?.totalCount || 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [record?.id, query]);
|
||||||
|
|
||||||
|
const handleDownload = (file) => {
|
||||||
|
const url = resolveFileUrl(file.fileUrl);
|
||||||
|
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 columns = [
|
||||||
|
{
|
||||||
|
title: "序号",
|
||||||
|
width: 60,
|
||||||
|
render: (_, __, index) =>
|
||||||
|
(query.pageIndex - 1) * query.pageSize + index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "文件名称",
|
||||||
|
dataIndex: "fileName",
|
||||||
|
ellipsis: true,
|
||||||
|
render: (v, r) => (
|
||||||
|
<div>
|
||||||
|
<div>{v || "-"}</div>
|
||||||
|
<div className="epd-archive__file-source">
|
||||||
|
{r.recordSource === "upload_additional_files"
|
||||||
|
? "评价机构补充上传"
|
||||||
|
: "业务节点自动归集"}
|
||||||
|
</div>
|
||||||
|
</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) => v || "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
width: 120,
|
||||||
|
render: (_, r) => (
|
||||||
|
<TableAction>
|
||||||
|
<PreviewUrlButton url={r.fileUrl}>查看</PreviewUrlButton>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
disabled={!r.fileUrl}
|
||||||
|
onClick={() => handleDownload(r)}
|
||||||
|
>
|
||||||
|
下载
|
||||||
|
</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
|
||||||
|
onSearch={(v) => setQuery((q) => ({ ...q, fileName: v, pageIndex: 1 }))}
|
||||||
|
className="epd-archive__search"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
size="small"
|
||||||
|
columns={columns}
|
||||||
|
dataSource={list}
|
||||||
|
loading={loading}
|
||||||
|
scroll={{ x: 800, y: 400 }}
|
||||||
|
pagination={{
|
||||||
|
total,
|
||||||
|
current: query.pageIndex,
|
||||||
|
pageSize: query.pageSize,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (t) => `共 ${t} 条`,
|
||||||
|
onChange: (page, pageSize) =>
|
||||||
|
setQuery((q) => ({ ...q, pageIndex: page, pageSize })),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArchiveModal;
|
||||||
|
|
@ -10,6 +10,7 @@ import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
|
||||||
import { EVAL_TYPE_OPTIONS, EVAL_TYPE_MAP } from "~/enumerate/constant";
|
import { EVAL_TYPE_OPTIONS, EVAL_TYPE_MAP } from "~/enumerate/constant";
|
||||||
import { tools } from "@cqsjjb/jjb-common-lib";
|
import { tools } from "@cqsjjb/jjb-common-lib";
|
||||||
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
|
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
|
||||||
|
import ArchiveModal from "./ArchiveModal";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -27,6 +28,7 @@ function EvalProjectDatabase(props) {
|
||||||
const [searchForm] = Form.useForm();
|
const [searchForm] = Form.useForm();
|
||||||
const [dataSource, setDataSource] = useState([]);
|
const [dataSource, setDataSource] = useState([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
|
const [archiveRecord, setArchiveRecord] = useState(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
safetyEvalBusiness,
|
safetyEvalBusiness,
|
||||||
|
|
@ -113,14 +115,16 @@ function EvalProjectDatabase(props) {
|
||||||
{
|
{
|
||||||
title: "最近更新",
|
title: "最近更新",
|
||||||
dataIndex: "updateTime",
|
dataIndex: "updateTime",
|
||||||
|
width: 180,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
dataIndex: "operate",
|
dataIndex: "operate",
|
||||||
fix: 'right',
|
fixed: "right",
|
||||||
width: 200,
|
width: 200,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return <TableAction>
|
return <TableAction>
|
||||||
|
<a onClick={() => setArchiveRecord(record)}>查看档案</a>
|
||||||
<a onClick={() => {
|
<a onClick={() => {
|
||||||
props.history.push(`/container/MonitorManage/EvalProjectMonitor/Detail?id=${record.id}`)
|
props.history.push(`/container/MonitorManage/EvalProjectMonitor/Detail?id=${record.id}`)
|
||||||
}}>项目过程</a>
|
}}>项目过程</a>
|
||||||
|
|
@ -189,6 +193,13 @@ function EvalProjectDatabase(props) {
|
||||||
}}
|
}}
|
||||||
onChange={handlePageChange}
|
onChange={handlePageChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{archiveRecord&&<ArchiveModal
|
||||||
|
record={archiveRecord}
|
||||||
|
loading={safetyEvalBusiness?.evalProjectArchiveFileLoading}
|
||||||
|
fetchFiles={props.evalProjectArchiveFilePage}
|
||||||
|
onClose={() => setArchiveRecord(null)}
|
||||||
|
/>}
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,59 @@
|
||||||
margin: 12px 0 16px;
|
margin: 12px 0 16px;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
color: rgba(0, 0, 0, 65%);
|
color: rgba(0, 0, 0, 65%);
|
||||||
font-size: 13px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
background: #fffbe6;
|
background: #fffbe6;
|
||||||
border: 1px solid #ffe58f;
|
border: 1px solid #ffe58f;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 项目档案查看弹窗 */
|
||||||
|
.epd-archive {
|
||||||
|
&__subtitle {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #0f172a;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__search {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
max-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__file-source {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue