Merge remote-tracking branch 'origin/dev-tmp1' into dev-tmp1

dev-tmp1
huwei 2026-08-27 09:27:09 +08:00
commit e55b4eaf80
14 changed files with 725 additions and 115 deletions

View File

@ -13,8 +13,8 @@ module.exports = {
//API_HOST: "http://localhost:80",
// API_HOST: "http://192.168.0.134",
API_HOST: "http://192.168.0.150", //太浅
// API_HOST: "https://gbs-gateway.qhdsafety.com",
//API_HOST: "http://192.168.0.150", //太浅
API_HOST: "https://gbs-gateway.qhdsafety.com",
// API_HOST: "http://192.168.0.103", //huwei
},
production: {

View File

@ -301,6 +301,18 @@ export const evalProcessControlFiles = declareRequest(
"processControlFiles: [] | res.data || []",
);
/** 项目档案文件列表-机构端 */
export const evalProjectArchiveFileOrgPage = declareRequest(
"evalProjectArchiveFileOrgLoading",
"Get > /safetyEval/eval-project-archive-file/org/list",
);
/** 新增项目档案文件(机构补充上传) */
export const evalProjectArchiveFileAdd = declareRequest(
"evalProjectArchiveFileAddLoading",
"Post > @/safetyEval/eval-project-archive-file",
);
// ===== 监管端-安评项目实时监控 =====
export const evalProjectMonitorStat = declareRequest(
@ -346,6 +358,12 @@ export const evalProjectArchivePage = declareRequest(
"Get > /safetyEval/institution/eval-project/archive-supervision-page",
);
/** 项目档案文件列表-监管端 */
export const evalProjectArchiveFilePage = declareRequest(
"evalProjectArchiveFileLoading",
"Get > /safetyEval/eval-project-archive-file/monitor/page",
);
// ===== 机构端-安评报告库 =====
export const evalReportSummary = declareRequest(

View File

@ -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: "项目归档",
@ -784,3 +784,47 @@ export const QUESTION_TYPE_MAP = QUESTION_TYPE_OPTIONS.reduce(
(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_sourceauto_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 }),
{},
);

View File

@ -94,12 +94,14 @@ function ClassAddPage(props) {
const handleSubmit = async () => {
try {
const values = await form.validateFields();
// 培训日期范围拆为后端 startDate/endDateyyyy-MM-dd
// 培训日期范围拆为后端 startDate/endDate;结束时间为当天最大值(与延期一致
const { trainingDateRange, ...rest } = values;
const payload = {
...rest,
startDate: trainingDateRange?.[0]?.format("YYYY-MM-DD"),
endDate: trainingDateRange?.[1]?.format("YYYY-MM-DD"),
endDate: trainingDateRange?.[1]
?.endOf("day")
.format("YYYY-MM-DD HH:mm:ss"),
...(isEdit ? { id: router.query.classId } : {}),
};
const res = await (isEdit

View File

@ -523,6 +523,7 @@ const EvalProjectMonitorDetail = (props) => {
<WpsEditor
onCancel={() => setWpsVisible(false)}
fileId={wpsDoc?.fileId}
fileName={wpsDoc?.name}
readOnly
/>
)}

View File

@ -249,7 +249,7 @@
border-radius: 4px;
background: #eff6ff;
color: #475569;
font-size: 13px;
font-size: 14px;
line-height: 1.65;
strong {

View File

@ -1,48 +1,54 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useMemo } from "react";
import {
Tag,
Button,
Card,
Table,
Input,
Flex,
Popover,
Image,
Select,
Row,
Col,
DatePicker,
Form,
Modal,
message,
} from "antd";
import dayjs from "dayjs";
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 { tools } from "@cqsjjb/jjb-common-lib";
import { useDebounce } from "~/utils";
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";
const { router } = tools;
const ArchiveContent = ({ readOnly, ...props }) => {
const projectId = router.query?.id;
const { processControlDetail, processControlFiles, evalProjectDetailData } =
const { processControlDetail, evalProjectDetailData } =
props.safetyEvalBusiness || {};
const { processControl, archiveFlag } = processControlDetail || {};
const [files, setFiles] = useState([]);
const [archiveForm] = Form.useForm();
const [archiveFiles, setArchiveFiles] = useState([]);
const [sourceFilter, setSourceFilter] = useState("");
const [keyword, setKeyword] = useState("");
const [supplementOpen, setSupplementOpen] = useState(false);
const [viewRecord, setViewRecord] = useState(null);
const fetchDetail = () => {
props.evalProcessControlDetail({ projectId });
};
const fetchFiles = () => {
props.evalProcessControlFiles({ projectId });
};
useEffect(() => {
if (projectId) {
fetchDetail();
fetchFiles();
}
}, [projectId]);
@ -59,11 +65,34 @@ const ArchiveContent = ({ readOnly, ...props }) => {
}
}, [processControlDetail]);
useEffect(() => {
if (processControlFiles) {
setFiles(processControlFiles);
const fetchArchiveFiles = () => {
props.evalProjectArchiveFileOrgPage({ projectId }).then((res) => {
if (res?.success !== false) {
setArchiveFiles(res?.data || []);
}
}, [processControlFiles]);
});
};
useEffect(() => {
if (!projectId) return;
fetchArchiveFiles();
}, [projectId]);
/** 本地筛选:来源 + 文件名称/档案类型关键字(防抖 400ms */
const debouncedKeyword = useDebounce(keyword, 400);
const filteredArchiveFiles = useMemo(() => {
const kw = debouncedKeyword.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, debouncedKeyword]);
const isSignatureCompleted =
processControl?.reviewerSignatureTaskStatus === "completed";
@ -80,91 +109,149 @@ const ArchiveContent = ({ readOnly, ...props }) => {
}
};
const isImage = (name) =>
/\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i.test(name || "");
/** 解析文件地址:相对路径拼接 window.fileUrl */
const resolveFileUrl = (raw) => {
if (!raw) return "";
const u = String(raw);
return /^https?:\/\//i.test(u) ? u : `${window.fileUrl || ""}${u}`;
};
const fileColumns = [
{ title: "流程名称", dataIndex: "processName", ellipsis: true },
{ title: "条目名称", dataIndex: "itemName", ellipsis: true },
{
title: "文件",
dataIndex: "projectDocUrl",
render: (val) => {
let fileList = [];
if (typeof val === "string") {
/** 单文件下载:跨域地址下 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 {
fileList = JSON.parse(val);
await downloadArchiveFile(f);
} catch {
fileList = [];
message.error(`文件下载失败:${f.name || f.url}`);
}
} else if (Array.isArray(val)) {
fileList = val;
}
if (!fileList.length) return "-";
return (
<Flex vertical gap={2}>
<Popover
title="附件列表"
content={
<Flex vertical gap={4}>
{fileList.map((f, i) => {
const url = f.url || f.response?.url;
return isImage(f.name) ? (
<Flex key={f.uid || i} align="center" gap={4}>
{<Image src={url} width={80} />}
</Flex>
) : (
<a
key={f.uid || i}
style={{ cursor: "pointer" }}
onClick={() => window.open(url)}
>
{f.name || "查看文件"}
</a>
);
})}
</Flex>
}
trigger="hover"
placement="topLeft"
>
<a
onClick={(e) => e.preventDefault()}
style={{ cursor: "pointer" }}
>
{fileList.length}个附件
</a>
</Popover>
</Flex>
);
};
const archiveColumns = [
{
title: "序号",
width: 60,
render: (_, __, index) => index + 1,
},
{
title: "文件名称",
dataIndex: "files",
ellipsis: true,
render: (v) => {
if(Array.isArray(v)){
return v.map(v=>v.name).join(',')
}
},
},
{
title: "档案类型",
dataIndex: "archiveType",
width: 180,
},
{
title: "来源/业务节点",
dataIndex: "recordSource",
width: 150,
render: (v, r) => (
<Flex vertical gap={4}>
<span
className={`pf-archive__source-tag${v === "upload_additional_files" ? " pf-archive__source-tag--upload" : ""
}`}
>
{ARCHIVE_RECORD_SOURCE_BADGE_MAP[v] || v || "-"}
</span>
<span className="pf-archive__node">{NODE_LABELS[r.businessNode] || "-"}</span>
</Flex>
),
},
{
title: "形成/上传人员",
dataIndex: "createName",
width: 120,
render: (v) => v || "-",
},
{
title: "形成时间",
dataIndex: "createTime",
width: 200,
render: (v) => v || "-",
},
{
title: "操作",
width: 110,
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>
),
},
];
return (
<Card
title="项目归档"
style={{ marginBottom: 16 }}
styles={{ body: { paddingBottom: 0 } }}
<div
>
<div className="pf-permission">
<Tag color="blue" className="pf-tag">
项目归档
</Tag>
<span className="pf-desc">
项目过程文件清单确认签字完成后归档入库
</span>
</div>
<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={sourceFilter}
onChange={setSourceFilter}
options={[
{ label: "全部来源", value: "" },
...ARCHIVE_RECORD_SOURCE_OPTIONS,
]}
className="pf-archive__source-select"
/>
</Flex>
<Table
dataSource={files}
columns={fileColumns}
rowKey={(record) =>
`${record.processName}-${record.materialName}-${record.itemName}`
}
pagination={false}
rowKey="id"
size="small"
dataSource={filteredArchiveFiles}
columns={archiveColumns}
loading={props.safetyEvalBusiness?.evalProjectArchiveFileOrgLoading}
pagination={false}
/>
<Form form={archiveForm} layout="vertical" disabled={readOnly} scrollToFirstError>
@ -197,18 +284,50 @@ const ArchiveContent = ({ readOnly, ...props }) => {
{!readOnly && (
<LayoutFooterPortal slotId="pf-footer-slot">
<Flex justify="flex-end" gap={8}>
<Button disabled={archiveFlag} onClick={() => setSupplementOpen(true)}>
补充上传
</Button>
<Button
type="primary"
onClick={handleArchive}
loading={props.safetyEvalBusiness?.processControlArchiveLoading}
disabled={!canArchive || archiveFlag}
>
{archiveFlag ? '已归档' : '双方签字并确认入库'}
{archiveFlag ? '已归档' : '确认归档入库'}
</Button>
</Flex>
</LayoutFooterPortal>
)}
</Card>
<ArchiveSupplementModal
open={supplementOpen}
projectId={projectId}
loading={props.safetyEvalBusiness?.evalProjectArchiveFileAddLoading}
onAdd={props.evalProjectArchiveFileAdd}
onClose={() => setSupplementOpen(false)}
onSuccess={() => {
setSupplementOpen(false);
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>
);
};

View File

@ -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.files || [];
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,
files: 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="files"
label="资料上传"
rules={[{ required: true, message: "请选择需要上传的文件" }]}
extra="支持上传多个文件"
/>
</Form>
</Modal>
);
}
export default ArchiveSupplementModal;

View File

@ -173,7 +173,6 @@ const ProjectFlow = (props) => {
};
const openReportEditor = async () => {
// 2026-08-18 前置拦截:报告文档 fileId 缺省(文档尚未生成/加载完成)时提示,不打开编辑器,避免 SDK 报错
if (!wpsDoc?.fileId) {
message.warning("报告文档尚未生成,请先创建报告后再查看");
return;
@ -272,7 +271,7 @@ const ProjectFlow = (props) => {
destroyOnHidden
/>
{visible && (
<WpsEditor onCancel={() => setVisible(false)} fileId={wpsDoc?.fileId} readOnly={readOnly || evalProjectDetailData?.archiveFlag === 1} />
<WpsEditor onCancel={() => setVisible(false)} fileId={wpsDoc?.fileId} fileName={wpsDoc?.name} readOnly={readOnly || evalProjectDetailData?.archiveFlag === 1} />
)}
</PageLayout>
);

View File

@ -291,3 +291,38 @@
display: none;
}
}
// 档案内容 - 档案文件列表
.pf-archive {
&__toolbar {
margin-bottom: 12px;
}
&__keyword-input {
width: 260px;
}
&__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;
}
}

View File

@ -0,0 +1,227 @@
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;

View File

@ -10,6 +10,7 @@ import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { EVAL_TYPE_OPTIONS, EVAL_TYPE_MAP } from "~/enumerate/constant";
import { tools } from "@cqsjjb/jjb-common-lib";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import ArchiveModal from "./ArchiveModal";
import "./index.less";
@ -27,6 +28,7 @@ function EvalProjectDatabase(props) {
const [searchForm] = Form.useForm();
const [dataSource, setDataSource] = useState([]);
const [total, setTotal] = useState(0);
const [archiveRecord, setArchiveRecord] = useState(null);
const {
safetyEvalBusiness,
@ -113,14 +115,16 @@ function EvalProjectDatabase(props) {
{
title: "最近更新",
dataIndex: "updateTime",
width: 180,
},
{
title: "操作",
dataIndex: "operate",
fix: 'right',
fixed: "right",
width: 200,
render: (_, record) => {
return <TableAction>
<a onClick={() => setArchiveRecord(record)}>查看档案</a>
<a onClick={() => {
props.history.push(`/container/MonitorManage/EvalProjectMonitor/Detail?id=${record.id}`)
}}>项目过程</a>
@ -189,6 +193,13 @@ function EvalProjectDatabase(props) {
}}
onChange={handlePageChange}
/>
{archiveRecord&&<ArchiveModal
record={archiveRecord}
loading={safetyEvalBusiness?.evalProjectArchiveFileLoading}
fetchFiles={props.evalProjectArchiveFilePage}
onClose={() => setArchiveRecord(null)}
/>}
</PageLayout>
);
}

View File

@ -3,9 +3,59 @@
margin: 12px 0 16px;
padding: 10px 16px;
color: rgba(0, 0, 0, 65%);
font-size: 13px;
font-size: 14px;
line-height: 1.6;
background: #fffbe6;
border: 1px solid #ffe58f;
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;
}
}

View File

@ -24,11 +24,16 @@ import ErrorBoundary from "./ErrorBoundary";
const { router } = tools;
const App = (props) => {
const { onCancel, safetyEvalBusiness, readOnly } = props;
const { onCancel, safetyEvalBusiness, readOnly, fileName } = props;
const { createDocLoading, evalReportCompletePreparationLoading } =
safetyEvalBusiness;
const [expandedMap, setExpandedMap] = useState({});
const [fileId, setFileId] = useState(props.fileId);
// 当前文档类型word / pdf决定 SDK 初始化时使用的 officeType
// 初始类型通过初始报告文件名fileName推断
const [fileType, setFileType] = useState(() =>
/\.pdf$/i.test(props.fileName || "") ? "pdf" : "word"
);
const [templateMap, setTemplateMap] = useState([]);
const [uploading, setUploading] = useState(false);
const sdkRef = useRef(null);
@ -73,8 +78,14 @@ const App = (props) => {
if (!newFileId) {
throw new Error(res?.errMessage || res?.message || "上传失败");
}
setFileType(/\.pdf$/i.test(file.name) ? "pdf" : "word");
setFileId(newFileId);
message.success("Word 文件上传成功,已在编辑器中打开");
props.queryMyOrCreateDoc({
docxUrl: "",
projectId:router.query.id,
});
message.success("文件上传成功,已在编辑器中打开");
})
.catch((e) => {
message.error(e?.message || "上传失败,请重试");
@ -103,7 +114,7 @@ const App = (props) => {
const blobUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = blobUrl;
a.download = `安全评价报告_${fileId}.docx`;
a.download = `安全评价报告_${fileId}.${fileType === "pdf" ? "pdf" : "docx"}`;
a.click();
URL.revokeObjectURL(blobUrl);
message.success("下载成功");
@ -147,7 +158,10 @@ const App = (props) => {
try {
const instance = WebOfficeSDK.init({
// 必填项
officeType: WebOfficeSDK.OfficeType.Writer,
officeType:
fileType === "pdf"
? WebOfficeSDK.OfficeType.Pdf
: WebOfficeSDK.OfficeType.Writer,
appId: "AK20260805KPWYZJ",
mount: document.getElementById("app-wps"),
fileId: fileId,
@ -173,6 +187,7 @@ const App = (props) => {
}
})();
return () => {
console.log(sdkRef.current)
sdkRef.current?.destroy();
window.removeEventListener("error", onWindowError, { capture: true });
window.removeEventListener("unhandledrejection", onWindowError, { capture: true });
@ -349,15 +364,19 @@ const App = (props) => {
size="small"
onClick={() => {
props
.createWpsDoc({ data:{
.createWpsDoc({
data: {
docxUrl: url, projectId: router.query.id
}})
}
})
.then((res) => {
const fileId = res?.data?.fileId;
if (!fileId) {
return;
}
setFileId(fileId);
// 模板为 docx 文档,重置为 Word 类型
setFileType("word");
});
}}
>