diff --git a/assets/app.js b/assets/app.js index c00e02c..37bb10c 100644 --- a/assets/app.js +++ b/assets/app.js @@ -710,18 +710,18 @@ } /* ---------- 主题色选择器(后台软件编辑) ---------- - * 用户 2026-11-04:「还是不够丰富,让用户自己选:点击之后弹出一个颜色选择器。」 + * 用户 2026-11-04:「还是不对呀,需要一个完整的色板,右侧那些默认颜色就不要了。」 * - * 原来是原生 :只有个小方块,用户不知道能点, - * 而且各浏览器系统面板样式不一。现在自带一个面板: - * 色相/饱和度渐变板(Canvas)+ 明度滑块 + 十六进制输入。 - * 预设圆点保留为快捷色,但真正的能力是**任意取色**。 + * 现在的形态是一块**完整色板**(不是小浮窗): + * 340×200 的色相/饱和度画布 + 色相条 + 明度条 + 十六进制输入。 + * 右侧那排预设圆点已删除 —— 既能任意取色,再摆一排样例就是重复。 * * 交互约定: - * - 点色块 → 面板出现(定位在其下方/上方,视空间而定) - * - 在渐变板上拖动 → 实时更新;滑条调明度 - * - 十六进制框可手输,输入合法立即同步 - * - 点「确定」或点面板外 → 关闭,值已写入隐藏字段 + * - 点色块 → 色板出现(下方空间不够自动翻到上方) + * - 画布内拖动 = 同时调饱和度与明度 + * - 色相条单独调 H;明度条单独调 V + * - 十六进制框可手输,合法立即同步 + * - 点「完成」/ 点色板外 / Esc → 关闭(值早已写入隐藏字段) */ function bindColorPicker() { var rows = document.querySelectorAll('[data-colorpicker]'); @@ -730,7 +730,6 @@ (function (row) { var trigger = row.querySelector('[data-color-trigger]'); var valueEl = row.querySelector('[data-color-value]'); - var presets = row.querySelectorAll('.color-preset'); var reset = row.querySelector('.color-reset'); var panel = row.querySelector('[data-color-panel]'); if (!trigger || !valueEl || !panel) { return; } @@ -738,6 +737,7 @@ var canvas = panel.querySelector('[data-cp-sv]'); var cursor = panel.querySelector('[data-cp-cursor]'); var hueEl = panel.querySelector('[data-cp-hue]'); + var valEl = panel.querySelector('[data-cp-val]'); // 明度条(可能不存在,做兼容) var prevEl = panel.querySelector('[data-cp-prev]'); var hexEl = panel.querySelector('[data-cp-hex]'); var okBtn = panel.querySelector('[data-cp-ok]'); @@ -801,17 +801,16 @@ } function moveCursor() { - /* ⚠️ 两个坑,都踩过: - * ① cursor 是 position:absolute 相对 .cp 定位,canvas 在 .cp 的 - * 内边距里侧 —— 要补 offsetLeft/offsetTop,否则整体偏移; - * ② canvas 的**显示尺寸**受 CSS width:100% 影响,不等于其内部 - * 像素宽高(220×150)。按内部尺寸算会越拉越偏。 - * 必须用 getBoundingClientRect() 的宽高。 */ - var rect = canvas.getBoundingClientRect(); - var w = rect.width || canvas.width; - var h = rect.height || canvas.height; - cursor.style.left = (canvas.offsetLeft + hsv.s * w) + 'px'; - cursor.style.top = (canvas.offsetTop + (1 - hsv.v) * h) + 'px'; + /* cursor 现在是 .cp-sv-wrap 的子元素(和 canvas 同级), + * 所以坐标可以直接相对 canvas 自己算 —— 不用再管 .cp 的 padding: + * left/top 相对 wrap,wrap 和 canvas 左/上边重合。 + * 但仍要注意:canvas 显示尺寸(CSS width:100%)≠ 内部像素宽高, + * 必须用 getBoundingClientRect() 的宽高。 */ + var br = canvas.getBoundingClientRect(); + var w = br.width || canvas.width; + var h = br.height || canvas.height; + cursor.style.left = (hsv.s * w) + 'px'; + cursor.style.top = ((1 - hsv.v) * h) + 'px'; cursor.style.background = curHex(); } @@ -824,16 +823,19 @@ if (prevEl) { prevEl.style.background = hex; } if (!skipHex && hexEl) { hexEl.value = hex.toUpperCase(); } hueEl.value = Math.round(hsv.h); + if (valEl) { valEl.value = Math.round(hsv.v * 100); } drawSV(); + drawValTrack(); moveCursor(); - syncPresets(hex); } - function syncPresets(v) { - for (var k = 0; k < presets.length; k++) { - var same = (presets[k].getAttribute('data-color') || '').toLowerCase() === (v || '').toLowerCase(); - if (same) { presets[k].classList.add('on'); } else { presets[k].classList.remove('on'); } - } + /* 明度条的底色要跟着当前色相变 —— 否则永远是灰阶, + * 用户看不清"拖到这里会得到什么颜色"。 */ + function drawValTrack() { + if (!valEl) { return; } + var pure = hsvToRgb(hsv.h, hsv.s, 1); + var top = rgbToHex(pure[0], pure[1], pure[2]); + valEl.style.background = 'linear-gradient(to right, #000, ' + top + ')'; } function setHex(hex, skipHex) { @@ -893,6 +895,14 @@ syncAll(); }); + // ---- 明度滑条 ---- + if (valEl) { + valEl.addEventListener('input', function () { + hsv.v = (parseFloat(this.value) || 0) / 100; + syncAll(); + }); + } + // ---- 十六进制手输 ---- hexEl.addEventListener('input', function () { var v = this.value.trim(); @@ -903,21 +913,15 @@ okBtn.addEventListener('click', function () { closePanel(); }); - // ---- 预设 / 用默认 ---- - for (var j = 0; j < presets.length; j++) { - presets[j].addEventListener('click', function () { - setHex(this.getAttribute('data-color') || ''); - }); - } + // ---- 用默认(清空 = 保存后回落到按 id 取的默认色)---- if (reset) { reset.addEventListener('click', function () { - // 清空 = 保存后回落到按 id 取的默认色 valueEl.value = ''; valueEl.setAttribute('name', 'color'); - syncPresets(''); trigger.style.setProperty('--cur', '#cccccc'); var hexTxt = trigger.querySelector('.color-trigger-hex'); if (hexTxt) { hexTxt.textContent = '跟随默认'; } + closePanel(); }); } diff --git a/assets/style.css b/assets/style.css index a33a615..e74346e 100644 --- a/assets/style.css +++ b/assets/style.css @@ -387,9 +387,9 @@ main.wrap{padding-top:24px; padding-bottom:40px; min-height:60vh} border-radius:9px; background:var(--card); cursor:pointer; flex:none; } -/* ---------- 自绘取色面板 ---------- - * 用户 2026-11-04:「点击之后弹出一个颜色选择器,让用户自己选。」 - * 点击色块 → 面板出现在其下方;下方空间不够时翻到上方(JS 加 .cp-up)。 */ +/* ---------- 自绘色板 ---------- + * 用户 2026-11-04:「需要一个完整的色板,右侧那些默认颜色就不要了。」 + * ⇒ 面板做大(宽 372),色板画布 340×200;右侧预设圆点已删除。 */ .color-trigger{ display:inline-flex; align-items:center; gap:9px; padding:5px 12px 5px 6px; height:34px; @@ -405,52 +405,63 @@ main.wrap{padding-top:24px; padding-bottom:40px; min-height:60vh} box-shadow:inset 0 0 0 1px rgba(0,0,0,.10); } .color-trigger-hex{font-weight:600; letter-spacing:.2px} +.color-trigger-caret{font-size:10px; color:var(--muted); margin-left:1px} .cp{ position:absolute; top:calc(100% + 8px); left:0; z-index:80; - width:248px; padding:10px; + width:372px; padding:14px; background:var(--card); border:1px solid var(--line-strong); - border-radius:12px; box-shadow:0 10px 30px rgba(16,24,40,.16); - user-select:none; + border-radius:14px; box-shadow:0 12px 36px rgba(16,24,40,.20); + user-select:none; box-sizing:border-box; } .cp.cp-up{top:auto; bottom:calc(100% + 8px)} +.cp-hd{ + font-size:12.5px; font-weight:700; color:var(--muted); + letter-spacing:.3px; margin:0 0 10px; +} +/* 画布容器:相对定位,光标挂在这里面,坐标计算就不用管面板 padding */ +.cp-sv-wrap{position:relative; border-radius:9px; overflow:hidden; line-height:0} .cp-sv{ - display:block; width:100%; height:150px; border-radius:8px; + display:block; width:100%; height:200px; border-radius:9px; cursor:crosshair; } .cp-cursor{ - position:absolute; width:14px; height:14px; margin:-7px 0 0 -7px; + position:absolute; width:15px; height:15px; margin:-7.5px 0 0 -7.5px; border:2px solid #fff; border-radius:50%; pointer-events:none; - box-shadow:0 0 0 1px rgba(0,0,0,.35); - /* 位置由 JS 写入 left/top,相对面板内边距(10px)计算,这里不加偏移 */ + box-shadow:0 0 0 1px rgba(0,0,0,.4), 0 1px 3px rgba(0,0,0,.3); +} +/* 两条滑条:色相 + 明度 */ +.cp-slider{margin:11px 0 0} +.cp-slider input[type=range]{ + -webkit-appearance:none; appearance:none; width:100%; height:14px; border-radius:7px; + outline:none; cursor:pointer; display:block; } -.cp-hue{margin:9px 0 8px} .cp-hue input[type=range]{ - -webkit-appearance:none; appearance:none; width:100%; height:12px; border-radius:6px; background:linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); - outline:none; cursor:pointer; } -.cp-hue input[type=range]::-webkit-slider-thumb{ - -webkit-appearance:none; width:16px; height:16px; border-radius:50%; - background:#fff; border:2px solid #fff; box-shadow:0 0 0 1px rgba(0,0,0,.35); cursor:pointer; +.cp-val input[type=range]{background:linear-gradient(to right, #000, #fff)} +.cp-slider input[type=range]::-webkit-slider-thumb{ + -webkit-appearance:none; width:18px; height:18px; border-radius:50%; + background:#fff; border:2px solid #fff; + box-shadow:0 0 0 1px rgba(0,0,0,.35), 0 1px 4px rgba(0,0,0,.25); cursor:pointer; } -.cp-hue input[type=range]::-moz-range-thumb{ - width:14px; height:14px; border-radius:50%; background:#fff; +.cp-slider input[type=range]::-moz-range-thumb{ + width:16px; height:16px; border-radius:50%; background:#fff; border:2px solid #fff; box-shadow:0 0 0 1px rgba(0,0,0,.35); cursor:pointer; } -.cp-foot{display:flex; align-items:center; gap:7px} +.cp-foot{display:flex; align-items:center; gap:8px; margin-top:13px} .cp-preview{ - width:26px; height:26px; border-radius:7px; flex:none; + width:30px; height:30px; border-radius:8px; flex:none; box-shadow:inset 0 0 0 1px rgba(0,0,0,.12); } .cp-hex{ - flex:1; min-width:0; height:28px; padding:0 8px; - border:1px solid var(--line-strong); border-radius:7px; - font-family:ui-monospace,Menlo,Consolas,monospace; font-size:12.5px; + flex:1; min-width:0; height:30px; padding:0 9px; + border:1px solid var(--line-strong); border-radius:8px; + font-family:ui-monospace,Menlo,Consolas,monospace; font-size:13px; text-transform:uppercase; background:var(--card); color:var(--fg); } -.cp-ok{flex:none; padding:5px 11px !important; font-size:12.5px !important} +.cp-ok{flex:none; padding:6px 14px !important; font-size:13px !important} .color-presets{display:flex; gap:6px; flex-wrap:wrap} .color-preset{ width:26px; height:26px; border-radius:50%; border:2px solid transparent; diff --git a/lib/admin_taxonomy.php b/lib/admin_taxonomy.php index caeb524..74c5738 100644 --- a/lib/admin_taxonomy.php +++ b/lib/admin_taxonomy.php @@ -538,44 +538,40 @@ function fb_admin_taxonomy_page($pdo, $editSw, $editCat) { // 主题色:前台按软件换色,用短链接进不同软件一眼就能区分 $curColor = $esw ? fb_color_norm((string)$esw['color']) : ''; $sugColor = $curColor !== '' ? $curColor : fb_color_default_for($esw ? (int)$esw['id'] : 0); - /* 🔴 必须用 fb_color_palette(),不要再在这里写死一份数组。 - * 以前这里硬编码了另外 8 个色值,和 util.php 的调色板完全不同步 —— - * 结果后台只能选那 8 个,用户在后台挑不到新颜色。 */ - $presets = fb_color_palette(); - /* 颜色选择:**点色块弹出取色面板**(色相/饱和度渐变 + 明度条 + 十六进制), - * 而不是只给一排预设圆点。 - * 用户 2026-11-04:「还是不够丰富,让用户自己选:点击之后弹出一个颜色选择器。」 - * ⇒ 预设只是快捷方式,真正要给的是**任意取色**能力。 - * 原生 只有一个小方块,用户不知道能点, - * 而且它的系统面板不在我们控制内(各浏览器样式不一)。 - * 这里自带一个面板:Canvas 渐变 + 拖动,谁都看得懂怎么用。 */ + /* 颜色选择:**点色块弹出一整块色板**。 + * 用户 2026-11-04:「还是不对呀,需要一个完整的色板,右侧那些默认颜色就不要了。」 + * ⇒ ① 右侧那排预设圆点删除,界面更干净,能力靠色板本身(任意取色)覆盖; + * ② 色板要"完整":足够大的色相/饱和度画布 + 色相条 + 明度条 + + * 十六进制输入。太小会让人觉得是"玩具",拖着不好定位颜色。 + * ⚠️ 不再需要 $presets:色板本身就能取任意颜色, + * 留着那排小圆点反而和色板重复、还占横向空间(用户明确要求去掉)。 */ echo '