$v) { $search[] = '{{' . $k . '}}'; $replace[] = htmlspecialchars((string)$v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } $html = str_replace($search, $replace, $html); } return $html; } /** * 把 HTML 里的相对资源路径(img/link/script href/src)改成绝对路径 * 仅处理以 ./ ../ 或非 http(s) 的相对量;base 传 public/assets/pdf 等目录 */ public static function absolutizeAssetUrls(string $html, string $baseDir): string { $baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR); // img src $html = preg_replace_callback('#(]+src=["\'])([^"\']+)(["\'])#i', function($m) use ($baseDir){ $u = $m[2]; if (preg_match('#^https?://#i', $u) || strpos($u, 'data:') === 0) return $m[0]; $abs = realpath($baseDir . DIRECTORY_SEPARATOR . ltrim($u, '/')) ?: ($baseDir . '/' . ltrim($u, '/')); return $m[1] . $abs . $m[3]; }, $html); // link href(如果你在 HTML 里写了 ,通常建议走 css_files 方式,这里也兼容) $html = preg_replace_callback('#(]+href=["\'])([^"\']+)(["\'])#i', function($m) use ($baseDir){ $u = $m[2]; if (preg_match('#^https?://#i', $u)) return $m[0]; $abs = realpath($baseDir . DIRECTORY_SEPARATOR . ltrim($u, '/')) ?: ($baseDir . '/' . ltrim($u, '/')); return $m[1] . $abs . $m[3]; }, $html); return $html; } /** * 渲染一个原生 PHP 模板为字符串 * @param string $file 模板路径(如 resource/templates/stock-order.php) * @param array $vars 将在模板中以变量名存在(如 $data) * @return string 完整 HTML */ public static function render(string $file, array $vars = []): string { if (!is_file($file)) { throw new \RuntimeException("Template not found: {$file}"); } // 将键名导出为同名变量,例如 'data' => [...] 变为 $data extract($vars, EXTR_OVERWRITE); // 开启缓冲,将 include 的输出抓取为字符串 ob_start(); ob_implicit_flush(false); try { /** @noinspection PhpIncludeInspection */ include $file; } catch (\Throwable $e) { // 清理缓冲并抛出更清晰的异常 ob_end_clean(); throw $e; } return ob_get_clean(); } }