diff --git a/public/static/css/phone-image-templates.css b/public/static/css/phone-image-templates.css index 9df46d3..4481255 100644 --- a/public/static/css/phone-image-templates.css +++ b/public/static/css/phone-image-templates.css @@ -747,7 +747,17 @@ body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-norm box-sizing: border-box; } +/* ============================================ + Editor Content - Basic Readability + 编辑器容器仅保留基础可读性样式 + ============================================ */ + +/* Editor container - basic readability only */ #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); width: 540px; min-height: 300px; padding: 10px; @@ -755,93 +765,95 @@ body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-norm box-sizing: border-box; } +/* Keep images constrained in editor */ +#editor-text-area img { + max-width: 100%; + height: auto; +} + /* ============================================ - Editor Content Typography (编辑器内排版样式) - 与截图输出区域(staging)的视觉保持一致 - 作用域: #editor-text-area 内部元素 + Render Preview Area (渲染预览区排版样式) + 中间预览列的完整排版样式 ============================================ */ -#editor-text-area { +#render-preview { 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 { +/* 预览区标题字号 */ +#render-preview 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 { +#render-preview h2 { font-weight: normal; font-size: 18px; letter-spacing: 1px; margin-top: 24px; margin-bottom: 12px; - color: #333; } -#editor-text-area h3 { +#render-preview h3 { font-weight: normal; font-size: 16px; margin-top: 20px; margin-bottom: 10px; - color: #555; } -#editor-text-area h4 { +#render-preview h4 { font-size: 14px; font-weight: bold; - margin-top: var(--pi-spacing-sm); + margin-top: 10px; margin-bottom: 5px; } -#editor-text-area h5 { +#render-preview h5 { font-size: 13px; font-weight: bold; - margin-top: var(--pi-spacing-sm); + margin-top: 10px; margin-bottom: 5px; } -#editor-text-area h6 { +#render-preview h6 { font-size: 12px; font-weight: bold; - margin-top: var(--pi-spacing-sm); + margin-top: 10px; margin-bottom: 5px; - color: var(--pi-color-text-light); + color: #999; } -/* 编辑区段落 */ -#editor-text-area p { +/* 预览区段落 */ +#render-preview p { text-indent: 2em; margin-bottom: 16px; color: #333; } -/* 编辑区图片 */ -#editor-text-area img { +/* 预览区图片 */ +#render-preview img { max-width: 100% !important; height: auto !important; display: block; margin: 10px 0; } -/* 编辑区表格 */ -#editor-text-area table { +/* 预览区表格 */ +#render-preview table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 13px; } -#editor-text-area th { +#render-preview th { background-color: #f0f0f0; font-weight: bold; padding: 8px 10px; @@ -849,17 +861,17 @@ body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-norm text-align: left; } -#editor-text-area td { +#render-preview td { padding: 8px 10px; border: 1px solid #ddd; } -#editor-text-area tr:nth-child(even) td { +#render-preview tr:nth-child(even) td { background-color: #f9f9f9; } -/* 编辑区代码块 */ -#editor-text-area pre { +/* 预览区代码块 */ +#render-preview pre { background: #f5f5f5; border: 1px solid #e0e0e0; border-radius: 4px; @@ -870,34 +882,34 @@ body > .page-header-right .layui-btn:not(.layui-btn-primary):not(.layui-btn-norm margin: 10px 0; } -#editor-text-area code { +#render-preview code { font-family: 'Courier New', Consolas, monospace; font-size: 13px; } -#editor-text-area pre code { +#render-preview pre code { background: none; border: none; padding: 0; } -/* 编辑区引用 */ -#editor-text-area blockquote { +/* 预览区引用 */ +#render-preview blockquote { border-left: 1px solid #ccc; font-style: normal; color: #888; padding-left: 15px; - margin: var(--pi-spacing-sm) 0; + margin: 10px 0; } -/* 编辑区列表 */ -#editor-text-area ul, -#editor-text-area ol { - padding-left: var(--pi-spacing); - margin-bottom: var(--pi-spacing-sm); +/* 预览区列表 */ +#render-preview ul, +#render-preview ol { + padding-left: 20px; + margin-bottom: 10px; } -#editor-text-area li { +#render-preview li { margin-bottom: 5px; } diff --git a/public/static/js/phone-image.js b/public/static/js/phone-image.js index 97c36bc..657e09a 100644 --- a/public/static/js/phone-image.js +++ b/public/static/js/phone-image.js @@ -95,9 +95,10 @@ var PhoneImageEngine = (function () { var pageHeight = sizeConfig.height; var contentAreaHeight = pageHeight - (config.contentPadding * 2); - // 从 wangeditor 读取内容 - var editorHtml = window.phoneImageEditor ? window.phoneImageEditor.getHtml() : postData.content_html; - var cleanHtml = preprocessContent(editorHtml); + // 从预览区读取已预处理的内容 + syncPreview(); + var previewEl = document.getElementById('render-preview'); + var cleanHtml = previewEl ? previewEl.innerHTML : ''; var blocks = parseHtmlToBlocks(cleanHtml); // 空内容检测 @@ -111,7 +112,7 @@ var PhoneImageEngine = (function () { pages.push(generateCoverPage(sizeConfig)); // 内容分页(使用 captureEditorBlocks,T3 会完善截图逻辑) - captureEditorBlocks(editorHtml, blocks, contentAreaHeight, sizeConfig).then(function(contentPages) { + captureEditorBlocks(cleanHtml, blocks, contentAreaHeight, sizeConfig).then(function(contentPages) { pages = pages.concat(contentPages); // 尾页 @@ -194,6 +195,15 @@ var PhoneImageEngine = (function () { return html; } + /** + * 同步渲染预览区 - 将编辑器HTML写入预览区 + */ + function syncPreview() { + var html = window.phoneImageEditor ? window.phoneImageEditor.getHtml() : postData.content_html; + var cleanHtml = preprocessContent(html); + $('#render-preview').html(cleanHtml); + } + /** * 将HTML解析为块级元素数组 * 每个块: { type, html, estimatedHeight } @@ -354,7 +364,6 @@ var PhoneImageEngine = (function () { if (blocks[i].type === 'page-break') { if (currentGroup.blocks.length > 0) groups.push(currentGroup); currentGroup = { blocks: [], domIndices: [] }; - // page-break 对应 wangeditor 中的 divider(hr),在 DOM 中也是一个子元素 domIdx++; continue; } @@ -369,70 +378,55 @@ var PhoneImageEngine = (function () { return deferred.promise(); } - // 获取编辑器子元素 - var editorChildren = getEditorChildren(); - - // 创建测量容器(500px = 540 - 20*2 padding,与内容区一致) - var $mc = $('
').css({ - width: '500px', - position: 'fixed', - left: '-9999px', - top: '0', - visibility: 'hidden', - background: '#ffffff', - fontSize: '14px', - lineHeight: '1.8', - boxSizing: 'border-box' - }); - $('body').append($mc); - - // 串行测高每组 - var gi = 0; - - function measureNext() { - if (gi >= groups.length) { - $mc.remove(); - // 给尚未设置高度的 block 设默认值 - for (var k = 0; k < blocks.length; k++) { - if (!blocks[k].estimatedHeight) { - blocks[k].estimatedHeight = 40; - } - } - // 用测量的实际高度后的 blocks 调用原有 paginateContent 分页 - var contentPages = paginateContent(blocks, contentAreaHeight, sizeConfig); - deferred.resolve(contentPages); - return; + // 从 #render-preview 获取子元素(已预处理的干净内容) + var previewEl = document.getElementById('render-preview'); + if (!previewEl) { + // fallback: 如果预览区不存在,给所有block设默认高度 + for (var k = 0; k < blocks.length; k++) { + if (!blocks[k].estimatedHeight) blocks[k].estimatedHeight = 40; } + var contentPages = paginateContent(blocks, contentAreaHeight, sizeConfig); + deferred.resolve(contentPages); + return deferred.promise(); + } + var previewChildren = Array.prototype.slice.call(previewEl.children); + + // 逐组测高 + for (var gi = 0; gi < groups.length; gi++) { var group = groups[gi]; - $mc.empty(); + var groupHeight = 0; for (var j = 0; j < group.domIndices.length; j++) { var di = group.domIndices[j]; - if (di < editorChildren.length) { - $mc.append(editorChildren[di].cloneNode(true)); + if (di < previewChildren.length) { + var rect = previewChildren[di].getBoundingClientRect(); + groupHeight += Math.round(rect.height); } } - requestAnimationFrame(function () { - var h = Math.round($mc[0].getBoundingClientRect().height); + if (groupHeight === 0) groupHeight = group.blocks.length * 40; - // 按比例分配测量的实际高度给组内各个 block - var totalEstimated = 0; - for (var k = 0; k < group.blocks.length; k++) { - totalEstimated += (group.blocks[k].estimatedHeight || 40); - } - for (var k = 0; k < group.blocks.length; k++) { - var ratio = (group.blocks[k].estimatedHeight || 40) / (totalEstimated || 1); - group.blocks[k].estimatedHeight = Math.round(h * ratio); - } - - gi++; - measureNext(); - }); + // 按比例分配测量的实际高度给组内各个 block + var totalEstimated = 0; + for (var k = 0; k < group.blocks.length; k++) { + totalEstimated += (group.blocks[k].estimatedHeight || 40); + } + for (var k = 0; k < group.blocks.length; k++) { + var ratio = (group.blocks[k].estimatedHeight || 40) / (totalEstimated || 1); + group.blocks[k].estimatedHeight = Math.round(groupHeight * ratio); + } } - measureNext(); + // 给尚未设置高度的 block 设默认值 + for (var k = 0; k < blocks.length; k++) { + if (!blocks[k].estimatedHeight) { + blocks[k].estimatedHeight = 40; + } + } + + var contentPages = paginateContent(blocks, contentAreaHeight, sizeConfig); + deferred.resolve(contentPages); return deferred.promise(); } @@ -1348,6 +1342,7 @@ var PhoneImageEngine = (function () { generateImages: generateImages, saveImages: saveImages, saveConfig: saveConfig, + syncPreview: syncPreview, setPageAlignment: setPageAlignment, exportLongImage: exportLongImage, getContentHtml: function () { diff --git a/view/admin/post/phone_image.html b/view/admin/post/phone_image.html index 6bb1d09..3c7065a 100644 --- a/view/admin/post/phone_image.html +++ b/view/admin/post/phone_image.html @@ -70,6 +70,31 @@ 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; overflow-x: auto; @@ -137,6 +162,12 @@
+ +
+
渲染预览
+
+
+
@@ -214,6 +245,12 @@ autoSaveTimer = setTimeout(function () { doAutoSave(); }, 2600); + + // 预览同步:300ms 防抖(独立于自动保存) + clearTimeout(window._previewSyncTimer); + window._previewSyncTimer = setTimeout(function () { + PhoneImageEngine.syncPreview(); + }, 300); }; // 粘贴处理:处理外部图片下载 @@ -289,6 +326,9 @@ PhoneImageEngine.init(postData, initConfig); + // 初始同步预览区 + PhoneImageEngine.syncPreview(); + // ========== 设置弹框 ========== $('#btn-settings').click(function () { var settingsHtml = '
';