fix(theme): 4 个亮色派生 #49deff/#4dffc0/#06d2ff/#0affa9 改为 Element mix() 算法

之前 HSL +35% 提亮算法生成荧光色 (例 #49deff / #4dffc0),
对政务/严肃网站完全不适用, 跟 Element Plus 默认色系也对不上.

新算法 (浅色) = 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)

新色 (全是低饱和度灰蓝/灰绿, 跟 Element 默认同源):
  PRIMARY (#007A95):
    light    #80bdca    lighter #4da2b5
    text     #b3d7df    mix     #3395aa
  SECONDARY (#009963):
    light    #80ccb1    lighter #4db892
    text     #b3e0d0    mix     #33ad82

深色 (deep/darker) 保持 HSL 算法不变 (合理, 政务场景需要深色 hover)

theme.js derivePalette 同步改成双轨: HSL 调深 + Element mix 调浅
This commit is contained in:
郭庆泰
2026-08-22 21:26:50 +08:00
parent 3c23222841
commit 746e9e01b4
2 changed files with 62 additions and 38 deletions
+33 -11
View File
@@ -9,13 +9,10 @@
* setBrandSecondary('#009963', { persist: true }) // 改次色 (绿) * setBrandSecondary('#009963', { persist: true }) // 改次色 (绿)
* applyStoredBrands() // 启动时调用, 同时应用两个 * applyStoredBrands() // 启动时调用, 同时应用两个
* *
* 派生色算法(HSL 空间调整,比简单 hex 加减更稳): * 派生色算法(双轨, 跟 theme.scss :root 保持一致):
* deep: lightness -= 12 * 深色 deep/darker — HSL lightness 调整 (适合 hover/active/导航)
* darker: lightness -= 22 * 浅色 light/lighter/text/mix — Element Plus mix(white, base, N*10%)
* light: lightness += 12 * (RGB 线性插值, 不会像 HSL +35% 那样生成荧光色, 适合政务网站)
* lighter: lightness += 35
* text: lightness += 22
* mix: 50% alpha + white (用于 focus border)
* *
* 双色系: 主色 (primary, 青蓝) + 次色 (secondary, 翠绿). * 双色系: 主色 (primary, 青蓝) + 次色 (secondary, 翠绿).
* 主色 → Element Plus --el-color-primary*; 次色 → --el-color-success* (绿=成功语义). * 主色 → Element Plus --el-color-primary*; 次色 → --el-color-success* (绿=成功语义).
@@ -77,10 +74,35 @@ function hslToHex({ h, s, l }) {
/** 把 lightness 限制在 8..92 之间 */ /** 把 lightness 限制在 8..92 之间 */
function clampL(l) { return Math.max(8, Math.min(92, l)) } 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>-* 变量 * 通用派生算法:输入主色 hex, 输出 7 个 --brand-<prefix>-* 变量
* @param {string} base 主色 hex * @param {string} base 主色 hex
* @param {string} [prefix='primary'] 变量名前缀 (primary / secondary) * @param {string} [prefix='primary'] 变量名前缀 (primary / secondary)
*
* 派生算法 (跟 theme.scss :root 保持一致):
* 深色 (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)
*/ */
export function derivePalette(base, prefix = 'primary') { export function derivePalette(base, prefix = 'primary') {
const hsl = rgbToHsl(hexToRgb(base)) const hsl = rgbToHsl(hexToRgb(base))
@@ -89,10 +111,10 @@ export function derivePalette(base, prefix = 'primary') {
[`--brand-${p}`]: base, [`--brand-${p}`]: base,
[`--brand-${p}-deep`]: hslToHex({ ...hsl, l: clampL(hsl.l - 12) }), [`--brand-${p}-deep`]: hslToHex({ ...hsl, l: clampL(hsl.l - 12) }),
[`--brand-${p}-darker`]: hslToHex({ ...hsl, l: clampL(hsl.l - 22) }), [`--brand-${p}-darker`]: hslToHex({ ...hsl, l: clampL(hsl.l - 22) }),
[`--brand-${p}-light`]: hslToHex({ ...hsl, l: clampL(hsl.l + 12) }), [`--brand-${p}-light`]: elMix('#ffffff', base, 0.5),
[`--brand-${p}-lighter`]: hslToHex({ ...hsl, l: clampL(hsl.l + 35) }), [`--brand-${p}-lighter`]: elMix('#ffffff', base, 0.7),
[`--brand-${p}-text`]: hslToHex({ ...hsl, l: clampL(hsl.l + 22) }), [`--brand-${p}-text`]: elMix('#ffffff', base, 0.3),
[`--brand-${p}-mix`]: hslToHex({ ...hsl, l: clampL(hsl.l + 45) }) [`--brand-${p}-mix`]: elMix('#ffffff', base, 0.8)
} }
} }
+29 -27
View File
@@ -4,22 +4,24 @@
所有色值都是 HSL/HEX 函数动态计算的派生色。 所有色值都是 HSL/HEX 函数动态计算的派生色。
改 :root 里的 --brand-primary / --brand-secondary 即可全局换色。 改 :root 里的 --brand-primary / --brand-secondary 即可全局换色。
派生色算法(hex → HSL → 调整亮度/饱和 → 转回 hex): 派生色算法(双轨):
--brand-primary 主色 深色 (deep/darker) — HSL 调亮度
--brand-primary-deep 暗 12% hover / active / 标题强调) --brand-primary-deep 暗 12% hover / active / 标题强调)
--brand-primary-darker 暗 22% (顶部导航 / 深底) --brand-primary-darker 暗 22% (顶部导航 / 深底)
--brand-primary-light 浅 22% (背景 / disabled 浅色 (light/lighter/text/mix) — Element Plus mix(white, base, N*10%)
--brand-primary-lighter 浅 55% hover-bg / 选中态) --brand-primary-light mix 50% white (背景 / disabled)
--brand-primary-text 浅 35% (链接默认) --brand-primary-lighter mix 70% white (hover-bg / 选中态)
--brand-primary-mix 50% 透明 + 白 (输入框 focus border --brand-primary-text mix 30% white (链接默认)
--brand-primary-mix mix 80% white (输入框 focus border)
之前 HSL +35% 算法会生成荧光色 (例 #49deff), 政务网站不适用;
改用 Element mix() 才是 RGB 线性插值, 全是低饱和度灰调.
次色绿色 #009963)— 走同一套派生算法, 命名 -secondary 次色 (绿色 #009963) 走同一套算法, 命名 -secondary.
语义上对应 Element Plus success (绿=成功/已批准/已完成), 自动 语义上对应 Element Plus success (绿=成功/已批准/已完成), 自动
绑定到 --el-color-success*, 这样所有 <el-button type="success"> / 绑定到 --el-color-success*.
el-tag type="success" 等都跟随品牌绿。
Element Plus 同步:main.js 在启动时读取两个 CSS 变量 Element Plus 同步:main.js 在启动时读取 --brand-primary 并联动
并通过 ElMessage / ElButton 的 --el-color-* 联动。 ElMessage / ElButton 的 --el-color-primary.
============================================================= */ ============================================================= */
:root { :root {
@@ -27,19 +29,19 @@
--brand-primary: #007A95; --brand-primary: #007A95;
--brand-primary-deep: #004858; --brand-primary-deep: #004858;
--brand-primary-darker: #002129; --brand-primary-darker: #002129;
--brand-primary-light: #00acd2; --brand-primary-light: #80bdca;
--brand-primary-lighter: #49deff; --brand-primary-lighter: #4da2b5;
--brand-primary-text: #06d2ff; --brand-primary-text: #b3d7df;
--brand-primary-mix: #7ce7ff; --brand-primary-mix: #3395aa;
/* 次色 — 翠绿 */ /* 次色 — 翠绿 */
--brand-secondary: #009963; --brand-secondary: #009963;
--brand-secondary-deep: #005c3b; --brand-secondary-deep: #005c3b;
--brand-secondary-darker: #00291a; --brand-secondary-darker: #00291a;
--brand-secondary-light: #00d68b; --brand-secondary-light: #80ccb1;
--brand-secondary-lighter: #4dffc0; --brand-secondary-lighter: #4db892;
--brand-secondary-text: #0affa9; --brand-secondary-text: #b3e0d0;
--brand-secondary-mix: #80ffd2; --brand-secondary-mix: #33ad82;
} }
/* Element Plus 联动:把品牌色同步给 Element Plus 内置变量。 /* Element Plus 联动:把品牌色同步给 Element Plus 内置变量。
@@ -47,16 +49,16 @@
:root { :root {
/* 主色 → primary */ /* 主色 → primary */
--el-color-primary: var(--brand-primary); --el-color-primary: var(--brand-primary);
--el-color-primary-light-3: var(--brand-primary-light); --el-color-primary-light-3: var(--brand-primary-text); /* text */
--el-color-primary-light-5: var(--brand-primary-lighter); --el-color-primary-light-5: var(--brand-primary-light); /* light */
--el-color-primary-light-7: var(--brand-primary-lighter); --el-color-primary-light-7: var(--brand-primary-lighter); /* lighter */
--el-color-primary-light-9: var(--brand-primary-lighter); --el-color-primary-light-9: var(--brand-primary-lighter); /* lighter */
--el-color-primary-dark-2: var(--brand-primary-deep); --el-color-primary-dark-2: var(--brand-primary-deep);
/* 次色 → success (绿色语义: 已批准 / 已完成) */ /* 次色 → success (绿色语义: 已批准 / 已完成) */
--el-color-success: var(--brand-secondary); --el-color-success: var(--brand-secondary);
--el-color-success-light-3: var(--brand-secondary-light); --el-color-success-light-3: var(--brand-secondary-text);
--el-color-success-light-5: var(--brand-secondary-lighter); --el-color-success-light-5: var(--brand-secondary-light);
--el-color-success-light-7: var(--brand-secondary-lighter); --el-color-success-light-7: var(--brand-secondary-lighter);
--el-color-success-light-8: 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-lighter);