Files
iot-device-management-frontend/apps/web-antd/public/lottie-tint.js
lzh b90b371d53 新增: 登录页 UI 重构 + 品牌 logo 与 Lottie 启动动画
视觉与品牌
- 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>
2026-04-23 19:35:04 +08:00

159 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 缓存读 colorPrimaryloading.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);