diff --git a/app/Support/StudyTourDeclarationExporter.php b/app/Support/StudyTourDeclarationExporter.php
index 462992e..3bf427f 100644
--- a/app/Support/StudyTourDeclarationExporter.php
+++ b/app/Support/StudyTourDeclarationExporter.php
@@ -264,6 +264,11 @@ class StudyTourDeclarationExporter
$full = storage_path('app/public/'.$rel);
$real = realpath($full);
$root = realpath(storage_path('app/public'));
+ if ($real === false || $root === false || ! is_file($real)) {
+ $full = public_path('storage/'.$rel);
+ $real = realpath($full);
+ $root = realpath(public_path('storage'));
+ }
if ($real === false || $root === false || ! is_file($real)) {
return null;
}
@@ -393,7 +398,7 @@ class StudyTourDeclarationExporter
return;
}
- $size = self::coverImageSize($path);
+ $size = self::fitImageSize($path, 360, 240);
try {
$section->addImage($path, [
'width' => $size['width'],
@@ -409,15 +414,13 @@ class StudyTourDeclarationExporter
/**
* @return array{width: int, height: int}
*/
- private static function coverImageSize(string $path): array
+ private static function fitImageSize(string $path, int $maxW, int $maxH): array
{
- $maxW = 360;
- $maxH = 240;
$info = @getimagesize($path);
$w = is_array($info) ? (int) ($info[0] ?? 0) : 0;
$h = is_array($info) ? (int) ($info[1] ?? 0) : 0;
if ($w <= 0 || $h <= 0) {
- return ['width' => $maxW, 'height' => 180];
+ return ['width' => $maxW, 'height' => (int) max(1, round($maxH * 0.75))];
}
$scale = min($maxW / $w, $maxH / $h, 1);
@@ -429,18 +432,77 @@ class StudyTourDeclarationExporter
private static function addHtmlAsParagraphs($section, string $html): void
{
- $text = self::htmlToPlainText($html);
- if ($text === '') {
+ $html = trim($html);
+ $hasImage = (bool) preg_match('/addText('(暂无)', ['color' => '86909C'], ['spaceAfter' => 160]);
return;
}
- foreach (preg_split('/\R/u', $text) ?: [] as $line) {
- $line = trim($line);
- if ($line === '') {
+
+ $parts = preg_split('/(
]*>)/iu', $html, -1, PREG_SPLIT_DELIM_CAPTURE) ?: [$html];
+ $wrote = false;
+ foreach ($parts as $part) {
+ $part = trim((string) $part);
+ if ($part === '') {
+ continue;
+ }
+ if (preg_match('/^
addText($line, [], ['spaceAfter' => 80]);
+ foreach (preg_split('/\R/u', $text) ?: [] as $line) {
+ $line = trim($line);
+ if ($line === '') {
+ continue;
+ }
+ $section->addText($line, [], ['spaceAfter' => 80]);
+ $wrote = true;
+ }
+ }
+
+ if (! $wrote) {
+ $section->addText('(暂无)', ['color' => '86909C'], ['spaceAfter' => 160]);
+ }
+ }
+
+ private static function imgSrcFromTag(string $imgTag): ?string
+ {
+ if (! preg_match('/\bsrc\s*=\s*(?:(["\'])(.*?)\1|([^\s>]+))/iu', $imgTag, $matches)) {
+ return null;
+ }
+ $quoted = trim((string) ($matches[2] ?? ''));
+ $bare = trim((string) ($matches[3] ?? ''));
+ $src = html_entity_decode($quoted !== '' ? $quoted : $bare, ENT_QUOTES | ENT_HTML5, 'UTF-8');
+ $src = trim($src);
+
+ return $src === '' ? null : $src;
+ }
+
+ private static function addInlineImage($section, ?string $src, int $maxW, int $maxH): void
+ {
+ $path = self::resolvePublicImagePath($src);
+ if ($path === null) {
+ $section->addText('(图片无法嵌入文档)', ['color' => '86909C'], ['spaceAfter' => 80]);
+
+ return;
+ }
+
+ $size = self::fitImageSize($path, $maxW, $maxH);
+ try {
+ $section->addImage($path, [
+ 'width' => $size['width'],
+ 'height' => $size['height'],
+ 'wrappingStyle' => 'inline',
+ ]);
+ $section->addTextBreak(1);
+ } catch (\Throwable) {
+ $section->addText('(图片无法嵌入文档)', ['color' => '86909C'], ['spaceAfter' => 80]);
}
}
diff --git a/tests/Unit/StudyTourDeclarationExporterTest.php b/tests/Unit/StudyTourDeclarationExporterTest.php
index e686fb2..f287cfc 100644
--- a/tests/Unit/StudyTourDeclarationExporterTest.php
+++ b/tests/Unit/StudyTourDeclarationExporterTest.php
@@ -33,6 +33,12 @@ class StudyTourDeclarationExporterTest extends TestCase
file_put_contents($png, base64_decode(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='
));
+ $uploadDir = storage_path('app/public/uploads');
+ if (! is_dir($uploadDir)) {
+ mkdir($uploadDir, 0777, true);
+ }
+ $implPng = $uploadDir.'/st-impl-test.png';
+ copy($png, $implPng);
$path = StudyTourDeclarationExporter::writeDocxFile([
'name' => '太湖蚕桑研学',
@@ -60,7 +66,7 @@ class StudyTourDeclarationExporterTest extends TestCase
'content' => '了解传统缫丝工艺',
]],
'fee_html' => '
680元/人
', - 'implementation_html' => '2026年7月1日第1场
', + 'implementation_html' => '2026年7月1日第1场
