mirror of
https://gitee.com/ulthon/ulthon_information.git
synced 2026-07-01 16:32:48 +08:00
feat(phone-image): 添加字号倍数控制功能
- CSS: 新增 --pi-font-scale 变量,全量 font-size 支持 calc 缩放 - JS: config.fontScale 影响分页计算,applyFontScale() 同步CSS变量 - HTML: 渲染预览区 Slider 控件(0.5x~2.0x),拖动即时预览,松手完整渲染 - 后端: PhoneImage.php 新增 fontScale 配置字段和校验 - 所有保存路径(autoSave/save/generate)包含 fontScale 持久化
This commit is contained in:
@@ -19,6 +19,7 @@ class PhoneImage implements PostOutputManagerInterface
|
||||
'fontSize' => ['type' => 'number', 'default' => 14, 'min' => 10, 'max' => 24],
|
||||
'watermark' => ['type' => 'text', 'default' => ''],
|
||||
'pageAlignments' => ['type' => 'json', 'default' => '{}'],
|
||||
'fontScale' => ['type' => 'number', 'default' => 1, 'min' => 0.5, 'max' => 2.0],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -38,6 +39,12 @@ class PhoneImage implements PostOutputManagerInterface
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (isset($config['fontScale'])) {
|
||||
$scale = floatval($config['fontScale']);
|
||||
if ($scale < 0.5 || $scale > 2.0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// watermark 是文本类型,无需特殊验证
|
||||
// pageAlignments 是 JSON 类型,存储时由框架自动序列化
|
||||
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
|
||||
/* --- CSS Custom Properties (小红书经典风格) --- */
|
||||
:root {
|
||||
/* 字号缩放 */
|
||||
--pi-font-scale: 1;
|
||||
/* 字号 */
|
||||
--pi-font-size-base: 14px;
|
||||
--pi-font-size-title: 26px;
|
||||
--pi-font-size-subtitle: 16px;
|
||||
--pi-font-size-small: 12px;
|
||||
--pi-font-size-base: calc(14px * var(--pi-font-scale, 1));
|
||||
--pi-font-size-title: calc(26px * var(--pi-font-scale, 1));
|
||||
--pi-font-size-subtitle: calc(16px * var(--pi-font-scale, 1));
|
||||
--pi-font-size-small: calc(12px * var(--pi-font-scale, 1));
|
||||
/* 行高 */
|
||||
--pi-line-height: 1.8;
|
||||
/* 间距 */
|
||||
@@ -66,7 +68,7 @@
|
||||
position: absolute;
|
||||
bottom: 60px;
|
||||
right: 20px;
|
||||
font-size: 12px;
|
||||
font-size: calc(12px * var(--pi-font-scale, 1));
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
@@ -105,7 +107,7 @@
|
||||
}
|
||||
|
||||
.page-cover .cover-title {
|
||||
font-size: 28px;
|
||||
font-size: calc(28px * var(--pi-font-scale, 1));
|
||||
font-weight: normal;
|
||||
letter-spacing: 2px;
|
||||
line-height: 1.5;
|
||||
@@ -163,7 +165,7 @@
|
||||
}
|
||||
|
||||
.page-cover.no-cover-image .cover-no-img-title {
|
||||
font-size: 36px;
|
||||
font-size: calc(36px * var(--pi-font-scale, 1));
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
color: var(--pi-color-text);
|
||||
@@ -220,7 +222,7 @@
|
||||
}
|
||||
|
||||
.page-header .page-title {
|
||||
font-size: 20px;
|
||||
font-size: calc(20px * var(--pi-font-scale, 1));
|
||||
font-weight: normal;
|
||||
letter-spacing: 1px;
|
||||
color: #666;
|
||||
@@ -250,7 +252,7 @@
|
||||
|
||||
.page-content h2 {
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
font-size: calc(18px * var(--pi-font-scale, 1));
|
||||
letter-spacing: 1px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 12px;
|
||||
@@ -259,28 +261,28 @@
|
||||
|
||||
.page-content h3 {
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
font-size: calc(16px * var(--pi-font-scale, 1));
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.page-content h4 {
|
||||
font-size: 14px;
|
||||
font-size: calc(14px * var(--pi-font-scale, 1));
|
||||
font-weight: bold;
|
||||
margin-top: var(--pi-spacing-sm);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.page-content h5 {
|
||||
font-size: 13px;
|
||||
font-size: calc(13px * var(--pi-font-scale, 1));
|
||||
font-weight: bold;
|
||||
margin-top: var(--pi-spacing-sm);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.page-content h6 {
|
||||
font-size: 12px;
|
||||
font-size: calc(12px * var(--pi-font-scale, 1));
|
||||
font-weight: bold;
|
||||
margin-top: var(--pi-spacing-sm);
|
||||
margin-bottom: 5px;
|
||||
@@ -310,7 +312,7 @@
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
margin-top: var(--pi-spacing-sm);
|
||||
font-size: 11px;
|
||||
font-size: calc(11px * var(--pi-font-scale, 1));
|
||||
color: #bbb;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -330,7 +332,7 @@
|
||||
.page-summary .summary-title {
|
||||
font-weight: normal;
|
||||
letter-spacing: 2px;
|
||||
font-size: 22px;
|
||||
font-size: calc(22px * var(--pi-font-scale, 1));
|
||||
margin-bottom: var(--pi-spacing);
|
||||
color: var(--pi-color-text);
|
||||
}
|
||||
@@ -447,7 +449,7 @@
|
||||
border-radius: 50%;
|
||||
background: var(--pi-color-accent);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-size: calc(14px * var(--pi-font-scale, 1));
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
border: none;
|
||||
@@ -473,7 +475,7 @@
|
||||
}
|
||||
|
||||
.page-break-marker .break-marker-label {
|
||||
font-size: 12px;
|
||||
font-size: calc(12px * var(--pi-font-scale, 1));
|
||||
color: #ff4d4f;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -488,7 +490,7 @@
|
||||
border-radius: 50%;
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-size: calc(14px * var(--pi-font-scale, 1));
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
border: none;
|
||||
@@ -529,7 +531,7 @@
|
||||
.preview-thumb-page-num {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-size: calc(12px * var(--pi-font-scale, 1));
|
||||
color: #999;
|
||||
margin-top: 6px;
|
||||
}
|
||||
@@ -547,7 +549,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-size: calc(14px * var(--pi-font-scale, 1));
|
||||
color: #666;
|
||||
user-select: none;
|
||||
border: none;
|
||||
@@ -576,7 +578,7 @@
|
||||
============================================ */
|
||||
|
||||
/* 表格美化样式(html2canvas兼容) */
|
||||
.page-content table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 13px; }
|
||||
.page-content table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: calc(13px * var(--pi-font-scale, 1)); }
|
||||
.page-content th { background-color: #f0f0f0; font-weight: bold; padding: 8px 10px; border: 1px solid #ddd; text-align: left; }
|
||||
.page-content td { padding: 8px 10px; border: 1px solid #ddd; }
|
||||
.page-content tr:nth-child(even) td { background-color: #f9f9f9; }
|
||||
@@ -587,14 +589,14 @@
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
overflow-x: auto;
|
||||
font-size: 13px;
|
||||
font-size: calc(13px * var(--pi-font-scale, 1));
|
||||
line-height: 1.6;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.content-flow code {
|
||||
font-family: 'Courier New', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
font-size: calc(13px * var(--pi-font-scale, 1));
|
||||
}
|
||||
|
||||
.content-flow pre code {
|
||||
@@ -604,7 +606,7 @@
|
||||
}
|
||||
|
||||
/* 中间栏表格样式 */
|
||||
.content-flow-block table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 13px; }
|
||||
.content-flow-block table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: calc(13px * var(--pi-font-scale, 1)); }
|
||||
.content-flow-block th { background-color: #f0f0f0; font-weight: bold; padding: 8px 10px; border: 1px solid #ddd; text-align: left; }
|
||||
.content-flow-block td { padding: 8px 10px; border: 1px solid #ddd; }
|
||||
.content-flow-block tr:nth-child(even) td { background-color: #f9f9f9; }
|
||||
@@ -785,7 +787,7 @@ body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-norm
|
||||
|
||||
/* 预览区标题字号 */
|
||||
#render-preview h1 {
|
||||
font-size: 22px;
|
||||
font-size: calc(22px * var(--pi-font-scale, 1));
|
||||
font-weight: bold;
|
||||
letter-spacing: 1px;
|
||||
margin-top: 24px;
|
||||
@@ -795,7 +797,7 @@ body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-norm
|
||||
|
||||
#render-preview h2 {
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
font-size: calc(18px * var(--pi-font-scale, 1));
|
||||
letter-spacing: 1px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 12px;
|
||||
@@ -803,27 +805,27 @@ body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-norm
|
||||
|
||||
#render-preview h3 {
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
font-size: calc(16px * var(--pi-font-scale, 1));
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#render-preview h4 {
|
||||
font-size: 14px;
|
||||
font-size: calc(14px * var(--pi-font-scale, 1));
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#render-preview h5 {
|
||||
font-size: 13px;
|
||||
font-size: calc(13px * var(--pi-font-scale, 1));
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#render-preview h6 {
|
||||
font-size: 12px;
|
||||
font-size: calc(12px * var(--pi-font-scale, 1));
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
@@ -850,7 +852,7 @@ body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-norm
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 10px 0;
|
||||
font-size: 13px;
|
||||
font-size: calc(13px * var(--pi-font-scale, 1));
|
||||
}
|
||||
|
||||
#render-preview th {
|
||||
@@ -877,14 +879,14 @@ body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-norm
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
overflow-x: auto;
|
||||
font-size: 13px;
|
||||
font-size: calc(13px * var(--pi-font-scale, 1));
|
||||
line-height: 1.6;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
#render-preview code {
|
||||
font-family: 'Courier New', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
font-size: calc(13px * var(--pi-font-scale, 1));
|
||||
}
|
||||
|
||||
#render-preview pre code {
|
||||
|
||||
@@ -19,7 +19,8 @@ var PhoneImageEngine = (function () {
|
||||
xiaohongshu: { width: 540, height: 720 },
|
||||
douyin: { width: 540, height: 960 }
|
||||
},
|
||||
contentPadding: 20
|
||||
contentPadding: 20,
|
||||
fontScale: 1
|
||||
};
|
||||
|
||||
// ===== 文章数据 =====
|
||||
@@ -71,6 +72,15 @@ var PhoneImageEngine = (function () {
|
||||
if (userConfig) {
|
||||
$.extend(config, userConfig);
|
||||
}
|
||||
applyFontScale();
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用字号缩放到CSS变量
|
||||
*/
|
||||
function applyFontScale() {
|
||||
var scale = config.fontScale || 1;
|
||||
document.documentElement.style.setProperty('--pi-font-scale', scale);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -656,7 +666,7 @@ var PhoneImageEngine = (function () {
|
||||
}
|
||||
|
||||
// 按句子拆分:句号、问号、叹号、换行(兼容不支持 lookbehind 的浏览器)
|
||||
var effectiveFontSize = 14;
|
||||
var effectiveFontSize = 14 * (config.fontScale || 1);
|
||||
var parts = text.split(/([。!?\n])/);
|
||||
var sentences = [];
|
||||
var current = '';
|
||||
@@ -1269,6 +1279,7 @@ var PhoneImageEngine = (function () {
|
||||
config: {
|
||||
size: saveConfig.size || config.size,
|
||||
watermark: saveConfig.watermark || config.watermark,
|
||||
fontScale: config.fontScale || 1,
|
||||
pageAlignments: config.pageAlignments || {}
|
||||
},
|
||||
content_html: saveConfig.content_html || postData.content_html
|
||||
@@ -1488,6 +1499,7 @@ var PhoneImageEngine = (function () {
|
||||
if (newConfig) {
|
||||
$.extend(config, newConfig);
|
||||
}
|
||||
applyFontScale();
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -165,7 +165,15 @@
|
||||
|
||||
<!-- 中间:渲染预览区 -->
|
||||
<div class="render-preview-area">
|
||||
<div class="preview-header">渲染预览</div>
|
||||
<div class="preview-header">
|
||||
<span>渲染预览</span>
|
||||
<div style="display:inline-flex;align-items:center;gap:8px;float:right;">
|
||||
<span style="font-size:12px;color:#666;">字号</span>
|
||||
<input type="range" id="font-scale-slider" min="0.5" max="2.0" step="0.1" value="1.0"
|
||||
style="width:100px;vertical-align:middle;">
|
||||
<span id="font-scale-value" style="font-size:12px;color:#1890ff;min-width:32px;">1.0x</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="render-preview"></div>
|
||||
</div>
|
||||
|
||||
@@ -195,7 +203,8 @@
|
||||
// 当前排版配置(从设置弹框维护)
|
||||
var currentConfig = {
|
||||
size: 'xiaohongshu',
|
||||
watermark: ''
|
||||
watermark: '',
|
||||
fontScale: 1
|
||||
};
|
||||
|
||||
var postData = {
|
||||
@@ -215,12 +224,14 @@
|
||||
var initConfig = {
|
||||
size: savedConfig.size || 'xiaohongshu',
|
||||
watermark: savedConfig.watermark || '',
|
||||
pageAlignments: savedConfig.pageAlignments || {}
|
||||
pageAlignments: savedConfig.pageAlignments || {},
|
||||
fontScale: savedConfig.fontScale || 1
|
||||
};
|
||||
|
||||
// 同步当前配置
|
||||
currentConfig.size = initConfig.size;
|
||||
currentConfig.watermark = initConfig.watermark;
|
||||
currentConfig.fontScale = initConfig.fontScale;
|
||||
|
||||
// ========== wangeditor 初始化 ==========
|
||||
var E = window.wangEditor;
|
||||
@@ -331,6 +342,31 @@
|
||||
// 初始同步预览区
|
||||
PhoneImageEngine.syncPreview();
|
||||
|
||||
// 字号倍数 Slider
|
||||
var $slider = $('#font-scale-slider');
|
||||
var $scaleValue = $('#font-scale-value');
|
||||
|
||||
// 拖动中: 仅更新CSS变量和显示值,不触发html2canvas
|
||||
$slider.on('input', function() {
|
||||
var val = parseFloat($(this).val());
|
||||
$scaleValue.text(val.toFixed(1) + 'x');
|
||||
currentConfig.fontScale = val;
|
||||
document.documentElement.style.setProperty('--pi-font-scale', val);
|
||||
PhoneImageEngine.updateConfig({ fontScale: val });
|
||||
});
|
||||
|
||||
// 松手后: 触发完整渲染(重新分页 + html2canvas截图)
|
||||
$slider.on('change', function() {
|
||||
var val = parseFloat($(this).val());
|
||||
doRender({ fontScale: val });
|
||||
});
|
||||
|
||||
// 初始化时更新 Slider 显示值
|
||||
if (initConfig.fontScale && initConfig.fontScale !== 1) {
|
||||
$slider.val(initConfig.fontScale);
|
||||
$scaleValue.text(parseFloat(initConfig.fontScale).toFixed(1) + 'x');
|
||||
}
|
||||
|
||||
// ========== 设置弹框 ==========
|
||||
$('#btn-settings').click(function () {
|
||||
var settingsHtml = '<div class="settings-form" style="padding:20px 20px 0;">';
|
||||
@@ -451,6 +487,7 @@
|
||||
PhoneImageEngine.saveConfig(postData.postId, {
|
||||
size: currentConfig.size,
|
||||
watermark: currentConfig.watermark,
|
||||
fontScale: currentConfig.fontScale || 1,
|
||||
content_html: PhoneImageEngine.getContentHtml()
|
||||
}, saveConfigUrl).then(function (data) {
|
||||
if (data.output_id) lastOutputId = data.output_id;
|
||||
@@ -476,6 +513,7 @@
|
||||
PhoneImageEngine.saveConfig(postData.postId, {
|
||||
size: currentConfig.size,
|
||||
watermark: currentConfig.watermark,
|
||||
fontScale: currentConfig.fontScale || 1,
|
||||
content_html: PhoneImageEngine.getContentHtml()
|
||||
}, saveConfigUrl).then(function (data) {
|
||||
if (data.output_id) lastOutputId = data.output_id;
|
||||
@@ -499,6 +537,7 @@
|
||||
PhoneImageEngine.saveImages(postData.postId, {
|
||||
size: currentConfig.size,
|
||||
watermark: currentConfig.watermark,
|
||||
fontScale: currentConfig.fontScale || 1,
|
||||
content_html: PhoneImageEngine.getContentHtml()
|
||||
}).then(function (data) {
|
||||
if (data.output_id) {
|
||||
@@ -584,10 +623,16 @@
|
||||
if (cfg.size) historyConfig.size = cfg.size;
|
||||
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.size) currentConfig.size = cfg.size;
|
||||
if (cfg.watermark !== undefined) currentConfig.watermark = cfg.watermark;
|
||||
if (cfg.fontScale !== undefined) {
|
||||
currentConfig.fontScale = cfg.fontScale;
|
||||
$slider.val(cfg.fontScale);
|
||||
$scaleValue.text(parseFloat(cfg.fontScale).toFixed(1) + 'x');
|
||||
}
|
||||
|
||||
lastOutputId = outputId;
|
||||
layer.closeAll();
|
||||
|
||||
Reference in New Issue
Block a user