新增: 登录页 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>
This commit is contained in:
lzh
2026-04-23 19:35:04 +08:00
parent 6e2ca79c44
commit b90b371d53
22 changed files with 727 additions and 26 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -0,0 +1,13 @@
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="800.000000" height="800.000000" viewBox="0 0 800.000000 800.000000" preserveAspectRatio="xMidYMid meet">
<metadata>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<rdf:Description dc:format="image/svg+xml" dc:Label="1" dc:ContentProducer="001191330110MACRLGPT8B00000" dc:ProduceID="418102974" dc:ReservedCode1="FGBF4Y0JzTd3Sjwm8fIlqyFfZ96OhJbTnUggD/vFfXE=" dc:ContentPropagator="001191330110MACRLGPT8B00000" dc:PropagateID="418102974" dc:ReservedCode2="FGBF4Y0JzTd3Sjwm8fIlqyFfZ96OhJbTnUggD/vFfXE="/>
</rdf:RDF>
</metadata>
<g transform="translate(0.000000,800.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none">
<path d="M2905 5903 c-179 -24 -370 -155 -458 -314 -63 -112 -77 -191 -77 -424 l0 -203 -57 -7 c-95 -11 -143 -25 -233 -67 -158 -75 -288 -236 -337 -418 -16 -59 -18 -128 -18 -735 l0 -670 28 -80 c78 -221 261 -386 483 -435 82 -18 129 -20 576 -20 l488 0 197 -161 c583 -476 706 -575 721 -580 9 -3 32 -2 50 3 30 8 38 19 77 101 23 51 86 181 139 291 53 109 96 200 96 201 0 5 211 440 216 444 2 3 28 -19 57 -47 l53 -52 430 0 c483 0 490 1 625 71 144 75 260 227 304 398 14 54 16 113 13 380 l-3 316 -33 75 c-119 276 -392 422 -594 319 l-38 -19 -20 40 c-28 55 -87 112 -146 141 -38 18 -69 25 -137 27 -82 4 -93 2 -151 -27 -35 -17 -65 -31 -68 -31 -2 0 -5 91 -7 203 -2 188 -4 206 -26 254 -49 108 -163 183 -280 183 -122 0 -242 -82 -284 -195 -20 -53 -21 -75 -21 -469 l0 -414 -35 -42 c-53 -63 -57 -93 -53 -384 4 -341 -4 -321 221 -535 37 -35 66 -66 65 -70 -2 -3 -15 -31 -30 -61 -14 -30 -100 -208 -190 -395 -90 -187 -179 -373 -198 -412 -19 -40 -39 -73 -43 -73 -4 0 -75 55 -156 123 -160 132 -372 306 -569 466 l-124 101 -522 0 c-449 0 -530 2 -577 16 -159 47 -279 155 -340 309 l-24 60 -3 635 c-2 393 1 652 7 681 6 25 20 71 31 102 28 76 133 188 211 228 103 52 152 59 391 59 l218 0 0 85 0 85 -110 0 -110 0 0 178 c0 258 25 343 131 457 63 67 124 105 210 129 50 15 170 16 1150 14 l1094 -3 79 -38 c89 -42 179 -126 214 -198 48 -99 52 -141 52 -510 l0 -347 83 2 82 1 0 370 c0 326 -3 378 -19 435 -55 198 -209 360 -409 427 l-82 28 -1090 1 c-600 1 -1103 -1 -1120 -3z m1959 -1042 c56 -52 56 -55 56 -495 l0 -406 100 0 100 0 0 109 c0 123 12 164 60 205 67 56 147 58 220 5 56 -41 71 -82 78 -211 l5 -108 92 0 92 0 2 55 c0 31 8 70 17 86 17 33 66 59 111 59 84 0 222 -112 273 -223 46 -97 52 -155 48 -427 -4 -222 -6 -251 -27 -309 -54 -155 -186 -272 -341 -301 -29 -6 -211 -10 -406 -10 l-354 0 -204 190 c-152 142 -208 201 -220 231 -13 32 -16 83 -16 274 0 148 4 236 10 240 6 4 24 30 40 58 l30 52 0 418 c0 459 0 460 61 511 30 25 39 28 91 24 43 -3 64 -10 82 -27z"/>
<path d="M2273 4340 c-37 -15 -54 -69 -37 -115 17 -43 40 -45 498 -45 l435 0 26 23 c35 29 37 89 6 121 -21 21 -27 21 -464 23 -243 1 -452 -2 -464 -7z"/>
<path d="M2257 3806 c-38 -35 -38 -96 -1 -125 26 -21 33 -21 801 -21 l774 0 24 25 c31 30 33 83 6 116 l-19 24 -779 3 -780 2 -26 -24z"/>
<path d="M2251 3274 c-30 -38 -27 -80 8 -115 l29 -29 447 0 c457 0 473 1 502 39 21 27 16 85 -10 109 -23 22 -25 22 -489 22 l-467 0 -20 -26z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,158 @@
/**
* 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);