视觉与品牌 - public/logo.svg、public/login-illustration.svg、favicon 替换为 logo.svg。 - preferences.ts 新增 logo.source 指向 /logo.svg。 - packages/effects/layouts 导出 LoginIllustration 共享组件(浮动插画,加载失败自动隐藏)。 Auth 布局重构 - layouts/auth.vue 替换默认 AuthPageLayout:主题色渐变背景 + 左侧浮动插画 + 品牌卡片 + 右上 LanguageToggle + 圆角登录卡片;KeepAlive 仅缓存 Login (CodeLogin/QrCodeLogin 需要每次刷新验证码/二维码)。 - 表单样式通过 :deep(.login-form-container) 覆盖输入框、选择框、主按钮, 全部走 --primary 变量,与主题色联动。 登录组件 - views/_core/authentication/login.vue: 关闭默认 codeLogin/qrcodeLogin/ thirdPartyLogin/register/docLink,改为手机 / 二维码 / 企微三按钮(IconifyIcon); 三方登录前检查租户,缺失时 message.warning + validateField。 - packages/effects/common-ui/authentication/login.vue + types.ts: 新增 showDocLink prop(默认 true),替代原本 HTML 注释掉 DocLink 的做法。 - sso-callback.vue: 等待提示改为 LottieLoading 动画。 Lottie 启动动画 - 新增 loading.html(注入进 index.html)+ public/loading.json + 运行时拷贝的 public/lottie_light.min.js(.gitignore 忽略)。 - 新增 public/lottie-tint.js:共享主题色适配器(LIGHTNESSES / SHADE_MAP / hslToRgb / patchColors / readPrimary),同时被启动白屏脚本与 Vue 组件使用, 消除两份几乎一致的实现。 - 新增 src/components/lottie-loading/LottieLoading.vue:按需加载 lottie-tint.js 并根据 CSS 变量 --primary 重新上色。 - vite.config.mts 新增 copyLottiePlayer 插件:configResolved 时无条件把 node_modules/lottie-web/.../lottie_light.min.js 拷到 public/,避免 mtime 误判。 - package.json 新增 lottie-web ^5.13.0 依赖。 i18n - 新增 otherLoginMethods / contactSupport / weComLogin 三个文案(zh-CN / en-US)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
159 lines
4.8 KiB
JavaScript
159 lines
4.8 KiB
JavaScript
/**
|
||
* Lottie 主题色适配器
|
||
*
|
||
* 把 public/loading.json 里硬编码的 5 档绿色替换为当前主题色的 5 档亮度,
|
||
* 供 loading.html 的初始白屏动画和 Vue 内的 LottieLoading 组件共用。
|
||
*
|
||
* 暴露为 window.__lottieTint,兼容 ES5,可被 <script src> 直接使用。
|
||
* Vue 组件通过 <script> 标签注入后也使用这同一份实现。
|
||
*/
|
||
(function (global) {
|
||
'use strict';
|
||
|
||
// JSON 里硬编码的 5 档绿色(取 2 位小数做指纹)-> 亮度索引
|
||
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];
|
||
|
||
// 默认主题色(与 packages/@core/preferences 默认值一致,用作兜底)
|
||
var FALLBACK_HSL = { h: 37, s: 100, l: 52 };
|
||
|
||
// HSL (0-360, 0-100, 0-100) -> RGB (0..1)
|
||
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) {
|
||
return [arr[0], arr[1], arr[2]]
|
||
.map(function (x) {
|
||
return x.toFixed(2);
|
||
})
|
||
.join(',');
|
||
}
|
||
|
||
function buildShades(hsl) {
|
||
return LIGHTNESSES.map(function (l) {
|
||
return hslToRgb(hsl.h, hsl.s, l);
|
||
});
|
||
}
|
||
|
||
// 走一遍 lottie JSON,把匹配的填充色替换为当前主题 5 档亮度
|
||
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]];
|
||
}
|
||
(data.layers || []).forEach(function (layer) {
|
||
(layer.shapes || []).forEach(function (group) {
|
||
if (!group.it) return;
|
||
group.it.forEach(function (item) {
|
||
if (item.ty !== 'fl' || !item.c) return;
|
||
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)) {
|
||
k.forEach(function (kf) {
|
||
if (kf.s && typeof kf.s[0] === 'number') {
|
||
var v = swap(kf.s);
|
||
if (v) kf.s = v;
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
});
|
||
return data;
|
||
}
|
||
|
||
// 从 localStorage 的 preferences 缓存读 colorPrimary(loading.html 启动时 Vue 尚未挂载,
|
||
// 无法读 CSS 变量,只能走 localStorage)
|
||
function readPrimaryFromLocalStorage() {
|
||
try {
|
||
for (var i = 0; i < localStorage.length; i++) {
|
||
var k = localStorage.key(i);
|
||
if (!k || !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;
|
||
// 支持 hsl(H S% L%) 和 hsl(H, S%, L%)
|
||
var m = /hsl\(\s*([\d.]+)(?:deg)?[\s,]+([\d.]+)%?[\s,]+([\d.]+)%?\s*\)/i.exec(
|
||
color,
|
||
);
|
||
if (!m) continue;
|
||
return { h: +m[1], s: +m[2], l: +m[3] };
|
||
}
|
||
} catch (e) {
|
||
/* ignore */
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// Vue 挂载后可以直接读 CSS 变量 --primary(格式 "H S% L%")
|
||
function readPrimaryFromCssVar() {
|
||
try {
|
||
var raw = getComputedStyle(document.documentElement)
|
||
.getPropertyValue('--primary')
|
||
.trim();
|
||
if (!raw) return null;
|
||
var m = /([\d.]+)(?:deg)?[\s,]+([\d.]+)%?[\s,]+([\d.]+)%?/.exec(raw);
|
||
return m ? { h: +m[1], s: +m[2], l: +m[3] } : null;
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/** 综合读取当前主题色,优先 CSS 变量,回退 localStorage,最后用默认色 */
|
||
function readPrimary() {
|
||
return (
|
||
readPrimaryFromCssVar() ||
|
||
readPrimaryFromLocalStorage() ||
|
||
FALLBACK_HSL
|
||
);
|
||
}
|
||
|
||
global.__lottieTint = {
|
||
FALLBACK_HSL: FALLBACK_HSL,
|
||
LIGHTNESSES: LIGHTNESSES,
|
||
SHADE_MAP: SHADE_MAP,
|
||
buildShades: buildShades,
|
||
hslToRgb: hslToRgb,
|
||
patchColors: patchColors,
|
||
readPrimary: readPrimary,
|
||
readPrimaryFromCssVar: readPrimaryFromCssVar,
|
||
readPrimaryFromLocalStorage: readPrimaryFromLocalStorage,
|
||
};
|
||
})(window);
|