mirror of
https://gitee.com/ulthon/ulthon_information.git
synced 2026-07-01 16:32:48 +08:00
refactor(phone-image): Wave 2 - form, controller, JS engine, API updates
T3: Add cover_text textarea to post edit form T4: Update Post controller - content copy + cover_text passing T5: Refactor JS engine - remove old APIs, add forced breaks, page numbers, per-page alignment T8: Add cover_text to API default_fields, apidoc (4 places), AGENTS.md
This commit is contained in:
@@ -73,6 +73,7 @@ AJAX请求 -> JSON; 普通请求 -> 模板页面(common@tpl/success|error)
|
|||||||
Post --(M:N)--> Category (通过 post_category)
|
Post --(M:N)--> Category (通过 post_category)
|
||||||
Post --(M:N)--> Tag (通过 post_tag)
|
Post --(M:N)--> Tag (通过 post_tag)
|
||||||
Post --(1:N)--> PostComment, PostVisit
|
Post --(1:N)--> PostComment, PostVisit
|
||||||
|
Post 字段含 cover_text: 封面文案,用于手机图片排版封面展示
|
||||||
Admin --(N:1)--> AdminGroup --(权限列表)--> AdminPermission
|
Admin --(N:1)--> AdminGroup --(权限列表)--> AdminPermission
|
||||||
表前缀: ul_,时间戳: int(10),软删除: delete_time字段
|
表前缀: ul_,时间戳: int(10),软删除: delete_time字段
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -358,7 +358,24 @@ class Post extends Common
|
|||||||
if (empty($model_post)) {
|
if (empty($model_post)) {
|
||||||
$this->error('文章不存在');
|
$this->error('文章不存在');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查询已有的排版输出,获取排版内容副本
|
||||||
|
$postOutput = \app\model\PostOutput::where('post_id', $id)
|
||||||
|
->where('output_type', 'phone_image')
|
||||||
|
->order('id', 'desc')
|
||||||
|
->find();
|
||||||
|
|
||||||
|
// 排版内容副本:优先使用已保存的副本,否则使用原文
|
||||||
|
$layoutContentHtml = $model_post->content_html;
|
||||||
|
$layoutConfig = [];
|
||||||
|
if ($postOutput && !empty($postOutput->config['content_html'])) {
|
||||||
|
$layoutContentHtml = $postOutput->config['content_html'];
|
||||||
|
$layoutConfig = $postOutput->config;
|
||||||
|
}
|
||||||
|
|
||||||
View::assign('post', $model_post);
|
View::assign('post', $model_post);
|
||||||
|
View::assign('layoutContentHtml', $layoutContentHtml);
|
||||||
|
View::assign('layoutConfig', $layoutConfig);
|
||||||
return View::fetch();
|
return View::fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,6 +412,10 @@ class Post extends Common
|
|||||||
}
|
}
|
||||||
|
|
||||||
$config = $data['config'] ?? [];
|
$config = $data['config'] ?? [];
|
||||||
|
// 确保排版内容副本被保存到 config
|
||||||
|
if (isset($data['content_html']) && !isset($config['content_html'])) {
|
||||||
|
$config['content_html'] = $data['content_html'];
|
||||||
|
}
|
||||||
$pages = $data['pages'] ?? [];
|
$pages = $data['pages'] ?? [];
|
||||||
|
|
||||||
$admin_id = session('admin_id') ?? 0;
|
$admin_id = session('admin_id') ?? 0;
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ class Articles extends BaseController
|
|||||||
'jump_to_url_status' => 0,
|
'jump_to_url_status' => 0,
|
||||||
'poster' => '',
|
'poster' => '',
|
||||||
'desc' => '',
|
'desc' => '',
|
||||||
|
'cover_text' => '',
|
||||||
'author_name' => '',
|
'author_name' => '',
|
||||||
'hits' => 0,
|
'hits' => 0,
|
||||||
'is_top' => 0,
|
'is_top' => 0,
|
||||||
|
|||||||
@@ -12,23 +12,14 @@ var PhoneImageEngine = (function () {
|
|||||||
|
|
||||||
// ===== 配置 =====
|
// ===== 配置 =====
|
||||||
var config = {
|
var config = {
|
||||||
template: 'minimal',
|
|
||||||
size: 'xiaohongshu',
|
size: 'xiaohongshu',
|
||||||
font: 'source-han-sans',
|
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
watermark: '',
|
watermark: '',
|
||||||
// 尺寸映射 (渲染尺寸, 实际输出是2倍)
|
pageAlignments: {}, // key=页码(1-based), value='top'|'center'
|
||||||
sizes: {
|
sizes: {
|
||||||
xiaohongshu: { width: 540, height: 720 },
|
xiaohongshu: { width: 540, height: 720 },
|
||||||
douyin: { width: 540, height: 960 }
|
douyin: { width: 540, height: 960 }
|
||||||
},
|
},
|
||||||
// 字体映射
|
|
||||||
fonts: {
|
|
||||||
'source-han-sans': "'Source Han Sans', 'SourceHanSans-Normal', sans-serif",
|
|
||||||
'alibaba-puhuiti': "'AlibabaPuHuiTi', sans-serif",
|
|
||||||
'lxgw-wenkai': "'LXGWWenKai', cursive"
|
|
||||||
},
|
|
||||||
// 页面内容边距
|
|
||||||
contentPadding: 20
|
contentPadding: 20
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +28,7 @@ var PhoneImageEngine = (function () {
|
|||||||
id: 0,
|
id: 0,
|
||||||
title: '',
|
title: '',
|
||||||
desc: '',
|
desc: '',
|
||||||
|
coverText: '', // 封面文案
|
||||||
content_html: '',
|
content_html: '',
|
||||||
poster: '',
|
poster: '',
|
||||||
author_name: '',
|
author_name: '',
|
||||||
@@ -47,18 +39,16 @@ var PhoneImageEngine = (function () {
|
|||||||
// ===== 分页结果 =====
|
// ===== 分页结果 =====
|
||||||
var pages = [];
|
var pages = [];
|
||||||
|
|
||||||
// ===== DOM引用 =====
|
|
||||||
var $container = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化引擎
|
* 初始化引擎
|
||||||
* @param {Object} options - {postId, title, desc, contentHtml, poster, authorName, createTime, categoryName}
|
* @param {Object} options - {postId, title, desc, coverText, contentHtml, poster, authorName, createTime, categoryName}
|
||||||
* @param {Object} userConfig - {template, size, font, fontSize, watermark}
|
* @param {Object} userConfig - {size, fontSize, watermark}
|
||||||
*/
|
*/
|
||||||
function init(options, userConfig) {
|
function init(options, userConfig) {
|
||||||
postData.id = options.postId || 0;
|
postData.id = options.postId || 0;
|
||||||
postData.title = options.title || '';
|
postData.title = options.title || '';
|
||||||
postData.desc = options.desc || '';
|
postData.desc = options.desc || '';
|
||||||
|
postData.coverText = options.coverText || '';
|
||||||
postData.content_html = options.contentHtml || '';
|
postData.content_html = options.contentHtml || '';
|
||||||
postData.poster = options.poster || '';
|
postData.poster = options.poster || '';
|
||||||
postData.author_name = options.authorName || '';
|
postData.author_name = options.authorName || '';
|
||||||
@@ -68,12 +58,11 @@ var PhoneImageEngine = (function () {
|
|||||||
if (userConfig) {
|
if (userConfig) {
|
||||||
$.extend(config, userConfig);
|
$.extend(config, userConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
$container = $('#phone-image-container');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染排版预览 - 生成分页后的HTML
|
* 渲染排版预览 - 生成分页后的HTML
|
||||||
|
* 两遍遍历: 先算总页数, 再带页码生成
|
||||||
* @returns {Array} 页面数组,每项是 { type, html, pageNum }
|
* @returns {Array} 页面数组,每项是 { type, html, pageNum }
|
||||||
*/
|
*/
|
||||||
function render() {
|
function render() {
|
||||||
@@ -82,22 +71,31 @@ var PhoneImageEngine = (function () {
|
|||||||
var pageHeight = sizeConfig.height;
|
var pageHeight = sizeConfig.height;
|
||||||
var contentAreaHeight = pageHeight - (config.contentPadding * 2);
|
var contentAreaHeight = pageHeight - (config.contentPadding * 2);
|
||||||
|
|
||||||
// 内容预处理
|
|
||||||
var cleanHtml = preprocessContent(postData.content_html);
|
var cleanHtml = preprocessContent(postData.content_html);
|
||||||
|
|
||||||
// 解析为块级元素
|
|
||||||
var blocks = parseHtmlToBlocks(cleanHtml);
|
var blocks = parseHtmlToBlocks(cleanHtml);
|
||||||
|
|
||||||
// 生成封面页
|
// 封面页
|
||||||
pages.push(generateCoverPage(sizeConfig));
|
pages.push(generateCoverPage(sizeConfig));
|
||||||
|
|
||||||
// 内容分页
|
// 内容分页
|
||||||
var contentPages = paginateContent(blocks, contentAreaHeight, sizeConfig);
|
var contentPages = paginateContent(blocks, contentAreaHeight, sizeConfig);
|
||||||
pages = pages.concat(contentPages);
|
pages = pages.concat(contentPages);
|
||||||
|
|
||||||
// 生成尾页
|
// 尾页
|
||||||
pages.push(generateSummaryPage(sizeConfig, pages.length));
|
pages.push(generateSummaryPage(sizeConfig, pages.length));
|
||||||
|
|
||||||
|
// 两遍遍历: 现在知道了总页数, 给每页补上 N/M 页码
|
||||||
|
var totalPages = pages.length;
|
||||||
|
for (var i = 0; i < pages.length; i++) {
|
||||||
|
if (pages[i].type === 'content') {
|
||||||
|
// 在页脚中添加 N/M 格式页码
|
||||||
|
pages[i].html = pages[i].html.replace(
|
||||||
|
'<span>' + pages[i].pageNum + '</span>',
|
||||||
|
'<span class="page-number-indicator">' + (i + 1) + '/' + totalPages + '</span>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染到DOM
|
// 渲染到DOM
|
||||||
renderToDOM(sizeConfig);
|
renderToDOM(sizeConfig);
|
||||||
|
|
||||||
@@ -191,6 +189,11 @@ var PhoneImageEngine = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
switch (tagName) {
|
switch (tagName) {
|
||||||
|
case 'hr':
|
||||||
|
block.type = 'page-break';
|
||||||
|
block.html = '';
|
||||||
|
block.estimatedHeight = 0;
|
||||||
|
break;
|
||||||
case 'h1':
|
case 'h1':
|
||||||
case 'h2':
|
case 'h2':
|
||||||
case 'h3':
|
case 'h3':
|
||||||
@@ -296,7 +299,7 @@ var PhoneImageEngine = (function () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 将块级元素按高度累加,超过可用高度时分页
|
* 将块级元素按高度累加,超过可用高度时分页
|
||||||
* 支持超大块拆分(超长文本拆成多段)
|
* 支持 <hr> 强制分页和超大块拆分(超长文本拆成多段)
|
||||||
*/
|
*/
|
||||||
function paginateContent(blocks, contentAreaHeight, sizeConfig) {
|
function paginateContent(blocks, contentAreaHeight, sizeConfig) {
|
||||||
var contentPages = [];
|
var contentPages = [];
|
||||||
@@ -307,6 +310,20 @@ var PhoneImageEngine = (function () {
|
|||||||
for (var i = 0; i < blocks.length; i++) {
|
for (var i = 0; i < blocks.length; i++) {
|
||||||
var block = blocks[i];
|
var block = blocks[i];
|
||||||
|
|
||||||
|
// 强制分页标记
|
||||||
|
if (block.type === 'page-break') {
|
||||||
|
// 结束当前页
|
||||||
|
if (currentPageBlocks.length > 0) {
|
||||||
|
contentPages.push(generateContentPage(
|
||||||
|
currentPageBlocks, pageNumber, sizeConfig, false
|
||||||
|
));
|
||||||
|
currentPageBlocks = [];
|
||||||
|
currentHeight = 0;
|
||||||
|
pageNumber++;
|
||||||
|
}
|
||||||
|
continue; // 跳过这个 page-break 块本身
|
||||||
|
}
|
||||||
|
|
||||||
// 超大块处理:单个块超过整页高度时需要拆分
|
// 超大块处理:单个块超过整页高度时需要拆分
|
||||||
if (block.estimatedHeight > contentAreaHeight) {
|
if (block.estimatedHeight > contentAreaHeight) {
|
||||||
// 先把当前页已有的内容推出去
|
// 先把当前页已有的内容推出去
|
||||||
@@ -449,6 +466,10 @@ var PhoneImageEngine = (function () {
|
|||||||
if (postData.desc) {
|
if (postData.desc) {
|
||||||
html += '<div class="cover-subtitle">' + escapeHtml(postData.desc) + '</div>';
|
html += '<div class="cover-subtitle">' + escapeHtml(postData.desc) + '</div>';
|
||||||
}
|
}
|
||||||
|
// 封面文案(有封面图)
|
||||||
|
if (postData.coverText) {
|
||||||
|
html += '<div style="font-size:14px;color:#1890ff;margin-top:8px;">' + escapeHtml(postData.coverText) + '</div>';
|
||||||
|
}
|
||||||
html += '<div class="cover-meta">';
|
html += '<div class="cover-meta">';
|
||||||
if (postData.category_name) {
|
if (postData.category_name) {
|
||||||
html += escapeHtml(postData.category_name) + ' | ';
|
html += escapeHtml(postData.category_name) + ' | ';
|
||||||
@@ -469,6 +490,10 @@ var PhoneImageEngine = (function () {
|
|||||||
if (postData.desc) {
|
if (postData.desc) {
|
||||||
html += '<div class="cover-no-img-desc">' + escapeHtml(postData.desc) + '</div>';
|
html += '<div class="cover-no-img-desc">' + escapeHtml(postData.desc) + '</div>';
|
||||||
}
|
}
|
||||||
|
// 封面文案(无封面图)
|
||||||
|
if (postData.coverText) {
|
||||||
|
html += '<div style="font-size:14px;color:#1890ff;margin-top:10px;padding:0 30px;word-break:break-word;">' + escapeHtml(postData.coverText) + '</div>';
|
||||||
|
}
|
||||||
html += '<div class="cover-no-img-meta">';
|
html += '<div class="cover-no-img-meta">';
|
||||||
if (postData.category_name) {
|
if (postData.category_name) {
|
||||||
html += '<span>' + escapeHtml(postData.category_name) + '</span>';
|
html += '<span>' + escapeHtml(postData.category_name) + '</span>';
|
||||||
@@ -490,13 +515,17 @@ var PhoneImageEngine = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成内容页HTML
|
* 生成内容页HTML(含逐页对齐支持)
|
||||||
*/
|
*/
|
||||||
function generateContentPage(blocks, pageNum, sizeConfig, isLast) {
|
function generateContentPage(blocks, pageNum, sizeConfig, isLast) {
|
||||||
var html = '<div class="phone-image-page page-body" style="width:' +
|
// 读取逐页对齐配置
|
||||||
|
var alignment = (config.pageAlignments && config.pageAlignments[pageNum]) || 'top';
|
||||||
|
var valignClass = alignment === 'center' ? ' valign-center' : '';
|
||||||
|
|
||||||
|
var html = '<div class="phone-image-page page-body' + valignClass + '" style="width:' +
|
||||||
sizeConfig.width + 'px;height:' + sizeConfig.height + 'px;">';
|
sizeConfig.width + 'px;height:' + sizeConfig.height + 'px;">';
|
||||||
|
|
||||||
// 页头(仅首页内容页显示标题)
|
// 页头(仅第一页内容页显示标题)
|
||||||
if (pageNum === 1) {
|
if (pageNum === 1) {
|
||||||
html += '<div class="page-header">';
|
html += '<div class="page-header">';
|
||||||
html += '<div class="page-title">' + escapeHtml(postData.title) + '</div>';
|
html += '<div class="page-title">' + escapeHtml(postData.title) + '</div>';
|
||||||
@@ -510,7 +539,7 @@ var PhoneImageEngine = (function () {
|
|||||||
}
|
}
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
// 页脚
|
// 页脚 - 占位,render() 中会替换页码
|
||||||
html += '<div class="page-footer">';
|
html += '<div class="page-footer">';
|
||||||
html += '<span>' + escapeHtml(postData.author_name || '') + '</span>';
|
html += '<span>' + escapeHtml(postData.author_name || '') + '</span>';
|
||||||
html += '<span>' + pageNum + '</span>';
|
html += '<span>' + pageNum + '</span>';
|
||||||
@@ -546,119 +575,46 @@ var PhoneImageEngine = (function () {
|
|||||||
|
|
||||||
// ===== DOM渲染 =====
|
// ===== DOM渲染 =====
|
||||||
|
|
||||||
// 当前预览页码 (0-based)
|
|
||||||
var currentPreviewPage = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渲染分页结果到DOM
|
* 全尺寸平铺渲染到 #paginated-preview
|
||||||
*/
|
*/
|
||||||
function renderToDOM(sizeConfig) {
|
function renderToDOM(sizeConfig) {
|
||||||
if (!$container || !$container.length) return;
|
var $preview = $('#paginated-preview');
|
||||||
|
if (!$preview.length) return;
|
||||||
|
|
||||||
var containerClasses = [
|
$preview.empty();
|
||||||
'phone-image-container',
|
|
||||||
'tpl-' + config.template,
|
|
||||||
'size-' + config.size,
|
|
||||||
'font-' + config.font
|
|
||||||
];
|
|
||||||
|
|
||||||
$container.attr('class', containerClasses.join(' '));
|
|
||||||
$container.css({
|
|
||||||
'font-family': config.fonts[config.font] || config.fonts['source-han-sans'],
|
|
||||||
'font-size': config.fontSize + 'px'
|
|
||||||
});
|
|
||||||
|
|
||||||
$container.empty();
|
|
||||||
for (var i = 0; i < pages.length; i++) {
|
for (var i = 0; i < pages.length; i++) {
|
||||||
$container.append(pages[i].html);
|
$preview.append(pages[i].html);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保当前页码有效
|
|
||||||
if (currentPreviewPage >= pages.length) {
|
|
||||||
currentPreviewPage = Math.max(0, pages.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示当前页, 隐藏其他页
|
|
||||||
showPreviewPage(currentPreviewPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示指定预览页
|
|
||||||
* @param {number} index 页码(0-based)
|
|
||||||
*/
|
|
||||||
function showPreviewPage(index) {
|
|
||||||
if (!$container || !$container.length) return;
|
|
||||||
var $allPages = $container.find('.phone-image-page');
|
|
||||||
$allPages.hide();
|
|
||||||
if (index >= 0 && index < $allPages.length) {
|
|
||||||
$allPages.eq(index).show();
|
|
||||||
currentPreviewPage = index;
|
|
||||||
}
|
|
||||||
// 更新页码显示
|
|
||||||
$container.trigger('pageChange', [currentPreviewPage, pages.length]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下一页
|
|
||||||
*/
|
|
||||||
function nextPage() {
|
|
||||||
if (currentPreviewPage < pages.length - 1) {
|
|
||||||
showPreviewPage(currentPreviewPage + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上一页
|
|
||||||
*/
|
|
||||||
function prevPage() {
|
|
||||||
if (currentPreviewPage > 0) {
|
|
||||||
showPreviewPage(currentPreviewPage - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前预览页码
|
|
||||||
*/
|
|
||||||
function getCurrentPageIndex() {
|
|
||||||
return currentPreviewPage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 图片生成 =====
|
// ===== 图片生成 =====
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 逐页截图生成图片
|
* 逐页截图生成图片(简化版:所有页面已在DOM中可见)
|
||||||
* @param {Function} [onProgress] 进度回调 function(currentIndex, totalPages, canvas)
|
* @param {Function} [onProgress] 进度回调 function(currentIndex, totalPages, canvas)
|
||||||
* @returns {Promise} jQuery Deferred, resolves with array of canvas objects
|
* @returns {Promise} jQuery Deferred, resolves with array of canvas objects
|
||||||
*/
|
*/
|
||||||
function generateImages(onProgress) {
|
function generateImages(onProgress) {
|
||||||
var deferred = $.Deferred();
|
var deferred = $.Deferred();
|
||||||
var canvases = [];
|
var canvases = [];
|
||||||
var $pages = $container.find('.phone-image-page');
|
var $pages = $('#paginated-preview .phone-image-page');
|
||||||
|
|
||||||
if ($pages.length === 0) {
|
if ($pages.length === 0) {
|
||||||
deferred.reject('没有可渲染的页面');
|
deferred.reject('没有可渲染的页面');
|
||||||
return deferred.promise();
|
return deferred.promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 截图时需要所有页面都显示
|
|
||||||
$pages.show();
|
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var total = $pages.length;
|
var total = $pages.length;
|
||||||
|
|
||||||
function captureNext() {
|
function captureNext() {
|
||||||
if (index >= total) {
|
if (index >= total) {
|
||||||
if (onProgress) onProgress(total, total, null);
|
if (onProgress) onProgress(total, total, null);
|
||||||
// 恢复单页显示
|
|
||||||
showPreviewPage(currentPreviewPage);
|
|
||||||
deferred.resolve(canvases);
|
deferred.resolve(canvases);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏其他页, 只显示当前要截图的页
|
|
||||||
$pages.hide();
|
|
||||||
$pages.eq(index).show();
|
|
||||||
|
|
||||||
html2canvas($pages.eq(index)[0], {
|
html2canvas($pages.eq(index)[0], {
|
||||||
scale: 2,
|
scale: 2,
|
||||||
useCORS: true,
|
useCORS: true,
|
||||||
@@ -672,8 +628,6 @@ var PhoneImageEngine = (function () {
|
|||||||
if (onProgress) onProgress(index, total, canvas);
|
if (onProgress) onProgress(index, total, canvas);
|
||||||
captureNext();
|
captureNext();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
// 恢复单页显示
|
|
||||||
showPreviewPage(currentPreviewPage);
|
|
||||||
deferred.reject('截图失败(第' + (index + 1) + '页): ' + err);
|
deferred.reject('截图失败(第' + (index + 1) + '页): ' + err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -748,16 +702,6 @@ var PhoneImageEngine = (function () {
|
|||||||
|
|
||||||
// ===== 配置切换 =====
|
// ===== 配置切换 =====
|
||||||
|
|
||||||
/**
|
|
||||||
* 切换模板
|
|
||||||
*/
|
|
||||||
function switchTemplate(name) {
|
|
||||||
config.template = name;
|
|
||||||
if ($container) {
|
|
||||||
$container.removeClass('tpl-minimal tpl-magazine tpl-mixed').addClass('tpl-' + name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换尺寸
|
* 切换尺寸
|
||||||
*/
|
*/
|
||||||
@@ -765,15 +709,18 @@ var PhoneImageEngine = (function () {
|
|||||||
config.size = name;
|
config.size = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== 逐页对齐 =====
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换字体
|
* 设置指定页的对齐方式
|
||||||
|
* @param {number} pageNum 页码(1-based)
|
||||||
|
* @param {string} align 'top'|'center'
|
||||||
*/
|
*/
|
||||||
function switchFont(name) {
|
function setPageAlignment(pageNum, align) {
|
||||||
config.font = name;
|
if (!config.pageAlignments) {
|
||||||
if ($container) {
|
config.pageAlignments = {};
|
||||||
$container.removeClass('font-source-han-sans font-alibaba-puhuiti font-lxgw-wenkai').addClass('font-' + name);
|
|
||||||
$container.css('font-family', config.fonts[name] || config.fonts['source-han-sans']);
|
|
||||||
}
|
}
|
||||||
|
config.pageAlignments[pageNum] = align;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 工具方法 =====
|
// ===== 工具方法 =====
|
||||||
@@ -802,76 +749,16 @@ var PhoneImageEngine = (function () {
|
|||||||
return pages.slice();
|
return pages.slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== AI 智能排版 =====
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用AI推荐配置
|
|
||||||
*/
|
|
||||||
function applyAiRecommendation(recommendation) {
|
|
||||||
if (recommendation.template) {
|
|
||||||
switchTemplate(recommendation.template);
|
|
||||||
$('.template-btn').removeClass('active');
|
|
||||||
$('.template-btn[data-template="' + recommendation.template + '"]').addClass('active');
|
|
||||||
$('[name="size"]').val(config.size);
|
|
||||||
}
|
|
||||||
if (recommendation.font) {
|
|
||||||
switchFont(recommendation.font);
|
|
||||||
$('[name="font"]').val(recommendation.font);
|
|
||||||
}
|
|
||||||
if (recommendation.font_size) {
|
|
||||||
config.fontSize = parseInt(recommendation.font_size);
|
|
||||||
$('[name="fontSize"]').val(config.fontSize);
|
|
||||||
$('#fontSizeValue').text(config.fontSize + 'px');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用AI优化内容(替换预览用的内容)
|
|
||||||
*/
|
|
||||||
var originalContentHtml = '';
|
|
||||||
function applyAiContent(optimizedContent) {
|
|
||||||
if (!originalContentHtml) {
|
|
||||||
originalContentHtml = postData.content_html;
|
|
||||||
}
|
|
||||||
if (optimizedContent.optimized_paragraphs) {
|
|
||||||
var newHtml = '';
|
|
||||||
for (var i = 0; i < optimizedContent.optimized_paragraphs.length; i++) {
|
|
||||||
newHtml += '<p>' + optimizedContent.optimized_paragraphs[i] + '</p>';
|
|
||||||
}
|
|
||||||
postData.content_html = newHtml;
|
|
||||||
postData.title = optimizedContent.optimized_title || postData.title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复原始内容
|
|
||||||
*/
|
|
||||||
function restoreOriginalContent() {
|
|
||||||
if (originalContentHtml) {
|
|
||||||
postData.content_html = originalContentHtml;
|
|
||||||
originalContentHtml = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 公开API =====
|
// ===== 公开API =====
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
render: render,
|
render: render,
|
||||||
paginate: render,
|
|
||||||
generateImages: generateImages,
|
generateImages: generateImages,
|
||||||
saveImages: saveImages,
|
saveImages: saveImages,
|
||||||
showGeneratedThumbnails: showGeneratedThumbnails,
|
showGeneratedThumbnails: showGeneratedThumbnails,
|
||||||
switchTemplate: switchTemplate,
|
|
||||||
switchSize: switchSize,
|
switchSize: switchSize,
|
||||||
switchFont: switchFont,
|
|
||||||
getConfig: getConfig,
|
getConfig: getConfig,
|
||||||
getPages: getPages,
|
getPages: getPages,
|
||||||
applyAiRecommendation: applyAiRecommendation,
|
setPageAlignment: setPageAlignment
|
||||||
applyAiContent: applyAiContent,
|
|
||||||
restoreOriginalContent: restoreOriginalContent,
|
|
||||||
nextPage: nextPage,
|
|
||||||
prevPage: prevPage,
|
|
||||||
showPreviewPage: showPreviewPage,
|
|
||||||
getCurrentPageIndex: getCurrentPageIndex
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -66,6 +66,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/notin}
|
{/notin}
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-form-label">
|
||||||
|
封面文案
|
||||||
|
<p class="layui-word-aux">用于手机图片排版封面展示</p>
|
||||||
|
</div>
|
||||||
|
<div class="layui-input-block">
|
||||||
|
<textarea name="cover_text" class="layui-textarea" placeholder="用于手机图片排版封面展示(可选)">{$post->getData('cover_text')}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<div class="layui-form-label">描述</div>
|
<div class="layui-form-label">描述</div>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ X-API-Key: {api_key}</div>
|
|||||||
"type": "1",
|
"type": "1",
|
||||||
"status": 1,
|
"status": 1,
|
||||||
"source": "api",
|
"source": "api",
|
||||||
|
"cover_text": "",
|
||||||
"create_time": 1700000000,
|
"create_time": 1700000000,
|
||||||
"update_time": 1700000000,
|
"update_time": 1700000000,
|
||||||
"categorys": [...],
|
"categorys": [...],
|
||||||
@@ -241,6 +242,7 @@ X-API-Key: {api_key}</div>
|
|||||||
"content_html": "<p>HTML内容</p>",
|
"content_html": "<p>HTML内容</p>",
|
||||||
"content_type": "html",
|
"content_type": "html",
|
||||||
"desc": "摘要",
|
"desc": "摘要",
|
||||||
|
"cover_text": "封面文案内容示例",
|
||||||
"poster": "/uploads/poster.jpg",
|
"poster": "/uploads/poster.jpg",
|
||||||
"type": "1",
|
"type": "1",
|
||||||
"status": 1,
|
"status": 1,
|
||||||
@@ -267,6 +269,7 @@ X-API-Key: {api_key}</div>
|
|||||||
<tr><td>content</td><td>string</td><td>否</td><td>文章内容(Markdown)</td></tr>
|
<tr><td>content</td><td>string</td><td>否</td><td>文章内容(Markdown)</td></tr>
|
||||||
<tr><td>content_html</td><td>string</td><td>否</td><td>文章内容(HTML)</td></tr>
|
<tr><td>content_html</td><td>string</td><td>否</td><td>文章内容(HTML)</td></tr>
|
||||||
<tr><td>desc</td><td>string</td><td>否</td><td>文章摘要</td></tr>
|
<tr><td>desc</td><td>string</td><td>否</td><td>文章摘要</td></tr>
|
||||||
|
<tr><td>cover_text</td><td>string</td><td>否</td><td>封面文案,用于手机图片排版封面展示</td></tr>
|
||||||
<tr><td>poster</td><td>string</td><td>否</td><td>封面图 URL</td></tr>
|
<tr><td>poster</td><td>string</td><td>否</td><td>封面图 URL</td></tr>
|
||||||
<tr><td>type</td><td>string</td><td>否</td><td>文章类型,默认 "1"</td></tr>
|
<tr><td>type</td><td>string</td><td>否</td><td>文章类型,默认 "1"</td></tr>
|
||||||
<tr><td>status</td><td>int</td><td>否</td><td>状态,默认 0(草稿)</td></tr>
|
<tr><td>status</td><td>int</td><td>否</td><td>状态,默认 0(草稿)</td></tr>
|
||||||
@@ -338,6 +341,7 @@ X-API-Key: {api_key}</div>
|
|||||||
<tr><td>content</td><td>string</td><td>否</td><td>文章内容(Markdown)</td></tr>
|
<tr><td>content</td><td>string</td><td>否</td><td>文章内容(Markdown)</td></tr>
|
||||||
<tr><td>content_html</td><td>string</td><td>否</td><td>文章内容(HTML)</td></tr>
|
<tr><td>content_html</td><td>string</td><td>否</td><td>文章内容(HTML)</td></tr>
|
||||||
<tr><td>desc</td><td>string</td><td>否</td><td>文章摘要</td></tr>
|
<tr><td>desc</td><td>string</td><td>否</td><td>文章摘要</td></tr>
|
||||||
|
<tr><td>cover_text</td><td>string</td><td>否</td><td>封面文案,用于手机图片排版封面展示</td></tr>
|
||||||
<tr><td>poster</td><td>string</td><td>否</td><td>封面图 URL</td></tr>
|
<tr><td>poster</td><td>string</td><td>否</td><td>封面图 URL</td></tr>
|
||||||
<tr><td>status</td><td>int</td><td>否</td><td>状态</td></tr>
|
<tr><td>status</td><td>int</td><td>否</td><td>状态</td></tr>
|
||||||
<tr><td>content_type</td><td>string</td><td>否</td><td>内容类型: "html"(默认) 或 "markdown"<br/>当为 "markdown" 时,系统会自动将 content 转换为 HTML 存储到 content_html 字段</td></tr>
|
<tr><td>content_type</td><td>string</td><td>否</td><td>内容类型: "html"(默认) 或 "markdown"<br/>当为 "markdown" 时,系统会自动将 content 转换为 HTML 存储到 content_html 字段</td></tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user