217 lines
8.2 KiB
PHP
217 lines
8.2 KiB
PHP
<?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"; |