|
@@ -2,12 +2,50 @@
|
|
|
<div class="file-preview">
|
|
|
<div v-if="isSupported">
|
|
|
<!-- PDF 预览 -->
|
|
|
- <iframe
|
|
|
- v-if="fileType === 'pdf'"
|
|
|
- :src="fileUrl"
|
|
|
- width="100%"
|
|
|
- height="830px"
|
|
|
- ></iframe>
|
|
|
+ <div v-if="fileType === 'pdf'" class="pdf-container">
|
|
|
+ <pdf
|
|
|
+ ref="pdfComponent"
|
|
|
+ v-if="pdfUrl"
|
|
|
+ :src="pdfUrl"
|
|
|
+ :page="currentPage"
|
|
|
+ :rotate="0"
|
|
|
+ @loaded="loadCompleted"
|
|
|
+ @num-pages="pageCount = $event"
|
|
|
+ class="pdf-viewer"
|
|
|
+ ></pdf>
|
|
|
+
|
|
|
+ <!-- 将高亮层移到PDF组件后面 -->
|
|
|
+ <div class="highlight-layer">
|
|
|
+ <div
|
|
|
+ v-for="(highlight, index) in highlights"
|
|
|
+ :key="index"
|
|
|
+ class="highlight-rect"
|
|
|
+ :style="getHighlightStyle(highlight)"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- PDF控制器 -->
|
|
|
+ <div v-if="pdfLoadComplete" class="pdf-controls">
|
|
|
+ <el-button-group>
|
|
|
+ <el-button @click="prevPage" :disabled="currentPage <= 1"
|
|
|
+ >上一页</el-button
|
|
|
+ >
|
|
|
+ <el-button @click="nextPage" :disabled="currentPage >= pageCount"
|
|
|
+ >下一页</el-button
|
|
|
+ >
|
|
|
+ </el-button-group>
|
|
|
+ <span class="page-info">{{ currentPage }} / {{ pageCount }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- PDF加载状态 -->
|
|
|
+ <div v-if="!pdfLoadComplete" class="pdf-loading">
|
|
|
+ <el-progress
|
|
|
+ type="circle"
|
|
|
+ :percentage="loadingProgress"
|
|
|
+ ></el-progress>
|
|
|
+ <span>PDF加载中...</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
|
|
|
<!-- 图片预览 -->
|
|
|
<img
|
|
@@ -83,7 +121,12 @@ import mammoth from "mammoth";
|
|
|
import LuckyExcel from "luckyexcel";
|
|
|
import { saveAs } from "file-saver";
|
|
|
import JSZip from "jszip";
|
|
|
+import pdf from "vue-pdf";
|
|
|
+
|
|
|
export default {
|
|
|
+ components: {
|
|
|
+ pdf,
|
|
|
+ },
|
|
|
props: {
|
|
|
fileUrl: {
|
|
|
type: String,
|
|
@@ -97,6 +140,14 @@ export default {
|
|
|
type: String,
|
|
|
required: true,
|
|
|
},
|
|
|
+ pageNum: {
|
|
|
+ type: String,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
+ coordinate: {
|
|
|
+ type: String,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
@@ -126,6 +177,15 @@ export default {
|
|
|
previewError: false,
|
|
|
errorMessage: "",
|
|
|
docData: null, // 保存文档数据用于下载
|
|
|
+ pdfUrl: null,
|
|
|
+ pdfLoadComplete: false,
|
|
|
+ currentPage: 1,
|
|
|
+ pageCount: 0,
|
|
|
+ highlights: [], // 存储高亮区域
|
|
|
+ pdfScale: 1, // PDF缩放比例
|
|
|
+ pdfViewport: null, // PDF视口信息
|
|
|
+ startX: 0,
|
|
|
+ endY: 0,
|
|
|
};
|
|
|
},
|
|
|
watch: {
|
|
@@ -138,6 +198,31 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
},
|
|
|
+ pageNum: {
|
|
|
+ immediate: true,
|
|
|
+ handler(newValue) {
|
|
|
+ if (this.fileType === "pdf" && newValue) {
|
|
|
+ this.currentPage = parseInt(newValue);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ fileUrl: {
|
|
|
+ immediate: true,
|
|
|
+ async handler(newValue) {
|
|
|
+ if (this.fileType === "pdf" && newValue) {
|
|
|
+ await this.initPdf();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ coordinate: {
|
|
|
+ handler(newValue) {
|
|
|
+ console.log(newValue);
|
|
|
+ if (newValue && this.pdfLoadComplete) {
|
|
|
+ this.addHighlight(newValue);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
},
|
|
|
computed: {
|
|
|
officePreviewUrl() {
|
|
@@ -167,6 +252,13 @@ export default {
|
|
|
isOffice() {
|
|
|
return ["ppt", "pptx"].includes(this.fileType);
|
|
|
},
|
|
|
+ pdfViewerUrl() {
|
|
|
+ if (this.fileType === "pdf") {
|
|
|
+ // 确保添加初始页码参数
|
|
|
+ return `${this.fileUrl}#page=${this.pageNum}`;
|
|
|
+ }
|
|
|
+ return this.fileUrl;
|
|
|
+ },
|
|
|
},
|
|
|
mounted() {
|
|
|
console.log(this.isWord);
|
|
@@ -177,8 +269,55 @@ export default {
|
|
|
} else if (this.$route.query.type == "xlsx") {
|
|
|
this.loadExcelContent();
|
|
|
}
|
|
|
+ window.addEventListener("resize", this.updateHighlightPosition);
|
|
|
},
|
|
|
methods: {
|
|
|
+ /* 翻页 */
|
|
|
+
|
|
|
+ scrollToPage(pageNumber) {
|
|
|
+ const iframe = this.$refs.previewFrame;
|
|
|
+ if (!iframe || !iframe.contentWindow) return;
|
|
|
+
|
|
|
+ const tryScrollMethods = [
|
|
|
+ // 方法1: 使用PDFViewerApplication API
|
|
|
+ () => {
|
|
|
+ iframe.contentWindow.PDFViewerApplication.page = parseInt(pageNumber);
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+ // 方法2: 使用PDFViewerApplication.pdfViewer
|
|
|
+ () => {
|
|
|
+ iframe.contentWindow.PDFViewerApplication.pdfViewer.currentPageNumber =
|
|
|
+ parseInt(pageNumber);
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+ // 方法3: 通过URL参数重新加载
|
|
|
+ () => {
|
|
|
+ const newUrl = this.fileUrl.split("#")[0] + `#page=${pageNumber}`;
|
|
|
+ if (iframe.src !== newUrl) {
|
|
|
+ iframe.src = newUrl;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 依次尝试不同的方法
|
|
|
+ for (const method of tryScrollMethods) {
|
|
|
+ try {
|
|
|
+ if (method()) {
|
|
|
+ console.log("PDF页面跳转成功");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.log("尝试下一种跳转方法");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 所有方法都失败时的处理
|
|
|
+ console.warn("PDF跳转失败,请检查PDF查看器配置");
|
|
|
+ this.$message.warning("PDF跳转可能不成功,请手动滚动到目标页面");
|
|
|
+ },
|
|
|
+
|
|
|
async loadWordContent() {
|
|
|
this.wordLoaded = false;
|
|
|
this.loadingProgress = 0;
|
|
@@ -269,7 +408,7 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
- // 增强文档内容显示
|
|
|
+ // 增强文档内容显
|
|
|
enhanceWordContent(content) {
|
|
|
return `
|
|
|
<div class="enhanced-word-content">
|
|
@@ -371,6 +510,177 @@ export default {
|
|
|
this.$message.error(`Excel文档加载失败: ${error.message}`);
|
|
|
}
|
|
|
},
|
|
|
+ // 添加 iframe 加载完成的处理函数
|
|
|
+ handleIframeLoad() {
|
|
|
+ const iframe = this.$refs.previewFrame;
|
|
|
+ if (iframe && iframe.contentWindow) {
|
|
|
+ // 等待PDF.js完全初始化
|
|
|
+ setTimeout(() => {
|
|
|
+ this.scrollToPage(this.pageNum);
|
|
|
+ }, 1000); // 给PDF.js足够的加载时间
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 添加消息处理函数
|
|
|
+ handlePdfMessage(event) {
|
|
|
+ // 处理来自 PDF.js 的消息
|
|
|
+ if (event.data && event.data.type === "pdfLoaded") {
|
|
|
+ // PDF 加载完成,可以执行翻页操作
|
|
|
+ this.scrollToPage(this.pageNum);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 监听页码变化
|
|
|
+ watchPageChange() {
|
|
|
+ const iframe = this.$refs.previewFrame;
|
|
|
+ if (iframe && iframe.contentWindow) {
|
|
|
+ try {
|
|
|
+ // 监听PDF.js的页码变化事件
|
|
|
+ iframe.contentWindow.PDFViewerApplication.eventBus.on(
|
|
|
+ "pagechanging",
|
|
|
+ (evt) => {
|
|
|
+ console.log("PDF页码变化:", evt.pageNumber);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ } catch (error) {
|
|
|
+ console.warn("无法监听PDF页码变化:", error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async initPdf() {
|
|
|
+ try {
|
|
|
+ if (this.type !== "ai") {
|
|
|
+ const res = await searchTaskInfo({
|
|
|
+ page: 1,
|
|
|
+ page_size: 10,
|
|
|
+ document_id: this.$route.query.id,
|
|
|
+ });
|
|
|
+ this.pdfUrl = { url: res.data.documentUrl };
|
|
|
+ } else {
|
|
|
+ this.pdfUrl = { url: this.fileUrl };
|
|
|
+ }
|
|
|
+ // 设置初始页码
|
|
|
+ this.currentPage = parseInt(this.pageNum) || 1;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("PDF初始化失败:", error);
|
|
|
+ this.$message.error("PDF加载失败,请重试");
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // PDF文档完全加载完成
|
|
|
+ loadCompleted() {
|
|
|
+ this.pdfLoadComplete = true;
|
|
|
+ this.loadingProgress = 100;
|
|
|
+
|
|
|
+ // 确保在PDF完全渲染后更新高亮
|
|
|
+ setTimeout(() => {
|
|
|
+ this.updateHighlightPosition();
|
|
|
+ // 如果有coordinate属性,添加高亮
|
|
|
+ if (this.coordinate) {
|
|
|
+ this.addHighlight(this.coordinate);
|
|
|
+ }
|
|
|
+ }, 500);
|
|
|
+ },
|
|
|
+
|
|
|
+ // PDF翻页控制
|
|
|
+ prevPage() {
|
|
|
+ if (this.currentPage > 1) {
|
|
|
+ this.currentPage--;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ nextPage() {
|
|
|
+ if (this.currentPage < this.pageCount) {
|
|
|
+ this.currentPage++;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 修改添加高亮方法
|
|
|
+ addHighlight(coordinates) {
|
|
|
+ try {
|
|
|
+ console.log("Adding highlight with coordinates:", coordinates);
|
|
|
+ let coords = JSON.parse(coordinates);
|
|
|
+
|
|
|
+ // 确保坐标数组格式正确
|
|
|
+ if (!Array.isArray(coords) || coords.length !== 4) {
|
|
|
+ console.error("Invalid coordinates format");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 正确获取起始点和终点坐标
|
|
|
+ const startX = coords[2]; // x1
|
|
|
+ const endY = coords[0]; // y1
|
|
|
+ const endX = coords[3]; // x2
|
|
|
+ const startY = coords[1]; // y2
|
|
|
+
|
|
|
+ // 计算矩形的位置和尺寸
|
|
|
+ const highlight = {
|
|
|
+ x: Math.min(startX, endX), // 取两个x坐标的最小值作为左边界
|
|
|
+ y: Math.min(startY, endY), // 取两个y坐标的最小值作为上边界
|
|
|
+ width: Math.abs(startX - endX), // 宽度为x坐标差的绝对值
|
|
|
+ height: Math.abs(startY - endY), // 高度为y坐标差的绝对值
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log("Processed highlight:", highlight);
|
|
|
+ this.startX = startX;
|
|
|
+ this.endY = endY;
|
|
|
+ this.highlights = [highlight];
|
|
|
+
|
|
|
+ // 强制更新高亮位置
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.updateHighlightPosition();
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error adding highlight:", error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 修改更新高亮位置方法
|
|
|
+ updateHighlightPosition() {
|
|
|
+ const pdfComponent = this.$refs.pdfComponent;
|
|
|
+ if (!pdfComponent || !pdfComponent.$el) return;
|
|
|
+
|
|
|
+ const canvas = pdfComponent.$el.querySelector("canvas");
|
|
|
+ if (!canvas) return;
|
|
|
+
|
|
|
+ // 获取PDF页面的实际尺寸和缩放比例
|
|
|
+ const rect = canvas.getBoundingClientRect();
|
|
|
+ const scaleX = rect.width / canvas.width;
|
|
|
+ const scaleY = rect.height / canvas.height;
|
|
|
+
|
|
|
+ this.pdfScale = {
|
|
|
+ scaleX,
|
|
|
+ scaleY,
|
|
|
+ width: rect.width,
|
|
|
+ height: rect.height,
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log("PDF scale updated:", this.pdfScale);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 修改高亮样式计算方法
|
|
|
+ getHighlightStyle(highlight) {
|
|
|
+ if (!this.pdfScale) return {};
|
|
|
+
|
|
|
+ const { scaleX, scaleY, height } = this.pdfScale;
|
|
|
+
|
|
|
+ // PDF坐标系转换(PDF坐标系原点在左下角,需要转换为左上角)
|
|
|
+ return {
|
|
|
+ position: "absolute",
|
|
|
+ left: `${this.startX}px`,
|
|
|
+ top: `${this.endY}px`,
|
|
|
+ width: `${highlight.width * scaleX}px`,
|
|
|
+ height: `${highlight.height * scaleY}px`,
|
|
|
+ backgroundColor: "rgba(255, 152, 0, 0.1)", // 淡橙色半透明背景
|
|
|
+ border: "2px solid #FF9800", // 橙色边框
|
|
|
+ pointerEvents: "none",
|
|
|
+ zIndex: 100,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ },
|
|
|
+ // 组件销毁时清理事件监听器
|
|
|
+ beforeDestroy() {
|
|
|
+ window.removeEventListener("message", this.handlePdfMessage);
|
|
|
+ window.removeEventListener("resize", this.updateHighlightPosition);
|
|
|
},
|
|
|
};
|
|
|
</script>
|
|
@@ -378,8 +688,10 @@ export default {
|
|
|
<style scoped>
|
|
|
.file-preview {
|
|
|
width: 100%;
|
|
|
- height: 850px;
|
|
|
- overflow: auto;
|
|
|
+ height: 100vh;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
.unsupported-file {
|
|
|
display: flex;
|
|
@@ -441,4 +753,122 @@ export default {
|
|
|
max-height: 830px;
|
|
|
padding: 20px;
|
|
|
}
|
|
|
+
|
|
|
+/* 添加PDF预览相关样式 */
|
|
|
+.pdf-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.pdf-error {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ text-align: center;
|
|
|
+ color: #f56c6c;
|
|
|
+}
|
|
|
+
|
|
|
+.pdf-loading {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.pdf-controls {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 0px;
|
|
|
+ left: 77%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20px;
|
|
|
+ padding: 10px 20px;
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ border-radius: 4px;
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
+ z-index: 1000;
|
|
|
+ margin: 0 auto;
|
|
|
+}
|
|
|
+
|
|
|
+.page-info {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+/* PDF页面容器样式 */
|
|
|
+.vuepdfiframe {
|
|
|
+ width: 100% !important;
|
|
|
+ height: 750px !important; /* 留出空间给分页控件 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 新增PDF查看器样式 */
|
|
|
+.pdf-viewer {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* PDF页面样式 */
|
|
|
+::v-deep .pdf-viewer canvas {
|
|
|
+ max-width: 771px;
|
|
|
+ height: 1024px !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保PDF内容居中显示 */
|
|
|
+:deep(.vue-pdf-embed) {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ width: 90%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.highlight-layer {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 2;
|
|
|
+}
|
|
|
+
|
|
|
+.highlight-rect {
|
|
|
+ position: absolute;
|
|
|
+ border: 2px solid #ff9800;
|
|
|
+ box-shadow: 0 0 4px rgba(255, 152, 0, 0.5);
|
|
|
+ background-color: rgba(255, 152, 0, 0.1);
|
|
|
+ mix-blend-mode: multiply;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ animation: highlight-flash 2s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.pdf-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+/* 添加动画效果 */
|
|
|
+@keyframes highlight-flash {
|
|
|
+ 0% {
|
|
|
+ border-color: rgba(255, 152, 0, 0.3);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ border-color: rgba(255, 152, 0, 1);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ border-color: rgba(255, 152, 0, 0.3);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.highlight-rect {
|
|
|
+ animation: highlight-flash 2s infinite;
|
|
|
+}
|
|
|
</style>
|