<template>
|
<div>
|
<!-- <el-button id="button" type="primary" @click="printReports" style="position: fixed; top: 90px; right: 20px; padding: 20px; background: #409eff; color: white; border: none; border-radius: 5px; cursor: pointer;">-->
|
<!-- 打印-->
|
<!-- </el-button>-->
|
<el-button
|
id="button"
|
type="primary"
|
@click="previewReport"
|
:loading="loading" style="position: fixed; top: 90px; right: 40px; padding: 20px; background: #409eff; color: white; border: none; border-radius: 5px; cursor: pointer;"
|
:disabled="loading">
|
{{ loading ? '生成中...' : '预览' }}
|
</el-button>
|
|
<div style="display: flex; align-items: center; gap: 20px; margin-bottom: 20px;">
|
<span>工程编号:</span>
|
<el-input readonly placeholder="" style="width: 150px" v-model="processId"></el-input>
|
</div>
|
|
<div v-if="pdfUrl || pdfLoading" style="margin-top: 20px;">
|
<!-- PDF加载时显示美化后的加载动画 -->
|
<div v-if="pdfLoading" style="display: flex; justify-content: center; align-items: center; height: 800px; border: 1px solid #ddd; background-color: #f5f5f5;">
|
<div style="text-align: center;">
|
<div class="loading-animation">
|
<div class="dot-flashing"></div>
|
</div>
|
<p style="margin-top: 20px; font-size: 18px; color: #666;">PDF文档加载中...</p>
|
<p style="font-size: 14px; color: #999; margin-top: 10px;">正在为您准备预览内容</p>
|
</div>
|
</div>
|
<!-- PDF加载完成时显示PDF -->
|
<iframe
|
v-else
|
:src="pdfUrl" style="width: 100%; height: 780px; border: 1px solid #ddd;"
|
title="PDF预览">
|
</iframe>
|
</div>
|
|
<div v-else style="margin-top: 20px;">
|
<div style="display: flex; justify-content: center; align-items: center; height: 780px; border: 1px solid #ddd; background-color: white;">
|
<div style="text-align: center; color: #999;">
|
<p style="font-size: 20px; margin-bottom: 10px;">优化报告预览区</p>
|
<p style="font-size: 14px;">点击上方"预览"按钮生成并查看优化报告</p>
|
</div>
|
</div>
|
</div>
|
|
<div ref="printContainer" style="position: relative;">
|
<RectRenderer
|
ref="rectRenderer"
|
:layoutData="layoutData"
|
:gw="currentGw"
|
:gh="currentGh"
|
:printLayout="printLayout"
|
:printWidth="currentPrintWidth"
|
:printHeight="currentPrintHeight"
|
:materialDetails="materialDetails"
|
:state="state"
|
:projectNo="processId"
|
style=""
|
v-if="dataLoaded"
|
/>
|
</div>
|
</div>
|
|
</template>
|
|
<script setup>
|
import { ref, onMounted, watch, reactive, inject } from 'vue';
|
import RectRenderer from './page/RectRenderer.vue';
|
import request from "@/utils/request";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
import requestOptimize from "@/utils/requestOptimize";
|
import useUserInfoStore from "@/stores/userInfo";
|
|
const userStore = useUserInfoStore();
|
|
const props = defineProps({
|
project : null,
|
state : null
|
});
|
const printLayout = ref('2rows-2cols');
|
const rectRenderer = ref(null);
|
const savedProjectNo = localStorage.getItem('projectNo');
|
const processId = ref('');
|
const layoutData = ref();
|
const optimizeUse = ref();
|
const reportData = ref();
|
const materialList = ref();
|
const productList = ref();
|
const dataLoaded = ref(false);
|
const materialDetails = ref();
|
const injectedProjectNo = inject('projectNo', null);
|
const state = ref();
|
const filePath = ref('');
|
const loading = ref(false);
|
const pdfLoading = ref(false);
|
const printLayouts = ref();
|
const printReport = ref();
|
const layoutRows = ref();
|
const layoutColumns = ref();
|
const glassInfoShow = ref();
|
const cutInfoShow = ref();
|
const username = userStore.user.userName;
|
|
const pdfUrl = ref('');
|
|
// 定义不同布局对应的尺寸
|
const layoutDimensions = {
|
'4rows-2cols': { width: 1000, height: 1000 },
|
'3rows-2cols': { width: 1000, height: 1000 },
|
'3rows-1col': { width: 1000, height: 1000 },
|
'2rows-2cols': { width: 1400, height: 800 }
|
};
|
|
// 当前布局的尺寸
|
const currentGw = ref(layoutDimensions[printLayout.value].width);
|
const currentGh = ref(layoutDimensions[printLayout.value].height);
|
const currentPrintWidth = ref(layoutDimensions[printLayout.value].width);
|
const currentPrintHeight = ref(layoutDimensions[printLayout.value].height);
|
|
const selectLayout = () => {
|
request.post(`/glassOptimize/getOptimizeInfo/${processId.value}`)
|
.then((res) => {
|
if (res.code == 200) {
|
try {
|
layoutData.value = res.data.layouts;
|
optimizeUse.value=res.data.optimizeUse[0];
|
// 添加控制台输出
|
console.log('layoutData:', layoutData.value);
|
console.log('optimizeUse:', optimizeUse.value);
|
} catch (error) {
|
console.error("解析布局数据失败:", error);
|
}
|
} else {
|
console.error("请求失败,状态码:", res.code);
|
}
|
})
|
.catch((error) => {
|
console.error("请求失败:", error);
|
});
|
};
|
|
const selectReportData= () => {
|
request.post(`/glassOptimize/getReportData/${processId.value}`)
|
.then((res) => {
|
if (res.code == 200) {
|
try {
|
reportData.value = res.data.reportData[0];
|
console.log('reportData:', reportData.value);
|
} catch (error) {
|
console.error("解析布局数据失败:", error);
|
}
|
} else {
|
console.error("请求失败,状态码:", res.code);
|
}
|
})
|
.catch((error) => {
|
console.error("请求失败:", error);
|
});
|
};
|
|
const fetchSettings = async (username) => {
|
try {
|
const response = await request.post(`/glassOptimize/selectOptimizeParms/${username}`);
|
if (response.code == 200) {
|
if (!response.data) {
|
console.error('响应数据为空');
|
return;
|
}
|
const parsedData = JSON.parse(response.data);
|
console.log('设置内容:', parsedData);
|
printLayouts.value = parsedData.server.printLayouts;
|
printReport.value = parsedData.server.printReport;
|
layoutRows.value = parsedData.server.layoutRows;
|
layoutColumns.value = parsedData.server.layoutColumns;
|
glassInfoShow.value = parsedData.server.glassInfoShow;
|
cutInfoShow.value = parsedData.server.cutInfoShow;
|
} else {
|
console.error('请求失败,状态码:', response.code);
|
}
|
} catch (error) {
|
console.error('请求发生错误:', error);
|
}
|
};
|
|
|
const selectMaterialData= () => {
|
request.post(`/glassOptimize/materialInfo/${processId.value}`)
|
.then((res) => {
|
if (res.code == 200) {
|
try {
|
materialList.value = res.data.materialList;
|
console.log('materialList:', materialList.value);
|
} catch (error) {
|
console.error("解析布局数据失败:", error);
|
}
|
} else {
|
console.error("请求失败,状态码:", res.code);
|
}
|
})
|
.catch((error) => {
|
console.error("请求失败:", error);
|
});
|
};
|
|
const selectProductData= () => {
|
request.post(`/glassOptimize/getProductList/${processId.value}`)
|
.then((res) => {
|
if (res.code == 200) {
|
try {
|
productList.value = res.data.productList;
|
console.log('productList:', productList.value);
|
} catch (error) {
|
console.error("解析布局数据失败:", error);
|
}
|
} else {
|
console.error("请求失败,状态码:", res.code);
|
}
|
})
|
.catch((error) => {
|
console.error("请求失败:", error);
|
});
|
};
|
|
const generateReport = async() => {
|
try {
|
// 确保有数据可以提交
|
if (!processId) {
|
ElMessage.warning('没有可打印的数据');
|
return;
|
}
|
|
const response = await requestOptimize.post('/api/reports', {
|
fileName: processId.value,
|
projectNo: processId.value,
|
companyName : '1',
|
glassThickness : optimizeUse.value.thickness,
|
glassType : optimizeUse.value.model,
|
quantity : String(optimizeUse.value.processingQuantity),
|
printLayouts : printLayouts.value || '0',
|
printReport : printReport.value || '0',
|
layouts : layoutData.value,
|
reportData:{
|
rectangleQuantity: reportData.value.rectangleQuantity,
|
otherShapeQuantity: reportData.value.otherShapeQuantity,
|
rectangleArea: reportData.value.rectangleArea,
|
otherShapeArea: reportData.value.otherShapeArea,
|
rectanglePerimeter: reportData.value.rectanglePerimeter,
|
otherShapePerimeter: reportData.value.otherShapePerimeter,
|
materialList: materialList.value,
|
productList: productList.value,
|
},
|
layoutSet: {
|
layoutRows: parseInt(layoutRows.value) || 2,
|
layoutColumns: parseInt(layoutColumns.value) || 2,
|
glassInfoShow: parseInt(glassInfoShow.value) || 0,
|
cutInfoShow: parseInt(cutInfoShow.value) || 0
|
}
|
|
}, {
|
headers: {
|
'Content-Type': 'application/json'
|
}
|
});
|
|
if (response.code == 200) {
|
ElMessage.success('保存成功');
|
filePath.value = response.data[0];
|
console.log('filePath:', filePath.value);
|
} else {
|
ElMessage.error('保存失败,请稍后再试');
|
}
|
|
} catch (error) {
|
console.error('保存失败:', error);
|
ElMessage.error('保存失败,请稍后再试');
|
}
|
};
|
|
const printReports = async () => {
|
try {
|
await generateReport();
|
if (!filePath.value) {
|
ElMessage.error('未收到有效的PDF文件路径');
|
return;
|
}
|
const encodedFilePath = encodeURIComponent(filePath.value);
|
|
const response = await request.get('/glassOptimize/reports/pdf', {
|
params: { filePath: encodedFilePath },
|
responseType: 'blob',
|
headers: {
|
'Accept': 'application/pdf'
|
}
|
});
|
|
// 检查响应数据是否存在且有效
|
if (!response) {
|
ElMessage.error('未能获取到PDF数据');
|
return;
|
}
|
|
const blob = new Blob([response], { type: 'application/pdf' });
|
|
// 检查 blob 是否有效
|
if (blob.size === 0) {
|
ElMessage.error('接收到空的PDF文件');
|
return;
|
}
|
|
const url = URL.createObjectURL(blob);
|
|
// 创建隐藏的 iframe
|
const iframe = document.createElement('iframe');
|
iframe.style.position = 'fixed';
|
iframe.style.left = '0';
|
iframe.style.top = '0';
|
iframe.style.width = '0';
|
iframe.style.height = '0';
|
iframe.style.border = 'none';
|
iframe.src = url;
|
|
// 标记是否已经清理过资源
|
let isCleanedUp = false;
|
// 清理资源函数
|
const cleanup = () => {
|
if (isCleanedUp) return;
|
isCleanedUp = true;
|
URL.revokeObjectURL(url);
|
if (iframe.parentNode) {
|
iframe.parentNode.removeChild(iframe);
|
}
|
};
|
|
iframe.onload = () => {
|
setTimeout(() => {
|
try {
|
// 确保 iframe 内容已加载完成
|
if (iframe.contentWindow) {
|
iframe.contentWindow.focus();
|
|
// 监听打印事件(某些浏览器支持)
|
const handleAfterPrint = () => {
|
window.removeEventListener('afterprint', handleAfterPrint);
|
// 延迟清理,确保打印完成
|
setTimeout(cleanup, 3000);
|
};
|
|
window.addEventListener('afterprint', handleAfterPrint);
|
|
// 执行打印
|
iframe.contentWindow.print();
|
ElMessage.success('报告生成成功,已启动打印...');
|
|
// 如果浏览器不支持 afterprint 事件,设置超时清理
|
setTimeout(cleanup, 10000);
|
}
|
} catch (printError) {
|
console.error('打印过程中出错:', printError);
|
// 如果程序化打印失败,至少打开 PDF 供用户手动打印
|
window.open(url, '_blank');
|
ElMessage.info('已打开PDF文件,请手动打印');
|
cleanup();
|
}
|
}, 1000); // 给更多时间确保PDF完全渲染
|
};
|
|
iframe.onerror = () => {
|
console.error('PDF 加载失败');
|
ElMessage.error('PDF 文件加载失败,请重试');
|
cleanup();
|
};
|
|
document.body.appendChild(iframe);
|
|
} catch (error) {
|
console.error('打印流程异常:', error);
|
|
if (error.response) {
|
const status = error.response.status;
|
if (status === 400) {
|
ElMessage.error('文件路径无效或不是PDF');
|
} else if (status === 404) {
|
ElMessage.error('PDF 文件不存在');
|
} else {
|
ElMessage.error(`服务器错误 (${status}),请稍后再试`);
|
}
|
} else if (error.request) {
|
ElMessage.error('网络错误,请检查连接');
|
} else {
|
ElMessage.error('打印失败,请稍后再试');
|
}
|
}
|
};
|
|
|
|
const previewReport = async () => {
|
try {
|
loading.value = true;
|
ElMessage.info('正在生成报告,请稍候...')
|
await generateReport();
|
if (!filePath.value) {
|
ElMessage.error('未收到有效的PDF文件路径');
|
loading.value = false;
|
return;
|
}
|
const encodedFilePath = encodeURIComponent(filePath.value);
|
|
// 设置PDF加载状态为true
|
pdfLoading.value = true;
|
|
const response = await request.get('/glassOptimize/reports/pdf', {
|
params: { filePath: encodedFilePath },
|
responseType: 'blob',
|
headers: {
|
'Accept': 'application/pdf'
|
}
|
});
|
|
// 检查响应数据是否存在且有效
|
if (!response) {
|
ElMessage.error('未能获取到PDF数据');
|
loading.value = false;
|
pdfLoading.value = false; // 设置PDF加载状态为false
|
return;
|
}
|
|
const blob = new Blob([response], { type: 'application/pdf' });
|
|
// 检查 blob 是否有效
|
if (blob.size === 0) {
|
ElMessage.error('接收到空的PDF文件');
|
loading.value = false;
|
pdfLoading.value = false; // 设置PDF加载状态为false
|
return;
|
}
|
|
// 创建PDF URL并赋值给pdfUrl用于界面显示
|
const url = URL.createObjectURL(blob);
|
pdfUrl.value = url;
|
|
ElMessage.success('报告生成成功,正在预览...');
|
loading.value = false;
|
|
// 延迟一小段时间后设置PDF加载完成,确保用户能看到加载状态
|
setTimeout(() => {
|
pdfLoading.value = false;
|
}, 500);
|
|
} catch (error) {
|
console.error('预览流程异常:', error);
|
loading.value = false;
|
pdfLoading.value = false; // 设置PDF加载状态为false
|
if (error.response) {
|
const status = error.response.status;
|
if (status === 400) {
|
ElMessage.error('文件路径无效或不是PDF');
|
} else if (status === 404) {
|
ElMessage.error('PDF 文件不存在');
|
} else {
|
ElMessage.error(`服务器错误 (${status}),请稍后再试`);
|
}
|
} else if (error.request) {
|
ElMessage.error('网络错误,请检查连接');
|
} else {
|
ElMessage.error('预览失败,请稍后再试');
|
}
|
}
|
};
|
|
const config = reactive({
|
columnTypes: '两列',
|
rowTypes: '两行',
|
plain: true,
|
printLayouts: true,
|
printReport: true,
|
glassInfo: '显示在下侧',
|
cutInfo: '显示'
|
|
})
|
|
onMounted(() => {
|
// 优先使用注入的 projectNo,其次使用 props,最后使用 localStorage
|
if (injectedProjectNo) {
|
processId.value = injectedProjectNo.value || injectedProjectNo;
|
} else if (props.project) {
|
processId.value = props.project.projectNumber || '';
|
state.value = props.state;
|
} else if (savedProjectNo) {
|
processId.value = savedProjectNo;
|
}
|
|
if (processId.value) {
|
selectLayout();
|
selectReportData();
|
selectMaterialData();
|
selectProductData();
|
fetchSettings(username);
|
}
|
|
});
|
|
const handlePrint = () => {
|
if (rectRenderer.value) {
|
rectRenderer.value.print();
|
}
|
};
|
|
const handleLayoutChange = () => {
|
// 更新布局尺寸
|
const dimensions = layoutDimensions[printLayout.value];
|
currentGw.value = dimensions.width;
|
currentGh.value = dimensions.height;
|
currentPrintWidth.value = dimensions.width;
|
currentPrintHeight.value = dimensions.height;
|
|
if (rectRenderer.value) {
|
rectRenderer.value.updateLayout();
|
}
|
};
|
|
// 监听布局变化
|
watch(printLayout, (newVal) => {
|
handleLayoutChange();
|
});
|
</script>
|
|
<style scoped>
|
.loading-animation {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
}
|
|
.dot-flashing {
|
position: relative;
|
width: 10px;
|
height: 10px;
|
border-radius: 5px;
|
background-color: #409eff;
|
color: #409eff;
|
animation: dotFlashing 1s infinite linear alternate;
|
animation-delay: .5s;
|
}
|
|
.dot-flashing::before, .dot-flashing::after {
|
content: '';
|
display: inline-block;
|
position: absolute;
|
top: 0;
|
width: 10px;
|
height: 10px;
|
border-radius: 5px;
|
background-color: #409eff;
|
color: #409eff;
|
}
|
|
.dot-flashing::before {
|
left: -15px;
|
animation: dotFlashing 1s infinite alternate;
|
animation-delay: 0s;
|
}
|
|
.dot-flashing::after {
|
left: 15px;
|
animation: dotFlashing 1s infinite alternate;
|
animation-delay: 1s;
|
}
|
|
@keyframes dotFlashing {
|
0% {
|
background-color: #409eff;
|
}
|
50%,
|
100% {
|
background-color: #c0d9f7;
|
}
|
}
|
</style>
|