Files
laoyang 8a9629650a v72:日历本显示与管理合并成一张清单,颜色改用内置调色模态框
用户 2026-10-21 两条:
  「日历设置里面日历本显示和日历管理可以放在一起」
  「预制的颜色点了之后没反应……选颜色那个地方点了之后是弹出 Windows 的
    一个颜色选择框,这个不好,我们把它改成内置的」

改动:
· calendar/display 整页删除,并入 calendar/calendars(保留 302 老地址)
· 新增 templates/_color_picker.php(按钮 + 值载体,表单/独立双模式)
· 新增 templates/_color_picker_modal.php(全页唯一模态框,预置色 + hex 手输)
· 共享本提到同一清单,标注归属与权限;owner 校验服务端做
· 清掉 display.php 时代遗留的死 CSS(.cal-set-page / .cal-set-book* 等)

部署中抓出并修掉两处真问题:
· 部署脚本「清掉被取代文件」清单漏了 display.php —— tar 只新增/覆盖,
  git rm 掉的文件在线上永远留着,被第 7 段断言拦下(断言是对的,缺的是删除步骤)
· _color_picker.php 里残留 <input type="color" class="cp-trigger">:
  原生取色控件既不能有子元素、也没有 data-cp-open ⇒
  按钮回显失效(找不到 .cp-trigger-dot/hex)+ 模态框永远打不开,
  用户点到的还是 Windows 取色框。改成 <button type="button"> + 色点 + hex
  (v72 探针在修复前实测 2 条红,修复后 50/0)
2026-10-21 20:01:23 +08:00

115 lines
7.2 KiB
PHP
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.
<?php
/**
* 内置调色器(v72 用户 2026-10-21)—— 按钮 + 值载体,模态框见 `_color_picker_modal.php`。
*
* 用户原话:
* 「另外一个日历本管理里面,这个颜色选择是有问题的,点了之后是没反应的,
* 就是预制的颜色点了之后没反应的。另外选颜色那个地方点了之后是弹出 Windows 的
* 一个颜色选择框,这个不好,我们把它改成内置的,就是就是在网页上弹出一个模态框
* 或者一个什么样的,然后选颜色,不要弹 Windows 的,那个一个体验也不好,
* 另外一个也不好看。」
*
* ⇒ 两件事:
* ① **预置色点了没反应** = `templates/calendar/calendars.php` 里压根没有绑定脚本
* (影院管理页 `movies/cinemas.php` 有一段,日历本页抄漏了)。现在由本组件的 JS
* 统一接管,**不再依赖调用页自己写一份绑定**(写两份迟早又漏一处)。
* ② **不要 Windows 系统取色框** ⇒ 去掉 `<input type="color">`,改成纯网页的
* 模态框:预置色板 + 自定义 hex 输入 + 实时预览。
*
* ─────────────────────────────────────────────────────────────────────
* 两种用法(靠 `$cpName` 是否为空区分):
*
* **A. 表单模式** —— 值随外层表单一起提交(新建/编辑日历本的弹表单用这个):
* ```php
* $cpName='color'; $cpValue='#4A7C82'; $cpPalette=\App\Calendar::PRESET_CAL_COLORS;
* require __DIR__ . '/../_color_picker.php';
* ```
* ⇒ 出 `<input type="hidden" name="color">`。
*
* **B. 独立模式**(`$cpName` 留空)—— 值**不参与表单提交**,由调用页自己拿去做 AJAX:
* ```php
* $cpName=''; $cpValue=$cal['color']; $cpId='calColor'.$id;
* $cpData=['cal-id'=>$id]; // 附加到 .cp-field 上的 data-*
* require __DIR__ . '/../_color_picker.php';
* ```
* ⇒ 出 `<input type="hidden">`(**无 name**)。调用页监听 `cp:change` 事件。
*
* 🔴 为什么要 B 模式:日历本清单是"一张大表单管显隐"(`show[]` 整页一次提交),
* 如果每行再塞一个 `name="color"`,一次提交会带上 N 个同名 `color` 字段,
* 服务端只能拿到最后一个 —— 于是"改了第 3 本的颜色,保存时把第 5 本也改了"。
* 所以行内改色走 AJAX 单独落库,**不混进那张大表单**。
*
* @var string $cpName 隐藏域 name;空串 = 独立模式(不提交)
* @var string $cpValue 初始值('#RRGGBB')
* @var array $cpPalette 预置色板('#RRGGBB' 列表)
* @var string $cpId 可选,值载体的 id;不给则用 'cp_' + $cpName(独立模式必须给)
* @var string $cpLabel 可选,按钮上的无障碍名
* @var array $cpData 可选,附加到容器上的 data-*(键 => 值)
*/
$cpName = (string)($cpName ?? 'color');
$cpValue = strtoupper((string)($cpValue ?? '#4A7C82'));
$cpPalette = array_values(array_filter((array)($cpPalette ?? []), 'is_string'));
$cpData = (array)($cpData ?? []);
$cpLabel = (string)($cpLabel ?? '选择颜色');
/* 🔴🔴 `$cpId` 必须**先给默认值**再用(2026-10-21 实测踩到):
原先只有下面那个 `if (!$cpId)` —— 表单模式下调用页**根本不传** `$cpId`,
于是这里读一个未定义变量 ⇒ `Warning: Undefined variable $cpId`
⇒ 那条 Warning 被 `display_errors` 打成 HTML **塞进页面正文**:
· 页面上会看见一段错字;
· 更糟的是它出现在 `<span class="cp-field">` **之前**,
某些布局下会把表单控件的行内关系弄乱;
· 而**页面照样 200、照样能用** ⇒ 只有"查正文里有没有 PHP 错误"的断言能抓到。
⇒ 教训:可选参数一律 `?? ''` 兜底,**不要靠 `if (!$x)` 当存在性判断**
(读它那一刻 Warning 就已经产生了)。
⚠️ 我的 v72 前端探针当时 47/0 全绿却没抓到它 —— 因为它只查 JS 报错、
不查服务端正文。已给探针补上这条(见 `_fb_v72_ui.py` 的「PHP 正文干净」)。 */
$cpId = (string)($cpId ?? '');
if ($cpId === '') {
// 表单模式用 name 派生;独立模式没有 name ⇒ 调用页必须给 id
$cpId = 'cp_' . preg_replace('/[^A-Za-z0-9_]/', '_', $cpName !== '' ? $cpName : 'color');
}
/* ⚠️ 初始值本身**不是**合法 hex 时(老数据、手工改库),退回落日色而不是把
`background: xxx` 直接拼进 style —— 那样会把整条样式声明弄坏,
旁边几个属性一起失效(用户看到的是"色块变成一个方块")。 */
if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $cpValue)) {
$cpValue = '#4A7C82';
}
$cpDataAttr = '';
foreach ($cpData as $k => $v) {
$cpDataAttr .= ' data-' . e((string)$k) . '="' . e((string)$v) . '"';
}
?>
<span class="cp-field" data-cp-field="<?= e($cpId) ?>"<?= $cpDataAttr ?>>
<?php /* ⚠️ 独立模式下**不写 name**:写了就会跟着外层表单一起提交。
独立模式的值靠 JS 读 `.cp-field` 里的这个 input。 */ ?>
<input type="hidden"<?= $cpName !== '' ? ' name="' . e($cpName) . '"' : '' ?>
id="<?= e($cpId) ?>" value="<?= e($cpValue) ?>">
<?php /* 🔴🔴 v72 修正(2026-10-21 部署时才被抓出来)—— 这里原来是一颗
`<input type="color" ... class="cp-trigger">`(原生取色控件)。
那是**没改完的旧写法**,同时踩了两件事:
① **用户点名要去掉的就是它**(「选颜色那个地方点了之后是弹出 Windows 的
一个颜色选择框,这个不好」)—— 留着它等于需求没做;
② CSS/JS 是照「按钮 + 内部小色点 + hex 文本」设计的:
`.cp-trigger` 是 `inline-flex` 胶囊、`.cp-trigger-dot` / `.cp-trigger-hex`
是它的子元素、模态框靠 `[data-cp-open]` + `[data-cp-target]` 打开并回填。
而原生 `<input type="color">` **不能有子元素**,也没有这两个属性
⇒
· `syncTrigger()` 找不到 `.cp-trigger-dot/hex` ⇒ 改完色**按钮上不刷新**;
· `document.querySelectorAll('[data-cp-open]')` 一个都取不到
⇒ 模态框**永远打不开**,用户点到的是浏览器原生取色框。
⇒ 现在输出的是 `<button type="button">`:**必须写 `type="button"`**,
不加会默认 `type="submit"`,点一下就提交外面那张大表单(日历本清单那张)。
⚠️ `data-cp-target` 指向**隐藏域**的 id(`$cpId`),不是按钮自己的 id ——
`open()` 靠它 `getElementById` 拿当前值,`syncTrigger()` 也靠 `.cp-field`
反查隐藏域。三者(field / hidden / button)必须在同一个 `.cp-field` 里。 */ ?>
<button type="button" class="cp-trigger"
data-cp-open data-cp-target="<?= e($cpId) ?>"
aria-label="<?= e($cpLabel) ?>">
<span class="cp-trigger-dot" style="background: <?= e($cpValue) ?>"></span>
<span class="cp-trigger-hex"><?= e($cpValue) ?></span>
</button>
</span>