修「光秃秃地图」回归 + 详情页改标签页布局(批 12)
回归修复(用户反馈:地图上什么都不显示):
- 批 11 重构时把 {lng,lat} 对象直接传给了 AMap.Marker.position ——
AMap 只认 [lng, lat] 数组/LngLat,构造器抛 Invalid Object,
forEach 当场中断,整张地图一个覆盖物都不剩(光秃秃地图)。
- 现在 position 一律归一化为数组(中点/单点三分支),并新增
_page11.php + _map12.js 真 AMap 无头浏览器用例锁死(覆盖双坐标/
单坐标/只有地名三种节点形态 + 无 JS 报错 + 标记数量断言),
已接入 _chain3.py 全链(步骤 10b)。
标签页布局(用户需求):
- 活动详情页拆成两个标签:「🗺️ 行程地图」(默认)与「📋 计划与实际」;
- 地图页独占整页,高度加大到 clamp(480px, 72vh, 860px)(手机 62vh);
- 节点页 = 原 时间轴(计划/实际两列)+ 评论区,默认隐藏;
- 切回地图标签时重新 setFitView(隐藏期间容器尺寸无意义);
- 活动没有可上图的执行节点时不渲染标签条,退化为原单页布局。
已验证:本地全链 12 步全绿(渲染检查 341 项 + 真 AMap 浏览器验证
11 项);远端 e2e 341 项 0 失败;部署静态断言/双 PHP 渲染/内容断言
全过后提交。
This commit is contained in:
+14
-3
@@ -1253,9 +1253,19 @@ video.video-thumb.video-thumb-mini {
|
||||
.lightbox-file-dl { display: inline-block; }
|
||||
|
||||
/* ── 20. 活动页路线地图(批 10)──────────────────────────────────────────── */
|
||||
.route-map-section { margin-top: 28px; }
|
||||
/* 批 12:详情页标签页 —— 地图独占一页、尽量大;节点详情切到另一页 */
|
||||
.route-tabs { display: flex; gap: 8px; margin: 0 0 14px; }
|
||||
.route-tab {
|
||||
border: 1px solid var(--border); background: var(--surface);
|
||||
border-radius: 999px; padding: 8px 18px; font-size: 14px;
|
||||
cursor: pointer; color: var(--text-soft);
|
||||
}
|
||||
.route-tab:hover { border-color: var(--border-strong); color: var(--text); }
|
||||
.route-tab.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||||
.route-panel[hidden] { display: none; }
|
||||
.route-map-section { margin-top: 0; }
|
||||
.route-map {
|
||||
height: 380px;
|
||||
height: clamp(480px, 72vh, 860px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
@@ -1290,5 +1300,6 @@ video.video-thumb.video-thumb-mini {
|
||||
}
|
||||
.route-legend-end { color: #ef4444; font-weight: 700; }
|
||||
@media (max-width: 640px) {
|
||||
.route-map { height: 300px; }
|
||||
.route-map { height: 62vh; }
|
||||
.route-tab { padding: 7px 14px; font-size: 13px; }
|
||||
}
|
||||
|
||||
@@ -67,18 +67,87 @@ foreach (($realityNodes ?? []) as $rn) {
|
||||
}
|
||||
?>
|
||||
<?php if ($routeNodes !== []): ?>
|
||||
<section class="route-map-section">
|
||||
<h2 class="section-title" id="route-map">🗺️ 行程路线</h2>
|
||||
<div class="route-tabs" role="tablist" aria-label="行程地图与节点详情切换">
|
||||
<button type="button" class="route-tab is-active" data-panel="route-panel-map"
|
||||
aria-selected="true">🗺️ 行程地图</button>
|
||||
<button type="button" class="route-tab" data-panel="route-panel-nodes"
|
||||
aria-selected="false">📋 计划与实际</button>
|
||||
</div>
|
||||
<div class="route-panel" id="route-panel-map">
|
||||
<section class="route-map-section">
|
||||
<div class="route-map" id="route-map-canvas"
|
||||
data-route="<?= e(json_encode($routeNodes, JSON_UNESCAPED_UNICODE)) ?>"></div>
|
||||
<p class="muted route-map-hint">
|
||||
按时间顺序连出的执行节点路线(节点间虚线衔接),圆标在每段行程的中间,点它可打开对应节点。
|
||||
<span class="route-legend-end">红点</span> = 到达地。只有地名没坐标的地点会自动向高德查询定位,太偏僻的地名可能查不到。
|
||||
</p>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
<div class="route-panel" id="route-panel-nodes" hidden>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="timeline-grid">
|
||||
<section class="timeline-col">
|
||||
<h2>📋 计划 (<?= count($planNodes) ?>)</h2>
|
||||
<?php if (empty($planNodes)): ?>
|
||||
<p class="muted">暂无计划节点</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($planNodes as $n): ?>
|
||||
<?php $cardCategory = 'plan'; require __DIR__ . '/_node_card.php'; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="timeline-col">
|
||||
<h2>🎬 实际 (<?= count($realityNodes) ?>)</h2>
|
||||
<?php if (empty($realityNodes)): ?>
|
||||
<p class="muted">暂无执行节点</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($realityNodes as $n): ?>
|
||||
<?php $cardCategory = 'reality'; require __DIR__ . '/_node_card.php'; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
<?php
|
||||
$commentTargetType = \App\Comments::TARGET_ACTIVITY;
|
||||
$commentTargetId = (int)$activity['id'];
|
||||
require dirname(__DIR__) . '/comments/_block.php';
|
||||
?>
|
||||
|
||||
<?php if ($routeNodes !== []): ?>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
/* ── 标签页切换:行程地图 / 计划与实际 ──────────────────────
|
||||
地图标签默认打开(地图是主视图);切回来时重新 setFitView,
|
||||
因为容器隐藏期间尺寸没有意义。 */
|
||||
var tabs = Array.prototype.slice.call(document.querySelectorAll('.route-tab'));
|
||||
var panels = {
|
||||
'route-panel-map': document.getElementById('route-panel-map'),
|
||||
'route-panel-nodes': document.getElementById('route-panel-nodes'),
|
||||
};
|
||||
var map = null; // 下面初始化后挂进来,供切换标签时重新适配视野
|
||||
tabs.forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
tabs.forEach(function (b) {
|
||||
var on = (b === btn);
|
||||
b.classList.toggle('is-active', on);
|
||||
b.setAttribute('aria-selected', on ? 'true' : 'false');
|
||||
});
|
||||
Object.keys(panels).forEach(function (id) {
|
||||
if (panels[id]) panels[id].hidden = (id !== btn.getAttribute('data-panel'));
|
||||
});
|
||||
if (btn.getAttribute('data-panel') === 'route-panel-map' && map) {
|
||||
map.setFitView(null, false, [50, 50, 50, 50]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* ── 路线地图(AMap 2.0,layout.php 已全局加载)────────────── */
|
||||
var el = document.getElementById('route-map-canvas');
|
||||
if (!el) return;
|
||||
|
||||
@@ -95,7 +164,7 @@ foreach (($realityNodes ?? []) as $rn) {
|
||||
}
|
||||
if (!route.length) return;
|
||||
|
||||
var map = new AMap.Map(el, { viewMode: '2D', zoom: 5 });
|
||||
map = new AMap.Map(el, { viewMode: '2D', zoom: 5 });
|
||||
var overlays = []; // 当前画上去的所有覆盖物;地理编码补点后整体重画
|
||||
|
||||
/* 全量重画:先清掉旧覆盖物再按当前 route 数据画。
|
||||
@@ -113,10 +182,13 @@ foreach (($realityNodes ?? []) as $rn) {
|
||||
if (s && en) inPath.push([s.lng, s.lat], [en.lng, en.lat]);
|
||||
if (s && prevEnd) hopPath.push([prevEnd.lng, prevEnd.lat], [s.lng, s.lat]);
|
||||
|
||||
/* 圆形标记放这段行程线的中点(起终点都有才取中点),缺一头就放有的一头 */
|
||||
/* 圆形标记放这段行程线的中点(起终点都有才取中点),缺一头就放有的一头。
|
||||
⚠️ position 必须是 [lng, lat] 数组 —— 把 {lng,lat} 对象直接传给
|
||||
AMap 会抛 Invalid Object,forEach 当场中断、整张地图一个覆盖物
|
||||
都不剩(批 11 上线后踩的坑,光秃秃地图就是它)。 */
|
||||
var pos = (s && en)
|
||||
? [(s.lng + en.lng) / 2, (s.lat + en.lat) / 2]
|
||||
: (s || en);
|
||||
: (s ? [s.lng, s.lat] : (en ? [en.lng, en.lat] : null));
|
||||
if (pos) {
|
||||
var html = '<div class="route-marker" title="' + n.title.replace(/"/g, '"') + '">'
|
||||
+ (n.img
|
||||
@@ -188,34 +260,3 @@ foreach (($realityNodes ?? []) as $rn) {
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="timeline-grid">
|
||||
<section class="timeline-col">
|
||||
<h2>📋 计划 (<?= count($planNodes) ?>)</h2>
|
||||
<?php if (empty($planNodes)): ?>
|
||||
<p class="muted">暂无计划节点</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($planNodes as $n): ?>
|
||||
<?php $cardCategory = 'plan'; require __DIR__ . '/_node_card.php'; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="timeline-col">
|
||||
<h2>🎬 实际 (<?= count($realityNodes) ?>)</h2>
|
||||
<?php if (empty($realityNodes)): ?>
|
||||
<p class="muted">暂无执行节点</p>
|
||||
<?php else: ?>
|
||||
<?php foreach ($realityNodes as $n): ?>
|
||||
<?php $cardCategory = 'reality'; require __DIR__ . '/_node_card.php'; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
<?php
|
||||
$commentTargetType = \App\Comments::TARGET_ACTIVITY;
|
||||
$commentTargetId = (int)$activity['id'];
|
||||
require dirname(__DIR__) . '/comments/_block.php';
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user