feat(@vben/web-antd): Lottie 品牌加载动画(主题色自适应)
- 新增全局 loading:自定义 apps/web-antd/loading.html 覆盖 inject-app-loading 的默认模板,Vue 挂载前就能播放品牌动画 - 新增 LottieLoading 组件,用于 SSO 回调等"白屏时间偏长"的运行时场景 - 换色方案:Lottie JSON 里原本 5 档硬编码绿色,按当前主题 colorPrimary 生成 5 档 HSL 亮度做指纹替换。挂载前从 localStorage preferences 读色, 挂载后读 CSS 变量 --primary,两条路径共用 public/lottie-theme-patch.js 一份 classic-JS 源,window.__LottieThemePatch__ 上暴露 - vite 插件:启动/构建时从 node_modules 把 lottie_light.min.js 拷到 public/ 供 loading.html <script> 加载;.gitignore 排除该产物 - LottieLoading 复用 loading.html 已经挂好的 window.lottie,不再把 ~170KB 播放器再打进 Vue 产物 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
151
apps/web-antd/public/lottie-theme-patch.js
Normal file
151
apps/web-antd/public/lottie-theme-patch.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Lottie 主题色换色共享模块(单一真源)
|
||||
*
|
||||
* 同时供两处使用:
|
||||
* 1. loading.html —— Vue 挂载前,用 localStorage 里缓存的 preferences 读主题色
|
||||
* 2. LottieLoading.vue —— 组件运行时,直接读 CSS 变量 --primary
|
||||
*
|
||||
* JSON 里的动画原本是 5 档硬编码绿色,按此映射替换为主题色的 5 档亮度。
|
||||
* 本文件不进打包,由 <script src="/lottie-theme-patch.js"> 引入,
|
||||
* 会在 window 上挂 __LottieThemePatch__。
|
||||
*/
|
||||
(function (global) {
|
||||
/** JSON 中硬编码的 5 档绿色(取 2 位小数指纹) -> 5 档亮度索引 */
|
||||
var SHADE_MAP = {
|
||||
'0.62,0.92,0.49': 0, // 最浅
|
||||
'0.44,0.74,0.08': 1,
|
||||
'0.30,0.62,0.04': 2,
|
||||
'0.18,0.47,0.02': 3,
|
||||
'0.14,0.39,0.02': 4, // 最深
|
||||
};
|
||||
|
||||
/** 5 档亮度(从浅到深),覆盖主流主题色 */
|
||||
var LIGHTNESSES = [72, 58, 48, 36, 26];
|
||||
|
||||
var FALLBACK_HSL = { h: 37, s: 100, l: 52 }; // 与 preferences/config.ts 默认色一致
|
||||
var HSL_PARSE_RE = /([\d.]+)(?:deg)?[\s,]+([\d.]+)%?[\s,]+([\d.]+)%?/i;
|
||||
var HSL_FN_RE = /hsl\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%?[\s,]+([\d.]+)%?\s*\)/i;
|
||||
|
||||
function hslToRgb(h, s, l) {
|
||||
var hh = (((h % 360) + 360) % 360) / 360;
|
||||
var ss = s / 100;
|
||||
var ll = l / 100;
|
||||
if (ss === 0) return [ll, ll, ll];
|
||||
var q = ll < 0.5 ? ll * (1 + ss) : ll + ss - ll * ss;
|
||||
var p = 2 * ll - q;
|
||||
function f(t) {
|
||||
var tt = t;
|
||||
if (tt < 0) tt += 1;
|
||||
if (tt > 1) tt -= 1;
|
||||
if (tt < 1 / 6) return p + (q - p) * 6 * tt;
|
||||
if (tt < 1 / 2) return q;
|
||||
if (tt < 2 / 3) return p + (q - p) * (2 / 3 - tt) * 6;
|
||||
return p;
|
||||
}
|
||||
return [f(hh + 1 / 3), f(hh), f(hh - 1 / 3)];
|
||||
}
|
||||
|
||||
function rgbKey(arr) {
|
||||
if (!arr || arr.length < 3) return '';
|
||||
return arr[0].toFixed(2) + ',' + arr[1].toFixed(2) + ',' + arr[2].toFixed(2);
|
||||
}
|
||||
|
||||
function buildShades(hsl) {
|
||||
return LIGHTNESSES.map(function (l) {
|
||||
return hslToRgb(hsl.h, hsl.s, l);
|
||||
});
|
||||
}
|
||||
|
||||
/** 就地替换 Lottie JSON 中的填充色(静态与关键帧两种形态) */
|
||||
function patchColors(data, shades) {
|
||||
function swap(arr) {
|
||||
var idx = SHADE_MAP[rgbKey(arr)];
|
||||
if (idx === undefined) return null;
|
||||
var s = shades[idx];
|
||||
return [s[0], s[1], s[2], arr[3] == null ? 1 : arr[3]];
|
||||
}
|
||||
var layers = (data && data.layers) || [];
|
||||
for (var li = 0; li < layers.length; li++) {
|
||||
var shapes = layers[li].shapes || [];
|
||||
for (var si = 0; si < shapes.length; si++) {
|
||||
var its = shapes[si].it;
|
||||
if (!its) continue;
|
||||
for (var ii = 0; ii < its.length; ii++) {
|
||||
var item = its[ii];
|
||||
if (item.ty !== 'fl' || !item.c) continue;
|
||||
var k = item.c.k;
|
||||
if (Array.isArray(k) && typeof k[0] === 'number') {
|
||||
var v = swap(k);
|
||||
if (v) item.c.k = v;
|
||||
} else if (Array.isArray(k)) {
|
||||
for (var ki = 0; ki < k.length; ki++) {
|
||||
var kf = k[ki];
|
||||
if (kf && kf.s && typeof kf.s[0] === 'number') {
|
||||
var v2 = swap(kf.s);
|
||||
if (v2) kf.s = v2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue 挂载前:扫 localStorage 里 preferences 缓存读 colorPrimary
|
||||
* namespacePrefix 用于把扫描范围限定在当前 app 的 key 上,避免误命中其它应用
|
||||
*/
|
||||
function readPrimaryHslFromLocalStorage(namespacePrefix) {
|
||||
try {
|
||||
for (var i = 0; i < localStorage.length; i++) {
|
||||
var k = localStorage.key(i);
|
||||
if (!k) continue;
|
||||
if (namespacePrefix && k.indexOf(namespacePrefix) !== 0) continue;
|
||||
if (!k.endsWith('-preferences')) continue;
|
||||
if (k.endsWith('-preferences-theme')) continue;
|
||||
if (k.endsWith('-preferences-locale')) continue;
|
||||
var raw = localStorage.getItem(k);
|
||||
if (!raw) continue;
|
||||
var parsed = JSON.parse(raw);
|
||||
var color =
|
||||
parsed &&
|
||||
parsed.value &&
|
||||
parsed.value.theme &&
|
||||
parsed.value.theme.colorPrimary;
|
||||
if (!color) continue;
|
||||
var m = HSL_FN_RE.exec(color);
|
||||
if (!m) continue;
|
||||
return { h: +m[1], s: +m[2], l: +m[3] };
|
||||
}
|
||||
} catch (e) {
|
||||
/* fall through to fallback */
|
||||
}
|
||||
return FALLBACK_HSL;
|
||||
}
|
||||
|
||||
/** Vue 运行时:读 CSS 变量 --primary(格式 "H S% L%") */
|
||||
function readPrimaryHslFromCss() {
|
||||
try {
|
||||
var raw = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--primary')
|
||||
.trim();
|
||||
if (!raw) return FALLBACK_HSL;
|
||||
var m = HSL_PARSE_RE.exec(raw);
|
||||
return m ? { h: +m[1], s: +m[2], l: +m[3] } : FALLBACK_HSL;
|
||||
} catch (e) {
|
||||
return FALLBACK_HSL;
|
||||
}
|
||||
}
|
||||
|
||||
global.__LottieThemePatch__ = {
|
||||
SHADE_MAP: SHADE_MAP,
|
||||
LIGHTNESSES: LIGHTNESSES,
|
||||
hslToRgb: hslToRgb,
|
||||
rgbKey: rgbKey,
|
||||
buildShades: buildShades,
|
||||
patchColors: patchColors,
|
||||
readPrimaryHslFromLocalStorage: readPrimaryHslFromLocalStorage,
|
||||
readPrimaryHslFromCss: readPrimaryHslFromCss,
|
||||
};
|
||||
})(window);
|
||||
Reference in New Issue
Block a user