廖井涛
90 分钟以前 f7a2fcdda7f1120498c5c5f75c5a99955fc54b43
north-glass-erp/northglass-erp/src/views/pp/glassOptimize/OptimizationRectPrint.vue
@@ -1,29 +1,54 @@
<template>
  <div>
    <el-button id="button" type="primary" @click="handlePrint" style="position: fixed; top: 90px; right: 20px; padding: 20px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">
      打印
<!--    <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>&nbsp;
      <el-checkbox v-model="config.plain">
        切材率
      </el-checkbox>
      <div style="margin-right: 30px;"></div>
      <span>布局选择:</span>
      <el-select v-model="config.type"  style="width: 120px;">
        <el-option v-for="type in linkTypes" :key="type" :value="type" />
      </el-select>
      <el-button id="button" type="primary" @click="handlePrint" style="background: #4CAF50; color: white; border: none; cursor: pointer;">
        预览
      </el-button>
    </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
      <RectRenderer
        ref="rectRenderer"
        :layoutData="layoutData"
        :layoutData="layoutData"
        :gw="currentGw"
        :gh="currentGh"
        :printLayout="printLayout"
@@ -44,6 +69,11 @@
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,
@@ -54,11 +84,26 @@
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 linkTypes = ['一列', '两列', '三列']
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 = {
@@ -75,13 +120,15 @@
const currentPrintHeight = ref(layoutDimensions[printLayout.value].height);
const selectLayout = () => {
  request.post(`/glassOptimize/selectOptimizeResult/${processId.value}`)
  request.post(`/glassOptimize/getOptimizeInfo/${processId.value}`)
    .then((res) => {
      if (res.code == 200) {
        try {
          layoutData.value = JSON.parse(res.data.data[0].Layouts);
          materialDetails.value=res.data.optimizeUse;
          dataLoaded.value=true;
          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);
        }
@@ -94,9 +141,344 @@
    });
};
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({
  type: '两列',
  columnTypes: '两列',
  rowTypes: '两行',
  plain: true,
  printLayouts: true,
  printReport: true,
  glassInfo: '显示在下侧',
  cutInfo: '显示'
})
onMounted(() => {
@@ -112,7 +494,12 @@
  if (processId.value) {
    selectLayout();
    selectReportData();
    selectMaterialData();
    selectProductData();
    fetchSettings(username);
  }
});
const handlePrint = () => {
@@ -128,7 +515,7 @@
  currentGh.value = dimensions.height;
  currentPrintWidth.value = dimensions.width;
  currentPrintHeight.value = dimensions.height;
  if (rectRenderer.value) {
    rectRenderer.value.updateLayout();
  }
@@ -139,3 +526,56 @@
  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>