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:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user