文件预览页面demo完成,访问路径为:/rules/new1
This commit is contained in:
+141
-191
@@ -1,40 +1,83 @@
|
||||
/**
|
||||
* 文档预览与内容抽取模块
|
||||
*
|
||||
* 依赖包说明:
|
||||
* 1. react-pdf - PDF文档预览
|
||||
* 安装命令: npm install react-pdf
|
||||
* 或: yarn add react-pdf
|
||||
*
|
||||
* 2. mammoth - Word文档转HTML预览
|
||||
* 安装命令: npm install mammoth
|
||||
* 或: yarn add mammoth
|
||||
*
|
||||
* 3. @remix-run/react, @remix-run/node - Remix框架组件
|
||||
* 安装命令: npm install @remix-run/react @remix-run/node
|
||||
* 或: yarn add @remix-run/react @remix-run/node
|
||||
*
|
||||
* 注意事项:
|
||||
* - react-pdf需要pdfjs-dist作为依赖,安装react-pdf时会自动安装
|
||||
* - 需要引入PDF.js worker文件,本代码通过CDN方式引入
|
||||
* - 如需本地加载PDF.js worker文件,请安装pdfjs-dist并修改worker配置
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useLoaderData } from "@remix-run/react";
|
||||
import { Document, Page, pdfjs } from "react-pdf";
|
||||
import type { LoaderFunctionArgs } from "@remix-run/node";
|
||||
import mammoth from "mammoth";
|
||||
|
||||
// 设置 pdfjs 工作线程
|
||||
/**
|
||||
* 设置 pdfjs 工作线程
|
||||
* 使用 CDN 上的 worker.js 文件处理 PDF 解析
|
||||
*/
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
|
||||
|
||||
// 模拟后端返回的抽取内容数据
|
||||
/**
|
||||
* 模拟后端返回的文档抽取内容数据
|
||||
* 实际应用中应从API获取
|
||||
*/
|
||||
const mockExtractedContent = [
|
||||
{ id: 1, text: "合同条款", page: 2, position: { start: 50, end: 60 } },
|
||||
{ id: 2, text: "签署日期", page: 5, position: { start: 120, end: 130 } },
|
||||
{ id: 3, text: "责任划分", page: 3, position: { start: 80, end: 90 } },
|
||||
];
|
||||
|
||||
/**
|
||||
* 文档抽取内容接口定义
|
||||
*/
|
||||
interface ExtractedContent {
|
||||
id: number;
|
||||
text: string;
|
||||
page: number;
|
||||
position: { start: number; end: number };
|
||||
id: number; // 内容唯一标识
|
||||
text: string; // 抽取的文本内容
|
||||
page: number; // 所在页码
|
||||
position: { // 在页面中的位置信息
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Loader 函数返回数据接口定义
|
||||
*/
|
||||
interface LoaderData {
|
||||
fileUrl: string;
|
||||
initialPage: number;
|
||||
extractedContent: ExtractedContent[];
|
||||
fileType: "pdf" | "docx";
|
||||
urls: Record<string, string>;
|
||||
fileUrl: string; // 当前文档URL
|
||||
initialPage: number; // 初始页码
|
||||
extractedContent: ExtractedContent[]; // 抽取内容数组
|
||||
fileType: "pdf" | "docx"; // 文档类型
|
||||
urls: Record<string, string>; // 可用文档URL列表
|
||||
}
|
||||
|
||||
// 定义文档加载成功回调类型
|
||||
/**
|
||||
* PDF文档加载成功回调接口
|
||||
*/
|
||||
interface DocumentLoadSuccess {
|
||||
numPages: number;
|
||||
numPages: number; // 文档总页数
|
||||
}
|
||||
|
||||
// 根据URL判断文件类型
|
||||
/**
|
||||
* 根据URL判断文件类型
|
||||
* @param url 文档URL
|
||||
* @returns 文档类型:"pdf" 或 "docx"
|
||||
*/
|
||||
function getFileTypeFromUrl(url: string): "pdf" | "docx" {
|
||||
const lowerCaseUrl = url.toLowerCase();
|
||||
if (lowerCaseUrl.endsWith(".pdf")) {
|
||||
@@ -46,15 +89,15 @@ function getFileTypeFromUrl(url: string): "pdf" | "docx" {
|
||||
return "pdf";
|
||||
}
|
||||
|
||||
// Remix Loader 函数
|
||||
/**
|
||||
* Remix Loader 函数 - 请求处理和数据加载
|
||||
*/
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
// 从URL获取查询参数
|
||||
const url = new URL(request.url);
|
||||
const page = url.searchParams.get("page") || 1;
|
||||
|
||||
// 实际文档 URL (PDF示例)
|
||||
// const fileUrl = "http://172.18.0.100:9000/docauditai/documents/%E5%90%88%E5%90%8C%E6%96%87%E6%A1%A3/2025/04%E6%9C%8816%E6%97%A5/%E7%AC%AC16%E5%8F%B7--%E9%94%80%E5%94%AE%E6%97%A0%E6%A0%87%E5%BF%97%E5%A4%96%E5%9B%BD%E5%8D%B7%E7%83%9F_10%E6%97%B626%E5%88%8632%E7%A7%92/%E7%AC%AC16%E5%8F%B7--%E9%94%80%E5%94%AE%E6%97%A0%E6%A0%87%E5%BF%97%E5%A4%96%E5%9B%BD%E5%8D%B7%E7%83%9F.pdf";
|
||||
|
||||
// 示例文档URLs
|
||||
// 示例文档URLs集合
|
||||
const urls = {
|
||||
// 1. 原始文档URL - 可能有CORS限制
|
||||
original: "https://dev-xc-enroll.oss-cn-guangzhou.aliyuncs.com/uploads/7840-230620112939.docx",
|
||||
@@ -64,127 +107,108 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
proxy: "https://dev-xc-enroll.oss-cn-guangzhou.aliyuncs.com/uploads/7840-230620112939.docx",
|
||||
// 4. 本地服务器上的文档 (假设已经部署)
|
||||
local: "/uploads/sample.docx",
|
||||
// 5. PDF示例 (如果Word文档问题无法解决)
|
||||
// 5. PDF示例
|
||||
pdf: "http://172.18.0.100:9000/docauditai/documents/%E5%90%88%E5%90%8C%E6%96%87%E6%A1%A3/2025/04%E6%9C%8816%E6%97%A5/%E7%AC%AC16%E5%8F%B7--%E9%94%80%E5%94%AE%E6%97%A0%E6%A0%87%E5%BF%97%E5%A4%96%E5%9B%BD%E5%8D%B7%E7%83%9F_10%E6%97%B626%E5%88%8632%E7%A7%92/%E7%AC%AC16%E5%8F%B7--%E9%94%80%E5%94%AE%E6%97%A0%E6%A0%87%E5%BF%97%E5%A4%96%E5%9B%BD%E5%8D%B7%E7%83%9F.pdf"
|
||||
};
|
||||
|
||||
// 使用本地文档或通过CORS代理的URL
|
||||
const fileUrl = urls.public; // 可以切换到其他URL进行测试
|
||||
// 使用默认文档URL
|
||||
const fileUrl = urls.pdf;
|
||||
|
||||
// 判断文件类型
|
||||
const fileType = getFileTypeFromUrl(fileUrl);
|
||||
|
||||
// 返回加载的数据
|
||||
return {
|
||||
fileUrl,
|
||||
initialPage: Number(page),
|
||||
extractedContent: mockExtractedContent,
|
||||
fileType,
|
||||
urls // 传递所有URL供前端选择
|
||||
urls
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 文档预览组件
|
||||
*/
|
||||
export default function Documents() {
|
||||
// 从loader获取数据
|
||||
const { fileUrl, extractedContent, fileType, urls } = useLoaderData<LoaderData>();
|
||||
const [numPages, setNumPages] = useState<number | null>(null);
|
||||
const [scrollToPage, setScrollToPage] = useState<number | null>(null);
|
||||
const [docxLoading, setDocxLoading] = useState(false); // 设置为false以避免加载指示器
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
const [debugInfo, setDebugInfo] = useState<string[]>([]);
|
||||
const docxContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [docxContentPositions, setDocxContentPositions] = useState<{[id: number]: number}>({});
|
||||
const [currentUrl, setCurrentUrl] = useState<string>(fileUrl);
|
||||
// 默认使用iframe模式
|
||||
const [showIframe, setShowIframe] = useState<boolean>(true);
|
||||
const [docxHtml, setDocxHtml] = useState<string>("");
|
||||
|
||||
// 状态管理
|
||||
const [numPages, setNumPages] = useState<number | null>(null); // PDF总页数
|
||||
const [scrollToPage, setScrollToPage] = useState<number | null>(null); // 滚动目标页码
|
||||
const [docxLoading, setDocxLoading] = useState(false); // Word文档加载状态
|
||||
const [loadError, setLoadError] = useState<string | null>(null); // 加载错误信息
|
||||
const [debugInfo, setDebugInfo] = useState<string[]>([]); // 调试信息
|
||||
const [docxHtml, setDocxHtml] = useState<string>(""); // 转换后的HTML内容
|
||||
const [currentUrl, setCurrentUrl] = useState<string>(fileUrl); // 当前文档URL
|
||||
|
||||
// 引用
|
||||
const docxContainerRef = useRef<HTMLDivElement>(null); // Word文档容器引用
|
||||
|
||||
// 处理抽取内容点击
|
||||
/**
|
||||
* 处理抽取内容点击事件 - 仅对PDF文档生效
|
||||
* @param item 被点击的抽取内容项
|
||||
*/
|
||||
const handleContentClick = (item: ExtractedContent) => {
|
||||
setScrollToPage(item.page);
|
||||
// 仅对PDF文档执行交互操作
|
||||
if (fileType === "pdf") {
|
||||
// 使用ID滚动到指定页面
|
||||
setScrollToPage(item.page);
|
||||
// 对于PDF,滚动到指定页面
|
||||
const pageElement = document.getElementById(`page-${item.page}`);
|
||||
if (pageElement) {
|
||||
pageElement.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
} else if (fileType === "docx" && !showIframe) {
|
||||
// 对于Word文档,滚动到提取内容位置 (仅本地渲染模式)
|
||||
const position = docxContentPositions[item.id];
|
||||
if (position !== undefined && docxContainerRef.current) {
|
||||
// 找到Word内容容器内的位置并滚动
|
||||
docxContainerRef.current.scrollTop = position;
|
||||
|
||||
// 高亮显示这个区域(模拟)
|
||||
highlightDocxContent(item);
|
||||
}
|
||||
} else if (fileType === "docx" && showIframe) {
|
||||
// 对于iframe中的Word文档,我们只能切换到特定iframe页面
|
||||
// 这里我们无法控制iframe内部的滚动,只能提示用户
|
||||
addDebugInfo(`在iframe中无法直接定位到"${item.text}",请在文档中手动查找`);
|
||||
}
|
||||
// DOCX文档不执行任何交互操作
|
||||
};
|
||||
|
||||
// 模拟在Word文档中高亮内容
|
||||
const highlightDocxContent = (item: ExtractedContent) => {
|
||||
// 移除之前的高亮
|
||||
const previousHighlights = document.querySelectorAll('.docx-highlight');
|
||||
previousHighlights.forEach(el => el.classList.remove('docx-highlight'));
|
||||
|
||||
// 由于我们没有确切的位置信息,这里使用一个模拟的方法
|
||||
// 实际项目中,您需要一个更精确的方法来找到文本位置
|
||||
if (docxContainerRef.current) {
|
||||
const textNodes = Array.from(docxContainerRef.current.querySelectorAll('p, span, div'))
|
||||
.filter(node => node.textContent?.includes(item.text));
|
||||
|
||||
textNodes.forEach(node => {
|
||||
node.classList.add('docx-highlight');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// PDF文档加载成功回调
|
||||
/**
|
||||
* PDF文档加载成功回调函数
|
||||
* @param param0 包含numPages的对象
|
||||
*/
|
||||
function onDocumentLoadSuccess({ numPages }: DocumentLoadSuccess) {
|
||||
setNumPages(numPages);
|
||||
console.log("PDF加载成功,页数:", numPages);
|
||||
}
|
||||
|
||||
// 简化的调试日志
|
||||
/**
|
||||
* 添加调试信息
|
||||
* @param info 调试信息文本
|
||||
*/
|
||||
const addDebugInfo = (info: string) => {
|
||||
console.log(info);
|
||||
setDebugInfo(prev => [...prev, `${new Date().toISOString().split('T')[1].split('.')[0]}: ${info}`]);
|
||||
};
|
||||
|
||||
// 切换到不同的文档URL
|
||||
/**
|
||||
* 切换文档URL
|
||||
* @param urlKey URL键名
|
||||
*/
|
||||
const switchDocumentUrl = (urlKey: keyof typeof urls) => {
|
||||
setCurrentUrl(urls[urlKey]);
|
||||
setDebugInfo([]);
|
||||
setLoadError(null);
|
||||
setDocxLoading(false);
|
||||
setShowIframe(true);
|
||||
addDebugInfo(`切换到新的文档URL: ${urls[urlKey]}`);
|
||||
};
|
||||
|
||||
// 切换到iframe模式 (当直接加载文档有CORS问题时)
|
||||
const switchToIframeMode = () => {
|
||||
setShowIframe(true);
|
||||
setDocxLoading(false);
|
||||
addDebugInfo("切换到iframe嵌入模式");
|
||||
};
|
||||
|
||||
// 使用mammoth处理Word文档
|
||||
/**
|
||||
* Word文档处理逻辑
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (fileType === "docx" && docxContainerRef.current && !showIframe) {
|
||||
if (fileType === "docx" && docxContainerRef.current) {
|
||||
setDocxLoading(true);
|
||||
setDebugInfo([]); // 清空之前的调试信息
|
||||
setDebugInfo([]); // 清空调试信息
|
||||
addDebugInfo(`准备加载Word文档: ${currentUrl}`);
|
||||
|
||||
const loadDocx = async () => {
|
||||
try {
|
||||
// 获取文件
|
||||
// 1. 获取文档文件
|
||||
addDebugInfo(`开始获取文件...`);
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(currentUrl, {
|
||||
// 添加CORS相关选项
|
||||
mode: 'cors',
|
||||
credentials: 'omit',
|
||||
headers: {
|
||||
@@ -197,12 +221,13 @@ export default function Documents() {
|
||||
throw new Error(`网络请求失败: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`);
|
||||
}
|
||||
|
||||
// 检查响应状态
|
||||
if (!response.ok) {
|
||||
throw new Error(`文档无法访问,状态码: ${response.status}`);
|
||||
}
|
||||
addDebugInfo(`文档下载成功,状态码: ${response.status}`);
|
||||
|
||||
// 转换为ArrayBuffer
|
||||
// 2. 将响应转换为ArrayBuffer
|
||||
addDebugInfo(`开始读取响应内容为ArrayBuffer...`);
|
||||
let buffer;
|
||||
try {
|
||||
@@ -213,10 +238,10 @@ export default function Documents() {
|
||||
throw new Error(`转换文档内容失败: ${bufferError instanceof Error ? bufferError.message : String(bufferError)}`);
|
||||
}
|
||||
|
||||
// 使用mammoth.js将Word转换为HTML,添加自定义选项
|
||||
// 3. 使用mammoth.js将Word转换为HTML
|
||||
addDebugInfo("使用mammoth开始转换文档为HTML...");
|
||||
try {
|
||||
// 添加自定义样式映射
|
||||
// 自定义样式映射
|
||||
const styleMap = `
|
||||
p[style-name='Heading 1'] => h1:fresh
|
||||
p[style-name='Heading 2'] => h2:fresh
|
||||
@@ -225,13 +250,14 @@ export default function Documents() {
|
||||
table => table.docx-table
|
||||
`;
|
||||
|
||||
// 创建简化版的转换选项
|
||||
// 转换选项
|
||||
const options = {
|
||||
arrayBuffer: buffer,
|
||||
styleMap: styleMap,
|
||||
includeDefaultStyleMap: true
|
||||
};
|
||||
|
||||
// 执行转换
|
||||
const result = await mammoth.convertToHtml(options);
|
||||
|
||||
// 检查转换警告
|
||||
@@ -243,60 +269,18 @@ export default function Documents() {
|
||||
|
||||
addDebugInfo("文档转换成功,获取到HTML内容");
|
||||
|
||||
// 为生成的HTML文档添加包装容器和样式
|
||||
// 4. 为生成的HTML添加包装容器和样式
|
||||
const enhancedHtml = `
|
||||
<div class="document-container">
|
||||
${result.value}
|
||||
<div class="format-note">
|
||||
<p>注意:本地转换使用了简化版格式,一些高级格式(如页眉页脚、复杂表格格式)可能无法完全显示。</p>
|
||||
<p>如需查看完整格式,请使用"嵌入模式"或下载文档。</p>
|
||||
<p>注意:部分复杂格式(如页眉页脚、复杂表格样式)可能无法完全显示。</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 存储HTML内容
|
||||
// 更新状态
|
||||
setDocxHtml(enhancedHtml);
|
||||
|
||||
// 查找匹配的内容并创建位置映射
|
||||
setTimeout(() => {
|
||||
try {
|
||||
if (docxContainerRef.current) {
|
||||
const positionsMap: {[id: number]: number} = {};
|
||||
|
||||
extractedContent.forEach((item) => {
|
||||
// 在HTML内容中查找文本
|
||||
// 使用更安全的查询方式
|
||||
if (docxContainerRef.current) {
|
||||
// 获取所有可能包含文本的元素
|
||||
const elements = docxContainerRef.current.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, td, th, span');
|
||||
|
||||
// 转为数组并过滤包含目标文本的元素
|
||||
const textElements = Array.from(elements).filter(element =>
|
||||
element.textContent?.includes(item.text)
|
||||
);
|
||||
|
||||
if (textElements.length > 0) {
|
||||
// 使用找到的第一个元素的位置
|
||||
const element = textElements[0];
|
||||
const rect = element.getBoundingClientRect();
|
||||
const containerRect = docxContainerRef.current.getBoundingClientRect();
|
||||
// 计算相对于容器的位置
|
||||
positionsMap[item.id] = rect.top - containerRect.top + docxContainerRef.current.scrollTop;
|
||||
|
||||
// 标记找到的元素
|
||||
element.classList.add('docx-content-found');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setDocxContentPositions(positionsMap);
|
||||
addDebugInfo(`已创建 ${Object.keys(positionsMap).length} 个内容位置映射`);
|
||||
}
|
||||
} catch (positionError) {
|
||||
addDebugInfo(`创建位置映射时出错: ${positionError instanceof Error ? positionError.message : String(positionError)}`);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
setDocxLoading(false);
|
||||
} catch (mammothError) {
|
||||
addDebugInfo(`Mammoth转换失败: ${mammothError instanceof Error ? mammothError.message : String(mammothError)}`);
|
||||
@@ -312,9 +296,11 @@ export default function Documents() {
|
||||
|
||||
loadDocx();
|
||||
}
|
||||
}, [currentUrl, fileType, extractedContent, showIframe]);
|
||||
}, [currentUrl, fileType]);
|
||||
|
||||
// 页面渲染完成后检查是否需要滚动
|
||||
/**
|
||||
* 页面滚动逻辑
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (scrollToPage && fileType === "pdf") {
|
||||
const pageElement = document.getElementById(`page-${scrollToPage}`);
|
||||
@@ -325,7 +311,10 @@ export default function Documents() {
|
||||
}
|
||||
}, [scrollToPage, fileType]);
|
||||
|
||||
// 生成所有PDF页面的数组
|
||||
/**
|
||||
* 生成所有PDF页面的渲染数组
|
||||
* @returns 页面组件数组
|
||||
*/
|
||||
const renderAllPages = () => {
|
||||
if (!numPages) return null;
|
||||
|
||||
@@ -347,45 +336,14 @@ export default function Documents() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-gray-50 p-6">
|
||||
<div className="flex h-screen bg-gray-50">
|
||||
{/* 文档展示区域 */}
|
||||
<div className="flex-1 mr-6">
|
||||
<div className="bg-white p-4 rounded-lg shadow-md">
|
||||
<div className="flex-1 mr-6 p-4">
|
||||
<div className="bg-white p-4 rounded-lg shadow-md h-full flex flex-col">
|
||||
<h1 className="text-2xl font-bold mb-4">文档预览 ({fileType.toUpperCase()})</h1>
|
||||
|
||||
{fileType === "docx" && (
|
||||
<div className="bg-gray-100 p-3 mb-4 rounded flex flex-col">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<p className="text-sm text-gray-600">Word文档预览模式</p>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setShowIframe(!showIframe)}
|
||||
className={`px-3 py-1 text-sm rounded ${showIframe ? 'bg-gray-200' : 'bg-blue-500 text-white'}`}
|
||||
>
|
||||
{showIframe ? "尝试本地渲染" : "使用嵌入模式"}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.open(currentUrl, '_blank')}
|
||||
className="px-3 py-1 bg-gray-500 text-white text-sm rounded"
|
||||
>
|
||||
下载文档
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{!showIframe && (
|
||||
<div className="text-xs text-gray-500 bg-yellow-50 p-2 rounded">
|
||||
<p>本地渲染说明:</p>
|
||||
<ul className="list-disc pl-5 mt-1">
|
||||
<li>本地渲染使用mammoth.js库将Word文档转换为HTML</li>
|
||||
<li>部分复杂格式(页眉页脚、复杂表格样式、特殊字体等)可能无法完全还原</li>
|
||||
<li>嵌入模式使用Google Docs提供原生渲染,格式更完整但加载较慢</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="w-full h-[80vh] overflow-auto bg-gray-100 rounded-lg p-4">
|
||||
{/* 文档内容显示区域 */}
|
||||
<div className="w-full flex-1 overflow-auto bg-gray-100 rounded-lg p-4">
|
||||
{loadError ? (
|
||||
<div className="text-red-500 flex flex-col items-center justify-center h-full">
|
||||
<p className="mb-4">加载错误:</p>
|
||||
@@ -405,9 +363,6 @@ export default function Documents() {
|
||||
<button onClick={() => switchDocumentUrl('proxy')} className="px-3 py-1 bg-blue-500 text-white rounded">
|
||||
使用CORS代理
|
||||
</button>
|
||||
<button onClick={() => switchToIframeMode()} className="px-3 py-1 bg-purple-500 text-white rounded">
|
||||
使用iframe嵌入
|
||||
</button>
|
||||
<button onClick={() => switchDocumentUrl('pdf')} className="px-3 py-1 bg-yellow-500 text-white rounded">
|
||||
切换到PDF
|
||||
</button>
|
||||
@@ -418,6 +373,7 @@ export default function Documents() {
|
||||
</div>
|
||||
</div>
|
||||
) : fileType === "pdf" ? (
|
||||
/* PDF 文档渲染 */
|
||||
<Document
|
||||
file={currentUrl}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
@@ -433,8 +389,10 @@ export default function Documents() {
|
||||
{renderAllPages()}
|
||||
</Document>
|
||||
) : (
|
||||
/* Word 文档渲染 */
|
||||
<>
|
||||
{docxLoading ? (
|
||||
/* 加载状态显示 */
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<div className="mb-6">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
|
||||
@@ -449,18 +407,8 @@ export default function Documents() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : showIframe ? (
|
||||
// 嵌入模式显示Word文档
|
||||
<div className="w-full h-full">
|
||||
<iframe
|
||||
src={`https://docs.google.com/viewer?url=${encodeURIComponent(currentUrl)}&embedded=true`}
|
||||
className="w-full h-full"
|
||||
frameBorder="0"
|
||||
title="谷歌文档查看器"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
// 本地渲染模式 (只有用户特别点击按钮才显示)
|
||||
/* 本地渲染的Word文档 */
|
||||
<div
|
||||
ref={docxContainerRef}
|
||||
className="w-full h-full"
|
||||
@@ -479,15 +427,16 @@ export default function Documents() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 抽取内容区域 */}
|
||||
<div className="w-80 bg-white p-4 rounded-lg shadow-md">
|
||||
{/* 抽取内容区域 - 始终显示,但DOCX模式下不交互 */}
|
||||
<div className="w-80 bg-white p-4 rounded-lg shadow-md mr-4 my-4 overflow-auto">
|
||||
<h2 className="text-xl font-semibold mb-4">抽取内容</h2>
|
||||
<ul className="space-y-3">
|
||||
{extractedContent.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => handleContentClick(item)}
|
||||
className="w-full text-left p-3 bg-gray-50 hover:bg-gray-100 cursor-pointer rounded-lg transition"
|
||||
className={`w-full text-left p-3 ${fileType === "pdf" ? "bg-gray-50 hover:bg-gray-100 cursor-pointer" : "bg-gray-100"} rounded-lg transition`}
|
||||
disabled={fileType === "docx"}
|
||||
aria-label={`查看内容: ${item.text}`}
|
||||
>
|
||||
<p className="text-sm font-medium">{item.text}</p>
|
||||
@@ -500,13 +449,14 @@ export default function Documents() {
|
||||
{/* 添加自定义样式 */}
|
||||
<style dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
/* 高亮显示样式 */
|
||||
.docx-highlight {
|
||||
background-color: #ffff00;
|
||||
outline: 2px solid orange;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 找到的内容高亮 */
|
||||
/* 找到的内容高亮样式 */
|
||||
.docx-content-found {
|
||||
background-color: rgba(255, 230, 0, 0.3);
|
||||
outline: 1px solid orange;
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
// 简单测试docx-preview能否正常工作
|
||||
import { renderAsync } from 'docx-preview';
|
||||
|
||||
async function testDocxPreview() {
|
||||
try {
|
||||
// DOCX文件URL
|
||||
const fileUrl = "https://dev-xc-enroll.oss-cn-guangzhou.aliyuncs.com/uploads/7840-230620112939.docx";
|
||||
console.log("正在获取文件:", fileUrl);
|
||||
|
||||
// 获取文件
|
||||
const response = await fetch(fileUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`网络请求失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
console.log("文件下载成功,准备解析");
|
||||
|
||||
// 转换为ArrayBuffer
|
||||
const buffer = await response.arrayBuffer();
|
||||
console.log("获取到ArrayBuffer,长度:", buffer.byteLength);
|
||||
|
||||
// 创建容器
|
||||
const container = document.getElementById('docx-container');
|
||||
if (!container) {
|
||||
throw new Error("找不到容器元素");
|
||||
}
|
||||
|
||||
// 渲染文档
|
||||
console.log("开始渲染文档...");
|
||||
await renderAsync(buffer, container, null, {
|
||||
className: "docx-viewer",
|
||||
inWrapper: true,
|
||||
ignoreWidth: false,
|
||||
ignoreHeight: false
|
||||
});
|
||||
|
||||
console.log("文档渲染成功");
|
||||
} catch (error) {
|
||||
console.error("文档渲染失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后执行
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const app = document.getElementById('app');
|
||||
if (app) {
|
||||
// 创建容器
|
||||
app.innerHTML = `
|
||||
<div style="width: 100%; height: 100vh; display: flex; flex-direction: column;">
|
||||
<h1>测试 docx-preview</h1>
|
||||
<div id="docx-container" style="flex: 1; border: 1px solid #ccc; overflow: auto;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 执行测试
|
||||
testDocxPreview();
|
||||
}
|
||||
});
|
||||
@@ -1,221 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Word文档预览方案测试</title>
|
||||
<style>
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
background-color: #f1f1f1;
|
||||
border: 1px solid #ccc;
|
||||
border-bottom: none;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.tab.active {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
#preview-container {
|
||||
flex: 1;
|
||||
border: 1px solid #ccc;
|
||||
overflow: hidden;
|
||||
background-color: #f9f9f9;
|
||||
position: relative;
|
||||
}
|
||||
.preview-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
}
|
||||
.preview-content.active {
|
||||
display: block;
|
||||
}
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
#log {
|
||||
height: 120px;
|
||||
overflow: auto;
|
||||
background-color: #2d2d2d;
|
||||
color: #0f0;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
margin-top: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.download-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
.download-button {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255,255,255,0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
.spinner {
|
||||
border: 5px solid #f3f3f3;
|
||||
border-top: 5px solid #3498db;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Word文档预览方案测试</h1>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab active" data-target="office">Office Online</div>
|
||||
<div class="tab" data-target="google">Google Docs</div>
|
||||
<div class="tab" data-target="download">下载查看</div>
|
||||
</div>
|
||||
|
||||
<div id="preview-container">
|
||||
<div id="loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
<div>加载中,请稍候...</div>
|
||||
</div>
|
||||
|
||||
<div id="office-preview" class="preview-content active">
|
||||
<iframe id="office-iframe" src=""></iframe>
|
||||
</div>
|
||||
|
||||
<div id="google-preview" class="preview-content">
|
||||
<iframe id="google-iframe" src=""></iframe>
|
||||
</div>
|
||||
|
||||
<div id="download-preview" class="preview-content">
|
||||
<div class="download-container">
|
||||
<p>在线预览无法工作?</p>
|
||||
<p>您可以下载文档在本地打开</p>
|
||||
<a id="download-link" href="" class="download-button" download target="_blank">
|
||||
下载文档
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="log">
|
||||
<div>测试页面已加载,正在尝试不同的文档预览方案...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 配置
|
||||
const docUrl = "https://dev-xc-enroll.oss-cn-guangzhou.aliyuncs.com/uploads/7840-230620112939.docx";
|
||||
const officeOnlineUrl = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(docUrl)}`;
|
||||
const googleDocsUrl = `https://docs.google.com/viewer?url=${encodeURIComponent(docUrl)}&embedded=true`;
|
||||
|
||||
// 日志函数
|
||||
function log(message) {
|
||||
const logContainer = document.getElementById('log');
|
||||
const logEntry = document.createElement('div');
|
||||
logEntry.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
|
||||
logContainer.appendChild(logEntry);
|
||||
logContainer.scrollTop = logContainer.scrollHeight;
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 设置iframe源
|
||||
document.getElementById('office-iframe').src = officeOnlineUrl;
|
||||
document.getElementById('google-iframe').src = googleDocsUrl;
|
||||
document.getElementById('download-link').href = docUrl;
|
||||
|
||||
// 设置标签切换
|
||||
const tabs = document.querySelectorAll('.tab');
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', function() {
|
||||
// 移除所有标签的active类
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
|
||||
// 添加当前标签的active类
|
||||
this.classList.add('active');
|
||||
|
||||
// 隐藏所有内容
|
||||
document.querySelectorAll('.preview-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
|
||||
// 显示对应内容
|
||||
const target = this.getAttribute('data-target');
|
||||
document.getElementById(`${target}-preview`).classList.add('active');
|
||||
|
||||
log(`切换到 ${target} 预览模式`);
|
||||
});
|
||||
});
|
||||
|
||||
// 监听iframe加载事件
|
||||
document.getElementById('office-iframe').addEventListener('load', function() {
|
||||
log('Office Online预览已加载');
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
});
|
||||
|
||||
document.getElementById('google-iframe').addEventListener('load', function() {
|
||||
log('Google Docs预览已加载');
|
||||
});
|
||||
|
||||
log('页面初始化完成,正在加载Office Online预览...');
|
||||
}
|
||||
|
||||
// 页面加载完成后执行初始化
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
// 添加错误处理
|
||||
window.addEventListener('error', function(event) {
|
||||
log(`错误: ${event.message}`);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user