Files
FamilyBook/templates/trips/_node_form.php
T
laoyang 7138314dc7 v24 第 15 批:活动 / 旅程两级语义(术语 · 派生时间城市 · 表单三项 · 题图可点)
用户原话四条,逐条落地:

1. 「旅程模块,按钮是记一次活动,而不是记一趟旅程。旅程是活动里面的概念。」
   → 顶层一律改称**活动**:按钮「记一次活动」、页标题「活动」、`N 次` / `N 段旅程`;
     活动详情三组改叫「计划的旅程 / 走过的旅程 / 没成行的」;
     节点表单「这段旅程叫什么」「阶段」;flash 文案 17 处同步。
     (导航条仍叫「旅程」——用户就是这么称呼这个模块,列表页加副标题说清两级关系。)

2. 「活动不需要开始和结束时间,是根据活动中的旅程,最早的旅程开始时间,和最晚的
     旅程结束时间,计算出来的。目的地也不需要…去的城市,也是根据旅程,得到的。」
   → 活动**不再存**这三样,全部**读时算出来**:
     · Trip::derived()      最早开始 / 最晚结束 / 城市去重
     · Trip::cities()       按最早出现时间排(先后顺序本身是信息,不是字母序)
     · Trip::attachDerived() 列表页批量补(避免 N+1)
     · Trip::activitySpan()  没有旅程时说「还没有旅程」,不用「日期待补」
     create/update 不再写 events 的起止与地点(老值保留不动,页面不读)。
     列表排序/搜索、首页「最近去了」改走子查询。

3. 「题图那里,没有办法上传图片,点击哪里都没反应。」——**真 bug**
   「点图 = 开文件框」那句 JS 只写在 people/form.php 的内页脚本里(头像专用);
   第 14 批加的旅程题图与图书封面照抄了同一套 DOM 却没有对应的 JS,
   于是图块有 cursor:pointer 与 hover 高亮(看着能点),点下去毫无反应。
   → 做成声明式通用绑定:图上写 data-pick-input="#fileInputId",
     app.js 统一接管(一个页面多个也各绑各的),并删掉旧的那份专用实现。
     三处(头像 / 题图 / 图书封面)全挂上。

4. 「添加活动的页面,顺序是这样的:添加标题,上传并裁剪题图,然后一段简介,就可以了。」
   → 主区就这三项,顺序照原话;时间/城市不做输入项(只留一句说明它们从哪来,
     不说清用户会满页找"怎么没地方填时间")。
     备注 / 公开 / 同行的人收进「更多设置」折叠区——功能一个没丢。

顺带修掉两处会炸的实现问题:
· ORDER BY 引用聚合别名(first_at)在 MariaDB/MySQL 报 1247,且只在运行时炸、
  php -l 看不出 → 改成 ORDER BY MIN(starts_at),否则整个旅程模块 Fatal。
· e2e 里"详情页显示了派生时间"原本写成整页查找,下面每段旅程自己也渲染日期,
  于是把派生值塞成 null 也照样绿(借光假绿)→ 判据收进页头那一段。

验证:e2e 845/0(+15 条本批断言)· 升级 134/0 · 桌面几何 428/0 · 移动几何 688/0 ·
交互 92/0 · 题图真机点击 4/0 · 读书窄屏 15/0 · 家谱 34/0 + 10/0 + 反向 3/3 ·
观影按钮 60/0 + 反向 3/3 · 本批反向验证 4/4(题图绑定抽掉→红、派生值不挂→红、
城市退回读 location→红、时间字段加回表单→红)· 部署静态断言新增两组(含"活动不许
再写起止时间/目的地"与"ORDER BY 不许引用聚合别名"的反退化防线)。
部署后服务器自检 19/19,五个关键文件 md5 与线上逐一核对一致。
2026-10-05 10:58:17 +08:00

119 lines
5.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 一段旅程的表单(新增 / 编辑共用;被 show.php 循环 require)。
*
* ⚠️ 术语(用户第 15 批 第 1 条):这是**一段旅程**;「计划 / 实际」是它的阶段。
*
* v24 第 13 批:旅程重构后,**不再分两条轨**,而是**一条记录 + 状态机**。
* 用户原话:「这个实际的行程和计划行程,是一条记录,不是两条」。
* 所以这里有「这是哪一类」的下拉:计划 / 实际
* (新建时由用户选;编辑时是当前状态)。
* 「修正」「标记为实际」「完成」「标记未成行」是**另外几个按钮**(在 show.php 上),
* 不走这张表单 —— 它们的语义是"对既有旅程的操作",不是"填一遍字段"。
*
* 变量(require 前设好):
* $nfNode 被编辑的节点数组,或 null(新增)
* $nfTripId 活动 id(events.id)
* $nfCategory 【兼容保留】'plan' | 'reality'(新版只用来定默认值)
* $nfPeople 可选人名册(选参与人用),为空就不显示成员多选
* $nfMembers 已选成员 id 列表
*/
$nfIsEdit = !empty($nfNode);
$nfPrefix = $nfIsEdit ? 'ne' . (int)$nfNode['id'] : 'nn';
// 默认状态:编辑时用它的 stage;新建时按调用方给的 $nfCategory 映射(兼容旧调用)
$nfStage = $nfIsEdit
? (string)($nfNode['stage'] ?? (($nfNode['category'] ?? 'plan') === 'reality' ? 'actual' : 'plan'))
: ($nfCategory === 'reality' ? 'actual' : 'plan');
?>
<form class="node-form" method="post" action="<?= e(url($nfIsEdit ? 'trips/node_update' : 'trips/node_add')) ?>">
<?= csrf_field() ?>
<input type="hidden" name="trip_id" value="<?= (int)$nfTripId ?>">
<?php /* v24 第 13 批:新建时选「计划 / 实际」——用户原话第 2 条明确要求。
一旦创建后要改,走行程上那三个按钮(修正/完成/标记未成行),不在这张表里。 */ ?>
<input type="hidden" name="stage" value="<?= e($nfStage) ?>">
<?php if ($nfIsEdit): ?>
<input type="hidden" name="id" value="<?= (int)$nfNode['id'] ?>">
<?php endif; ?>
<div class="nf-grid">
<div class="field">
<label for="<?= e($nfPrefix) ?>-title">这段旅程叫什么 *</label>
<input class="input" id="<?= e($nfPrefix) ?>-title" name="title" required
value="<?= e($nfNode['title'] ?? '') ?>" placeholder="如:栈桥看海">
</div>
<div class="field">
<label for="<?= e($nfPrefix) ?>-type">类型</label>
<select class="select" id="<?= e($nfPrefix) ?>-type" name="node_type">
<?php foreach (\App\Trip::NODE_TYPES as $tk => $tv): ?>
<option value="<?= e($tk) ?>"<?= ($nfNode['node_type'] ?? 'other') === $tk ? ' selected' : '' ?>>
<?= e($tv[1] . ' ' . $tv[0]) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php if (!$nfIsEdit): ?>
<div class="field">
<label for="<?= e($nfPrefix) ?>-stage">阶段 *</label>
<select class="select" id="<?= e($nfPrefix) ?>-stage" name="stage_pick">
<option value="plan"<?= $nfStage === 'plan' ? ' selected' : '' ?>>计划(还没走)</option>
<option value="actual"<?= $nfStage === 'actual' ? ' selected' : '' ?>>实际(已经走过)</option>
</select>
<span class="tip">计划走完后可用「修正 / 完成」把它转成实际。</span>
</div>
<?php else: ?>
<div class="field">
<label>当前状态</label>
<div class="node-stage-ro"><?= e(\App\Trip::stageWord($nfStage)) ?></div>
</div>
<?php endif; ?>
<div class="field">
<label for="<?= e($nfPrefix) ?>-place">地点</label>
<input class="input" id="<?= e($nfPrefix) ?>-place" name="place"
value="<?= e($nfNode['place'] ?? '') ?>" placeholder="选填">
</div>
<div class="field">
<label for="<?= e($nfPrefix) ?>-status">状态</label>
<select class="select" id="<?= e($nfPrefix) ?>-status" name="status">
<?php foreach (\App\Trip::NODE_STATUS as $sk => $sv): ?>
<option value="<?= e($sk) ?>"<?= ($nfNode['status'] ?? 'planned') === $sk ? ' selected' : '' ?>>
<?= e($sv) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php /* 用户 2026-10-04 第 7 条:节点的起止也是**一个范围**,一次选完 */ ?>
<div class="field dt-range">
<label>开始 / 结束</label>
<?php $dtName = 'starts_at'; $dtValue = $nfNode['starts_at'] ?? '';
$dtId = $nfPrefix . '-s'; $dtSeconds = true; $dtRange = 'start';
require __DIR__ . '/../_datetime.php'; ?>
<?php $dtName = 'ends_at'; $dtValue = $nfNode['ends_at'] ?? '';
$dtId = $nfPrefix . '-e'; $dtSeconds = true; $dtRange = 'end';
require __DIR__ . '/../_datetime.php'; ?>
<span class="tip">结束时间可不填。</span>
</div>
<div class="field full">
<label for="<?= e($nfPrefix) ?>-detail">记录</label>
<textarea class="input" id="<?= e($nfPrefix) ?>-detail" name="detail" rows="2"
placeholder="这段旅程发生了什么"><?= e($nfNode['detail'] ?? '') ?></textarea>
</div>
<?php if (!empty($nfPeople)): ?>
<div class="field full">
<label>这段旅程里有谁(不勾 = 整个活动同行的人)</label>
<div class="nf-members">
<?php foreach ($nfPeople as $p): ?>
<label class="check-item">
<input type="checkbox" name="members[]" value="<?= (int)$p['id'] ?>"
<?= in_array((int)$p['id'], $nfMembers, true) ? 'checked' : '' ?>>
<span><?= e(person_name($p)) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<div class="btn-row">
<button class="btn btn-sm" type="submit"><?= $nfIsEdit ? '保存' : '加这段旅程' ?></button>
</div>
</form>