Files
ulthon_information/view/admin/post/phone_image.html
augushong 83a2bd48a2 feat(post): 新增手机图片排版与AI智能排版功能
- 新增手机图片排版功能,支持小红书/抖音尺寸输出
- 新增AI智能排版顾问,支持内容分析与优化推荐
- 新增AI供应商管理,支持多渠道配置与同步
- 新增文章输出管理页面,支持图片预览与批量下载
- 新增字体文件与排版样式配置
2026-05-01 12:23:17 +08:00

426 lines
17 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/css/phone-image-templates.css">
<link rel="stylesheet" href="/static/css/phone-image-fonts.css">
<style>
body {
margin: 0;
padding: 0;
background: #f2f2f2;
}
.page-header {
background: #fff;
padding: 15px 20px;
border-bottom: 1px solid #e8e8e8;
display: flex;
justify-content: space-between;
align-items: center;
}
.page-header h3 {
margin: 0;
font-size: 18px;
}
.main-layout {
display: flex;
height: calc(100vh - 60px);
}
.toolbar {
width: 260px;
background: #fff;
border-right: 1px solid #e8e8e8;
padding: 15px;
overflow-y: auto;
}
.preview-area {
flex: 1;
padding: 20px;
overflow-y: auto;
display: flex;
justify-content: center;
align-items: flex-start;
}
.toolbar .layui-form-label {
width: 60px;
padding: 6px 8px;
font-size: 13px;
}
.toolbar .layui-input-block {
margin-left: 70px;
}
.toolbar .layui-form-item {
margin-bottom: 12px;
}
.template-btn-group {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.template-btn {
flex: 1;
padding: 8px;
text-align: center;
border: 2px solid #e8e8e8;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
background: #fff;
}
.template-btn.active {
border-color: #1890ff;
color: #1890ff;
}
.template-btn:hover {
border-color: #1890ff;
}
.action-btns {
margin-top: 15px;
}
.action-btns .layui-btn {
width: 100%;
margin-bottom: 8px;
}
.phone-frame {
background: #fff;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
border-radius: 8px;
overflow: hidden;
}
.preview-nav {
text-align: center;
margin-top: 10px;
}
.preview-nav span {
margin: 0 10px;
cursor: pointer;
color: #1890ff;
}
</style>
</head>
<body>
<!-- 隐藏div存放文章HTML内容供JS读取 -->
<div id="post-content-html" style="display:none;">{$post->content_html|raw}</div>
<div class="page-header">
<div>
<a href="{:url('post/index')}" class="layui-btn layui-btn-sm layui-btn-primary"><i
class="layui-icon layui-icon-return"></i> 返回列表</a>
<a href="{:url('post/postOutputList',['id'=>$post.id])}" class="layui-btn layui-btn-sm">输出管理</a>
</div>
<h3>{$post.title}</h3>
</div>
<div class="main-layout">
<!-- 左侧工具栏 -->
<div class="toolbar">
<div class="layui-form" lay-filter="phoneImageForm">
<div class="layui-form-item">
<label class="layui-form-label">模板</label>
<div class="layui-input-block">
<div class="template-btn-group">
<div class="template-btn active" data-template="minimal">简约</div>
<div class="template-btn" data-template="magazine">杂志</div>
<div class="template-btn" data-template="mixed">图文</div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">尺寸</label>
<div class="layui-input-block">
<select name="size" lay-filter="size">
<option value="xiaohongshu">小红书 (1080x1440)</option>
<option value="douyin">抖音 (1080x1920)</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">字体</label>
<div class="layui-input-block">
<select name="font" lay-filter="font">
<option value="source-han-sans">思源黑体</option>
<option value="alibaba-puhuiti">阿里巴巴普惠体</option>
<option value="lxgw-wenkai">霞鹜文楷</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">字号</label>
<div class="layui-input-block">
<input type="range" name="fontSize" min="10" max="24" value="14" lay-filter="fontSize"
style="width:100%;">
<span id="fontSizeValue">14px</span>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">水印</label>
<div class="layui-input-block">
<input type="text" name="watermark" placeholder="可选水印文字" class="layui-input">
</div>
</div>
<!-- AI 智能排版 -->
<div style="margin-top: 15px; margin-bottom: 10px; padding-top: 10px; border-top: 1px solid #e8e8e8;">
<label class="layui-form-label" style="font-size: 13px; color: #1890ff;">AI 助手</label>
<div class="layui-input-block" style="margin-top: 5px;">
<button type="button" class="layui-btn layui-btn-sm layui-btn-normal" id="btn-ai-recommend" style="width: 100%; margin-bottom: 5px;">
<i class="layui-icon layui-icon-magic"></i> AI 智能排版
</button>
<button type="button" class="layui-btn layui-btn-sm layui-btn-warm" id="btn-ai-optimize" style="width: 100%;">
<i class="layui-icon layui-icon-edit"></i> AI 优化内容
</button>
</div>
</div>
<div id="ai-reason" style="display:none; margin: 10px 0; padding: 8px 12px; background: #f0f7ff; border-radius: 4px; font-size: 12px; color: #666; line-height: 1.6;"></div>
<div id="ai-content-panel" style="display:none; margin: 10px 0; padding: 10px; background: #fffbe6; border: 1px solid #ffe58f; border-radius: 4px; font-size: 12px;">
<div style="font-weight: bold; margin-bottom: 5px;">AI 内容优化建议</div>
<div id="ai-optimized-title" style="margin-bottom: 5px;"></div>
<div id="ai-summary-points" style="margin-bottom: 8px;"></div>
<button type="button" class="layui-btn layui-btn-xs layui-btn-normal" id="btn-apply-ai">应用优化</button>
<button type="button" class="layui-btn layui-btn-xs layui-btn-primary" id="btn-keep-original">保持原文</button>
</div>
<div class="action-btns">
<button type="button" class="layui-btn" id="btn-preview"><i
class="layui-icon layui-icon-refresh"></i> 预览排版</button>
<button type="button" class="layui-btn layui-btn-normal" id="btn-generate"><i
class="layui-icon layui-icon-picture"></i> 生成并保存</button>
<button type="button" class="layui-btn layui-btn-warm" id="btn-download"><i
class="layui-icon layui-icon-download-circle"></i> 打包下载</button>
</div>
</div>
</div>
<!-- 右侧预览区 -->
<div class="preview-area">
<div>
<div class="phone-frame">
<div id="phone-image-container"
class="phone-image-container tpl-minimal size-xiaohongshu font-source-han-sans">
</div>
</div>
<div class="preview-nav">
<span id="prev-page"><i class="layui-icon layui-icon-left"></i> 上一页</span>
<span id="page-info">第 1 页 / 共 0 页</span>
<span id="next-page">下一页 <i class="layui-icon layui-icon-right"></i></span>
</div>
</div>
</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/html2canvas/html2canvas.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 = null;
var downloadBaseUrl = '{:url("post/downloadPostOutputZip", ["id" => 0])}';
var postData = {
postId: {$post.id},
title: '{$post.title|raw}',
desc: '{$post.desc|default=""}',
contentHtml: $('#post-content-html').html(),
poster: '{$post.poster|default=""}',
authorName: '{$post.author_name|default=""}',
createTime: '{$post.create_time_text|default=""}',
categoryName: ''
};
// 初始化引擎
PhoneImageEngine.init(postData, {
template: 'minimal',
size: 'xiaohongshu',
font: 'source-han-sans',
fontSize: 14
});
// 模板切换
$('.template-btn').click(function () {
$('.template-btn').removeClass('active');
$(this).addClass('active');
PhoneImageEngine.switchTemplate($(this).data('template'));
doRender();
});
// 尺寸切换
form.on('select(size)', function (data) {
PhoneImageEngine.switchSize(data.value);
doRender();
});
// 字体切换
form.on('select(font)', function (data) {
PhoneImageEngine.switchFont(data.value);
doRender();
});
// 字号调整
$('[name="fontSize"]').on('input', function () {
$('#fontSizeValue').text($(this).val() + 'px');
});
// 预览
$('#btn-preview').click(function () {
doRender();
});
function doRender() {
var fontSize = parseInt($('[name="fontSize"]').val()) || 14;
PhoneImageEngine.init(postData, {
template: $('.template-btn.active').data('template'),
size: $('[name="size"]').val(),
font: $('[name="font"]').val(),
fontSize: fontSize
});
var pages = PhoneImageEngine.render();
layer.msg('排版完成,共 ' + pages.length + ' 页');
}
// 生成并保存
$('#btn-generate').click(function () {
var btn = $(this);
btn.prop('disabled', true).text('生成中...');
layer.msg('正在生成图片,请稍候...');
PhoneImageEngine.saveImages(postData.postId, {
template: $('.template-btn.active').data('template'),
size: $('[name="size"]').val(),
font: $('[name="font"]').val(),
fontSize: parseInt($('[name="fontSize"]').val()) || 14,
watermark: $('[name="watermark"]').val()
}).then(function (data) {
if (data.output_id) {
lastOutputId = data.output_id;
}
layer.msg('保存成功!');
btn.prop('disabled', false).html('<i class="layui-icon layui-icon-picture"></i> 生成并保存');
}).catch(function (err) {
layer.msg('保存失败: ' + err);
btn.prop('disabled', false).html('<i class="layui-icon layui-icon-picture"></i> 生成并保存');
});
});
// 打包下载
$('#btn-download').click(function () {
if (!lastOutputId) {
layer.msg('请先生成并保存图片');
return;
}
var url = downloadBaseUrl.replace('/0/', '/' + lastOutputId + '/');
window.open(url);
});
// ===== AI 智能排版 =====
// AI智能排版推荐
$('#btn-ai-recommend').click(function () {
var btn = $(this);
btn.prop('disabled', true).text('AI 分析中...');
$.post('{:url("post/aiRecommend")}', { post_id: postData.postId }, function (res) {
btn.prop('disabled', false).html('<i class="layui-icon layui-icon-magic"></i> AI 智能排版');
if (res.code === 0) {
PhoneImageEngine.applyAiRecommendation(res.data);
if (res.data.reason) {
$('#ai-reason').html('AI 推荐理由: ' + escapeHtmlSimple(res.data.reason)).show();
}
doRender();
layer.msg('AI 排版推荐已应用');
} else {
layer.msg(res.msg || 'AI 分析失败');
}
}).fail(function () {
btn.prop('disabled', false).html('<i class="layui-icon layui-icon-magic"></i> AI 智能排版');
layer.msg('网络错误');
});
});
// AI优化内容
$('#btn-ai-optimize').click(function () {
var btn = $(this);
btn.prop('disabled', true).text('AI 优化中...');
$.post('{:url("post/aiOptimizeContent")}', { post_id: postData.postId }, function (res) {
btn.prop('disabled', false).html('<i class="layui-icon layui-icon-edit"></i> AI 优化内容');
if (res.code === 0) {
var data = res.data;
var titleHtml = '<b>原标题:</b> ' + escapeHtmlSimple(postData.title);
if (data.optimized_title) {
titleHtml += '<br><b>优化标题:</b> ' + escapeHtmlSimple(data.optimized_title);
}
$('#ai-optimized-title').html(titleHtml);
if (data.summary_points) {
var points = '<b>要点:</b><br>';
for (var i = 0; i < data.summary_points.length; i++) {
points += (i + 1) + '. ' + escapeHtmlSimple(data.summary_points[i]) + '<br>';
}
$('#ai-summary-points').html(points);
}
$('#ai-content-panel').data('aiData', data).show();
} else {
layer.msg(res.msg || 'AI 优化失败');
}
}).fail(function () {
btn.prop('disabled', false).html('<i class="layui-icon layui-icon-edit"></i> AI 优化内容');
layer.msg('网络错误');
});
});
// 应用AI优化
$('#btn-apply-ai').click(function () {
var aiData = $('#ai-content-panel').data('aiData');
if (aiData) {
PhoneImageEngine.applyAiContent(aiData);
doRender();
$('#ai-content-panel').hide();
layer.msg('已应用 AI 优化内容');
}
});
// 保持原文
$('#btn-keep-original').click(function () {
PhoneImageEngine.restoreOriginalContent();
doRender();
$('#ai-content-panel').hide();
layer.msg('已恢复原文');
});
function escapeHtmlSimple(text) {
if (!text) return '';
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
});
</script>
</body>
</html>