safety-eval-service-frontend/src/pages/Container/WpsEditor/index.js

382 lines
15 KiB
JavaScript
Raw Normal View History

2026-07-20 15:03:26 +08:00
import { useState, useEffect, useRef } from "react";
2026-07-20 11:16:32 +08:00
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
2026-07-20 15:03:26 +08:00
import {
Flex,
Button,
Tabs,
Input,
Tag,
Progress,
Typography,
message,
} from "antd";
2026-07-20 13:38:09 +08:00
import { ArrowLeftOutlined } from "@ant-design/icons";
2026-07-20 11:16:32 +08:00
import WebOfficeSDK from "~/web-office-sdk-solution-v2.0.7/web-office-sdk-solution-v2.0.7.es.js";
import { createPortal } from "react-dom";
2026-07-20 18:31:32 +08:00
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
2026-07-20 11:16:32 +08:00
import "./index.less";
2026-07-20 13:38:09 +08:00
const App = (props) => {
2026-07-20 18:31:32 +08:00
const { onCancel, safetyEvalBusiness } = props;
2026-07-21 13:33:42 +08:00
const { createDocLoading } = safetyEvalBusiness;
2026-07-20 11:16:32 +08:00
const [expandedMap, setExpandedMap] = useState({});
2026-07-20 18:31:32 +08:00
const [fileId, setFileId] = useState(props.fileId);
const [templateMap, setTemplateMap] = useState([]);
2026-07-20 15:03:26 +08:00
const sdkRef = useRef(null);
2026-07-20 18:31:32 +08:00
useEffect(() => {
props.fetchWpsDocTemplate().then((res) => {
if (res?.data) {
const list = Object.entries(res.data).map(([name, url]) => ({
name,
url,
}));
setTemplateMap(list);
}
});
}, []);
2026-07-20 15:03:26 +08:00
const handleSave = async () => {
if (!sdkRef.current) {
message.warning("文档尚未加载完成");
return;
}
await sdkRef.current.save();
message.success("保存成功");
};
2026-07-20 11:16:32 +08:00
2026-07-24 17:05:44 +08:00
const handleDownload = async () => {
if (!sdkRef.current) {
message.warning("文档尚未加载完成");
return;
}
try {
// 先保存最新内容
await sdkRef.current.save();
// 调用后端下载接口获取下载地址
const res = await props.downloadWpsDoc({ fileId });
const url = res?.data?.url || res?.url;
if (!url) throw new Error("下载地址获取失败");
// 跨域场景下 a.download 无效,通过 fetch 转 blob 再下载
const blobRes = await fetch(url);
if (!blobRes.ok) throw new Error("文件下载失败");
const blob = await blobRes.blob();
const blobUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = blobUrl;
a.download = `安全评价报告_${fileId}.docx`;
a.click();
URL.revokeObjectURL(blobUrl);
message.success("下载成功");
} catch {
message.error("下载失败,请重试");
}
};
2026-07-20 11:16:32 +08:00
const toggleExpand = (index) => {
setExpandedMap((prev) => ({ ...prev, [index]: !prev[index] }));
};
useEffect(() => {
(async function () {
const instance = WebOfficeSDK.init({
// 必填项
officeType: WebOfficeSDK.OfficeType.Writer,
2026-07-24 16:13:41 +08:00
appId: "SX20260724JJYTBX",
2026-07-20 11:16:32 +08:00
mount: document.getElementById("app-sbsb"),
2026-07-20 18:31:32 +08:00
fileId: fileId,
2026-07-20 11:16:32 +08:00
token: sessionStorage.getItem("token"),
2026-07-21 13:33:42 +08:00
2026-07-20 11:16:32 +08:00
});
await instance.ready();
2026-07-20 15:03:26 +08:00
sdkRef.current = instance;
2026-07-20 11:16:32 +08:00
})();
2026-07-20 18:31:32 +08:00
}, [fileId]);
2026-07-20 11:16:32 +08:00
return (
<div className="WpsEditor-container">
<PageLayout
2026-07-20 13:38:09 +08:00
title={
<Flex align="center" gap={8}>
2026-07-20 15:03:26 +08:00
<ArrowLeftOutlined
onClick={() => {
console.log("点击了返回按钮");
onCancel();
}}
/>
2026-07-20 13:38:09 +08:00
<span>安全评价报告编制</span>
</Flex>
}
2026-07-20 11:16:32 +08:00
extra={
<Flex gap={12}>
2026-07-20 15:03:26 +08:00
<Button onClick={handleSave}>保存</Button>
2026-07-24 17:05:44 +08:00
<Button onClick={handleDownload}>下载报告</Button>
2026-07-24 16:13:41 +08:00
2026-07-20 11:16:32 +08:00
</Flex>
}
>
<div className="WpsEditor-flexContainer">
<div id="app-sbsb" className="WpsEditor-editorArea"></div>
<div className="WpsEditor-sidePanel">
<Tabs
type="card"
tabPlacement="top"
items={[
{
label: "知识库",
key: "1",
children: (
<div className="WpsEditor-dock">
<Input.Search placeholder="搜索法规、案例、措施" />
2026-07-20 15:03:26 +08:00
<Flex gap={4} className="WpsEditor-dock-cats">
{[
"推荐",
"法规标准",
"危险因素",
"评价方法",
"事故案例",
].map((cat) => (
<Button
key={cat}
size="small"
type={cat === "推荐" ? "primary" : "default"}
>
{cat}
</Button>
2026-07-20 11:16:32 +08:00
))}
</Flex>
<div className="WpsEditor-dock-list">
{[
2026-07-20 15:03:26 +08:00
{
cat: "法规标准",
title: "中华人民共和国安全生产法",
desc: "2021年修正当前有效。可引用至评价依据章节发多少个给梵蒂换个房间官方电话冈的是非观大使馆反对给多少给多少规范化的。",
},
{
cat: "危险因素",
title: "液氨泄漏事故场景",
desc: "包含泄漏扩散、中毒窒息、次生火灾及应急处置要点。",
},
{
cat: "评价方法",
title: "安全检查表法",
desc: "适用于法规符合性、设施状态和安全管理制度逐项检查。",
},
{
cat: "评价方法",
title: "安全检查表法",
desc: "适用于法规符合性、设施状态和安全管理制度逐项检查。",
},
{
cat: "评价方法",
title: "安全检查表法",
desc: "适用于法规符合性、设施状态和安全管理制度逐项检查。",
},
{
cat: "评价方法",
title: "安全检查表法",
desc: "适用于法规符合性、设施状态和安全管理制度逐项检查。",
},
{
cat: "评价方法",
title: "安全检查表法",
desc: "适用于法规符合性、设施状态和安全管理制度逐项检查。",
},
2026-07-20 11:16:32 +08:00
].map((item, i) => {
const expanded = expandedMap[i];
return (
2026-07-20 15:03:26 +08:00
<div key={i} className="WpsEditor-resourceCard">
2026-07-20 11:16:32 +08:00
<Tag color="blue">{item.cat}</Tag>
2026-07-20 15:03:26 +08:00
<Typography.Text
strong
className="WpsEditor-resourceTitle"
>
{item.title}
</Typography.Text>
2026-07-20 11:16:32 +08:00
<Typography.Paragraph
type="secondary"
className="WpsEditor-resourceDesc"
ellipsis={expanded ? false : { rows: 2 }}
>
{item.desc}
</Typography.Paragraph>
<Button
type="link"
size="small"
onClick={() => toggleExpand(i)}
>
{expanded ? "收起" : "展开"}
</Button>
</div>
);
})}
</div>
</div>
),
},
{
label: "模板",
key: "2",
children: (
<div className="WpsEditor-dock">
2026-07-20 15:03:26 +08:00
<Flex
justify="space-between"
align="center"
className="WpsEditor-dock-heading"
>
2026-07-20 11:16:32 +08:00
<Typography.Text strong>机构报告模板</Typography.Text>
2026-07-20 15:03:26 +08:00
<Button type="link" size="small">
管理
</Button>
2026-07-20 11:16:32 +08:00
</Flex>
<div className="WpsEditor-dock-list">
2026-07-20 18:31:32 +08:00
{templateMap.map(({ name, url }, i) => (
<div key={i} className="WpsEditor-templateCard">
2026-07-20 11:16:32 +08:00
<div>
2026-07-20 18:31:32 +08:00
<Typography.Text strong>{name}</Typography.Text>
{/* <Typography.Paragraph
2026-07-20 15:03:26 +08:00
type="secondary"
style={{ margin: 0, fontSize: 12 }}
>
2026-07-20 18:31:32 +08:00
{url}
</Typography.Paragraph> */}
2026-07-20 11:16:32 +08:00
</div>
2026-07-20 18:31:32 +08:00
<Button
loading={createDocLoading}
size="small"
onClick={() => {
2026-07-21 13:33:42 +08:00
props
.createWpsDoc({ docxUrl: url })
.then((res) => {
const fileId = res?.data?.fileId;
if (!fileId) {
return;
}
setFileId(fileId);
});
2026-07-20 18:31:32 +08:00
}}
>
应用
</Button>
2026-07-20 11:16:32 +08:00
</div>
))}
</div>
<div className="WpsEditor-templateNote">
<Typography.Text strong>模板包含</Typography.Text>
2026-07-20 15:03:26 +08:00
<Typography.Paragraph
type="secondary"
style={{ margin: 0, fontSize: 12 }}
>
封面责任页目录章节结构页眉页脚表格样式附件与附图目录
</Typography.Paragraph>
2026-07-20 11:16:32 +08:00
</div>
</div>
),
},
{
label: "签字",
key: "3",
children: (
<div className="WpsEditor-dock">
2026-07-20 15:03:26 +08:00
<Flex
justify="space-between"
align="center"
className="WpsEditor-dock-heading"
>
2026-07-20 11:16:32 +08:00
<Typography.Text strong>责任页签字</Typography.Text>
2026-07-20 15:03:26 +08:00
<span className="WpsEditor-signProgress">
1 / 5 已完成
</span>
2026-07-20 11:16:32 +08:00
</Flex>
<Progress percent={20} size="small" />
<div className="WpsEditor-dock-list">
{[
2026-07-20 15:03:26 +08:00
{
initial: "张",
name: "张建国",
role: "项目负责人",
status: "signed",
},
{
initial: "李",
name: "李明华",
role: "报告编制人",
status: "pending",
},
{
initial: "王",
name: "王丽萍",
role: "内部审核人",
status: "locked",
},
{
initial: "陈",
name: "陈志强",
role: "技术负责人",
status: "locked",
},
{
initial: "王",
name: "王丽萍",
role: "过程控制负责人",
status: "locked",
},
2026-07-20 11:16:32 +08:00
].map((signer, i) => (
2026-07-20 15:03:26 +08:00
<div
key={i}
className={`WpsEditor-signer ${signer.status}`}
>
<div className="WpsEditor-signerAvatar">
{signer.initial}
</div>
2026-07-20 11:16:32 +08:00
<div className="WpsEditor-signerInfo">
2026-07-20 15:03:26 +08:00
<Typography.Text strong>
{signer.name}
</Typography.Text>
<Typography.Paragraph
type="secondary"
style={{ margin: 0, fontSize: 12 }}
>
{signer.role}
</Typography.Paragraph>
2026-07-20 11:16:32 +08:00
</div>
2026-07-20 15:03:26 +08:00
{signer.status === "signed" && (
<Tag color="success">已签字</Tag>
)}
{signer.status === "pending" && (
<Button size="small" type="primary" ghost>
下发APP签字
</Button>
)}
2026-07-20 11:16:32 +08:00
{signer.status === "locked" && <Tag>待内审</Tag>}
</div>
))}
</div>
<div className="WpsEditor-signRule">
2026-07-20 15:03:26 +08:00
<Typography.Paragraph
type="secondary"
style={{ margin: 0, fontSize: 12 }}
>
签字由 PC 端下发至本人
APP手写签署后自动回传各版本签字独立留痕
2026-07-20 11:16:32 +08:00
</Typography.Paragraph>
</div>
</div>
),
},
]}
/>
</div>
</div>
</PageLayout>
</div>
);
};
2026-07-20 13:38:09 +08:00
export default function WpsEditor(props) {
2026-07-20 18:31:32 +08:00
return createPortal(<ConnectApp {...props} />, document.body);
2026-07-20 11:16:32 +08:00
}
2026-07-20 18:31:32 +08:00
const ConnectApp = Connect([NS_SAFETY_EVAL_BUSINESS], true)(App);