Files
laoyang fd90e33d94 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。
2026-10-22 22:05:17 +08:00

117 lines
5.5 KiB
JavaScript
Raw Permalink 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.
/* ============================================================================
通用「新建弹框」开关(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); }
};
})();