diff --git a/public/static/css/phone-image-templates.css b/public/static/css/phone-image-templates.css index fcb161a..606d6e4 100644 --- a/public/static/css/phone-image-templates.css +++ b/public/static/css/phone-image-templates.css @@ -608,3 +608,393 @@ .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; } + +/* ============================================ + Page Layout Shell (页面布局外壳) + 两栏布局:左侧编辑器 + 右侧预览 + 这些样式替代 phone-image.html 中的 inline style + ============================================ */ + +/* --- 顶部操作栏(限定 body > 直接子元素,避免与模板 .page-header 冲突) --- */ +body > .page-header { + background: #fff; + padding: 10px 20px; + border-bottom: 1px solid #e8e8e8; + display: flex; + justify-content: space-between; + align-items: center; + height: 52px; + box-sizing: border-box; + z-index: 100; + position: relative; +} + +body > .page-header-left { + display: flex; + align-items: center; + gap: 10px; +} + +body > .page-header-left h3 { + margin: 0; + font-size: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 300px; + color: var(--pi-color-text); +} + +body > .page-header-right { + display: flex; + align-items: center; + gap: 8px; +} + +/* 操作栏按钮统一样式 */ +body > .page-header-right .layui-btn { + font-size: 13px; + border-radius: 2px; +} + +body > .page-header-right .layui-btn i { + font-size: 13px; + margin-right: 3px; +} + +/* 按钮组去掉多余边框 */ +body > .page-header-right .layui-btn-group { + margin: 0; + vertical-align: middle; +} + +body > .page-header-right .layui-btn-group .layui-btn { + border-radius: 0; + margin: 0; +} + +body > .page-header-right .layui-btn-group .layui-btn:first-child { + border-radius: 2px 0 0 2px; +} + +body > .page-header-right .layui-btn-group .layui-btn:last-child { + border-radius: 0 2px 2px 0; +} + +/* 返回按钮 */ +body > .page-header-left .layui-btn-primary:hover { + color: var(--pi-color-accent); + border-color: var(--pi-color-accent); +} + +/* 主操作按钮 hover */ +body > .page-header-right .layui-btn-normal:hover { + opacity: 0.85; +} + +body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-normal):hover { + opacity: 0.9; +} + +/* 更多下拉菜单可读性优化 */ +.layui-dropdown-menu { + min-width: 130px; + font-size: 13px; +} + +.layui-dropdown-menu li { + padding: 8px 16px; + cursor: pointer; + transition: background 0.15s; +} + +.layui-dropdown-menu li:hover { + background: rgba(24, 144, 255, 0.06); + color: var(--pi-color-accent); +} + +/* --- 主布局两栏 --- */ +.main-layout { + display: flex; + height: calc(100vh - 52px); +} + +/* ============================================ + Editor Area (wangeditor 编辑区) + 左侧编辑栏容器与工具栏 + ============================================ */ + +.content-flow-area { + width: 540px; + overflow-y: auto; + border-right: 1px solid #e8e8e8; + background: #fafafa; + flex-shrink: 0; +} + +#editor-toolbar { + width: 540px; + border-bottom: 1px solid #e8e8e8; + background: #fff; + position: sticky; + top: 0; + z-index: 10; + box-sizing: border-box; +} + +#editor-text-area { + width: 540px; + min-height: 300px; + padding: 10px; + background: #fff; + box-sizing: border-box; +} + +/* ============================================ + Editor Content Typography (编辑器内排版样式) + 与截图输出区域(staging)的视觉保持一致 + 作用域: #editor-text-area 内部元素 + ============================================ */ + +#editor-text-area { + font-family: var(--pi-font-family); + font-size: var(--pi-font-size-base); + line-height: var(--pi-line-height); + color: var(--pi-color-text); +} + +/* 编辑区标题字号 — 与 .page-content h2~h6 对齐 */ +#editor-text-area h1 { + font-size: 22px; + font-weight: bold; + letter-spacing: 1px; + margin-top: 24px; + margin-bottom: 12px; + color: var(--pi-color-text); + line-height: 1.4; +} + +#editor-text-area h2 { + font-weight: normal; + font-size: 18px; + letter-spacing: 1px; + margin-top: 24px; + margin-bottom: 12px; + color: #333; +} + +#editor-text-area h3 { + font-weight: normal; + font-size: 16px; + margin-top: 20px; + margin-bottom: 10px; + color: #555; +} + +#editor-text-area h4 { + font-size: 14px; + font-weight: bold; + margin-top: var(--pi-spacing-sm); + margin-bottom: 5px; +} + +#editor-text-area h5 { + font-size: 13px; + font-weight: bold; + margin-top: var(--pi-spacing-sm); + margin-bottom: 5px; +} + +#editor-text-area h6 { + font-size: 12px; + font-weight: bold; + margin-top: var(--pi-spacing-sm); + margin-bottom: 5px; + color: var(--pi-color-text-light); +} + +/* 编辑区段落 */ +#editor-text-area p { + text-indent: 2em; + margin-bottom: 16px; + color: #333; +} + +/* 编辑区图片 */ +#editor-text-area img { + max-width: 100% !important; + height: auto !important; + display: block; + margin: 10px 0; +} + +/* 编辑区表格 */ +#editor-text-area table { + width: 100%; + border-collapse: collapse; + margin: 10px 0; + font-size: 13px; +} + +#editor-text-area th { + background-color: #f0f0f0; + font-weight: bold; + padding: 8px 10px; + border: 1px solid #ddd; + text-align: left; +} + +#editor-text-area td { + padding: 8px 10px; + border: 1px solid #ddd; +} + +#editor-text-area tr:nth-child(even) td { + background-color: #f9f9f9; +} + +/* 编辑区代码块 */ +#editor-text-area pre { + background: #f5f5f5; + border: 1px solid #e0e0e0; + border-radius: 4px; + padding: 12px; + overflow-x: auto; + font-size: 13px; + line-height: 1.6; + margin: 10px 0; +} + +#editor-text-area code { + font-family: 'Courier New', Consolas, monospace; + font-size: 13px; +} + +#editor-text-area pre code { + background: none; + border: none; + padding: 0; +} + +/* 编辑区引用 */ +#editor-text-area blockquote { + border-left: 1px solid #ccc; + font-style: normal; + color: #888; + padding-left: 15px; + margin: var(--pi-spacing-sm) 0; +} + +/* 编辑区列表 */ +#editor-text-area ul, +#editor-text-area ol { + padding-left: var(--pi-spacing); + margin-bottom: var(--pi-spacing-sm); +} + +#editor-text-area li { + margin-bottom: 5px; +} + +/* ============================================ + Divider / 分割线视觉样式 (wangeditor divider) + 在编辑器中显示为分页标记提示 + ============================================ */ + +/* wangeditor divider 元素 — 虚线分页标记 */ +.content-flow-area [data-w-e-type="divider"], +#editor-text-area [data-w-e-type="divider"], +#editor-text-area hr.w-e-textarea-divider, +#editor-text-area hr { + border: none; + border-top: 2px dashed var(--pi-color-accent); + margin: 15px 0; + padding: 8px 0; + text-align: center; + position: relative; + height: 0; + background: transparent; + clear: both; +} + +/* 分页标记文字提示(通过 ::after 伪元素) + 注意: wangeditor divider 是 void 元素,::after 可能不生效 + 如不生效需在 JS 中插入额外 span 提示元素 */ +.content-flow-area [data-w-e-type="divider"]::after, +#editor-text-area [data-w-e-type="divider"]::after, +#editor-text-area hr.w-e-textarea-divider::after, +#editor-text-area hr::after { + content: '-- 分页标记 --'; + display: inline-block; + font-size: 12px; + color: var(--pi-color-accent); + background: #fafafa; + padding: 0 10px; + position: relative; + top: -8px; + line-height: 1.8; +} + +/* 分割线 hover 高亮(编辑态) */ +.content-flow-area [data-w-e-type="divider"]:hover, +#editor-text-area [data-w-e-type="divider"]:hover, +#editor-text-area hr.w-e-textarea-divider:hover, +#editor-text-area hr:hover { + border-color: #40a9ff; +} + +/* ============================================ + Settings Dialog (设置弹框表单样式) + ============================================ */ + +.settings-form { + padding: 20px 20px 0; +} + +.settings-form .layui-form-item { + margin-bottom: 15px; +} + +.settings-form .layui-form-label { + width: 60px; + font-size: 13px; + color: var(--pi-color-text-light); +} + +.settings-form .layui-input-block { + margin-left: 90px; +} + +.settings-form .layui-input { + border-radius: 2px; + font-size: 13px; +} + +.settings-form .layui-select { + border-radius: 2px; +} + +.settings-form .layui-form-select dl { + font-size: 13px; +} + +/* ============================================ + Preview Area (右侧预览区域) + ============================================ */ + +.paginated-preview-area { + flex: 1; + overflow-x: auto; + overflow-y: hidden; + padding: 20px; + background: #f5f5f5; +} + +#paginated-preview { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 20px; + height: 100%; + padding: 0 10px; +} diff --git a/view/admin/post/phone_image.html b/view/admin/post/phone_image.html index 11c110e..ad2b21a 100644 --- a/view/admin/post/phone_image.html +++ b/view/admin/post/phone_image.html @@ -198,8 +198,17 @@ } }; + var autoSaveTimer = null; + var autoSaveLock = false; + editorConfig.onChange = function (editor) { - // T5会实现自动保存 + // 自动保存:2.6s 防抖 + clearTimeout(autoSaveTimer); + autoSaveLock = true; + updateSaveState('waiting'); + autoSaveTimer = setTimeout(function () { + doAutoSave(); + }, 2600); }; // 粘贴处理:处理外部图片下载 @@ -369,10 +378,45 @@ align: 'right' }); + // ========== 自动保存相关函数 ========== + 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'); + PhoneImageEngine.saveConfig(postData.postId, { + size: currentConfig.size, + watermark: currentConfig.watermark, + content_html: PhoneImageEngine.getContentHtml() + }, saveConfigUrl).then(function (data) { + if (data.output_id) lastOutputId = data.output_id; + updateSaveState('saved'); + }).catch(function (err) { + updateSaveState('error'); + }); + } + } + + // ========== 保存状态指示器 ========== + $('已保存').insertAfter('#btn-generate'); + // ========== 保存配置(不生成图片) ========== $('#btn-save').click(function () { var btn = $(this); btn.prop('disabled', true); + // 清除自动保存定时器,避免冲突 + clearTimeout(autoSaveTimer); + autoSaveLock = false; layer.msg('保存中...'); PhoneImageEngine.saveConfig(postData.postId, { @@ -383,8 +427,10 @@ if (data.output_id) lastOutputId = data.output_id; $('#post-content-html').html(PhoneImageEngine.getContentHtml()); layer.msg('保存成功'); + updateSaveState('saved'); }).catch(function (err) { layer.msg('保存失败: ' + err); + updateSaveState('error'); }).always(function () { setTimeout(function () { btn.prop('disabled', false); }, 2000); }); @@ -504,9 +550,9 @@ var cfg = res.data.config || {}; - // 更新 postData 中的 contentHtml - if (res.data.content_html) { - postData.contentHtml = res.data.content_html; + // 用 setHtml 将历史内容加载到 wangeditor + if (res.data.content_html && window.phoneImageEditor) { + window.phoneImageEditor.setHtml(res.data.content_html); } // 构建历史配置 @@ -522,8 +568,8 @@ lastOutputId = outputId; layer.closeAll(); - // 全量初始化引擎(加载历史是新内容,需要完整初始化) - PhoneImageEngine.init(postData, historyConfig); + // 更新引擎配置并重新渲染 + PhoneImageEngine.updateConfig(historyConfig); var loadIdx3 = layer.load(); PhoneImageEngine.render().then(function (pages) { layer.close(loadIdx3);