【vue】调用同页面其他组件方法几种新思路

该方法适合复杂界面分成多个组件,组件里面又嵌套子组件,且要触发的方法组参复杂,比如我这个就涉及到canvas转html转pdf的报告导出功能。

方案1:使用window挂载目标方法(简单快捷)

父组件:

javascript 复制代码
const handlePowerSwitch = async (on) => {
  // 单机版停止调度时记录仿真报告
  if (appStore.features === 'emulator' && !on) {
    // 直接调用孙子组件的方法,等待完成
    if (window.exportSimulationReport) {
      await window.exportSimulationReport();
    }
  }
  
  const res = await appStore.changePowerAction(on);
  if (!res.success) {
    appStore.power_action = !on;
  } 
};

孙子组件:

javascript 复制代码
// 提供全局方法供父组件调用
const exportSimulationReport = async () => {
  if (exporting.value) return;
  
  try {
    exporting.value = true;
    
    // 准备地图信息数据
    const blocks = Models.allBlocks();
    const mapStats = {
      total: Object.keys(blocks).length,
      charger: 0,
      entry: 0,
      exit: 0,
      wait: 0,
      queue: 0,
      sorter: 0,
    };

    // 使用更高效的方式统计
    Object.values(blocks).forEach(block => {
      const statsMap = {
        'chargerPort': 'charger',
        'loadPort': 'entry', 
        'unloadPort': 'exit',
        'waitPort': 'wait',
        'queuePort': 'queue',
        'sorterPort': 'sorter'
      };
      if (statsMap[block.tag]) {
        mapStats[statsMap[block.tag]]++;
      }
    });

    // 更新地图信息数据
    mapInfoData.value = [
      { label: 'scene.report.total_blocks', value: mapStats.total },
      { label: 'scene.report.charger_count', value: mapStats.charger },
      { label: 'scene.report.entry_count', value: mapStats.entry },
      { label: 'scene.report.exit_count', value: mapStats.exit },
      { label: 'scene.report.wait_count', value: mapStats.wait },
      { label: 'scene.report.queue_count', value: mapStats.queue },
      { label: 'scene.report.sorter_count', value: mapStats.sorter },
    ];

    // 生成地图 SVG 数据
    const svgData = generateMapSVG(blocks, t);
    mapSvgData.value = svgData;

    // 使用微任务等待渲染更新
    await nextTick();
    
    // 添加延迟确保渲染完成
    await new Promise(resolve => requestAnimationFrame(() => 
      setTimeout(resolve, 100)
    ));

    // 生成报告
    await generatePDFReport(mapStats);
    
  } catch (error) {
    console.error('导出报告失败:', error);
    throw error; // 向上传递错误
  } finally {
    exporting.value = false;
  }
};

// 将PDF生成分离为独立函数
const generatePDFReport = async (mapStats) => {
  return new Promise(async (resolve, reject) => {
    try {
      const template = document.getElementById('report-template');
      const canvas = await html2canvas(template, {
        scale: 1.5, // 降低缩放比例提高性能
        useCORS: true,
        logging: false,
        backgroundColor: '#ffffff',
        async: true, // 启用异步渲染
        removeContainer: true, // 清理临时容器
        onclone: (clonedDoc) => {
          const style = clonedDoc.createElement('style');
          style.innerHTML = `
            @font-face {
              font-family: 'Microsoft YaHei';
              src: local('Microsoft YaHei'), local('PingFang SC'), local('SimHei');
            }
            * {
              font-family: 'Microsoft YaHei', 'PingFang SC', 'SimHei', Arial, sans-serif !important;
            }
            svg {
              width: 100% !important;
              height: auto !important;
              max-height: 400px !important;
            }
          `;
          clonedDoc.head.appendChild(style);
        },
      });

      const imgData = canvas.toDataURL('image/jpeg', 0.9); // 降低图片质量

      // 创建 PDF
      const pdf = new jsPDF({
        orientation: 'portrait',
        unit: 'mm',
        format: 'a4',
      });

      const pageWidth = pdf.internal.pageSize.width;
      const pageHeight = pdf.internal.pageSize.height;
      const imgWidth = pageWidth;
      const imgHeight = (canvas.height * imgWidth) / canvas.width;

      // 分页处理
      let heightLeft = imgHeight;
      let position = 0;

      pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
      heightLeft -= pageHeight;

      while (heightLeft > 0) {
        position = heightLeft - imgHeight;
        pdf.addPage();
        pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
        heightLeft -= pageHeight;
      }

      // 保存到IndexDB
      const fileName = `simulation_report_${Date.now()}.pdf`;
      const dataUriString = pdf.output('datauristring');
      const base64 = dataUriString.split(',')[1];
      
      await localDB.addReport({
        title: fileName,
        base64: base64,
        duration: consoleStore.info.duration,
        throughput_per_hour: consoleStore.info.throughput_per_hour,
        robots: consoleStore.info.robots,
        total: mapStats.total,
      });

      resolve();
    } catch (error) {
      reject(error);
    }
  });
};

// 在组件挂载时注册方法
onMounted(() => {
  window.exportSimulationReport = exportSimulationReport;
  
  Scene_.getRatio().then(res => {
    const data = res.data || [];
    envRatio.value = data.map(item => ({
      value: item,
      label: 'x' + item,
    }));
  });
});

onUnmounted(() => {
  // 清理全局方法
  delete window.exportSimulationReport;
  sceneTimer.value && clearInterval(sceneTimer.value);
});

方案2:使用Vue的Provide/Inject(更优雅)

父组件:

javascript 复制代码
// 提供方法给子孙组件
const exportMethods = {
  exportSimulationReport: null
};

provide('exportMethods', exportMethods);

const handlePowerSwitch = async (on) => {
  if (appStore.features === 'emulator' && !on) {
    if (exportMethods.exportSimulationReport) {
      await exportMethods.exportSimulationReport();
    }
  }
  
  const res = await appStore.changePowerAction(on);
  if (!res.success) {
    appStore.power_action = !on;
  } 
};

孙子组件:

javascript 复制代码
const exportMethods = inject('exportMethods');

onMounted(() => {
  // 注册导出方法
  exportMethods.exportSimulationReport = exportSimulationReport;
});

onUnmounted(() => {
  // 清理方法
  exportMethods.exportSimulationReport = null;
});

方案3:addEventListener+listenerComplete

1、若目标方法无异步函数或不需要等待

在主组件中添加dispatchEvent,用来触发目标事件

html 复制代码
// index.js
<script setup>
const handlePowerSwitch = async on => {
  if( appStore.features === 'emulator' && !on) {
    // 触发孙子组件导出仿真报告事件
    window.dispatchEvent(new CustomEvent('exportSimulationReport'));
  }
  // ......rest
  
};
</script>

在目标组件中添加addEventListener

html 复制代码
// SceneInfoPanel.vue
<script setup>
import { onMounted, onUnmounted } from 'vue'; 

// 监听导出报告事件
const handleExportReportEvent = () => {
  handleExportReport();
};

onMounted(() => {
  // 添加事件监听器
  window.addEventListener('exportSimulationReport', handleExportReportEvent);
  
  // ......rest
});

onUnmounted(() => {
  // 清理事件监听器
  window.removeEventListener('exportSimulationReport', handleExportReportEvent);
  
});
</script>

2、若目标方法有异步函数,下一步操作需等待目标方法结束

html 复制代码
<script setup>
// ... 其他导入 ...

const handlePowerSwitch = async on => {
  // 单机版停止调度时记录仿真报告
  if (appStore.features === 'emulator' && !on) {
    // 等待导出仿真报告完成
    await new Promise((resolve) => {
      // 创建一次性事件监听器
      const handleExport = () => {
        window.removeEventListener('exportSimulationReportComplete', handleExport);
        resolve();
      };
      
      window.addEventListener('exportSimulationReportComplete', handleExport);
      
      // 触发导出仿真报告事件
      window.dispatchEvent(new CustomEvent('exportSimulationReport'));
    });
  }
  
  const res = await appStore.changePowerAction(on);
  // 若开关机失败 开关要复原
  if (!res.success) {
    appStore.power_action = !on;
  } 
};

// ... 其他代码 ...
</script>

在目标组件中:

html 复制代码
<script setup>
// ... 其他导入和代码 ...

// 修改 dispatchExport 函数,在完成后发送完成事件
const dispatchExport = async() => {
  if (exporting.value) return; // 防止重复导出
  try {
    exporting.value = true;
    // 准备地图信息数据
    const blocks = Models.allBlocks();
    const mapStats = {
      total: Object.keys(blocks).length,
      charger: 0,
      entry: 0,
      exit: 0,
      wait: 0,
      queue: 0,
      sorter: 0,
    };

    for (let block of Object.values(blocks)) {
      if (block.tag === 'chargerPort') mapStats.charger++;
      else if (block.tag === 'loadPort') mapStats.entry++;
      else if (block.tag === 'unloadPort') mapStats.exit++;
      else if (block.tag === 'waitPort') mapStats.wait++;
      else if (block.tag === 'queuePort') mapStats.queue++;
      else if (block.tag === 'sorterPort') mapStats.sorter++;
    }

    // 更新地图信息数据
    mapInfoData.value = [
      { label: 'scene.report.total_blocks', value: mapStats.total },
      { label: 'scene.report.charger_count', value: mapStats.charger },
      { label: 'scene.report.entry_count', value: mapStats.entry },
      { label: 'scene.report.exit_count', value: mapStats.exit },
      { label: 'scene.report.wait_count', value: mapStats.wait },
      { label: 'scene.report.queue_count', value: mapStats.queue },
      { label: 'scene.report.sorter_count', value: mapStats.sorter },
    ];

    // 生成地图 SVG 数据
    const svgData = generateMapSVG(blocks, t);
    mapSvgData.value = svgData;

    try {
      // 等待一下确保内容都已渲染
      await new Promise(resolve => setTimeout(resolve, 500));

      // 使用 html2canvas 将地图转换为图片
      const template = document.getElementById('report-template');
      const canvas = await html2canvas(template, {
        scale: 2,
        useCORS: true,
        logging: false,
        backgroundColor: '#ffffff',
        onclone: clonedDoc => {
          // 添加字体样式
          const style = clonedDoc.createElement('style');
          style.innerHTML = `
                    @font-face {
                        font-family: 'Microsoft YaHei';
                        src: local('Microsoft YaHei'), local('PingFang SC'), local('SimHei');
                    }
                    * {
                        font-family: 'Microsoft YaHei', 'PingFang SC', 'SimHei', Arial, sans-serif !important;
                    }
                    svg {
                        width: 100% !important;
                        height: auto !important;
                        max-height: 400px !important;
                    }
                `;
          clonedDoc.head.appendChild(style);
        },
      });
      const imgData = canvas.toDataURL('image/jpeg', 1.0);

      // 创建 PDF
      const pdf = new jsPDF({
        orientation: 'portrait',
        unit: 'mm',
        format: 'a4',
        // 直接设置页边距
        marginTop: 50,
        marginBottom: 40,
        marginLeft: 30,
        marginRight: 30,
      });
      // 获取页面尺寸
      const pageWidth = pdf.internal.pageSize.width;
      const pageHeight = pdf.internal.pageSize.height;

      // 计算图片尺寸以适应 A4 页面
      const imgWidth = pageWidth;
      const imgHeight = (canvas.height * imgWidth) / canvas.width;

      // 如果内容超过一页,需要分页处理
      let heightLeft = imgHeight;
      let position = 0;

      pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
      heightLeft -= pageHeight;

      while (heightLeft > 0) {
        position = heightLeft - imgHeight;
        pdf.addPage();
        pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
        heightLeft -= pageHeight;
      }

      // 生成文件名
      const fileName = `simulation_report_${new Date()
        .toISOString()
        .replace(/[^0-9]/g, '')
        .slice(0, 14)}.pdf`;
      const dataUriString = pdf.output('datauristring');
      const base64 = dataUriString.split(',')[1];
      // 保存至indexDB
      await localDB.addReport({
        title: fileName,
        base64: base64,
        duration: consoleStore.info.duration,
        throughput_per_hour: consoleStore.info.throughput_per_hour,
        robots: consoleStore.info.robots,
        total: mapStats.total,
      });

    } finally {
    }
  } catch (error) {
    console.error('导出报告失败:', error);
  } finally {
    exporting.value = false;
    // 发送导出完成事件
    window.dispatchEvent(new CustomEvent('exportSimulationReportComplete'));
  }
}

// ... 其他代码 ...
</script>
相关推荐
巴啦啦臭魔仙1 小时前
uniapp scroll-view自定义下拉刷新的坑
前端·javascript·uni-app
小满zs1 小时前
Next.js第九章(AI)
前端
晨枫阳1 小时前
不同语言的元组对比
java·前端·javascript
柒儿吖2 小时前
Electron for 鸿蒙PC 窗口问题完整解决方案
javascript·electron·harmonyos
flashlight_hi3 小时前
LeetCode 分类刷题:404. 左叶子之和
javascript·算法·leetcode
芳草萋萋鹦鹉洲哦3 小时前
【tauri+pixijs】关于unicode/ascII/GB2312
前端·tauri·pixijs
木易 士心3 小时前
th-table 中 基于双字段计算的表格列展示方案
前端·javascript·angular.js
fakaifa4 小时前
【全开源】智慧共享农场源码独立版+uniapp前端
前端·uni-app·智慧农场·源码下载·智慧农场小程序·智慧共享农场
toooooop84 小时前
uniapp多个页面监听?全局监听uni.$emit/$on
前端·javascript·uni-app