Files
augushong bf32bce922 feat(typesetting): 页面品牌标识 - 数据链路+渲染函数+分页修正
- Post.php: phoneImage()传递siteName/siteLogo到模板
- phone_image.html: postData增加siteName/siteLogo字段
- phone-image.js: 新增generateBrandHeader()+preloadBrandLogo()
- phone-image.js: 3个页面生成函数调用品牌头部
- phone-image.js: contentAreaHeight扣减BRAND_HEADER_HEIGHT(36px)
- logo预转base64规避html2canvas CORS,过滤默认头像
2026-05-17 11:12:56 +08:00

581 lines
17 KiB
PHP

<?php
declare(strict_types=1);
namespace app\admin\controller;
use app\model\Nav;
use app\model\Post as ModelPost;
use app\model\PostCategory;
use app\model\PostTag;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\Table\TableExtension;
use League\CommonMark\MarkdownConverter;
use League\HTMLToMarkdown\HtmlConverter;
use think\facade\Cache;
use think\facade\View;
use think\Request;
class Post extends Common
{
/**
* 显示资源列表.
*
* @return \think\Response
*/
public function index()
{
//
$model_list = ModelPost::with(['categorys.category', 'tags.tag'])
->where('type', $this->request->param('type', 1))
->order('id desc');
$category_id = $this->request->param('category_id', 0);
if (!empty($category_id)) {
$model_list = $model_list->where('category_id', $category_id);
}
$list = $model_list->paginate([
'query' => $this->request->get(),
]);
View::assign('list', $list);
return View::fetch();
}
public function indexOutput()
{
$platform_type = $this->request->param('platform_type', '');
if (empty($platform_type)) {
$this->error('参数platform_type不能为空');
}
$platform_status = $this->request->param('platform_status', '');
$model_list = ModelPost::with(['categorys.category', 'tags.tag'])
->where('type', $this->request->param('type', 1))
->order('id desc');
if ($platform_status == 1) {
$model_list = $model_list->where('post_platform_status', 'like', "%,{$platform_type},%");
} elseif ($platform_status === '0') {
$model_list = $model_list->where('post_platform_status', 'not like', "%,{$platform_type},%");
}
$list = $model_list->paginate([
'query' => $this->request->get(),
]);
foreach ($list as $model_post) {
if (empty($model_post->post_platform_status)) {
$model_post->platform_status = 0;
} else {
if (strpos($model_post->post_platform_status, ",$platform_type,") === false) {
$model_post->platform_status = 0;
} else {
$model_post->platform_status = 1;
}
}
$model_post->platform_status_title = ModelPost::LIST_POST_PLATFORM_STATUS[$model_post->platform_status];
}
$platform_data = item_post_platform($platform_type);
View::assign('platform_data', $platform_data);
View::assign('list', $list);
View::assign('platform_type', $platform_type);
return View::fetch();
}
/**
* 显示创建资源表单页.
*
* @return \think\Response
*/
public function create()
{
//
return View::fetch();
}
/**
* 保存新建的资源.
*
* @param Request $request
* @return \think\Response
*/
public function save(Request $request)
{
//
$post_data = $request->post();
$post_data['uid'] = uniqid();
$categorys = [];
$tags = [];
if (isset($post_data['categorys'])) {
$categorys = $post_data['categorys'];
unset($post_data['categorys']);
}
if (isset($post_data['tags'])) {
$tags = $post_data['tags'];
unset($post_data['tags']);
}
$model_post = ModelPost::create($post_data);
foreach ($categorys as $category) {
PostCategory::create([
'post_id' => $model_post->id,
'category_id' => $category,
]);
}
foreach ($tags as $tag) {
PostTag::create([
'post_id' => $model_post->id,
'tag_id' => $tag,
]);
}
return $this->success('添加成功', url('index', [
'type' => $this->request->param('type'),
'category_id' => $this->request->param('category_id'),
]));
}
public function convertHtml2Markdown()
{
$content = $this->request->post('content');
$converter = new HtmlConverter();
$markdown = $converter->convert($content);
return json_message(['markdown' => $markdown]);
}
/**
* 显示指定的资源.
*
* @param int $id
* @return \think\Response
*/
public function read($id)
{
//
}
/**
* 显示编辑资源表单页.
*
* @param int $id
* @return \think\Response
*/
public function edit($id)
{
//
$model_post = ModelPost::find($id);
View::assign('post', $model_post);
return View::fetch();
}
public function editContent($id)
{
$model_post = ModelPost::find($id);
View::assign('post', $model_post);
if ($model_post->content_type === 'markdown') {
return View::fetch('edit_content_markdown');
}
return View::fetch();
}
public function output($id)
{
$model_post = ModelPost::find($id);
$list_post_platform = list_post_platform();
foreach ($list_post_platform as $model_platform) {
$platform_info = explode("\n", $model_platform->desc);
$model_platform->home_url = $platform_info[0] ?? '';
$model_platform->account = $platform_info[1] ?? '';
}
View::assign('post', $model_post);
View::assign('list_post_platform', $list_post_platform);
$list_post_category = PostCategory::with('category')->where('post_id', $id)->select();
View::assign('list_post_category', $list_post_category);
return View::fetch();
}
/**
* 保存更新的资源.
*
* @param Request $request
* @param int $id
* @return \think\Response
*/
public function update(Request $request, $id)
{
//
$post_data = $request->post();
// 禁止修改 content_type
unset($post_data['content_type']);
$model_post = ModelPost::find($id);
$categorys = [];
$tags = [];
if (isset($post_data['categorys'])) {
$categorys = $post_data['categorys'];
unset($post_data['categorys']);
$old_category_list = PostCategory::where('post_id', $id)->select();
$old_category_id_list = array_column((array) $old_category_list, 'id');
// 旧的有新的没有
foreach ($old_category_list as $model_category) {
if (!in_array($model_category->id, $categorys)) {
$model_category->delete();
}
}
// 旧的没有新的有
foreach ($categorys as $category) {
if (!in_array($category, $old_category_id_list)) {
PostCategory::create([
'post_id' => $model_post->id,
'category_id' => $category,
]);
}
}
}
if (isset($post_data['tags'])) {
$tags = $post_data['tags'];
unset($post_data['tags']);
$old_tag_list = PostTag::where('post_id', $id)->select();
$old_tag_id_list = array_column((array) $old_tag_list, 'id');
foreach ($old_tag_list as $model_tag) {
if (!in_array($model_tag->id, $tags)) {
$model_tag->delete();
}
}
foreach ($tags as $tag) {
if (!in_array($tag, $old_tag_id_list)) {
PostTag::create([
'post_id' => $model_post->id,
'tag_id' => $tag,
]);
}
}
}
// Markdown->HTML 自动转换
if ($model_post->getData('content_type') === 'markdown' && isset($post_data['content'])) {
$environment = Environment::createCommonMarkEnvironment();
$environment->addExtension(new TableExtension());
$converter = new MarkdownConverter($environment);
$post_data['content_html'] = $converter->convert($post_data['content'])->getContent();
}
$model_post->save($post_data);
Cache::delete('sitemap_last_etag');
return $this->success('保存成功', url('index', ['type' => $model_post->getData('type')]));
}
public function setPostPlatformData()
{
$type = $this->request->param('type', 1);
$value = $this->request->param('value', '');
$post_id = $this->request->param('post_id', 0);
$model_post = ModelPost::find($post_id);
if (empty($model_post)) {
return json_message('文章不存在');
}
$post_platform_data = $model_post->post_platform_data_array;
$post_platform_data[$type] = $value;
$model_post->post_platform_data = json_encode($post_platform_data);
$model_post->post_platform_status = ',' . implode(',', array_keys($post_platform_data)) . ',';
$model_post->save();
return htmx()->message('文章设置成功');
}
/**
* 删除指定资源.
*
* @param int $id
* @return \think\Response
*/
public function delete($id)
{
//
$model_post = ModelPost::find($id);
$model_post->delete();
PostCategory::where('post_id', $id)->delete();
PostTag::where('post_id', $id)->delete();
return json_message();
}
public function downloadMarkdown($id)
{
$model_post = ModelPost::find($id);
$mardkown = $model_post->content_markdown;
return download($mardkown, $model_post->title . '.md', true);
}
/**
* 手机图片排版操作页
*/
public function phoneImage($id)
{
$model_post = ModelPost::find($id);
if (empty($model_post)) {
$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)) {
$config = (array) $postOutput->config;
if (!empty($config['content_html'])) {
$layoutContentHtml = $config['content_html'];
$layoutConfig = $config;
}
}
View::assign('post', $model_post);
View::assign('layoutContentHtml', $layoutContentHtml);
View::assign('layoutConfig', $layoutConfig);
View::assign('lastOutputId', $postOutput ? $postOutput->id : 0);
View::assign('siteName', \app\common\tools\Site::name() ?? '');
View::assign('siteLogo', \app\common\tools\Site::logo() ?? '');
return View::fetch();
}
/**
* 保存输出记录
*/
public function savePostOutput()
{
$data = json_decode(file_get_contents('php://input'), true);
if (empty($data['post_id'])) {
return json(['code' => 500, 'msg' => '缺少文章ID']);
}
$post = ModelPost::find($data['post_id']);
if (empty($post)) {
return json(['code' => 500, 'msg' => '文章不存在']);
}
$config = $data['config'] ?? [];
// 确保排版内容副本被保存到 config
if (isset($data['content_html']) && !isset($config['content_html'])) {
$config['content_html'] = $data['content_html'];
}
$pages = $data['pages'] ?? [];
$admin_id = session('admin_id') ?? 0;
$phoneImage = new \app\common\tools\PhoneImage();
$output = $phoneImage->createOutput((int) $data['post_id'], $config, (int) $admin_id);
foreach ($pages as $index => $pageData) {
$phoneImage->savePageImage($output->id, $index + 1, $pageData);
}
// 保存长图(如果有)
$longImage = $data['long_image'] ?? '';
if (!empty($longImage)) {
$phoneImage->saveLongImage($output->id, $longImage);
}
$phoneImage->completeOutput($output->id, count($pages));
return json(['code' => 0, 'msg' => '保存成功', 'data' => ['output_id' => $output->id]]);
}
/**
* 仅保存配置(不生成图片)
*/
public function savePostOutputConfig()
{
$data = json_decode(file_get_contents('php://input'), true);
if (empty($data['post_id'])) {
return json(['code' => 500, 'msg' => '缺少文章ID']);
}
try {
$config = $data['config'] ?? [];
if (isset($data['content_html']) && !isset($config['content_html'])) {
$config['content_html'] = $data['content_html'];
}
$admin_id = session('admin_id') ?? 0;
$output = \app\common\tools\PhoneImage::saveConfigOnly((int) $data['post_id'], $config, (int) $admin_id);
return json(['code' => 0, 'msg' => '', 'data' => ['output_id' => $output->id]]);
} catch (\Exception $e) {
return json(['code' => 500, 'msg' => $e->getMessage()]);
}
}
/**
* 下载ZIP
*/
public function downloadPostOutputZip($id)
{
$phoneImage = new \app\common\tools\PhoneImage();
try {
$zipPath = $phoneImage->createZip((int) $id);
$output = \app\model\PostOutput::find($id);
if (empty($output)) {
throw new \Exception('输出记录不存在');
}
$post = ModelPost::find($output->post_id);
$fileName = ($post ? $post->title : 'output') . '_phone_image_' . date('Ymd') . '.zip';
// 请求结束后清理临时ZIP文件
$tempFile = $zipPath;
register_shutdown_function(function () use ($tempFile) {
if (file_exists($tempFile)) {
@unlink($tempFile);
}
});
return download($zipPath, $fileName);
} catch (\Exception $e) {
$this->error($e->getMessage());
}
}
/**
* 导出查看页面
*/
public function outputView($id)
{
$output = \app\model\PostOutput::find((int) $id);
if (empty($output)) {
$this->error('输出记录不存在');
}
$post = ModelPost::find($output->post_id);
if (empty($post)) {
$this->error('文章不存在');
}
$allFiles = \app\model\PostOutputFile::where('output_id', (int) $id)
->order('page', 'asc')
->select();
// 分离长图和分页
$longImage = null;
$pages = [];
foreach ($allFiles as $file) {
$item = [
'id' => $file->id,
'page' => $file->page,
'file_url' => $file->file_url,
'width' => $file->width,
'height' => $file->height,
'image_src' => '',
];
// 优先使用 image_data (data URI)
if (!empty($file->image_data)) {
$item['image_src'] = 'data:image/jpeg;base64,' . $file->image_data;
} elseif (!empty($file->file_url)) {
$item['image_src'] = $file->file_url;
}
if ($file->page == 0) {
$longImage = $item;
} else {
$pages[] = $item;
}
}
View::assign('output', $output);
View::assign('post', $post);
View::assign('longImage', $longImage);
View::assign('pages', $pages);
View::assign('downloadZipUrl', url('post/downloadPostOutputZip', ['id' => $id]));
return View::fetch('post/output_view');
}
/**
* AI智能排版推荐.
*/
public function aiRecommend()
{
$postId = $this->request->post('post_id', 0);
$post = ModelPost::find($postId);
if (empty($post)) {
return json(['code' => 500, 'msg' => '文章不存在']);
}
$advisor = new \app\common\tools\AiLayoutAdvisor();
$result = $advisor->analyzeAndRecommend($post);
if ($result['success']) {
return json(['code' => 0, 'data' => $result['data']]);
}
return json(['code' => 500, 'msg' => $result['msg']]);
}
/**
* AI内容优化.
*/
public function aiOptimizeContent()
{
$postId = $this->request->post('post_id', 0);
$post = ModelPost::find($postId);
if (empty($post)) {
return json(['code' => 500, 'msg' => '文章不存在']);
}
$advisor = new \app\common\tools\AiLayoutAdvisor();
$result = $advisor->optimizeContent($post);
if ($result['success']) {
return json(['code' => 0, 'data' => $result['data']]);
}
return json(['code' => 500, 'msg' => $result['msg']]);
}
}