refactor(contractList): 移除旧的PDF导出逻辑,改用浏览器原生打印
This commit is contained in:
@@ -156,7 +156,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<span slot="footer">
|
<span slot="footer">
|
||||||
<el-button @click="exportDialogVisible = false">取 消</el-button>
|
<el-button @click="exportDialogVisible = false">取 消</el-button>
|
||||||
<el-button type="primary" :loading="exportLoading" @click="confirmExport">确认导出</el-button>
|
<el-button type="primary" @click="confirmExport">确认导出</el-button>
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
@@ -164,8 +164,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { listOrder, updateOrder } from "@/api/crm/order";
|
import { listOrder, updateOrder } from "@/api/crm/order";
|
||||||
import html2canvas from 'html2canvas';
|
|
||||||
import jsPDF from 'jspdf';
|
|
||||||
import contractLogo from '@/assets/images/contractLogo.png';
|
import contractLogo from '@/assets/images/contractLogo.png';
|
||||||
import {
|
import {
|
||||||
parseProductContent,
|
parseProductContent,
|
||||||
@@ -204,7 +202,6 @@ export default {
|
|||||||
},
|
},
|
||||||
// 导出预览
|
// 导出预览
|
||||||
exportDialogVisible: false,
|
exportDialogVisible: false,
|
||||||
exportLoading: false,
|
|
||||||
exportRow: null,
|
exportRow: null,
|
||||||
selectAllColumns: true,
|
selectAllColumns: true,
|
||||||
columnIndeterminate: false,
|
columnIndeterminate: false,
|
||||||
@@ -434,6 +431,12 @@ export default {
|
|||||||
.sign-section { margin-top: 24px; font-size: 12px; line-height: 2.2; }
|
.sign-section { margin-top: 24px; font-size: 12px; line-height: 2.2; }
|
||||||
.sign-section .col { width: 48%; }
|
.sign-section .col { width: 48%; }
|
||||||
.sign-row { display: flex; justify-content: space-between; }
|
.sign-row { display: flex; justify-content: space-between; }
|
||||||
|
@media print {
|
||||||
|
@page { size: A4; margin: 10mm; }
|
||||||
|
body { background: #fff; padding: 0; display: block; }
|
||||||
|
.a4-page { box-shadow: none; margin: 0; }
|
||||||
|
table tr { page-break-inside: avoid; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -490,190 +493,13 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
/** 确认导出PDF */
|
/** 确认导出PDF - 借助浏览器原生打印 */
|
||||||
async confirmExport() {
|
confirmExport() {
|
||||||
const row = this.exportRow;
|
const iframe = this.$refs.previewFrame;
|
||||||
if (!row) return;
|
if (iframe && iframe.contentWindow) {
|
||||||
|
iframe.contentWindow.print();
|
||||||
this.exportLoading = true;
|
|
||||||
const loading = this.$loading({
|
|
||||||
lock: true,
|
|
||||||
text: '正在生成PDF,请稍候...',
|
|
||||||
background: 'rgba(0, 0, 0, 0.7)'
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
let productData = parseProductContent(row.productContent);
|
|
||||||
if (!productData.productName && row.productName) {
|
|
||||||
productData.productName = row.productName;
|
|
||||||
}
|
|
||||||
const products = productData.products && productData.products.length > 0 ? productData.products : [];
|
|
||||||
|
|
||||||
const productTableHtml = this.buildProductTableHtml(productData, products);
|
|
||||||
|
|
||||||
// 解析合同内容
|
|
||||||
let contractContentHtml = '';
|
|
||||||
if (row.contractContent) {
|
|
||||||
let htmlContent = row.contractContent;
|
|
||||||
const pTagRegex = /<p[^>]*>([\s\S]*?)<\/p>/g;
|
|
||||||
let match;
|
|
||||||
const pContents = [];
|
|
||||||
while ((match = pTagRegex.exec(htmlContent)) !== null) {
|
|
||||||
let content = match[1].replace(/<[^>]*>/g, '').replace(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, "'").trim();
|
|
||||||
if (content) {
|
|
||||||
const chineseNumberRegex = /^[一二三四五六七八九十]+、/;
|
|
||||||
pContents.push(chineseNumberRegex.test(content) ? content : ' ' + content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pContents.length === 0) {
|
|
||||||
const textContent = htmlContent.replace(/<[^>]*>/g, '').replace(/ /g, ' ').trim();
|
|
||||||
if (textContent) pContents.push(textContent);
|
|
||||||
}
|
|
||||||
if (pContents.length > 0) {
|
|
||||||
contractContentHtml = '<div style="margin-top:10px;font-size:12px;line-height:1.8;">' + pContents.join('<br/>') + '</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalTax = products.reduce((a, p) => a + (parseFloat(p.taxTotal) || 0), 0);
|
|
||||||
const amountWords = productData.totalAmountInWords || convertToChinese(totalTax) || '零元整';
|
|
||||||
|
|
||||||
// 构建完整的合同HTML(794px 宽,适合A4)
|
|
||||||
const contractHtml = `
|
|
||||||
<div id="contract-pdf-content" style="width:794px;padding:30px 40px;font-family:'SimSun','宋体',serif;color:#000;background:#fff;box-sizing:border-box;position:relative;">
|
|
||||||
<img src="${contractLogo}" style="position:absolute;left:20px;top:20px;height:80px;" crossorigin="anonymous" />
|
|
||||||
<div style="text-align:center;padding-top:10px;">
|
|
||||||
<div style="font-size:20px;font-weight:bold;letter-spacing:2px;">嘉祥科伦普重工有限公司</div>
|
|
||||||
</div>
|
|
||||||
<div style="position:relative;margin:12px 0 8px 0;text-align:center;">
|
|
||||||
<div style="font-size:22px;font-weight:bold;letter-spacing:6px;">产 品 销 售 合 同</div>
|
|
||||||
<div style="position:absolute;right:0;top:50%;transform:translateY(-50%);font-size:11px;">合同编号:${row.contractCode || ''}</div>
|
|
||||||
</div>
|
|
||||||
<div style="display:flex;justify-content:space-between;margin-bottom:8px;font-size:13px;line-height:2;">
|
|
||||||
<div style="width:55%;">
|
|
||||||
<div>供方(甲方):${row.supplier || ''}</div>
|
|
||||||
<div>需方(乙方):${row.customer || ''}</div>
|
|
||||||
</div>
|
|
||||||
<div style="width:40%;text-align:left;">
|
|
||||||
<div>签订时间:${row.signTime ? this.parseTime(row.signTime, '{y}年{m}月{d}日') : ''}</div>
|
|
||||||
<div>签订地点:${row.signLocation || ''}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="font-size:13px;font-weight:bold;margin-bottom:5px;">一、产品内容</div>
|
|
||||||
${productTableHtml}
|
|
||||||
${contractContentHtml}
|
|
||||||
<div style="margin-top:40px;font-size:12px;line-height:2.2;">
|
|
||||||
<div style="display:flex;justify-content:space-between;">
|
|
||||||
<div style="width:48%;">
|
|
||||||
<div>供方(甲方):${row.supplier || ''}</div>
|
|
||||||
<div>地址:${row.supplierAddress || ''}</div>
|
|
||||||
<div>电话:${row.supplierPhone || ''}</div>
|
|
||||||
<div>开户行:${row.supplierBank || ''}</div>
|
|
||||||
<div>账号:${row.supplierAccount || ''}</div>
|
|
||||||
<div>税号:${row.supplierTaxNo || ''}</div>
|
|
||||||
</div>
|
|
||||||
<div style="width:48%;">
|
|
||||||
<div>需方(乙方):${row.customer || ''}</div>
|
|
||||||
<div>地址:${row.customerAddress || ''}</div>
|
|
||||||
<div>电话:${row.customerPhone || ''}</div>
|
|
||||||
<div>开户行:${row.customerBank || ''}</div>
|
|
||||||
<div>账号:${row.customerAccount || ''}</div>
|
|
||||||
<div>税号:${row.customerTaxNo || ''}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const container = document.createElement('div');
|
|
||||||
container.style.position = 'fixed';
|
|
||||||
container.style.left = '-9999px';
|
|
||||||
container.style.top = '0';
|
|
||||||
container.innerHTML = contractHtml;
|
|
||||||
document.body.appendChild(container);
|
|
||||||
|
|
||||||
const element = document.getElementById('contract-pdf-content');
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
|
|
||||||
const canvas = await html2canvas(element, {
|
|
||||||
scale: 2,
|
|
||||||
useCORS: true,
|
|
||||||
allowTaint: true,
|
|
||||||
logging: false,
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
imageTimeout: 5000
|
|
||||||
});
|
|
||||||
|
|
||||||
document.body.removeChild(container);
|
|
||||||
|
|
||||||
const pdf = new jsPDF('p', 'mm', 'a4');
|
|
||||||
const pdfWidth = 210;
|
|
||||||
const pdfHeight = 297;
|
|
||||||
const margin = 5;
|
|
||||||
const contentWidth = pdfWidth - margin * 2;
|
|
||||||
const contentHeight = pdfHeight - margin * 2;
|
|
||||||
const scale = contentWidth / canvas.width;
|
|
||||||
|
|
||||||
function isBlankRow(canvas, y) {
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
const row = ctx.getImageData(0, y, canvas.width, 1).data;
|
|
||||||
let whiteCount = 0;
|
|
||||||
const total = row.length / 4;
|
|
||||||
for (let i = 0; i < row.length; i += 4) {
|
|
||||||
if (row[i] > 250 && row[i + 1] > 250 && row[i + 2] > 250) whiteCount++;
|
|
||||||
}
|
|
||||||
return whiteCount / total >= 0.95;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findBreakY(canvas, targetY, range) {
|
|
||||||
range = range || 40;
|
|
||||||
for (let offset = 0; offset < range; offset++) {
|
|
||||||
if (targetY + offset < canvas.height && isBlankRow(canvas, targetY + offset)) return targetY + offset;
|
|
||||||
if (targetY - offset > 0 && isBlankRow(canvas, targetY - offset)) return targetY - offset;
|
|
||||||
}
|
|
||||||
return targetY;
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentY = 0;
|
|
||||||
let firstPage = true;
|
|
||||||
|
|
||||||
while (currentY < canvas.height) {
|
|
||||||
const pageHeightPx = Math.round(contentHeight / scale);
|
|
||||||
let endY = Math.min(currentY + pageHeightPx, canvas.height);
|
|
||||||
|
|
||||||
if (endY < canvas.height) {
|
|
||||||
endY = findBreakY(canvas, endY);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sliceHeight = endY - currentY;
|
|
||||||
const pageCanvas = document.createElement('canvas');
|
|
||||||
pageCanvas.width = canvas.width;
|
|
||||||
pageCanvas.height = sliceHeight;
|
|
||||||
pageCanvas.getContext('2d').drawImage(canvas, 0, currentY, canvas.width, sliceHeight, 0, 0, canvas.width, sliceHeight);
|
|
||||||
|
|
||||||
const imgData = pageCanvas.toDataURL('image/jpeg', 0.95);
|
|
||||||
const pageImgHeight = sliceHeight * scale;
|
|
||||||
|
|
||||||
if (firstPage) {
|
|
||||||
pdf.addImage(imgData, 'JPEG', margin, margin, contentWidth, pageImgHeight);
|
|
||||||
firstPage = false;
|
|
||||||
} else {
|
|
||||||
pdf.addPage();
|
|
||||||
pdf.addImage(imgData, 'JPEG', margin, margin, contentWidth, pageImgHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentY = endY;
|
|
||||||
}
|
|
||||||
|
|
||||||
pdf.save(`合同_${row.contractCode || row.contractName || '未命名'}.pdf`);
|
|
||||||
this.$message.success('PDF导出成功');
|
|
||||||
this.exportDialogVisible = false;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('PDF导出失败:', error);
|
|
||||||
this.$message.error('PDF导出失败,请重试');
|
|
||||||
} finally {
|
|
||||||
loading.close();
|
|
||||||
this.exportLoading = false;
|
|
||||||
}
|
}
|
||||||
|
this.exportDialogVisible = false;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user