线路导出

master
lion 3 days ago
parent 2cc9ebeaac
commit aae326753b

@ -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('/<img\b/iu', $html);
if ($html === '' || (! $hasImage && self::htmlToPlainText($html) === '')) {
$section->addText('(暂无)', ['color' => '86909C'], ['spaceAfter' => 160]);
return;
}
foreach (preg_split('/\R/u', $text) ?: [] as $line) {
$line = trim($line);
if ($line === '') {
$parts = preg_split('/(<img\b[^>]*>)/iu', $html, -1, PREG_SPLIT_DELIM_CAPTURE) ?: [$html];
$wrote = false;
foreach ($parts as $part) {
$part = trim((string) $part);
if ($part === '') {
continue;
}
if (preg_match('/^<img\b/iu', $part)) {
self::addInlineImage($section, self::imgSrcFromTag($part), 480, 400);
$wrote = true;
continue;
}
$text = self::htmlToPlainText($part);
if ($text === '') {
continue;
}
$section->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]);
}
}

@ -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' => '<p>680元/人</p>',
'implementation_html' => '<p>2026年7月1日第1场</p>',
'implementation_html' => '<p>2026年7月1日第1场</p><p><img src="/storage/uploads/st-impl-test.png"></p>',
'season_options' => [
['value' => 'spring', 'label' => '春季'],
['value' => 'summer', 'label' => '夏季'],
@ -105,15 +111,17 @@ class StudyTourDeclarationExporterTest extends TestCase
'缫丝体验',
'680元/人',
'封面图',
'2026年7月1日第1场',
] as $needle) {
$this->assertStringContainsString($needle, $xml, "missing {$needle}");
}
$this->assertGreaterThan(0, $mediaCount, 'cover image should be embedded');
$this->assertGreaterThanOrEqual(2, $mediaCount, 'cover and implementation images should be embedded');
$phpWord = IOFactory::load($path);
$this->assertNotEmpty($phpWord->getSections());
@unlink($path);
@unlink($png);
@unlink($implPng);
}
}

Loading…
Cancel
Save