wps只读去掉工具栏

dev-tmp1
tangjie 2026-08-19 10:09:32 +08:00
parent e45c4d2404
commit fcf1a193f2
8 changed files with 1008 additions and 1303 deletions

View File

@ -12,10 +12,10 @@ module.exports = {
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095 // 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
//API_HOST: "http://localhost:80", //API_HOST: "http://localhost:80",
// API_HOST: "http://192.168.0.134", // API_HOST: "http://192.168.0.134",
// API_HOST: "http://192.168.0.150", //太浅 // API_HOST: "http://192.168.0.150", //太浅
// API_HOST: "https://gbs-gateway.qhdsafety.com", API_HOST: "https://gbs-gateway.qhdsafety.com",
API_HOST: "http://192.168.0.103", //huwei // API_HOST: "http://192.168.0.103", //huwei
}, },
production: { production: {
// 应用后端分支名称,部署上线需要 // 应用后端分支名称,部署上线需要

View File

@ -29,7 +29,6 @@
"@rc-component/util": "^1.11.1", "@rc-component/util": "^1.11.1",
"antd": "^6.6.1", "antd": "^6.6.1",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"docx-preview": "^0.4.0",
"docxtemplater": "latest", "docxtemplater": "latest",
"echarts": "^6.1.0", "echarts": "^6.1.0",
"history": "^4.10.1", "history": "^4.10.1",

File diff suppressed because it is too large Load Diff

View File

@ -1,193 +0,0 @@
import { useEffect, useRef, useState } from "react";
import { Modal, Empty, Alert } from "antd";
import { renderAsync } from "docx-preview";
import "./index.less";
const FILE_TYPE_MAP = {
docx: "word",
pdf: "pdf",
};
/** 根据 url/类型识别文件类型,仅支持 docx/pdf */
function resolveFileType(url, type) {
if (type && FILE_TYPE_MAP[type]) return type;
const ext = (url || "").split("?")[0].split(".").pop()?.toLowerCase();
return FILE_TYPE_MAP[ext] ? ext : "";
}
/** 相对地址拼接文件服务前缀 */
function resolveUrl(raw) {
if (!raw) return "";
const u = String(raw).trim();
if (/^https?:\/\//i.test(u)) return u;
const base = window.fileUrl || "";
return base ? `${base}${u}` : u;
}
/** 渲染 Word 文档到容器 */
async function renderDocx(url, container) {
if (!container) throw new Error("预览容器尚未就绪,请稍后重试");
const res = await fetch(url, { credentials: "same-origin" });
if (!res.ok) throw new Error(`文档加载失败:${res.status} ${res.statusText}`);
const blob = await res.blob();
await renderAsync(blob, container, container, {
className: "office-preview-docx",
inWrapper: true,
ignoreWidth: false,
ignoreHeight: false,
ignoreFonts: false,
breakPages: true,
renderHeaders: true,
renderFooters: true,
renderFootnotes: true,
renderEndnotes: true,
});
}
/** 文档预览组件:支持 Word(docx)、PDF支持弹窗/页面两种模式 */
export default function OfficePreview({
url,
fileType = "auto",
mode = "modal",
open = false,
onCancel,
title = "文档预览",
width = 1000,
className = "",
style = {},
emptyText = "暂无文档",
}) {
const containerRef = useRef(null);
const aliveRef = useRef(true);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [ready, setReady] = useState(false);
const resolvedUrl = resolveUrl(url);
const type = resolveFileType(resolvedUrl, fileType);
useEffect(() => {
aliveRef.current = true;
return () => {
aliveRef.current = false;
};
}, []);
useEffect(() => {
if (mode === "modal" && !open) {
setReady(false);
return;
}
if (!resolvedUrl || type !== "docx") return;
setReady(false);
setError("");
let rafId = null;
let cancelled = false;
const tryRender = () => {
if (cancelled) return;
const node = containerRef.current;
if (!node) {
rafId = requestAnimationFrame(tryRender);
return;
}
setLoading(true);
renderDocx(resolvedUrl, node)
.then(() => {
if (aliveRef.current && !cancelled) {
setReady(true);
}
})
.catch((e) => {
console.error("OfficePreview 文档渲染失败", e);
if (aliveRef.current && !cancelled) {
setError(e?.message || "文档渲染失败,请稍后重试");
}
})
.finally(() => {
if (aliveRef.current && !cancelled) {
setLoading(false);
}
});
};
tryRender();
return () => {
cancelled = true;
if (rafId !== null) cancelAnimationFrame(rafId);
const node = containerRef.current;
if (node) node.innerHTML = "";
};
}, [resolvedUrl, type, open, mode]);
const renderContent = () => {
if (!resolvedUrl) {
return <Empty description={emptyText} image={Empty.PRESENTED_IMAGE_SIMPLE} />;
}
if (!type) {
return (
<Alert
type="warning"
showIcon
message="不支持的文件类型"
description="当前仅支持 Word(.docx) 和 PDF(.pdf) 格式预览。"
/>
);
}
if (error) {
return <Alert type="error" showIcon message={error} />;
}
return (
<div className={`office-preview-body office-preview-${type}`} style={style}>
{type === "docx" && (
<div
ref={containerRef}
className="office-preview-docxContainer"
style={{ display: ready ? "block" : "none" }}
/>
)}
{type === "pdf" && (
<iframe
className="office-preview-pdfFrame"
src={resolvedUrl}
title={title}
frameBorder="0"
/>
)}
{loading && (
<div className="office-preview-loading">
<div className="office-preview-loading-text">文档加载中...</div>
</div>
)}
</div>
);
};
const body = (
<div className={`office-preview ${className}`}>{renderContent()}</div>
);
if (mode === "page") {
return body;
}
return (
<Modal
title={title}
open={open}
onCancel={onCancel}
width={width}
footer={null}
destroyOnHidden
centered={false}
className="office-preview-modal"
>
{body}
</Modal>
);
}

View File

@ -1,82 +0,0 @@
.office-preview {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.office-preview-body {
position: relative;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.office-preview-docxContainer {
/* 固定占弹窗 90% 视口高度,确保有足够可视区域同时保留滚动条 */
height: 90vh;
max-height: 100%;
overflow-y: auto;
overflow-x: auto;
background: #f5f5f5;
border: 1px solid #e8e8e8;
border-radius: 4px;
padding: 12px;
box-sizing: border-box;
}
.office-preview-pdfFrame {
height: 90vh;
max-height: 100%;
width: 100%;
border: 1px solid #e8e8e8;
border-radius: 4px;
background: #f5f5f5;
}
.office-preview-loading {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.6);
z-index: 10;
}
.office-preview-loading-text {
color: #1677ff;
font-size: 14px;
}
}
.office-preview-modal {
/* 弹窗顶部紧贴,让中间文档区域最大化 */
.micro-temp-modal {
top: 0;
padding-bottom: 0;
}
.micro-temp-modal-content {
/* 弹窗内容占满视口高度,由正文容器再以 90vh 显示 */
height: 75vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.micro-temp-modal-header {
flex-shrink: 0;
padding: 8px 16px;
}
.micro-temp-modal-body {
flex: 1;
/* 覆盖 src/pages/Container/index.less 中全局 .micro-temp-modal-body { max-height: 66vh } 的限制,
让文档预览区域能占满更大可视高度 */
max-height: none !important;
overflow: hidden;
padding: 4px 8px 8px 8px;
}
}

View File

@ -1,3 +1,2 @@
export { default as OrgInfoGuard } from "./OrgInfoGuard"; export { default as OrgInfoGuard } from "./OrgInfoGuard";
export { default as PreviewUrlButton } from "./PreviewUrlButton"; export { default as PreviewUrlButton } from "./PreviewUrlButton";
export { default as OfficePreview } from "./OfficePreview";

View File

@ -20,7 +20,7 @@ import { NS_SAFETY_EVAL_BUSINESS } from "~/enumerate/namespace";
import { tools } from "@cqsjjb/jjb-common-lib"; import { tools } from "@cqsjjb/jjb-common-lib";
import { EVAL_TYPE_MAP, ROLE_DEFINITIONS_MAP } from "~/enumerate/constant"; import { EVAL_TYPE_MAP, ROLE_DEFINITIONS_MAP } from "~/enumerate/constant";
import { PROJECT_TYPE_MAP } from "~/enumerate/constant"; import { PROJECT_TYPE_MAP } from "~/enumerate/constant";
import { OfficePreview } from "~/components"; import WpsEditor from "~/pages/Container/WpsEditor";
import "./index.less"; import "./index.less";
const { router } = tools; const { router } = tools;
@ -369,8 +369,7 @@ const EvalProjectMonitorDetail = (props) => {
const [surveyPersonnel, setSurveyPersonnel] = useState([]); const [surveyPersonnel, setSurveyPersonnel] = useState([]);
const [surveyAttachments, setSurveyAttachments] = useState({}); const [surveyAttachments, setSurveyAttachments] = useState({});
const [activeKey, setActiveKey] = useState("BUSINESS_CONTRACT"); const [activeKey, setActiveKey] = useState("BUSINESS_CONTRACT");
const [previewOpen, setPreviewOpen] = useState(false); const [wpsVisible, setWpsVisible] = useState(false);
const [previewUrl, setPreviewUrl] = useState("");
const fetchContract = async () => { const fetchContract = async () => {
if (!projectId) return; if (!projectId) return;
@ -472,14 +471,11 @@ const EvalProjectMonitorDetail = (props) => {
activeKey={activeKey} activeKey={activeKey}
onChange={(key) => { onChange={(key) => {
if (key === "REPORT_PREPARATION") { if (key === "REPORT_PREPARATION") {
// 2026-08-18 监管端报告改用本地 OfficePreview 预览,避免 WPS SDK 内部空指针报错 if (!wpsDoc?.fileId) {
const url = wpsDoc?.wpsUrl;
if (!url) {
message.warning("暂无报告文档,请先在机构端生成报告后再查看"); message.warning("暂无报告文档,请先在机构端生成报告后再查看");
return; return;
} }
setPreviewUrl(url); setWpsVisible(true);
setPreviewOpen(true);
return; return;
} }
setActiveKey(key); setActiveKey(key);
@ -516,13 +512,13 @@ const EvalProjectMonitorDetail = (props) => {
}, },
]} ]}
/> />
<OfficePreview {wpsVisible && (
mode="modal" <WpsEditor
open={previewOpen} onCancel={() => setWpsVisible(false)}
onCancel={() => setPreviewOpen(false)} fileId={wpsDoc?.fileId}
url={previewUrl} readOnly
width={1100} />
/> )}
</PageLayout> </PageLayout>
); );
}; };

View File

@ -122,7 +122,15 @@ const App = (props) => {
customArgs: { customArgs: {
readOnly, readOnly,
}, },
...(readOnly ? { commonOptions: { isBrowserViewMode: true } } : {}), ...(readOnly
? {
commonOptions: {
isBrowserViewMode: true,
// 只读模式下隐藏 WPS 顶部区域(头部和工具栏),页面已有自己的标题栏
isShowTopArea: false,
},
}
: {}),
}); });
await instance.ready(); await instance.ready();
sdkRef.current = instance; sdkRef.current = instance;