diff --git a/app/common/tools/PhoneImage.php b/app/common/tools/PhoneImage.php
index 3735497..621f077 100644
--- a/app/common/tools/PhoneImage.php
+++ b/app/common/tools/PhoneImage.php
@@ -20,6 +20,7 @@ class PhoneImage implements PostOutputManagerInterface
'watermark' => ['type' => 'text', 'default' => ''],
'pageAlignments' => ['type' => 'json', 'default' => '{}'],
'fontScale' => ['type' => 'number', 'default' => 1, 'min' => 0.5, 'max' => 2.0],
+ 'tableFontScale' => ['type' => 'number', 'default' => 1, 'min' => 0.5, 'max' => 2.0],
];
}
@@ -45,6 +46,12 @@ class PhoneImage implements PostOutputManagerInterface
return false;
}
}
+ if (isset($config['tableFontScale'])) {
+ $scale = floatval($config['tableFontScale']);
+ if ($scale < 0.5 || $scale > 2.0) {
+ return false;
+ }
+ }
// watermark 是文本类型,无需特殊验证
// pageAlignments 是 JSON 类型,存储时由框架自动序列化
diff --git a/public/static/css/phone-image-templates.css b/public/static/css/phone-image-templates.css
index 249016e..0d93485 100644
--- a/public/static/css/phone-image-templates.css
+++ b/public/static/css/phone-image-templates.css
@@ -50,6 +50,7 @@
--pi-radius: 12px;
/* 字体 */
--pi-font-family: 'Source Han Sans', 'SourceHanSans-Normal', sans-serif;
+ --pi-table-font-scale: 1;
}
/* ============================================
@@ -251,6 +252,16 @@
margin-top: var(--pi-spacing-sm);
}
+/* 内容页 - 作者声明 */
+.author-credit {
+ font-size: 11px;
+ color: #999;
+ padding-bottom: 8px;
+ margin-bottom: 12px;
+ border-bottom: 1px solid #eee;
+ text-indent: 0;
+}
+
/* 内容页 - 正文区域 */
.page-content {
flex: 1;
@@ -600,7 +611,7 @@
============================================ */
/* 表格美化样式(html2canvas兼容) */
-.page-content table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: calc(13px * var(--pi-font-scale, 1)); }
+.page-content table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: calc(13px * var(--pi-table-font-scale, 1)); }
.page-content th { background-color: #f0f0f0; font-weight: bold; padding: 4px 10px; border: 1px solid #ddd; text-align: left; }
.page-content td { padding: 4px 10px; border: 1px solid #ddd; }
.page-content tr:nth-child(even) td { background-color: #f9f9f9; }
@@ -628,7 +639,7 @@
}
/* 中间栏表格样式 */
-.content-flow-block table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: calc(13px * var(--pi-font-scale, 1)); }
+.content-flow-block table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: calc(13px * var(--pi-table-font-scale, 1)); }
.content-flow-block th { background-color: #f0f0f0; font-weight: bold; padding: 4px 10px; border: 1px solid #ddd; text-align: left; }
.content-flow-block td { padding: 4px 10px; border: 1px solid #ddd; }
.content-flow-block tr:nth-child(even) td { background-color: #f9f9f9; }
diff --git a/public/static/js/phone-image.js b/public/static/js/phone-image.js
index 514b149..3cec47e 100644
--- a/public/static/js/phone-image.js
+++ b/public/static/js/phone-image.js
@@ -99,7 +99,8 @@ var PhoneImageEngine = (function () {
douyin: { width: 540, height: 960 }
},
contentPadding: 20,
- fontScale: 1
+ fontScale: 1,
+ tableFontScale: 1
};
// ===== 文章数据 =====
@@ -159,10 +160,17 @@ var PhoneImageEngine = (function () {
*/
function applyFontScale() {
var scale = config.fontScale || 1;
+ var tableScale = config.tableFontScale || 1;
var preview = document.getElementById('render-preview');
- if (preview) preview.style.setProperty('--pi-font-scale', scale);
+ if (preview) {
+ preview.style.setProperty('--pi-font-scale', scale);
+ preview.style.setProperty('--pi-table-font-scale', tableScale);
+ }
var staging = document.getElementById('render-staging');
- if (staging) staging.style.setProperty('--pi-font-scale', scale);
+ if (staging) {
+ staging.style.setProperty('--pi-font-scale', scale);
+ staging.style.setProperty('--pi-table-font-scale', tableScale);
+ }
}
/**
@@ -914,6 +922,11 @@ var PhoneImageEngine = (function () {
html += '';
}
+ // 作者声明(所有内容页)
+ if (postData.author_name) {
+ html += '
';
for (var i = 0; i < blocks.length; i++) {
@@ -1125,17 +1138,19 @@ var PhoneImageEngine = (function () {
// 保存模式: 高质量绘制原图到canvas
renderPureImageToCanvas(pureSrc, opts.sizeConfig.width, opts.sizeConfig.height).then(function (canvas) {
results.push(canvas);
+ if (opts.streaming && opts.onPageReady) opts.onPageReady(canvas, idx);
if (pageProgressCallback) pageProgressCallback(idx + 1, total);
idx++;
captureNext();
}).catch(function () {
// 加载失败,回退到html2canvas
- capturePageViaHtml2Canvas($elem, $staging, opts, results, deferred, function () { if (pageProgressCallback) pageProgressCallback(idx + 1, total); idx++; captureNext(); });
+ capturePageViaHtml2Canvas($elem, $staging, opts, results, deferred, function () { if (opts.streaming && opts.onPageReady) opts.onPageReady(results[results.length - 1], idx); if (pageProgressCallback) pageProgressCallback(idx + 1, total); idx++; captureNext(); });
});
return;
} else {
// 缩略图模式: 直接用src
results.push(pureSrc);
+ if (opts.streaming && opts.onPageReady) opts.onPageReady(pureSrc, idx);
if (pageProgressCallback) pageProgressCallback(idx + 1, total);
idx++;
captureNext();
@@ -1144,7 +1159,7 @@ var PhoneImageEngine = (function () {
}
}
- capturePageViaHtml2Canvas($elem, $staging, opts, results, deferred, function () { idx++; captureNext(); });
+ capturePageViaHtml2Canvas($elem, $staging, opts, results, deferred, function () { if (opts.streaming && opts.onPageReady) opts.onPageReady(results[results.length - 1], idx); idx++; captureNext(); });
}
captureNext();
@@ -1183,15 +1198,22 @@ var PhoneImageEngine = (function () {
function renderThumbnails(sizeConfig) {
var deferred = $.Deferred();
+ // 流式渲染:先清空容器,再逐页追加缩略图
+ initThumbnails(sizeConfig);
+
doCapturePages({
scale: 1,
outputCanvas: false,
quality: 0.85,
- sizeConfig: sizeConfig
+ sizeConfig: sizeConfig,
+ streaming: true,
+ onPageReady: function (dataUrl, pageIndex) {
+ appendThumbnail(dataUrl, pageIndex, sizeConfig, pages[pageIndex]);
+ }
}, function (current, total) {
PhoneImageLogPanel.log('截图: 第 ' + current + '/' + total + ' 页', 'info');
}).then(function (dataUrls) {
- displayThumbnails(dataUrls, sizeConfig);
+ updateThumbnailPageNumbers(dataUrls.length);
deferred.resolve(pages);
}).catch(function (err) {
deferred.reject(err);
@@ -1201,51 +1223,69 @@ var PhoneImageEngine = (function () {
}
/**
- * 在右侧显示缩略图
- * @param {Array} dataUrls - base64 图片数据
+ * 初始化缩略图容器(流式渲染第一步:清空容器)
* @param {Object} sizeConfig
*/
- function displayThumbnails(dataUrls, sizeConfig) {
+ function initThumbnails(sizeConfig) {
var $preview = $('#paginated-preview');
if (!$preview.length) return;
-
$preview.empty();
- if (dataUrls.length === 0) return;
+ }
+
+ /**
+ * 追加单个缩略图到预览区(流式渲染:每页截图完成后立即追加)
+ * @param {string} dataUrl - base64 图片数据或图片src
+ * @param {number} pageIndex - 页面索引(0-based)
+ * @param {Object} sizeConfig
+ * @param {Object} pageData - 页面对象 { type, pageNum, ... }
+ */
+ function appendThumbnail(dataUrl, pageIndex, sizeConfig, pageData) {
+ var $preview = $('#paginated-preview');
+ if (!$preview.length) 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 $item = $('
');
+ $item.css('width', thumbWidth + 'px');
- var $img = $('
![]()
');
- $img.attr('src', dataUrls[i]);
- $img.css('width', thumbWidth + 'px');
- $item.append($img);
+ var $img = $('
![]()
');
+ $img.attr('src', dataUrl);
+ $img.css('width', thumbWidth + 'px');
+ $item.append($img);
- // 页码
- var $pageNum = $('
' + (i + 1) + '/' + dataUrls.length + '');
- $item.append($pageNum);
+ // 页码(初始只显示序号,全部完成后更新为 N/M)
+ var $pageNum = $('
' + (pageIndex + 1) + '');
+ $item.append($pageNum);
- // 对齐按钮(仅内容页)
- if (pages[i] && pages[i].type === 'content') {
- var pageNum = pages[i].pageNum || (i);
- var currentAlign = (config.pageAlignments && config.pageAlignments[pageNum]) || 'top';
- var isActiveCenter = currentAlign === 'center';
- var isActiveBottom = currentAlign === 'bottom';
- var activeClass = isActiveCenter ? ' active-center' : (isActiveBottom ? ' active-bottom' : '');
- var symbol = isActiveCenter ? '\u2195' : (isActiveBottom ? '\u2193' : '\u2191');
- var $toggle = $('
');
- $item.append($toggle);
- }
-
- $preview.append($item);
+ // 对齐按钮(仅内容页)
+ if (pageData && pageData.type === 'content') {
+ var pageNum = pageData.pageNum || (pageIndex);
+ var currentAlign = (config.pageAlignments && config.pageAlignments[pageNum]) || 'top';
+ var isActiveCenter = currentAlign === 'center';
+ var isActiveBottom = currentAlign === 'bottom';
+ var activeClass = isActiveCenter ? ' active-center' : (isActiveBottom ? ' active-bottom' : '');
+ var symbol = isActiveCenter ? '\u2195' : (isActiveBottom ? '\u2193' : '\u2191');
+ var $toggle = $('
');
+ $item.append($toggle);
}
+
+ $preview.append($item);
+ }
+
+ /**
+ * 更新所有缩略图页码为 N/M 格式(流式渲染全部完成后调用)
+ * @param {number} totalPages
+ */
+ function updateThumbnailPageNumbers(totalPages) {
+ var $items = $('#paginated-preview .preview-thumb-item');
+ $items.each(function (i) {
+ $(this).find('.preview-thumb-page-num').text((i + 1) + '/' + totalPages);
+ });
}
/**
diff --git a/view/admin/post/phone_image.html b/view/admin/post/phone_image.html
index 5af8887..976aeb9 100644
--- a/view/admin/post/phone_image.html
+++ b/view/admin/post/phone_image.html
@@ -285,7 +285,8 @@
var currentConfig = {
size: 'xiaohongshu',
watermark: '',
- fontScale: 1
+ fontScale: 1,
+ tableFontScale: 1
};
var postData = {
@@ -306,13 +307,15 @@
size: savedConfig.size || 'xiaohongshu',
watermark: savedConfig.watermark || '',
pageAlignments: savedConfig.pageAlignments || {},
- fontScale: savedConfig.fontScale || 1
+ fontScale: savedConfig.fontScale || 1,
+ tableFontScale: savedConfig.tableFontScale || 1
};
// 同步当前配置
currentConfig.size = initConfig.size;
currentConfig.watermark = initConfig.watermark;
currentConfig.fontScale = initConfig.fontScale;
+ currentConfig.tableFontScale = initConfig.tableFontScale;
// ========== wangeditor 初始化 ==========
var E = window.wangEditor;
@@ -467,6 +470,7 @@
size: currentConfig.size,
watermark: currentConfig.watermark,
fontScale: currentConfig.fontScale || 1,
+ tableFontScale: currentConfig.tableFontScale || 1,
content_html: PhoneImageEngine.getContentHtml()
}, saveConfigUrl).then(function(data) {
if (data.output_id) lastOutputId = data.output_id;
@@ -533,13 +537,27 @@
settingsHtml += '
';
settingsHtml += '
';
settingsHtml += '
';
+ settingsHtml += '
';
settingsHtml += '';
settingsHtml += '
';
layer.open({
type: 1,
title: '排版设置',
- area: ['400px', '220px'],
+ area: ['400px', '280px'],
content: settingsHtml,
btn: ['确定', '取消'],
success: function (layero) {
@@ -548,8 +566,10 @@
yes: function (index, layero) {
currentConfig.size = layero.find('[name="s_size"]').val();
currentConfig.watermark = layero.find('[name="s_watermark"]').val();
+ var tblScaleSel = layero.find('[name="s_table_font_scale"]').val();
+ currentConfig.tableFontScale = tblScaleSel === 'custom' ? (currentConfig.tableFontScale || 1) : parseFloat(tblScaleSel);
layer.close(index);
- PhoneImageLogPanel.log('应用新设置: 尺寸=' + currentConfig.size, 'info');
+ PhoneImageLogPanel.log('应用新设置: 尺寸=' + currentConfig.size + ', 表格字号=' + (currentConfig.tableFontScale || 1) + 'x', 'info');
doRender();
}
});
@@ -562,7 +582,8 @@
renderTimer = setTimeout(function () {
var newConfig = {
size: currentConfig.size,
- watermark: currentConfig.watermark
+ watermark: currentConfig.watermark,
+ tableFontScale: currentConfig.tableFontScale || 1
};
if (extraConfig) {
$.extend(newConfig, extraConfig);
@@ -634,6 +655,7 @@
size: currentConfig.size,
watermark: currentConfig.watermark,
fontScale: currentConfig.fontScale || 1,
+ tableFontScale: currentConfig.tableFontScale || 1,
content_html: PhoneImageEngine.getContentHtml()
}, saveConfigUrl).then(function (data) {
if (data.output_id) lastOutputId = data.output_id;
@@ -662,6 +684,7 @@
size: currentConfig.size,
watermark: currentConfig.watermark,
fontScale: currentConfig.fontScale || 1,
+ tableFontScale: currentConfig.tableFontScale || 1,
content_html: PhoneImageEngine.getContentHtml()
}, saveConfigUrl).then(function (data) {
if (data.output_id) lastOutputId = data.output_id;
@@ -688,6 +711,7 @@
size: currentConfig.size,
watermark: currentConfig.watermark,
fontScale: currentConfig.fontScale || 1,
+ tableFontScale: currentConfig.tableFontScale || 1,
content_html: PhoneImageEngine.getContentHtml()
}, function(current, total, canvas) {
if (total > 0) {
@@ -779,6 +803,7 @@
if (cfg.watermark !== undefined) historyConfig.watermark = cfg.watermark;
if (cfg.pageAlignments) historyConfig.pageAlignments = cfg.pageAlignments;
if (cfg.fontScale !== undefined) historyConfig.fontScale = cfg.fontScale;
+ if (cfg.tableFontScale !== undefined) historyConfig.tableFontScale = cfg.tableFontScale;
// 同步当前配置
if (cfg.size) currentConfig.size = cfg.size;
@@ -787,6 +812,9 @@
currentConfig.fontScale = cfg.fontScale;
setFontScaleUI(cfg.fontScale);
}
+ if (cfg.tableFontScale !== undefined) {
+ currentConfig.tableFontScale = cfg.tableFontScale;
+ }
lastOutputId = outputId;
PhoneImageLogPanel.log('历史配置已加载', 'success');