Browse Source

实现electron打印,还存在图片排列格式不正确的问题

dengdx 1 month ago
parent
commit
f332d7083c

+ 2 - 2
.build/h5_for_electron.build.win.x64.js

@@ -29,8 +29,8 @@ function getGhToken() {
   }
 }
 
-const TARO_API_URL = 'http://101.43.219.60:7700'; // 远程地址,这里写死,要做成部署后可配置
-const TARO_MQTT_URL ='ws://101.43.219.60:8083/mqtt';
+const TARO_API_URL = 'http://192.168.110.245:6001'; // 远程地址,这里写死,要做成部署后可配置
+const TARO_MQTT_URL ='ws://192.168.110.245:8083/mqtt';
 const rootDir = path.join(__dirname, '..');          // 项目根目录
 
 

+ 406 - 1
main.js

@@ -1,10 +1,12 @@
 // main.js  (ESM 版本)
-import { app, BrowserWindow, Menu, ipcMain, shell } from 'electron';
+import { app, BrowserWindow, Menu, ipcMain, shell, dialog } from 'electron';
 import { fileURLToPath } from 'url';
 import { dirname, join } from 'path';
 import { exec } from 'child_process';
 import { promisify } from 'util';
 import { writeLog } from './src/log/log-writer.js';
+import { tmpdir } from 'os';
+import { writeFileSync } from 'fs';
 
 // -------------- 构造 ESM 版 __dirname --------------
 const __filename = fileURLToPath(import.meta.url);
@@ -163,3 +165,406 @@ ipcMain.handle('exit-shutdown', async () => {
   const requiresAdmin = true;
   return await executeSystemCommand(commands.shutdown, requiresAdmin);
 });
+
+// ========== 打印相关 IPC 处理器 ==========
+
+/**
+ * 打印HTML内容
+ */
+// 工具:一次性事件监听
+const once = (emitter, name) =>
+  new Promise(resolve => emitter.once(name, resolve));
+
+// 工具:生成不二次编码的 data URL
+const makeDataURL = html =>
+  `data:text/html;charset=utf-8,${Buffer.from(html, 'utf-8')}`;
+
+ipcMain.handle('print-film', async (event, options) => {
+  let printWindow = null;
+  try {
+    writeLog('info', `开始打印,选项: ${JSON.stringify(options)}`);
+
+    const { html, orientation, paperSize, silent, margins } = options;
+
+    // 1. 创建隐藏窗口
+    printWindow = new BrowserWindow({
+      show: true,               // true 仅调试时打开
+      webPreferences: {
+        nodeIntegration: false,
+        contextIsolation: false,
+        //sandbox: true
+      }
+    });
+
+    // 2. 加载 HTML
+    const loaded = once(printWindow.webContents, 'did-finish-load');
+    await printWindow.loadURL(makeDataURL(html));
+    await loaded;
+
+    // 3. 打印参数
+    const printOpts = {
+      silent: Boolean(silent),
+      printBackground: true,
+      color: true,
+      margins: {
+        marginType: 'custom',
+        top: margins?.top ?? 10,
+        bottom: margins?.bottom ?? 10,
+        left: margins?.left ?? 10,
+        right: margins?.right ?? 10
+      },
+      landscape: orientation === 'landscape',
+      pageSize: paperSize || 'A4'
+    };
+
+    // 4. 执行打印(带 60 秒超时)
+    const [success, reason] = await Promise.race([
+      new Promise(res => {
+        printWindow.webContents.print(printOpts, (ok, err) => res([ok, err]));
+      }),
+      new Promise((_, rej) =>
+        setTimeout(() => rej(new Error('打印超时(60s)')), 60_000)
+      )
+    ]);
+
+    // 5. 清理窗口
+    if (printWindow && !printWindow.isDestroyed()) {
+      printWindow.close();
+      printWindow = null;
+    }
+
+    if (success) {
+      writeLog('info', '打印成功');
+      return { success: true };
+    }
+    throw new Error(reason || '打印失败');
+  } catch (err) {
+    writeLog('error', `打印出错: ${err.message}`);
+    // 确保异常时窗口也关掉
+    if (printWindow && !printWindow.isDestroyed()) {
+      printWindow.close();
+    }
+    return { success: false, error: err.message };
+  }
+});
+
+/**
+ * 打印HTML内容到文件(使用File System API)
+ */
+ipcMain.handle('print-film-to-file', async (event, options) => {
+  let printWindow = null;
+  const tempFiles = [];
+
+  try {
+    writeLog('info', `开始文件系统打印,选项: ${JSON.stringify(options)}`);
+
+    const { html, imageDataList, orientation, paperSize, silent, margins } = options;
+
+    // 1. 创建临时图片文件
+    const fs = await import('fs/promises');
+    const os = await import('os');
+    const path = await import('path');
+    const { fileURLToPath, pathToFileURL } = await import('url');
+
+    const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'electron-print-'));
+
+    // 当前时间戳用于文件名
+    const timestamp = Date.now();
+
+    // 处理每个图片数据
+    const imageUrlMap = new Map();
+
+    for (let i = 0; i < imageDataList.length; i++) {
+      const { canvasId, data: base64Data } = imageDataList[i];
+
+      // 将base64转换为buffer
+      const buffer = Buffer.from(base64Data, 'base64');
+
+      // 生成临时文件路径
+      const tempFilePath = path.join(tempDir, `print-image-${timestamp}-${i}.png`);
+
+      // 写入文件
+      await fs.writeFile(tempFilePath, buffer);
+      tempFiles.push(tempFilePath);
+      writeLog('info', `临时图片文件已保存: ${tempFilePath}`);
+
+      // 使用Node.js内置方法构造file:// URL
+      const fileUrl = pathToFileURL(tempFilePath).href;
+      writeLog('info', `File URL构造: ${fileUrl}`);
+      writeLog('info', `原始路径: ${tempFilePath}`);
+
+      // 记录文件路径映射
+      imageUrlMap.set(canvasId, fileUrl);
+    }
+
+    // 2. 生成最终HTML,将占位符替换为文件URL
+    let finalHtml = html;
+    for (const [canvasId, fileUrl] of imageUrlMap.entries()) {
+      finalHtml = finalHtml.replace(
+        new RegExp(`<img id="${canvasId}"[^>]*>`, 'g'),
+        `<img id="${canvasId}" src="${fileUrl}" style="max-width: 100%;" alt="打印图片" />`
+      );
+    }
+
+    writeLog('info', `HTML占位符已替换完成,临时文件数量: ${tempFiles.length}`);
+
+    // 3. 包装完整的HTML文档
+    const fullHtml = `
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>打印胶片</title>
+  <style>
+    * {
+      margin: 0;
+      padding: 0;
+      box-sizing: border-box;
+    }
+
+    body {
+      width: 100%;
+      height: 100%;
+      background: white;
+    }
+
+    @page {
+      size: ${paperSize} ${orientation};
+      margin: ${margins?.top ?? 10}mm ${margins?.right ?? 10}mm ${margins?.bottom ?? 10}mm ${margins?.left ?? 10}mm;
+    }
+
+    .print-only {
+      display: block !important;
+    }
+
+    .screen-only {
+      display: none !important;
+    }
+  </style>
+</head>
+<body>
+235689
+  <div id="film-print-area">
+    ${finalHtml}
+  </div>
+</body>
+</html>
+    `;
+
+    // 4. 创建隐藏窗口(调试时可以设为true查看窗口内容)
+    printWindow = new BrowserWindow({
+      show: true, // 设为true调试内容
+      webPreferences: {
+        nodeIntegration: false,
+        contextIsolation: false,
+      }
+    });
+
+    writeLog('info', '打印窗口已创建,开始加载HTML');
+    writeLog('info',`把完整的html文件保存起来:${fullHtml}`)
+
+    // 5. 加载 HTML
+    const loaded = once(printWindow.webContents, 'did-finish-load');
+    // const htmlDataURL = makeDataURL(fullHtml);
+    // await printWindow.loadURL(htmlDataURL);
+    // 1. 把完整 HTML 写入临时文件
+const tmpHtml = join(tmpdir(), `electron-print-${Date.now()}.html`);
+await writeFileSync(tmpHtml, fullHtml, 'utf8');
+
+// 2. 用 file:// 协议加载
+await printWindow.loadURL(`file://${tmpHtml.replace(/\\/g, '/')}`);
+
+    await loaded;
+
+    writeLog('info', 'HTML加载完成,等待图片加载...');
+
+    // 6. 等待所有图片加载完成(重要的是这个步骤)
+    await new Promise((resolve, reject) => {
+      let loadedCount = 0;
+      const totalImages = imageDataList.length;
+
+      if (totalImages === 0) {
+        // 没有图片,直接继续
+        resolve();
+        return;
+      }
+
+      const checkLoaded = () => {
+        loadedCount++;
+        writeLog('info', `图片已加载 ${loadedCount}/${totalImages}`);
+
+        if (loadedCount >= totalImages) {
+          writeLog('info', '所有图片加载完成');
+          resolve();
+        }
+      };
+
+      printWindow.webContents.on('console-message', (event, level, message) => {
+        // 监控图片加载的调试信息
+        if (message.includes('图片加载完成')) {
+          checkLoaded();
+        }
+      });
+
+      // 设置超时(20秒)
+      setTimeout(() => {
+        writeLog('warn', `图片加载超时 (已加载 ${loadedCount}/${totalImages}),仍继续打印`);
+        resolve();
+      }, 10000);
+    });
+
+    // 7. 执行打印
+    const printOpts = {
+      silent: false,//Boolean(silent),
+      printBackground: true,
+      color: true,
+      margins: {
+        marginType: 'custom',
+        top: margins?.top ?? 10,
+        bottom: margins?.bottom ?? 10,
+        left: margins?.left ?? 10,
+        right: margins?.right ?? 10
+      },
+      landscape: orientation === 'landscape',
+      pageSize: paperSize || 'A4'
+    };
+
+    writeLog('info', '开始执行打印');
+    const [success, reason] = await Promise.race([
+      new Promise( res => {
+        // 使用 printDialog 显示对话框
+        // printWindow.webContents.printDialog(printOpts);
+        printWindow.webContents.print(printOpts, (ok, err) => res([ok, err]));
+      }),
+      new Promise((_, rej) =>
+        setTimeout(() => rej(new Error('打印超时(60s)')), 60_000)
+      )
+    ]);
+
+    // 8. 清理临时文件
+    // try {
+    //   for (const tempFile of tempFiles) {
+    //     await fs.unlink(tempFile);
+    //     writeLog('info', `临时文件已删除: ${tempFile}`);
+    //   }
+    //   // 删除临时目录
+    //   await fs.rmdir(tempDir, { recursive: true });
+    //   writeLog('info', `临时目录已删除: ${tempDir}`);
+    // } catch (cleanupError) {
+    //   writeLog('warn', `清理临时文件失败: ${cleanupError.message}`);
+    // }
+
+    if (success) {
+      writeLog('info', '文件系统打印成功');
+      return { success: true };
+    }
+    throw new Error(reason || '打印失败');
+
+  } catch (err) {
+    writeLog('error', `文件系统打印出错: ${err.message}`);
+
+    // 清理资源
+    if (printWindow && !printWindow.isDestroyed()) {
+      printWindow.close();
+    }
+
+    // // 清理临时文件
+    // try {
+    //   const fs = await import('fs/promises');
+    //   for (const tempFile of tempFiles) {
+    //     try {
+    //       await fs.unlink(tempFile);
+    //     } catch (e) {
+    //       // 忽略单个文件删除错误
+    //     }
+    //   }
+    // } catch (cleanupError) {
+    //   writeLog('warn', `清理临时文件失败: ${cleanupError.message}`);
+    // }
+
+    return { success: false, error: err.message };
+  }
+});
+
+/**
+ * 导出为PDF
+ */
+ipcMain.handle('print-to-pdf', async (event, options) => {
+  try {
+    writeLog('info', `开始导出PDF,选项: ${JSON.stringify(options)}`);
+
+    const { html, orientation, paperSize, margins } = options;
+
+    // 创建隐藏窗口
+    const pdfWindow = new BrowserWindow({
+      show: false,
+      webPreferences: {
+        nodeIntegration: false,
+        contextIsolation: true,
+      },
+    });
+
+    // 加载HTML内容
+    await pdfWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`);
+
+    // 等待加载完成
+    await new Promise(resolve => {
+      pdfWindow.webContents.on('did-finish-load', resolve);
+    });
+
+    // PDF选项
+    const pdfOptions = {
+      printBackground: true,
+      landscape: orientation === 'landscape',
+      pageSize: paperSize || 'A4',
+      margins: {
+        top: margins?.top || 10,
+        bottom: margins?.bottom || 10,
+        left: margins?.left || 10,
+        right: margins?.right || 10,
+      },
+    };
+
+    // 生成PDF
+    const data = await pdfWindow.webContents.printToPDF(pdfOptions);
+
+    // 关闭窗口
+    pdfWindow.close();
+
+    // 显示保存对话框
+    const { filePath } = await dialog.showSaveDialog(win, {
+      title: '保存PDF',
+      defaultPath: `film-${Date.now()}.pdf`,
+      filters: [
+        { name: 'PDF文件', extensions: ['pdf'] }
+      ]
+    });
+
+    if (filePath) {
+      const fs = await import('fs/promises');
+      await fs.writeFile(filePath, data);
+      writeLog('info', `PDF已保存到: ${filePath}`);
+
+      // 询问是否打开文件
+      const { response } = await dialog.showMessageBox(win, {
+        type: 'info',
+        message: 'PDF导出成功',
+        detail: `文件已保存到:\n${filePath}`,
+        buttons: ['打开文件', '关闭'],
+        defaultId: 0
+      });
+
+      if (response === 0) {
+        shell.openPath(filePath);
+      }
+
+      return { success: true, filePath };
+    }
+
+    return { success: false, error: '用户取消保存' };
+
+  } catch (error) {
+    writeLog('error', `导出PDF出错: ${error.message}`);
+    return { success: false, error: error.message };
+  }
+});

+ 32 - 0
preload.js

@@ -10,3 +10,35 @@ contextBridge.exposeInMainWorld('electronAPI', {
   // 日志功能(保留原有功能)
   writeLog: (level, msg) => ipcRenderer.invoke('write-log', level, msg)
 });
+
+// 暴露打印API
+contextBridge.exposeInMainWorld('electronPrint', {
+  /**
+   * 打印HTML内容(传统方式)
+   * @param {Object} options - 打印选项
+   * @param {string} options.html - 要打印的HTML内容
+   * @param {string} options.orientation - 打印方向 ('portrait' | 'landscape')
+   * @param {string} options.paperSize - 纸张尺寸 ('A3' | 'A4')
+   * @param {boolean} options.silent - 是否静默打印
+   * @param {Object} options.margins - 页边距
+   */
+  print: (options) => ipcRenderer.invoke('print-film', options),
+
+  /**
+   * 打印HTML内容(使用File System API)
+   * @param {Object} options - 打印选项
+   * @param {string} options.html - 要打印的HTML内容
+   * @param {Array} options.imageDataList - 图片数据数组
+   * @param {string} options.orientation - 打印方向 ('portrait' | 'landscape')
+   * @param {string} options.paperSize - 纸张尺寸 ('A3' | 'A4')
+   * @param {boolean} options.silent - 是否静默打印
+   * @param {Object} options.margins - 页边距
+   */
+  printToFile: (options) => ipcRenderer.invoke('print-film-to-file', options),
+
+  /**
+   * 导出为PDF
+   * @param {Object} options - 导出选项
+   */
+  printToPDF: (options) => ipcRenderer.invoke('print-to-pdf', options),
+});

+ 17 - 2
src/pages/output/print/PrintControl.tsx

@@ -39,13 +39,28 @@ const PrintControl: React.FC = () => {
 
     try {
       setIsPrinting(true);
-      
+
       console.log('[本地打印] 开始打印,胶片尺寸:', selectedSize);
-      
+
+      // 调试:检查 Electron 环境
+      console.log('[本地打印] 检查 window.electronPrint:', !!window.electronPrint);
+      console.log('[本地打印] 检查 window.electronAPI:', !!window.electronAPI);
+
+      // 检查胶片元素是否存在
+      const filmElement = document.getElementById('film-print-area');
+      if (!filmElement) {
+        throw new Error('未找到胶片元素 film-print-area');
+      }
+
+      // 检查Canvas数量
+      const canvases = filmElement.querySelectorAll('canvas');
+      console.log(`[本地打印] 发现 ${canvases.length} 个Canvas元素`);
+
       await printManager.print({
         elementId: 'film-print-area',
         filmSize: selectedSize,
         orientation: activeFilm?.orientation === 'horizontal' ? 'landscape' : 'portrait',
+        silent: false, // 显示打印对话框,让用户选择打印机
       });
 
       message.success('打印完成');

+ 53 - 21
src/services/print/adapters/ElectronPrintAdapter.ts

@@ -20,8 +20,14 @@ export class ElectronPrintAdapter implements IPrintAdapter {
     }
 
     // 检查Electron打印API是否可用
-    if (!window.electronPrint) {
-      throw new Error('Electron打印API不可用,请确保已在preload.js中暴露');
+    console.log('[Electron打印] 检查 window.electronPrint:', window.electronPrint);
+    if (!window.electronPrint?.printToFile) {
+      throw new Error('Electron打印文件系统API不可用,请确保已在preload.js中暴露');
+    }
+
+    // 检查printToFile方法是否存在
+    if (typeof window.electronPrint.printToFile !== 'function') {
+      throw new Error('electronPrint.printToFile 方法不可用');
     }
 
     this.isPrinting = true;
@@ -35,41 +41,57 @@ export class ElectronPrintAdapter implements IPrintAdapter {
 
       console.log('[Electron打印] 开始打印流程');
 
-      // 2. 准备打印:将Canvas转换为图片
-      await prepareFilmForPrint(element);
+      // 2. 准备打印:提取Canvas数据(File System API方案)
+      const { html, imageDataList } = await prepareFilmForPrint(element);
 
-      // 3. 获取打印内容的HTML
-      const printContent = element.outerHTML;
-
-      // 4. 包装完整的HTML文档
-      const fullHtml = this.wrapHtmlDocument(printContent, options);
+      // 调试:验证数据
+      console.log('[Electron打印] 处理后的HTML长度:', html.length);
+      console.log('[Electron打印] 图片数据数量:', imageDataList.length);
+      imageDataList.forEach((item, index) => {
+        console.log(`[Electron打印] 图片 ${index}: ID=${item.canvasId}, 数据长度=${item.data.length}`);
+      });
 
-      // 5. 调用Electron打印API
-      console.log('[Electron打印] 调用 electronPrint.print()');
-      await window.electronPrint.print({
-        html: fullHtml,
+      // 3. 调用Electron文件系统打印API
+      console.log('[Electron打印] 调用 electronPrint.printToFile()');
+      console.log('[Electron打印] 传递参数:', {
+        html: html,
+        imageDataList: imageDataList,
         orientation: options.orientation,
         paperSize: options.paperSize,
         silent: options.silent || false,
         margins: options.margins,
       });
 
+      try {
+        await window.electronPrint.printToFile({
+          html: html,
+          imageDataList: imageDataList,
+          orientation: options.orientation,
+          paperSize: options.paperSize,
+          silent: options.silent || false,
+          margins: options.margins,
+        });
+      } catch (ipcError) {
+        console.error('[Electron打印] IPC 调用失败:', ipcError);
+        throw ipcError;
+      }
+
       console.log('[Electron打印] 打印完成');
 
-      // 6. 清理
+      // 4. 清理
       cleanupAfterPrint(element);
       this.isPrinting = false;
 
     } catch (error) {
       this.isPrinting = false;
       console.error('[Electron打印] 打印失败:', error);
-      
+
       // 确保清理
       const element = document.getElementById(options.elementId);
       if (element) {
         cleanupAfterPrint(element);
       }
-      
+
       throw error;
     }
   }
@@ -139,13 +161,22 @@ export class ElectronPrintAdapter implements IPrintAdapter {
       margin: ${defaultMargins.top}mm ${defaultMargins.right}mm ${defaultMargins.bottom}mm ${defaultMargins.left}mm;
     }
 
-    /* 隐藏Canvas,显示图片 */
-    .screen-only {
-      display: none !important;
+      /* 打印时隐藏Canvas,显示图片 */
+    @media print {
+      .screen-only {
+        display: none !important;
+      }
+
+      .print-only {
+        display: block !important;
+      }
     }
 
-    .print-only {
-      display: block !important;
+    /* 屏幕显示时隐藏打印专用图片 */
+    @media screen {
+      .print-only {
+        display: none !important;
+      }
     }
 
     /* 确保打印内容占满页面 */
@@ -157,6 +188,7 @@ export class ElectronPrintAdapter implements IPrintAdapter {
   </style>
 </head>
 <body>
+地对地导弹
   ${content}
 </body>
 </html>

+ 18 - 0
src/services/print/types.ts

@@ -77,6 +77,7 @@ export enum PrintStatus {
  */
 export interface ElectronPrintAPI {
   print: (options: ElectronPrintOptions) => Promise<void>;
+  printToFile: (options: ElectronPrintToFileOptions) => Promise<void>;
   printToPDF?: (options: ElectronPrintOptions) => Promise<void>;
 }
 
@@ -93,6 +94,23 @@ export interface ElectronPrintOptions {
   };
 }
 
+export interface ElectronPrintToFileOptions {
+  html: string;
+  imageDataList: Array<{
+    canvasId: string;
+    data: string;
+  }>;
+  orientation?: 'portrait' | 'landscape';
+  paperSize?: string;
+  silent?: boolean;
+  margins?: {
+    top: number;
+    right: number;
+    bottom: number;
+    left: number;
+  };
+}
+
 /**
  * Android打印API类型声明
  */

+ 44 - 23
src/services/print/utils/canvasToImage.ts

@@ -4,35 +4,56 @@
  */
 
 /**
- * 在打印前准备:将所有Canvas转换为图片并临时插入
+ * 在打印前准备:提取Canvas数据用于文件系统存储
+ * 返回Canvas数据数组,每个元素包含原始HTML片段和图片数据的占位符
  * @param filmElement 胶片容器元素
  */
 export async function prepareFilmForPrint(
   filmElement: HTMLElement
-): Promise<void> {
-  const canvases = filmElement.querySelectorAll('canvas');
-  
-  if (canvases.length === 0) {
-    console.warn('[打印] 未找到Canvas元素');
-    return;
-  }
-
-  console.log(`[打印] 找到 ${canvases.length} 个Canvas元素,开始转换`);
-  
+): Promise<{ html: string; imageDataList: Array<{ canvasId: string; data: string }> }> {
+  const canvases0 = filmElement.querySelectorAll('canvas');
   // 等待所有Canvas渲染完成
-  await waitForCanvasesReady(canvases);
-  
-  // 转换所有Canvas为图片
-  for (const canvas of Array.from(canvases)) {
-    try {
-      await convertCanvasToImage(canvas);
-    } catch (error) {
-      console.error('[打印] Canvas转图片失败:', error);
-      // 继续处理其他Canvas
-    }
+  await waitForCanvasesReady(canvases0);
+  const canvases = Array.from(filmElement.querySelectorAll('canvas')) as HTMLCanvasElement[];
+
+  console.log(`[Canvas转图片] 找到 ${canvases.length} 个Canvas,开始提取数据`);
+
+  // 为每个Canvas生成唯一ID
+  const imageDataList: Array<{ canvasId: string; data: string }> = [];
+
+  // 处理Canvas元素,准备占位符
+  let processedHtml = filmElement.innerHTML;
+  let placeholderCounter = 0;
+
+  for (const canvas of canvases) {
+    // 生成Canvas数据
+    const dataURL = canvas.toDataURL('image/png');
+    console.log(`[Canvas转图片] Canvas转DataURL完成,长度: ${dataURL.length}`);
+
+    // 生成唯一占位符ID
+    const placeholderId = `print-image-${Date.now()}-${placeholderCounter++}`;
+
+    // 添加到数据列表
+    imageDataList.push({
+      canvasId: placeholderId,
+      data: dataURL.replace('data:image/png;base64,', '') // 移除data URL前缀,只保存base64数据
+    });
+
+    // 在HTML中用占位符替换Canvas
+    const canvasOuterHTML = canvas.outerHTML;
+    const placeholderHTML = `<img id="${placeholderId}" class="print-only" style="max-width: 100%;" alt="打印图片" />`;
+    processedHtml = processedHtml.replace(canvasOuterHTML, placeholderHTML);
+
+    // 添加screen-only类到隐藏的Canvas(如果有的话)
+    canvas.classList.add('screen-only');
   }
-  
-  console.log('[打印] Canvas转图片完成');
+
+  console.log(`[Canvas转图片] 数据提取完成,共 ${imageDataList.length} 个图片数据`);
+
+  return {
+    html: processedHtml,
+    imageDataList
+  };
 }
 
 /**