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

@@ -379,6 +379,7 @@ class Post extends Common
View::assign('post', $model_post);
View::assign('layoutContentHtml', $layoutContentHtml);
View::assign('layoutConfig', $layoutConfig);
View::assign('lastOutputId', $postOutput ? $postOutput->id : 0);
return View::fetch();
}
@@ -431,6 +432,12 @@ class Post extends Common
$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]]);
@@ -580,6 +587,60 @@ class Post extends Common
}
}
/**
* 导出查看页面
*/
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');
}
/**
* 获取输出文件列表(AJAX)
*/
@@ -607,6 +668,7 @@ class Post extends Common
'file_size' => $file->file_size,
'width' => $file->width,
'height' => $file->height,
'image_data' => $file->image_data ?: null,
];
}

View File

@@ -70,40 +70,69 @@ class PhoneImage implements PostOutputManagerInterface
*/
public function savePageImage(int $outputId, int $page, string $imageData)
{
$imageData = str_replace('data:image/jpeg;base64,', '', $imageData);
$imageData = str_replace('data:image/png;base64,', '', $imageData);
$imageData = str_replace(' ', '+', $imageData);
$decoded = base64_decode($imageData);
// 保留原始base64用于存储
$rawBase64 = str_replace('data:image/jpeg;base64,', '', $imageData);
$rawBase64 = str_replace('data:image/png;base64,', '', $rawBase64);
$rawBase64 = str_replace(' ', '+', $rawBase64);
$decoded = base64_decode($rawBase64);
if ($decoded === false) {
return false;
}
$dateDir = date('Ymd');
$relativeDir = '/upload/post_output/' . $dateDir;
$fileName = $outputId . '_' . $page . '.jpg';
$relativePath = $relativeDir . '/' . $fileName;
$fullDir = App::getRootPath() . '/public' . $relativeDir;
$fullPath = $fullDir . '/' . $fileName;
if (!is_dir($fullDir)) {
mkdir($fullDir, 0777, true);
}
file_put_contents($fullPath, $decoded);
$imageInfo = getimagesize($fullPath);
$width = $imageInfo[0] ?? 0;
$height = $imageInfo[1] ?? 0;
$imageInfo = @getimagesizefromstring($decoded);
$width = is_array($imageInfo) ? $imageInfo[0] : 0;
$height = is_array($imageInfo) ? $imageInfo[1] : 0;
$fileRecord = PostOutputFile::create([
'output_id' => $outputId,
'page' => $page,
'file_path' => $relativePath,
'file_url' => $relativePath,
'file_path' => '',
'file_url' => '',
'file_size' => strlen($decoded),
'width' => $width,
'height' => $height,
'image_data' => $rawBase64,
]);
return $fileRecord;
}
/**
* 保存长图
* @param int $outputId 输出记录ID
* @param string $imageData base64编码的长图数据
* @return PostOutputFile|false
*/
public function saveLongImage(int $outputId, string $imageData)
{
$rawBase64 = str_replace('data:image/jpeg;base64,', '', $imageData);
$rawBase64 = str_replace('data:image/png;base64,', '', $rawBase64);
$rawBase64 = str_replace(' ', '+', $rawBase64);
$decoded = base64_decode($rawBase64);
if ($decoded === false) {
return false;
}
$imageInfo = @getimagesizefromstring($decoded);
$width = is_array($imageInfo) ? $imageInfo[0] : 0;
$height = is_array($imageInfo) ? $imageInfo[1] : 0;
// 先删除该output_id下已有的长图(page=0)
PostOutputFile::where('output_id', $outputId)
->where('page', 0)
->delete();
$fileRecord = PostOutputFile::create([
'output_id' => $outputId,
'page' => 0,
'file_path' => '',
'file_url' => '',
'file_size' => strlen($decoded),
'width' => $width,
'height' => $height,
'image_data' => $rawBase64,
]);
return $fileRecord;
@@ -137,22 +166,42 @@ class PhoneImage implements PostOutputManagerInterface
$zipFileName = 'post_output_' . $outputId . '.zip';
$zipPath = $tempDir . '/' . $zipFileName;
$tempFiles = [];
$zip = new \ZipArchive();
if ($zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
throw new \Exception('无法创建ZIP文件');
}
try {
$zip = new \ZipArchive();
if ($zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
throw new \Exception('无法创建ZIP文件');
}
foreach ($files as $file) {
$filePath = App::getRootPath() . '/public' . $file->file_path;
if (file_exists($filePath)) {
foreach ($files as $file) {
$pageName = str_pad((string) $file->page, 2, '0', STR_PAD_LEFT) . '.jpg';
$zip->addFile($filePath, $pageName);
if (!empty($file->image_data)) {
$decoded = base64_decode($file->image_data);
if ($decoded !== false) {
$tempFilePath = $tempDir . '/page_' . $file->page . '_' . $outputId . '.jpg';
file_put_contents($tempFilePath, $decoded);
$tempFiles[] = $tempFilePath;
$zip->addFile($tempFilePath, $pageName);
}
} else {
$filePath = App::getRootPath() . '/public' . $file->file_path;
if (file_exists($filePath)) {
$zip->addFile($filePath, $pageName);
}
}
}
$zip->close();
} finally {
foreach ($tempFiles as $tempFile) {
if (file_exists($tempFile)) {
@unlink($tempFile);
}
}
}
$zip->close();
return $zipPath;
}