From 8bb232a3f012dfb85dc9c31b73d087beb79e12f1 Mon Sep 17 00:00:00 2001 From: augushong Date: Tue, 19 May 2026 00:35:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(phone-image):=20=E7=94=9F=E6=88=90?= =?UTF-8?q?=E5=9C=96=E7=89=87SnapDOM=E6=88=AA=E5=9C=96=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增captureDomPages(): 從右側DOM容器串行截圖 - 新增generateLongImageFromCanvases(): canvas拼接長圖 - saveImages()條件分支: useDomPreview走DOM截圖, 否則走舊路徑 - 截圖參數scale=2, backgroundColor=#ffffff - 保留doCapturePages/capturePagesFromStaging作為備用 --- public/static/js/phone-image.js | 137 +++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 4 deletions(-) diff --git a/public/static/js/phone-image.js b/public/static/js/phone-image.js index d85bbf6..342dbbc 100644 --- a/public/static/js/phone-image.js +++ b/public/static/js/phone-image.js @@ -1644,7 +1644,119 @@ var PhoneImageEngine = (function () { } /** - * 将页面渲染到隐藏区域并高质量截图(用于保存) + * 从右侧DOM分页容器截图(DOM预览模式) + * 串行截图每个 .dom-page-container,避免并行导致内存溢出 + * @param {Function} [onProgress] 进度回调 function(current, total) + * @returns {jQuery Deferred} resolves with canvas array + */ + function captureDomPages(onProgress) { + var deferred = $.Deferred(); + + var containers = document.querySelectorAll('#paginated-preview .dom-page-container'); + if (!containers || containers.length === 0) { + deferred.reject('未找到DOM分页容器'); + return deferred.promise(); + } + + var sizeConfig = config.sizes[config.size] || config.sizes.xiaohongshu; + var outputWidth = sizeConfig.width * 2; // scale=2 + var outputHeight = sizeConfig.height * 2; + var totalPages = containers.length; + var canvases = []; + + console.log('[PhoneImage] DOM截图开始,共' + totalPages + '页'); + PhoneImageLogPanel.log('DOM截图开始,共' + totalPages + '页', 'info'); + + var idx = 0; + + function captureNext() { + if (idx >= totalPages) { + console.log('[PhoneImage] DOM截图完成,成功' + canvases.length + '/' + totalPages + '页'); + PhoneImageLogPanel.log('DOM截图完成,成功' + canvases.length + '/' + totalPages + '页', 'success'); + deferred.resolve(canvases); + return; + } + + // 进度显示 + if (onProgress) onProgress(idx + 1, totalPages); + PhoneImageLogPanel.log('截图中 ' + (idx + 1) + '/' + totalPages, 'info'); + + try { + snapdom.toCanvas(containers[idx], { + scale: 2, + backgroundColor: '#ffffff' + }).then(function (canvas) { + // 确保尺寸正确 + if (canvas.width !== outputWidth || canvas.height !== outputHeight) { + var resizedCanvas = document.createElement('canvas'); + resizedCanvas.width = outputWidth; + resizedCanvas.height = outputHeight; + var ctx = resizedCanvas.getContext('2d'); + ctx.drawImage(canvas, 0, 0, outputWidth, outputHeight); + canvas = resizedCanvas; + } + + canvases.push(canvas); + idx++; + captureNext(); + }).catch(function (err) { + console.error('[PhoneImage] 第' + (idx + 1) + '页截图失败:', err); + PhoneImageLogPanel.log('第' + (idx + 1) + '页截图失败: ' + err, 'error'); + // 继续下一页 + idx++; + captureNext(); + }); + } catch (e) { + console.error('[PhoneImage] 第' + (idx + 1) + '页截图异常:', e); + PhoneImageLogPanel.log('第' + (idx + 1) + '页截图异常: ' + e, 'error'); + idx++; + captureNext(); + } + } + + captureNext(); + return deferred.promise(); + } + + /** + * 将canvas数组拼接为长图base64 + * @param {Array} canvases - canvas对象数组 + * @returns {jQuery Deferred} resolves with base64 string or null + */ + function generateLongImageFromCanvases(canvases) { + var deferred = $.Deferred(); + + if (!canvases || canvases.length === 0) { + deferred.resolve(null); + return deferred.promise(); + } + + var maxWidth = 0; + var totalHeight = 0; + for (var i = 0; i < canvases.length; i++) { + totalHeight += canvases[i].height; + if (canvases[i].width > maxWidth) { + maxWidth = canvases[i].width; + } + } + + var longCanvas = document.createElement('canvas'); + longCanvas.width = maxWidth; + longCanvas.height = totalHeight; + var ctx = longCanvas.getContext('2d'); + + var y = 0; + for (var j = 0; j < canvases.length; j++) { + ctx.drawImage(canvases[j], 0, y); + y += canvases[j].height; + } + + deferred.resolve(longCanvas.toDataURL('image/jpeg', 0.85)); + return deferred.promise(); + } + + /** + * 将页面渲染到隐藏区域并高质量截图(用于保存,旧流程备用) * @returns {jQuery Deferred} resolves with canvas array */ function capturePagesFromStaging() { @@ -1695,15 +1807,32 @@ var PhoneImageEngine = (function () { var deferred = $.Deferred(); // 第一步:生成分页图片和长图 - capturePagesFromStaging().then(function (canvases) { + // DOM预览模式:直接从右侧DOM容器截图;旧模式:从隐藏渲染区截图 + var capturePromise; + if (config.useDomPreview) { + capturePromise = captureDomPages(function (current, total) { + if (onProgress) onProgress(current, total, null); + }); + } else { + capturePromise = capturePagesFromStaging(); + } + + capturePromise.then(function (canvases) { if (onProgress) onProgress(canvases.length, canvases.length, null); var pagesData = []; for (var i = 0; i < canvases.length; i++) { pagesData.push(canvases[i].toDataURL('image/jpeg', 0.92)); } - // 第二步:生成长图 - return generateLongImageBase64().then(function(longBase64) { + // 第二步:生成长图(DOM模式用canvas拼接,旧模式用编辑器DOM截图) + var longImagePromise; + if (config.useDomPreview) { + longImagePromise = generateLongImageFromCanvases(canvases); + } else { + longImagePromise = generateLongImageBase64(); + } + + return longImagePromise.then(function(longBase64) { return { pages: pagesData, longImage: longBase64 }; }); }).then(function(result) {