行程:#47~#51 打包 —— 双轨状态、动态类型、动作按钮、去提示 (#51)

#47 编辑浮层居中、类型换行、去掉手动状态选择、按钮改胶囊
#48 计划/实际双轨:完成即转实际行程,取消≠删除,记真实开始/结束时间
#50 类型点击无反应(@Builder 值传参不建立依赖)
#51 节点类型动态加载(服务端9种,原端上硬编码5种)+ 地址结构化回调

API 新增:GET /trips/<id>/node-types、PATCH /trips/<id>/nodes/action
DB 新增:trip_nodes.actual_started_at / actual_ended_at
This commit is contained in:
2026-11-05 12:40:57 +08:00
parent a1140437a1
commit f58a128816
9 changed files with 1361 additions and 46 deletions
+84 -12
View File
@@ -36,10 +36,14 @@ foreach ((array)($nodes ?? []) as $n) {
if (!isset($bysStage[$sg])) { $sg = 'plan'; }
$bysStage[$sg][] = $n;
}
/* 🔴 #48(2026-11-05):**提示语删掉**(用户:「已走过 —— 也出现在日历与首页
『往年今日』,这句话删除」)。不只删这一句 —— 三个分组的提示语一律去掉:
组头只留名称和条数,界面更干净。
⚠️ 保留数组结构(只用 [0] 名称),改动面最小。 */
$groupMeta = [
'plan' => ['计划的行程', '还没走 —— 可以修正 / 完成 / 标记未成行'],
'actual' => ['实际行程', '已走过 —— 也出现在日历与首页「往年今日」'],
'skipped' => ['未成行', '这次没去成;不上日程,但在这里留着可查'],
'plan' => ['计划的行程'],
'actual' => ['实际行程'],
'skipped' => ['未成行'],
];
?>
<div class="page-head">
@@ -228,14 +232,15 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
<?php endif; ?>
</div>
<?php else: ?>
<?php foreach (['plan', 'actual', 'skipped'] as $sg): ?>
<?php /* 🔴 #48:**实际行程放最前** —— 用户:「到最后整个旅程结束之后,
我们最终看到的是实际行程。」⇒ actual → plan → skipped(未成行沉底)。 */ ?>
<?php foreach (['actual', 'plan', 'skipped'] as $sg): ?>
<?php $list = $bysStage[$sg]; if (!$list) { continue; } /* 空分组不显示 */ ?>
<div class="card card-pad trip-stage-card stage-<?= e($sg) ?>">
<h2 class="cinema-h2">
<?= e($groupMeta[$sg][0]) ?>
<span class="group-n"><?= count($list) ?> 段</span>
</h2>
<p class="stage-hint"><?= e($groupMeta[$sg][1]) ?></p>
<ol class="node-list">
<?php foreach ($list as $n): ?>
@@ -250,8 +255,12 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
?>
<li class="node-row status-<?= e((string)$n['status']) ?> stage-<?= e($sg) ?>"
id="node-<?= $nid ?>">
<span class="node-ico" title="<?= e(\App\Trip::typeWord((string)$n['node_type'], $fid)) ?>">
<?= e(\App\Trip::typeIcon((string)$n['node_type'], $fid)) ?>
<?php /* 🔴 2026-11-05(用户:「明明选的是坐火车,网页版的图标显示的还是一辆汽车」):
左侧图标改成**出行方式优先** —— 坐火车就显 🚄,
不再一律显节点类型的 🚗。与端上 `fbNodeLeadIcon()` 同一逻辑。 */ ?>
<span class="node-ico" title="<?= e(\App\Trip::typeWord((string)$n['node_type'], $fid)
. ($mode !== '' ? ' · ' . \App\Trip::modeWord($mode) : '')) ?>">
<?= e(\App\Trip::leadIcon((string)$n['node_type'], $mode, $fid)) ?>
</span>
<div class="node-body">
<div class="node-head">
@@ -285,8 +294,22 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
<?php if ($span !== '日期待补'): ?>
<div class="node-time">
<?= e($span) ?>
<?php if ($sg === 'actual'): ?><span class="actual-tag">实际</span><?php endif; ?>
<?php /* 🔴 #48:**计划时间与实际时间同时显示**。
用户:「原来计划的时间和现在真正开始的时间都要记录。」
⇒ 计划用常规色,实际用主题色并标注「实际」。 */ ?>
<?php if ($sg === 'actual'): ?>
<span class="t-plan">计划 <?= e($span) ?></span>
<?php
$aStart = (string)($n['actual_started_at'] ?? '');
$aEnd = (string)($n['actual_ended_at'] ?? '');
if ($aStart !== ''):
$aSpan = \App\Trip::nodeSpan($aStart, $aEnd !== '' ? $aEnd : null);
?>
<span class="t-actual">实际 <?= e($aSpan) ?></span>
<?php endif; ?>
<?php else: ?>
<?= e($span) ?>
<?php endif; ?>
</div>
<?php endif; ?>
@@ -316,23 +339,72 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
$navFrom = \App\Geo::navUrl($n['from_lng'] ?? null, $n['from_lat'] ?? null, (string)($n['from_place'] ?? ''));
$navTo = \App\Geo::navUrl($n['to_lng'] ?? null, $n['to_lat'] ?? null, (string)($n['to_place'] ?? ''));
?>
<?php /* 🔴 2026-11-05(用户:「直接用个标签,然后右边一个箭头,
到北京南站就得了,不用写从哪到哪」):
两个地名都做成**胶囊标签**,箭头只作分隔。
有坐标的套 <a>(可点开导航),没坐标的套 span ——
**两种情况都用同一个类**,样式才一致(原来无链接时
是裸文本,和另一侧的胶囊对不齐,这正是"位都没对齐"的观感来源)。 */ ?>
<?php if ($navFrom !== ''): ?>
<a href="<?= e($navFrom) ?>" target="_blank" rel="noopener"
title="<?= e((string)($n['from_addr'] ?? '')) ?>"><?= e((string)$n['from_place']) ?></a>
<?php else: ?>
<?= e((string)($n['from_place'] ?? '—')) ?>
<span class="route-stop"><?= e((string)($n['from_place'] ?? '—')) ?></span>
<?php endif; ?>
<span class="arrow">→</span>
<?php if ($navTo !== ''): ?>
<a href="<?= e($navTo) ?>" target="_blank" rel="noopener"
title="<?= e((string)($n['to_addr'] ?? '')) ?>"><?= e((string)$n['to_place']) ?></a>
<?php else: ?>
<?= e((string)($n['to_place'] ?? '—')) ?>
<span class="route-stop"><?= e((string)($n['to_place'] ?? '—')) ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($n['detail'])): ?>
<!-- 🔴🔴 #48(2026-11-05)行程动作按钮组。
用户原话:
「计划行程右上角就不是两个按钮了,而是至少四个按钮:
一个是"开始",一个是"完成",一个是"编辑",一个是"删除"。
在删除前面还应该有一个按钮,这个按钮是取消,
指的就是不去了,但这个事情我并不把它删掉。」
⇒ 计划行程:开始 / 完成 / 编辑 / 取消 / 删除
⇒ 实际行程:编辑 / 删除(无状态、无开始无完成)
⇒ 未成行: 编辑 / 删除(已取消,不再转)
⚠️ 当前状态用实心 chip 表示,与端上 pillBtn(selected) 同一视觉语言。
⚠️ 按钮规则来自 Trip::nodeAvailableActions() —— 前后端共用一份。
⚠️ 这里用 HTML 注释而不是 PHP 注释:PHP 的 /* */ 注释里遇到 `?>`
会提前结束 PHP 段(词法层面 `?>` 优先于注释),导致解析错误。 -->
<?php
$stageNow = (string)($n['stage'] ?? 'plan');
$statusNow = (string)($n['status'] ?? 'planned');
$acts = \App\Trip::nodeAvailableActions($stageNow, $statusNow);
$isActual = $stageNow === 'actual';
?>
<div class="node-actions">
<?php if (!$isActual && $stageNow !== 'skipped'): ?>
<span class="node-act is-on"><?= e(\App\Trip::NODE_STATUS[$statusNow] ?? '计划中') ?></span>
<?php endif; ?>
<?php foreach ($acts as $a): ?>
<?php
$label = $a === 'start' ? '开始' : '完成';
$cls = $a === 'start' ? 'go' : 'ok';
?>
<button type="button" class="node-act node-act-<?= e($cls) ?>"
data-node-act="<?= e($a) ?>"
data-node="<?= e($nid) ?>"><?= e($label) ?></button>
<?php endforeach; ?>
<?php if (!$isActual && $stageNow !== 'skipped'): ?>
<button type="button" class="node-act node-act-warn"
data-node-act="cancel"
data-node="<?= e($nid) ?>"
title="不去了,但数据保留">取消</button>
<?php endif; ?>
</div>
<?php if (!empty($n['detail'])): ?>
<p class="node-detail"><?= nl2br(e((string)$n['detail'])) ?></p>
<?php endif; ?>