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:
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
/**
|
||||
* 一段行程的新增 / 编辑 —— **独立页面**(v24 第 16 批 用户第 5 / 6 / 7 条)。
|
||||
*
|
||||
* 用户原话:
|
||||
* · 第 5 条:「行程的编辑功能,点击后会把页面撑破。编辑行程,打开新的页面进行编辑。」
|
||||
* · 第 6 条:「添加计划行程和实际行程的按钮,都放在右上角。点击后打开新的页面进行添加。」
|
||||
* · 第 7 条:「添加和编辑行程的内容,你去找 triplog 里面的添加节点功能,跟那个一样。」
|
||||
*
|
||||
* ⚠️ 为什么必须独立成页(而不是继续用 `<details>` 内联):
|
||||
* TripLog 的节点表单有十几组字段(类型/时间/出行方式/交通细节/起终点/内容/
|
||||
* 参与人/照片/状态),内联在主页面里会把整个详情页撑得极长;
|
||||
* 更要命的是**编辑**时每个行程内联一份表单 → 页面上 N 份表单互相干扰
|
||||
* (元素 id 撞车、密码管理器逐个扫描变卡)。这正是用户说的"把页面撑破"。
|
||||
*
|
||||
* 字段顺序照 TripLog 的 nodes/form.php:
|
||||
* 类型 → 起止时间 → 标题 → 出行方式(+车辆+交通细节)→ 起点 → 终点
|
||||
* → 内容 → 参与人 → 照片 →(编辑时)状态与花销。
|
||||
*
|
||||
* 变量(require 前设好):
|
||||
* $ev 所属旅程(events 行,带 title)
|
||||
* $n 被编辑的行程;null = 新增
|
||||
* $stage 'plan'|'actual'(新增时由按钮决定;编辑时取行程自己的)
|
||||
* $people 名册
|
||||
* $types 节点类型(NodeType::all)
|
||||
* $vehicles 可挑的车(Vehicle::pickable,含"来自财务"的只读候选)
|
||||
* $expenses 已有花销(编辑时)
|
||||
* $errors 校验错误
|
||||
*/
|
||||
$isEdit = !empty($n);
|
||||
$canWrite = \App\Auth::canWrite();
|
||||
$fid = (int)\App\Auth::familyId();
|
||||
$people = $people ?? [];
|
||||
$types = $types ?? [];
|
||||
$vehicles = $vehicles ?? [];
|
||||
$expenses = $expenses ?? [];
|
||||
$errors = $errors ?? [];
|
||||
|
||||
$stage = (string)($stage ?? ($n['stage'] ?? 'plan'));
|
||||
if (!isset(\App\Trip::NODE_STAGES[$stage])) { $stage = 'plan'; }
|
||||
$curType = (string)($n['node_type'] ?? '');
|
||||
$curMode = (string)($n['travel_mode'] ?? '');
|
||||
$picked = array_map('intval', array_column((array)($n['members'] ?? []), 'id'));
|
||||
|
||||
// 参与人角色:person_id => role
|
||||
$roles = [];
|
||||
foreach ((array)($n['members'] ?? []) as $m) {
|
||||
if (!empty($m['role_tag'])) {
|
||||
$roles[(int)$m['id']] = (string)$m['role_tag'];
|
||||
}
|
||||
}
|
||||
$back = url('trips/show', ['id' => (int)$ev['id']]);
|
||||
?>
|
||||
<div class="page-head">
|
||||
<h1>
|
||||
<?= $isEdit ? '编辑行程' : ('添加' . ($stage === 'actual' ? '实际' : '计划') . '行程') ?>
|
||||
</h1>
|
||||
<span class="sub">
|
||||
<?= e((string)$ev['title']) ?>
|
||||
<?php if ($isEdit): ?> · <?= e(\App\Trip::stageWord((string)($n['stage'] ?? $stage))) ?><?php endif; ?>
|
||||
</span>
|
||||
<div class="actions">
|
||||
<a class="btn btn-ghost" href="<?= e($back) ?>">返回旅程</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-pad">
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
action="<?= e(url($isEdit ? 'trips/node_update' : 'trips/node_add')) ?>">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="trip_id" value="<?= (int)$ev['id'] ?>">
|
||||
<?php /* 新增时阶段由按钮决定;编辑时是"当前状态"(只读展示,改状态走详情页那几个动作) */ ?>
|
||||
<input type="hidden" name="stage" value="<?= e($stage) ?>">
|
||||
<?php if ($isEdit): ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$n['id'] ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-grid">
|
||||
<?php /* ---- 类型(TripLog 的第一个字段)---- */ ?>
|
||||
<div class="field">
|
||||
<label for="nf-type">类型 *</label>
|
||||
<select class="select" id="nf-type" name="node_type">
|
||||
<?php foreach ($types as $t): ?>
|
||||
<?php $tn = (string)$t['name']; ?>
|
||||
<option value="<?= e($tn) ?>"<?= $curType === $tn ? ' selected' : '' ?>>
|
||||
<?= e(($t['icon'] ?? '') . ' ' . $tn) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php /* 老数据的英文 key 不在类型表里 → 补一个当前值,否则一保存就被改成别的 */ ?>
|
||||
<?php if ($curType !== '' && !in_array($curType, array_column($types, 'name'), true)): ?>
|
||||
<option value="<?= e($curType) ?>" selected>
|
||||
<?= e(\App\Trip::typeIcon($curType, $fid) . ' ' . \App\Trip::typeWord($curType, $fid)) ?>
|
||||
</option>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
<span class="tip">在右上角「设置 → 节点类型」里可以增删自定义。</span>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>当前状态</label>
|
||||
<div class="node-stage-ro"><?= e(\App\Trip::stageWord($stage)) ?></div>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 标题 ---- */ ?>
|
||||
<div class="field full">
|
||||
<label for="nf-title">标题 *</label>
|
||||
<input class="input" id="nf-title" name="title" required
|
||||
value="<?= e(old('title', $n['title'] ?? '')) ?>" placeholder="例如:出发去栈桥">
|
||||
<?php if (!empty($errors['title'])): ?><span class="err"><?= e($errors['title']) ?></span><?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 起止时间:一个范围控件 ---- */ ?>
|
||||
<div class="field dt-range full">
|
||||
<label>开始 / 结束</label>
|
||||
<?php $dtName = 'starts_at'; $dtValue = $n['starts_at'] ?? '';
|
||||
$dtId = 'nf-s'; $dtSeconds = true; $dtRange = 'start';
|
||||
require __DIR__ . '/../_datetime.php'; ?>
|
||||
<?php $dtName = 'ends_at'; $dtValue = $n['ends_at'] ?? '';
|
||||
$dtId = 'nf-e'; $dtSeconds = true; $dtRange = 'end';
|
||||
require __DIR__ . '/../_datetime.php'; ?>
|
||||
<span class="tip">结束时间可不填。</span>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 出行方式 + 车辆(TripLog 的 travel_modes)----
|
||||
用户第 8 条:车辆从财务里**读**,也可以加自己的(租的 / 借的)。 */ ?>
|
||||
<div class="field">
|
||||
<label for="nf-mode">出行方式</label>
|
||||
<select class="select" id="nf-mode" name="travel_mode" data-mode-select>
|
||||
<option value="">— 不填 —</option>
|
||||
<?php foreach (\App\Trip::TRAVEL_MODES as $mk => $mv): ?>
|
||||
<option value="<?= e($mk) ?>"<?= $curMode === $mk ? ' selected' : '' ?>>
|
||||
<?= e($mv[1] . ' ' . $mv[0]) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field" data-mode-vehicle hidden>
|
||||
<label for="nf-veh">车辆</label>
|
||||
<select class="select" id="nf-veh" name="vehicle_id">
|
||||
<option value="">— 不指定 —</option>
|
||||
<?php
|
||||
$curVeh = (int)($n['vehicle_id'] ?? 0);
|
||||
$ledgerShown = false;
|
||||
foreach ($vehicles as $v):
|
||||
$isLedger = (($v['from'] ?? '') === 'ledger');
|
||||
if ($isLedger && !$ledgerShown) {
|
||||
echo '<optgroup label="财务里已登记的车">';
|
||||
$ledgerShown = true;
|
||||
}
|
||||
?>
|
||||
<option value="<?= $isLedger ? (int)$v['id'] : (int)$v['id'] ?>"
|
||||
<?= $curVeh === (int)$v['id'] ? ' selected' : '' ?>>
|
||||
<?= e(\App\Vehicle::label($v) . ($isLedger ? '(来自财务)' : '')) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($ledgerShown): ?></optgroup><?php endif; ?>
|
||||
</select>
|
||||
<span class="tip">
|
||||
财务里登记过的车子会直接列在这里;租的车、借别人的车到
|
||||
「设置 → 车辆管理」加一台。
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 交通细节(照 TripLog:线路名 / 车次 / 承运方 / 座位 / 登机口)---- */ ?>
|
||||
<div class="field">
|
||||
<label for="nf-line">线路名</label>
|
||||
<input class="input" id="nf-line" name="line_name"
|
||||
value="<?= e(old('line_name', $n['line_name'] ?? '')) ?>" placeholder="如:京沪高铁、地铁 3 号线">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="nf-tripno">车次 / 航班号</label>
|
||||
<input class="input" id="nf-tripno" name="trip_no"
|
||||
value="<?= e(old('trip_no', $n['trip_no'] ?? '')) ?>" placeholder="如:G1234、CA1234">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="nf-carrier">承运方</label>
|
||||
<input class="input" id="nf-carrier" name="carrier"
|
||||
value="<?= e(old('carrier', $n['carrier'] ?? '')) ?>" placeholder="如:中国铁路、滴滴">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="nf-seat">座位 / 车厢</label>
|
||||
<input class="input" id="nf-seat" name="seat_info"
|
||||
value="<?= e(old('seat_info', $n['seat_info'] ?? '')) ?>" placeholder="如:7 车 12F">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="nf-gate">登机口 / 站台</label>
|
||||
<input class="input" id="nf-gate" name="gate"
|
||||
value="<?= e(old('gate', $n['gate'] ?? '')) ?>" placeholder="如:T3 登机口 B12">
|
||||
</div>
|
||||
|
||||
<?php /* ---- 起点 / 终点(TripLog 的 start/end_location)---- */ ?>
|
||||
<div class="field">
|
||||
<label for="nf-from">出发地</label>
|
||||
<input class="input" id="nf-from" name="from_place"
|
||||
value="<?= e(old('from_place', $n['from_place'] ?? '')) ?>" placeholder="如:家里">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="nf-to">到达地</label>
|
||||
<input class="input" id="nf-to" name="to_place"
|
||||
value="<?= e(old('to_place', $n['to_place'] ?? '')) ?>" placeholder="如:青岛北站">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="nf-place">地点</label>
|
||||
<input class="input" id="nf-place" name="place"
|
||||
value="<?= e(old('place', $n['place'] ?? '')) ?>" placeholder="这一段主要在哪(用于统计城市)">
|
||||
<span class="tip">旅程页上的"去了几个城市"就是按这里统计的。</span>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="nf-status">执行状态</label>
|
||||
<select class="select" id="nf-status" name="status">
|
||||
<?php foreach (\App\Trip::NODE_STATUS as $sk => $sv): ?>
|
||||
<option value="<?= e($sk) ?>"<?= ($n['status'] ?? 'planned') === $sk ? ' selected' : '' ?>>
|
||||
<?= e($sv) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<label for="nf-detail">记录</label>
|
||||
<textarea class="input" id="nf-detail" name="detail" rows="4"
|
||||
placeholder="这一段发生了什么"><?= e(old('detail', $n['detail'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 参与人(+角色,TripLog 的 role_tag)---- */ ?>
|
||||
<div class="field full">
|
||||
<label>参与的人</label>
|
||||
<?php if (!$people): ?>
|
||||
<p class="empty-hint">名册里还没有人。</p>
|
||||
<?php else: ?>
|
||||
<div class="nf-members">
|
||||
<?php foreach ($people as $p): ?>
|
||||
<?php $pid = (int)$p['id']; ?>
|
||||
<label class="check-item">
|
||||
<input type="checkbox" name="members[]" value="<?= $pid ?>"
|
||||
<?= in_array($pid, $picked, true) ? 'checked' : '' ?>>
|
||||
<span><?= e(person_name($p)) ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<details class="role-set">
|
||||
<summary class="link-quiet">给参与的人标个角色(可选)</summary>
|
||||
<div class="role-grid">
|
||||
<?php foreach ($people as $p): ?>
|
||||
<?php $pid = (int)$p['id']; ?>
|
||||
<div class="role-row">
|
||||
<span class="role-who"><?= e(person_name($p)) ?></span>
|
||||
<input class="input" name="roles[<?= $pid ?>]" maxlength="30"
|
||||
value="<?= e($roles[$pid] ?? '') ?>" placeholder="如:司机 / 摄影">
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<span class="tip">不填也没关系;填了会显示在那个人名字旁边。</span>
|
||||
</details>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 照片(用户第 3 条:图片视频都针对一次具体行程)---- */ ?>
|
||||
<div class="field full">
|
||||
<label for="nf-photo">这一段行程的照片</label>
|
||||
<?php if ($isEdit && !empty($n['attachs'])): ?>
|
||||
<div class="photo-grid">
|
||||
<?php foreach ($n['attachs'] as $ph): ?>
|
||||
<figure class="photo-cell">
|
||||
<a href="<?= e(media_url((string)$ph['key'])) ?>" data-prev="image"
|
||||
data-src="<?= e(media_url((string)$ph['key'])) ?>"
|
||||
data-dl="<?= e(media_url((string)$ph['key'], ['dl' => 1])) ?>"
|
||||
data-name="<?= e((string)($ph['name'] ?? '照片')) ?>">
|
||||
<img src="<?= e(media_url((string)$ph['key'])) ?>" alt="">
|
||||
</a>
|
||||
<form method="post" action="<?= e(url('trips/photo_del')) ?>" class="photo-del">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$ev['id'] ?>">
|
||||
<input type="hidden" name="photo_id" value="<?= (int)$ph['id'] ?>">
|
||||
<button class="btn btn-ghost btn-sm" type="submit"
|
||||
onclick="return confirm('删掉这张照片?')">删除</button>
|
||||
</form>
|
||||
</figure>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input class="input" type="file" id="nf-photo" name="photo[]" accept="image/*" multiple>
|
||||
<span class="tip">可多选;图片、视频都会存到这段行程下面。</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<button class="btn" type="submit"><?= $isEdit ? '保存' : '加这段行程' ?></button>
|
||||
<a class="btn btn-ghost" href="<?= e($back) ?>">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php /* ==================== 花销(只在编辑时出现)====================
|
||||
用户第 2 条要"一共花了多少钱"、本次确认「行程级花销记录」。
|
||||
⚠️ 新增时不给花销面板:那时行程 id 还不存在,花销挂不上(先存行程再加花销,
|
||||
少一种"提交了但没存上"的状态)。 */ ?>
|
||||
<?php if ($isEdit): ?>
|
||||
<div class="card card-pad">
|
||||
<h2 class="cinema-h2">
|
||||
花销(<?= count($expenses) ?> 笔)
|
||||
<span class="hint">这一段花了多少钱;旅程页的"总花销"是各段之和</span>
|
||||
</h2>
|
||||
|
||||
<?php if (!$expenses): ?>
|
||||
<p class="empty-hint">还没记花销。</p>
|
||||
<?php else: ?>
|
||||
<ul class="exp-list">
|
||||
<?php foreach ($expenses as $ex): ?>
|
||||
<li class="exp-row">
|
||||
<span class="exp-cat"><?= e(\App\Expense::CATS[(string)$ex['category']] ?? '其他') ?></span>
|
||||
<span class="exp-title"><?= e((string)($ex['title'] ?? '')) ?></span>
|
||||
<?php if (!empty($ex['person_name'])): ?>
|
||||
<span class="exp-who small muted"><?= e((string)$ex['person_name']) ?> 付</span>
|
||||
<?php endif; ?>
|
||||
<span class="exp-amt"><?= e(\App\Expense::money((int)$ex['amount_cents'])) ?></span>
|
||||
<form method="post" action="<?= e(url('trips/expense_del')) ?>" class="res-inline">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="node_id" value="<?= (int)$n['id'] ?>">
|
||||
<input type="hidden" name="expense_id" value="<?= (int)$ex['id'] ?>">
|
||||
<button class="btn btn-ghost btn-sm" type="submit"
|
||||
onclick="return confirm('删掉这笔花销?')">删</button>
|
||||
</form>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<p class="exp-total">合计 <b><?= e(\App\Expense::money(\App\Expense::sumOfNode((int)$n['id']))) ?></b></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<form method="post" action="<?= e(url('trips/expense_add')) ?>" class="exp-form">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="node_id" value="<?= (int)$n['id'] ?>">
|
||||
<div class="exp-form-row">
|
||||
<input class="input" name="amount" required placeholder="金额,如 128.50" inputmode="decimal">
|
||||
<select class="select" name="category" aria-label="类别">
|
||||
<?php foreach (\App\Expense::CATS as $ck => $cv): ?>
|
||||
<option value="<?= e($ck) ?>"><?= e($cv) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php /* ⚠️ 「谁付的」也是选人 —— 必须走**全局选人组件**(v24 第 8 条:
|
||||
全站的选人一律弹窗 + 头像 + 拼音联想,不许再写裸 <select>)。
|
||||
裸下拉的判据在部署断言里,写完空跑会直接红。 */ ?>
|
||||
<div class="exp-who">
|
||||
<?php
|
||||
$ppName = 'person_id';
|
||||
$ppPeople = $people;
|
||||
$ppSelected = [];
|
||||
$ppMode = 'single';
|
||||
$ppOpenBtn = '谁付的(可不选)';
|
||||
$ppTitle = '这笔是谁付的';
|
||||
$ppEmptyText = '不指定';
|
||||
$ppAllowEmpty = true;
|
||||
require __DIR__ . '/../_person_picker.php';
|
||||
?>
|
||||
</div>
|
||||
<input class="input" name="title" placeholder="花在哪(可空)">
|
||||
<button class="btn btn-sm" type="submit">加一笔</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
Reference in New Issue
Block a user