fix(theme): 4 个亮色 #4da2b5/#4db892/#80bdca/#80ccb1 不适合做背景色

问题: 之前 Element mix(white) 算法在 S=100% 主色 (#007A95)
上生成的中色 (#4da2b5 / #4db892) 跟主色对比度太低,
作 hover-bg / 选中态底色时白字看不清.

新算法: HSL 降饱和 + 提亮度 (色相不变, 灰度提升).
  text    = S-40 L+15  (中蓝可读, 链接用)
  light   = S-65 L+40  (灰中, disabled 浅背景)
  lighter = S-65 L+50  (灰浅, hover-bg / 选中态, **够淡字可读**)
  mix     = S-75 L+60  (近白, focus border)

主色 (#007A95):
  text    #2d9cb4  lighter #b7d6dd
  light   #95c2cc  mix     #dde8ea

次色 (#009963):
  text    #2eb887  lighter #baded1
  light   #98cdba  mix     #dfece7

Element Plus 联动:
  --el-color-primary-light-9 → var(--brand-primary-mix) (近白, 最浅)
  --el-color-success-light-9 → var(--brand-secondary-mix)

theme.js derivePalette 同步改成同算法 (运行时 setBrand 仍正确)
This commit is contained in:
郭庆泰
2026-08-22 21:31:08 +08:00
parent 746e9e01b4
commit 80a276e661
2 changed files with 35 additions and 44 deletions
+11 -24
View File
@@ -74,21 +74,6 @@ function hslToHex({ h, s, l }) {
/** 把 lightness 限制在 8..92 之间 */
function clampL(l) { return Math.max(8, Math.min(92, l)) }
/**
* Element Plus mix() — RGB 线性插值 (Sass mixColor 复刻)
* mix(color1, color2, weight) → color1*(1-w) + color2*w
* 比 HSL 大幅提亮更克制, 适合政务网站的浅色调.
*/
function elMix(color1, color2, weight) {
weight = Math.max(Math.min(weight, 1), 0)
const r1 = parseInt(color1.slice(1, 3), 16), g1 = parseInt(color1.slice(3, 5), 16), b1 = parseInt(color1.slice(5, 7), 16)
const r2 = parseInt(color2.slice(1, 3), 16), g2 = parseInt(color2.slice(3, 5), 16), b2 = parseInt(color2.slice(5, 7), 16)
const r = Math.round(r1 * (1 - weight) + r2 * weight)
const g = Math.round(g1 * (1 - weight) + g2 * weight)
const b = Math.round(b1 * (1 - weight) + b2 * weight)
return '#' + [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('')
}
/**
* 通用派生算法:输入主色 hex, 输出 7 个 --brand-<prefix>-* 变量
* @param {string} base 主色 hex
@@ -98,11 +83,13 @@ function elMix(color1, color2, weight) {
* 深色 (HSL lightness):
* deep = L - 12 (hover / active / 标题强调)
* darker = L - 22 (顶部导航 / 深底)
* 浅色 (Element Plus mix(white, base, N*10%)):
* light = 50% mix (背景 / disabled)
* lighter = 70% mix (hover-bg / 选中态)
* text = 30% mix (链接默认)
* mix = 80% mix (输入框 focus border)
* 浅色 (HSL 降饱和 + 提亮度, 适合政务网站):
* text = S-40 L+15 (链接默认, 中蓝可读)
* lighter = S-65 L+50 (hover-bg / 选中态, 浅灰)
* light = S-65 L+40 (背景 / disabled, 灰中)
* mix = S-75 L+60 (输入框 focus border, 近白)
* 之前试过 Element mix(white), 但 S=100% 的主色提亮后跟主色
* 对比度太低 (例 #4da2b5), 不适合做选中态背景.
*/
export function derivePalette(base, prefix = 'primary') {
const hsl = rgbToHsl(hexToRgb(base))
@@ -111,10 +98,10 @@ export function derivePalette(base, prefix = 'primary') {
[`--brand-${p}`]: base,
[`--brand-${p}-deep`]: hslToHex({ ...hsl, l: clampL(hsl.l - 12) }),
[`--brand-${p}-darker`]: hslToHex({ ...hsl, l: clampL(hsl.l - 22) }),
[`--brand-${p}-light`]: elMix('#ffffff', base, 0.5),
[`--brand-${p}-lighter`]: elMix('#ffffff', base, 0.7),
[`--brand-${p}-text`]: elMix('#ffffff', base, 0.3),
[`--brand-${p}-mix`]: elMix('#ffffff', base, 0.8)
[`--brand-${p}-text`]: hslToHex({ h: hsl.h, s: Math.max(0, hsl.s - 40), l: clampL(hsl.l + 15) }),
[`--brand-${p}-lighter`]: hslToHex({ h: hsl.h, s: Math.max(0, hsl.s - 65), l: clampL(hsl.l + 50) }),
[`--brand-${p}-light`]: hslToHex({ h: hsl.h, s: Math.max(0, hsl.s - 65), l: clampL(hsl.l + 40) }),
[`--brand-${p}-mix`]: hslToHex({ h: hsl.h, s: Math.max(0, hsl.s - 75), l: clampL(hsl.l + 60) })
}
}
+24 -20
View File
@@ -8,13 +8,16 @@
深色 (deep/darker) — HSL 调亮度
--brand-primary-deep 暗 12% hover / active / 标题强调)
--brand-primary-darker 暗 22% (顶部导航 / 深底)
浅色 (light/lighter/text/mix) — Element Plus mix(white, base, N*10%)
--brand-primary-light mix 50% white (背景 / disabled)
--brand-primary-lighter mix 70% white (hover-bg / 选中态)
--brand-primary-text mix 30% white (链接默认)
--brand-primary-mix mix 80% white (输入框 focus border)
之前 HSL +35% 算法会生成荧光色 (例 #49deff), 政务网站不适用;
改用 Element mix() 才是 RGB 线性插值, 全是低饱和度灰调.
浅色 (light/lighter/text/mix) — HSL 降饱和 + 提亮度
之前试过 Element mix(white) 算法, 但 S=100% 的主色提亮后
跟主色对比度太低 (例 #4da2b5 / #4db892), 文字与背景难辨认,
不适合做选中态/hover-bg.
现在: 同时降饱和 (S-N) 和提亮度 (L+N), 色相不变, 但灰度提升,
是 Element 在 S 低基色上的实际效果模拟, 适合政务网站.
--brand-primary-text S-40 L+15 (链接默认, 中蓝可读)
--brand-primary-lighter S-65 L+50 (hover-bg / 选中态, 浅灰蓝)
--brand-primary-light S-65 L+40 (背景 / disabled, 灰中蓝)
--brand-primary-mix S-75 L+60 (输入框 focus border, 近白)
次色 (绿色 #009963) 走同一套算法, 命名 -secondary.
语义上对应 Element Plus success (绿=成功/已批准/已完成), 自动
@@ -29,19 +32,19 @@
--brand-primary: #007A95;
--brand-primary-deep: #004858;
--brand-primary-darker: #002129;
--brand-primary-light: #80bdca;
--brand-primary-lighter: #4da2b5;
--brand-primary-text: #b3d7df;
--brand-primary-mix: #3395aa;
--brand-primary-text: #2d9cb4;
--brand-primary-lighter: #b7d6dd;
--brand-primary-light: #95c2cc;
--brand-primary-mix: #dde8ea;
/* 次色 — 翠绿 */
--brand-secondary: #009963;
--brand-secondary-deep: #005c3b;
--brand-secondary-darker: #00291a;
--brand-secondary-light: #80ccb1;
--brand-secondary-lighter: #4db892;
--brand-secondary-text: #b3e0d0;
--brand-secondary-mix: #33ad82;
--brand-secondary-text: #2eb887;
--brand-secondary-lighter: #baded1;
--brand-secondary-light: #98cdba;
--brand-secondary-mix: #dfece7;
}
/* Element Plus 联动:把品牌色同步给 Element Plus 内置变量。
@@ -49,10 +52,11 @@
:root {
/* 主色 → primary */
--el-color-primary: var(--brand-primary);
--el-color-primary-light-3: var(--brand-primary-text); /* text */
--el-color-primary-light-5: var(--brand-primary-light); /* light */
--el-color-primary-light-7: var(--brand-primary-lighter); /* lighter */
--el-color-primary-light-9: var(--brand-primary-lighter); /* lighter */
--el-color-primary-light-3: var(--brand-primary-text); /* text (中蓝可读) */
--el-color-primary-light-5: var(--brand-primary-light); /* light (灰中) */
--el-color-primary-light-7: var(--brand-primary-lighter); /* lighter (灰浅) */
--el-color-primary-light-8: var(--brand-primary-lighter); /* lighter */
--el-color-primary-light-9: var(--brand-primary-mix); /* mix (近白) */
--el-color-primary-dark-2: var(--brand-primary-deep);
/* 次色 → success (绿色语义: 已批准 / 已完成) */
@@ -61,6 +65,6 @@
--el-color-success-light-5: var(--brand-secondary-light);
--el-color-success-light-7: var(--brand-secondary-lighter);
--el-color-success-light-8: var(--brand-secondary-lighter);
--el-color-success-light-9: var(--brand-secondary-lighter);
--el-color-success-light-9: var(--brand-secondary-mix);
--el-color-success-dark-2: var(--brand-secondary-deep);
}