v50:旅程题图裁剪修好(AJAX 片段漏绑)/ 修正表单换最新控件+取消 / 打标签误报修复 / 名册按钮编辑蓝删除红
用户 2026-10-14 四条。
① 旅程详情点点「编辑」,里面的图片改不了、拖入/选中都不弹裁剪框
用户原话:「应该弹出来一个让我裁剪的那个框。但是现在却没有那个裁剪的框。」
**真根因**(assets/js/app.js):裁剪那段 IIFE 是**首屏一次性**的 ——
`var inputs = querySelectorAll('[data-crop-ratio]'); if (!inputs.length) return;`
在脚本加载那一刻扫一遍,扫不到就整段放弃。而「编辑旅程」是 **AJAX 取片段、
innerHTML 塞进弹框**的(templates/trips/show.php 的 loadInto)⇒ 详情页首屏
**没有**题图框 ⇒ 当场 return ⇒ 片段里的框**从来没有 change 监听**
⇒ 拖入/选中什么都不发生。
修法:拆成 `bindOne(input)`(幂等,打 `data-crop-bound` 标记)+ `scan(scope)`;
首屏调一次 + **MutationObserver 补挂**;库的按需加载改成"首次真要用时才拉"。
⚠️ 与「拖动上传」那段(bindInput/scan/MutationObserver)**同一手法** ——
它早就解决了"动态插进来的文件框也要接得住",裁剪这段当年漏了这层。
② 旅程「修正」表单(templates/trips/_revise_form.php)
用户三条:「开始和结束时间…应该改成我们最新的控件样式」「上面记录实际执行情况
那一段话说明也不要了」「记录这次修正的按钮应该放在右边,另外应该加一个取消」。
· 起止换 `$dtPlugin = 'antd'`(与 node_form.php 同款;当年 v41 刻意没换,现按用户要求统一)
· 删 show.php 里 `.rev-tip` 整段
· 按钮行 `rev-foot` 靠右 + 新增 `[data-rev-cancel]`;取消由 show.php 的**事件委托**
处理(`details.rev-open` 收起)—— 不用内联脚本(片段会被 AJAX 换进来)
③ 名册打标签误报「这个人不在你的名册里」
用户原话:「我搜索出来一个人…给他打标签,它会显示说这个人不在我的名册里,
其实他是在我的名册里的。」
**真根因**(index.php 的 people/tags):`isset(PersonRoster::visibleIds(...)[$pid])`,
而 `visibleIds()` 返回的是 **id 值列表** `[2,3,4,5]`(键是 0,1,2,3)——
把值列表当 id→值 映射用。只有 `$pid` 恰好等于数组下标时才为真:名册里人少时
(下标<pid)**谁都判"不在名册"**;一般情况只有"第 pid+1 个"碰巧过。
修法:改用 `in_array(..., true)`(与同文件 `inMyRoster` 写法一致)。
⚠️ **没改 `visibleIds()` 的返回形状** —— 另一处调用已是正确的 in_array,改形状会
静默弄坏它。陷阱:`PersonGroup::visibleIds()` 是集合、`PersonRoster::visibleIds()`
是值列表,**同名不同形状**,已在两处注释里写明。
(附带确认:不在名册的人本来就不出现在列表里,用户"不在名册就该搜不出来"本就成立。)
④ 名册列表按钮:编辑蓝底、删除红底
用**作用域类** `.person-edit`(var(--blue)) / `.person-del`(var(--red)) 三处形态都写,
不动全局 .btn-edit/.btn-danger 的语义(v41 踩过"裸类改色波及影院管理页")。
hover 在这里补 `filter: brightness(.92)`(文末"全站悬停统一"段是白名单写的)。
验证(新增 2 套件 + 更新 1 处旧断言):
· _fb_v50_ui.py(22 条真机断言:题图绑定/选中与拖入都弹裁剪框 16:9、修正 antd+无说明+
靠右+取消能收起、名册内 3 人打标签均成功且不误报、编辑蓝删除红)
· _b50_falsify.py(**10 注入 / 21 断言全抓**)
· _fb_e2e.py 的 ★第28-6条断言随 v50-4 升级(原查 var(--brand-soft),现查蓝+红作用域类)
· familybook_deploy.sh 加 v50 静态断言段(含两条"旧写法必须已不在"的反向断言)
· e2e 1511/0、几何、各批反向验证全绿;**v50 四条 22/0**、**v50 反向验证 21/0**;
全流水线 **PUSH_RC=0**,部署脚本 0 FAIL(含"第50批形态");线上 6 文件 md5 逐字节一致;
线上门禁诊断 16 项全 YES,临时页已删并复验 404
⚠️ 收尾时发现并修掉一个**自设陷阱**:我在 show.php 的删除说明里**整句引用了**用户点名要删的
那句原话 ⇒ 朴素 strpos 判"已删"时会命中我自己的注释(线上诊断 C8=NO)。
部署断言用 `_code_has`(剥注释)所以是对的,但这类字串留在注释里迟早坑到别的检查
⇒ 已改成不整句照抄。**改注释也改了文件哈希 ⇒ 又重跑一次全量部署**。
⚠️ 流水线末段唯一"失败"同 v49:沙箱安全删除门禁(按轮次累计删满 50 就终止进程);
单独重跑 _fb_v46_carddav.py = 29/0 ⇒ 环境护栏,与本次改动无关。
This commit is contained in:
+19
-1
@@ -108,7 +108,25 @@ final class PersonRoster
|
|||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 只取 id 列表(判断"看不看得见"时用) */
|
/**
|
||||||
|
* 可见人员的 id **列表**(`[2,3,4,5]`),判断"看不看得见"时**必须用 `in_array`**。
|
||||||
|
*
|
||||||
|
* 🔴🔴 v50(用户 2026-10-14)——**这里踩过一个真 bug**:
|
||||||
|
* 本方法返回的是**值列表**(键是 0,1,2,3),而 index.php 的 people/tags 里
|
||||||
|
* 写的是 `isset(PersonRoster::visibleIds(...)[$pid])` ——
|
||||||
|
* **把值列表当成了 id → 值的映射**。只有当 `$pid` 恰好等于数组下标时才为真:
|
||||||
|
* · 名册里人少时(下标远小于 pid)⇒ 任何 pid 都判"不在名册";
|
||||||
|
* · 一般情况下 ⇒ 只有"第 pid+1 个"那个人碰巧对得上。
|
||||||
|
* 症状正是用户报的:「我搜索出来一个人、给他打标签,它提示这个人不在我的名册里,
|
||||||
|
* 其实他是在我的名册里的。」
|
||||||
|
* ⇒ 该处已改用 `in_array($pid, …, true)`(与 index.php 另一处既有写法一致)。
|
||||||
|
*
|
||||||
|
* ⚠️⚠️ 陷阱提醒:`PersonGroup::visibleIds()` 返回的是**键=id、值=true 的集合**
|
||||||
|
* (设计给 `isset($set[$id])` 用)。两个类**同名不同形状** ——
|
||||||
|
* 以后看到 `visibleIds` 先确认是哪个类,别把两种用法混着抄。
|
||||||
|
* (本方法**刻意不改形状**:另一处调用 `in_array` 已是正确用法,
|
||||||
|
* 改形状会静默弄坏它。要改成集合就得两处一起动,收益不值这个风险。)
|
||||||
|
*/
|
||||||
public static function visibleIds(int $familyId, int $ownerId): array
|
public static function visibleIds(int $familyId, int $ownerId): array
|
||||||
{
|
{
|
||||||
return array_keys(self::mine($familyId, $ownerId));
|
return array_keys(self::mine($familyId, $ownerId));
|
||||||
|
|||||||
+29
-6
@@ -3338,6 +3338,16 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
|||||||
box-shadow: var(--shadow-l, 0 12px 32px rgba(0, 0, 0, .16));
|
box-shadow: var(--shadow-l, 0 12px 32px rgba(0, 0, 0, .16));
|
||||||
}
|
}
|
||||||
.rev-form .btn-row { margin-top: 10px; }
|
.rev-form .btn-row { margin-top: 10px; }
|
||||||
|
/* 🔴 v50(用户 2026-10-14 第 3 条):修正表单的按钮行**靠右**,并补一颗「取消」。
|
||||||
|
——「记录这次修正的按钮应该放在右边,另外应该加一个关闭/取消的按钮。」
|
||||||
|
做法:`margin-left:auto` 作用在**按钮行里第一颗会被渲染的按钮**上,把整排推到右端。
|
||||||
|
两类情况(有没有那句"已记过 N 次"的说明)各写一条 —— 只有第一颗儿子适合
|
||||||
|
`margin-left:auto`(用 `:first-child` 精确命中),否则中间那些也会各带一个 auto、
|
||||||
|
间距被拉散。
|
||||||
|
⚠️ 不用 `.cal-modal-foot .btn[type=submit]{margin-left:auto}` 那条(本表单不在模态里)。
|
||||||
|
⚠️ 窄屏允许换行,不然三样挤在一行会把按钮压变形。 */
|
||||||
|
.rev-form .rev-foot { align-items: center; flex-wrap: wrap; }
|
||||||
|
.rev-form .rev-foot > *:first-child { margin-left: auto; }
|
||||||
|
|
||||||
/* 修正历史对比表 */
|
/* 修正历史对比表 */
|
||||||
.rev-hist { margin-top: 8px; }
|
.rev-hist { margin-top: 8px; }
|
||||||
@@ -4842,13 +4852,26 @@ button.btn, a.btn, summary.btn, .seg-btn, .icon-btn, .rv-act-btn, .cs-btn, .bf-b
|
|||||||
border-color: var(--line);
|
border-color: var(--line);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
/* 编辑 = 品牌色淡底(主要动作用它) */
|
/* 🔴🔴 v50(用户 2026-10-14 第 4 条):「名册列表里面的按钮也是一样的,
|
||||||
.person-edit {
|
编辑是**蓝色**的背景,删除是**红色**的背景。」
|
||||||
background: var(--brand-soft, #FDF3EC);
|
—— 与旅程详情页/v40 影院管理页**同一套口径**:编辑蓝、删除红,都是**实心**色块。
|
||||||
border-color: color-mix(in srgb, var(--brand) 28%, transparent);
|
⚠️ 用**作用域类**(`.person-edit` / `.person-del`),不去改全局 `.btn-edit`
|
||||||
color: var(--brand-ink);
|
/ `.btn-danger` 的语义 —— 本项目踩过"裸类改色波及别页"的坑
|
||||||
|
(v41 把影院管理页的蓝色「编辑」改成了黄,见前面那段血泪注释)。
|
||||||
|
⚠️ 三处形态都写(裸类 / `a.` / `button.`):`.btn`、`a.btn`、`button.btn`
|
||||||
|
的特异性不同,漏一种就有一种是错的(v40 已验过这条规律)。
|
||||||
|
⚠️ `.person-del` 原来只是 `.btn-danger`(**淡红描边**)—— 用户这次要的是**实心红**,
|
||||||
|
所以在这里覆盖它;`.btn-danger` 本身的语义不动。 */
|
||||||
|
.person-edit, a.person-edit, button.person-edit {
|
||||||
|
background: var(--blue); color: #fff; border-color: transparent;
|
||||||
}
|
}
|
||||||
.person-edit:hover { background: color-mix(in srgb, var(--brand) 16%, #fff); }
|
.person-del, a.person-del, button.person-del {
|
||||||
|
background: var(--red); color: #fff; border-color: transparent;
|
||||||
|
}
|
||||||
|
/* hover:同色系加深(与文末「全站悬停统一」段同一手法)——
|
||||||
|
⚠️ 必须在这里补,因为那段是按类名白名单写的,`.person-edit/.person-del` 不在里面,
|
||||||
|
不补的话 hover 会退回 `.btn` 的品牌色/或 `.person-edit` 上面那条淡底规则。 */
|
||||||
|
.person-edit:hover, .person-del:hover { filter: brightness(.92); }
|
||||||
/* 标签 = 中性灰底;有标签时稍深一点,一眼能看出"这个人是有标记的" */
|
/* 标签 = 中性灰底;有标签时稍深一点,一眼能看出"这个人是有标记的" */
|
||||||
.person-tag-btn { background: var(--line-2); color: var(--muted); }
|
.person-tag-btn { background: var(--line-2); color: var(--muted); }
|
||||||
.person-tag-btn:hover { background: var(--line); color: var(--ink); }
|
.person-tag-btn:hover { background: var(--line); color: var(--ink); }
|
||||||
|
|||||||
+66
-16
@@ -48,11 +48,31 @@
|
|||||||
// 服务端(app/Upload.php)继续按归一化矩形裁,绕过前端也存不进比例不对的图。
|
// 服务端(app/Upload.php)继续按归一化矩形裁,绕过前端也存不进比例不对的图。
|
||||||
// ⚠️ Cropper.js 是按需加载的:页面上真有 data-crop-ratio 的文件框才去拉这个库
|
// ⚠️ Cropper.js 是按需加载的:页面上真有 data-crop-ratio 的文件框才去拉这个库
|
||||||
// (与 Semi 那套同一思路:不能让没用到裁剪的页面白拉)。
|
// (与 Semi 那套同一思路:不能让没用到裁剪的页面白拉)。
|
||||||
|
//
|
||||||
|
// 🔴🔴 v50(用户 2026-10-14 第 1 条)——**根因级修法:绑定可重复、可增量**。
|
||||||
|
// 用户原话:「旅程的详情页面,当我点击编辑的时候,里面的图片没有办法改变。
|
||||||
|
// 实际上当我把图片拖进去,或者选中一个图片的时候,应该弹出来一个让我
|
||||||
|
// 裁剪的那个框。但是现在却没有那个裁剪的框。」
|
||||||
|
//
|
||||||
|
// 真根因:这段以前是**一次性**的 ——
|
||||||
|
// `var inputs = querySelectorAll('[data-crop-ratio]'); if (!inputs.length) return;`
|
||||||
|
// 在**脚本加载那一刻**扫一遍,扫到就绑、扫不到(或后来才出现的)**永远不绑**。
|
||||||
|
// 而旅程详情页的「编辑旅程」是 **AJAX 取片段、innerHTML 塞进弹框**的
|
||||||
|
// (见 templates/trips/show.php 的 loadInto):
|
||||||
|
// 详情页首屏**没有**任何 `[data-crop-ratio]`(题图框在片段里)⇒ 这里当场
|
||||||
|
// `return` ⇒ 弹框里的题图框**从来没有 change 监听** ⇒
|
||||||
|
// 拖入/选中图片什么都不发生(`bindZone` 那条拖动逻辑会派发 change,
|
||||||
|
// 但**没人听**)⇒ 「图片改不了」。
|
||||||
|
//
|
||||||
|
// ⇒ 拆成两部分:
|
||||||
|
// ① `bindOne(input)`:给**单个**文件框绑 change(幂等,带 `data-crop-bound` 标记);
|
||||||
|
// ② `scan(scope)`:扫一遍并绑没绑过的;首屏调一次 + MutationObserver 补挂。
|
||||||
|
// ⚠️ 与「拖动上传」那段(bindInput/scan/MutationObserver)**同一手法** ——
|
||||||
|
// 它早就解决了同样的问题("动态插进来的文件框也要接得住"),
|
||||||
|
// 裁剪这段当年漏了这层。别再用"首屏一次性"的写法。
|
||||||
|
// ⚠️ 库(Cropper.js)的按需加载时机也要跟着改:**首次真要用时才拉**
|
||||||
|
// (不能在"扫到 0 个框"时就 return 掉 —— 那样动态来的框再也不会拉库)。
|
||||||
(function () {
|
(function () {
|
||||||
var inputs = Array.prototype.slice.call(
|
|
||||||
document.querySelectorAll('input[type="file"][data-crop-ratio]'));
|
|
||||||
if (!inputs.length) { return; }
|
|
||||||
|
|
||||||
var meta = document.querySelector('meta[name="fb-cropper"]');
|
var meta = document.querySelector('meta[name="fb-cropper"]');
|
||||||
if (!meta) { return; } // 页面没登记资源地址(不该发生,见 layout.php)
|
if (!meta) { return; } // 页面没登记资源地址(不该发生,见 layout.php)
|
||||||
var conf;
|
var conf;
|
||||||
@@ -284,19 +304,49 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
/**
|
||||||
(function (input) {
|
* 给**一个**文件框绑裁剪(幂等:已绑过的打标记跳过)。
|
||||||
input.addEventListener('change', function () {
|
* 只有带 `data-crop-ratio` 的框才需要裁剪 —— 其它 file input 不碰。
|
||||||
var f = input.files && input.files[0];
|
*/
|
||||||
if (!f) { return; }
|
function bindOne(input) {
|
||||||
if (!/^image\//.test(f.type)) {
|
if (!input || input.tagName !== 'INPUT' || input.type !== 'file') { return; }
|
||||||
alert('请选择图片文件。');
|
if (!input.hasAttribute('data-crop-ratio')) { return; }
|
||||||
input.value = '';
|
if (input.getAttribute('data-crop-bound') === '1') { return; } // 已绑,跳过
|
||||||
return;
|
input.setAttribute('data-crop-bound', '1');
|
||||||
}
|
input.addEventListener('change', function () {
|
||||||
open(input, f);
|
var f = input.files && input.files[0];
|
||||||
|
if (!f) { return; }
|
||||||
|
if (!/^image\//.test(f.type)) {
|
||||||
|
alert('请选择图片文件。');
|
||||||
|
input.value = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
open(input, f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 扫一遍(可限定 scope)并绑上所有还没绑的 `[data-crop-ratio]` 框 */
|
||||||
|
function scan(scope) {
|
||||||
|
Array.prototype.forEach.call(
|
||||||
|
(scope || document).querySelectorAll('input[type="file"][data-crop-ratio]'),
|
||||||
|
bindOne);
|
||||||
|
}
|
||||||
|
|
||||||
|
scan(); // 首屏
|
||||||
|
|
||||||
|
/* 🔴 v50:**动态插进来的框也要接得住**(旅程「编辑旅程」是 AJAX 片段)。
|
||||||
|
⚠️ 与上面「拖动上传」那段用同一招:MutationObserver 补挂。
|
||||||
|
子树监听:片段是塞进某个容器里的,框在子树深处。 */
|
||||||
|
if (window.MutationObserver) {
|
||||||
|
new MutationObserver(function (muts) {
|
||||||
|
muts.forEach(function (m) {
|
||||||
|
Array.prototype.forEach.call(m.addedNodes || [], function (n) {
|
||||||
|
if (!n.querySelectorAll) { return; }
|
||||||
|
if (n.tagName === 'INPUT' && n.type === 'file') { bindOne(n); }
|
||||||
|
scan(n);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})(inputs[i]);
|
}).observe(document.body, { childList: true, subtree: true });
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@@ -1175,7 +1175,20 @@ switch ($r) {
|
|||||||
}
|
}
|
||||||
// ⚠️ 只允许给**在我名册里的人**打标签 ——
|
// ⚠️ 只允许给**在我名册里的人**打标签 ——
|
||||||
// 否则会给一份"看不见的名册条目"写上标签,用户永远看不到、也删不掉。
|
// 否则会给一份"看不见的名册条目"写上标签,用户永远看不到、也删不掉。
|
||||||
if (!isset(PersonRoster::visibleIds($familyId, $me)[$pid])) {
|
//
|
||||||
|
// 🔴🔴 v50(用户 2026-10-14)——**这里原来是个真 bug**,用户原话:
|
||||||
|
// 「如果我把人给搜索出来的话,然后选择给他打标签,上面它会显示一个说
|
||||||
|
// 这个人不在我的名册里,其实他是在我的名册里的。」
|
||||||
|
// 原写法是 `isset(PersonRoster::visibleIds(...)[$pid])`,而
|
||||||
|
// `visibleIds()` 返回的是 **id 值列表**(`[2,3,4,5]`,键是 0,1,2,3)——
|
||||||
|
// 拿它当"id → 值"的映射去 isset,只有当 `$pid` 恰好等于数组下标时才是真:
|
||||||
|
// · 名册里人少 ⇒ 下标都小于 pid ⇒ **谁都判"不在名册"**(用户遇到的就是这个);
|
||||||
|
// · 一般情况 ⇒ 只有"第 pid+1 个人"碰巧对得上(时好时坏,最难查)。
|
||||||
|
// ⇒ 改用 `in_array`(与 views/show 那处 `inMyRoster` 的写法**一致**)。
|
||||||
|
// ⚠️ 别改成"给 visibleIds 换形状":另一个调用点已经是对的 in_array,
|
||||||
|
// 改形状会把那处静默弄坏(同类陷阱:PersonGroup::visibleIds 是**集合**,
|
||||||
|
// 同名不同形状,见 PersonRoster::visibleIds 的注释)。
|
||||||
|
if (!in_array($pid, PersonRoster::visibleIds($familyId, $me), true)) {
|
||||||
$msg = '这个人不在你的名册里。';
|
$msg = '这个人不在你的名册里。';
|
||||||
if ($isAjax) {
|
if ($isAjax) {
|
||||||
header('Content-Type: application/json; charset=utf-8', true, 200);
|
header('Content-Type: application/json; charset=utf-8', true, 200);
|
||||||
|
|||||||
@@ -13,6 +13,21 @@
|
|||||||
* · 修正 = 记**实际**发生了什么(计划不动,实际值进历史,供对比)。
|
* · 修正 = 记**实际**发生了什么(计划不动,实际值进历史,供对比)。
|
||||||
* 两者都作用在同一条 trip_nodes 记录上(用户明确"是一条记录,不是两条")。
|
* 两者都作用在同一条 trip_nodes 记录上(用户明确"是一条记录,不是两条")。
|
||||||
*
|
*
|
||||||
|
* 🔴 v50(用户 2026-10-14)三条:
|
||||||
|
* ① 「修正一个计划行程的时候的开始和结束时间,仍然是之前的那种**控件样式**,
|
||||||
|
* 应该改成我们**最新的控件**样式。」
|
||||||
|
* ⇒ 换 `$dtPlugin = 'antd'`(与「添加/编辑行程」的 node_form.php 同款)。
|
||||||
|
* 当年(v41)这里刻意**没换**,理由是"修正表单内联在 `<details>` 里、
|
||||||
|
* 一段行程一份、可能十几份并存,antd 每份都要挂 React 组件,不划算"。
|
||||||
|
* 用户现在明确要统一,且实测详情页通常只有一两段计划行程需要修正 ——
|
||||||
|
* 按用户要求换,代价可接受。
|
||||||
|
* ② 「上面记录实际执行情况那一段话说明也不要了。」
|
||||||
|
* ⇒ 删掉 `.rev-tip` 那段(那段文字在 show.php 的 `.rev-panel` 里,见那边的改动)。
|
||||||
|
* ③ 「记录这次修正的按钮应该放在**右边**,另外应该加一个**关闭/取消**的按钮,
|
||||||
|
* 就是如果不再修正的话,应该有一个取消,不要让这个框显示了。」
|
||||||
|
* ⇒ 按钮行右对齐;补一颗「取消」——`.rev-open` 是 `<details>`,
|
||||||
|
* 取消 = 把 `open` 去掉(见下面内联脚本 + show.php 的委托)。
|
||||||
|
*
|
||||||
* 变量(require 前设好):
|
* 变量(require 前设好):
|
||||||
* $rvNode 被修正的这段旅程
|
* $rvNode 被修正的这段旅程
|
||||||
* $rvTripId 活动 id
|
* $rvTripId 活动 id
|
||||||
@@ -28,21 +43,17 @@ $rvN = (int)($rvNode['revision_n'] ?? 0);
|
|||||||
<input type="hidden" name="id" value="<?= (int)$rvNode['id'] ?>">
|
<input type="hidden" name="id" value="<?= (int)$rvNode['id'] ?>">
|
||||||
|
|
||||||
<div class="nf-grid">
|
<div class="nf-grid">
|
||||||
<?php /* 实际的起止时间:默认填计划值,方便"只改时间"这种最常见的情况 */ ?>
|
<?php /* 实际的起止时间:默认填计划值,方便"只改时间"这种最常见的情况。
|
||||||
|
🔴 v50:换成与「添加/编辑行程」**同一套最新控件**(AntD RangePicker)。
|
||||||
|
⚠️ 隐藏字段仍是 `rev_starts_at_date/_hour/_minute`,提交契约不变
|
||||||
|
(trips/node_revise 收的还是 read_dt_input 那一套)。 */ ?>
|
||||||
<div class="field dt-range full">
|
<div class="field dt-range full">
|
||||||
<label>实际的开始 / 结束</label>
|
<label>实际的开始 / 结束</label>
|
||||||
<?php /* ⚠️ v41 边界说明(别以为这里漏了):用户第 7 条要求"用最新控件"的是
|
|
||||||
**添加计划行程 / 实际行程**(那是 `trips/node_new`,走 node_form.php,
|
|
||||||
已经换成 AntD)。**「修正」表单不换** ——
|
|
||||||
它内联在详情页的 `<details>` 里,一段计划行程一个实例、可能同时存在
|
|
||||||
十几个;antd 每个实例都要挂一批 React 组件与 popper 容器,
|
|
||||||
这种"N 份并存"的场景明显不划算。
|
|
||||||
它用的是自建下拉(`_datetime.php`),**本来就能精确到分**,功能不缺。 */ ?>
|
|
||||||
<?php $dtName = 'rev_starts_at'; $dtValue = $rvNode['starts_at'] ?? '';
|
<?php $dtName = 'rev_starts_at'; $dtValue = $rvNode['starts_at'] ?? '';
|
||||||
$dtId = $rvPrefix . '-s'; $dtRange = 'start';
|
$dtId = $rvPrefix . '-s'; $dtRange = 'start'; $dtPlugin = 'antd';
|
||||||
require __DIR__ . '/../_datetime.php'; ?>
|
require __DIR__ . '/../_datetime.php'; ?>
|
||||||
<?php $dtName = 'rev_ends_at'; $dtValue = $rvNode['ends_at'] ?? '';
|
<?php $dtName = 'rev_ends_at'; $dtValue = $rvNode['ends_at'] ?? '';
|
||||||
$dtId = $rvPrefix . '-e'; $dtRange = 'end';
|
$dtId = $rvPrefix . '-e'; $dtRange = 'end'; $dtPlugin = 'antd';
|
||||||
require __DIR__ . '/../_datetime.php'; ?>
|
require __DIR__ . '/../_datetime.php'; ?>
|
||||||
<span class="tip">默认是计划的时间,改成实际发生的即可。</span>
|
<span class="tip">默认是计划的时间,改成实际发生的即可。</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,13 +83,16 @@ $rvN = (int)($rvNode['revision_n'] ?? 0);
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row">
|
<?php /* 🔴 v50 用户第 3 条:按钮**靠右**(`margin-left:auto` 把主按钮推过去),
|
||||||
|
并补一颗「取消」把 details 收起来(不再修正时用)。 */ ?>
|
||||||
|
<div class="btn-row rev-foot">
|
||||||
|
<?php if ($rvN > 0): ?>
|
||||||
|
<span class="tip rev-foot-note">之前已记过 <?= $rvN ?> 次;原计划数据都留着。</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<button class="btn btn-ghost btn-sm" type="button" data-rev-cancel>取消</button>
|
||||||
<button class="btn btn-sm" type="submit">
|
<button class="btn btn-sm" type="submit">
|
||||||
<?= $rvN > 0 ? '再记一次修正' : '记录这次修正' ?>
|
<?= $rvN > 0 ? '再记一次修正' : '记录这次修正' ?>
|
||||||
</button>
|
</button>
|
||||||
<?php if ($rvN > 0): ?>
|
|
||||||
<span class="tip" style="align-self:center">之前已记过 <?= $rvN ?> 次;原计划数据都留着。</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<?php unset($rvPrefix, $rvN); ?>
|
<?php unset($rvPrefix, $rvN); ?>
|
||||||
@@ -417,11 +417,17 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
|
|||||||
<details class="rev-open">
|
<details class="rev-open">
|
||||||
<summary class="btn btn-warn btn-sm">修正</summary>
|
<summary class="btn btn-warn btn-sm">修正</summary>
|
||||||
<div class="rev-panel">
|
<div class="rev-panel">
|
||||||
<p class="rev-tip">
|
<?php /* 🔴 v50(用户 2026-10-14 第 3 条):
|
||||||
记录实际执行情况。这仍与上面那条计划是<b>同一条记录</b>,
|
「上面…那一段话说明也不要了。」
|
||||||
原来的计划数据会保留在「修正记录」里,方便日后对比。
|
⇒ 删掉原来那段 `.rev-tip`(开头是「记录…」、解释"仍是同一条记录"
|
||||||
点完这里<b>这段行程仍是计划</b>;确认无误后再点「完成」。
|
的那三行)。表单本身已经说清"实际的开始/结束""这次修正的说明",
|
||||||
</p>
|
那段解释属于噪音。
|
||||||
|
⚠️ 这段注释**刻意不整句照抄**用户点名的原话 ——
|
||||||
|
部署断言要查"那句已删",若在注释里留下原文,
|
||||||
|
那种朴素 grep 会命中注释而恒红(本项目已踩过多次)。
|
||||||
|
要查"删没删"的断言请用 `_code_has`(剥注释)。
|
||||||
|
⚠️ 别顺手删 `.rev-panel` / `.rev-open` 这两个类名 ——
|
||||||
|
它们是展开区的结构锚点(CSS 与测试都用)。 */ ?>
|
||||||
<?php
|
<?php
|
||||||
$rvNode = $n; $rvTripId = (int)$t['id'];
|
$rvNode = $n; $rvTripId = (int)$t['id'];
|
||||||
$rvPeople = $people ?? [];
|
$rvPeople = $people ?? [];
|
||||||
@@ -651,6 +657,19 @@ foreach ($bysStage as $lst) { $shown += count($lst); }
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t.closest('[data-nf-close]')) { setOpen(false); return; }
|
if (t.closest('[data-nf-close]')) { setOpen(false); return; }
|
||||||
|
/* 🔴 v50(用户 2026-10-14 第 3 条):「应该加一个关闭/取消的按钮…不要再修正的话,
|
||||||
|
应该有一个取消,不要让这个框显示了。」
|
||||||
|
「修正」区是 `<details class="rev-open">`(原生可折叠,不用 JS 也能开)——
|
||||||
|
⇒ 取消 = 把这个 details 的 `open` 去掉,回到收起状态。
|
||||||
|
⚠️ 用委托(`closest`)而不是在 `_revise_form.php` 里内联脚本:
|
||||||
|
那份片段会被 AJAX 换进来,内联脚本的位置与幂等都要额外照看;委托一次绑定就够。 */
|
||||||
|
var rc = t.closest('[data-rev-cancel]');
|
||||||
|
if (rc) {
|
||||||
|
e.preventDefault();
|
||||||
|
var det = rc.closest('details.rev-open');
|
||||||
|
if (det) { det.open = false; }
|
||||||
|
return;
|
||||||
|
}
|
||||||
var h = host();
|
var h = host();
|
||||||
if (h && t === h) { setOpen(false); return; } // 点遮罩关;点面板内部不关
|
if (h && t === h) { setOpen(false); return; } // 点遮罩关;点面板内部不关
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user