mirror of
https://gitee.com/ulthon/ulthon_information.git
synced 2026-07-01 16:12:47 +08:00
- 新增 public/static/lib/snapdom/snapdom.js (IIFE构建)
- phone_image.html script引用从html2canvas改为snapdom
- phone-image.js 3处截图调用全部替换为snapdom.toCanvas()
- capturePageViaHtml2Canvas (分页截图)
- 长图生成 (scale:2)
- 导出单页 (scale:2)
- API映射: html2canvas(elem,{scale,useCORS,width,height,logging}) -> snapdom.toCanvas(elem,{scale,backgroundColor})
- 返回值兼容: Promise<Canvas> 下游toDataURL不受影响
- 保留html2canvas目录不删除(output.html仍引用)
805 lines
33 KiB
HTML
805 lines
33 KiB
HTML
<!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>
|
||
</div>
|
||
</div>
|
||
<div id="render-preview"></div>
|
||
</div>
|
||
|
||
<!-- 右侧:分页排版预览 -->
|
||
<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" class="preview-thumbnails"></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 src="/static/lib/snapdom/snapdom.js"></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
|
||
};
|
||
|
||
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
|
||
};
|
||
|
||
// 同步当前配置
|
||
currentConfig.size = initConfig.size;
|
||
currentConfig.watermark = initConfig.watermark;
|
||
currentConfig.fontScale = initConfig.fontScale;
|
||
currentConfig.tableFontScale = initConfig.tableFontScale;
|
||
|
||
// ========== 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,
|
||
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,
|
||
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);
|
||
}
|
||
|
||
// ========== 设置弹框 ==========
|
||
$('#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
|
||
};
|
||
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: {} });
|
||
}
|
||
doRender();
|
||
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');
|
||
});
|
||
}
|
||
}
|
||
|
||
// ========== 保存状态指示器 ==========
|
||
$('<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,
|
||
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,
|
||
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> |