|
@@ -0,0 +1,740 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="test-container">
|
|
|
|
+ <div class="test-header">
|
|
|
|
+ <h3>Word文档转换测试</h3>
|
|
|
|
+ <div class="upload-section">
|
|
|
|
+ <input type="file" @change="handleFileUpload" accept=".docx,.doc" />
|
|
|
|
+ <div class="quick-actions" v-if="htmlContent">
|
|
|
|
+ <button @click="directToPdf" class="action-btn">直接转为PDF并下载</button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="convert-type">
|
|
|
|
+ <label>
|
|
|
|
+ <input type="radio" v-model="convertType" value="html" /> HTML
|
|
|
|
+ </label>
|
|
|
|
+ <label>
|
|
|
|
+ <input type="radio" v-model="convertType" value="image" /> Image
|
|
|
|
+ </label>
|
|
|
|
+ <label>
|
|
|
|
+ <input type="radio" v-model="convertType" value="pdf" /> PDF
|
|
|
|
+ </label>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div v-if="loading" class="loading">
|
|
|
|
+ <div class="spinner" />
|
|
|
|
+ <div v-if="pdfProgress > 0" class="progress">
|
|
|
|
+ 转换进度: {{ pdfProgress }}%
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="result-container">
|
|
|
|
+ <!-- HTML显示 -->
|
|
|
|
+ <div v-if="convertType === 'html'" class="result-section">
|
|
|
|
+ <div id="html-container" class="html-result" v-html="htmlContent" />
|
|
|
|
+ <div class="actions">
|
|
|
|
+ <button @click="copyToClipboard">复制HTML</button>
|
|
|
|
+ <button @click="convertHtmlToImage">转为图片</button>
|
|
|
|
+ <button @click="convertHtmlToPdf">转为PDF</button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 图片显示 -->
|
|
|
|
+ <div v-if="convertType === 'image'" class="result-section">
|
|
|
|
+ <div v-if="imageUrl" class="image-result">
|
|
|
|
+ <img :src="imageUrl" alt="转换后的图片" />
|
|
|
|
+ </div>
|
|
|
|
+ <div v-else class="no-result">请先转换为图片</div>
|
|
|
|
+ <div class="actions" v-if="imageUrl">
|
|
|
|
+ <button @click="downloadImage">下载图片</button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- PDF显示 -->
|
|
|
|
+ <div v-if="convertType === 'pdf'" class="result-section">
|
|
|
|
+ <div v-if="pdfUrl" class="pdf-result">
|
|
|
|
+ <iframe :src="pdfUrl" frameborder="0" />
|
|
|
|
+ </div>
|
|
|
|
+ <div v-else class="no-result">请先转换为PDF</div>
|
|
|
|
+ <div class="actions" v-if="pdfUrl">
|
|
|
|
+ <button @click="downloadPdf">下载PDF</button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup lang="ts">
|
|
|
|
+import { ref, onMounted, watch, onUnmounted } from 'vue';
|
|
|
|
+import mammoth from 'mammoth';
|
|
|
|
+import html2canvas from 'html2canvas';
|
|
|
|
+import jsPDF from 'jspdf';
|
|
|
|
+import 'jspdf-autotable';
|
|
|
|
+
|
|
|
|
+defineOptions({
|
|
|
|
+ name: 'Test4Page',
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const loading = ref<boolean>(false);
|
|
|
|
+const convertType = ref<'html' | 'image' | 'pdf'>('html');
|
|
|
|
+const imageUrl = ref<string>('');
|
|
|
|
+const pdfUrl = ref<string>('');
|
|
|
|
+const pdfBytes = ref<Uint8Array | null>(null);
|
|
|
|
+const pdfDocument = ref<jsPDF | null>(null);
|
|
|
|
+const pdfProgress = ref<number>(0);
|
|
|
|
+const htmlContent = ref<string>('');
|
|
|
|
+const fileData = ref<ArrayBuffer | null>(null);
|
|
|
|
+const fileName = ref<string>('');
|
|
|
|
+
|
|
|
|
+// 监听转换类型变化
|
|
|
|
+watch(convertType, async (newType) => {
|
|
|
|
+ if (newType === 'image' && htmlContent.value && !imageUrl.value) {
|
|
|
|
+ // 如果切换到图片模式且已有HTML但没有图片,则生成图片
|
|
|
|
+ await convertHtmlToImage();
|
|
|
|
+ } else if (newType === 'pdf' && htmlContent.value && !pdfUrl.value) {
|
|
|
|
+ // 如果切换到PDF模式且已有HTML但没有PDF,则生成PDF
|
|
|
|
+ await convertHtmlToPdf();
|
|
|
|
+ }
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+const handleFileUpload = async (event: Event) => {
|
|
|
|
+ const input = event.target as HTMLInputElement;
|
|
|
|
+ const file = input.files?.[0];
|
|
|
|
+
|
|
|
|
+ if (!file) {
|
|
|
|
+ alert('请选择文件');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fileName.value = file.name;
|
|
|
|
+ pdfUrl.value = '';
|
|
|
|
+ pdfBytes.value = null;
|
|
|
|
+ htmlContent.value = '';
|
|
|
|
+ fileData.value = null;
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // 读取Word文件
|
|
|
|
+ const arrayBuffer = await file.arrayBuffer();
|
|
|
|
+ fileData.value = arrayBuffer;
|
|
|
|
+
|
|
|
|
+ // 根据当前选择的转换类型进行处理
|
|
|
|
+ if (convertType.value === 'html') {
|
|
|
|
+ await convertWordToHtml(arrayBuffer);
|
|
|
|
+ } else if (convertType.value === 'image') {
|
|
|
|
+ alert('请先选择HTML模式进行转换');
|
|
|
|
+ convertType.value = 'html';
|
|
|
|
+ await convertWordToHtml(arrayBuffer);
|
|
|
|
+ } else if (convertType.value === 'pdf') {
|
|
|
|
+ // 先转为HTML,再转为PDF
|
|
|
|
+ await convertWordToHtml(arrayBuffer);
|
|
|
|
+ // 等待DOM更新
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
+ await convertHtmlToPdf();
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('文件处理失败:', error);
|
|
|
|
+ alert('文件处理失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
|
|
|
+ loading.value = false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// Word转HTML方法
|
|
|
|
+const convertWordToHtml = async (arrayBuffer: ArrayBuffer): Promise<void> => {
|
|
|
|
+ try {
|
|
|
|
+ loading.value = true;
|
|
|
|
+ console.log('开始转换Word到HTML');
|
|
|
|
+
|
|
|
|
+ // 使用mammoth转换为HTML
|
|
|
|
+ const result = await mammoth.convertToHtml({ arrayBuffer });
|
|
|
|
+ htmlContent.value = result.value;
|
|
|
|
+
|
|
|
|
+ // 确保转换类型是html
|
|
|
|
+ convertType.value = 'html';
|
|
|
|
+
|
|
|
|
+ console.log('HTML转换完成');
|
|
|
|
+ loading.value = false;
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('HTML转换失败:', error);
|
|
|
|
+ alert('HTML转换失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
|
|
|
+ loading.value = false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// HTML转图片方法
|
|
|
|
+const convertHtmlToImage = async (): Promise<boolean> => {
|
|
|
|
+ try {
|
|
|
|
+ loading.value = true;
|
|
|
|
+
|
|
|
|
+ // 确保HTML已经渲染
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
+
|
|
|
|
+ // 必须确保HTML容器是可见的
|
|
|
|
+ if (convertType.value !== 'html') {
|
|
|
|
+ convertType.value = 'html';
|
|
|
|
+ // 再次等待DOM更新
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const container = document.getElementById('html-container') as HTMLElement;
|
|
|
|
+ if (!container) {
|
|
|
|
+ console.error('找不到HTML容器');
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ console.log('开始转换HTML到图片');
|
|
|
|
+
|
|
|
|
+ // 获取原始容器高度和样式
|
|
|
|
+ const originalHeight = container.offsetHeight;
|
|
|
|
+ const originalScrollHeight = container.scrollHeight;
|
|
|
|
+
|
|
|
|
+ console.log(`容器实际高度: ${originalHeight}px, 滚动高度: ${originalScrollHeight}px`);
|
|
|
|
+
|
|
|
|
+ // 设置样式(临时)
|
|
|
|
+ const originalStyles = {
|
|
|
|
+ border: container.style.border,
|
|
|
|
+ boxShadow: container.style.boxShadow,
|
|
|
|
+ borderRadius: container.style.borderRadius,
|
|
|
|
+ margin: container.style.margin,
|
|
|
|
+ padding: container.style.padding,
|
|
|
|
+ height: container.style.height,
|
|
|
|
+ maxHeight: container.style.maxHeight,
|
|
|
|
+ overflow: container.style.overflow
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 修改容器使其显示全部内容,不产生滚动条
|
|
|
|
+ container.style.border = 'none';
|
|
|
|
+ container.style.boxShadow = 'none';
|
|
|
|
+ container.style.borderRadius = '0';
|
|
|
|
+ container.style.margin = '0';
|
|
|
|
+ container.style.padding = '0';
|
|
|
|
+ container.style.height = `${originalScrollHeight}px`; // 关键修改:设置为滚动高度
|
|
|
|
+ container.style.maxHeight = 'none'; // 移除最大高度限制
|
|
|
|
+ container.style.overflow = 'visible'; // 确保内容不被裁剪
|
|
|
|
+
|
|
|
|
+ // 让DOM更新样式
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
+
|
|
|
|
+ console.log('修改后容器高度:', container.offsetHeight);
|
|
|
|
+
|
|
|
|
+ // 使用html2canvas转换,关键修改:禁用滚动处理
|
|
|
|
+ const canvas = await html2canvas(container, {
|
|
|
|
+ scale: 2, // 提高清晰度
|
|
|
|
+ useCORS: true,
|
|
|
|
+ allowTaint: true,
|
|
|
|
+ backgroundColor: '#ffffff',
|
|
|
|
+ windowHeight: container.scrollHeight, // 使用滚动高度
|
|
|
|
+ height: container.scrollHeight, // 设置捕获高度为滚动高度
|
|
|
|
+ width: container.offsetWidth,
|
|
|
|
+ scrollY: 0, // 禁用滚动捕获
|
|
|
|
+ scrollX: 0, // 禁用滚动捕获
|
|
|
|
+ logging: true, // 开启日志便于调试
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 恢复容器原始样式
|
|
|
|
+ container.style.border = originalStyles.border;
|
|
|
|
+ container.style.boxShadow = originalStyles.boxShadow;
|
|
|
|
+ container.style.borderRadius = originalStyles.borderRadius;
|
|
|
|
+ container.style.margin = originalStyles.margin;
|
|
|
|
+ container.style.padding = originalStyles.padding;
|
|
|
|
+ container.style.height = originalStyles.height;
|
|
|
|
+ container.style.maxHeight = originalStyles.maxHeight;
|
|
|
|
+ container.style.overflow = originalStyles.overflow;
|
|
|
|
+
|
|
|
|
+ console.log(`Canvas尺寸: ${canvas.width}x${canvas.height}`);
|
|
|
|
+
|
|
|
|
+ // 转为图片URL
|
|
|
|
+ imageUrl.value = canvas.toDataURL('image/png');
|
|
|
|
+
|
|
|
|
+ // 切换到图片模式
|
|
|
|
+ convertType.value = 'image';
|
|
|
|
+
|
|
|
|
+ console.log('图片转换完成');
|
|
|
|
+ loading.value = false;
|
|
|
|
+ return true;
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('图片转换失败:', error);
|
|
|
|
+ alert('图片转换失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
|
|
|
+ loading.value = false;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// HTML转PDF方法 - 简化版,不使用分页处理
|
|
|
|
+const convertHtmlToPdf = async (): Promise<boolean> => {
|
|
|
|
+ try {
|
|
|
|
+ // 重置进度
|
|
|
|
+ pdfProgress.value = 0;
|
|
|
|
+ loading.value = true;
|
|
|
|
+
|
|
|
|
+ // 确保HTML已经渲染
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
+
|
|
|
|
+ // 必须确保HTML容器是可见的
|
|
|
|
+ if (convertType.value !== 'html') {
|
|
|
|
+ convertType.value = 'html';
|
|
|
|
+ // 再次等待DOM更新
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const container = document.getElementById('html-container') as HTMLElement;
|
|
|
|
+ if (!container) {
|
|
|
|
+ console.error('找不到HTML容器');
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ console.log('开始转换HTML到PDF (简化方法)');
|
|
|
|
+ pdfProgress.value = 10;
|
|
|
|
+
|
|
|
|
+ // 获取原始容器高度和样式
|
|
|
|
+ const originalHeight = container.offsetHeight;
|
|
|
|
+ const originalScrollHeight = container.scrollHeight;
|
|
|
|
+ const originalWidth = container.offsetWidth;
|
|
|
|
+
|
|
|
|
+ console.log(`容器实际高度: ${originalHeight}px, 滚动高度: ${originalScrollHeight}px, 宽度: ${originalWidth}px`);
|
|
|
|
+
|
|
|
|
+ // 获取样式(临时)
|
|
|
|
+ const originalStyles = {
|
|
|
|
+ border: container.style.border,
|
|
|
|
+ boxShadow: container.style.boxShadow,
|
|
|
|
+ borderRadius: container.style.borderRadius,
|
|
|
|
+ margin: container.style.margin,
|
|
|
|
+ padding: container.style.padding,
|
|
|
|
+ height: container.style.height,
|
|
|
|
+ maxHeight: container.style.maxHeight,
|
|
|
|
+ overflow: container.style.overflow
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 修改容器使其显示全部内容,不产生滚动条
|
|
|
|
+ container.style.border = 'none';
|
|
|
|
+ container.style.boxShadow = 'none';
|
|
|
|
+ container.style.borderRadius = '0';
|
|
|
|
+ container.style.margin = '0';
|
|
|
|
+ container.style.padding = '0';
|
|
|
|
+ container.style.height = `${originalScrollHeight}px`; // 关键修改:设置为滚动高度
|
|
|
|
+ container.style.maxHeight = 'none'; // 移除最大高度限制
|
|
|
|
+ container.style.overflow = 'visible'; // 确保内容不被裁剪
|
|
|
|
+
|
|
|
|
+ // 让DOM更新样式
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
|
+
|
|
|
|
+ console.log('修改后容器高度:', container.offsetHeight);
|
|
|
|
+ pdfProgress.value = 30;
|
|
|
|
+
|
|
|
|
+ // 使用html2canvas捕获整个内容为一个大canvas
|
|
|
|
+ const canvas = await html2canvas(container, {
|
|
|
|
+ scale: 2, // 提高清晰度
|
|
|
|
+ useCORS: true,
|
|
|
|
+ allowTaint: true,
|
|
|
|
+ backgroundColor: '#ffffff',
|
|
|
|
+ windowHeight: container.scrollHeight, // 使用滚动高度
|
|
|
|
+ height: container.scrollHeight, // 设置捕获高度为滚动高度
|
|
|
|
+ width: container.offsetWidth,
|
|
|
|
+ scrollY: 0, // 禁用滚动捕获
|
|
|
|
+ scrollX: 0, // 禁用滚动捕获
|
|
|
|
+ logging: true, // 开启日志便于调试
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ console.log(`Canvas尺寸: ${canvas.width}x${canvas.height}`);
|
|
|
|
+ pdfProgress.value = 70;
|
|
|
|
+
|
|
|
|
+ // 恢复容器原始样式
|
|
|
|
+ container.style.border = originalStyles.border;
|
|
|
|
+ container.style.boxShadow = originalStyles.boxShadow;
|
|
|
|
+ container.style.borderRadius = originalStyles.borderRadius;
|
|
|
|
+ container.style.margin = originalStyles.margin;
|
|
|
|
+ container.style.padding = originalStyles.padding;
|
|
|
|
+ container.style.height = originalStyles.height;
|
|
|
|
+ container.style.maxHeight = originalStyles.maxHeight;
|
|
|
|
+ container.style.overflow = originalStyles.overflow;
|
|
|
|
+
|
|
|
|
+ // 创建PDF实例 - 使用自定义大小以适应内容
|
|
|
|
+ const imgWidth = 550; // A4宽度左右,减去边距
|
|
|
|
+ const imgHeight = canvas.height * imgWidth / canvas.width;
|
|
|
|
+
|
|
|
|
+ // 创建合适尺寸的PDF
|
|
|
|
+ const pdf = new jsPDF({
|
|
|
|
+ orientation: imgHeight > imgWidth ? 'portrait' : 'landscape',
|
|
|
|
+ unit: 'pt',
|
|
|
|
+ format: [imgWidth + 40, imgHeight + 40] // 添加边距
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 存储PDF引用
|
|
|
|
+ pdfDocument.value = pdf;
|
|
|
|
+
|
|
|
|
+ // 将canvas添加到PDF
|
|
|
|
+ pdf.addImage(
|
|
|
|
+ canvas.toDataURL('image/jpeg', 0.95),
|
|
|
|
+ 'JPEG',
|
|
|
|
+ 20, // 左边距
|
|
|
|
+ 20, // 上边距
|
|
|
|
+ imgWidth,
|
|
|
|
+ imgHeight
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ pdfProgress.value = 90;
|
|
|
|
+ console.log('PDF页面生成完成');
|
|
|
|
+
|
|
|
|
+ // 保存PDF (两种方式)
|
|
|
|
+ try {
|
|
|
|
+ // 方式1: 使用Blob URL (用于预览)
|
|
|
|
+ const pdfOutput = pdf.output('blob');
|
|
|
|
+ pdfUrl.value = URL.createObjectURL(pdfOutput);
|
|
|
|
+ console.log('PDF Blob URL创建成功:', pdfUrl.value);
|
|
|
|
+
|
|
|
|
+ // 方式2: 保存PDF字节 (用于直接下载)
|
|
|
|
+ const pdfData = pdf.output('arraybuffer');
|
|
|
|
+ pdfBytes.value = new Uint8Array(pdfData);
|
|
|
|
+ console.log('PDF字节数据创建成功, 大小:', pdfBytes.value.length);
|
|
|
|
+
|
|
|
|
+ // 转换为PDF模式
|
|
|
|
+ convertType.value = 'pdf';
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('创建PDF URL失败:', error);
|
|
|
|
+ alert('创建PDF URL失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ console.log('PDF生成完成');
|
|
|
|
+ pdfProgress.value = 100;
|
|
|
|
+
|
|
|
|
+ loading.value = false;
|
|
|
|
+ pdfProgress.value = 0; // 清除进度
|
|
|
|
+ return true;
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('PDF转换失败:', error);
|
|
|
|
+ alert('PDF转换失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
|
|
|
+ loading.value = false;
|
|
|
|
+ pdfProgress.value = 0; // 清除进度
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 下载PDF
|
|
|
|
+const downloadPdf = () => {
|
|
|
|
+ console.log('开始下载PDF...');
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // 方法1: 使用存储的PDF文档直接保存 (优先)
|
|
|
|
+ if (pdfDocument.value) {
|
|
|
|
+ const outputFilename = (fileName.value ? fileName.value.replace(/\.[^/.]+$/, '') : 'document') + '.pdf';
|
|
|
|
+ console.log('使用jsPDF直接保存文件:', outputFilename);
|
|
|
|
+ pdfDocument.value.save(outputFilename);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 方法2: 使用Blob URL (如果pdfDocument不可用)
|
|
|
|
+ if (pdfUrl.value) {
|
|
|
|
+ console.log('使用Blob URL下载PDF:', pdfUrl.value);
|
|
|
|
+ const link = document.createElement('a');
|
|
|
|
+ link.href = pdfUrl.value;
|
|
|
|
+ link.download = (fileName.value ? fileName.value.replace(/\.[^/.]+$/, '') : 'document') + '.pdf';
|
|
|
|
+ document.body.appendChild(link);
|
|
|
|
+ link.click();
|
|
|
|
+ document.body.removeChild(link);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 没有可用的PDF数据
|
|
|
|
+ console.error('没有PDF可下载');
|
|
|
|
+ alert('没有PDF可下载');
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('PDF下载失败:', error);
|
|
|
|
+ alert('PDF下载失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 下载图片
|
|
|
|
+const downloadImage = () => {
|
|
|
|
+ if (!imageUrl.value) {
|
|
|
|
+ alert('没有图片可下载');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const link = document.createElement('a');
|
|
|
|
+ link.href = imageUrl.value;
|
|
|
|
+ link.download = (fileName.value ? fileName.value.replace(/\.[^/.]+$/, '') : 'document') + '.png';
|
|
|
|
+ document.body.appendChild(link);
|
|
|
|
+ link.click();
|
|
|
|
+ document.body.removeChild(link);
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// 一键转为PDF
|
|
|
|
+const directToPdf = async () => {
|
|
|
|
+ try {
|
|
|
|
+ loading.value = true;
|
|
|
|
+ console.log('一键转换文档为PDF');
|
|
|
|
+
|
|
|
|
+ // 如果当前已经是HTML模式并且有HTML内容,直接转PDF
|
|
|
|
+ if (convertType.value === 'html' && htmlContent.value) {
|
|
|
|
+ console.log('直接从HTML转为PDF');
|
|
|
|
+ const success = await convertHtmlToPdf();
|
|
|
|
+
|
|
|
|
+ if (success) {
|
|
|
|
+ // 等待PDF生成完成后下载
|
|
|
|
+ console.log('HTML转PDF成功,准备下载');
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
+ downloadPdf();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ loading.value = false;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 否则,先转为HTML,再转为PDF
|
|
|
|
+ if (fileData.value) {
|
|
|
|
+ console.log('从Word文档转为HTML,再转为PDF');
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // 转换为HTML
|
|
|
|
+ const result = await mammoth.convertToHtml({ arrayBuffer: fileData.value });
|
|
|
|
+ htmlContent.value = result.value;
|
|
|
|
+ convertType.value = 'html';
|
|
|
|
+
|
|
|
|
+ // 等待DOM更新
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
+
|
|
|
|
+ // 然后转为PDF
|
|
|
|
+ const success = await convertHtmlToPdf();
|
|
|
|
+
|
|
|
|
+ if (success) {
|
|
|
|
+ // 等待PDF生成完成后下载
|
|
|
|
+ console.log('HTML转PDF成功,准备下载');
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
+ downloadPdf();
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('文档转换过程中出错:', error);
|
|
|
|
+ alert('文档转换失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ alert('请先上传Word文档');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ loading.value = false;
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('一键转PDF失败:', error);
|
|
|
|
+ alert('一键转PDF失败: ' + (error instanceof Error ? error.message : '未知错误'));
|
|
|
|
+ loading.value = false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// HTML相关方法
|
|
|
|
+const copyToClipboard = async () => {
|
|
|
|
+ if (!htmlContent.value) {
|
|
|
|
+ alert('没有HTML内容可复制');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ await navigator.clipboard.writeText(htmlContent.value);
|
|
|
|
+ alert('已复制到剪贴板');
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('复制失败:', error);
|
|
|
|
+ alert('复制失败');
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+onMounted(() => {
|
|
|
|
+ console.log('Test4Page mounted');
|
|
|
|
+});
|
|
|
|
+
|
|
|
|
+// 在组件销毁时清理资源
|
|
|
|
+onUnmounted(() => {
|
|
|
|
+ console.log('Test4Page unmounted, 清理资源');
|
|
|
|
+
|
|
|
|
+ // 清理Blob URL
|
|
|
|
+ if (pdfUrl.value) {
|
|
|
|
+ try {
|
|
|
|
+ URL.revokeObjectURL(pdfUrl.value);
|
|
|
|
+ console.log('已清理PDF Blob URL');
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('清理PDF Blob URL失败:', error);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (imageUrl.value) {
|
|
|
|
+ try {
|
|
|
|
+ URL.revokeObjectURL(imageUrl.value);
|
|
|
|
+ console.log('已清理图片Blob URL');
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('清理图片Blob URL失败:', error);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 清理其他引用
|
|
|
|
+ pdfDocument.value = null;
|
|
|
|
+ pdfBytes.value = null;
|
|
|
|
+ htmlContent.value = '';
|
|
|
|
+ fileData.value = null;
|
|
|
|
+});
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
+.test-container {
|
|
|
|
+ max-width: 800px;
|
|
|
|
+ margin: 0 auto;
|
|
|
|
+ padding: 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.test-header {
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.upload-section {
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.convert-type {
|
|
|
|
+ margin-top: 10px;
|
|
|
|
+ display: flex;
|
|
|
|
+ gap: 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.loading {
|
|
|
|
+ margin-top: 10px;
|
|
|
|
+ color: #666;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.result-container {
|
|
|
|
+ margin-top: 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.result-section {
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+ border-top: 1px solid #eee;
|
|
|
|
+ padding-top: 20px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+h4 {
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
+ color: #333;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.html-result {
|
|
|
|
+ padding: 15px;
|
|
|
|
+ border: 1px solid #eee;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ background-color: #fff;
|
|
|
|
+ min-height: 200px;
|
|
|
|
+ overflow: visible;
|
|
|
|
+ width: 100%;
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.image-container, .pdf-container {
|
|
|
|
+ border: 1px solid #ddd;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ background-color: #f9f9f9;
|
|
|
|
+ min-height: 200px;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.actions {
|
|
|
|
+ margin-top: 15px;
|
|
|
|
+ text-align: right;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.download-btn {
|
|
|
|
+ padding: 8px 16px;
|
|
|
|
+ background-color: #4CAF50;
|
|
|
|
+ color: white;
|
|
|
|
+ border: none;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.download-btn:hover {
|
|
|
|
+ background-color: #45a049;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.action-buttons {
|
|
|
|
+ margin-top: 20px;
|
|
|
|
+ text-align: right;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.convert-btn {
|
|
|
|
+ padding: 8px 16px;
|
|
|
|
+ background-color: #4CAF50;
|
|
|
|
+ color: white;
|
|
|
|
+ border: none;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ margin-left: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.convert-btn:hover {
|
|
|
|
+ background-color: #45a049;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.quick-actions {
|
|
|
|
+ margin-top: 10px;
|
|
|
|
+ text-align: right;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.action-btn {
|
|
|
|
+ padding: 8px 16px;
|
|
|
|
+ background-color: #4CAF50;
|
|
|
|
+ color: white;
|
|
|
|
+ border: none;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.action-btn:hover {
|
|
|
|
+ background-color: #45a049;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.image-result {
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+ width: 100%;
|
|
|
|
+ overflow: auto;
|
|
|
|
+
|
|
|
|
+ img {
|
|
|
|
+ max-width: 100%;
|
|
|
|
+ display: block;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.no-result {
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+ color: #666;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.pdf-result {
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+ width: 100%;
|
|
|
|
+ height: 600px;
|
|
|
|
+
|
|
|
|
+ iframe {
|
|
|
|
+ width: 100%;
|
|
|
|
+ height: 100%;
|
|
|
|
+ border: 1px solid #eee;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.spinner {
|
|
|
|
+ border: 4px solid rgba(0, 0, 0, 0.1);
|
|
|
|
+ border-left-color: #4CAF50;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ width: 30px;
|
|
|
|
+ height: 30px;
|
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
|
+ margin: 0 auto;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+@keyframes spin {
|
|
|
|
+ 0% {
|
|
|
|
+ transform: rotate(0deg);
|
|
|
|
+ }
|
|
|
|
+ 100% {
|
|
|
|
+ transform: rotate(360deg);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.progress {
|
|
|
|
+ margin-top: 10px;
|
|
|
|
+ color: #666;
|
|
|
|
+}
|
|
|
|
+</style>
|