diff --git a/public/static/js/phone-image.js b/public/static/js/phone-image.js
index 59775a9..7b3578c 100644
--- a/public/static/js/phone-image.js
+++ b/public/static/js/phone-image.js
@@ -39,6 +39,20 @@ var PhoneImageEngine = (function () {
// ===== 分页结果 =====
var pages = [];
+ /**
+ * 确保隐藏渲染区域存在
+ */
+ function ensureStaging() {
+ if ($('#render-staging').length === 0) {
+ $('
').css({
+ position: 'fixed',
+ left: '-9999px',
+ top: 0,
+ visibility: 'hidden'
+ }).appendTo('body');
+ }
+ }
+
/**
* 初始化引擎
* @param {Object} options - {postId, title, desc, coverText, contentHtml, poster, authorName, createTime, categoryName}
@@ -87,9 +101,10 @@ var PhoneImageEngine = (function () {
/**
* 渲染排版预览 - 生成分页后的HTML
* 两遍遍历: 先算总页数, 再带页码生成
- * @returns {Array} 页面数组,每项是 { type, html, pageNum }
+ * @returns {jQuery Deferred} resolves with pages array
*/
function render() {
+ var deferred = $.Deferred();
pages = [];
var sizeConfig = config.sizes[config.size] || config.sizes.xiaohongshu;
var pageHeight = sizeConfig.height;
@@ -120,16 +135,17 @@ var PhoneImageEngine = (function () {
}
}
- // 渲染到DOM
- renderToDOM(sizeConfig);
-
- // 渲染内容流(中间栏)
+ // 渲染内容流(中间栏)- 同步
renderContentFlow(blocks);
- // 渲染逐页对齐指示器
- renderAlignmentToggles();
+ // 渲染缩略图(右侧)- 异步截图
+ renderThumbnails(sizeConfig).then(function () {
+ deferred.resolve(pages);
+ }).catch(function (err) {
+ deferred.reject(err);
+ });
- return pages;
+ return deferred.promise();
}
/**
@@ -606,48 +622,167 @@ var PhoneImageEngine = (function () {
// ===== DOM渲染 =====
/**
- * 全尺寸平铺渲染到 #paginated-preview
+ * 渲染到隐藏区域 → 截图 → 显示缩略图
+ * @param {Object} sizeConfig
+ * @returns {jQuery Deferred} resolves with pages array
*/
- function renderToDOM(sizeConfig) {
+ function renderThumbnails(sizeConfig) {
+ var deferred = $.Deferred();
+ ensureStaging();
+
+ var $staging = $('#render-staging');
+ $staging.empty();
+
+ // 渲染所有页面到隐藏区域
+ for (var i = 0; i < pages.length; i++) {
+ $staging.append(pages[i].html);
+ }
+
+ // 等待一帧确保 DOM 渲染完成
+ requestAnimationFrame(function () {
+ var $pageElems = $staging.find('.phone-image-page');
+ if ($pageElems.length === 0) {
+ displayThumbnails([], sizeConfig);
+ deferred.resolve(pages);
+ return;
+ }
+
+ // 串行截图每一页
+ var thumbnailDataUrls = [];
+ var idx = 0;
+ var total = $pageElems.length;
+
+ function captureNext() {
+ if (idx >= total) {
+ $staging.empty();
+ displayThumbnails(thumbnailDataUrls, sizeConfig);
+ deferred.resolve(pages);
+ return;
+ }
+
+ var $elem = $pageElems.eq(idx);
+ html2canvas($elem[0], {
+ scale: 1,
+ useCORS: true,
+ backgroundColor: '#ffffff',
+ width: $elem.outerWidth(),
+ height: $elem.outerHeight(),
+ logging: false
+ }).then(function (canvas) {
+ thumbnailDataUrls.push(canvas.toDataURL('image/jpeg', 0.85));
+ idx++;
+ captureNext();
+ }).catch(function (err) {
+ deferred.reject('截图失败(第' + (idx + 1) + '页): ' + err);
+ });
+ }
+
+ captureNext();
+ });
+
+ return deferred.promise();
+ }
+
+ /**
+ * 在右侧显示缩略图
+ * @param {Array} dataUrls - base64 图片数据
+ * @param {Object} sizeConfig
+ */
+ function displayThumbnails(dataUrls, sizeConfig) {
var $preview = $('#paginated-preview');
if (!$preview.length) return;
$preview.empty();
- for (var i = 0; i < pages.length; i++) {
- $preview.append(pages[i].html);
+ if (dataUrls.length === 0) return;
+
+ // 计算缩放比例:缩略图高度 = 容器高度 - 40(页码标签) - 40(上下padding)
+ var containerHeight = $preview.parent().height() || 700;
+ var scaleRatio = (containerHeight - 80) / sizeConfig.height;
+ var thumbWidth = Math.round(sizeConfig.width * scaleRatio);
+
+ for (var i = 0; i < dataUrls.length; i++) {
+ var $item = $('
');
+ $item.css('width', thumbWidth + 'px');
+
+ var $img = $('
![]()
');
+ $img.attr('src', dataUrls[i]);
+ $img.css('width', thumbWidth + 'px');
+ $item.append($img);
+
+ // 页码
+ var $pageNum = $('
' + (i + 1) + '/' + dataUrls.length + '');
+ $item.append($pageNum);
+
+ $preview.append($item);
}
}
+ /**
+ * 将页面渲染到隐藏区域并高质量截图(用于保存)
+ * @returns {jQuery Deferred} resolves with canvas array
+ */
+ function capturePagesFromStaging() {
+ var deferred = $.Deferred();
+ ensureStaging();
+
+ var $staging = $('#render-staging');
+ $staging.empty();
+
+ var sizeConfig = config.sizes[config.size] || config.sizes.xiaohongshu;
+
+ // 添加 size class wrapper
+ var sizeClass = 'size-' + config.size;
+ var $wrapper = $('
');
+ for (var i = 0; i < pages.length; i++) {
+ $wrapper.append(pages[i].html);
+ }
+ $staging.append($wrapper);
+
+ requestAnimationFrame(function () {
+ var $pageElems = $staging.find('.phone-image-page');
+ if ($pageElems.length === 0) {
+ deferred.reject('没有可渲染的页面');
+ return;
+ }
+
+ var canvases = [];
+ var idx = 0;
+ var total = $pageElems.length;
+
+ function captureNext() {
+ if (idx >= total) {
+ $staging.empty();
+ deferred.resolve(canvases);
+ return;
+ }
+
+ html2canvas($pageElems.eq(idx)[0], {
+ scale: 2,
+ useCORS: true,
+ backgroundColor: '#ffffff',
+ width: $pageElems.eq(idx).outerWidth(),
+ height: $pageElems.eq(idx).outerHeight(),
+ logging: false
+ }).then(function (canvas) {
+ canvases.push(canvas);
+ idx++;
+ captureNext();
+ }).catch(function (err) {
+ deferred.reject('截图失败(第' + (idx + 1) + '页): ' + err);
+ });
+ }
+
+ captureNext();
+ });
+
+ return deferred.promise();
+ }
+
/**
* 为每页渲染逐页对齐切换指示器
*/
function renderAlignmentToggles() {
- var $pages = $('#paginated-preview .phone-image-page');
- $pages.each(function (index) {
- var $page = $(this);
-
- // 只给内容页添加(跳过封面页和总结页)
- if (!$page.hasClass('page-body')) return;
-
- // 通过 pages 数组获取真正的 pageNum
- var pageNum = index;
- if (pages[index] && pages[index].pageNum) {
- pageNum = pages[index].pageNum;
- }
-
- // 移除旧的 toggle
- $page.find('.page-alignment-toggle').remove();
-
- // 当前对齐状态
- var currentAlign = (config.pageAlignments && config.pageAlignments[pageNum]) || 'top';
- var isActiveCenter = currentAlign === 'center';
-
- var $toggle = $('');
-
- $page.append($toggle);
- });
+ // No-op: alignment toggles are now rendered as part of displayThumbnails
}
/**
@@ -737,49 +872,27 @@ var PhoneImageEngine = (function () {
// ===== 图片生成 =====
/**
- * 逐页截图生成图片(简化版:所有页面已在DOM中可见)
+ * 逐页截图生成图片(使用隐藏渲染区域高质量截图)
* @param {Function} [onProgress] 进度回调 function(currentIndex, totalPages, canvas)
* @returns {Promise} jQuery Deferred, resolves with array of canvas objects
*/
function generateImages(onProgress) {
var deferred = $.Deferred();
- var canvases = [];
- var $pages = $('#paginated-preview .phone-image-page');
- if ($pages.length === 0) {
+ if (pages.length === 0) {
deferred.reject('没有可渲染的页面');
return deferred.promise();
}
- var index = 0;
- var total = $pages.length;
+ if (onProgress) onProgress(0, pages.length, null);
- function captureNext() {
- if (index >= total) {
- if (onProgress) onProgress(total, total, null);
- deferred.resolve(canvases);
- return;
- }
+ capturePagesFromStaging().then(function (canvases) {
+ if (onProgress) onProgress(canvases.length, canvases.length, null);
+ deferred.resolve(canvases);
+ }).catch(function (err) {
+ deferred.reject(err);
+ });
- html2canvas($pages.eq(index)[0], {
- scale: 2,
- useCORS: true,
- backgroundColor: '#ffffff',
- width: $pages.eq(index).outerWidth(),
- height: $pages.eq(index).outerHeight(),
- logging: false
- }).then(function (canvas) {
- canvases.push(canvas);
- index++;
- if (onProgress) onProgress(index, total, canvas);
- captureNext();
- }).catch(function (err) {
- deferred.reject('截图失败(第' + (index + 1) + '页): ' + err);
- });
- }
-
- if (onProgress) onProgress(0, total, null);
- captureNext();
return deferred.promise();
}
diff --git a/view/admin/post/phone_image.html b/view/admin/post/phone_image.html
index 2aa4f95..c1e0631 100644
--- a/view/admin/post/phone_image.html
+++ b/view/admin/post/phone_image.html
@@ -222,8 +222,14 @@
fontSize: fontSize,
watermark: $('[name="watermark"]').val()
});
- var pages = PhoneImageEngine.render();
- layer.msg('排版完成,共 ' + pages.length + ' 页');
+ var loadIdx = layer.load();
+ PhoneImageEngine.render().then(function(pages) {
+ layer.close(loadIdx);
+ layer.msg('排版完成,共 ' + pages.length + ' 页');
+ }).catch(function(err) {
+ layer.close(loadIdx);
+ layer.msg('渲染失败: ' + err);
+ });
}, 300);
}