v81:首页卡片「新增电影」「新建纪念日」弹框(不离开首页就把事办了)
用户 2026-10-22:
「首页上的其他卡片也应该有这些基本功能。比如电影观影,它有一个"新增电影"的功能;
如果是纪念日,就有一个"新增纪念日"。实际上,就是把原来模块里弹的那个框,现在拿到
这边来。目的就是为了在首页上就能快速完成一定工作,而不用进到模块里面。」
做法:复用模块页**同一份**表单,不另写。
- app/HomeCards.php:TYPES 每张卡多登记 add(按钮文案)/ add_modal(弹框容器 id)。
- templates/home.php:按登记表挂 data-fb-add-open="#<id>"(没登记的仍用 v78 的
data-tk-add-open,两种写法并存但各有唯一来源);页尾 require 模块的表单模板。
- assets/js/add_modal.js(新):通用开关,只认 [data-fb-add-modal] /
data-fb-add-open / data-fb-add-close;幂等闸门;Esc 先让 antd 收面板;
打开那一刻才 boot 日期控件(hidden 时尺寸为 0,antd 会定位到屏幕左上角)。
- templates/{movies,days}/form.php:只在首页内嵌时($fbAddModalAttr)挂通用属性
并输出 back=home,模块页行为一字不变。
- index.php:days/save 与 movies/create 加 back=home 白名单(取 POST 再比字面量,
不把 POST 当跳转地址)。
过程中修掉两个真问题:
- 登记表第一版写 anAddCard,而 days/form.php 实际是 anNewCard ⇒ 按钮属性写对了、
DOM 也不报错,点下去毫无反应。已加「按钮指向的容器真的存在」判据与部署静态
断言里的交叉校验(拿登记表 id 去 templates/ 里逐个找 id="...")。
- 探针判「提交后回首页」原本读 pg.url,而 wait_for_load_state(networkidle) 在
点击那一刻本就 idle 时立刻返回 ⇒ 读到提交前的地址,恒绿。改成拦那次 POST 的
响应看 (状态码, Location),拆成 ⑭a 提交真发生 / ⑭b 302 指向 home / ⑭c 浏览器停在 home。
验证:正向探针 23/0 · 反向验证 14/0(七注入各自变红)· e2e 1572/0 · 几何 428/0。
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
/* ============================================================================
|
||||
通用「新建弹框」开关(v81)
|
||||
----------------------------------------------------------------------------
|
||||
用户原话(2026-10-22):
|
||||
「首页上的其他卡片也应该有这些基本功能。比如电影观影,它有一个"新增电影"的
|
||||
功能;如果是纪念日,就有一个"新增纪念日"。实际上,就是把原来模块里弹的那个框,
|
||||
现在拿到这边来。目的就是为了在首页上就能快速完成一定工作,而不用进到模块里面。」
|
||||
|
||||
v78 给「近期任务」写过一份 `task_add.js`(只能管那一张弹框)。这一批要把
|
||||
观影、纪念日也搬上来 ⇒ 三份几乎一样的脚本 = 三处迟早不同步。所以抽这一份**通用的**:
|
||||
每张弹框只需声明自己的「容器 id / 开按钮 / 关按钮」三个名字。
|
||||
|
||||
用法(在弹框模板里给容器加两个属性即可,不需要改 JS):
|
||||
<div class="cal-modal" id="mvAddCard"
|
||||
data-fb-modal="mvAddCard" ← 容器 id(= 触发器的 data-fb-modal-open 值)
|
||||
data-fb-modal-open="[data-mv-add-open]"
|
||||
data-fb-modal-close="[data-mv-add-close]"
|
||||
hidden>
|
||||
或者更省事:容器上加 `data-fb-add-modal`,触发器上加
|
||||
`data-fb-add-open="#<容器id>"`、`data-fb-add-close` —— 全站约定见下。
|
||||
|
||||
行为(与站内其它浮层一致,v78 那套原样保留):
|
||||
· 点触发器开、点 ✕/取消 关、点**遮罩**关、点面板内部**不**关、Esc 关;
|
||||
· Esc 时若 antd 的日期面板开着,**先让 antd 收自己的**(否则一次 Esc 关两层,用户懵);
|
||||
· 打开那一刻才升级日期控件 —— 弹框初始 `hidden`,antd 面板拿到 0 尺寸会定位到
|
||||
屏幕左上角(task_add.js 与 tasks/show.php 都为此专门 boot 过,同一条坑)。
|
||||
|
||||
⚠️ 全站加载,页面里没有带 `data-fb-add-modal` 的容器时**什么都不做**(几行判断)。
|
||||
⚠️ 幂等闸门 `window.__fbAddModalBound`:脚本可能被重复引入(AJAX 换正文、模板重复),
|
||||
不加会绑两遍 ⇒ 点一次开两次、关两次。
|
||||
⚠️ 为什么不用内联 script:内联脚本在 AJAX 换正文时**不会被重跑**(v42/v47 连着栽过)。
|
||||
============================================================================ */
|
||||
(function () {
|
||||
'use strict';
|
||||
if (window.__fbAddModalBound) { return; }
|
||||
window.__fbAddModalBound = true;
|
||||
|
||||
/* 容器:`data-fb-add-modal` 自带 id 也算(省去写两遍) */
|
||||
function containers() {
|
||||
return Array.prototype.slice.call(
|
||||
document.querySelectorAll('[data-fb-add-modal]'));
|
||||
}
|
||||
|
||||
function antdOpen() {
|
||||
var d = document.querySelector('.ant-picker-dropdown');
|
||||
return !!(d && !d.classList.contains('ant-picker-dropdown-hidden'));
|
||||
}
|
||||
|
||||
/* 打开时要做的"补动作":日期控件必须等可见之后再升级(hidden 时宽高为 0)。
|
||||
两步都要:先绑自建下拉(兜底),再挂 antd(正主);两个函数都自判幂等。 */
|
||||
function wake(c) {
|
||||
if (window.fbUpgradeRange) { window.fbUpgradeRange(); }
|
||||
if (window.fbBootAntd) { window.fbBootAntd(); }
|
||||
/* 焦点落到第一颗输入框:键盘用户不用先 Tab 一遍 */
|
||||
var f = c.querySelector('input:not([type=hidden]), textarea, select');
|
||||
if (f && !f.disabled) { try { f.focus(); } catch (e) {} }
|
||||
}
|
||||
|
||||
function show(c, on) {
|
||||
if (!c) { return; }
|
||||
c.hidden = !on;
|
||||
/* 触发器的 aria-expanded 跟着走(有就更新,没有不报错) */
|
||||
var sel = c.getAttribute('data-fb-modal-open');
|
||||
if (sel) {
|
||||
Array.prototype.forEach.call(document.querySelectorAll(sel), function (b) {
|
||||
b.setAttribute('aria-expanded', on ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
if (on) { wake(c); }
|
||||
}
|
||||
|
||||
/* 由点击位置反推"该开/该关哪一张":
|
||||
· 触发器上写 `data-fb-add-open="#id"`(显式指定容器);
|
||||
· 关闭按钮/遮罩则就近找祖先里的 `[data-fb-add-modal]`。 */
|
||||
function targetFor(t) {
|
||||
if (t.closest('[data-fb-add-close]')) {
|
||||
return { c: t.closest('[data-fb-add-modal]'), on: false };
|
||||
}
|
||||
var opener = t.closest('[data-fb-add-open]');
|
||||
if (opener) {
|
||||
var sel = opener.getAttribute('data-fb-add-open') || '';
|
||||
var c = sel ? document.querySelector(sel) : null;
|
||||
return { c: c, on: true };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var t = e.target;
|
||||
if (!t || !t.closest) { return; }
|
||||
var hit = targetFor(t);
|
||||
if (hit) {
|
||||
if (hit.c) { e.preventDefault(); }
|
||||
show(hit.c, hit.on);
|
||||
return;
|
||||
}
|
||||
/* 点遮罩关、点面板内不关 —— 容器本身就是遮罩层,所以只认 === */
|
||||
if (t.hasAttribute && t.hasAttribute('data-fb-add-modal')) {
|
||||
show(t, false);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Escape') { return; }
|
||||
var open = containers().filter(function (c) { return !c.hidden; });
|
||||
if (!open.length) { return; }
|
||||
if (antdOpen()) { return; } // 让 antd 先收它自己的面板
|
||||
show(open[open.length - 1], false);
|
||||
});
|
||||
|
||||
/* 供其它脚本(提交成功后就地插入回包等)主动开合:
|
||||
window.fbAddModal.open('#id') / .close('#id') */
|
||||
window.fbAddModal = {
|
||||
open: function (sel) { show(document.querySelector(sel), true); },
|
||||
close: function (sel) { show(document.querySelector(sel), false); }
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user