v24 第 16 批:旅程 / 行程两级(术语固定 · 题图16:9 · 五项统计 · 独立行程页 · 设置三页)
用户十条,逐条落地:
1. 「题图应该按照 16:9 显示,因为添加的时候是按这个比例裁剪的,现在的显示比例不对。」
→ 详情页题图容器从 4:3 改成 16:9,与 ImageSlot 的 cover 槽位同一个数字
(部署断言比对这两处,避免哪天改一处漏一处)。
2. 「题图的右侧,显示的是参与人都有谁、去了几个城市、一共去了几天、一共几段行程,
一共花了多少钱,这些信息,都从该活动下的所有行程统计总结,简介那一段话保留。」
→ 新增 Trip::summary():参与人(各段并集)/ 城市(去重)/ 天数 / 段数 / 总花销,
全部读时算(与"时间城市派生"同一条原则,不存冗余)。
天数按**日历天**含首尾(8/1→8/3 = 3 天,不是 24 小时整除的 2 天)。
城市与时间从标题旁挪进信息区,简介保留。
3. 「活动页面,不需要上传图片,图片视频,都是针对一次具体的行程的。」
→ 删掉旅程页的照片上传板块与照片墙;照片一律挂在**行程**上
(行程表单里有上传,附件 target_type='trip_node')。
4. 「计划行程、未成行的行程,都不要显示……只要是没有就是 0 条,那个类型就不显示了,
现在显示的是 0 条,这样没意义。」
→ 空分组直接跳过不渲染;全空时给一句引导(否则新建旅程页面一片空白)。
有内容的分组照常显示(计划 / 实际 / 未成行三种)。
5. 「行程的编辑功能,点击后会把页面撑破。编辑行程,打开新的页面进行编辑。」
→ 行程的新增与编辑改成**独立页面**(trips/node_new / trips/node_edit)。
原来内联在详情页:N 份表单互相撞元素 id、密码管理器逐个扫描变卡,
正是"把页面撑破"的成因。
6. 「添加计划行程和实际行程的按钮,都放在右上角。点击后打开新的页面进行添加。」
→ 详情页右上角两个按钮(+ 计划行程 / + 实际行程),指向独立新增页。
顺带:标题旁边不再放时间(时间移到题图右侧信息区)。
7. 「添加和编辑行程的内容,你去找 triplog 里面的添加节点功能,跟那个一样。」
→ 字段全量照搬 TripLog 的 nodes/form.php:
类型 / 标题 / 起止时间 / 出行方式 + 车辆 / 线路名 / 承运方 / 车次航班号 /
座位 / 登机口 / 出发地 / 到达地 / 内容 / 参与人(含角色)/ 照片 / 花销。
交通字段不做条件列(先填高铁车次、后来改成自驾,填过的东西不该丢)。
8. 「右上角的按钮,需要增加一个设置按钮,用来设置车辆管理、出行方式、节点类型。
注意,我们在财务中,已经把车辆添加进去了,所以这里不用重复添加那里面的车辆,
直接读取就可以,但是可以添加其他的车辆,比如租的车,借别人的车。」
→ 新增旅程设置菜单(三页):车辆管理 / 出行方式 / 节点类型。
车辆**两个来源刻意不合并**:财务里 kind='car' 的账目(那是资产,没有车牌车型)
由 Vehicle::fromLedger() 只读列出,选车下拉里标「来自财务」;
自家 / 租的 / 借的车录进 vehicles 表。不复制财务的车(避免同一台车两处维护)。
节点类型做成**可自定义**的表(TripLog 同款),内置 5 种不可删;
行程里存类型**名字**,所以改类型名会级联更新已用它的行程。
9. 「活动都改名吧,改成旅程,新建旅程,旅程包含行程,这个名字就固定下来。」
→ 术语固定:**旅程**(容器)包含**行程**(单元)。
顶层按钮「新建旅程」、详情页三组叫「计划的行程 / 实际行程 / 未成行」、
列表「N 段行程」、设置菜单「旅程设置」。上一批的"活动"叫法全部换掉。
10. 「新建旅程里面,怎么还有备注啊,同行的人啊,这些都不要了,这些都是从行程里面
总结出来的。在新建旅程和编辑旅程的时候,都不要了。」
→ 旅程表单只剩:标题 → 题图 → 简介 + 公开开关。
备注与同行的人全部移除(参与人 = 各段行程参与人的合集)。
⚠️ 但**老数据里活动级那份参与人名单不能被清空**(那时还没有行程级参与人),
所以路由"没传这个键就不动",并加了断言钉住这一点。
数据结构(Schema v21,老库自动升级):
· 新表 vehicles(自家/租的/借的)、node_types(可自定义)、trip_expenses(行程级花销)
· trip_nodes 补 9 列:出行方式 / 车辆 / 线路名 / 承运方 / 车次 / 座位 / 登机口 / 起终点
· trip_node_members 补 role_tag(司机 / 摄影担当这类角色)
· 花销金额存**分**,解析走字符串拆小数点(128.55*100 在浮点下会差一分)
验证:e2e 891/0(+46 条本批断言)· 升级 134/0 · 桌面几何 428/0 · 交互 92/0 ·
窄屏溢出 30/0(扩到行程表单 + 设置三页 + 详情页)· 题图点击 4/0 · 静态体检全绿。
**反向验证 6/6**:题图退回 4:3、标题旁加回时间、空分组照旧渲染、编辑退回非独立页、
车辆不读财务、总花销不汇总 —— 六种坏法全部被断言抓住。
部署静态断言新增第 16 批一组(含"空分组不许渲染""行程页不许有照片上传"
"旅程表单不许有备注与同行的人""题图必须 16:9"等反退化防线)。
部署后服务器自检 19/19,六个关键文件 md5 与线上逐一核对一致。
This commit is contained in:
+198
-161
@@ -1,53 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* 活动详情(v12 建立,v24 第 15 批整理术语与派生值)。
|
||||
* 旅程详情(v24 第 16 批重做)。
|
||||
*
|
||||
* 页面结构:封面 + 简介 + 同行的人 + 照片 + 下面的若干段**旅程**。
|
||||
* ⚠️ 术语(用户第 9 条:「活动都改名吧,改成旅程,新建旅程,旅程包含行程,
|
||||
* 这个名字就固定下来」):
|
||||
* · **旅程** = 这一页(原来叫"活动")
|
||||
* · **行程** = 里面的一段一段(原来叫"旅程")
|
||||
*
|
||||
* ⚠️ 术语(用户 2026-10-05 第 15 批 第 1 条):这里是**一次活动**,
|
||||
* 里头一段一段的才是**旅程**。所以「+ 加一段计划行程」这类按钮
|
||||
* 统一改成「+ 加一段旅程」(阶段用「计划 / 实际」修饰)。
|
||||
* 页面结构(用户第 1/2/3/6 条):
|
||||
* ① 页头:标题 + 右上角按钮(计划行程 / 实际行程 / 设置 / 编辑 / 删除)
|
||||
* —— 标题旁边**不放时间**(第 6 条:时间移到内容里、题图右侧)
|
||||
* ② 封面行:左 16:9 题图 + 右侧信息区
|
||||
* 信息区 = 参与人 / 城市数 / 天数 / 行程段数 / 总花销 + 简介那一段话
|
||||
* ③ 行程分组:**只显示非空的分组**(第 4 条:"只要是没有就是 0 条,
|
||||
* 那个类型就不显示了,现在显示的是 0 条,这样没意义")
|
||||
* ④ **没有照片上传板块**(第 3 条:图片视频都针对一次具体行程)
|
||||
*
|
||||
* ⚠️ 时间与城市都来自 $t 上挂的**派生值**(Trip::derived(),用户第 2 条)——
|
||||
* 页面不再读 events.starts_at / location。
|
||||
* @param array $t 旅程(events + trip_details + 派生值)
|
||||
* @param array $nodes 全部行程(已带上成员/附件/花销)
|
||||
* @param array $sm 汇总信息(Trip::summary())
|
||||
*/
|
||||
$canWrite = \App\Auth::canWrite();
|
||||
// v24 第 13 批:不再按 category 双轨分,按**状态机 stage** 分三组展示。
|
||||
// 计划行程 / 实际行程 / 未成行(未成行的也要显示,用户明确要求"只不过要标记上")。
|
||||
$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'] ?? (($n['category'] ?? 'plan') === 'reality' ? 'actual' : 'plan'));
|
||||
$sg = (string)($n['stage'] ?? 'plan');
|
||||
if (!isset($bysStage[$sg])) { $sg = 'plan'; }
|
||||
$bysStage[$sg][] = $n;
|
||||
}
|
||||
// 每条行程带上它的修正历史(模板里画"原计划 → 实际"的对比)
|
||||
foreach ($bysStage as $sg => &$lst) {
|
||||
foreach ($lst as &$nd) {
|
||||
$nd['revisions'] = \App\Trip::revisions((int)$nd['id']);
|
||||
}
|
||||
unset($nd);
|
||||
}
|
||||
unset($lst);
|
||||
$memberIds = array_map('intval', array_column((array)($t['attendees'] ?? []), 'id'));
|
||||
$groupMeta = [
|
||||
'plan' => ['计划的行程', '还没走 —— 可以修正 / 完成 / 标记未成行'],
|
||||
'actual' => ['实际行程', '已走过 —— 也出现在日历与首页「往年今日」'],
|
||||
'skipped' => ['未成行', '这次没去成;不上日程,但在这里留着可查'],
|
||||
];
|
||||
?>
|
||||
<div class="page-head">
|
||||
<h1><?= e((string)$t['title']) ?></h1>
|
||||
<?php /* 时间/城市都是派生的:一段旅程都没加时显示「还没有旅程」,不是「日期待补」 */ ?>
|
||||
<span class="sub"><?= e(\App\Trip::activitySpan($t['starts_at'] ?? null, $t['ends_at'] ?? null)) ?></span>
|
||||
<?php /* ⚠️ 用户第 6 条:标题旁边**不放**时间。时间在下面题图右侧的信息区里。 */ ?>
|
||||
<div class="actions">
|
||||
<?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>
|
||||
<form method="post" action="<?= e(url('trips/destroy')) ?>"
|
||||
onsubmit="return confirm('确定删除「<?= e((string)$t['title']) ?>」吗?里面的旅程、照片会一起删掉,不可恢复。')">
|
||||
<?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-danger" type="submit">删除</button>
|
||||
<button class="btn btn-ghost btn-icon" type="submit" title="删除这个旅程" aria-label="删除">🗑</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<span class="link-quiet">只读账号</span>
|
||||
<?php endif; ?>
|
||||
<a class="btn btn-ghost" href="<?= e(url('trips')) ?>">返回活动</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php /* ==================== 封面行:题图 + 信息区 ====================
|
||||
⚠️ 第 1 条:「题图应该按照 16:9 显示,因为添加的时候,是按照这个比例裁剪的,
|
||||
现在的显示比例不对」—— 之前 CSS 写的是 4:3,把 16:9 的图裁掉两边。
|
||||
16:9 这个数字与 ImageSlot 里 cover 槽位的比例**必须一致**,
|
||||
部署断言里有一条专门比对它。 */ ?>
|
||||
<div class="card card-pad trip-hero">
|
||||
<div class="trip-hero-cover">
|
||||
<?php if (!empty($t['cover_key'])): ?>
|
||||
@@ -56,145 +84,173 @@ $memberIds = array_map('intval', array_column((array)($t['attendees'] ?? []), 'i
|
||||
<span class="trip-cover-empty">🧳</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="trip-hero-body">
|
||||
<?php /* 地名可能是好几个城市 → 用派生出来的列表逐颗画(用户第 2 条) */ ?>
|
||||
<?php if (!empty($t['cities'])): ?>
|
||||
<p class="trip-loc">
|
||||
<?php foreach ((array)$t['cities'] as $i => $city): ?>
|
||||
<?= $i > 0 ? '<span class="city-sep">·</span>' : '📍' ?>
|
||||
<span class="city-chip"><?= e((string)$city) ?></span>
|
||||
<?php endforeach; ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($t['summary'])): ?>
|
||||
<p class="info-text"><?= nl2br(e((string)$t['summary'])) ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($t['attendees'])): ?>
|
||||
<?php /* v24 第 14 批 用户第 4 条:「一个活动的参与人,应该是这个活动下的
|
||||
所有旅程的参与人的合集」—— 这上面的名单是**每段行程参与人的并集**,
|
||||
在行程里加了人,这里自动出现,不用再到活动上登记一遍。 */ ?>
|
||||
<?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 ($t['attendees'] as $a): ?>
|
||||
<?php foreach ($people as $a): ?>
|
||||
<?= person_chip($a, url('people/show', ['id' => (int)$a['id']])) ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php /* ---- 简介:用户第 2 条明确"简介那一段话保留" ---- */ ?>
|
||||
<?php if (!empty($t['summary'])): ?>
|
||||
<p class="info-text trip-sum"><?= nl2br(e((string)$t['summary'])) ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($t['note'])): ?>
|
||||
<p class="remark-text"><?= nl2br(e((string)$t['note'])) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 照片 ---- */ ?>
|
||||
<div class="card card-pad">
|
||||
<h2 class="cinema-h2">照片</h2>
|
||||
<?php if (!$photos): ?>
|
||||
<p class="empty-hint">还没有照片。</p>
|
||||
<?php else: ?>
|
||||
<div class="photo-grid">
|
||||
<?php foreach ($photos as $ph): ?>
|
||||
<figure class="photo-cell">
|
||||
<?php /* v24 第 3 条:照片也在**本页**预览(原来 target=_blank 是另开一个标签页
|
||||
只显示一张图,既没有下载按钮也不好回来看) */ ?>
|
||||
<a href="<?= e(media_url((string)$ph['key'])) ?>"
|
||||
data-prev="image"
|
||||
data-src="<?= e(media_url((string)$ph['key'])) ?>"
|
||||
data-dl="<?= e(media_url((string)$ph['key'], ['dl' => 1])) ?>"
|
||||
data-name="<?= e((string)($ph['name'] ?? '照片')) ?>">
|
||||
<img src="<?= e(media_url((string)$ph['key'])) ?>" alt="<?= e((string)($ph['name'] ?? '')) ?>">
|
||||
</a>
|
||||
<?php if ($canWrite): ?>
|
||||
<form method="post" action="<?= e(url('trips/photo_del')) ?>" class="photo-del">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||||
<input type="hidden" name="photo_id" value="<?= (int)$ph['id'] ?>">
|
||||
<button class="btn btn-ghost btn-sm" type="submit"
|
||||
onclick="return confirm('删掉这张照片?')">删除</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</figure>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($canWrite): ?>
|
||||
<form class="inline-form" method="post" enctype="multipart/form-data" action="<?= e(url('trips/photo_add')) ?>">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||||
<input type="file" name="photo" accept="image/*" multiple required>
|
||||
<button class="btn btn-sm" type="submit">上传照片</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php /* ==================== 这个活动里的旅程(v24 第 13 批重构)====================
|
||||
⚠️ 术语(用户第 15 批 第 1 条):这一块里每一条都是**一段旅程**,
|
||||
不是"行程";分三组是按它的**阶段**(计划 / 实际 / 未成行)。
|
||||
|
||||
用户 2026-10-05(第 13 批)原话要点:
|
||||
· 刚开始添加时选「计划 / 实际」
|
||||
· 计划的三段有三个按钮:删除(**标记未成行,不真删**)、修正(记实际执行,可无限次)、
|
||||
完成(实际=计划,直接变实际)
|
||||
· 未成行的仍显示、但带标记;不上日程、不进往年今日
|
||||
· 实际的会同时上日程 + 往年今日
|
||||
分三组渲染(未成行放最后,用灰调)。 */ ?>
|
||||
<?php /* ==================== 行程分组 ====================
|
||||
用户第 4 条:「只要是没有就是 0 条,那个类型就不显示了,现在显示的是 0 条,这样没意义。」
|
||||
⚠️ 所以这里**跳过空分组**。但要给"一段都还没有"的情况一句引导 ——
|
||||
否则新建的旅程页面上会一片空白,人不知道该干什么。 */ ?>
|
||||
<?php
|
||||
$stageMeta = [
|
||||
'plan' => ['计划的旅程', '还没走。每段下面的「修正 / 完成 / 标记未成行」都能用'],
|
||||
'actual' => ['走过的旅程', '已走过 —— 会出现在日历与首页「往年今日」'],
|
||||
'skipped' => ['没成行的', '这次没去成;不再出现在日程与「往年今日」,但在这里保留可查'],
|
||||
];
|
||||
foreach (['plan', 'actual', 'skipped'] as $sg):
|
||||
$list = $bysStage[$sg];
|
||||
$shown = 0;
|
||||
foreach ($bysStage as $lst) { $shown += count($lst); }
|
||||
?>
|
||||
<div class="card card-pad trip-stage-card stage-<?= e($sg) ?>">
|
||||
<h2 class="cinema-h2">
|
||||
<?= e($stageMeta[$sg][0]) ?>
|
||||
<span class="group-n"><?= count($list) ?> 段</span>
|
||||
</h2>
|
||||
<p class="stage-hint"><?= e($stageMeta[$sg][1]) ?></p>
|
||||
|
||||
<?php if (!$list): ?>
|
||||
<p class="empty-hint">
|
||||
<?= $sg === 'plan' ? '还没有排计划。点下面的「+ 加一段旅程」,把打算走的地方排进来。'
|
||||
: ($sg === 'actual' ? '还没有走过的旅程。' : '没有未成行的。') ?>
|
||||
</p>
|
||||
<?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'];
|
||||
$span = \App\Trip::formatSpan($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-<?= (int)$n['id'] ?>">
|
||||
<span class="node-ico" title="<?= e(\App\Trip::typeWord((string)$n['node_type'])) ?>">
|
||||
<?= e(\App\Trip::typeIcon((string)$n['node_type'])) ?>
|
||||
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::stageWord($sg)) ?></span>
|
||||
<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>
|
||||
<span class="rev-badge" title="已修正过多次">已修正 <?= (int)$n['revision_n'] ?> 次</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($n['place'])): ?>
|
||||
<span class="node-place">📍 <?= e((string)$n['place']) ?></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 $span = \App\Trip::formatSpan($n['starts_at'] ?? null, $n['ends_at'] ?? null); ?>
|
||||
|
||||
<?php if ($span !== '日期待补'): ?>
|
||||
<div class="node-time">
|
||||
<?= e($span) ?>
|
||||
<?php if ($sg === 'actual'): ?><span class="actual-tag">实际</span><?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php /* 交通细节(TripLog 同款):只显示**填过的**那几个,没填就不占地方 */ ?>
|
||||
<?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']; }
|
||||
if (!empty($n['seat_info'])) { $traffic[] = '座位 ' . (string)$n['seat_info']; }
|
||||
if (!empty($n['gate'])) { $traffic[] = '登机口/站台 ' . (string)$n['gate']; }
|
||||
?>
|
||||
<?php if ($traffic): ?>
|
||||
<div class="node-traffic">
|
||||
<?php foreach ($traffic as $tf): ?><span class="ntag"><?= e($tf) ?></span><?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($n['from_place']) || !empty($n['to_place'])): ?>
|
||||
<div class="node-route-line">
|
||||
<?= e((string)($n['from_place'] ?? '—')) ?> <span class="arrow">→</span> <?= e((string)($n['to_place'] ?? '—')) ?>
|
||||
</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 if (!empty($n['members'])): ?>
|
||||
<div class="trip-people">
|
||||
<?php foreach ($n['members'] as $mm): ?><?= person_chip($mm) ?><?php endforeach; ?>
|
||||
<?php foreach ($n['members'] as $mm): ?>
|
||||
<span class="node-member">
|
||||
<?= person_chip($mm) ?>
|
||||
<?php if (!empty($mm['role_tag'])): ?>
|
||||
<span class="node-role"><?= e((string)$mm['role_tag']) ?></span>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($n['attachs'])): ?>
|
||||
<?php
|
||||
$attRows = $n['attachs'];
|
||||
@@ -204,7 +260,7 @@ foreach (['plan', 'actual', 'skipped'] as $sg):
|
||||
?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php /* ---- 修正记录:原计划 → 每次修正的对比 ---- */ ?>
|
||||
<?php /* ---- 修正记录对比(第 13 批) ---- */ ?>
|
||||
<?php if (!empty($n['revisions'])): ?>
|
||||
<details class="rev-hist">
|
||||
<summary class="link-quiet">看修正记录(<?= count($n['revisions']) ?> 条)</summary>
|
||||
@@ -229,7 +285,10 @@ foreach (['plan', 'actual', 'skipped'] as $sg):
|
||||
|
||||
<?php if ($canWrite): ?>
|
||||
<div class="node-ops">
|
||||
<?php /* ---- 计划行程:三个动作(用户点名的三个按钮)---- */ ?>
|
||||
<?php /* 用户第 5 条:**编辑行程打开新页面**(内联的编辑面板会把页面撑破) */ ?>
|
||||
<a class="btn btn-ghost btn-sm"
|
||||
href="<?= e(url('trips/node_edit', ['id' => $nid])) ?>">编辑</a>
|
||||
|
||||
<?php if ($sg === 'plan'): ?>
|
||||
<details class="rev-open">
|
||||
<summary class="btn btn-ghost btn-sm">修正</summary>
|
||||
@@ -237,7 +296,7 @@ foreach (['plan', 'actual', 'skipped'] as $sg):
|
||||
<p class="rev-tip">
|
||||
记录实际执行情况。这仍与上面那条计划是<b>同一条记录</b>,
|
||||
原来的计划数据会保留在「修正记录」里,方便日后对比。
|
||||
点完这里<b>这段旅程仍是计划</b>;确认无误后再点「标记为实际」。
|
||||
点完这里<b>这段行程仍是计划</b>;确认无误后再点「标记为实际」。
|
||||
</p>
|
||||
<?php
|
||||
$rvNode = $n; $rvTripId = (int)$t['id'];
|
||||
@@ -247,71 +306,49 @@ foreach (['plan', 'actual', 'skipped'] as $sg):
|
||||
?>
|
||||
</div>
|
||||
</details>
|
||||
<form method="post" action="<?= e(url('trips/node_done')) ?>"
|
||||
<form method="post" action="<?= e(url('trips/node_done')) ?>" class="res-inline"
|
||||
onsubmit="return confirm('实际执行与计划完全相同,直接转为实际?')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$n['id'] ?>">
|
||||
<input type="hidden" name="id" value="<?= $nid ?>">
|
||||
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
||||
<button class="btn btn-sm" type="submit">完成</button>
|
||||
</form>
|
||||
<form method="post" action="<?= e(url('trips/node_skip')) ?>"
|
||||
onsubmit="return confirm('标记为「未成行」?它不会再出现在日程与往年今日,但在这个活动里仍然看得到。')">
|
||||
<form method="post" action="<?= e(url('trips/node_skip')) ?>" class="res-inline"
|
||||
onsubmit="return confirm('标记为「未成行」?它不会再出现在日程与往年今日,但在这个旅程里仍然看得到。')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$n['id'] ?>">
|
||||
<input type="hidden" name="id" value="<?= $nid ?>">
|
||||
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
||||
<button class="btn btn-ghost btn-sm" type="submit">标记未成行</button>
|
||||
</form>
|
||||
<?php if ((int)($n['revision_n'] ?? 0) > 0): ?>
|
||||
<form method="post" action="<?= e(url('trips/node_actual')) ?>">
|
||||
<form method="post" action="<?= e(url('trips/node_actual')) ?>" class="res-inline">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$n['id'] ?>">
|
||||
<input type="hidden" name="id" value="<?= $nid ?>">
|
||||
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
||||
<button class="btn btn-sm btn-primary" type="submit">标记为实际</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php elseif ($sg === 'skipped'): ?>
|
||||
<form method="post" action="<?= e(url('trips/node_restore')) ?>">
|
||||
<form method="post" action="<?= e(url('trips/node_restore')) ?>" class="res-inline">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$n['id'] ?>">
|
||||
<input type="hidden" name="id" value="<?= $nid ?>">
|
||||
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
||||
<button class="btn btn-ghost btn-sm" type="submit">恢复为计划</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<details class="node-edit">
|
||||
<summary class="link-quiet">编辑</summary>
|
||||
<?php
|
||||
$nfNode = $n; $nfTripId = (int)$t['id']; $nfCategory = $sg;
|
||||
$nfPeople = $people ?? [];
|
||||
$nfMembers = array_map('intval', array_column((array)($n['members'] ?? []), 'id'));
|
||||
require __DIR__ . '/_node_form.php';
|
||||
?>
|
||||
</details>
|
||||
<form method="post" action="<?= e(url('trips/node_del')) ?>"
|
||||
onsubmit="return confirm('彻底删除这一段旅程?连同它的修正记录一起消失,不可恢复。\n\n如果你只是想表达「这次没去成」,请用「标记未成行」。')">
|
||||
<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="<?= (int)$n['id'] ?>">
|
||||
<input type="hidden" name="id" value="<?= $nid ?>">
|
||||
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
||||
<button class="btn btn-ghost btn-sm" type="submit">彻底删除</button>
|
||||
<button class="btn btn-ghost btn-sm" type="submit">删除</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canWrite && $sg !== 'skipped'): ?>
|
||||
<details class="node-add">
|
||||
<?php /* 术语:加的是"一段旅程",阶段只是它的属性 */ ?>
|
||||
<summary class="link-quiet">+ 加一段旅程(<?= $sg === 'actual' ? '实际' : '计划' ?>)</summary>
|
||||
<?php
|
||||
$nfNode = null; $nfTripId = (int)$t['id']; $nfCategory = $sg;
|
||||
$nfPeople = $people ?? []; $nfMembers = $memberIds;
|
||||
require __DIR__ . '/_node_form.php';
|
||||
?>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
Reference in New Issue
Block a user