refactor(phone-image): 清理死代码、修复历史记录和媒体标签安全移除

T8: 删除estimateImageHeight/estimateTableHeight/showGeneratedThumbnails/switchSize/
    getConfig/getPages/renderContentFlow等未使用函数,exportLongImage添加render锁检查
T9: loadFromHistory恢复pageAlignments,font_size→fontSize命名统一(PHP+JS双向兼容),
    修复历史加载时fontSize显示值bug
T10: preprocessContent移除iframe/video/svg/embed/object标签,
    封面图添加onerror处理
This commit is contained in:
augushong
2026-05-07 21:53:03 +08:00
parent 491a71bd44
commit 90b4b1d5f2
3 changed files with 37 additions and 86 deletions

View File

@@ -18,7 +18,7 @@ class PhoneImage implements PostOutputManagerInterface
'template' => ['type' => 'select', 'options' => ['minimal', 'magazine', 'mixed'], 'default' => 'minimal'],
'size' => ['type' => 'select', 'options' => ['xiaohongshu', 'douyin'], 'default' => 'xiaohongshu'],
'font' => ['type' => 'select', 'options' => ['source-han-sans', 'alibaba-puhuiti', 'lxgw-wenkai'], 'default' => 'source-han-sans'],
'font_size' => ['type' => 'number', 'default' => 14, 'min' => 10, 'max' => 24],
'fontSize' => ['type' => 'number', 'default' => 14, 'min' => 10, 'max' => 24],
'watermark' => ['type' => 'text', 'default' => ''],
];
}
@@ -39,9 +39,9 @@ class PhoneImage implements PostOutputManagerInterface
if (isset($config['font']) && !in_array($config['font'], $fields['font']['options'])) {
return false;
}
if (isset($config['font_size'])) {
$size = intval($config['font_size']);
if ($size < $fields['font_size']['min'] || $size > $fields['font_size']['max']) {
if (isset($config['fontSize'])) {
$size = intval($config['fontSize']);
if ($size < $fields['fontSize']['min'] || $size > $fields['fontSize']['max']) {
return false;
}
}

View File

@@ -227,6 +227,14 @@ var PhoneImageEngine = (function () {
if (!html) return '';
html = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '');
html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
// 移除不支持的媒体标签
html = html.replace(/<iframe[\s\S]*?<\/iframe>/gi, '[媒体内容]');
html = html.replace(/<video[\s\S]*?<\/video>/gi, '[媒体内容]');
html = html.replace(/<svg[\s\S]*?<\/svg>/gi, '');
html = html.replace(/<embed[^>]*>/gi, '');
html = html.replace(/<object[\s\S]*?<\/object>/gi, '');
// 清除空段落
html = html.replace(/<p[^>]*>\s*<\/p>/gi, '');
@@ -357,7 +365,6 @@ var PhoneImageEngine = (function () {
block.estimatedHeight = 0;
block.needsMeasurement = true;
}
block.$img = $el;
break;
case 'ul':
case 'ol':
@@ -415,22 +422,6 @@ var PhoneImageEngine = (function () {
return lines * hSize * 1.4 + 20; // heading行高1.4 + 20px上下间距
}
/**
* 估算图片高度
* 默认按内容区宽度的60%估算
*/
function estimateImageHeight($img) {
var w = parseInt($img.attr('width'), 10);
var h = parseInt($img.attr('height'), 10);
if (w > 0 && h > 0) {
// 按比例缩放到内容宽度
var ratio = getContentWidth() / w;
return Math.round(h * ratio) + 10;
}
// 无尺寸信息时默认300px
return 310;
}
/**
* 估算列表高度
*/
@@ -447,15 +438,6 @@ var PhoneImageEngine = (function () {
return estimateTextHeight(text, config.fontSize) + 10;
}
/**
* 估算表格高度 (已废弃 - DOM实测替代)
* @deprecated 使用 measureBlockHeights() 替代
*/
function estimateTableHeight($table) {
// DOM实测将覆盖此值返回0作为占位
return 0;
}
// ===== 分页核心算法 =====
/**
@@ -644,7 +626,7 @@ var PhoneImageEngine = (function () {
html += '" style="width:' + sizeConfig.width + 'px;height:' + sizeConfig.height + 'px;">';
if (hasCover) {
html += '<img class="cover-image" src="' + escapeHtml(postData.poster) + '" alt="">';
html += '<img class="cover-image" src="' + escapeHtml(postData.poster) + '" alt="" onerror="this.style.display=\'none\'">';
html += '<div class="cover-title">' + escapeHtml(postData.title) + '</div>';
if (postData.desc) {
html += '<div class="cover-subtitle">' + escapeHtml(postData.desc) + '</div>';
@@ -1409,34 +1391,6 @@ var PhoneImageEngine = (function () {
return deferred.promise();
}
/**
* 将生成的canvas数组显示为缩略图
* @param {Array} canvases canvas对象数组
* @param {string} containerSelector 容器选择器
*/
function showGeneratedThumbnails(canvases, containerSelector) {
var $container = $(containerSelector);
$container.empty();
for (var i = 0; i < canvases.length; i++) {
var thumb = canvases[i].toDataURL('image/jpeg', 0.5);
var $item = $('<div class="phone-thumb-item" data-index="' + i + '">' +
'<img src="' + thumb + '" alt="第' + (i + 1) + '页">' +
'<span class="phone-thumb-page">' + (i + 1) + '</span>' +
'</div>');
$container.append($item);
}
}
// ===== 配置切换 =====
/**
* 切换尺寸
*/
function switchSize(name) {
config.size = name;
}
// ===== 逐页对齐 =====
/**
@@ -1456,6 +1410,13 @@ var PhoneImageEngine = (function () {
*/
function exportLongImage() {
var deferred = $.Deferred();
if (render._locked) {
layer.msg('请等待渲染完成');
deferred.reject('rendering');
return deferred.promise();
}
var $flow = $('#content-flow');
if (!$flow.length) {
deferred.reject('内容流容器不存在');
@@ -1511,20 +1472,6 @@ var PhoneImageEngine = (function () {
return div.innerHTML;
}
/**
* 获取当前配置
*/
function getConfig() {
return $.extend({}, config);
}
/**
* 获取分页结果
*/
function getPages() {
return pages.slice();
}
// ===== 公开API =====
return {
init: init,
@@ -1532,12 +1479,7 @@ var PhoneImageEngine = (function () {
generateImages: generateImages,
saveImages: saveImages,
saveConfig: saveConfig,
showGeneratedThumbnails: showGeneratedThumbnails,
switchSize: switchSize,
getConfig: getConfig,
getPages: getPages,
setPageAlignment: setPageAlignment,
renderContentFlow: renderContentFlow,
insertPageBreak: insertPageBreak,
removePageBreak: removePageBreak,
exportLongImage: exportLongImage

View File

@@ -223,15 +223,19 @@
});
var renderTimer = null;
function doRender() {
function doRender(extraConfig) {
clearTimeout(renderTimer);
renderTimer = setTimeout(function() {
var fontSize = parseInt($('[name="fontSize"]').val()) || 14;
PhoneImageEngine.init(postData, {
var initConfig = {
size: $('[name="size"]').val(),
fontSize: fontSize,
watermark: $('[name="watermark"]').val()
});
};
if (extraConfig) {
$.extend(initConfig, extraConfig);
}
PhoneImageEngine.init(postData, initConfig);
var loadIdx = layer.load();
PhoneImageEngine.render().then(function(pages) {
layer.close(loadIdx);
@@ -377,9 +381,10 @@
$('[name="size"]').val(cfg.size);
form.render('select');
}
if (cfg.fontSize) {
$('[name="fontSize"]').val(cfg.fontSize);
$('#fontSizeValue').text(cfg.fontSize + 'px');
if (cfg.fontSize || cfg.font_size) {
var fs = cfg.fontSize || cfg.font_size;
$('[name="fontSize"]').val(fs);
$('#fontSizeValue').text(fs + 'px');
}
if (cfg.watermark !== undefined) {
$('[name="watermark"]').val(cfg.watermark);
@@ -394,9 +399,13 @@
// 关闭历史弹窗
layer.closeAll();
// 重新初始化引擎并渲染
// 重新初始化引擎并渲染恢复pageAlignments
lastOutputId = outputId;
doRender();
var renderConfig = {};
if (cfg.pageAlignments) {
renderConfig.pageAlignments = cfg.pageAlignments;
}
doRender(renderConfig);
layer.msg('已加载历史配置');
}).fail(function () {
layer.close(loadIdx2);