2026-10-02 09:28:07 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
/**
|
2026-10-05 13:26:49 +08:00
|
|
|
|
* 旅程详情(v24 第 16 批重做)。
|
2026-10-02 09:28:07 +08:00
|
|
|
|
*
|
2026-10-05 13:26:49 +08:00
|
|
|
|
* ⚠️ 术语(用户第 9 条:「活动都改名吧,改成旅程,新建旅程,旅程包含行程,
|
|
|
|
|
|
* 这个名字就固定下来」):
|
|
|
|
|
|
* · **旅程** = 这一页(原来叫"活动")
|
|
|
|
|
|
* · **行程** = 里面的一段一段(原来叫"旅程")
|
2026-10-05 10:58:17 +08:00
|
|
|
|
*
|
2026-10-05 13:26:49 +08:00
|
|
|
|
* 页面结构(用户第 1/2/3/6 条):
|
|
|
|
|
|
* ① 页头:标题 + 右上角按钮(计划行程 / 实际行程 / 设置 / 编辑 / 删除)
|
|
|
|
|
|
* —— 标题旁边**不放时间**(第 6 条:时间移到内容里、题图右侧)
|
|
|
|
|
|
* ② 封面行:左 16:9 题图 + 右侧信息区
|
|
|
|
|
|
* 信息区 = 参与人 / 城市数 / 天数 / 行程段数 / 总花销 + 简介那一段话
|
|
|
|
|
|
* ③ 行程分组:**只显示非空的分组**(第 4 条:"只要是没有就是 0 条,
|
|
|
|
|
|
* 那个类型就不显示了,现在显示的是 0 条,这样没意义")
|
|
|
|
|
|
* ④ **没有照片上传板块**(第 3 条:图片视频都针对一次具体行程)
|
2026-10-05 10:58:17 +08:00
|
|
|
|
*
|
2026-10-05 13:26:49 +08:00
|
|
|
|
* @param array $t 旅程(events + trip_details + 派生值)
|
|
|
|
|
|
* @param array $nodes 全部行程(已带上成员/附件/花销)
|
|
|
|
|
|
* @param array $sm 汇总信息(Trip::summary())
|
2026-10-02 09:28:07 +08:00
|
|
|
|
*/
|
|
|
|
|
|
$canWrite = \App\Auth::canWrite();
|
2026-10-05 13:26:49 +08:00
|
|
|
|
$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);
|
|
|
|
|
|
|
|
|
|
|
|
// 三组:计划 / 实际 / 未成行
|
2026-10-04 20:57:40 +08:00
|
|
|
|
$bysStage = ['plan' => [], 'actual' => [], 'skipped' => []];
|
2026-10-02 09:28:07 +08:00
|
|
|
|
foreach ((array)($nodes ?? []) as $n) {
|
2026-10-05 13:26:49 +08:00
|
|
|
|
$sg = (string)($n['stage'] ?? 'plan');
|
2026-10-04 20:57:40 +08:00
|
|
|
|
if (!isset($bysStage[$sg])) { $sg = 'plan'; }
|
|
|
|
|
|
$bysStage[$sg][] = $n;
|
2026-10-02 09:28:07 +08:00
|
|
|
|
}
|
2026-11-05 12:40:57 +08:00
|
|
|
|
/* 🔴 #48(2026-11-05):**提示语删掉**(用户:「已走过 —— 也出现在日历与首页
|
|
|
|
|
|
『往年今日』,这句话删除」)。不只删这一句 —— 三个分组的提示语一律去掉:
|
|
|
|
|
|
组头只留名称和条数,界面更干净。
|
|
|
|
|
|
⚠️ 保留数组结构(只用 [0] 名称),改动面最小。 */
|
2026-10-05 13:26:49 +08:00
|
|
|
|
$groupMeta = [
|
2026-11-05 12:40:57 +08:00
|
|
|
|
'plan' => ['计划的行程'],
|
|
|
|
|
|
'actual' => ['实际行程'],
|
|
|
|
|
|
'skipped' => ['未成行'],
|
2026-10-05 13:26:49 +08:00
|
|
|
|
];
|
2026-10-02 09:28:07 +08:00
|
|
|
|
?>
|
|
|
|
|
|
<div class="page-head">
|
|
|
|
|
|
<h1><?= e((string)$t['title']) ?></h1>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php /* ⚠️ 用户第 6 条:标题旁边**不放**时间。时间在下面题图右侧的信息区里。 */ ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<div class="actions">
|
|
|
|
|
|
<?php if ($canWrite): ?>
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<?php /* 🔴🔴 v41(用户 2026-10-10 第 4 条):
|
|
|
|
|
|
「旅程详情页面右上角的按钮,顺序分别是从左到右:
|
|
|
|
|
|
+计划行程、+实际行程、编辑、删除、返回旅程和设置。
|
|
|
|
|
|
计划行程的按钮用蓝色背景;实际行程的按钮用绿色背景;
|
|
|
|
|
|
编辑用黄色背景;删除用红色背景;设置就用这个图标,不要变。」
|
|
|
|
|
|
⇒ 两条硬要求:
|
|
|
|
|
|
① **顺序**:+计划行程 → +实际行程 → 编辑 → 删除 → 返回旅程 → 设置(☰);
|
|
|
|
|
|
② **配色**:计划=蓝、实际=绿、编辑=黄、删除=红;设置保持图标不变。
|
|
|
|
|
|
⚠️ 之前是「返回旅程在最左、其余 ghost/实心混着」,与本条**完全相反** ——
|
|
|
|
|
|
所以这一次是**按新顺序重排整段**,不是微调。
|
|
|
|
|
|
⚠️ 原来"+计划行程/编辑"是 `<a>`(跳页面),现在全部改成**弹框**(见第 5 条),
|
|
|
|
|
|
所以这里用 `<button type="button">` + data 属性,由页内脚本打开弹框。 */ ?>
|
|
|
|
|
|
<button class="btn btn-plan" type="button"
|
|
|
|
|
|
data-node-open="<?= e(url('trips/node_new', ['id' => (int)$t['id'], 'stage' => 'plan', 'embed' => 1])) ?>"
|
|
|
|
|
|
aria-haspopup="dialog" aria-expanded="false">+ 计划行程</button>
|
|
|
|
|
|
<button class="btn btn-actual" type="button"
|
|
|
|
|
|
data-node-open="<?= e(url('trips/node_new', ['id' => (int)$t['id'], 'stage' => 'actual', 'embed' => 1])) ?>"
|
|
|
|
|
|
aria-haspopup="dialog" aria-expanded="false">+ 实际行程</button>
|
|
|
|
|
|
<button class="btn btn-edit" type="button"
|
|
|
|
|
|
data-trip-edit="<?= e(url('trips/edit', ['id' => (int)$t['id'], 'embed' => 1])) ?>"
|
|
|
|
|
|
aria-haspopup="dialog" aria-expanded="false">编辑</button>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<form method="post" action="<?= e(url('trips/destroy')) ?>" class="res-inline"
|
|
|
|
|
|
onsubmit="return confirm('确定删除「<?= e((string)$t['title']) ?>」吗?里面的行程、照片会一起删掉,不可恢复。')">
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?= csrf_field() ?>
|
|
|
|
|
|
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<button class="btn btn-del" type="submit" title="删除这个旅程">删除</button>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
</form>
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<a class="btn btn-ghost" href="<?= e(url('trips')) ?>">返回旅程</a>
|
|
|
|
|
|
<?php /* ⚠️ 设置**恒为动作区最后一个**(全站统一:观影/日程/任务/资源都如此)。
|
|
|
|
|
|
用户这次明确"设置就用这个图标,不要变" ⇒ 保持 `_settings_menu.php`
|
|
|
|
|
|
原样(纯图标 ☰),一个字都不动。 */ ?>
|
2026-10-06 12:08:40 +08:00
|
|
|
|
<?php $trSetCurrent = ''; require __DIR__ . '/_settings_menu.php'; ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php else: ?>
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<a class="btn btn-ghost" href="<?= e(url('trips')) ?>">返回旅程</a>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<span class="link-quiet">只读账号</span>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php /* ==================== 封面行:题图 + 信息区 ====================
|
|
|
|
|
|
⚠️ 第 1 条:「题图应该按照 16:9 显示,因为添加的时候,是按照这个比例裁剪的,
|
|
|
|
|
|
现在的显示比例不对」—— 之前 CSS 写的是 4:3,把 16:9 的图裁掉两边。
|
|
|
|
|
|
16:9 这个数字与 ImageSlot 里 cover 槽位的比例**必须一致**,
|
2026-10-05 16:45:10 +08:00
|
|
|
|
部署断言里有一条专门比对它。
|
|
|
|
|
|
⚠️ 第 17 批 第 4 条:「旅程详情页面,图片右侧显示的是**聚合的城市**,
|
|
|
|
|
|
而不是终点地址。」—— 城市来自 `Trip::cities()`(出发地 ∪ 到达地,去重),
|
|
|
|
|
|
见 summary() → cities()。 */ ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<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>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<div class="trip-hero-body">
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?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>
|
|
|
|
|
|
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<?php /* 参与人是谁:用户点名要("显示的是参与人都有谁")。
|
|
|
|
|
|
🔴 v41(用户 2026-10-10):标题后面那个括号(以及括号里那句解释)不要了。
|
|
|
|
|
|
这个括号连带里面的内容都不要了。」⇒ 只留标题四个字。 */ ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php if ($people): ?>
|
2026-10-05 10:58:17 +08:00
|
|
|
|
<div class="trip-people-block">
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<span class="small muted">参与的人</span>
|
2026-10-05 10:58:17 +08:00
|
|
|
|
<div class="viewers-list">
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php foreach ($people as $a): ?>
|
2026-10-05 10:58:17 +08:00
|
|
|
|
<?= person_chip($a, url('people/show', ['id' => (int)$a['id']])) ?>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</div>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
|
2026-10-05 22:56:49 +08:00
|
|
|
|
<?php /* ---- 简介(用户第 2 条明确"简介那一段话保留")。
|
|
|
|
|
|
🔴 v24 第 19 批 第 4 条修的是这里:用户报「详情页题图右侧有一句话显示,
|
|
|
|
|
|
但点击编辑进去,却看不到这句话」。原因就是**这句话读的字段和表单存的
|
|
|
|
|
|
字段不是同一个**:
|
|
|
|
|
|
· 旅程表单存的是 `trip_details.summary`(编辑页那个「一段简介」框);
|
|
|
|
|
|
· 而这里先读 `summary`,再读 `events.note`(**老字段**,表单里早就没有
|
|
|
|
|
|
这个输入框了),两句都用 .trip-sum 的画法渲染 ——
|
|
|
|
|
|
于是老数据里的 `note` 会在详情页冒出来,编辑页却无从修改/删除。
|
|
|
|
|
|
现在只渲染 `summary` 一个来源:**页面显示的每一句话,编辑页都能改**。
|
|
|
|
|
|
`note` 不再出现在详情页(数据仍留在库里,不删)。
|
|
|
|
|
|
老数据里"只填过备注、没填过简介"的那批由
|
|
|
|
|
|
`_tooling/_fb_b19_backfill.php` 一次性搬进 summary。 */ ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php if (!empty($t['summary'])): ?>
|
|
|
|
|
|
<p class="info-text trip-sum"><?= nl2br(e((string)$t['summary'])) ?></p>
|
2026-10-11 18:35:03 +08:00
|
|
|
|
<?php elseif ($canWrite): ?>
|
|
|
|
|
|
<?php /* 🔴 v42(用户 2026-10-11 第 6 条):「旅程详情页面,参与的人下面,
|
|
|
|
|
|
原来有旅程的简介,怎么没了?加回来。」
|
|
|
|
|
|
⚠️ 排查结论:简介**一直在**(上面的 `!empty($t['summary'])` 分支),
|
|
|
|
|
|
结构从 v40 到 v41 一字未动(v41 只把「参与的人」后面那个括号
|
|
|
|
|
|
连同里面的说明整句去掉了)。
|
|
|
|
|
|
⇒ 用户"看不到"的真实原因是**那条旅程的简介是空的** ——
|
|
|
|
|
|
他多半是先用 v41 那个"新建旅程带入了旧数据"的 bug(本批第 1 条)
|
|
|
|
|
|
建/改过,summary 没存进去。
|
|
|
|
|
|
⇒ 修法两层:
|
|
|
|
|
|
① 第 1 条那个 bug 已修(新建不再串数据);
|
|
|
|
|
|
② 这里补一句**可点的占位**:简介为空但能写的人,看到"还没有简介,
|
|
|
|
|
|
点这里写一句" —— 否则整块是空的,用户会以为"功能被删了"。 */ ?>
|
|
|
|
|
|
<p class="trip-sum-empty">
|
|
|
|
|
|
还没有简介 ——
|
|
|
|
|
|
<a href="<?= e(url('trips/edit', ['id' => (int)$t['id']])) ?>">点这里写一句</a>。
|
|
|
|
|
|
</p>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-10-05 22:56:49 +08:00
|
|
|
|
<?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; ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php /* ==================== 行程分组 ====================
|
|
|
|
|
|
用户第 4 条:「只要是没有就是 0 条,那个类型就不显示了,现在显示的是 0 条,这样没意义。」
|
|
|
|
|
|
⚠️ 所以这里**跳过空分组**。但要给"一段都还没有"的情况一句引导 ——
|
|
|
|
|
|
否则新建的旅程页面上会一片空白,人不知道该干什么。 */ ?>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?php
|
2026-10-05 13:26:49 +08:00
|
|
|
|
$shown = 0;
|
|
|
|
|
|
foreach ($bysStage as $lst) { $shown += count($lst); }
|
2026-10-04 20:57:40 +08:00
|
|
|
|
?>
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<?php /* 🔴 v41:行程列表包一层 `#nodeListHost` —— 弹框里保存成功后,
|
|
|
|
|
|
前端只换这一块(保留滚动位置、不整页闪白)。
|
|
|
|
|
|
⚠️ 注意 `tripListHost`(旅程列表)与 `nodeListHost`(行程列表)是两个
|
|
|
|
|
|
不同页面的容器,前端脚本两个 id 都认。 */ ?>
|
|
|
|
|
|
<div id="nodeListHost">
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php if ($shown === 0): ?>
|
|
|
|
|
|
<div class="card card-pad empty">
|
|
|
|
|
|
<b>还没有行程</b>
|
|
|
|
|
|
<?php if ($canWrite): ?>
|
|
|
|
|
|
点右上角「+ 计划行程」把打算走的地方排进来,或者「+ 实际行程」直接记已经走过的。
|
|
|
|
|
|
参与人、城市、天数、花销都会从行程里自动汇总到这里。
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php else: ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
家人还没有记录行程。
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php else: ?>
|
2026-11-05 12:40:57 +08:00
|
|
|
|
<?php /* 🔴 #48:**实际行程放最前** —— 用户:「到最后整个旅程结束之后,
|
|
|
|
|
|
我们最终看到的是实际行程。」⇒ actual → plan → skipped(未成行沉底)。 */ ?>
|
|
|
|
|
|
<?php foreach (['actual', 'plan', 'skipped'] as $sg): ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?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>
|
|
|
|
|
|
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<ol class="node-list">
|
|
|
|
|
|
<?php foreach ($list as $n): ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
$nid = (int)$n['id'];
|
2026-10-05 16:45:10 +08:00
|
|
|
|
// ⚠️ 第 17 批 第 4 条末:「行程中,时间明明具体到了时分秒,怎么不显示?」
|
|
|
|
|
|
// → 行程行改用 nodeSpan()(精确到时分),**不再**用只到日期的 formatSpan()。
|
|
|
|
|
|
// formatSpan 那个精度是给"旅程"(容器)用的。
|
|
|
|
|
|
$span = \App\Trip::nodeSpan($n['starts_at'] ?? null, $n['ends_at'] ?? null);
|
2026-10-05 13:26:49 +08:00
|
|
|
|
$mode = (string)($n['travel_mode'] ?? '');
|
|
|
|
|
|
$spent = (int)($n['spent_cents'] ?? 0);
|
|
|
|
|
|
?>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<li class="node-row status-<?= e((string)$n['status']) ?> stage-<?= e($sg) ?>"
|
2026-10-05 13:26:49 +08:00
|
|
|
|
id="node-<?= $nid ?>">
|
2026-11-05 12:40:57 +08:00
|
|
|
|
<?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)) ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
<div class="node-body">
|
|
|
|
|
|
<div class="node-head">
|
|
|
|
|
|
<b><?= e((string)$n['title']) ?></b>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<span class="node-badge nb-<?= e($sg) ?>"><?= e(\App\Trip::typeWord((string)$n['node_type'], $fid)) ?></span>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?php if ((int)($n['revision_n'] ?? 0) > 0): ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<span class="rev-badge" title="已修正过多次">已修正 <?= (int)$n['revision_n'] ?> 次</span>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?php endif; ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php if ($mode !== ''): ?>
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
/* 🔴 v41(用户 2026-10-10 第 8 条)原话:
|
|
|
|
|
|
「行程里面,交通工具的信息放在后面。比如现在的思路是出发去菏泽,
|
|
|
|
|
|
后面写赶路,再后面写火车。那就应该把火车的车次号直接放在"火车"
|
|
|
|
|
|
这两个字后面。如果是自驾,就应该把自驾的车牌号放在后面。」
|
|
|
|
|
|
⇒ 交通方式的**标识**(车次号 / 车牌号)紧跟模式词,同一个 chip:
|
|
|
|
|
|
火车 → 「🚄 火车 G1234」;自驾 → 「🚗 自驾 鲁B·12345」。
|
|
|
|
|
|
⚠️ 不再把 trip_no 单独丢进下面那排 `.ntag`(那正是"隔了一段"的来源)。
|
|
|
|
|
|
⚠️ 自驾取的是**车辆库里的车牌**(`Vehicle::label` 里已含车牌),
|
|
|
|
|
|
不是 `trip_no` —— 自驾那条字段本来就不收车次。 */
|
|
|
|
|
|
$modeExtra = '';
|
|
|
|
|
|
if (\App\Trip::modeNeedsVehicle($mode) && !empty($n['vehicle_plate'])) {
|
|
|
|
|
|
$modeExtra = (string)$n['vehicle_plate'];
|
|
|
|
|
|
} elseif (!empty($n['trip_no'])) {
|
|
|
|
|
|
$modeExtra = (string)$n['trip_no'];
|
|
|
|
|
|
}
|
|
|
|
|
|
?>
|
|
|
|
|
|
<span class="node-mode"><?= e(\App\Trip::modeIcon($mode)) ?> <?= e(\App\Trip::modeWord($mode)) ?><?php
|
|
|
|
|
|
if ($modeExtra !== ''): ?><span class="node-mode-no"><?= e($modeExtra) ?></span><?php endif; ?></span>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php endif; ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
</div>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php if ($span !== '日期待补'): ?>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<div class="node-time">
|
2026-11-05 12:40:57 +08:00
|
|
|
|
<?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; ?>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
</div>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php endif; ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
|
2026-10-05 16:45:10 +08:00
|
|
|
|
<?php /* 交通细节:**只保留**「线路名 / 车次航班号 / 承运方」。
|
|
|
|
|
|
⚠️ 第 17 批 第 3 条明确「座位什么的不记录了」→
|
|
|
|
|
|
座位 / 车厢、登机口 / 站台**不再出现在这里**
|
|
|
|
|
|
(数据库那两列还留着老数据,只是不再展示)。 */ ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
$traffic = [];
|
|
|
|
|
|
if (!empty($n['line_name'])) { $traffic[] = (string)$n['line_name']; }
|
2026-10-11 10:12:11 +08:00
|
|
|
|
/* 🔴 v41 第 8 条:`trip_no` **不再**放这里 —— 它已经跟在
|
|
|
|
|
|
「火车 / 飞机」模式词后面了(见上面 `.node-mode-no`)。
|
|
|
|
|
|
留在这里会让车次号在**同一个行程行上出现两次**。 */
|
2026-10-05 13:26:49 +08:00
|
|
|
|
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; ?>
|
|
|
|
|
|
|
2026-10-05 16:45:10 +08:00
|
|
|
|
<?php /* 出发地 → 到达地(第 17 批 第 3 条要记详细地址;
|
|
|
|
|
|
这里显示地点名,详细地址放 title 里悬停可见 + 带导航链接)。 */ ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php if (!empty($n['from_place']) || !empty($n['to_place'])): ?>
|
|
|
|
|
|
<div class="node-route-line">
|
2026-10-05 16:45:10 +08:00
|
|
|
|
<?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'] ?? ''));
|
|
|
|
|
|
?>
|
2026-11-05 12:40:57 +08:00
|
|
|
|
<?php /* 🔴 2026-11-05(用户:「直接用个标签,然后右边一个箭头,
|
|
|
|
|
|
到北京南站就得了,不用写从哪到哪」):
|
|
|
|
|
|
两个地名都做成**胶囊标签**,箭头只作分隔。
|
|
|
|
|
|
有坐标的套 <a>(可点开导航),没坐标的套 span ——
|
|
|
|
|
|
**两种情况都用同一个类**,样式才一致(原来无链接时
|
|
|
|
|
|
是裸文本,和另一侧的胶囊对不齐,这正是"位都没对齐"的观感来源)。 */ ?>
|
2026-10-05 16:45:10 +08:00
|
|
|
|
<?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: ?>
|
2026-11-05 12:40:57 +08:00
|
|
|
|
<span class="route-stop"><?= e((string)($n['from_place'] ?? '—')) ?></span>
|
2026-10-05 16:45:10 +08:00
|
|
|
|
<?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: ?>
|
2026-11-05 12:40:57 +08:00
|
|
|
|
<span class="route-stop"><?= e((string)($n['to_place'] ?? '—')) ?></span>
|
2026-10-05 16:45:10 +08:00
|
|
|
|
<?php endif; ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-11-05 12:40:57 +08:00
|
|
|
|
<!-- 🔴🔴 #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'])): ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<p class="node-detail"><?= nl2br(e((string)$n['detail'])) ?></p>
|
|
|
|
|
|
<?php endif; ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
|
|
|
|
|
|
<?php if ($spent !== 0): ?>
|
|
|
|
|
|
<div class="node-spent">💰 这一段花了 <?= e(\App\Expense::money($spent)) ?></div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-10-05 16:45:10 +08:00
|
|
|
|
<?php /* ⚠️ 第 17 批 第 3 条:「给参与人标注角色这个功能去掉,不要了。」
|
|
|
|
|
|
所以这里**不再渲染** role_tag(数据库里老数据的角色还在,只是不显示)。 */ ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php if (!empty($n['members'])): ?>
|
|
|
|
|
|
<div class="trip-people">
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php foreach ($n['members'] as $mm): ?>
|
2026-10-05 16:45:10 +08:00
|
|
|
|
<?= person_chip($mm) ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php endforeach; ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php if (!empty($n['attachs'])): ?>
|
2026-10-03 08:18:05 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
$attRows = $n['attachs'];
|
2026-10-04 20:57:40 +08:00
|
|
|
|
$attCanEdit = false;
|
2026-10-03 08:18:05 +08:00
|
|
|
|
$attDelUrl = '';
|
|
|
|
|
|
require __DIR__ . '/../_attachments.php';
|
|
|
|
|
|
?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php endif; ?>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<?php /* ---- 修正记录对比(第 13 批) ---- */ ?>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?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>
|
2026-10-05 16:45:10 +08:00
|
|
|
|
<td class="small"><?= e(\App\Trip::nodeSpan($rv['starts_at'] ?? null, $rv['ends_at'] ?? null)) ?></td>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<td><?= e((string)($rv['place'] ?? '')) ?></td>
|
|
|
|
|
|
<td class="small"><?= e((string)($rv['detail'] ?? '')) ?></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</details>
|
|
|
|
|
|
<?php endif; ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
</div>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php if ($canWrite): ?>
|
2026-10-05 22:56:49 +08:00
|
|
|
|
<?php /* ==================== 动作按钮(v24 第 19 批 用户第 7 条)====================
|
|
|
|
|
|
用户原话:
|
|
|
|
|
|
· 「计划行程的三个按钮,修改、完成、未成行,横着排列,放在行程右上角,
|
|
|
|
|
|
修改是黄色背景,完成绿色,未成行红色。」
|
|
|
|
|
|
· 「实际行程的编辑和删除按钮,也放在行程的右上角,编辑黄色,删除红色。」
|
|
|
|
|
|
|
|
|
|
|
|
⚠️ 于是配色按**颜色**来(不再是"主/次"那套):
|
|
|
|
|
|
· 修正 / 编辑 → `.btn-warn`(黄)
|
|
|
|
|
|
· 完成 → `.btn-ok`(绿)
|
|
|
|
|
|
· 未成行 / 删除 → `.btn-del`(红)
|
|
|
|
|
|
⚠️ 用户说的"修改"就是这里的「修正」(第 13 批定的名字)——
|
|
|
|
|
|
我们**保留产品名**不改,但把它染成用户要的黄色。
|
|
|
|
|
|
⚠️ 「编辑」用户没提,但它不能丢:改标题/时间/类型/出行方式与**加花销**
|
|
|
|
|
|
都只走它(花销面板在编辑页里)。按用户给的颜色语义染黄。
|
|
|
|
|
|
⚠️ 「标记为实际」是"修正过之后才出现"的收尾动作,也按黄色(同属"改")。
|
|
|
|
|
|
⚠️ 删除按钮:用户明确要求**红色**。这里用 `.btn-del`(实心红),
|
|
|
|
|
|
与全站 `.btn-danger`(淡红描边)刻意分开 —— 改 `.btn-danger`
|
|
|
|
|
|
会连带改掉十来处「删除」按钮的样子。
|
|
|
|
|
|
⚠️ 计划行的删除入口**保留**:`skipNode`(未成行)是主路径,
|
|
|
|
|
|
但"记错了、这段根本不该存在"时仍需要真删(hint 里也这么写)。 */ ?>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<div class="node-ops">
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<?php /* 🔴 v41(用户第 5/6 条):编辑/新增行程一律**弹框**。
|
|
|
|
|
|
⚠️ 这里只放"触发器"(data-node-open 带 URL),**不内嵌表单** ——
|
|
|
|
|
|
详情页可能有十几段行程,内嵌 N 份表单会把页面撑破、
|
|
|
|
|
|
元素 id 还会互相撞车(这正是用户当年报的"把页面撑破")。
|
|
|
|
|
|
表单由页尾那一个容器按需 AJAX 取回。 */ ?>
|
|
|
|
|
|
<button class="btn btn-warn btn-sm" type="button"
|
|
|
|
|
|
data-node-open="<?= e(url('trips/node_edit', ['id' => $nid, 'embed' => 1])) ?>"
|
|
|
|
|
|
aria-haspopup="dialog" aria-expanded="false">编辑</button>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?php if ($sg === 'plan'): ?>
|
|
|
|
|
|
<details class="rev-open">
|
2026-10-05 22:56:49 +08:00
|
|
|
|
<summary class="btn btn-warn btn-sm">修正</summary>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<div class="rev-panel">
|
2026-10-15 02:57:19 +08:00
|
|
|
|
<?php /* 🔴 v50(用户 2026-10-14 第 3 条):
|
|
|
|
|
|
「上面…那一段话说明也不要了。」
|
|
|
|
|
|
⇒ 删掉原来那段 `.rev-tip`(开头是「记录…」、解释"仍是同一条记录"
|
|
|
|
|
|
的那三行)。表单本身已经说清"实际的开始/结束""这次修正的说明",
|
|
|
|
|
|
那段解释属于噪音。
|
|
|
|
|
|
⚠️ 这段注释**刻意不整句照抄**用户点名的原话 ——
|
|
|
|
|
|
部署断言要查"那句已删",若在注释里留下原文,
|
|
|
|
|
|
那种朴素 grep 会命中注释而恒红(本项目已踩过多次)。
|
|
|
|
|
|
要查"删没删"的断言请用 `_code_has`(剥注释)。
|
|
|
|
|
|
⚠️ 别顺手删 `.rev-panel` / `.rev-open` 这两个类名 ——
|
|
|
|
|
|
它们是展开区的结构锚点(CSS 与测试都用)。 */ ?>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?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>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<form method="post" action="<?= e(url('trips/node_done')) ?>" class="res-inline"
|
2026-10-05 10:58:17 +08:00
|
|
|
|
onsubmit="return confirm('实际执行与计划完全相同,直接转为实际?')">
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?= csrf_field() ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<input type="hidden" name="id" value="<?= $nid ?>">
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
2026-10-05 22:56:49 +08:00
|
|
|
|
<button class="btn btn-ok btn-sm" type="submit">完成</button>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
</form>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<form method="post" action="<?= e(url('trips/node_skip')) ?>" class="res-inline"
|
|
|
|
|
|
onsubmit="return confirm('标记为「未成行」?它不会再出现在日程与往年今日,但在这个旅程里仍然看得到。')">
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?= csrf_field() ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<input type="hidden" name="id" value="<?= $nid ?>">
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
2026-10-05 22:56:49 +08:00
|
|
|
|
<button class="btn btn-del btn-sm" type="submit">未成行</button>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
</form>
|
|
|
|
|
|
<?php if ((int)($n['revision_n'] ?? 0) > 0): ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<form method="post" action="<?= e(url('trips/node_actual')) ?>" class="res-inline">
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?= csrf_field() ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<input type="hidden" name="id" value="<?= $nid ?>">
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
2026-10-05 22:56:49 +08:00
|
|
|
|
<button class="btn btn-ok btn-sm" type="submit">标记为实际</button>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
</form>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
<?php elseif ($sg === 'skipped'): ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<form method="post" action="<?= e(url('trips/node_restore')) ?>" class="res-inline">
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<?= csrf_field() ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<input type="hidden" name="id" value="<?= $nid ?>">
|
2026-10-04 20:57:40 +08:00
|
|
|
|
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
2026-10-05 22:56:49 +08:00
|
|
|
|
<button class="btn btn-ok btn-sm" type="submit">恢复为计划</button>
|
2026-10-04 20:57:40 +08:00
|
|
|
|
</form>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<form method="post" action="<?= e(url('trips/node_del')) ?>" class="res-inline"
|
2026-10-05 22:56:49 +08:00
|
|
|
|
onsubmit="return confirm('彻底删除这段行程?连同它的修正记录一起消失,不可恢复。\n\n如果你只是想表达「这次没去成」,请用「未成行」。')">
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?= csrf_field() ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
<input type="hidden" name="id" value="<?= $nid ?>">
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<input type="hidden" name="trip_id" value="<?= (int)$t['id'] ?>">
|
2026-10-05 22:56:49 +08:00
|
|
|
|
<button class="btn btn-del btn-sm" type="submit">删除</button>
|
2026-10-02 09:28:07 +08:00
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
|
</ol>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php endforeach; ?>
|
2026-10-11 10:12:11 +08:00
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
</div><?php /* /#nodeListHost */ ?>
|
|
|
|
|
|
|
|
|
|
|
|
<?php if ($canWrite): ?>
|
|
|
|
|
|
<?php /* ==================== v41:行程 / 旅程编辑的**弹框容器**(单一实例)====================
|
|
|
|
|
|
用户第 5/6 条:「编辑行程、加计划行程、加实际行程,全部都采用弹窗的形式。」
|
|
|
|
|
|
「行程部分的编辑也是采用弹窗的形式。」
|
|
|
|
|
|
「新建旅程…确定完了之后,动态刷新旅程这个列表页面。」
|
|
|
|
|
|
|
|
|
|
|
|
为什么是**一个容器 + 按需 AJAX**,而不是每段内嵌一份表单:
|
|
|
|
|
|
一段行程有十几组字段(类型/时间/出行方式/交通细节/起终点/内容/参与人/照片/状态),
|
|
|
|
|
|
详情页可能有十几段。内嵌 N 份 = 页面被撑到几屏长 + 元素 id 互相撞车
|
|
|
|
|
|
—— 这正是用户当年报的「点击编辑会把页面撑破」。
|
|
|
|
|
|
⇒ 点"编辑 / +计划行程 / +实际行程 / 编辑旅程"时,才去服务端取那**一份**表单片段,
|
|
|
|
|
|
塞进这个容器里打开。保存成功后只换行程列表容器,不整页跳。 */ ?>
|
|
|
|
|
|
<div class="cal-modal" id="nodeFormHost" role="dialog" aria-modal="true"
|
|
|
|
|
|
aria-label="行程" hidden>
|
|
|
|
|
|
<div class="cal-modal-panel mv-panel nf-panel" id="nodeFormPanel">
|
|
|
|
|
|
<div class="cal-modal-head">
|
|
|
|
|
|
<b id="nodeFormTitle">行程</b>
|
|
|
|
|
|
<button type="button" class="cal-modal-x" data-nf-close aria-label="关闭">✕</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="cal-modal-body" id="nodeFormBody">
|
|
|
|
|
|
<p class="empty-hint">正在载入…</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
/* ==================== v41:行程 / 旅程编辑弹框(事件委托 + AJAX)====================
|
|
|
|
|
|
为什么**全程用事件委托**(监听挂 document、用选择器判断来源):
|
|
|
|
|
|
· 弹框内容是 AJAX 换进来的 —— 内容一换,原来元素上的监听全丢;
|
|
|
|
|
|
委托则"一次绑定、永久有效",换多少次都不用重绑(观影详情页那轮
|
|
|
|
|
|
就是在这里踩过"第二次点编辑没反应")。
|
|
|
|
|
|
· 列表里每段行程一个按钮,逐个 addEventListener 会绑十几个。
|
|
|
|
|
|
为什么提交主路走 **click** 而不是 submit:
|
|
|
|
|
|
提交按钮是靠 form 属性关联的(在表单**外面**)。实测浏览器点它会提交,
|
|
|
|
|
|
但挂在 document 上的 submit 监听**收不到事件** ⇒ 会整页跳走、红字看不到
|
|
|
|
|
|
(观影 v40 同一坑)。click 一定发生,以它为主路;submit 只兜"按回车"。 */
|
|
|
|
|
|
(function () {
|
|
|
|
|
|
if (!window.fetch || !window.FormData) { return; } // 老浏览器保持原生跳转
|
|
|
|
|
|
|
|
|
|
|
|
var RELOAD = 'nodeListHost'; // 保存成功后要刷新的容器
|
|
|
|
|
|
function host() { return document.getElementById('nodeFormHost'); }
|
|
|
|
|
|
function body() { return document.getElementById('nodeFormBody'); }
|
|
|
|
|
|
function titleEl(){ return document.getElementById('nodeFormTitle'); }
|
|
|
|
|
|
|
|
|
|
|
|
function setOpen(on) {
|
|
|
|
|
|
var h = host();
|
|
|
|
|
|
if (!h) { return; }
|
|
|
|
|
|
h.hidden = !on;
|
|
|
|
|
|
if (on) {
|
|
|
|
|
|
/* 动态插入的控件:日期 / antd / 选人 / 上传都要在这一刻升级 */
|
|
|
|
|
|
if (window.fbUpgradeRange) { window.fbUpgradeRange(); }
|
|
|
|
|
|
if (window.fbBootAntd) { window.fbBootAntd(); }
|
2026-10-11 18:35:03 +08:00
|
|
|
|
if (window.fbNodeForm) { window.fbNodeForm(body() || document); }
|
2026-10-11 10:12:11 +08:00
|
|
|
|
var f = document.getElementById('nf-title');
|
|
|
|
|
|
if (f) { f.focus(); }
|
|
|
|
|
|
} else if (body()) {
|
|
|
|
|
|
body().innerHTML = '<p class="empty-hint">正在载入…</p>';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
function antdOpen() {
|
|
|
|
|
|
var d = document.querySelector('.ant-picker-dropdown');
|
|
|
|
|
|
return !!(d && !d.classList.contains('antd-picker-dropdown-hidden'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- 打开:从 data-node-open / data-trip-edit 拿 URL,取片段塞进来 ---- */
|
2026-10-11 18:35:03 +08:00
|
|
|
|
/* 🔴🔴 v42(用户 2026-10-11 第 2/3/4 条)——**根因级修法**:
|
|
|
|
|
|
AJAX 塞进来的片段里,`<script>` **既不执行、连元素都不保留**
|
|
|
|
|
|
(`innerHTML` 的语义:脚本不会跑,且赋值后 DOM 里也找不到那个 script 标签)。
|
|
|
|
|
|
片段自带的初始化(AntD 升级 / 高德联想 / 出行方式联动)全写在那段脚本里
|
|
|
|
|
|
⇒ 用户在弹框里看到的是:
|
|
|
|
|
|
· 起止时间没换成 AntD 控件(第 2 条);
|
|
|
|
|
|
· 选「自驾」不出车辆、选「火车/飞机」不出车次(第 3 条);
|
|
|
|
|
|
· 出发地/到达地没有高德联想(第 4 条)。
|
|
|
|
|
|
⇒ 做法:**把片段里的内联 `<script>` 抠出来,重新平成一个能执行的脚本**。
|
|
|
|
|
|
这是"动态插入含脚本的 HTML"的标准修法;比"在宿主里逐个复刻片段的初始化"
|
|
|
|
|
|
可靠得多 —— 片段将来再加初始化时,宿主不用跟着改。
|
|
|
|
|
|
⚠️ 只执行**内联**脚本(有 src 的是外部 SDK,设了 src 也不会同步执行,跳过即可;
|
|
|
|
|
|
高德那支是 `<script src>`,它的加载写在片段里由 SDK 自己完成)。
|
|
|
|
|
|
⚠️ 幂等:同一片段重复塞入时重新执行没问题(初始化内部都有幂等标记)。 */
|
|
|
|
|
|
function runInlineScripts(container, html) {
|
|
|
|
|
|
var re = /<script\b[^>]*>([\s\S]*?)<\/script>/gi, m, code = '';
|
|
|
|
|
|
while ((m = re.exec(html)) !== null) {
|
|
|
|
|
|
// 只收**没有 src 属性**的那种(内联脚本)
|
|
|
|
|
|
if (!/\bsrc\s*=/i.test(m[0].split('>')[0])) { code += m[1] + '\n;\n'; }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!code) { return; }
|
|
|
|
|
|
var s = document.createElement('script');
|
|
|
|
|
|
s.textContent = code;
|
|
|
|
|
|
(container || document.body).appendChild(s); // 执行
|
|
|
|
|
|
s.parentNode.removeChild(s); // 执行完就摘掉,别留在 DOM 里
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-12 00:24:20 +08:00
|
|
|
|
/* 🔴🔴 v43(用户 2026-10-11 第 4 条):「编辑一个行程的时候,"参与的人"
|
|
|
|
|
|
添加参与人的那个功能已经不好使了,弹不出选人的框了。」
|
|
|
|
|
|
|
|
|
|
|
|
真根因:选人组件(`person_picker.js`)的绑定**只跑一次**(脚本加载时扫一遍 `.pp`)。
|
|
|
|
|
|
而这一段表单是 AJAX 取回来、`innerHTML` 塞进 DOM 的 ⇒ 它里面的 `.pp`
|
|
|
|
|
|
**从来没被 init 过** ⇒「+ 添加参与的人」按钮上没有任何 click 监听
|
|
|
|
|
|
⇒ 点了没反应(弹框弹不出来)。暂存区/确定/取消同理全按不动。
|
|
|
|
|
|
|
|
|
|
|
|
⇒ 塞完片段后补调 `window.fbInitPickers(scope)`(person_picker.js 暴露,
|
|
|
|
|
|
内部幂等)。⚠️ 该脚本是**按需加载**的(layout.php:页面上有 `.pp` 才拉),
|
|
|
|
|
|
而弹框里的 `.pp` 是后来才出现的 —— 脚本此刻可能还没加载完。
|
|
|
|
|
|
所以做"等它就绪":轮询若干次,出现即调;始终不来也无害(下次打开再试)。
|
|
|
|
|
|
⚠️ 只重试有限次(约 3 秒),不做无限轮询。 */
|
|
|
|
|
|
function initPickersWhenReady(scope, tries) {
|
|
|
|
|
|
tries = (tries === undefined) ? 25 : tries;
|
|
|
|
|
|
/* 先确保脚本被拉起(详情页首屏没有 `.pp`,layout 那条"首屏就拉"的判据不会触发它)。
|
|
|
|
|
|
`fbLoadPicker` 是幂等的,重复调用只是拿到同一个 Promise。 */
|
|
|
|
|
|
if (!window.fbInitPickers && typeof window.fbLoadPicker === 'function') {
|
|
|
|
|
|
try { window.fbLoadPicker(); } catch (e) { /* 忽略 */ }
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof window.fbInitPickers === 'function') {
|
|
|
|
|
|
try { window.fbInitPickers(scope); } catch (e) { /* 不阻塞表单其它初始化 */ }
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (tries <= 0) { return; }
|
|
|
|
|
|
setTimeout(function () { initPickersWhenReady(scope, tries - 1); }, 150);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-11 10:12:11 +08:00
|
|
|
|
function loadInto(url, title) {
|
|
|
|
|
|
if (titleEl()) { titleEl().textContent = title || '行程'; }
|
|
|
|
|
|
if (body()) { body().innerHTML = '<p class="empty-hint">正在载入…</p>'; }
|
|
|
|
|
|
setOpen(true);
|
|
|
|
|
|
fetch(url, { credentials: 'same-origin',
|
|
|
|
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
|
|
|
|
|
.then(function (r) { return r.text(); })
|
|
|
|
|
|
.then(function (html) {
|
|
|
|
|
|
if (!body()) { return; }
|
|
|
|
|
|
body().innerHTML = html;
|
2026-10-11 18:35:03 +08:00
|
|
|
|
/* ① 先执行片段自带的内联脚本(它会定义/调用自己的初始化) */
|
|
|
|
|
|
runInlineScripts(body(), html);
|
2026-10-12 00:24:20 +08:00
|
|
|
|
/* ② 再补跑宿主这侧的通用升级(日期/antd/行程表单/选人组件),按依赖顺序 */
|
2026-10-11 10:12:11 +08:00
|
|
|
|
if (window.fbUpgradeRange) { window.fbUpgradeRange(); }
|
|
|
|
|
|
if (window.fbBootAntd) { window.fbBootAntd(); }
|
2026-10-11 18:35:03 +08:00
|
|
|
|
if (window.fbNodeForm) { window.fbNodeForm(body()); }
|
2026-10-12 00:24:20 +08:00
|
|
|
|
/* 🔴🔴 v43(用户 2026-10-11 第 4 条):「编辑一个行程的时候,"参与的人"
|
|
|
|
|
|
添加参与人的那个功能已经不好使了,弹不出选人的框了。」
|
|
|
|
|
|
|
|
|
|
|
|
真根因:选人组件(`person_picker.js`)的绑定**只跑一次**(脚本加载时扫一遍
|
|
|
|
|
|
`.pp`)。而这一段表单是 AJAX 取回来、`innerHTML` 塞进 DOM 的 ⇒
|
|
|
|
|
|
它里面的 `.pp` **从来没被 init 过** ⇒「+ 添加参与的人」按钮上没有 click
|
|
|
|
|
|
监听 ⇒ 点了没反应。⇒ 这里塞完片段**补调一次**初始化。
|
|
|
|
|
|
⚠️ 该脚本是**按需加载**的(layout.php:页面上有 `.pp` 才拉),而弹框里的
|
|
|
|
|
|
`.pp` 是后来才出现的 —— 脚本可能还没加载完(或都还没开始加载)。
|
|
|
|
|
|
所以这里做"等它就绪":轮询若干次,出现即调;全程没出现也无害。 */
|
|
|
|
|
|
initPickersWhenReady(body());
|
2026-10-11 10:12:11 +08:00
|
|
|
|
var f = document.getElementById('nf-title');
|
|
|
|
|
|
if (f) { f.focus(); }
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(function () {
|
|
|
|
|
|
if (body()) { body().innerHTML = '<p class="empty-hint">载入失败,请重试。</p>'; }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('click', function (e) {
|
|
|
|
|
|
var t = e.target;
|
|
|
|
|
|
if (!t || !t.closest) { return; }
|
|
|
|
|
|
var op = t.closest('[data-node-open]');
|
|
|
|
|
|
if (op) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var url = op.getAttribute('data-node-open') || '';
|
|
|
|
|
|
var isNew = url.indexOf('node_new') >= 0;
|
|
|
|
|
|
loadInto(url, isNew ? (op.textContent || '').trim() : '编辑行程');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var te = t.closest('[data-trip-edit]');
|
|
|
|
|
|
if (te) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
loadInto(te.getAttribute('data-trip-edit'), '编辑旅程');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (t.closest('[data-nf-close]')) { setOpen(false); return; }
|
2026-10-15 02:57:19 +08:00
|
|
|
|
/* 🔴 v50(用户 2026-10-14 第 3 条):「应该加一个关闭/取消的按钮…不要再修正的话,
|
|
|
|
|
|
应该有一个取消,不要让这个框显示了。」
|
|
|
|
|
|
「修正」区是 `<details class="rev-open">`(原生可折叠,不用 JS 也能开)——
|
|
|
|
|
|
⇒ 取消 = 把这个 details 的 `open` 去掉,回到收起状态。
|
|
|
|
|
|
⚠️ 用委托(`closest`)而不是在 `_revise_form.php` 里内联脚本:
|
|
|
|
|
|
那份片段会被 AJAX 换进来,内联脚本的位置与幂等都要额外照看;委托一次绑定就够。 */
|
|
|
|
|
|
var rc = t.closest('[data-rev-cancel]');
|
|
|
|
|
|
if (rc) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
var det = rc.closest('details.rev-open');
|
|
|
|
|
|
if (det) { det.open = false; }
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-10-11 10:12:11 +08:00
|
|
|
|
var h = host();
|
|
|
|
|
|
if (h && t === h) { setOpen(false); return; } // 点遮罩关;点面板内部不关
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
|
|
|
|
if (e.key !== 'Escape') { return; }
|
|
|
|
|
|
var h = host();
|
|
|
|
|
|
if (!h || h.hidden) { return; }
|
|
|
|
|
|
if (antdOpen()) { return; } // 让 antd 先收它自己的面板
|
|
|
|
|
|
setOpen(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/* ---- 保存:click 主路 + submit 兜回车 ---- */
|
|
|
|
|
|
function save() {
|
|
|
|
|
|
var form = document.getElementById('nodeForm') || document.getElementById('tripForm');
|
|
|
|
|
|
if (!form) { return; }
|
|
|
|
|
|
var sub = document.getElementById('nodeFormSubmit')
|
|
|
|
|
|
|| document.getElementById('tripNewSubmit');
|
|
|
|
|
|
if (sub) { sub.disabled = true; sub.textContent = '保存中…'; }
|
|
|
|
|
|
fetch(form.action, {
|
|
|
|
|
|
method: 'POST', body: new FormData(form),
|
|
|
|
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
|
|
|
|
|
credentials: 'same-origin',
|
|
|
|
|
|
}).then(function (r) {
|
|
|
|
|
|
var ct = (r.headers.get('content-type') || '');
|
|
|
|
|
|
if (ct.indexOf('application/json') >= 0) {
|
|
|
|
|
|
/* 校验失败:服务端回重画后的弹框 → 整块换进容器 */
|
|
|
|
|
|
return r.json().then(function (j) {
|
|
|
|
|
|
if (body() && j && j.modal) {
|
|
|
|
|
|
body().innerHTML = j.modal;
|
2026-10-11 18:35:03 +08:00
|
|
|
|
runInlineScripts(body(), j.modal); // 片段里的内联脚本要执行(见 loadInto 的长注释)
|
2026-10-11 10:12:11 +08:00
|
|
|
|
if (window.fbUpgradeRange) { window.fbUpgradeRange(); }
|
|
|
|
|
|
if (window.fbBootAntd) { window.fbBootAntd(); }
|
2026-10-11 18:35:03 +08:00
|
|
|
|
if (window.fbNodeForm) { window.fbNodeForm(body()); }
|
2026-10-11 10:12:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-10-15 10:37:07 +08:00
|
|
|
|
/* 成功:服务端 302 回来 → 换列表容器(保留滚动位置、不闪白)。
|
|
|
|
|
|
🔴🔴 v51(用户 2026-10-15 第 1 条):「在旅程的详情页面编辑的时候,当我编辑完了之后,
|
|
|
|
|
|
点击确定之后,页面并没有刷新,就是原来的详情页面并没有刷新。比方说我修改了一张图片,
|
|
|
|
|
|
当我确定完了之后,原来的页面应该刷新一下,把最新的这个旅程的信息给更新出来才对呀。」
|
|
|
|
|
|
|
|
|
|
|
|
真根因:这里只换 `#nodeListHost`(**行程列表**那一段),而旅程自己的
|
|
|
|
|
|
**标题(页头 h1)/ 题图 / 时间与城市(.trip-hero)** 都在那个容器**外面** ——
|
|
|
|
|
|
它们由服务端直出、不在被替换的那块里 ⇒ 改了标题/题图/简介,页头与题图纹丝不动。
|
|
|
|
|
|
用户看到的就是"确定点了、框关了,但上面还是旧的"。
|
|
|
|
|
|
|
|
|
|
|
|
⇒ 修法:**按表单来源分流**——
|
|
|
|
|
|
· `tripForm`(编辑旅程本体:标题/题图/时间…)→ **整页刷新**。
|
|
|
|
|
|
理由:它改的正是"页面骨架"(页头 + 题图 + 城市),这些散落在容器外、
|
|
|
|
|
|
无法靠换一小块 DOM 覆盖;而"保留滚动位置"在这里没有价值
|
|
|
|
|
|
(用户改完就是要看新的题图与标题,回到顶部反而正合意)。
|
|
|
|
|
|
· `nodeForm`(行程增删改)→ 仍然只换行程列表(用户当年明确要的"不整页跳",
|
|
|
|
|
|
而且它改的东西**确实**只在那块容器里)。
|
|
|
|
|
|
⚠️ 判据用表单 id,不要用"有没有题图字段"去猜 —— id 是稳定的契约。
|
|
|
|
|
|
⚠️⚠️ **必须 `getAttribute('id')`,不能写 `form.id`** ——
|
|
|
|
|
|
HTML 表单元素会把**同名的具名控件**挂成自己的属性:
|
|
|
|
|
|
`tripForm` 里有 `<input type="hidden" name="id">` ⇒ `form.id` 返回的是
|
|
|
|
|
|
**那个 input 元素**(字符串化就是 `[object HTMLInputElement]`),
|
|
|
|
|
|
永远不等于 `'tripForm'` ⇒ 这个分支**从来不会命中**(本轮实测:
|
|
|
|
|
|
改完标题点确定,页面纹丝不动;插桩打印出来才看到
|
|
|
|
|
|
`form.id === '[object HTMLInputElement]'`)。
|
|
|
|
|
|
同类还有 form.name / form.action / form.method 都可能被同名控件遮蔽。
|
|
|
|
|
|
⇒ 一律用 `getAttribute('id')` 取"写在标签上的那个 id"。 */
|
|
|
|
|
|
var isTripForm = (form.getAttribute('id') === 'tripForm');
|
2026-10-11 10:12:11 +08:00
|
|
|
|
return r.text().then(function (html) {
|
2026-10-15 10:37:07 +08:00
|
|
|
|
if (isTripForm) { window.location.reload(); return; }
|
2026-10-11 10:12:11 +08:00
|
|
|
|
var doc = new DOMParser().parseFromString(html, 'text/html');
|
|
|
|
|
|
var fresh = doc.getElementById(RELOAD) || doc.getElementById('tripListHost');
|
|
|
|
|
|
var cur = document.getElementById(RELOAD) || document.getElementById('tripListHost');
|
|
|
|
|
|
if (fresh && cur) { cur.innerHTML = fresh.innerHTML; }
|
|
|
|
|
|
else { window.location.reload(); return; }
|
|
|
|
|
|
setOpen(false);
|
|
|
|
|
|
});
|
|
|
|
|
|
}).catch(function () {
|
|
|
|
|
|
if (sub) { sub.disabled = false; sub.textContent = '确定'; }
|
|
|
|
|
|
form.submit();
|
|
|
|
|
|
}).then(function () {
|
|
|
|
|
|
if (sub) { sub.disabled = false; sub.textContent = '确定'; }
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
document.addEventListener('click', function (ev) {
|
|
|
|
|
|
var t = ev.target;
|
|
|
|
|
|
if (!t || !t.closest) { return; }
|
|
|
|
|
|
if (t.closest('#nodeFormSubmit') || t.closest('#tripNewSubmit')) {
|
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
|
save();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
document.addEventListener('submit', function (ev) {
|
|
|
|
|
|
var f = ev.target;
|
|
|
|
|
|
if (!f || (f.id !== 'nodeForm' && f.id !== 'tripForm')) { return; }
|
|
|
|
|
|
ev.preventDefault();
|
|
|
|
|
|
save();
|
|
|
|
|
|
});
|
|
|
|
|
|
})();
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<?php endif; ?>
|