feat(theme): 加 --brand-secondary 翠绿 #009963 (双品牌色系)
- theme.scss 加 7 个 --brand-secondary-* 派生色 (HSL 算法) - 绑定 Element Plus --el-color-success* (绿=成功语义) - theme.js derivePalette 重构成通用函数 (base, prefix), 加 setBrandSecondary / applyStoredBrands - main.js 启动改调 applyStoredBrands, 主色+次色一起从 localStorage 恢复 主色 primary (青蓝 #007A95) / 次色 secondary (翠绿 #009963) 改 theme.scss :root 两个变量即可全局换色.
This commit is contained in:
+63
-19
@@ -2,11 +2,12 @@
|
||||
* 品牌主题色切换工具
|
||||
* ----------------------------------------
|
||||
* 用法:
|
||||
* import { setBrand } from '@/assets/theme'
|
||||
* setBrand('#0f766e') // 改主题色(自动重算变体)
|
||||
* import { setBrand, setBrandSecondary, applyStoredBrands } from '@/assets/theme'
|
||||
* setBrand('#0f766e') // 改主色(自动重算变体)
|
||||
* setBrand('#1E3A8A') // 改回默认
|
||||
* setBrand('#1E3A8A', { persist: true }) // 写 localStorage
|
||||
* applyStoredBrand() // 应用持久化的颜色
|
||||
* setBrandSecondary('#009963', { persist: true }) // 改次色 (绿)
|
||||
* applyStoredBrands() // 启动时调用, 同时应用两个
|
||||
*
|
||||
* 派生色算法(HSL 空间调整,比简单 hex 加减更稳):
|
||||
* deep: lightness -= 12
|
||||
@@ -15,9 +16,13 @@
|
||||
* lighter: lightness += 35
|
||||
* text: lightness += 22
|
||||
* mix: 50% alpha + white (用于 focus border)
|
||||
*
|
||||
* 双色系: 主色 (primary, 青蓝) + 次色 (secondary, 翠绿).
|
||||
* 主色 → Element Plus --el-color-primary*; 次色 → --el-color-success* (绿=成功语义).
|
||||
*/
|
||||
|
||||
const KEY = 'brand-primary'
|
||||
const KEY_PRIMARY = 'brand-primary'
|
||||
const KEY_SECONDARY = 'brand-secondary'
|
||||
|
||||
/** hex -> {r,g,b} */
|
||||
function hexToRgb(hex) {
|
||||
@@ -73,38 +78,70 @@ function hslToHex({ h, s, l }) {
|
||||
function clampL(l) { return Math.max(8, Math.min(92, l)) }
|
||||
|
||||
/**
|
||||
* 根据主色算出 7 个派生色 hex
|
||||
* 通用派生算法:输入主色 hex, 输出 7 个 --brand-<prefix>-* 变量
|
||||
* @param {string} base 主色 hex
|
||||
* @param {string} [prefix='primary'] 变量名前缀 (primary / secondary)
|
||||
*/
|
||||
export function derivePalette(primary) {
|
||||
const hsl = rgbToHsl(hexToRgb(primary))
|
||||
export function derivePalette(base, prefix = 'primary') {
|
||||
const hsl = rgbToHsl(hexToRgb(base))
|
||||
const p = prefix
|
||||
return {
|
||||
'--brand-primary': primary,
|
||||
'--brand-primary-deep': hslToHex({ ...hsl, l: clampL(hsl.l - 12) }),
|
||||
'--brand-primary-darker': hslToHex({ ...hsl, l: clampL(hsl.l - 22) }),
|
||||
'--brand-primary-light': hslToHex({ ...hsl, l: clampL(hsl.l + 12) }),
|
||||
'--brand-primary-lighter': hslToHex({ ...hsl, l: clampL(hsl.l + 35) }),
|
||||
'--brand-primary-text': hslToHex({ ...hsl, l: clampL(hsl.l + 22) }),
|
||||
'--brand-primary-mix': hslToHex({ ...hsl, l: clampL(hsl.l + 45) })
|
||||
[`--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`]: hslToHex({ ...hsl, l: clampL(hsl.l + 12) }),
|
||||
[`--brand-${p}-lighter`]: hslToHex({ ...hsl, l: clampL(hsl.l + 35) }),
|
||||
[`--brand-${p}-text`]: hslToHex({ ...hsl, l: clampL(hsl.l + 22) }),
|
||||
[`--brand-${p}-mix`]: hslToHex({ ...hsl, l: clampL(hsl.l + 45) })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 :root 上 7 个 CSS 变量
|
||||
* 次色派生 (兼容旧调用, 等价 derivePalette(base, 'secondary'))
|
||||
*/
|
||||
export function deriveSecondaryPalette(secondary) {
|
||||
return derivePalette(secondary, 'secondary')
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 :root 上主色 7 个 CSS 变量
|
||||
* @param {string} primary 主色 hex
|
||||
* @param {object} [opts] { persist: bool } 是否写 localStorage
|
||||
*/
|
||||
export function setBrand(primary, opts = {}) {
|
||||
const palette = derivePalette(primary)
|
||||
const palette = derivePalette(primary, 'primary')
|
||||
const root = document.documentElement
|
||||
for (const [k, v] of Object.entries(palette)) root.style.setProperty(k, v)
|
||||
if (opts.persist) localStorage.setItem(KEY, primary)
|
||||
if (opts.persist) localStorage.setItem(KEY_PRIMARY, primary)
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用持久化在 localStorage 的颜色(无则保持现状)
|
||||
* 设置 :root 上次色 7 个 CSS 变量
|
||||
* @param {string} secondary 次色 hex
|
||||
* @param {object} [opts] { persist: bool }
|
||||
*/
|
||||
export function setBrandSecondary(secondary, opts = {}) {
|
||||
const palette = derivePalette(secondary, 'secondary')
|
||||
const root = document.documentElement
|
||||
for (const [k, v] of Object.entries(palette)) root.style.setProperty(k, v)
|
||||
if (opts.persist) localStorage.setItem(KEY_SECONDARY, secondary)
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次性应用两个持久化颜色 (无则保持现状)
|
||||
*/
|
||||
export function applyStoredBrands() {
|
||||
const p = localStorage.getItem(KEY_PRIMARY)
|
||||
if (p) setBrand(p)
|
||||
const s = localStorage.getItem(KEY_SECONDARY)
|
||||
if (s) setBrandSecondary(s)
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用持久化的主色 (无则保持现状)
|
||||
*/
|
||||
export function applyStoredBrand() {
|
||||
const stored = localStorage.getItem(KEY)
|
||||
const stored = localStorage.getItem(KEY_PRIMARY)
|
||||
if (stored) setBrand(stored)
|
||||
}
|
||||
|
||||
@@ -113,4 +150,11 @@ export function applyStoredBrand() {
|
||||
*/
|
||||
export function getBrand() {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue('--brand-primary').trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前 :root 的次色(只读)
|
||||
*/
|
||||
export function getBrandSecondary() {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue('--brand-secondary').trim()
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
品牌主题色(Brand Theme)
|
||||
------------------------------------------------------------
|
||||
所有色值都是 HSL/HEX 函数动态计算的派生色。
|
||||
改 :root 里的 --brand-primary 即可全局换色。
|
||||
改 :root 里的 --brand-primary / --brand-secondary 即可全局换色。
|
||||
|
||||
派生色算法(hex → HSL → 调整亮度/饱和 → 转回 hex):
|
||||
--brand-primary 主色
|
||||
@@ -13,11 +13,17 @@
|
||||
--brand-primary-text 浅 35% (链接默认)
|
||||
--brand-primary-mix 50% 透明 + 白 (输入框 focus border)
|
||||
|
||||
Element Plus 同步:main.js 在启动时读取 --brand-primary
|
||||
并通过 ElMessage / ElButton 的 --el-color-primary 联动。
|
||||
次色(绿色 #009963)— 走同一套派生算法, 命名 -secondary。
|
||||
语义上对应 Element Plus success (绿=成功/已批准/已完成), 自动
|
||||
绑定到 --el-color-success*, 这样所有 <el-button type="success"> /
|
||||
el-tag type="success" 等都跟随品牌绿。
|
||||
|
||||
Element Plus 同步:main.js 在启动时读取两个 CSS 变量
|
||||
并通过 ElMessage / ElButton 的 --el-color-* 联动。
|
||||
============================================================= */
|
||||
|
||||
:root {
|
||||
/* 主色 — 青蓝 */
|
||||
--brand-primary: #007A95;
|
||||
--brand-primary-deep: #004858;
|
||||
--brand-primary-darker: #002129;
|
||||
@@ -25,15 +31,34 @@
|
||||
--brand-primary-lighter: #49deff;
|
||||
--brand-primary-text: #06d2ff;
|
||||
--brand-primary-mix: #7ce7ff;
|
||||
|
||||
/* 次色 — 翠绿 */
|
||||
--brand-secondary: #009963;
|
||||
--brand-secondary-deep: #005c3b;
|
||||
--brand-secondary-darker: #00291a;
|
||||
--brand-secondary-light: #00d68b;
|
||||
--brand-secondary-lighter: #4dffc0;
|
||||
--brand-secondary-text: #0affa9;
|
||||
--brand-secondary-mix: #80ffd2;
|
||||
}
|
||||
|
||||
/* Element Plus 联动:把品牌色同步给 Element Plus 内置变量。
|
||||
这样所有 <el-button type="primary"> 等都跟主题色。 */
|
||||
这样所有 <el-button type="primary"> / type="success"> 等都跟主题色。 */
|
||||
:root {
|
||||
/* 主色 → primary */
|
||||
--el-color-primary: var(--brand-primary);
|
||||
--el-color-primary-light-3: var(--brand-primary-light);
|
||||
--el-color-primary-light-5: var(--brand-primary-lighter);
|
||||
--el-color-primary-light-7: var(--brand-primary-lighter);
|
||||
--el-color-primary-light-9: var(--brand-primary-lighter);
|
||||
--el-color-primary-dark-2: var(--brand-primary-deep);
|
||||
|
||||
/* 次色 → success (绿色语义: 已批准 / 已完成) */
|
||||
--el-color-success: var(--brand-secondary);
|
||||
--el-color-success-light-3: var(--brand-secondary-light);
|
||||
--el-color-success-light-5: 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-9: var(--brand-secondary-lighter);
|
||||
--el-color-success-dark-2: var(--brand-secondary-deep);
|
||||
}
|
||||
+2
-2
@@ -8,12 +8,12 @@ import App from './App.vue'
|
||||
import router from './router'
|
||||
import './assets/main.scss'
|
||||
import './assets/theme.scss'
|
||||
import { applyStoredBrand } from './assets/theme'
|
||||
import { applyStoredBrands } from './assets/theme'
|
||||
|
||||
const app = createApp(App)
|
||||
for (const [k, v] of Object.entries(ElIcons)) app.component(k, v)
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(ElementPlus, { locale: zhCn })
|
||||
applyStoredBrand()
|
||||
applyStoredBrands()
|
||||
app.mount('#app')
|
||||
|
||||
Reference in New Issue
Block a user