|
|
|
@ -1,230 +1,132 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="pdf-viewer">
|
|
|
|
|
<div class="fixedToolBar">
|
|
|
|
|
<!-- 其中包含的功能有分页,页数跳转,文件下载 -->
|
|
|
|
|
<i @click="downloadFile" class="fa fa-download cursor-pointer download" hover-color="var(--el-color-primary)"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sidebar">
|
|
|
|
|
<ul>
|
|
|
|
|
<li v-for="(page,pageIndex) in pagesInfo" :key="page.number" @click="navigateToPage(page.number)">
|
|
|
|
|
<a>
|
|
|
|
|
<img :src="page.thumbnail" alt="Page Thumbnail" :class="pageIndex===currentPage?'thumbnail active':'thumbnail'" />
|
|
|
|
|
<span>{{ pageIndex+1 }}</span>
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="pdf-container" @scroll="handleScroll">
|
|
|
|
|
<!-- <input type="file" @change="handleFileChange" accept="application/pdf" /> -->
|
|
|
|
|
<div ref="pdfContainer" class="pdf-pages"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, onMounted, onUnmounted } from 'vue';
|
|
|
|
|
import * as pdfjsLib from 'pdfjs-dist';
|
|
|
|
|
import 'pdfjs-dist/build/pdf.worker.entry';
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import {renderAsync,renderDocument} from 'docx-preview';
|
|
|
|
|
import { ref } from "vue";
|
|
|
|
|
import { useRoute } from 'vue-router';
|
|
|
|
|
import { ElMessage } from 'element-plus';
|
|
|
|
|
import {queryFileFlowRepTemplateApi,exportTemplateApi} from '@/api/reporting/RepTemplate/RepTemplate';
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
window.removeEventListener('scroll', handleScroll);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 设置 worker 源
|
|
|
|
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/build/pdf.worker.entry';
|
|
|
|
|
|
|
|
|
|
const pdfContainer = ref(null);
|
|
|
|
|
const pdfDocument = ref(null);
|
|
|
|
|
const pdfInfo = ref({});
|
|
|
|
|
const pagesInfo = ref([]);
|
|
|
|
|
let currentPage = ref(0);//设置当前页在的索引页
|
|
|
|
|
let pageHeight = ref(0);//获取单个canvas的高度
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
// 也可以直接是在线地址
|
|
|
|
|
//获取当前预览文件参数的信息
|
|
|
|
|
//获取当前文件的信息
|
|
|
|
|
const pageQuery = ref(route || null);
|
|
|
|
|
const params = pageQuery.value?.query?.fileData;
|
|
|
|
|
//是否能够预览的状态显示
|
|
|
|
|
const fileData = pageQuery.value?.query?.fileData;
|
|
|
|
|
//是否渲染完成
|
|
|
|
|
const downLoadErrorMsg = {
|
|
|
|
|
"1001":"预览失败,文件路径不存在",
|
|
|
|
|
"1002":"预览失败,未找到文件信息",
|
|
|
|
|
"1003":"预览失败,请联系管理员",
|
|
|
|
|
}
|
|
|
|
|
const docxContainer = ref(null);
|
|
|
|
|
|
|
|
|
|
//渲染左侧的缩略图
|
|
|
|
|
const generateThumbnails = async () => {
|
|
|
|
|
if (!pdfDocument.value) return;
|
|
|
|
|
|
|
|
|
|
const numPages = pdfDocument.value.numPages;
|
|
|
|
|
const thumbnails = [];
|
|
|
|
|
|
|
|
|
|
for (let pageNumber = 1; pageNumber <= numPages; pageNumber++) {
|
|
|
|
|
const page = await pdfDocument.value.getPage(pageNumber);
|
|
|
|
|
const viewport = page.getViewport({ scale: 0.5 }); // 缩略图比例
|
|
|
|
|
|
|
|
|
|
// 创建一个新的 canvas 元素
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
const context = canvas.getContext('2d');
|
|
|
|
|
|
|
|
|
|
canvas.height = viewport.height;
|
|
|
|
|
canvas.width = viewport.width;
|
|
|
|
|
|
|
|
|
|
const renderContext = {
|
|
|
|
|
canvasContext: context,
|
|
|
|
|
viewport: viewport
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await page.render(renderContext).promise;
|
|
|
|
|
|
|
|
|
|
// 将 canvas 转换为 base64 图片
|
|
|
|
|
const thumbnail = canvas.toDataURL('image/png');
|
|
|
|
|
thumbnails.push({ number: pageNumber, thumbnail });
|
|
|
|
|
}
|
|
|
|
|
// 模拟获取 DOCX 文件的 ArrayBuffer
|
|
|
|
|
const fetchDocFile = async () => {
|
|
|
|
|
// 这里假设你有一个 API 可以返回 DOCX 文件的 ArrayBuffer
|
|
|
|
|
const response = await queryFileFlowRepTemplateApi(fileData);
|
|
|
|
|
console.log(response,"response文件流信息");
|
|
|
|
|
const { data, headers } = response;
|
|
|
|
|
if(headers['status'] == 500){// 说明此时下载文件有错误,需要获取WARN和ERROR的值
|
|
|
|
|
const code = headers['error']?headers['error'].substring(headers['error'].length-4):'1003';
|
|
|
|
|
console.log(code)
|
|
|
|
|
switch(code){
|
|
|
|
|
case "1001":
|
|
|
|
|
case "1002":
|
|
|
|
|
ElMessage.error(downLoadErrorMsg[code]);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
ElMessage.error(downLoadErrorMsg['1003']);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const arrayBuffer = await new File([data], 'document.docx', { type: data.type }).arrayBuffer();
|
|
|
|
|
return arrayBuffer;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pagesInfo.value = thumbnails;
|
|
|
|
|
// 过滤空页
|
|
|
|
|
const filterEmptyPages = (pages) => {
|
|
|
|
|
console.log(pages,"获取到的pages");
|
|
|
|
|
return pages.filter(page => {
|
|
|
|
|
// 确保 page 是一个 DOM 元素并且有 textContent 属性
|
|
|
|
|
if (page && page.textContent) {
|
|
|
|
|
return page.textContent.trim() !== '';
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
//渲染全部界面
|
|
|
|
|
const renderAllPages = async () => {
|
|
|
|
|
if (!pdfDocument.value) return;
|
|
|
|
|
|
|
|
|
|
const numPages = pdfDocument.value.numPages;
|
|
|
|
|
for (let pageNumber = 1; pageNumber <= numPages; pageNumber++) {
|
|
|
|
|
await renderPage(pageNumber);
|
|
|
|
|
}
|
|
|
|
|
// 显示页码
|
|
|
|
|
const addPageNumbers = (pages) => {
|
|
|
|
|
pages.forEach((page, index) => {
|
|
|
|
|
const pageNumberElement = document.createElement('div');
|
|
|
|
|
pageNumberElement.className = 'page-number';
|
|
|
|
|
pageNumberElement.textContent = `第 ${index + 1} 页`;
|
|
|
|
|
page.appendChild(pageNumberElement);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
//渲染单个界面
|
|
|
|
|
const renderPage = async (pageNumber) => {
|
|
|
|
|
if (!pdfDocument.value) return;
|
|
|
|
|
|
|
|
|
|
const page = await pdfDocument.value.getPage(pageNumber);
|
|
|
|
|
const viewport = page.getViewport({ scale: 1.5 });
|
|
|
|
|
|
|
|
|
|
// 创建一个新的 canvas 元素
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
|
const context = canvas.getContext('2d');
|
|
|
|
|
|
|
|
|
|
canvas.height = viewport.height;
|
|
|
|
|
canvas.width = viewport.width;
|
|
|
|
|
canvas.style.margin = '10px 0';
|
|
|
|
|
pageHeight.value = viewport.height + 10;
|
|
|
|
|
// 将 canvas 添加到容器中
|
|
|
|
|
pdfContainer.value.appendChild(canvas);
|
|
|
|
|
|
|
|
|
|
const renderContext = {
|
|
|
|
|
canvasContext: context,
|
|
|
|
|
viewport: viewport
|
|
|
|
|
};
|
|
|
|
|
// 调整图片和文字的 CSS 样式
|
|
|
|
|
const adjustStyles = (container: HTMLElement) => {
|
|
|
|
|
// 为图片设置 z-index,确保图片不会遮挡文字
|
|
|
|
|
container.querySelectorAll('img').forEach(img => {
|
|
|
|
|
img.style.position = 'relative';
|
|
|
|
|
img.style.zIndex = '1';
|
|
|
|
|
// img.style.top = '-30px';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await page.render(renderContext).promise;
|
|
|
|
|
};
|
|
|
|
|
//点击缩略图进行界面的定位
|
|
|
|
|
const navigateToPage = (pageNumber) => {
|
|
|
|
|
const pageElements = pdfContainer.value.querySelectorAll('canvas');
|
|
|
|
|
pageElements.forEach((element, index) => {
|
|
|
|
|
if (index + 1 === pageNumber) {
|
|
|
|
|
element.scrollIntoView({ behavior: 'smooth' });
|
|
|
|
|
}
|
|
|
|
|
// 为文字设置 z-index,确保文字在图片之上
|
|
|
|
|
container.querySelectorAll('p, span, div').forEach(textElement => {
|
|
|
|
|
textElement.style.position = 'relative';
|
|
|
|
|
textElement.style.zIndex = '2';
|
|
|
|
|
});
|
|
|
|
|
// 更新当前页索引
|
|
|
|
|
currentPage.value = pageNumber - 1;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//监听滚动事件,设置currentPage的值
|
|
|
|
|
const handleScroll = (event) => {
|
|
|
|
|
const scrollElement = event.target;
|
|
|
|
|
const scrollHeight = scrollElement.scrollHeight;
|
|
|
|
|
const scrollTop = scrollElement.scrollTop;
|
|
|
|
|
currentPage.value = Math.round((scrollTop) / (pageHeight.value + 70));
|
|
|
|
|
//下载文件
|
|
|
|
|
const downloadFile = () => {
|
|
|
|
|
exportTemplateApi({pId:fileData});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//获取到的blob数据转换为file文件
|
|
|
|
|
const TurnBlobForRender = async (data) => {
|
|
|
|
|
if(!data){return}
|
|
|
|
|
/**
|
|
|
|
|
* 异步函数用于获取并处理DOC文件流
|
|
|
|
|
* 此函数首先调用API获取DOC文件流,然后根据响应头处理可能的错误情况
|
|
|
|
|
* 如果文件流有效,它将被转换为ArrayBuffer类型,并用于渲染文档
|
|
|
|
|
*/
|
|
|
|
|
const init = async ()=>{
|
|
|
|
|
try {
|
|
|
|
|
const blob = new Blob([data], { type: 'application/pdf' });
|
|
|
|
|
const convertedFile = new File([blob], { type: 'application/pdf' });;
|
|
|
|
|
if(convertedFile.size == 0)return;
|
|
|
|
|
pdfDocument.value = await pdfjsLib.getDocument({ data: data }).promise;
|
|
|
|
|
await generateThumbnails();
|
|
|
|
|
renderAllPages();
|
|
|
|
|
currentPage.value = 0; // 初始化当前页为第一页
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error loading PDF:', error);
|
|
|
|
|
// 假设你已经有一个方法来获取 DOCX 文件的 ArrayBuffer
|
|
|
|
|
const arrayBuffer = await fetchDocFile();
|
|
|
|
|
// 假设你有一个API可以返回DOC文件流
|
|
|
|
|
if (!(arrayBuffer instanceof ArrayBuffer)) {
|
|
|
|
|
throw new Error('Invalid arrayBuffer type');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
//从后台获取文件流
|
|
|
|
|
const fetchDocFile = async () => {
|
|
|
|
|
// 这里假设你有一个 API 可以返回 DOCX 文件的 ArrayBuffer
|
|
|
|
|
const response = await queryFileFlowRepTemplateApi(params);
|
|
|
|
|
const { data, headers } = response;
|
|
|
|
|
if(headers['status'] == 500){// 说明此时下载文件有错误,需要获取WARN和ERROR的值
|
|
|
|
|
const code = headers['error']?headers['error'].substring(headers['error'].length-4):'1003';
|
|
|
|
|
console.log(code)
|
|
|
|
|
switch(code){
|
|
|
|
|
case "1001":
|
|
|
|
|
case "1002":
|
|
|
|
|
ElMessage.error(downLoadErrorMsg[code]);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
ElMessage.error(downLoadErrorMsg['1003']);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
// 渲染 DOCX 文件
|
|
|
|
|
const pages = await renderAsync(arrayBuffer, docxContainer.value);
|
|
|
|
|
adjustStyles(docxContainer.value);
|
|
|
|
|
// 确保 pages 是一个数组
|
|
|
|
|
const pagesArray = Array.isArray(pages) ? pages : [pages];
|
|
|
|
|
|
|
|
|
|
// 过滤空页
|
|
|
|
|
const filteredPages = filterEmptyPages(pagesArray);
|
|
|
|
|
|
|
|
|
|
// 显示页码
|
|
|
|
|
addPageNumbers(filteredPages);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error rendering DOCX file:', error);
|
|
|
|
|
}
|
|
|
|
|
// const file = await new File([data], 'document.pdf', { type: data.type });
|
|
|
|
|
TurnBlobForRender(data);
|
|
|
|
|
};
|
|
|
|
|
fetchDocFile();//进入界面需要获取blob文件流
|
|
|
|
|
|
|
|
|
|
// const handleFileChange = async (event) => {
|
|
|
|
|
// const file = event.target.files[0];
|
|
|
|
|
// if (file) {
|
|
|
|
|
// if (!file.type.startsWith('application/pdf')) {
|
|
|
|
|
// console.error('Invalid file type. Only PDF files are allowed.');
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// if (file.size > 5 * 1024 * 1024) { // 限制文件大小为5MB
|
|
|
|
|
// console.error('File size exceeds the limit of 5MB.');
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// const fileReader = new FileReader();
|
|
|
|
|
// fileReader.onload = async (e) => {
|
|
|
|
|
// const typedArray = new Uint8Array(e.target.result);
|
|
|
|
|
// try {
|
|
|
|
|
// pdfDocument.value = await pdfjsLib.getDocument({ data: typedArray }).promise;
|
|
|
|
|
// await generateThumbnails();
|
|
|
|
|
// renderAllPages();
|
|
|
|
|
// currentPage.value = 0;//初始化当前页为第一页
|
|
|
|
|
// } catch (error) {
|
|
|
|
|
// console.error('Error loading PDF:', error);
|
|
|
|
|
// }
|
|
|
|
|
// };
|
|
|
|
|
// fileReader.onerror = (error) => {
|
|
|
|
|
// console.error('Error reading file:', error);
|
|
|
|
|
// };
|
|
|
|
|
// fileReader.readAsArrayBuffer(file);
|
|
|
|
|
// }
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
init();
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div class="wrap">
|
|
|
|
|
<div class="fixedToolBar">
|
|
|
|
|
<!-- 其中包含的功能有分页,页数跳转,文件下载 -->
|
|
|
|
|
<i @click="downloadFile" class="fa fa-download cursor-pointer download" hover-color="var(--el-color-primary)"></i>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="docx-container" ref="docxContainer"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.pdf-viewer {
|
|
|
|
|
display: flex;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
background-color: rgba(0, 0, 0, 0.7);
|
|
|
|
|
padding-top:70px;
|
|
|
|
|
}
|
|
|
|
|
.fixedToolBar{
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
@ -242,54 +144,12 @@ fetchDocFile();//进入界面需要获取blob文件流
|
|
|
|
|
top:13px;
|
|
|
|
|
right:15px;
|
|
|
|
|
}
|
|
|
|
|
.sidebar {
|
|
|
|
|
width: 250px;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
border-right: 1px solid #ccc;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar h3 {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar ul {
|
|
|
|
|
list-style-type: none;
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar li {
|
|
|
|
|
text-align: center;
|
|
|
|
|
width: 120px;
|
|
|
|
|
margin:20px auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar a {
|
|
|
|
|
text-decoration: none;
|
|
|
|
|
color: #fff;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar img.thumbnail {
|
|
|
|
|
width: 120px;
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
}
|
|
|
|
|
#docx-container {
|
|
|
|
|
height: 100vh;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
padding:50px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
.pdf-container {
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pdf-pages {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
.sidebar img.active{
|
|
|
|
|
border:5px solid rgb(153, 196, 238)!important;
|
|
|
|
|
}
|
|
|
|
|
</style>
|