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:
2026-10-05 13:26:49 +08:00
parent 7138314dc7
commit 2e51d3f5e4
16 changed files with 2265 additions and 456 deletions
+248 -38
View File
@@ -24,6 +24,7 @@ use App\ContactImporter;
use App\Crypto;
use App\Database;
use App\Douban;
use App\Expense;
use App\Family;
use App\Geo;
use App\Home;
@@ -36,6 +37,7 @@ use App\Movie;
use App\MovieReview;
use App\Module;
use App\MovieStats;
use App\NodeType;
use App\Nickname;
use App\Person;
use App\PersonGroup;
@@ -53,6 +55,7 @@ use App\Task;
use App\Trip;
use App\Upload;
use App\UserSetting;
use App\Vehicle;
use App\View;
if (!Database::hasTables()) {
@@ -2764,7 +2767,7 @@ switch ($r) {
case 'trips/new':
Auth::requireWrite();
View::render('trips/form', [
'title' => '记一次活动',
'title' => '新建旅程',
't' => null,
// 新建时默认勾上「我自己」—— 一趟旅程里几乎总有我,少点一下
'presetAttendees' => [(int)Auth::personId()],
@@ -2778,7 +2781,7 @@ switch ($r) {
$familyId = (int)Auth::familyId();
$t = Trip::find($familyId, (int)($_GET['id'] ?? 0), (int)Auth::personId());
if (!$t) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
View::render('trips/form', [
@@ -2798,7 +2801,7 @@ switch ($r) {
$id = $isEdit ? (int)($_POST['id'] ?? 0) : 0;
$t = $isEdit ? Trip::find($familyId, $id) : null;
if ($isEdit && !$t) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
$d = [
@@ -2820,14 +2823,19 @@ switch ($r) {
// ③ 勾选 → '1'。
// 之前写成 `?? ''` 会把 ① 也变成"键存在且为空" → 被当成不公开,
// 于是数据在列表里**静默消失**(实测:新建的旅程从列表与日历上都没了)。
'attendees' => filter_family_person_ids((array)($_POST['attendees'] ?? []), $familyId),
// ⚠️ v24 第 16 批 用户第 10 条:建/编辑旅程的表单里**没有**同行的人了
// ("这些都是从行程里面总结出来的")。所以这里**不再收** attendees。
// ⚠️ 但**不能**就此把活动上那份名单清空:老数据里很多人的参与人
// 只登记在这一层(那时还没有行程级参与人),清掉就"人没了"。
// 所以"键不存在"时保持原样(Trip::saveAttendees 收到空数组会全删,
// 这里刻意不传这个键)。新数据的参与人一律从行程里长出来。
];
if (array_key_exists('is_public', $_POST)) {
$d['is_public'] = (string)$_POST['is_public'];
}
$errors = [];
if ($d['title'] === '') {
$errors['title'] = '活动名字不能为空';
$errors['title'] = '旅程名字不能为空';
}
// 「回来不能早于出发」的校验一并删掉:时间不再由这里填,
// 而是从各段旅程算 max/min —— 二者反了也不可能(min ≤ max 恒成立)。
@@ -2851,7 +2859,7 @@ switch ($r) {
if ($errors) {
set_old($_POST);
View::render('trips/form', [
'title' => $isEdit ? '编辑活动' : '记一次活动',
'title' => $isEdit ? '编辑旅程' : '新建旅程',
't' => $isEdit ? array_merge($t, $d) : null,
'people' => Person::roster($familyId, (int)Auth::personId(), 'member', ''),
'errors' => $errors,
@@ -2866,7 +2874,7 @@ switch ($r) {
}
$newId = Trip::create($familyId, (int)Auth::personId(), $d);
clear_old();
flash('ok', '这次活动记下了。' . ($warns ? '(' . implode(';', $warns) . ')' : ''));
flash('ok', '这个旅程记下了。' . ($warns ? '(' . implode(';', $warns) . ')' : ''));
redirect('trips/show', ['id' => $newId]);
break;
@@ -2875,29 +2883,214 @@ switch ($r) {
$meId = (int)Auth::personId();
$t = Trip::find($familyId, (int)($_GET['id'] ?? 0), $meId);
if (!$t) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
View::render('trips/show', [
'title' => (string)$t['title'],
't' => $t,
'photos' => $t['photos'],
'nodes' => Trip::nodes((int)$t['id'], $meId),
'people' => Person::roster($familyId, $meId, 'member', ''),
// 用户第 2 条:题图右侧那五项统计(参与人/城市/天数/段数/花销)
'sm' => Trip::summary((int)$t['id'], $meId),
], 'trips');
break;
// ---------------- 行程:新增 / 编辑(独立页面,v24 第 16 批 第 5/6/7 条)----------------
//
// ⚠️ 用户第 5 条:「行程的编辑功能,点击后会把页面撑破。编辑行程,打开新的页面进行编辑。」
// 以前用的是详情页里的 <details> 内联表单 —— 每个行程一份,
// TripLog 那种十几组字段的表单内联一开就把页面撑到几屏长,
// 而且 N 份表单的元素 id 互相撞、密码管理器逐个扫描还会卡。
//
// 表单字段照 TripLog 的 nodes/form.php(用户第 7 条)。
case 'trips/node_new':
case 'trips/node_edit':
Auth::requireWrite();
$familyId = (int)Auth::familyId();
$isEdit = $r === 'trips/node_edit';
$t = null;
$nd = null;
$stage = 'plan';
if ($isEdit) {
$nd = Trip::node((int)($_GET['id'] ?? 0));
if (!$nd) {
flash('err', '找不到这段行程。');
redirect('trips');
}
// ⚠️ 必须校验这段行程确实属于**本家的**某个旅程:
// 否则知道 id 就能改别人家的行程(走的还是 Auth::requireWrite)
$t = Trip::find($familyId, (int)$nd['event_id'], (int)Auth::personId());
if (!$t) {
flash('err', '找不到这段行程。');
redirect('trips');
}
$stage = (string)($nd['stage'] ?? 'plan');
} else {
$t = Trip::find($familyId, (int)($_GET['id'] ?? 0), (int)Auth::personId());
if (!$t) {
flash('err', '找不到这个旅程。');
redirect('trips');
}
$stage = (string)($_GET['stage'] ?? 'plan');
if (!isset(Trip::NODE_STAGES[$stage]) || $stage === 'skipped') {
$stage = 'plan'; // "未成行"是标记出来的,不是新建出来的
}
}
View::render('trips/node_form', [
'title' => ($isEdit ? '编辑行程' : '添加行程') . ' · ' . (string)$t['title'],
'ev' => $t,
'n' => $nd,
'stage' => $stage,
'people' => Person::roster($familyId, (int)Auth::personId(), 'member', ''),
'types' => NodeType::all($familyId),
'vehicles' => Vehicle::pickable($familyId),
'expenses' => $nd ? Expense::ofNode((int)$nd['id']) : [],
'errors' => [],
], 'trips');
break;
// ---------------- 花销(行程级,v24 第 16 批 第 2 条)----------------
case 'trips/expense_add':
$familyId = (int)Auth::familyId();
Auth::requireWrite();
csrf_check();
$nodeId = (int)($_POST['node_id'] ?? 0);
// 校验这段行程确实属于本家(顺着 nodes → events 查一遍)
$st = Database::get()->prepare(
"SELECT n.id FROM trip_nodes n JOIN events e ON e.id = n.event_id
WHERE n.id = ? AND n.family_id = ? AND e.family_id = ?"
);
$st->execute([$nodeId, $familyId, $familyId]);
if ($st->fetchColumn() === false) {
flash('err', '找不到这段行程。');
redirect('trips');
}
$res = Expense::add($familyId, $nodeId, [
'amount' => (string)($_POST['amount'] ?? ''),
'category' => (string)($_POST['category'] ?? ''),
'title' => (string)($_POST['title'] ?? ''),
'person_id' => (int)($_POST['person_id'] ?? 0),
]);
flash($res['ok'] ? 'ok' : 'err', $res['ok'] ? '花销记下了。' : (string)$res['error']);
redirect('trips/node_edit', ['id' => $nodeId]);
break;
case 'trips/expense_del':
$familyId = (int)Auth::familyId();
Auth::requireWrite();
csrf_check();
$nodeId = (int)($_POST['node_id'] ?? 0);
Expense::destroy($familyId, (int)($_POST['expense_id'] ?? 0));
flash('ok', '这笔花销删掉了。');
redirect('trips/node_edit', ['id' => $nodeId]);
break;
// ---------------- 旅程设置:车辆 / 出行方式 / 节点类型(用户第 8 条)----------------
case 'trips/vehicles':
$familyId = (int)Auth::familyId();
View::render('trips/vehicles', [
'title' => '车辆管理',
'ledger' => Vehicle::fromLedger($familyId),
'mine' => Vehicle::all($familyId),
], 'trips');
break;
case 'trips/vehicles_save':
$familyId = (int)Auth::familyId();
Auth::requireWrite();
csrf_check();
$id = (int)($_POST['id'] ?? 0);
$res = Vehicle::save($familyId, $_POST, $id > 0 ? $id : null);
flash($res['ok'] ? 'ok' : 'err', $res['ok'] ? '车辆已保存。' : (string)$res['error']);
redirect('trips/vehicles');
break;
case 'trips/vehicles_del':
$familyId = (int)Auth::familyId();
Auth::requireWrite();
csrf_check();
$res = Vehicle::destroy($familyId, (int)($_POST['id'] ?? 0));
flash($res['ok'] ? 'ok' : 'err', $res['ok'] ? '车辆已删除。' : (string)$res['error']);
redirect('trips/vehicles');
break;
case 'trips/modes':
$familyId = (int)Auth::familyId();
// "常用信息"的记忆:每种出行方式下用户常填的值(不是新增交通方式)
View::render('trips/modes', [
'title' => '出行方式',
'modes' => Trip::TRAVEL_MODES,
'memo' => json_decode((string)Setting::get($familyId, 'trip_mode_memo', '{}'), true) ?: [],
], 'trips');
break;
case 'trips/modes_save':
$familyId = (int)Auth::familyId();
Auth::requireWrite();
csrf_check();
$raw = (array)($_POST['memo'] ?? []);
$out = [];
foreach ($raw as $k => $v) {
// 只认确实存在的出行方式,值限长 —— 表单可以改,不能凭它往设置里塞任意东西
if (!isset(Trip::TRAVEL_MODES[(string)$k])) {
continue;
}
$v = mb_substr(trim((string)$v), 0, 60, 'UTF-8');
if ($v !== '') {
$out[(string)$k] = $v;
}
}
Setting::set($familyId, 'trip_mode_memo', json_encode($out, JSON_UNESCAPED_UNICODE));
flash('ok', '出行方式设置已保存。');
redirect('trips/modes');
break;
case 'trips/types':
$familyId = (int)Auth::familyId();
$types = NodeType::all($familyId);
foreach ($types as &$ty) {
$ty['used'] = NodeType::usedCount($familyId, (string)$ty['name']);
}
unset($ty);
View::render('trips/types', [
'title' => '节点类型',
'types' => $types,
], 'trips');
break;
case 'trips/types_save':
$familyId = (int)Auth::familyId();
Auth::requireWrite();
csrf_check();
$id = (int)($_POST['id'] ?? 0);
$res = NodeType::save($familyId, $_POST, $id > 0 ? $id : null);
flash($res['ok'] ? 'ok' : 'err', $res['ok'] ? '类型已保存。' : (string)$res['error']);
redirect('trips/types');
break;
case 'trips/types_del':
$familyId = (int)Auth::familyId();
Auth::requireWrite();
csrf_check();
$res = NodeType::destroy($familyId, (int)($_POST['id'] ?? 0));
flash($res['ok'] ? 'ok' : 'err', $res['ok'] ? '类型已删除。' : (string)$res['error']);
redirect('trips/types');
break;
case 'trips/destroy':
$familyId = (int)Auth::familyId();
Auth::requireWrite();
csrf_check();
$id = (int)($_POST['id'] ?? 0);
if (!Trip::find($familyId, $id)) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
Trip::destroy($familyId, $id);
flash('ok', '这次活动已删除。');
flash('ok', '这个旅程已删除。');
redirect('trips');
break;
@@ -2907,7 +3100,7 @@ switch ($r) {
csrf_check();
$id = (int)($_POST['id'] ?? 0);
if (!Trip::find($familyId, $id)) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
$warns = attach_save_files($familyId, 'trip', $id, 'photo', (int)Auth::personId());
@@ -2925,7 +3118,7 @@ switch ($r) {
csrf_check();
$id = (int)($_POST['id'] ?? 0);
if (!Trip::find($familyId, $id)) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
\App\Attachment::remove((int)($_POST['photo_id'] ?? 0));
@@ -2943,34 +3136,51 @@ switch ($r) {
$tripId = (int)($_POST['trip_id'] ?? 0);
$t = Trip::find($familyId, $tripId);
if (!$t) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
$isEdit = $r === 'trips/node_update';
$nid = $isEdit ? (int)($_POST['id'] ?? 0) : 0;
// v24 第 16 批:表单照 TripLog 的节点,字段全量(用户第 7 条)。
// 参与人的角色标签走 roles[person_id] => 角色(TripLog 的 role_tag)。
$roles = [];
foreach ((array)($_POST['roles'] ?? []) as $pid => $role) {
$roles[(int)$pid] = (string)$role;
}
$d = [
'title' => trim((string)($_POST['title'] ?? '')),
'detail' => trim((string)($_POST['detail'] ?? '')),
'place' => trim((string)($_POST['place'] ?? '')),
'starts_at' => (string)(read_dt_input('starts_at') ?? ''),
'ends_at' => (string)(read_dt_input('ends_at') ?? ''),
'node_type' => (string)($_POST['node_type'] ?? 'other'),
'status' => (string)($_POST['status'] ?? 'planned'),
'category' => (string)($_POST['category'] ?? 'plan'),
// v24 第 13 批:新建时用户在表单里选的「计划 / 实际」。
// 编辑时表单带的是隐藏 stage(保持原状态),也一并读。
'stage' => (string)($_POST['stage_pick'] ?? $_POST['stage'] ?? 'plan'),
'members' => filter_family_person_ids((array)($_POST['members'] ?? []), $familyId),
'title' => trim((string)($_POST['title'] ?? '')),
'detail' => trim((string)($_POST['detail'] ?? '')),
'place' => trim((string)($_POST['place'] ?? '')),
'starts_at' => (string)(read_dt_input('starts_at') ?? ''),
'ends_at' => (string)(read_dt_input('ends_at') ?? ''),
'node_type' => (string)($_POST['node_type'] ?? 'other'),
'status' => (string)($_POST['status'] ?? 'planned'),
'category' => (string)($_POST['category'] ?? 'plan'),
// 新建时用户在表单里选的「计划 / 实际」;编辑时表单带的是隐藏 stage
'stage' => (string)($_POST['stage_pick'] ?? $_POST['stage'] ?? 'plan'),
'members' => filter_family_person_ids((array)($_POST['members'] ?? []), $familyId),
'roles' => $roles,
// ---- v21:交通与起终点(TripLog 同款)----
'travel_mode' => (string)($_POST['travel_mode'] ?? ''),
'vehicle_id' => (int)($_POST['vehicle_id'] ?? 0),
'line_name' => trim((string)($_POST['line_name'] ?? '')),
'carrier' => trim((string)($_POST['carrier'] ?? '')),
'trip_no' => trim((string)($_POST['trip_no'] ?? '')),
'seat_info' => trim((string)($_POST['seat_info'] ?? '')),
'gate' => trim((string)($_POST['gate'] ?? '')),
'from_place' => trim((string)($_POST['from_place'] ?? '')),
'to_place' => trim((string)($_POST['to_place'] ?? '')),
];
if ($d['title'] === '') {
flash('err', '这一段得有个名字。');
flash('err', '这段行程得有个名字。');
redirect('trips/show', ['id' => $tripId]);
}
$newId = Trip::saveNode($familyId, $tripId, $d, $isEdit ? $nid : null);
$warns = attach_save_files($familyId, 'trip_node', $newId, 'attach', (int)Auth::personId());
flash('ok', $isEdit ? '这一段已保存。' : '已加一段。'
$warns = attach_save_files($familyId, 'trip_node', $newId, 'photo', (int)Auth::personId());
flash('ok', ($isEdit ? '这段行程已保存。' : '行程已加进来。')
. ($warns ? '(' . implode(';', $warns) . ')' : ''));
redirect('trips/show', ['id' => $tripId]);
// ⚠️ 用户第 5 条:编辑是**独立页面**,保存后回详情页(而不是留在表单上)
redirect('trips/show', ['id' => $tripId], 'node-' . $newId);
break;
case 'trips/node_del':
@@ -2979,7 +3189,7 @@ switch ($r) {
csrf_check();
$tripId = (int)($_POST['trip_id'] ?? 0);
if (!Trip::find($familyId, $tripId)) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
Trip::deleteNode((int)($_POST['id'] ?? 0));
@@ -3002,7 +3212,7 @@ switch ($r) {
csrf_check();
$tripId = (int)($_POST['trip_id'] ?? 0);
if (!Trip::find($familyId, $tripId)) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
$nid = (int)($_POST['id'] ?? 0);
@@ -3025,7 +3235,7 @@ switch ($r) {
csrf_check();
$tripId = (int)($_POST['trip_id'] ?? 0);
if (!Trip::find($familyId, $tripId)) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
Trip::markActual((int)($_POST['id'] ?? 0));
@@ -3040,7 +3250,7 @@ switch ($r) {
csrf_check();
$tripId = (int)($_POST['trip_id'] ?? 0);
if (!Trip::find($familyId, $tripId)) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
Trip::completeNode((int)($_POST['id'] ?? 0));
@@ -3055,11 +3265,11 @@ switch ($r) {
csrf_check();
$tripId = (int)($_POST['trip_id'] ?? 0);
if (!Trip::find($familyId, $tripId)) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
Trip::skipNode((int)($_POST['id'] ?? 0));
flash('ok', '已标记为未成行。它不再出现在日程与往年今日,但在这段旅程里仍然看得到。');
flash('ok', '已标记为未成行。它不再出现在日程与往年今日,但在这个旅程里仍然看得到。');
redirect('trips/show', ['id' => $tripId]);
break;
@@ -3070,7 +3280,7 @@ switch ($r) {
csrf_check();
$tripId = (int)($_POST['trip_id'] ?? 0);
if (!Trip::find($familyId, $tripId)) {
flash('err', '找不到这次活动。');
flash('err', '找不到这个旅程。');
redirect('trips');
}
Trip::restoreNode((int)($_POST['id'] ?? 0));