- Added keyframes and animation for a smooth scale-in effect in the Tailwind configuration. - Updated Image component to improve quality handling and style definitions. - Introduced WordViewer and WordViewerModal components for document viewing capabilities. - Enhanced Modal component to support embedded mode with customizable position and dimensions. - Integrated IframeModal across various pages for user assistance and content display. - Updated Content and Menu pages to utilize new components and improve user interaction. - Added utility function to retrieve public paths for resources.
216 lines
5.4 KiB
TypeScript
216 lines
5.4 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import mammoth from "mammoth";
|
|
|
|
interface WordViewerProps {
|
|
/** Word 文档的路径 */
|
|
docPath: string;
|
|
/** 容器的类名 */
|
|
className?: string;
|
|
}
|
|
|
|
export const WordViewer = ({ docPath, className = "" }: WordViewerProps) => {
|
|
const [htmlContent, setHtmlContent] = useState<string>("");
|
|
const [loading, setLoading] = useState<boolean>(true);
|
|
const [error, setError] = useState<string>("");
|
|
|
|
useEffect(() => {
|
|
const loadDocument = async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError("");
|
|
|
|
// 获取 Word 文档
|
|
const response = await fetch(docPath);
|
|
if (!response.ok) {
|
|
throw new Error("无法加载文档");
|
|
}
|
|
|
|
// 转换为 ArrayBuffer
|
|
const arrayBuffer = await response.arrayBuffer();
|
|
|
|
// 使用 mammoth 转换为 HTML
|
|
const result = await mammoth.convertToHtml({
|
|
arrayBuffer: arrayBuffer,
|
|
});
|
|
|
|
setHtmlContent(result.value);
|
|
|
|
// 如果有警告信息,可以在控制台输出
|
|
if (result.messages.length > 0) {
|
|
console.warn("文档转换警告:", result.messages);
|
|
}
|
|
} catch (err) {
|
|
console.error("加载文档失败:", err);
|
|
setError(err instanceof Error ? err.message : "加载文档失败");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
loadDocument();
|
|
}, [docPath]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className={`flex justify-center items-center ${className}`}>
|
|
<div className="text-[#82B2D6] text-2xl font-bold">加载中...</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className={`flex justify-center items-center ${className}`}>
|
|
<div className="text-red-500 text-2xl font-bold">加载失败: {error}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={`h-full flex flex-col p-4 ${className}`}>
|
|
{/* 头部工具栏 */}
|
|
{/* <div className="flex justify-between items-center mb-4">
|
|
<h3 className="text-[#82B2D6] text-2xl font-bold">工作室活动文稿</h3>
|
|
<a
|
|
href={docPath}
|
|
download="工作室活动文稿.docx"
|
|
className="px-6 py-2 bg-[#82B2D6] text-white rounded-lg hover:bg-[#6a9bc4] transition-colors text-sm font-medium"
|
|
>
|
|
下载文档
|
|
</a>
|
|
</div> */}
|
|
|
|
{/* 文档内容区域 custom-scrollbar*/}
|
|
<div className="flex-1 overflow-y-auto bg-white rounded-lg px-4 py-0 ">
|
|
<div
|
|
className="word-content"
|
|
dangerouslySetInnerHTML={{ __html: htmlContent }}
|
|
style={{
|
|
maxWidth: "100%",
|
|
color: "#333",
|
|
lineHeight: "1.8",
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{/* 内联样式 - 美化文档内容 */}
|
|
<style>{`
|
|
.word-content h1 {
|
|
font-size: 2em;
|
|
font-weight: bold;
|
|
margin: 1em 0 0.5em;
|
|
color: #1a1a1a;
|
|
border-bottom: 2px solid #82B2D6;
|
|
padding-bottom: 0.3em;
|
|
}
|
|
|
|
.word-content h2 {
|
|
font-size: 1.5em;
|
|
font-weight: bold;
|
|
margin: 0.8em 0 0.4em;
|
|
color: #2a2a2a;
|
|
}
|
|
|
|
.word-content h3 {
|
|
font-size: 1.3em;
|
|
font-weight: bold;
|
|
margin: 0.6em 0 0.3em;
|
|
color: #3a3a3a;
|
|
}
|
|
|
|
.word-content p {
|
|
margin: 0.8em 0;
|
|
text-align: justify;
|
|
text-indent: 2em;
|
|
}
|
|
|
|
/* 表格内的段落不需要缩进 */
|
|
.word-content table p {
|
|
text-indent: 0;
|
|
margin: 0.4em 0;
|
|
}
|
|
|
|
.word-content img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
display: block;
|
|
margin: 1.5em auto;
|
|
border-radius: 4px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.word-content ul,
|
|
.word-content ol {
|
|
margin: 0.8em 0;
|
|
padding-left: 2em;
|
|
}
|
|
|
|
.word-content li {
|
|
margin: 0.4em 0;
|
|
}
|
|
|
|
.word-content table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 1em 0;
|
|
table-layout: fixed;
|
|
}
|
|
|
|
.word-content table th,
|
|
.word-content table td {
|
|
border: 1px solid #ddd;
|
|
padding: 0.8em;
|
|
text-align: left;
|
|
vertical-align: top;
|
|
word-wrap: break-word;
|
|
word-break: break-word;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
.word-content table th {
|
|
background-color: #f5f5f5;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* 表格第一列(标题列)固定宽度 */
|
|
.word-content table tr td:first-child,
|
|
.word-content table tr th:first-child {
|
|
width: 200px;
|
|
min-width: 200px;
|
|
white-space: normal;
|
|
font-weight: 600;
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.word-content strong,
|
|
.word-content b {
|
|
font-weight: bold;
|
|
color: #1a1a1a;
|
|
}
|
|
|
|
.word-content em,
|
|
.word-content i {
|
|
font-style: italic;
|
|
}
|
|
|
|
.word-content a {
|
|
color: #82B2D6;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.word-content a:hover {
|
|
color: #6a9bc4;
|
|
}
|
|
|
|
.word-content blockquote {
|
|
border-left: 4px solid #82B2D6;
|
|
padding-left: 1em;
|
|
margin: 1em 0;
|
|
color: #666;
|
|
font-style: italic;
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
};
|