`,改成纯网页的 * 模态框:预置色板 + 自定义 hex 输入 + 实时预览。 * * ───────────────────────────────────────────────────────────────────── * 两种用法(靠 `$cpName` 是否为空区分): * * **A. 表单模式** —— 值随外层表单一起提交(新建/编辑日历本的弹表单用这个): * ```php * $cpName='color'; $cpValue='#4A7C82'; $cpPalette=\App\Calendar::PRESET_CAL_COLORS; * require __DIR__ . '/../_color_picker.php'; * ``` * ⇒ 出 ``。 * * **B. 独立模式**(`$cpName` 留空)—— 值**不参与表单提交**,由调用页自己拿去做 AJAX: * ```php * $cpName=''; $cpValue=$cal['color']; $cpId='calColor'.$id; * $cpData=['cal-id'=>$id]; // 附加到 .cp-field 上的 data-* * require __DIR__ . '/../_color_picker.php'; * ``` * ⇒ 出 ``(**无 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 **塞进页面正文**: · 页面上会看见一段错字; · 更糟的是它出现在 `` **之前**, 某些布局下会把表单控件的行内关系弄乱; · 而**页面照样 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) . '"'; } ?> > id="" value=""> `(原生取色控件)。 那是**没改完的旧写法**,同时踩了两件事: ① **用户点名要去掉的就是它**(「选颜色那个地方点了之后是弹出 Windows 的 一个颜色选择框,这个不好」)—— 留着它等于需求没做; ② CSS/JS 是照「按钮 + 内部小色点 + hex 文本」设计的: `.cp-trigger` 是 `inline-flex` 胶囊、`.cp-trigger-dot` / `.cp-trigger-hex` 是它的子元素、模态框靠 `[data-cp-open]` + `[data-cp-target]` 打开并回填。 而原生 `` **不能有子元素**,也没有这两个属性 ⇒ · `syncTrigger()` 找不到 `.cp-trigger-dot/hex` ⇒ 改完色**按钮上不刷新**; · `document.querySelectorAll('[data-cp-open]')` 一个都取不到 ⇒ 模态框**永远打不开**,用户点到的是浏览器原生取色框。 ⇒ 现在输出的是 `