v24 第 20 批:拖动即时上传 / 观影图片带出 / 行程保存修复 / 日程红线与删除范围与多提醒 / 悬停统一(用户 9 条)
用户 9 条原话要点:
①「观影模块,编辑观影,已经保存的海报和票根、合影,都没带进来。」
②「个人设置页面,"只影响你自己看到的样子"这个提示删除,不要了。我怎么称呼家人,
这个卡片,不要了。我的资料这个卡片,不要了。」
③「旅程模块,编辑行程,点击保存没有反应。」
④「所有上传图片视频和附件的模块,都允许多次拖动添加文件上传,拖动一个进去,
就开始上传,还可以继续拖动,继续上传,最后统一保存即可。」
⑤「添加行程,线路名、承运方,都不要了。车辆,只在出行方式选择自驾时才出现,
其他情况下默认不出现。出发地和到达地,应该在同一行。」
⑥「日程模块,周视图和日视图,应该在视图中间显示一条红线,用来表示当前进行的时间…
用一个不断从上到下的红线,来表示当前时间在视图上的刻度和位置。」
⑦「对于重复的日程,当点击删除的时候,应该提示是删除这一次,还是全部都删除,
还是删除这一次及以后的。」
⑧「日程的提醒,是可以多选的,就是说,一个日程,我可以选择让它 5 分钟提醒,
10 分钟提醒等等。」
⑨「很多按钮的悬停效果非常奇怪,感觉纯粹是换了一个背景色…应该调整所有按钮的悬停效果,
保持统一,但是不要单纯的换一个不协调的背景色。」
🔴 三个真根因(都是"看着不像 bug"的地方):
1. **③ 点保存没反应 = `<form>` 嵌套**(HTML 非法):`trips/node_form.php` 的照片缩略图上
套了 `<form action=photo_del>`,而整块在**外层行程表单内部**。浏览器解析到内层
`</form>` 时**把外层表单提前关掉** → 「保存」按钮**掉出表单**(真机实测
`button.form === null`)。源码里完全看不出,**只有真浏览器量 `btn.form` 才现形**;
且**只有这段行程已经有照片时才触发** —— 所以表现为"有时候好、有时候坏"。
→ 按钮留在原位(布局不动),用 HTML5 `form="id"` 关联到**表单外**的独立表单。
2. **⑤ 车辆藏不住(第 17 批就写过、一直没生效)**:`.field` 自己声明 `display:flex`,
作者样式压过浏览器默认的 `[hidden]{display:none}` → `hidden` 形同虚设。
**本项目第 4 次栽同一个坑**(.att-item / .pv / .pp-item 各一次)→ 补 `.field[hidden]`。
3. **④ 拖动即时上传的卡片"看不见"**:模板给 `.stage-list` 加了 `hidden`(没人上传时
不留空边框),JS 建卡片时忘了 `list.hidden = false` → 卡片在 DOM 里但整条清单
`display:none`、尺寸 0×0。**HTTP 层断言全绿**(HTML 里确实有卡片),
**只有真机量尺寸才发现** —— 由新写的真机探针抓到。
本批新增能力:
- **拖动即时上传**(第 4 条):新增 `upload_stage.php` + `app/StagedUpload.php` +
`templates/_stage_drop.php`。拖进来 → **立刻** POST 到暂存目录 → 回 `stoken` →
放进隐藏字段 `stage[]` → 保存时在 `attach_save_files()` 里**认领**(移文件 + 建附件行)。
`media.php` 加 `?stage=` 预览(拖完立刻看得见缩略图)。
⚠️ 三个必须踩准:**串行队列**(并发会让缩略图顺序乱跳)、**提交守卫**
(还有没传完就 preventDefault,否则"拖了但没保存上"必然发生)、**卡片清单要 un-hide**。
⚠️ 海报/票根走**独立字段名** `stage_poster`/`stage_ticket`(各自单槽位、要按比例重裁)。
⚠️ 落点**就近取**(`input.closest('.field')`):一个表单里多个上传区不能串到第一个去。
- **观影编辑带出已存图片**(第 1 条):编辑页显示海报 / 票根 / 合影 + 各自的删除入口;
新增 `movies/media_del`(只删不换也能删,槽位走白名单);`movies/update` 收编辑页
新选的照片时**合并**而不是替换(否则一编辑就把已有合影冲掉)。
- **行程表单**(第 5 条):去掉线路名 / 承运方(**库里的值保留**:UPDATE 时不参与,
与 seat_info/gate 同一套);出发地/到达地包进 `.loc-row` 真同一行;车辆按自驾显隐。
- **当前时间红线**(第 6 条):位置在服务端算(与 `.tg-block` 同一套"24h = 100%"坐标),
前端每分钟自己挪(对齐到下一分钟整再起定时器),跨午夜 reload;只画在**今天那一列**。
- **重复日程删除三档**(第 7 条):与「编辑」那三档同名同序(全部 / 这一次及以后 /
只这一次)。「只删这一次」→ `skipOnce`(**绝不删整条系列**);「这一次及以后」→
老系列截 UNTIL,**必须清掉 COUNT**(两者并存时 COUNT 优先,不清就截不断)。
- **提醒多选**(第 8 条):同列存逗号串(`5,15,60`);Schema 把它从 **INT 改 VARCHAR**
(INT 存逗号串会被隐式转换,多档会静默只剩第一档);ICS 导出吐**多个 VALARM**。
- **悬停统一**(第 9 条):原来 14 种互不相干的 hover 底色 → 统一成**两套手法**:
有底色 `filter: brightness(.92)` 同色系加深 + 轻抬升;描边白底 `--hov-veil` 中性薄雾
+ 边框微显。放在 CSS **最末**用一条兜底收口(新按钮自动继承),并把十几条旧规则
清成"交给统一段"。`person_picker.js` 同时改为按需加载。
验证(本地全套):
- e2e **1004/0**(+21 条本批断言)· 升级 149/0 · 桌面几何 428/0 ·
**真机探针 25/0**(连续拖两次 / 红线位置与颜色与几何 / 车辆显隐 / 出发到达同行)。
- **反向验证 5/5**:①删除按钮改回嵌套 form ②卡片清单不 un-hide ③观影编辑不渲染海报
④车辆隐藏失效 ⑤「只删这一次」做成删全部 —— 五种坏法全部被抓住,还原后复绿 1004/0。
过程中还**抓到两条弱断言**(只查 DOM 有无、不查可见性/是否真渲染),已一并加强。
- 部署脚本静态断言段本地空跑 **305 条全过**。
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* 拖动即时上传的**落点 + 暂存清单**(v24 第 20 批 用户第 4 条)。
|
||||
*
|
||||
* 用户原话:「所有上传图片视频和附件的模块,都允许多次拖动添加文件上传,
|
||||
* 拖动一个进去,就开始上传,还可以继续拖动,继续上传,最后统一保存即可。」
|
||||
*
|
||||
* 这个片段只负责**画出落点**(一个虚线框 + 一个空清单容器),
|
||||
* 真正的行为在 assets/js/app.js 的 `queueDrop()` 里:
|
||||
* 拖进来 → 立刻 POST 到 upload/stage → 回来把 `stoken` 塞进隐藏字段 `stage[]`。
|
||||
*
|
||||
* ⚠️ 为什么"落点"要和"文件框"分开:
|
||||
* 文件框(`<input type=file>`)仍然保留(点它一样能选文件),
|
||||
* 拖动只是**多一种**输入方式;而且"拖到哪一块"需要一块**看得见**的区域 ——
|
||||
* 单靠文件框本身,用户不知道该往哪儿拖。
|
||||
*
|
||||
* ⚠️ 落点必须能被 app.js 找回来:`data-drop-stage` 挂在 `<div>` 上,
|
||||
* app.js 从「被拖的文件框」`input.form.querySelector('[data-drop-stage]')` 找它。
|
||||
* 所以**每个上传区都要有一个文件框**(哪怕隐藏)—— 这符合现状(都有)。
|
||||
*
|
||||
* 用法(require 前设好,都可选):
|
||||
* $sdHint 提示文案
|
||||
* $sdSlot 图片槽位键('poster' / 'ticket' / 'cover' / 'photo'…;决定图片怎么裁)
|
||||
* $sdMulti 是否允许多次/多个(默认 true)
|
||||
* $sdAccept accept 提示(只影响选择框,不影响拖动)
|
||||
*/
|
||||
$sdHint = (string)($sdHint ?? '也可以把文件直接拖到这块虚线区域里 —— 拖一个就开始上传,可以接着再拖,最后点「保存」一起记下。');
|
||||
$sdSlot = (string)($sdSlot ?? '');
|
||||
$sdMulti = $sdMulti ?? true;
|
||||
$sdAccept = (string)($sdAccept ?? '');
|
||||
/* ⚠️ **海报 / 票根走另一条路**:它们各自只有一个槽位、且比例不同
|
||||
(海报 2:3、票根 free)。如果也塞进 `stage[]` 一起认领,
|
||||
服务端就只能统一按 'photo' 处理 —— 海报会被存成不是 2:3 的图,
|
||||
页面上就变形了。所以这两个用**独立的字段名**把令牌带回去,
|
||||
由 movies 路由按槽位重跑 Upload::image(见 index.php 里那一段)。
|
||||
其余所有附件类上传(照片/资源/任务/日历)都走通用的 `stage[]`。 */
|
||||
$sdField = in_array($sdSlot, ['poster', 'ticket'], true) ? ('stage_' . $sdSlot) : 'stage[]';
|
||||
?>
|
||||
<div class="stage-drop" data-drop-stage data-stage-field="<?= e($sdField) ?>"
|
||||
<?= $sdSlot !== '' ? 'data-stage-slot="' . e($sdSlot) . '"' : '' ?>>
|
||||
<span class="stage-drop-tip" data-stage-tip><?= e($sdHint) ?></span>
|
||||
<div class="stage-list" data-stage-list hidden></div>
|
||||
</div>
|
||||
<?php
|
||||
// ⚠️ 只 unset 本片段自己的变量(与 _datetime.php / _loc_field.php 同一约定)
|
||||
unset($sdHint, $sdSlot, $sdMulti, $sdAccept, $sdField);
|
||||
@@ -36,16 +36,23 @@ $evIsAllDay = array_key_exists('all_day', $evF) ? !empty($evF['all_day']) : $evA
|
||||
|
||||
<div class="field">
|
||||
<label>提醒</label>
|
||||
<?php /* 不提醒 = NULL(导出 ics 时不写 VALARM);档位就是用户第 6 条列的那些。 */ ?>
|
||||
<select class="select" name="reminder_minutes" aria-label="提前多久提醒">
|
||||
<?php $cur = $evF['reminder_minutes'] ?? null; ?>
|
||||
<option value=""<?= $cur === null || $cur === '' ? ' selected' : '' ?>>不提醒</option>
|
||||
<?php /* v24 第 20 批 第 8 条:**多选**(用户原话:「一个日程,我可以选择让它
|
||||
5分钟提醒,10分钟提醒等等。」)。
|
||||
⚠️ 用 checkbox 而不是 `<select multiple>`:后者在手机上要"按住再点",
|
||||
几乎没法用(任务共享那个 multiple 下拉就是因此被换掉的)。
|
||||
⚠️ 字段名必须是 `reminder_minutes[]` —— Calendar::reminderMinutes() 认得数组。
|
||||
写成 `reminder_minutes`(不带 [])会让 PHP 只留最后一个勾选。 */
|
||||
$curReminders = \App\Calendar::reminderList($evF['reminder_minutes'] ?? null); ?>
|
||||
<div class="rem-picks">
|
||||
<?php foreach (\App\Calendar::REMINDERS as $mins => $word): ?>
|
||||
<option value="<?= (int)$mins ?>"<?= (string)$cur === (string)$mins ? ' selected' : '' ?>>
|
||||
<?= e($word) ?>
|
||||
</option>
|
||||
<label class="chk-inline rem-pick">
|
||||
<input type="checkbox" name="reminder_minutes[]" value="<?= (int)$mins ?>"
|
||||
<?= in_array((int)$mins, $curReminders, true) ? 'checked' : '' ?>>
|
||||
<span><?= e($word) ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<span class="tip">可以选多个(到点各响一次);一个都不选就是不提醒。</span>
|
||||
</div>
|
||||
|
||||
<?php /* v24 第 5、6 条:起止各一个「日期 + 时分秒」合在一起的控件(全局统一),
|
||||
|
||||
@@ -33,10 +33,11 @@ $canWrite = \App\Auth::canWrite() && !empty($ev['_can_write']);
|
||||
<?php if (!empty($ev['location'])): ?>
|
||||
<div class="kv-row"><dt>地点</dt><dd><?= e((string)$ev['location']) ?></dd></div>
|
||||
<?php endif; ?>
|
||||
<?php if (($ev['reminder_minutes'] ?? null) !== null): ?>
|
||||
<div class="kv-row"><dt>提醒</dt>
|
||||
<dd><?= e(\App\Calendar::REMINDERS[(int)$ev['reminder_minutes']] ?? '') ?></dd>
|
||||
</div>
|
||||
<?php /* v24 第 20 批 第 8 条:提醒可以**多档**,所以要列全("提前 5 分钟、提前 1 小时")。
|
||||
取的是同一个逗号串,Calendar::reminderWords() 负责翻成人话。 */ ?>
|
||||
<?php $rvWords = \App\Calendar::reminderWords($ev['reminder_minutes'] ?? null); ?>
|
||||
<?php if ($rvWords !== ''): ?>
|
||||
<div class="kv-row"><dt>提醒</dt><dd><?= e($rvWords) ?></dd></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($ev['note'])): ?>
|
||||
<div class="kv-row"><dt>备注</dt><dd><?= nl2br(e((string)$ev['note'])) ?></dd></div>
|
||||
@@ -116,13 +117,57 @@ $canWrite = \App\Auth::canWrite() && !empty($ev['_can_write']);
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php /* ⚠️ 删除表单必须放在上面那个表单**外面**:<form> 套 <form> 是非法 HTML,
|
||||
浏览器会把外层表单在嵌套处切开,"保存"按钮就失效了。 */ ?>
|
||||
<form method="post" action="<?= e(url('calendar/event_del')) ?>" class="ev-del"
|
||||
onsubmit="return confirm('删除这条日程?')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$ev['id'] ?>">
|
||||
<input type="hidden" name="ym" value="<?= e($ym) ?>">
|
||||
<button class="btn btn-danger btn-sm" type="submit">删除这条日程</button>
|
||||
</form>
|
||||
浏览器会把外层表单在切开处把外层提前关掉,"保存"按钮就失效了。 */ ?>
|
||||
<?php
|
||||
/* ==================== 删除(v24 第 20 批 用户第 7 条)====================
|
||||
用户原话:「对于重复的日程,当点击删除的时候,应该提示是删除这一次,
|
||||
还是全部都删除,还是删除这一次及以后的。」
|
||||
|
||||
⚠️ 用**原生 radio + 提交**而不是 JS 弹框:不用 JS 也能选范围,
|
||||
而且"删哪一档"这件事必须让人看清文字再点(三种后果差别很大,手滑代价高)。
|
||||
⚠️ 只有**重复日程**才给这三档。不重复的给三档会让人以为"是不是还有别的次数"
|
||||
(其实就一条),反而制造困惑 —— 那种情况只给一个「删除」。
|
||||
⚠️ `occ`(点开的是哪一天)必须带上:「只删这一次」「这一次及以后」都靠它定位。
|
||||
从日历某一天点进来才有它(URL 的 ?d=);从列表直接打开就只给"全部删除"。
|
||||
⚠️ 三档的措辞与**编辑**那三档保持一致(改全部 / 改这一次及以后 / 只改这一次),
|
||||
用户学一次就够,不用记第二套说法。
|
||||
*/
|
||||
$evIsRepeat = trim((string)($ev['rrule'] ?? '')) !== '';
|
||||
?>
|
||||
<?php if ($evIsRepeat && !empty($occ)): ?>
|
||||
<form method="post" action="<?= e(url('calendar/event_del')) ?>" class="ev-del ev-del-scope"
|
||||
onsubmit="return confirm('按上面选的范围删除?')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$ev['id'] ?>">
|
||||
<input type="hidden" name="ym" value="<?= e($ym) ?>">
|
||||
<input type="hidden" name="occ" value="<?= e($occ) ?>">
|
||||
<p class="ev-del-head">这是一条<b>重复日程</b>,删哪些?</p>
|
||||
<div class="ev-del-picks">
|
||||
<label class="chk-inline">
|
||||
<input type="radio" name="scope" value="once">
|
||||
<span>只删<b>这一次</b>(<?= e($occ) ?>)</span>
|
||||
</label>
|
||||
<label class="chk-inline">
|
||||
<input type="radio" name="scope" value="future">
|
||||
<span>删<b>这一次及以后</b>(<?= e($occ) ?> 起,之前的留着)</span>
|
||||
</label>
|
||||
<label class="chk-inline">
|
||||
<input type="radio" name="scope" value="all" checked>
|
||||
<span>删<b>全部</b>(整条系列)</span>
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-danger btn-sm" type="submit">删除</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<form method="post" action="<?= e(url('calendar/event_del')) ?>" class="ev-del"
|
||||
onsubmit="return confirm('删除这条日程?')">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$ev['id'] ?>">
|
||||
<input type="hidden" name="ym" value="<?= e($ym) ?>">
|
||||
<?php if (!empty($occ)): ?><input type="hidden" name="occ" value="<?= e($occ) ?>"><?php endif; ?>
|
||||
<input type="hidden" name="scope" value="all">
|
||||
<button class="btn btn-danger btn-sm" type="submit">删除这条日程</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -215,6 +215,24 @@ $headText = $view === 'month' ? date('Y 年 n 月', $ts)
|
||||
<?php for ($h = 0; $h < 24; $h++): ?>
|
||||
<span class="tg-line"></span>
|
||||
<?php endfor; ?>
|
||||
<?php /* ==================== 当前时间红线(v24 第 20 批 用户第 6 条)====================
|
||||
用户原话:「日程模块,周视图和日视图,应该在视图中间显示一条红线,
|
||||
用来表示当前进行的时间,就是说,用一个不断从上到下的红线,
|
||||
来表示当前时间在视图上的刻度和位置。」
|
||||
|
||||
⚠️ 只在**今天那一列**画(别的日子没有"现在"可言)。
|
||||
⚠️ 位置是服务端算好的百分比(与上面 .tg-block 用的是同一套:
|
||||
列高 = 24h,所以 1 分钟 = 100/1440 %)。**不在 CSS 里写死**
|
||||
某个时间 —— 那就成了"固定的一条线",不叫"当前时间"。
|
||||
⚠️ 线的**移动**由 app.js 里的一分钟定时器负责(`initNowLines()`):
|
||||
服务端给的是"渲染那一刻"的位置,用户开着页面不走,
|
||||
过一小时线还停在那儿就荒谬了。
|
||||
⚠️ 全天日程的"全天"那一行**不画** —— 那条线表达的是"一天之内的时间刻度",
|
||||
画在全天行上会让人以为全天日程跟时间有关。 */ ?>
|
||||
<?php if ($c['is_today'] && !empty($nowTop)): ?>
|
||||
<span class="tg-now" data-now-line style="top: <?= (float)$nowTop ?>%;"
|
||||
title="现在 <?= e(date('H:i')) ?>"></span>
|
||||
<?php endif; ?>
|
||||
<?php foreach (($tgrid['timed'][$i] ?? []) as $ev): ?>
|
||||
<a class="tg-block" data-cal="<?= (int)$ev['cal'] ?>"
|
||||
style="background: <?= e((string)$ev['color']) ?>; top: <?= (float)$ev['top'] ?>%; height: <?= (float)$ev['height'] ?>%;"
|
||||
|
||||
+17
-1
@@ -146,7 +146,23 @@ $isLoggedIn = \App\Auth::check();
|
||||
</div>
|
||||
|
||||
<script src="<?= e(asset('/assets/js/app.js')) ?>"></script>
|
||||
<script src="<?= e(asset('/assets/js/person_picker.js')) ?>"></script>
|
||||
<?php /* v24 第 20 批:选人组件脚本改成**按需加载**(页面上真有 `.pp` 才拉)。
|
||||
⚠️ 理由与 Cropper.js 同一套:`person_picker.js` 只在放选人组件的页面才有用,
|
||||
而它并不小;每页都无条件带一份,等于让"没有选人组件"的页面白付流量。
|
||||
⚠️ 判据用 `document.querySelector('.pp[data-pp]')`(组件根节点的真实标记),
|
||||
**不是**含糊地找 "pp" 这两个字母(页面里 `pp` 太容易撞:分页、其它类名…)。
|
||||
⚠️ DOM 必须已经解析到组件才判断 → 脚本放在 `</body>` 前(本文件本来就是),
|
||||
所以这里能直接查;不需要 DOMContentLoaded 包一层。
|
||||
⚠️ 无 JS 兜底不受影响:组件的 `<noscript>` 块本来就是给"脚本没跑"准备的。 */ ?>
|
||||
<script>
|
||||
(function () {
|
||||
if (!document.querySelector('.pp[data-pp]')) { return; }
|
||||
var s = document.createElement('script');
|
||||
s.src = <?= json_encode(asset('/assets/js/person_picker.js')) ?>;
|
||||
s.defer = true;
|
||||
document.head.appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
<script src="<?= e(asset('/assets/js/filter.js')) ?>"></script>
|
||||
<script src="<?= e(asset('/assets/js/preview.js')) ?>"></script>
|
||||
</body>
|
||||
|
||||
+119
-20
@@ -164,32 +164,102 @@ $selAttendees = array_map('intval', (array)old('attendees', $curAttendees));
|
||||
<textarea class="input" id="note" name="note" rows="2"><?= e(old('note', $m['note'] ?? '')) ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="poster">海报</label>
|
||||
<input class="input" id="poster" name="poster" type="file" accept="image/*">
|
||||
<?php if ($isEdit && !empty($m['poster_key'])): ?>
|
||||
<span class="tip">已有海报,不选新图就保留原图。</span>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
/**
|
||||
* v24 第 20 批 第 1 条:编辑页把**已经存过的**海报 / 票根 / 现场合影**显示出来**。
|
||||
*
|
||||
* 用户原话:「观影模块,编辑观影,已经保存的海报和票根、合影,都没带进来。」
|
||||
* ⚠️ 原来这里只有两句灰字提示("已有海报,不选新图就保留原图。")——
|
||||
* 也就是说**图上你根本看不见图**:想确认"当时选的是哪张"、或想换掉它,
|
||||
* 都得先回到详情页看一眼。用户要的是"带进来",即**看得见**。
|
||||
*
|
||||
* 做法:能预览的(图片)给缩略图 + 删除;不可预览的给一个下载入口。
|
||||
* ⚠️ 换图仍然靠"选新文件"(服务端换新图时会顺手删掉旧对象,见 index.php movies/update),
|
||||
* 所以这里**不需要**"确认换这张"之类的中间态。
|
||||
* ⚠️ 每一块的删除按钮都关联到一个**独立的表单** —— 它们必须在**外层表单之外**
|
||||
* (form 嵌套会让外层表单提前关闭,"保存"键就失效了,第 20 批 第 3 条刚栽过)。
|
||||
* 按钮留在原位、用 HTML5 的 `form="id"` 关联到外面那个表单。
|
||||
*/
|
||||
$mHasPoster = $isEdit && !empty($m['poster_key']);
|
||||
$mHasTicket = $isEdit && !empty($m['ticket_key']);
|
||||
$mPhotos = $isEdit ? (array)($m['photos'] ?? []) : [];
|
||||
$mDelForms = []; // 收集起来,在表单**外面**统一渲染
|
||||
?>
|
||||
<div class="form-grid" style="margin-top:4px">
|
||||
<?php /* ---- 海报 ---- */ ?>
|
||||
<div class="field">
|
||||
<label for="poster">海报</label>
|
||||
<?php if ($mHasPoster): ?>
|
||||
<?php $mDelForms['poster'] = 'movies/media_del'; ?>
|
||||
<div class="mv-slot">
|
||||
<a href="<?= e(media_url((string)$m['poster_key'])) ?>" data-prev="image"
|
||||
data-src="<?= e(media_url((string)$m['poster_key'])) ?>"
|
||||
data-dl="<?= e(media_url((string)$m['poster_key'], ['dl' => 1])) ?>"
|
||||
data-name="海报">
|
||||
<img src="<?= e(media_url((string)$m['poster_key'])) ?>" alt="海报">
|
||||
</a>
|
||||
<button class="btn btn-ghost btn-sm" type="submit" form="mv-del-poster"
|
||||
onclick="return confirm('删掉现在这张海报?')">删除这张</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input class="input" id="poster" name="poster" type="file" accept="image/*">
|
||||
<span class="tip"><?= $mHasPoster ? '想换就选一张新的(旧的会自动删掉)。' : '还没有海报。' ?></span>
|
||||
<?php $sdSlot = 'poster'; require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label for="ticket">票根</label>
|
||||
<input class="input" id="ticket" name="ticket" type="file" accept="image/*">
|
||||
<?php if ($isEdit && !empty($m['ticket_key'])): ?>
|
||||
<span class="tip">已有票根,不选新图就保留原图。</span>
|
||||
<?php endif; ?>
|
||||
<?php /* ---- 票根 ---- */ ?>
|
||||
<div class="field">
|
||||
<label for="ticket">票根</label>
|
||||
<?php if ($mHasTicket): ?>
|
||||
<div class="mv-slot">
|
||||
<a href="<?= e(media_url((string)$m['ticket_key'])) ?>" data-prev="image"
|
||||
data-src="<?= e(media_url((string)$m['ticket_key'])) ?>"
|
||||
data-dl="<?= e(media_url((string)$m['ticket_key'], ['dl' => 1])) ?>"
|
||||
data-name="票根">
|
||||
<img src="<?= e(media_url((string)$m['ticket_key'])) ?>" alt="票根">
|
||||
</a>
|
||||
<button class="btn btn-ghost btn-sm" type="submit" form="mv-del-ticket"
|
||||
onclick="return confirm('删掉现在这张票根?')">删除这张</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input class="input" id="ticket" name="ticket" type="file" accept="image/*">
|
||||
<span class="tip"><?= $mHasTicket ? '想换就选一张新的(旧的会自动删掉)。' : '还没有票根。' ?></span>
|
||||
<?php $sdSlot = 'ticket'; require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!$isEdit): ?>
|
||||
<div class="field full">
|
||||
<label for="photo">现场照片(可多选)</label>
|
||||
<input class="input" id="photo" name="photo[]" type="file" accept="image/*" multiple>
|
||||
<span class="tip">之后也可以在详情页里逐张补传。</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php /* ---- 现场合影(用户第 1 条点名的三样之一)----
|
||||
新增时可以一次多选;**编辑时在这里逐张看、逐张删**(与新选的图合并保存)。*/ ?>
|
||||
<div class="field full">
|
||||
<label for="photo">现场合影</label>
|
||||
<?php if ($mPhotos): ?>
|
||||
<div class="photo-grid">
|
||||
<?php foreach ($mPhotos as $mp): $mpId = (int)$mp['id']; ?>
|
||||
<figure class="photo-cell">
|
||||
<a href="<?= e(media_url((string)$mp['key'])) ?>" data-prev="image"
|
||||
data-src="<?= e(media_url((string)$mp['key'])) ?>"
|
||||
data-dl="<?= e(media_url((string)$mp['key'], ['dl' => 1])) ?>"
|
||||
data-name="现场照片">
|
||||
<img src="<?= e(media_url((string)$mp['key'])) ?>" alt="">
|
||||
</a>
|
||||
<button class="btn btn-ghost btn-sm" type="submit" form="mv-del-photo-<?= $mpId ?>"
|
||||
onclick="return confirm('删掉这张照片?')">删除</button>
|
||||
</figure>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($isEdit): ?>
|
||||
<input class="input" id="photo" name="photo[]" type="file" accept="image/*" multiple>
|
||||
<span class="tip">可以继续加(可多选);删掉的在保存后生效。</span>
|
||||
<?php $sdSlot = 'photo'; $sdHint = '也可以把文件直接拖到这里 —— 拖一个就开始上传,可以接着再拖,最后点「保存」一起记下。'; require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
<?php else: ?>
|
||||
<input class="input" id="photo" name="photo[]" type="file" accept="image/*" multiple>
|
||||
<span class="tip">可以多选;之后也能在详情页里逐张补传。</span>
|
||||
<?php $sdSlot = 'photo'; $sdHint = '也可以把文件直接拖到这里 —— 拖一个就开始上传,可以接着再拖,最后点「记下这场电影」一起记下。'; require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="btn-row">
|
||||
<div class="btn-row">
|
||||
<button type="submit" class="btn"><?= $isEdit ? '保存' : '记下这场电影' ?></button>
|
||||
<a class="btn btn-ghost"
|
||||
href="<?= e($isEdit ? url('movies/show', ['id' => (int)$m['id']]) : url('movies')) ?>">取消</a>
|
||||
@@ -197,6 +267,35 @@ $selAttendees = array_map('intval', (array)old('attendees', $curAttendees));
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php /* ---- 删除海报 / 票根 / 合影的**独立表单**(必须在外层表单之外)----
|
||||
⚠️ 见本文件顶部关于 form 嵌套的说明:按钮用 `form="id"` 指过来,按钮位置不变。
|
||||
⚠️ 关联表单要自带全部字段(csrf / 记录 id / 照片 id)—— 提交的是**它自己**的字段。 */ ?>
|
||||
<?php if ($mHasPoster): ?>
|
||||
<form id="mv-del-poster" method="post" action="<?= e(url('movies/media_del')) ?>" class="form-outside">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<input type="hidden" name="slot" value="poster">
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php if ($mHasTicket): ?>
|
||||
<form id="mv-del-ticket" method="post" action="<?= e(url('movies/media_del')) ?>" class="form-outside">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<input type="hidden" name="slot" value="ticket">
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($mPhotos as $mp): ?>
|
||||
<form id="mv-del-photo-<?= (int)$mp['id'] ?>" method="post"
|
||||
action="<?= e(url('movies/photo_del')) ?>" class="form-outside">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$m['id'] ?>">
|
||||
<input type="hidden" name="photo_id" value="<?= (int)$mp['id'] ?>">
|
||||
<?php /* 从编辑页删的 → 删完回编辑页(见 index.php movies/photo_del 的说明) */ ?>
|
||||
<input type="hidden" name="back" value="edit">
|
||||
</form>
|
||||
<?php endforeach; ?>
|
||||
<?php unset($mHasPoster, $mHasTicket, $mPhotos, $mDelForms, $mp, $mpId); ?>
|
||||
|
||||
<?php /* ---- 新建影院:弹框(不跳转,见上)---- */ ?>
|
||||
<div class="cn-modal" id="cinemaNewModal" hidden>
|
||||
<div class="cn-mask" data-cn-close></div>
|
||||
|
||||
@@ -101,6 +101,7 @@ $canWrite = \App\Auth::canWrite();
|
||||
<div class="field full">
|
||||
<label for="rs-file">附件(可多选)</label>
|
||||
<input class="input" type="file" id="rs-file" name="attach[]" multiple>
|
||||
<?php $sdSlot = 'photo'; $sdHint = '也可以把文件直接拖到这里 —— 拖一个就开始上传,可以接着再拖,最后点「保存」/「新建」一起记下。'; require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
|
||||
+21
-22
@@ -4,12 +4,23 @@
|
||||
*
|
||||
* 与「系统设置」(?r=settings,仅管理员)的分工:
|
||||
* · 系统设置改一次**全家都变**:slogan 兜底、附件存储、运行环境、**公共分组**;
|
||||
* · 个人设置只影响**我自己看到的样子**:我怎么称呼家人、我自己的分组。
|
||||
* · 个人设置只影响**我自己看到的样子**:我自己的分组、天气地点、首页与导航的模块。
|
||||
*
|
||||
* ⚠️ 「我的资料」表单**不在这页**(用户明确要求合并):
|
||||
* 「编辑成员」和「改自己的资料」本来就是一件事,所以只剩
|
||||
* `templates/people/form.php` 那一张表单,两个入口都指过去 ——
|
||||
* 这页顶部那个按钮,和名册卡片上的「编辑资料」,落到的完全是同一个页面。
|
||||
* `templates/people/form.php` 那一张表单,两个入口都指过去。
|
||||
*
|
||||
* ────────────────────────────────────────────────────────────────
|
||||
* v24 第 20 批 第 2 条(用户明确"不要了"的三处,都已删除):
|
||||
* ① 页头那句副标题「只影响你自己看到的样子」—— **删掉**(用户点名);
|
||||
* ② 「我怎么称呼家人」整张卡片(templates/settings/_nicknames.php)
|
||||
* —— **不再加载**。⚠️ 「我叫他什么」这个能力本身还在(名册/详情页里照常出现、
|
||||
* `person_name()` 照常按昵称显示);只是不再在这页单独占一张卡。
|
||||
* 所以是"撤掉入口"而不是"删功能",数据一个字没动。
|
||||
* ③ 「我的资料」卡片(姓名/头像/生日那句 + 两个按钮)—— **删掉**。
|
||||
* ⚠️ 改资料的入口在别处都还有:右上角头像下拉 →「个人设置」进来后有页头那个
|
||||
* 「编辑我的资料」按钮,名册与详情页也各有「编辑资料」;所以不会"进不去"。
|
||||
* 页头那个按钮**保留**(它就是"我的资料"现在唯一的入口,删了就真没地方点了)。
|
||||
*/
|
||||
$groupCounts = $groupCounts ?? [];
|
||||
$groupTotal = count($groups);
|
||||
@@ -17,7 +28,7 @@ $rosterCount = $rosterCount ?? 0;
|
||||
?>
|
||||
<div class="page-head">
|
||||
<h1>个人设置</h1>
|
||||
<span class="sub">只影响你自己看到的样子</span>
|
||||
<?php /* v24 第 20 批 第 2 条:副标题「只影响你自己看到的样子」按用户要求删除 */ ?>
|
||||
<div class="actions">
|
||||
<a class="btn btn-sm" href="<?= e(url('people/edit', ['id' => (int)$person['id']])) ?>">编辑我的资料</a>
|
||||
<a class="btn btn-ghost btn-sm" href="<?= e(url('people/show', ['id' => (int)$person['id']])) ?>">看我的详情页</a>
|
||||
@@ -27,24 +38,10 @@ $rosterCount = $rosterCount ?? 0;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-pad">
|
||||
<h2 class="section-title">我的资料 <span class="hint">姓名、头像、生日、联系方式</span></h2>
|
||||
<p class="small muted" style="margin:0 0 14px">
|
||||
<?= e((string)$person['name']) ?>
|
||||
<?php if (trim((string)($person['slogan'] ?? '')) !== ''): ?>
|
||||
· 「<?= e((string)$person['slogan']) ?>」
|
||||
<?php endif; ?>
|
||||
<br>
|
||||
资料的编辑表单只有一张,和「编辑成员」是同一个页面 —— 从这里进去改的就是你自己。
|
||||
</p>
|
||||
<div class="btn-row" style="margin-top:0">
|
||||
<a class="btn" href="<?= e(url('people/edit', ['id' => (int)$person['id']])) ?>">编辑我的资料</a>
|
||||
<a class="btn btn-ghost" href="<?= e(url('auth/password')) ?>">改登录密码</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-gap"></div>
|
||||
<?php require __DIR__ . '/_nicknames.php'; ?>
|
||||
<?php /* v24 第 20 批 第 2 条:原来这里有两块,用户要求都删掉 ——
|
||||
① 「我的资料」卡片(姓名 + slogan + 「编辑我的资料 / 改登录密码」两个按钮)
|
||||
② 「我怎么称呼家人」卡片(require _nicknames.php)
|
||||
下面直接接天气地点。改登录密码在右上角头像下拉里也有入口。 */ ?>
|
||||
|
||||
<!-- ==================== 天气地点(用户 2026-10-03 第 1 条)==================== -->
|
||||
<?php /* 原话:「天气这个这样吧,让用户在个人设置中填写自己的城市。」
|
||||
@@ -54,6 +51,8 @@ $rosterCount = $rosterCount ?? 0;
|
||||
<div class="card card-pad" id="weather" style="margin-top:20px">
|
||||
<h2 class="section-title">
|
||||
天气 · 我所在的城市
|
||||
<?php /* v24 第 20 批 第 2 条:这里的 "只影响你自己首页上的那一格" 保留 ——
|
||||
用户点名要删的是页头那句副标题,不是这一处。 */ ?>
|
||||
<span class="hint">只影响你自己首页上的那一格</span>
|
||||
</h2>
|
||||
<p class="small muted" style="margin:0 0 16px">
|
||||
|
||||
@@ -259,6 +259,7 @@ $isChild = !empty($t['parent_id']);
|
||||
<input type="hidden" name="id" value="<?= (int)$t['id'] ?>">
|
||||
<input type="file" name="attach[]" multiple required>
|
||||
<button class="btn btn-sm" type="submit">上传附件</button>
|
||||
<?php $sdSlot = 'photo'; $sdHint = '也可以把文件直接拖到这里 —— 拖一个就开始上传,可以接着再拖,最后点「上传附件」一起记下。'; require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
@@ -191,30 +191,34 @@ $amap = \App\Geo::jsConfig();
|
||||
value="<?= e(old('trip_no', $n['trip_no'] ?? '')) ?>" placeholder="如:G1234、CA1234">
|
||||
<span class="tip">座位这类同团不同座的信息就不记了。</span>
|
||||
</div>
|
||||
<?php /* 线路名与承运方:火车/飞机时最有意义(「京沪高铁」「中国铁路」),
|
||||
也允许其他方式填(比如「轮渡 5 号线」)—— 所以不按方式隐藏。 */ ?>
|
||||
<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-carrier">承运方</label>
|
||||
<input class="input" id="nf-carrier" name="carrier"
|
||||
value="<?= e(old('carrier', $n['carrier'] ?? '')) ?>" placeholder="如:中国铁路、国航">
|
||||
</div>
|
||||
<?php /* 线路名与承运方:v24 第 20 批 第 5 条 用户明确「都不要了」——
|
||||
原话:「添加行程,线路名、承运方,都不要了。」
|
||||
⚠️ 数据库那两列**保留**(`line_name` / `carrier`):TripLog 迁过来的
|
||||
老数据还在里面(第 19 批补迁又填了一批),删列不可逆。
|
||||
这里只是**界面不再收/不再显示**;saveNode 收到的空值会
|
||||
保持原值不动(见 Trip::saveNode 里对这两列的说明)。 */ ?>
|
||||
|
||||
<?php /* ---- 出发地 / 到达地:高德联想 + 详细地址(用户第 17 批 第 3 条)----
|
||||
用户原话:「出发地和到达地,都用高德地图的接口,记录详细地址」。 */ ?>
|
||||
<div class="field">
|
||||
<?php $locPrefix = 'from'; $locLabel = '出发地'; $locIcon = '🚩';
|
||||
$locPlaceholder = '输入地址,如「家里」「青岛北站」'; $locRow = $fromRow;
|
||||
require __DIR__ . '/_loc_field.php'; ?>
|
||||
</div>
|
||||
<div class="field">
|
||||
<?php $locPrefix = 'to'; $locLabel = '到达地'; $locIcon = '🏁';
|
||||
$locPlaceholder = '输入地址,如「青岛北站」「栈桥」'; $locRow = $toRow;
|
||||
require __DIR__ . '/_loc_field.php'; ?>
|
||||
用户原话:「出发地和到达地,都用高德地图的接口,记录详细地址」。
|
||||
v24 第 20 批 第 5 条:「出发地和到达地,应该在同一行。」
|
||||
⚠️ 做法:把两个 .field 包进一个 `.loc-row`(`grid-column: 1/-1` 占满整行),
|
||||
行内再分两列 —— 不能只把两个 field 相邻放着"指望它们拼一行":
|
||||
`.form-grid` 是两列网格,每个 .field 各占一格,
|
||||
两格之间会有 gap、且窄屏折行后顺序会乱。
|
||||
包一层显式的一行两列,宽窄屏行为都可预期。 */ ?>
|
||||
<div class="field full">
|
||||
<div class="loc-row">
|
||||
<div class="field">
|
||||
<?php $locPrefix = 'from'; $locLabel = '出发地'; $locIcon = '🚩';
|
||||
$locPlaceholder = '输入地址,如「家里」「青岛北站」'; $locRow = $fromRow;
|
||||
require __DIR__ . '/_loc_field.php'; ?>
|
||||
</div>
|
||||
<div class="field">
|
||||
<?php $locPrefix = 'to'; $locLabel = '到达地'; $locIcon = '🏁';
|
||||
$locPlaceholder = '输入地址,如「青岛北站」「栈桥」'; $locRow = $toRow;
|
||||
require __DIR__ . '/_loc_field.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
@@ -257,12 +261,28 @@ $amap = \App\Geo::jsConfig();
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 照片(用户第 16 批 第 3 条:图片视频都针对一次具体行程)---- */ ?>
|
||||
<?php /* ---- 照片(用户第 16 批 第 3 条:图片视频都针对一次具体行程)----
|
||||
🔴 v24 第 20 批 第 3 条修的是这里:用户报「编辑行程,点击保存没有反应」。
|
||||
根因是**这里原来每个缩略图上套了一个 `<form>`**,而整块又在外面那个
|
||||
行程表单**里面** —— HTML 不允许 form 嵌套:浏览器解析到内层 `</form>` 时
|
||||
会把**外层表单提前关掉**,于是紧随其后的「保存」按钮**掉出了表单**
|
||||
(真机实测 `button.form === null`),点了自然毫无反应。
|
||||
⚠️ 源码里完全看不出来(标签看着都对),只有真浏览器量 `btn.form` 才现形;
|
||||
而且**只有这段行程已经有照片时**才触发 —— 所以"有时候好、有时候坏"。
|
||||
|
||||
修法:用 HTML5 的 `form="id"` 属性。删除按钮**留在原位**(外观、布局都不变),
|
||||
但把它**关联到页面末尾那个独立表单**上 —— 不需要真的套一层 <form>,
|
||||
也就不会破坏外层表单。(比"把 <form> 搬到外面再用绝对定位摆回去"稳得多:
|
||||
那种做法一旦父容器有 transform/filter,定位元素会改锚点、按钮就飘了。)
|
||||
⚠️ 每个缩略图的 `photo_id` 不同 → 每张一个独立的删除表单(id 带 $ph['id'])。*/ ?>
|
||||
<div class="field full">
|
||||
<label for="nf-photo">这一段行程的照片</label>
|
||||
<?php $delForms = []; ?>
|
||||
<?php if ($isEdit && !empty($n['attachs'])): ?>
|
||||
<div class="photo-grid">
|
||||
<?php foreach ($n['attachs'] as $ph): ?>
|
||||
<?php $phDelId = 'nf-phdel-' . (int)$ph['id'];
|
||||
$delForms[] = $ph; ?>
|
||||
<figure class="photo-cell">
|
||||
<a href="<?= e(media_url((string)$ph['key'])) ?>" data-prev="image"
|
||||
data-src="<?= e(media_url((string)$ph['key'])) ?>"
|
||||
@@ -270,19 +290,16 @@ $amap = \App\Geo::jsConfig();
|
||||
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>
|
||||
<button class="btn btn-ghost btn-sm" type="submit"
|
||||
form="<?= e($phDelId) ?>"
|
||||
onclick="return confirm('删掉这张照片?')">删除</button>
|
||||
</figure>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input class="input" type="file" id="nf-photo" name="photo[]" accept="image/*" multiple>
|
||||
<span class="tip">可多选;图片、视频都会存到这段行程下面。</span>
|
||||
<?php $sdSlot = 'photo'; $sdHint = '也可以把文件直接拖到这里 —— 拖一个就开始上传,可以接着再拖,最后点「保存」一起记下。'; require __DIR__ . '/../_stage_drop.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -293,6 +310,24 @@ $amap = \App\Geo::jsConfig();
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?php /* ---- 删除照片用的**独立表单**(在外层行程表单之外)----
|
||||
见上面 photo-grid 的说明:用 `form="id"` 把按钮关联到这里,
|
||||
既保住"不嵌套",又保住按钮的视觉位置。
|
||||
⚠️ 关联表单必须**自带全部字段**(csrf / 宿主的 id / photo_id)——
|
||||
按 `form=` 关联时,浏览器提交的是**那个表单自己的**字段,
|
||||
不会把外层表单的字段带过去(少了 csrf 会被 csrf_check 挡下,
|
||||
少了 id 会定位不到旅程)。所以这里逐个写全。
|
||||
⚠️ 平时是全空的(没有照片就没有这些表单)。 */ ?>
|
||||
<?php foreach (($delForms ?? []) as $ph): ?>
|
||||
<form id="nf-phdel-<?= (int)$ph['id'] ?>" method="post"
|
||||
action="<?= e(url('trips/photo_del')) ?>" class="form-outside">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int)$ev['id'] ?>">
|
||||
<input type="hidden" name="photo_id" value="<?= (int)$ph['id'] ?>">
|
||||
</form>
|
||||
<?php endforeach; ?>
|
||||
<?php unset($delForms); ?>
|
||||
|
||||
<?php /* ==================== 花销(只在编辑时出现)====================
|
||||
用户第 2 条要"一共花了多少钱"、第 16 批确认「行程级花销记录」。
|
||||
⚠️ 新增时不给花销面板:那时行程 id 还不存在,花销挂不上(先存行程再加花销,
|
||||
|
||||
Reference in New Issue
Block a user