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-10-05 13:26:49 +08:00
|
|
|
|
$groupMeta = [
|
|
|
|
|
|
'plan' => ['计划的行程', '还没走 —— 可以修正 / 完成 / 标记未成行'],
|
|
|
|
|
|
'actual' => ['实际行程', '已走过 —— 也出现在日历与首页「往年今日」'],
|
|
|
|
|
|
'skipped' => ['未成行', '这次没去成;不上日程,但在这里留着可查'],
|
|
|
|
|
|
];
|
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>
|
|
|
|
|
|
<?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: ?>
|
|
|
|
|
|
<?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>
|
|
|
|
|
|
|
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 ?>">
|
|
|
|
|
|
<span class="node-ico" title="<?= e(\App\Trip::typeWord((string)$n['node_type'], $fid)) ?>">
|
|
|
|
|
|
<?= e(\App\Trip::typeIcon((string)$n['node_type'], $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">
|
|
|
|
|
|
<?= e($span) ?>
|
|
|
|
|
|
<?php if ($sg === 'actual'): ?><span class="actual-tag">实际</span><?php endif; ?>
|
|
|
|
|
|
</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'] ?? ''));
|
|
|
|
|
|
?>
|
|
|
|
|
|
<?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; ?>
|
2026-10-05 13:26:49 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
|
2026-10-02 09:28:07 +08:00
|
|
|
|
<?php if (!empty($n['detail'])): ?>
|
|
|
|
|
|
<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">
|
|
|
|
|
|
<p class="rev-tip">
|
2026-10-05 10:58:17 +08:00
|
|
|
|
记录实际执行情况。这仍与上面那条计划是<b>同一条记录</b>,
|
2026-10-04 20:57:40 +08:00
|
|
|
|
原来的计划数据会保留在「修正记录」里,方便日后对比。
|
2026-10-05 22:56:49 +08:00
|
|
|
|
点完这里<b>这段行程仍是计划</b>;确认无误后再点「完成」。
|
2026-10-04 20:57:40 +08:00
|
|
|
|
</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>
|
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(); }
|
|
|
|
|
|
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,取片段塞进来 ---- */
|
|
|
|
|
|
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;
|
|
|
|
|
|
if (window.fbUpgradeRange) { window.fbUpgradeRange(); }
|
|
|
|
|
|
if (window.fbBootAntd) { window.fbBootAntd(); }
|
|
|
|
|
|
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; }
|
|
|
|
|
|
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;
|
|
|
|
|
|
if (window.fbUpgradeRange) { window.fbUpgradeRange(); }
|
|
|
|
|
|
if (window.fbBootAntd) { window.fbBootAntd(); }
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
/* 成功:服务端 302 回来 → 只换列表容器(保留滚动位置、不闪白) */
|
|
|
|
|
|
return r.text().then(function (html) {
|
|
|
|
|
|
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; ?>
|