Files
ulthon_information/view/admin/post/phone_image.html
augushong 7bb066b704 feat(phone-image): codeFontScale UI控件+集成
- HTML新增代碼字號下拉選擇控件(0.5x-2.0x+自定義輸入)
- currentConfig初始化恢復codeFontScale
- change事件更新config+applyFontScale+debouncedSaveConfig
- 所有saveConfig/saveImages調用包含codeFontScale
2026-05-19 00:43:29 +08:00

922 lines
40 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{$post.title} - 手机图片排版</title>
<link rel="stylesheet" href="/static/lib/layui/css/layui.css">
<link rel="stylesheet" href="/static/lib/wangeditor/css/style.css">
<link rel="stylesheet" href="/static/css/phone-image-templates.css">
<link rel="stylesheet" href="/static/css/phone-image-fonts.css">
<style>
html {
height: 100%;
overflow: hidden;
}
body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background: #f2f2f2;
display: flex;
flex-direction: column;
}
.page-header {
background: #fff;
padding: 10px 20px;
border-bottom: 1px solid #e8e8e8;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.page-header-left {
display: flex;
align-items: center;
gap: 10px;
}
.page-header-left h3 {
margin: 0;
font-size: 16px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px;
}
.page-header-right {
display: flex;
align-items: center;
gap: 8px;
}
.main-layout {
display: flex;
flex: 1;
min-height: 0;
}
.content-flow-area {
width: 540px;
overflow-y: auto;
border-right: 1px solid #e8e8e8;
background: #fafafa;
}
#editor-toolbar {
border-bottom: 1px solid #e8e8e8;
background: #fff;
}
#editor-text-area {
min-height: 300px;
padding: 10px;
background: #fff;
}
.render-preview-area {
width: 540px;
overflow-y: auto;
border-right: 1px solid #e8e8e8;
background: #fff;
flex-shrink: 0;
}
.render-preview-area .preview-header {
padding: 8px 15px;
background: #fafafa;
border-bottom: 1px solid #e8e8e8;
font-size: 13px;
color: #999;
position: sticky;
top: 0;
z-index: 5;
}
#render-preview {
min-height: 300px;
padding: 10px;
box-sizing: border-box;
}
.paginated-preview-area {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: #f5f5f5;
}
#paginated-preview {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 20px;
padding: 0 10px;
}
.preview-thumb-zone {
flex: 1;
overflow-x: auto;
overflow-y: hidden;
padding: 20px;
min-height: 0;
}
/* 设置弹框样式 */
.settings-form .layui-form-item {
margin-bottom: 15px;
}
.settings-form .layui-form-label {
width: 60px;
}
.settings-form .layui-input-block {
margin-left: 90px;
}
/* 操作日志面板 */
.log-panel {
height: 200px;
flex-shrink: 0;
border-top: 1px solid #e8e8e8;
display: flex;
flex-direction: column;
background: #1e1e1e;
color: #d4d4d4;
font-family: Consolas, 'Courier New', monospace;
font-size: 12px;
}
.log-panel.collapsed {
height: auto;
}
.log-panel.collapsed .log-panel-body {
display: none;
}
.log-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 10px;
background: #2d2d2d;
border-bottom: 1px solid #3e3e3e;
flex-shrink: 0;
}
.log-panel-body {
flex: 1;
overflow-y: auto;
padding: 4px 10px;
min-height: 0;
}
.log-panel-actions {
display: flex;
gap: 6px;
}
.log-entry {
padding: 2px 0;
line-height: 1.5;
border-bottom: 1px solid #2a2a2a;
}
.log-info { color: #4fc3f7; }
.log-warn { color: #ffb74d; }
.log-error { color: #ef5350; }
.log-success { color: #81c784; }
.log-time {
color: #888;
margin-right: 8px;
}
</style>
</head>
<body>
<!-- 隐藏div存放文章HTML内容供JS读取 -->
<div id="post-content-html" style="display:none;">{$layoutContentHtml|raw}</div>
<div class="page-header">
<div class="page-header-left">
<a href="{:url('post/index')}" class="layui-btn layui-btn-sm layui-btn-primary"><i
class="layui-icon layui-icon-return"></i> 返回列表</a>
<h3>{$post.title}</h3>
</div>
<div class="page-header-right">
<button type="button" class="layui-btn layui-btn-sm layui-btn-primary" id="btn-settings"><i
class="layui-icon layui-icon-set"></i> 设置</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-warm" id="btn-render"><i
class="layui-icon layui-icon-refresh"></i> 生成</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal" id="btn-save"><i
class="layui-icon layui-icon-ok"></i> 保存</button>
<button type="button" class="layui-btn layui-btn-sm" id="btn-generate"><i
class="layui-icon layui-icon-picture"></i> 生成并保存</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-primary" id="btn-export-page"><i
class="layui-icon layui-icon-upload-circle"></i> 导出页面</button>
</div>
</div>
<div class="main-layout">
<!-- 左侧wangeditor 编辑器 -->
<div class="content-flow-area">
<div id="editor-toolbar"></div>
<div id="editor-text-area"></div>
</div>
<!-- 中间:渲染预览区 -->
<div class="render-preview-area">
<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>
<select id="font-scale-select" style="height:26px;font-size:12px;border:1px solid #d9d9d9;border-radius:3px;padding:0 4px;">
<option value="0.5">0.5x</option>
<option value="0.8">0.8x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.2">1.2x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
<option value="custom">自定义</option>
</select>
<input type="number" id="font-scale-custom" min="0.5" max="2.0" step="0.1" value="1.0"
style="width:60px;height:24px;font-size:12px;border:1px solid #d9d9d9;border-radius:3px;text-align:center;display:none;">
<span id="font-scale-value" style="font-size:12px;color:#1890ff;min-width:32px;">1.0x</span>
<span style="font-size:12px;color:#666;margin-left:8px;">表格字号</span>
<select id="table-font-scale-select" style="height:26px;font-size:12px;border:1px solid #d9d9d9;border-radius:3px;padding:0 4px;">
<option value="0.5">0.5x</option>
<option value="0.8">0.8x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.2">1.2x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
<option value="custom">自定义</option>
</select>
<input type="number" id="table-font-scale-custom" min="0.5" max="2.0" step="0.1" value="1.0"
style="width:60px;height:24px;font-size:12px;border:1px solid #d9d9d9;border-radius:3px;text-align:center;display:none;">
<span id="table-font-scale-value" style="font-size:12px;color:#1890ff;min-width:32px;">1.0x</span>
<span style="font-size:12px;color:#666;margin-left:8px;">代码字号</span>
<select id="code-font-scale-select" style="height:26px;font-size:12px;border:1px solid #d9d9d9;border-radius:3px;padding:0 4px;">
<option value="0.5">0.5x</option>
<option value="0.8">0.8x</option>
<option value="1.0" selected>1.0x</option>
<option value="1.2">1.2x</option>
<option value="1.5">1.5x</option>
<option value="2.0">2.0x</option>
<option value="custom">自定义</option>
</select>
<input type="number" id="code-font-scale-custom" min="0.5" max="2.0" step="0.1" value="1.0"
style="width:60px;height:24px;font-size:12px;border:1px solid #d9d9d9;border-radius:3px;text-align:center;display:none;">
<span id="code-font-scale-value" style="font-size:12px;color:#1890ff;min-width:32px;">1.0x</span>
</div>
</div>
<div id="render-preview"></div>
</div>
<!-- 右侧:分页排版预览 (DOM分页渲染) -->
<div class="paginated-preview-area">
<div class="preview-header">
<span>分页预览</span>
<div style="display:inline-flex;align-items:center;gap:8px;float:right;">
<button id="btn-reset-alignments" type="button" style="height:26px;font-size:12px;border:1px solid #d9d9d9;border-radius:3px;padding:0 8px;cursor:pointer;background:#fff;">重置</button>
</div>
</div>
<div class="preview-thumb-zone">
<div id="paginated-preview"></div>
</div>
<!-- 日志面板由 LogPanel.init() 动态插入 -->
</div>
</div>
<script src="/static/lib/jquery/jquery-3.4.1.min.js"></script>
<script src="/static/lib/layui/layui.js"></script>
<script src="/static/lib/wangeditor/index.js"></script>
<script>var _saved_jQuery_dollar = window.$;</script>
<script src="/static/lib/snapdom/snapdom.js"></script>
<script>if (window._saved_jQuery_dollar) window.$ = window._saved_jQuery_dollar;</script>
<script src="/static/js/phone-image.js"></script>
<script>
layui.use(['form', 'layer'], function () {
var form = layui.form;
var layer = layui.layer;
var lastOutputId = <?php echo $lastOutputId ? (int) $lastOutputId : 'null'; ?>;
var downloadBaseUrl = '{:url("post/downloadPostOutputZip", ["id" => 0])}';
var outputViewUrl = '{:url("post/outputView", ["id" => 0])}';
var saveConfigUrl = '{:url("post/savePostOutputConfig")}';
// 当前排版配置(从设置弹框维护)
var currentConfig = {
size: 'xiaohongshu',
watermark: '',
fontScale: 1,
tableFontScale: 1,
codeFontScale: 1
};
var postData = {
postId: {$post.id},
title: <?php echo json_encode($post->title ?? ''); ?>,
desc: <?php echo json_encode($post->desc ?? ''); ?>,
coverText: <?php echo json_encode($post->getData('cover_text') ?? ''); ?>,
contentHtml: $('#post-content-html').html(),
poster: <?php echo json_encode($post->poster ?? ''); ?>,
authorName: <?php echo json_encode($post->author_name ?? ''); ?>,
siteName: <?php echo json_encode($siteName ?? ''); ?>,
siteLogo: <?php echo json_encode($siteLogo ?? ''); ?>,
createTime: <?php echo json_encode($post->create_time_text ?? ''); ?>,
categoryName: ''
};
// 恢复之前保存的排版配置
var savedConfig = <?php echo $layoutConfig ? json_encode($layoutConfig) : '{}'; ?>;
var initConfig = {
size: savedConfig.size || 'xiaohongshu',
watermark: savedConfig.watermark || '',
pageAlignments: savedConfig.pageAlignments || {},
fontScale: savedConfig.fontScale || 1,
tableFontScale: savedConfig.tableFontScale || 1,
codeFontScale: savedConfig.codeFontScale || 1
};
// 同步当前配置
currentConfig.size = initConfig.size;
currentConfig.watermark = initConfig.watermark;
currentConfig.fontScale = initConfig.fontScale;
currentConfig.tableFontScale = initConfig.tableFontScale;
currentConfig.codeFontScale = initConfig.codeFontScale;
currentConfig.pageAlignments = initConfig.pageAlignments || {};
// ========== wangeditor 初始化 ==========
var E = window.wangEditor;
var editorConfig = { MENU_CONF: {} };
editorConfig.placeholder = '请输入排版内容,使用分割线标记分页位置';
editorConfig.scroll = false;
editorConfig.MENU_CONF['uploadImage'] = {
server: '{:url("File/wangEditorSave")}',
fieldName: 'file',
meta: {
type: 'editor'
}
};
var autoSaveTimer = null;
var autoSaveLock = false;
editorConfig.onChange = function (editor) {
// 自动保存2.6s 防抖
clearTimeout(autoSaveTimer);
autoSaveLock = true;
updateSaveState('waiting');
autoSaveTimer = setTimeout(function () {
doAutoSave();
}, 2600);
// 预览同步300ms 防抖(独立于自动保存)
clearTimeout(window._previewSyncTimer);
window._previewSyncTimer = setTimeout(function () {
PhoneImageEngine.syncPreview();
}, 300);
};
// 粘贴处理:处理外部图片下载
editorConfig.customPaste = function (editor, event) {
var pasteStr = event.clipboardData.getData('text/html');
var imgReg = /<img.*?(?:>|\/>)/gi;
var srcReg = /src=[\'\"]?([^\'\"]*)[\'\"]?/i;
var arr = pasteStr.match(imgReg);
if (arr == null || arr.length == 0) {
return true;
}
PhoneImageLogPanel.log('粘贴图片处理中...', 'info');
layer.load();
for (var i = 0; i < arr.length; i++) {
var src = arr[i].match(srcReg);
if (src[1]) {
var imgSrc = src[1];
var prefix = imgSrc.substr(0, 4);
if (prefix == 'http') {
$.ajax({
async: false,
type: 'POST',
url: "{:url('File/urlSave')}",
data: {
url: imgSrc,
type: 'editor'
},
success: function (result) {
pasteStr = pasteStr.replace(imgSrc, result.data.src)
}
})
} else if (prefix == 'data') {
$.ajax({
async: false,
type: 'POST',
url: '{:url("File/base64Save")}',
data: {
data: imgSrc,
type: 'editor'
},
success: function (result) {
pasteStr = pasteStr.replace(imgSrc, result.data.src)
}
})
}
}
}
PhoneImageLogPanel.log('粘贴图片处理完成', 'success');
layer.closeAll('loading');
editor.dangerouslyInsertHtml(pasteStr);
event.preventDefault();
return false;
};
var phoneImageEditor = E.createEditor({
selector: '#editor-text-area',
html: postData.contentHtml,
config: editorConfig
});
var toolbar = E.createToolbar({
editor: phoneImageEditor,
selector: '#editor-toolbar',
config: {
excludeKeys: ['fullScreen'],
insertKeys: {
index: 24,
keys: ['divider']
}
}
});
window.phoneImageEditor = phoneImageEditor;
PhoneImageEngine.init(postData, initConfig);
// 初始化日志面板
PhoneImageLogPanel.init('.paginated-preview-area');
PhoneImageLogPanel.log('排版引擎已初始化', 'info');
// 初始同步预览区
PhoneImageEngine.syncPreview();
// 字号倍数 Dropdown + 自定义输入
var $scaleSelect = $('#font-scale-select');
var $scaleCustom = $('#font-scale-custom');
var $scaleValue = $('#font-scale-value');
var fontScalePresets = [0.5, 0.8, 1.0, 1.2, 1.5, 2.0];
function setFontScaleUI(val) {
var $select = $('#font-scale-select');
var $custom = $('#font-scale-custom');
if (fontScalePresets.indexOf(val) >= 0) {
$select.val(String(val));
$custom.hide();
} else {
$select.val('custom');
$custom.val(val).show();
}
$('#font-scale-value').text(parseFloat(val).toFixed(1) + 'x');
}
function applyFontScaleValue(val) {
val = Math.max(0.5, Math.min(2.0, parseFloat(val) || 1.0));
$scaleValue.text(val.toFixed(1) + 'x');
currentConfig.fontScale = val;
document.getElementById('render-preview').style.setProperty('--pi-font-scale', val);
var staging = document.getElementById('render-staging');
if (staging) staging.style.setProperty('--pi-font-scale', val);
PhoneImageEngine.updateConfig({ fontScale: val });
}
function triggerFontScaleSave(val) {
val = parseFloat(val);
PhoneImageLogPanel.log('字号调整为 ' + val.toFixed(1) + 'x', 'info');
doRender({ fontScale: val });
updateSaveState('saving');
PhoneImageEngine.saveConfig(postData.postId, {
size: currentConfig.size,
watermark: currentConfig.watermark,
fontScale: currentConfig.fontScale || 1,
tableFontScale: currentConfig.tableFontScale || 1,
codeFontScale: currentConfig.codeFontScale || 1,
content_html: PhoneImageEngine.getContentHtml()
}, saveConfigUrl).then(function(data) {
if (data.output_id) lastOutputId = data.output_id;
PhoneImageLogPanel.log('字号配置已保存', 'success');
updateSaveState('saved');
}).catch(function(err) {
PhoneImageLogPanel.log('字号保存失败: ' + err, 'error');
updateSaveState('error');
});
}
// 下拉选择: 预设值直接应用,自定义则显示输入框
$scaleSelect.on('change', function() {
var selected = $(this).val();
if (selected === 'custom') {
$scaleCustom.show().focus();
} else {
$scaleCustom.hide();
var val = parseFloat(selected);
applyFontScaleValue(val);
triggerFontScaleSave(val);
}
});
// 自定义输入: 实时预览
$scaleCustom.on('input', function() {
var val = parseFloat($(this).val());
if (!isNaN(val) && val >= 0.5 && val <= 2.0) {
applyFontScaleValue(val);
}
});
// 自定义输入: 失焦时触发保存
$scaleCustom.on('change', function() {
var val = parseFloat($(this).val());
if (isNaN(val) || val < 0.5) val = 0.5;
if (val > 2.0) val = 2.0;
$(this).val(val);
applyFontScaleValue(val);
triggerFontScaleSave(val);
});
// 初始化时更新字号显示值
if (initConfig.fontScale && initConfig.fontScale !== 1) {
setFontScaleUI(initConfig.fontScale);
}
// ========== 表格字号下拉 ==========
var $tblScaleSelect = $('#table-font-scale-select');
var $tblScaleCustom = $('#table-font-scale-custom');
var $tblScaleValue = $('#table-font-scale-value');
var tblScalePresets = [0.5, 0.8, 1.0, 1.2, 1.5, 2.0];
function setTableFontScaleUI(val) {
if (tblScalePresets.indexOf(val) >= 0) {
$tblScaleSelect.val(String(val));
$tblScaleCustom.hide();
} else {
$tblScaleSelect.val('custom');
$tblScaleCustom.val(val).show();
}
$tblScaleValue.text(parseFloat(val).toFixed(1) + 'x');
}
function applyTableFontScaleValue(val) {
val = Math.max(0.5, Math.min(2.0, parseFloat(val) || 1.0));
$tblScaleValue.text(val.toFixed(1) + 'x');
currentConfig.tableFontScale = val;
PhoneImageEngine.updateConfig({ tableFontScale: val });
}
function triggerTableFontScaleSave(val) {
val = parseFloat(val);
PhoneImageLogPanel.log('表格字号调整为 ' + val.toFixed(1) + 'x', 'info');
doRender({ tableFontScale: val });
updateSaveState('saving');
PhoneImageEngine.saveConfig(postData.postId, {
size: currentConfig.size,
watermark: currentConfig.watermark,
fontScale: currentConfig.fontScale || 1,
tableFontScale: currentConfig.tableFontScale || 1,
codeFontScale: currentConfig.codeFontScale || 1,
content_html: PhoneImageEngine.getContentHtml()
}, saveConfigUrl).then(function(data) {
if (data.output_id) lastOutputId = data.output_id;
PhoneImageLogPanel.log('表格字号配置已保存', 'success');
updateSaveState('saved');
}).catch(function(err) {
PhoneImageLogPanel.log('表格字号保存失败: ' + err, 'error');
updateSaveState('error');
});
}
$tblScaleSelect.on('change', function() {
var selected = $(this).val();
if (selected === 'custom') {
$tblScaleCustom.show().focus();
} else {
$tblScaleCustom.hide();
var val = parseFloat(selected);
applyTableFontScaleValue(val);
triggerTableFontScaleSave(val);
}
});
$tblScaleCustom.on('input', function() {
var val = parseFloat($(this).val());
if (!isNaN(val) && val >= 0.5 && val <= 2.0) {
applyTableFontScaleValue(val);
}
});
$tblScaleCustom.on('change', function() {
var val = parseFloat($(this).val());
if (isNaN(val) || val < 0.5) val = 0.5;
if (val > 2.0) val = 2.0;
$(this).val(val);
applyTableFontScaleValue(val);
triggerTableFontScaleSave(val);
});
if (initConfig.tableFontScale && initConfig.tableFontScale !== 1) {
setTableFontScaleUI(initConfig.tableFontScale);
}
// ========== 代码字号下拉 ==========
var $codeScaleSelect = $('#code-font-scale-select');
var $codeScaleCustom = $('#code-font-scale-custom');
var $codeScaleValue = $('#code-font-scale-value');
var codeScalePresets = [0.5, 0.8, 1.0, 1.2, 1.5, 2.0];
function setCodeFontScaleUI(val) {
if (codeScalePresets.indexOf(val) >= 0) {
$codeScaleSelect.val(String(val));
$codeScaleCustom.hide();
} else {
$codeScaleSelect.val('custom');
$codeScaleCustom.val(val).show();
}
$codeScaleValue.text(parseFloat(val).toFixed(1) + 'x');
}
function applyCodeFontScaleValue(val) {
val = Math.max(0.5, Math.min(2.0, parseFloat(val) || 1.0));
$codeScaleValue.text(val.toFixed(1) + 'x');
currentConfig.codeFontScale = val;
PhoneImageEngine.updateConfig({ codeFontScale: val });
PhoneImageEngine.applyFontScale();
}
$codeScaleSelect.on('change', function() {
var selected = $(this).val();
if (selected === 'custom') {
$codeScaleCustom.show().focus();
} else {
$codeScaleCustom.hide();
var val = parseFloat(selected);
applyCodeFontScaleValue(val);
PhoneImageLogPanel.log('代码字号调整为 ' + val.toFixed(1) + 'x', 'info');
updateSaveState('saving');
PhoneImageEngine.debouncedSaveConfig();
}
});
$codeScaleCustom.on('input', function() {
var val = parseFloat($(this).val());
if (!isNaN(val) && val >= 0.5 && val <= 2.0) {
applyCodeFontScaleValue(val);
}
});
$codeScaleCustom.on('change', function() {
var val = parseFloat($(this).val());
if (isNaN(val) || val < 0.5) val = 0.5;
if (val > 2.0) val = 2.0;
$(this).val(val);
applyCodeFontScaleValue(val);
PhoneImageLogPanel.log('代码字号调整为 ' + val.toFixed(1) + 'x', 'info');
updateSaveState('saving');
PhoneImageEngine.debouncedSaveConfig();
});
if (initConfig.codeFontScale && initConfig.codeFontScale !== 1) {
setCodeFontScaleUI(initConfig.codeFontScale);
}
// ========== 设置弹框 ==========
$('#btn-settings').click(function () {
var settingsHtml = '<div class="settings-form" style="padding:20px 20px 0;">';
settingsHtml += '<form class="layui-form" lay-filter="settingsForm">';
settingsHtml += '<div class="layui-form-item">';
settingsHtml += '<label class="layui-form-label">尺寸</label>';
settingsHtml += '<div class="layui-input-block">';
settingsHtml += '<select name="s_size" lay-filter="s_size">';
settingsHtml += '<option value="xiaohongshu"' + (currentConfig.size === 'xiaohongshu' ? ' selected' : '') + '>小红书 (1080x1440)</option>';
settingsHtml += '<option value="douyin"' + (currentConfig.size === 'douyin' ? ' selected' : '') + '>抖音 (1080x1920)</option>';
settingsHtml += '</select>';
settingsHtml += '</div>';
settingsHtml += '</div>';
settingsHtml += '<div class="layui-form-item">';
settingsHtml += '<label class="layui-form-label">水印</label>';
settingsHtml += '<div class="layui-input-block">';
settingsHtml += '<input type="text" name="s_watermark" value="' + (currentConfig.watermark || '') + '" placeholder="可选水印文字" class="layui-input">';
settingsHtml += '</div>';
settingsHtml += '</div>';
settingsHtml += '</form>';
settingsHtml += '</div>';
layer.open({
type: 1,
title: '排版设置',
area: ['400px', '220px'],
content: settingsHtml,
btn: ['确定', '取消'],
success: function (layero) {
form.render('select', 'settingsForm');
},
yes: function (index, layero) {
currentConfig.size = layero.find('[name="s_size"]').val();
currentConfig.watermark = layero.find('[name="s_watermark"]').val();
layer.close(index);
PhoneImageLogPanel.log('应用新设置: 尺寸=' + currentConfig.size, 'info');
doRender();
}
});
});
// ========== 渲染逻辑 ==========
var renderTimer = null;
function doRender(extraConfig) {
clearTimeout(renderTimer);
renderTimer = setTimeout(function () {
var newConfig = {
size: currentConfig.size,
watermark: currentConfig.watermark,
tableFontScale: currentConfig.tableFontScale || 1,
codeFontScale: currentConfig.codeFontScale || 1
};
if (extraConfig) {
$.extend(newConfig, extraConfig);
}
PhoneImageEngine.updateConfig(newConfig);
PhoneImageEngine.render().then(function (pages) {
layer.msg('排版完成,共 ' + pages.length + ' 页');
}).catch(function (err) {
if (err !== 'rendering') {
layer.msg('渲染失败: ' + err);
}
});
}, 300);
}
// ========== 生成按钮 ==========
$('#btn-render').click(function () {
doRender();
});
// ========== 导出页面按钮 ==========
$('#btn-export-page').click(function () {
if (!lastOutputId) {
layer.msg('请先生成并保存');
return;
}
var url = outputViewUrl.replace('id=0', 'id=' + lastOutputId);
window.open(url);
});
// ========== 重置对齐 ==========
$('#btn-reset-alignments').click(function () {
currentConfig.pageAlignments = {};
if (typeof PhoneImageEngine !== 'undefined') {
PhoneImageEngine.updateConfig({ pageAlignments: {} });
// 逐页重置对齐并刷新DOM
var totalPages = $('.preview-thumb-item').length;
for (var i = 0; i < totalPages; i++) {
PhoneImageEngine.setPageAlignment(i + 1, 'top');
PhoneImageEngine.refreshDomPage(i);
}
// 重置所有select控件
$('.thumb-alignment-select').val('top');
// 防抖自动保存
PhoneImageEngine.debouncedSaveConfig();
}
layer.msg('已重置所有页面对齐');
});
// ========== 自动保存相关函数 ==========
function updateSaveState(state) {
var $state = $('#save-state');
if (!$state.length) return;
switch(state) {
case 'waiting': $state.text('等待保存...').css('color', '#e6a23c'); break;
case 'saving': $state.text('保存中...').css('color', '#409eff'); break;
case 'saved': $state.text('已保存').css('color', '#67c23a'); break;
case 'error': $state.text('保存失败').css('color', '#f56c6c'); break;
}
}
function doAutoSave() {
if (autoSaveLock) {
autoSaveLock = false;
updateSaveState('saving');
PhoneImageLogPanel.log('自动保存中...', 'info');
PhoneImageEngine.saveConfig(postData.postId, {
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;
PhoneImageLogPanel.log('自动保存完成', 'success');
updateSaveState('saved');
}).catch(function (err) {
PhoneImageLogPanel.log('自动保存失败: ' + err, 'error');
updateSaveState('error');
});
}
}
// ========== 引擎内部防抖保存事件监听 ==========
$(document).on('phone-image:save-config', function () {
// 同步pageAlignments到currentConfig
currentConfig.pageAlignments = PhoneImageEngine.updateConfig.call.length ? currentConfig.pageAlignments : currentConfig.pageAlignments;
updateSaveState('saving');
PhoneImageLogPanel.log('自动保存对齐配置...', 'info');
PhoneImageEngine.saveConfig(postData.postId, {
size: currentConfig.size,
watermark: currentConfig.watermark,
fontScale: currentConfig.fontScale || 1,
tableFontScale: currentConfig.tableFontScale || 1,
codeFontScale: currentConfig.codeFontScale || 1,
content_html: PhoneImageEngine.getContentHtml()
}, saveConfigUrl).then(function(data) {
if (data.output_id) lastOutputId = data.output_id;
PhoneImageLogPanel.log('对齐配置已保存', 'success');
updateSaveState('saved');
}).catch(function(err) {
PhoneImageLogPanel.log('对齐配置保存失败: ' + err, 'error');
updateSaveState('error');
});
});
// ========== 保存状态指示器 ==========
$('<span id="save-state" style="font-size:12px;color:#999;">已保存</span>').insertAfter('#btn-generate');
// ========== 保存配置(不生成图片) ==========
$('#btn-save').click(function () {
var btn = $(this);
btn.prop('disabled', true);
// 清除自动保存定时器,避免冲突
clearTimeout(autoSaveTimer);
autoSaveLock = false;
PhoneImageLogPanel.log('保存配置中...', 'info');
PhoneImageEngine.saveConfig(postData.postId, {
size: currentConfig.size,
watermark: currentConfig.watermark,
fontScale: currentConfig.fontScale || 1,
tableFontScale: currentConfig.tableFontScale || 1,
codeFontScale: currentConfig.codeFontScale || 1,
content_html: PhoneImageEngine.getContentHtml()
}, saveConfigUrl).then(function (data) {
if (data.output_id) lastOutputId = data.output_id;
$('#post-content-html').html(PhoneImageEngine.getContentHtml());
PhoneImageLogPanel.log('配置保存成功', 'success');
layer.msg('保存成功');
updateSaveState('saved');
}).catch(function (err) {
PhoneImageLogPanel.log('保存失败: ' + err, 'error');
layer.msg('保存失败: ' + err);
updateSaveState('error');
}).always(function () {
setTimeout(function () { btn.prop('disabled', false); }, 2000);
});
});
// ========== 生成并保存 ==========
$('#btn-generate').click(function () {
var btn = $(this);
btn.prop('disabled', true).html('<i class="layui-icon layui-icon-picture"></i> 生成中...');
PhoneImageLogPanel.log('开始生成图片...', 'info');
PhoneImageEngine.saveImages(postData.postId, {
size: currentConfig.size,
watermark: currentConfig.watermark,
fontScale: currentConfig.fontScale || 1,
tableFontScale: currentConfig.tableFontScale || 1,
codeFontScale: currentConfig.codeFontScale || 1,
content_html: PhoneImageEngine.getContentHtml()
}, function(current, total, canvas) {
if (total > 0) {
PhoneImageLogPanel.log('截图: 第 ' + current + '/' + total + ' 页', 'info');
}
}).then(function (data) {
if (data.output_id) {
lastOutputId = data.output_id;
}
$('#post-content-html').html(PhoneImageEngine.getContentHtml());
PhoneImageLogPanel.log('图片生成并保存成功 (output_id: ' + data.output_id + ')', 'success');
layer.msg('保存成功!');
btn.prop('disabled', false).html('<i class="layui-icon layui-icon-picture"></i> 生成并保存');
}).catch(function (err) {
PhoneImageLogPanel.log('保存失败: ' + err, 'error');
layer.msg('保存失败: ' + err);
btn.prop('disabled', false).html('<i class="layui-icon layui-icon-picture"></i> 生成并保存');
});
});
// 初始渲染
doRender();
});
</script>
<div id="render-staging" style="position:fixed;left:-9999px;top:0;visibility:hidden;"></div>
</body>
</html>