From 8ab3f479a72a478e3ce4211478dfe88935505f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E8=BF=9B?= Date: Fri, 24 Jul 2026 18:25:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9B=BE=E7=89=87=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=20+=20=E9=87=8D=E5=8F=A0=E5=8E=BB=E9=87=8D?= =?UTF-8?q?=20bug=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - IMG 标签自动从 src URL 下载图片并嵌入 PPT - URL 拼接用 origin 而非完整 pageUrl(修复 404) - collectSlideChildren 补全 backgroundImages 字段传递 - 重叠去重 key 加 type,避免不同类型节点被误删 - 三个 PPT 都验证通过 --- convert-w2p.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/convert-w2p.js b/convert-w2p.js index d2ce2f6..e2b0fa7 100644 --- a/convert-w2p.js +++ b/convert-w2p.js @@ -261,7 +261,7 @@ function collectObjects(nodeId, ctx, processed) { * @param {Object} input web-to-pixso 格式的 JSON 对象 * @returns {Object} html2pptx Schema JSON 对象 */ -function convert(input) { +async function convert(input) { if (!input || !input.nodes) { throw new Error('无效输入:缺少 "nodes" 字段'); } @@ -272,6 +272,57 @@ function convert(input) { } const canvas = input.canvas || {}; + + // ===== 图片下载:RECTANGLE+IMG 标签且无 backgroundImages 时,从 src URL 下载 ===== + const pageUrl = input.source?.url || ''; + const https = require('https'); + const http = require('http'); + + function downloadImage(url) { + return new Promise(function(resolve) { + try { + var mod = url.startsWith('https') ? https : http; + mod.get(url, { timeout: 5000 }, function(res) { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + downloadImage(res.headers.location).then(resolve); + return; + } + var chunks = []; + res.on('data', function(c) { chunks.push(c); }); + res.on('end', function() { + var buf = Buffer.concat(chunks); + var b64 = 'data:image/png;base64,' + buf.toString('base64'); + resolve(b64); + }); + res.on('error', function() { resolve(null); }); + }).on('error', function() { resolve(null); }); + } catch (e) { resolve(null); } + }); + } + + async function fetchNodeImages(node) { + if (!node) return; + if (node.tag === 'IMG' && node.attributes?.src && (!node.backgroundImages || node.backgroundImages.length === 0)) { + var src = node.attributes.src; + // 拼接完整 URL:相对路径基于 origin,绝对路径基于 origin + var origin = pageUrl.replace(/^(https?:\/\/[^\/]+).*/, '$1'); + if (src.startsWith('/')) src = origin + src; + else if (!src.startsWith('http')) src = pageUrl.replace(/\/[^\/]*$/, '/') + src; + console.log('📥 下载图片: ' + src); + var data = await downloadImage(src); + if (data) { + if (!input.assets) input.assets = {}; + var key = 'img-' + (node.id || Math.random().toString(36).slice(2)); + input.assets[key] = { data: data }; + node.backgroundImages = [key]; + node.type = 'IMAGE'; // 标记为 IMAGE 类型 + } + } + if (node.children) for (var c of node.children) await fetchNodeImages(c); + } + + // 异步下载所有图片 + await fetchNodeImages(inputRoot); // 如果没有 canvas 信息则默认 1920×1080(16:9 常见尺寸) const canvasWidth = canvas.width || 1920; const canvasHeight = canvas.height || 1080; @@ -293,6 +344,7 @@ function convert(input) { rect: { x: nr.x - slideX, y: nr.y - slideY, w: rw, h: rh }, styles: node.styles || {}, src: node.src || node.text || '', layerGroup: node.layerGroup || '', + backgroundImages: node.backgroundImages, parentW: parentW || rw, _depth: depth }); @@ -578,15 +630,15 @@ function convert(input) { return za - zb; }); - // ===== 同坐标完全重叠去重:保留后者(高层) ===== + // ===== 同坐标完全重叠去重:保留后者(高层),仅同类型 ===== var seen = {}; var overlapRemove = new Set(); for (var i = sg.children.length - 1; i >= 0; i--) { var ci = sg.children[i]; if (!ci.rect) continue; - var key = Math.round(ci.rect.x) + ',' + Math.round(ci.rect.y) + ',' + Math.round(ci.rect.w) + ',' + Math.round(ci.rect.h); + var key = ci.type + ',' + Math.round(ci.rect.x) + ',' + Math.round(ci.rect.y) + ',' + Math.round(ci.rect.w) + ',' + Math.round(ci.rect.h); if (seen[key]) { - overlapRemove.add(ci.id); // 后出现的(更高层)保留,前面的移除 + overlapRemove.add(ci.id); } else { seen[key] = true; } @@ -881,6 +933,7 @@ function convert(input) { // ============================================================ if (require.main === module) { +(async function() { const args = process.argv.slice(2); if (args.length < 1) { @@ -912,7 +965,7 @@ if (require.main === module) { // 执行转换并写入 try { - const result = convert(inputJson); + const result = await convert(inputJson); console.log('result slides:', result.slides.length, 'first objects:', result.slides[0]?.objects?.length); // 渲染 PPTX(支持自定义布局) @@ -975,6 +1028,7 @@ if (require.main === module) { console.error('❌ 转换失败:', err.message); process.exit(1); } +})(); } module.exports = { convert };