IHG/script/output.php
2026-04-28 23:41:08 +08:00

217 lines
8.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
$jsonFile = __DIR__ . '/hotels.json';
$outDir = __DIR__ . '/../dist';
$pageSize = 100;
if (!is_dir($outDir)) {
mkdir($outDir, 0777, true);
}
function e(mixed $v): string {
return htmlspecialchars((string)($v ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function getv(array $arr, string $path, mixed $default = null): mixed {
$cur = $arr;
foreach (explode('.', $path) as $key) {
if (!is_array($cur) || !array_key_exists($key, $cur)) return $default;
$cur = $cur[$key];
}
return $cur;
}
function pickImage(array $hotel): string {
$photos = getv($hotel, 'media.primaryPhotos.allPhotos', []);
foreach ($photos as $p) {
foreach (($p['formats'] ?? []) as $f) {
if (($f['aspectWidth'] ?? '') === '4' && ($f['aspectHeight'] ?? '') === '3') {
return $f['url'] ?? '';
}
}
}
return getv($hotel, 'profile.primaryImageUrl.originalUrl', '');
}
function normalizeHotel(array $h): array {
return [
'code' => $h['hotelCode'] ?? '',
'name' => getv($h, 'profile.name', ''),
'brand' => getv($h, 'brandInfo.brandName', ''),
'brandCode' => getv($h, 'brandInfo.brandCode', ''),
'phone' => getv($h, 'callCenter.phoneNumber', ''),
'address' => trim(implode(' ', array_filter([
getv($h, 'address.street1', ''),
getv($h, 'address.street3', ''),
getv($h, 'address.city', ''),
getv($h, 'address.state.name', ''),
getv($h, 'address.zip', ''),
]))),
'km' => getv($h, 'distanceFrom.kilometers', ''),
'miles' => getv($h, 'distanceFrom.miles', ''),
'image' => pickImage($h),
'website' => getv($h, 'profile.independentNonIHGWebsiteURL', ''),
'facilities' => array_values(array_filter(array_map(
fn($x) => $x['name'] ?? '',
$h['facilities'] ?? []
))),
'tax' => getv($h, 'tax.taxAndFeeDetail', ''),
'price' => getv($h, 'rate.price', getv($h, 'price.amount', null)),
'currency' => getv($h, 'rate.currency', getv($h, 'price.currency', 'CNY')),
];
}
function pageName(string $type, int $page): string {
if ($type === 'cards') return $page === 1 ? 'index.html' : "page-$page.html";
return $page === 1 ? 'table.html' : "table-$page.html";
}
function renderLayout(string $body, string $title): string {
return '<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>' . e($title) . '</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body{margin:0;background:#f4f4f4;font-family:Arial,"Microsoft YaHei",sans-serif;color:#111}
.wrap{max-width:1460px;margin:0 auto;padding:20px}
.top{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}
.top a{margin-left:10px;color:#0b5c7a;text-decoration:none}
.card{display:grid;grid-template-columns:438px 1fr 210px;background:#fff;border-radius:18px;margin-bottom:24px;overflow:hidden}
.img{width:438px;height:292px;object-fit:cover;background:#ddd}
.info{padding:28px}
.brand{display:inline-block;background:#0b684d;color:#fff;border-radius:4px;padding:6px 9px;margin-right:10px;font-weight:bold}
h2{display:inline;font-size:24px}
.addr{color:#444;margin:8px 0 6px;font-size:17px}
.dist{color:#555;margin-bottom:26px}
.features{display:grid;grid-template-columns:repeat(2,minmax(180px,1fr));gap:13px 40px;font-size:18px;color:#333}
.features span:before{content:"◆";font-size:12px;color:#666;margin-right:9px}
.side{padding:36px 24px;text-align:right;display:flex;flex-direction:column;justify-content:center}
.price{font-size:32px;font-weight:bold}
.btn{background:#c9310c;color:#fff;border-radius:7px;padding:18px 24px;text-decoration:none;font-weight:bold;display:inline-block;margin-top:28px}
.tax{color:#555;margin-top:12px}
table{width:100%;border-collapse:collapse;background:#fff}
th,td{border:1px solid #ddd;padding:10px;vertical-align:top}
th{background:#eee}
.thumb{width:110px;height:78px;object-fit:cover}
.pager{margin:22px 0;text-align:center}
.pager a{margin:0 8px;color:#0b5c7a}
@media(max-width:900px){
.card{grid-template-columns:1fr}
.img{width:100%;height:240px}
.side{text-align:left}
}
</style>
</head>
<body>
<div class="wrap">' . $body . '</div>
</body>
</html>';
}
function renderPager(string $type, int $page, int $totalPages): string {
$html = '<div class="pager">';
if ($page > 1) {
$html .= '<a href="' . e(pageName($type, $page - 1)) . '">上一页</a>';
}
$html .= ' 第 ' . $page . ' / ' . $totalPages . ' 页 ';
if ($page < $totalPages) {
$html .= '<a href="' . e(pageName($type, $page + 1)) . '">下一页</a>';
}
$html .= '</div>';
return $html;
}
function renderCardsPage(array $hotels, int $page, int $totalPages, int $total): string {
$body = '<div class="top"><div>找到 ' . $total . ' 家酒店</div><div><a href="' . e(pageName('table', $page)) . '">表格模式</a></div></div>';
foreach ($hotels as $h) {
$features = '';
foreach (array_slice($h['facilities'], 0, 8) as $f) {
$features .= '<span>' . e($f) . '</span>';
}
$price = $h['price'] !== null
? '<div class="price">' . e(number_format((float)$h['price'])) . ' <small>' . e($h['currency']) . '</small></div><div>每晚</div>'
: '<div class="price">暂无价格</div>';
$body .= '
<div class="card">
<img class="img" src="' . e($h['image']) . '" loading="lazy">
<div class="info">
<div><span class="brand">' . e($h['brandCode']) . '</span><h2>' . e($h['brand'] . ' ' . $h['name']) . '</h2></div>
<div class="addr">' . e($h['address']) . ' ' . e($h['phone']) . '</div>
<div class="dist">📍 ' . e($h['km']) . ' 公里,' . e($h['miles']) . ' 英里 距离市中心</div>
<div class="features">' . $features . '</div>
</div>
<div class="side">
<div>⚡ Only a few left from</div>
' . $price . '
<div class="tax">' . e($h['tax']) . '</div>
<a class="btn" href="' . e($h['website'] ?: '#') . '">选择酒店</a>
</div>
</div>';
}
$body .= renderPager('cards', $page, $totalPages);
return renderLayout($body, '酒店列表');
}
function renderTablePage(array $hotels, int $page, int $totalPages, int $total): string {
$body = '<div class="top"><div>找到 ' . $total . ' 家酒店</div><div><a href="' . e(pageName('cards', $page)) . '">卡片模式</a></div></div>';
$body .= '<table><thead><tr>
<th>图片</th><th>酒店</th><th>品牌</th><th>地址</th><th>距离</th><th>电话</th><th>设施</th><th>价格</th><th>税费</th>
</tr></thead><tbody>';
foreach ($hotels as $h) {
$price = $h['price'] !== null
? number_format((float)$h['price']) . ' ' . $h['currency']
: '暂无';
$body .= '<tr>
<td><img class="thumb" src="' . e($h['image']) . '" loading="lazy"></td>
<td>' . e($h['name']) . '<br><small>' . e($h['code']) . '</small></td>
<td>' . e($h['brand']) . '</td>
<td>' . e($h['address']) . '</td>
<td>' . e($h['km']) . ' 公里 / ' . e($h['miles']) . ' 英里</td>
<td>' . e($h['phone']) . '</td>
<td>' . e(implode('、', array_slice($h['facilities'], 0, 8))) . '</td>
<td>' . e($price) . '</td>
<td>' . e($h['tax']) . '</td>
</tr>';
}
$body .= '</tbody></table>';
$body .= renderPager('table', $page, $totalPages);
return renderLayout($body, '酒店表格');
}
$raw = file_get_contents($jsonFile);
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
$hotelsRaw = getv($data, 'data.getHotels.hotelInfo', []);
$hotels = array_map('normalizeHotel', $hotelsRaw);
$total = count($hotels);
$totalPages = max(1, (int)ceil($total / $pageSize));
for ($page = 1; $page <= $totalPages; $page++) {
$chunk = array_slice($hotels, ($page - 1) * $pageSize, $pageSize);
file_put_contents(
$outDir . '/' . pageName('cards', $page),
renderCardsPage($chunk, $page, $totalPages, $total)
);
file_put_contents(
$outDir . '/' . pageName('table', $page),
renderTablePage($chunk, $page, $totalPages, $total)
);
}
echo "生成完成:{$outDir}\n";
echo "卡片首页:{$outDir}/index.html\n";
echo "表格首页:{$outDir}/table.html\n";