115 lines
7.2 KiB
PHP
115 lines
7.2 KiB
PHP
<?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>
|