Files
FamilyBook/templates/trips/show.php
T
laoyang 1756c6265d v24 第 19 批:拖动上传 / 返还按钮 / TripLog 补迁 / 参与人回填 / 预览翻页 / 行程按钮(用户 8 条)
用户 8 条原话要点:
  ①「为所有的图片视频附件上传处,提供拖动上传的功能」
  ②「旅程详情页面,增加返回按钮,点击后返回到旅程列表」
  ③「已有行程的出行方式,包括航班号,怎么没从 triplog 系统迁移过来?」
  ④「题图右侧有一句话显示,但点编辑进去却看不到这句话」
  ⑤「编辑行程,参与的人没有带进来,还是空的。同样的问题也出在观影编辑时」
  ⑥「预览的时候左右有箭头可以直接看下一张;md5 的文件名放在预览图下面没有意义,去掉;
     如果是文件,则在图标上显示文件名」
  ⑦「计划行程的修改/完成/未成行横着排列放行程右上角,修改黄、完成绿、未成行红;
     实际行程的编辑删除也放右上角,编辑黄、删除红」
  ⑧「选人弹框暂存区太小了看不见人;前面的复选框没用,去掉」

🔴 抓到的两个真根因(都不是"没写对",是两处看似无关的地方):
  1. **⑤ 参与人不回填 = 两个独立原因叠加**
     (a) `Trip::node()`(单条)只 `SELECT *`,**不带 members/attachs**;而
         `Trip::nodes()`(列表)是带的。编辑页走前者 → `$ppSelected=[]` 永远空。
         同一处还让编辑页的照片墙永远不显示。→ 单条也挂 members/attachs/花销。
     (b) `person_picker.js` 的 `init()` 末尾**无条件** `refresh()` → `syncChips()`
         第一句 `chips.innerHTML=''` 再从**懒建**的候选清单重建 —— 首屏清单还没建,
         一个 input 都没有 → **服务端渲染的 chips 全被清掉、补不回来**。
         症状极隐蔽:curl 看到的 HTML 里有 3 个 chip,浏览器里数出来是 0。
         → `syncChips` 加 `if (!built) return;` 守卫 + `ppSetSelected` 前先 `box.ppBuild()`。
  2. **④ 那句话 = 详情页读了两个字段**:`display:summary` + `events.note`(老字段,
     表单里早没有输入框)→ 老数据的 note 在详情页冒出来、却无从修改。
     → 详情页只渲 summary;老数据给一句"点这里补简介"的提示;
        由 `_fb_b19_backfill.php` 一次性搬进 summary。

本批新增能力:
- **拖动上传**(第 1 条):app.js 自动扫全页 `input[type=file]` 挂 DnD,**不改任何模板**
  → 全站十几处上传点一次到位;`data-pick-input` 指向的**预览图**也当落点
  (题图/头像的 input 是 sr-only,往图上拖才自然)。三个必须踩准的点:
  `dragover` 必须 preventDefault;`dt.items.add` 真写回 `input.files`;
  写回后**手动派发 `change`**(`input.files=` 不会自己触发,预览/裁剪器都听 change)。
  MutationObserver 补挂动态出现的框。
- **预览左右翻页**(第 6 条):候选 = 同页全部 `[data-prev]`(文档顺序),
  `‹ ›` 按钮 + 键盘 ←→(**只在图片上**接管方向键,视频/PDF 里方向键另有含义);
  只有一个附件时箭头隐藏(点了没反应比没有更糟);右上角带"第几张/共几张"。
- **附件墙**(第 6 条):缩略图下**不再写**(哈希)文件名;文件的名字压到**图标里**;
  `att-doc-name` 两行封顶 + 省略号(长名不撑变形)。
- **行程按钮**(第 7 条):`.node-ops` 由 `column` 改 `row` + 贴顶;新增
  `.btn-warn/.btn-ok/.btn-del`(**实心**黄/绿/红)—— 与全站淡红描边的 `.btn-danger`
  刻意分开(改它会连带改掉十来处「删除」的样子)。「编辑」保留(改标题/加花销的唯一入口)。
- **选人弹框**(第 8 条):暂存区 148px 死高度 → `min-height:132px; max-height:46vh`,
  chip 头像 20→28px;多选加 `.pp-compact` 隐藏复选框(**只 `display:none`,
  input 仍在 DOM** —— 提交语义全靠它,绝不能 remove)。

上线补迁(`_tooling/_fb_b19_backfill.php`,幂等、不覆盖已有值):
- ③ TripLog 的 `travel_modes.mode_type` + `trip_number/line_name/carrier`
  → FamilyBook 的专列。`drive_own/drive_rental` 都映射成 `drive`(FamilyBook 的
  "自驾"是一个方式,"哪台车"由 vehicle_id 表达);车辆按**车牌**对上;
  顺手删掉 detail 里那段重复的「班次:…」。线上实跑 18 条、跳过 2 条。
- ④ `events.note` → `trip_details.summary`(只搬 summary 为空、note 有内容的,2 条)。

验证(本地全套 + 线上):
- e2e **960/0**(+23 条本批断言,含"成员清空后编辑页也不显示"的反向)· 升级 149/0 ·
  城市解析 32/32 · 桌面几何 428/0 · 真机 UI **27/0**(拖动/翻页/按钮几何与配色)·
  真机选人 chip **2/0**。
- **反向验证 4/4**:①`Trip::node` 去掉 members ②`syncChips` 去掉守卫
  ③详情页把 note 渲回来 ④附件墙把文件名写回去 —— 四种坏法全部被抓到,
  且还原后 e2e 复绿 960/0。
- 线上:部署成功(双版本 l 通过 + 19 条站点自检全过);补迁后 18 段行程有出行方式、
  7 段有车次、10 段挂车、2 个旅程有简介;线上 `Trip::node()` 实调
  members/attachs 都回来了(部署前是 0)。
2026-10-05 22:56:49 +08:00

418 lines
23 KiB
PHP
Raw 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
/**
* 旅程详情(v24 第 16 批重做)。
*
* ⚠️ 术语(用户第 9 条:「活动都改名吧,改成旅程,新建旅程,旅程包含行程,
* 这个名字就固定下来」):
* · **旅程** = 这一页(原来叫"活动")
* · **行程** = 里面的一段一段(原来叫"旅程")
*
* 页面结构(用户第 1/2/3/6 条):
* ① 页头:标题 + 右上角按钮(计划行程 / 实际行程 / 设置 / 编辑 / 删除)
* —— 标题旁边**不放时间**(第 6 条:时间移到内容里、题图右侧)
* ② 封面行:左 16:9 题图 + 右侧信息区
* 信息区 = 参与人 / 城市数 / 天数 / 行程段数 / 总花销 + 简介那一段话
* ③ 行程分组:**只显示非空的分组**(第 4 条:"只要是没有就是 0 条,
* 那个类型就不显示了,现在显示的是 0 条,这样没意义")
* ④ **没有照片上传板块**(第 3 条:图片视频都针对一次具体行程)
*
* @param array $t 旅程(events + trip_details + 派生值)
* @param array $nodes 全部行程(已带上成员/附件/花销)
* @param array $sm 汇总信息(Trip::summary())
*/
$canWrite = \App\Auth::canWrite();
$fid = (int)\App\Auth::familyId();
$sm = $sm ?? [];
$people = $sm['people'] ?? [];
$cities = $sm['cities'] ?? [];
$days = (int)($sm['days'] ?? 0);
$nodeN = (int)($sm['nodes'] ?? 0);
$cents = (int)($sm['cents'] ?? 0);
// 三组:计划 / 实际 / 未成行
$bysStage = ['plan' => [], 'actual' => [], 'skipped' => []];
foreach ((array)($nodes ?? []) as $n) {
$sg = (string)($n['stage'] ?? 'plan');
if (!isset($bysStage[$sg])) { $sg = 'plan'; }
$bysStage[$sg][] = $n;
}
$groupMeta = [
'plan' => ['计划的行程', '还没走 —— 可以修正 / 完成 / 标记未成行'],
'actual' => ['实际行程', '已走过 —— 也出现在日历与首页「往年今日」'],
'skipped' => ['未成行', '这次没去成;不上日程,但在这里留着可查'],
];
?>
<div class="page-head">
<h1><?= e((string)$t['title']) ?></h1>
<?php /* ⚠️ 用户第 6 条:标题旁边**不放**时间。时间在下面题图右侧的信息区里。 */ ?>
<div class="actions">
<?php /* 用户第 19 批 第 2 条:「旅程详情页面,增加返回按钮,点击后返回到旅程列表。」
按全站规矩放在动作区最左(右起是:设置 → 删除 → 动作),
文案与观影页统一成「返回旅程」。 */ ?>
<a class="btn btn-ghost" href="<?= e(url('trips')) ?>">返回旅程</a>
<?php if ($canWrite): ?>
<?php /* 用户第 6 条:添加计划行程 / 实际行程的按钮都放**右上角**,
点击打开**新页面**(第 5 条:不再内联,内联会把页面撑破)。 */ ?>
<a class="btn btn-ghost" href="<?= e(url('trips/node_new', ['id' => (int)$t['id'], 'stage' => 'plan'])) ?>">
+ 计划行程
</a>
<a class="btn" href="<?= e(url('trips/node_new', ['id' => (int)$t['id'], 'stage' => 'actual'])) ?>">
+ 实际行程
</a>
<a class="btn btn-ghost" href="<?= e(url('trips/edit', ['id' => (int)$t['id']])) ?>">编辑</a>
<?php /* 用户第 8 条:右上角增加一个**设置**按钮(车辆管理 / 出行方式 / 节点类型)。
按全站规矩:设置按钮只有图标(三条横线)、无文字、贴最右。 */ ?>
<?php $trSetCurrent = ''; require __DIR__ . '/_settings_menu.php'; ?>
<form method="post" action="<?= e(url('trips/destroy')) ?>" class="res-inline"
onsubmit="return confirm('确定删除「<?= e((string)$t['title']) ?>」吗?里面的行程、照片会一起删掉,不可恢复。')">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
<button class="btn btn-ghost btn-icon" type="submit" title="删除这个旅程" aria-label="删除">🗑</button>
</form>
<?php else: ?>
<span class="link-quiet">只读账号</span>
<?php endif; ?>
</div>
</div>
<?php /* ==================== 封面行:题图 + 信息区 ====================
⚠️ 第 1 条:「题图应该按照 16:9 显示,因为添加的时候,是按照这个比例裁剪的,
现在的显示比例不对」—— 之前 CSS 写的是 4:3,把 16:9 的图裁掉两边。
16:9 这个数字与 ImageSlot 里 cover 槽位的比例**必须一致**,
部署断言里有一条专门比对它。
⚠️ 第 17 批 第 4 条:「旅程详情页面,图片右侧显示的是**聚合的城市**,
而不是终点地址。」—— 城市来自 `Trip::cities()`(出发地 ∪ 到达地,去重),
见 summary() → cities()。 */ ?>
<div class="card card-pad trip-hero">
<div class="trip-hero-cover">
<?php if (!empty($t['cover_key'])): ?>
<img src="<?= e(media_url((string)$t['cover_key'])) ?>" alt="">
<?php else: ?>
<span class="trip-cover-empty">🧳</span>
<?php endif; ?>
</div>
<div class="trip-hero-body">
<?php /* ---- 时间与城市:从行程算出来的(第 15 批第 2 条);
第 6 条要求放在这里(题图右侧)而不是标题旁 ---- */ ?>
<div class="trip-when">
<span class="trip-when-date"><?= e(\App\Trip::activitySpan($t['starts_at'] ?? null, $t['ends_at'] ?? null)) ?></span>
<?php if ($cities): ?>
<span class="trip-when-cities">
<?php foreach ($cities as $i => $c): ?>
<?= $i > 0 ? '<span class="city-sep">·</span>' : '' ?><span class="city-chip"><?= e((string)$c) ?></span>
<?php endforeach; ?>
</span>
<?php endif; ?>
</div>
<?php /* ---- 五项统计(用户第 2 条):参与人 / 城市 / 天数 / 段数 / 花销 ---- */ ?>
<div class="trip-stats">
<div class="ts-item">
<span class="ts-n"><?= count($people) ?></span>
<span class="ts-l">参与人</span>
</div>
<div class="ts-item">
<span class="ts-n"><?= count($cities) ?></span>
<span class="ts-l">个城市</span>
</div>
<div class="ts-item">
<span class="ts-n"><?= $days > 0 ? $days : '—' ?></span>
<span class="ts-l">天</span>
</div>
<div class="ts-item">
<span class="ts-n"><?= $nodeN ?></span>
<span class="ts-l">段行程</span>
</div>
<div class="ts-item">
<span class="ts-n"><?= e(\App\Expense::money($cents)) ?></span>
<span class="ts-l">总花销</span>
</div>
</div>
<?php /* 参与人是谁:用户点名要("显示的是参与人都有谁") */ ?>
<?php if ($people): ?>
<div class="trip-people-block">
<span class="small muted">参与的人(各段行程的合集)</span>
<div class="viewers-list">
<?php foreach ($people as $a): ?>
<?= person_chip($a, url('people/show', ['id' => (int)$a['id']])) ?>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php /* ---- 简介(用户第 2 条明确"简介那一段话保留")。
🔴 v24 第 19 批 第 4 条修的是这里:用户报「详情页题图右侧有一句话显示,
但点击编辑进去,却看不到这句话」。原因就是**这句话读的字段和表单存的
字段不是同一个**:
· 旅程表单存的是 `trip_details.summary`(编辑页那个「一段简介」框);
· 而这里先读 `summary`,再读 `events.note`(**老字段**,表单里早就没有
这个输入框了),两句都用 .trip-sum 的画法渲染 ——
于是老数据里的 `note` 会在详情页冒出来,编辑页却无从修改/删除。
现在只渲染 `summary` 一个来源:**页面显示的每一句话,编辑页都能改**。
`note` 不再出现在详情页(数据仍留在库里,不删)。
老数据里"只填过备注、没填过简介"的那批由
`_tooling/_fb_b19_backfill.php` 一次性搬进 summary。 */ ?>
<?php if (!empty($t['summary'])): ?>
<p class="info-text trip-sum"><?= nl2br(e((string)$t['summary'])) ?></p>
<?php endif; ?>
<?php /* 老数据兜底(v24 第 19 批 第 4 条):只填过「备注」、没填过「简介」的旅程,
简介这里就是空的。**不能把 note 直接当简介显示** —— 那正是用户报的
"详情页有、编辑页看不到"。但也不能让用户觉得"我的字丢了",
所以给一句能点进去补的提示;真正的内容会被 `_fb_b19_backfill.php`
一次性搬进 summary(搬完这条提示自然消失)。 */ ?>
<?php if (empty($t['summary']) && !empty($t['note'])): ?>
<?php if ($canWrite): ?>
<p class="trip-sum-legacy">
这段旅程的旧「备注」还没搬成简介 ——
<a href="<?= e(url('trips/edit', ['id' => (int)$t['id']])) ?>">点这里补一段简介</a>。
</p>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<?php /* ==================== 行程分组 ====================
用户第 4 条:「只要是没有就是 0 条,那个类型就不显示了,现在显示的是 0 条,这样没意义。」
⚠️ 所以这里**跳过空分组**。但要给"一段都还没有"的情况一句引导 ——
否则新建的旅程页面上会一片空白,人不知道该干什么。 */ ?>
<?php
$shown = 0;
foreach ($bysStage as $lst) { $shown += count($lst); }
?>
<?php if ($shown === 0): ?>
<div class="card card-pad empty">
<b>还没有行程</b>
<?php if ($canWrite): ?>
点右上角「+ 计划行程」把打算走的地方排进来,或者「+ 实际行程」直接记已经走过的。
参与人、城市、天数、花销都会从行程里自动汇总到这里。
<?php else: ?>
家人还没有记录行程。
<?php endif; ?>
</div>
<?php else: ?>
<?php foreach (['plan', 'actual', '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): ?>
<?php
$nid = (int)$n['id'];
// ⚠️ 第 17 批 第 4 条末:「行程中,时间明明具体到了时分秒,怎么不显示?」
// → 行程行改用 nodeSpan()(精确到时分),**不再**用只到日期的 formatSpan()。
// formatSpan 那个精度是给"旅程"(容器)用的。
$span = \App\Trip::nodeSpan($n['starts_at'] ?? null, $n['ends_at'] ?? null);
$mode = (string)($n['travel_mode'] ?? '');
$spent = (int)($n['spent_cents'] ?? 0);
?>
<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)) ?>
</span>
<div class="node-body">
<div class="node-head">
<b><?= e((string)$n['title']) ?></b>
<span class="node-badge nb-<?= e($sg) ?>"><?= e(\App\Trip::typeWord((string)$n['node_type'], $fid)) ?></span>
<?php if ((int)($n['revision_n'] ?? 0) > 0): ?>
<span class="rev-badge" title="已修正过多次">已修正 <?= (int)$n['revision_n'] ?> 次</span>
<?php endif; ?>
<?php if ($mode !== ''): ?>
<span class="node-mode"><?= e(\App\Trip::modeIcon($mode)) ?> <?= e(\App\Trip::modeWord($mode)) ?></span>
<?php endif; ?>
</div>
<?php if ($span !== '日期待补'): ?>
<div class="node-time">
<?= e($span) ?>
<?php if ($sg === 'actual'): ?><span class="actual-tag">实际</span><?php endif; ?>
</div>
<?php endif; ?>
<?php /* 交通细节:**只保留**「线路名 / 车次航班号 / 承运方」。
⚠️ 第 17 批 第 3 条明确「座位什么的不记录了」→
座位 / 车厢、登机口 / 站台**不再出现在这里**
(数据库那两列还留着老数据,只是不再展示)。 */ ?>
<?php
$traffic = [];
if (!empty($n['line_name'])) { $traffic[] = (string)$n['line_name']; }
if (!empty($n['trip_no'])) { $traffic[] = (string)$n['trip_no']; }
if (!empty($n['carrier'])) { $traffic[] = (string)$n['carrier']; }
?>
<?php if ($traffic): ?>
<div class="node-traffic">
<?php foreach ($traffic as $tf): ?><span class="ntag"><?= e($tf) ?></span><?php endforeach; ?>
</div>
<?php endif; ?>
<?php /* 出发地 → 到达地(第 17 批 第 3 条要记详细地址;
这里显示地点名,详细地址放 title 里悬停可见 + 带导航链接)。 */ ?>
<?php if (!empty($n['from_place']) || !empty($n['to_place'])): ?>
<div class="node-route-line">
<?php
$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 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'] ?? '—')) ?>
<?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'] ?? '—')) ?>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($n['detail'])): ?>
<p class="node-detail"><?= nl2br(e((string)$n['detail'])) ?></p>
<?php endif; ?>
<?php if ($spent !== 0): ?>
<div class="node-spent">💰 这一段花了 <?= e(\App\Expense::money($spent)) ?></div>
<?php endif; ?>
<?php /* ⚠️ 第 17 批 第 3 条:「给参与人标注角色这个功能去掉,不要了。」
所以这里**不再渲染** role_tag(数据库里老数据的角色还在,只是不显示)。 */ ?>
<?php if (!empty($n['members'])): ?>
<div class="trip-people">
<?php foreach ($n['members'] as $mm): ?>
<?= person_chip($mm) ?>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (!empty($n['attachs'])): ?>
<?php
$attRows = $n['attachs'];
$attCanEdit = false;
$attDelUrl = '';
require __DIR__ . '/../_attachments.php';
?>
<?php endif; ?>
<?php /* ---- 修正记录对比(第 13 批) ---- */ ?>
<?php if (!empty($n['revisions'])): ?>
<details class="rev-hist">
<summary class="link-quiet">看修正记录(<?= count($n['revisions']) ?> 条)</summary>
<table class="rev-table">
<thead>
<tr><th>#</th><th>时间</th><th>地点</th><th>说明</th></tr>
</thead>
<tbody>
<?php foreach ($n['revisions'] as $rv): ?>
<tr>
<td><?= (int)$rv['seq'] === 1 ? '原计划' : '第 ' . ((int)$rv['seq'] - 1) . ' 次修正' ?></td>
<td class="small"><?= e(\App\Trip::nodeSpan($rv['starts_at'] ?? null, $rv['ends_at'] ?? null)) ?></td>
<td><?= e((string)($rv['place'] ?? '')) ?></td>
<td class="small"><?= e((string)($rv['detail'] ?? '')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</details>
<?php endif; ?>
</div>
<?php if ($canWrite): ?>
<?php /* ==================== 动作按钮(v24 第 19 批 用户第 7 条)====================
用户原话:
· 「计划行程的三个按钮,修改、完成、未成行,横着排列,放在行程右上角,
修改是黄色背景,完成绿色,未成行红色。」
· 「实际行程的编辑和删除按钮,也放在行程的右上角,编辑黄色,删除红色。」
⚠️ 于是配色按**颜色**来(不再是"主/次"那套):
· 修正 / 编辑 → `.btn-warn`(黄)
· 完成 → `.btn-ok`(绿)
· 未成行 / 删除 → `.btn-del`(红)
⚠️ 用户说的"修改"就是这里的「修正」(第 13 批定的名字)——
我们**保留产品名**不改,但把它染成用户要的黄色。
⚠️ 「编辑」用户没提,但它不能丢:改标题/时间/类型/出行方式与**加花销**
都只走它(花销面板在编辑页里)。按用户给的颜色语义染黄。
⚠️ 「标记为实际」是"修正过之后才出现"的收尾动作,也按黄色(同属"改")。
⚠️ 删除按钮:用户明确要求**红色**。这里用 `.btn-del`(实心红),
与全站 `.btn-danger`(淡红描边)刻意分开 —— 改 `.btn-danger`
会连带改掉十来处「删除」按钮的样子。
⚠️ 计划行的删除入口**保留**:`skipNode`(未成行)是主路径,
但"记错了、这段根本不该存在"时仍需要真删(hint 里也这么写)。 */ ?>
<div class="node-ops">
<?php /* 用户第 5 条:**编辑行程打开新页面**(内联的编辑面板会把页面撑破) */ ?>
<a class="btn btn-warn btn-sm"
href="<?= e(url('trips/node_edit', ['id' => $nid])) ?>">编辑</a>
<?php if ($sg === 'plan'): ?>
<details class="rev-open">
<summary class="btn btn-warn btn-sm">修正</summary>
<div class="rev-panel">
<p class="rev-tip">
记录实际执行情况。这仍与上面那条计划是<b>同一条记录</b>,
原来的计划数据会保留在「修正记录」里,方便日后对比。
点完这里<b>这段行程仍是计划</b>;确认无误后再点「完成」。
</p>
<?php
$rvNode = $n; $rvTripId = (int)$t['id'];
$rvPeople = $people ?? [];
$rvMembers = array_map('intval', array_column((array)($n['members'] ?? []), 'id'));
require __DIR__ . '/_revise_form.php';
?>
</div>
</details>
<form method="post" action="<?= e(url('trips/node_done')) ?>" class="res-inline"
onsubmit="return confirm('实际执行与计划完全相同,直接转为实际?')">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= $nid ?>">
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
<button class="btn btn-ok btn-sm" type="submit">完成</button>
</form>
<form method="post" action="<?= e(url('trips/node_skip')) ?>" class="res-inline"
onsubmit="return confirm('标记为「未成行」?它不会再出现在日程与往年今日,但在这个旅程里仍然看得到。')">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= $nid ?>">
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
<button class="btn btn-del btn-sm" type="submit">未成行</button>
</form>
<?php if ((int)($n['revision_n'] ?? 0) > 0): ?>
<form method="post" action="<?= e(url('trips/node_actual')) ?>" class="res-inline">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= $nid ?>">
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
<button class="btn btn-ok btn-sm" type="submit">标记为实际</button>
</form>
<?php endif; ?>
<?php elseif ($sg === 'skipped'): ?>
<form method="post" action="<?= e(url('trips/node_restore')) ?>" class="res-inline">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= $nid ?>">
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
<button class="btn btn-ok btn-sm" type="submit">恢复为计划</button>
</form>
<?php endif; ?>
<form method="post" action="<?= e(url('trips/node_del')) ?>" class="res-inline"
onsubmit="return confirm('彻底删除这段行程?连同它的修正记录一起消失,不可恢复。\n\n如果你只是想表达「这次没去成」,请用「未成行」。')">
<?= csrf_field() ?>
<input type="hidden" name="id" value="<?= $nid ?>">
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
<button class="btn btn-del btn-sm" type="submit">删除</button>
</form>
</div>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ol>
</div>
<?php endforeach; ?>
<?php endif; ?>