feat(output_view): 导出页面重构 - 长图卡片化展示、缩略图增大、预览优化、纯图片页原图保存

- output_view.html: 长图改为固定高度卡片(70px),Blob URL查看,缩略图minmax(280px,1fr),
  竖图预览优先填充视口高度,下载功能完整保留
- phone-image.js: renderPureImageToCanvas()使用naturalWidth/naturalHeight保持原图分辨率,
  新增长图生成和保存功能
- Post.php: 新增outputView()方法提供导出页面渲染数据
- PhoneImage.php: 图片数据改为DB存储,新增saveLongImage()方法
- phone_image.html: 添加导出页面入口按钮
- 新增数据库迁移: post_output_file表添加image_data字段
This commit is contained in:
augushong
2026-05-14 23:22:19 +08:00
parent f60d1abff6
commit 10879a8037
6 changed files with 784 additions and 83 deletions

View File

@@ -912,14 +912,15 @@ var PhoneImageEngine = (function () {
return;
}
var canvas = document.createElement('canvas');
canvas.width = width * 2; // scale:2 高质量
canvas.height = height * 2;
// 使用原图尺寸,保持原始清晰度
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
var ctx = canvas.getContext('2d');
// 白色背景
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 缩放绘制
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// 按原图尺寸绘制,不缩放
ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
deferred.resolve(canvas);
};
img.onerror = function () {
@@ -1179,6 +1180,7 @@ var PhoneImageEngine = (function () {
function saveImages(postId, saveConfig, onProgress) {
var deferred = $.Deferred();
// 第一步:生成分页图片和长图
capturePagesFromStaging().then(function (canvases) {
if (onProgress) onProgress(canvases.length, canvases.length, null);
var pagesData = [];
@@ -1186,19 +1188,27 @@ var PhoneImageEngine = (function () {
pagesData.push(canvases[i].toDataURL('image/jpeg', 0.92));
}
// 第二步:生成长图
return generateLongImageBase64().then(function(longBase64) {
return { pages: pagesData, longImage: longBase64 };
});
}).then(function(result) {
var pagesData = result.pages;
var longImageData = result.longImage;
// 检查数据总大小,防止超大文章导致请求失败
var totalSize = 0;
for (var i = 0; i < pagesData.length; i++) {
if (pagesData[i]) {
totalSize += pagesData[i].length;
}
if (pagesData[i]) totalSize += pagesData[i].length;
}
if (longImageData) totalSize += longImageData.length;
if (totalSize > 16 * 1024 * 1024) {
layer.msg('数据量过大超过16MB请减少内容或联系管理员');
deferred.reject('数据量过大超过16MB');
return;
}
// 第三步:发送到后端
$.ajax({
url: '/index.php/admin/post/savePostOutput',
type: 'POST',
@@ -1207,7 +1217,8 @@ var PhoneImageEngine = (function () {
output_type: 'phone_image',
config: saveConfig || config,
content_html: postData.content_html,
pages: pagesData
pages: pagesData,
long_image: longImageData
}),
contentType: 'application/json',
success: function (result) {
@@ -1218,6 +1229,20 @@ var PhoneImageEngine = (function () {
}
},
error: function (xhr) {
// 容错后端可能返回200 JSON + 500错误页面的混合响应
// 尝试从响应文本开头提取有效JSON
try {
var text = xhr.responseText || '';
var jsonEnd = text.indexOf('}');
if (jsonEnd > 0) {
var jsonStr = text.substring(0, jsonEnd + 1);
var parsed = JSON.parse(jsonStr);
if (parsed && parsed.code === 0) {
deferred.resolve(parsed.data || {});
return;
}
}
} catch (e) { /* 解析失败,走正常错误流程 */ }
deferred.reject('网络错误: ' + xhr.statusText);
}
});
@@ -1262,6 +1287,19 @@ var PhoneImageEngine = (function () {
}
},
error: function (xhr) {
// 容错后端可能返回200 JSON + 500错误页面的混合响应
try {
var text = xhr.responseText || '';
var jsonEnd = text.indexOf('}');
if (jsonEnd > 0) {
var jsonStr = text.substring(0, jsonEnd + 1);
var parsed = JSON.parse(jsonStr);
if (parsed && parsed.code === 0) {
deferred.resolve(parsed.data || {});
return;
}
}
} catch (e) { /* 解析失败,走正常错误流程 */ }
deferred.reject('网络错误: ' + xhr.statusText);
}
});
@@ -1283,6 +1321,68 @@ var PhoneImageEngine = (function () {
config.pageAlignments[pageNum] = align;
}
// ===== 长图base64生成不触发下载 =====
/**
* 生成长图base64不触发下载
* @returns {jQuery Deferred} resolves with base64 string or null
*/
function generateLongImageBase64() {
var deferred = $.Deferred();
var editorChildren = getEditorChildren();
if (editorChildren.length === 0) {
deferred.resolve(null);
return deferred.promise();
}
var $container = $('<div>').css({
width: '540px',
position: 'fixed',
left: '-9999px',
top: '0',
visibility: 'visible',
background: '#ffffff',
padding: '20px',
boxSizing: 'border-box',
fontSize: '14px',
lineHeight: '1.8'
});
for (var i = 0; i < editorChildren.length; i++) {
var el = editorChildren[i];
var isDivider = el.getAttribute('data-w-e-type') === 'divider' ||
el.tagName.toLowerCase() === 'hr';
if (!isDivider) {
$container.append(el.cloneNode(true));
}
}
$('body').append($container);
requestAnimationFrame(function () {
html2canvas($container[0], {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff',
width: $container.outerWidth(),
height: $container[0].scrollHeight,
logging: false
}).then(function (canvas) {
$container.remove();
// 使用JPEG压缩减少体积
deferred.resolve(canvas.toDataURL('image/jpeg', 0.85));
}).catch(function (err) {
$container.remove();
// 长图生成失败不阻断主流程
console.warn('长图生成失败:', err);
deferred.resolve(null);
});
});
return deferred.promise();
}
// ===== 导出长图 =====
/**