821 lines
23 KiB
Vue
821 lines
23 KiB
Vue
<script setup lang="ts">
|
||
import { computed, reactive } from 'vue';
|
||
import type { CSSProperties, Component } from 'vue';
|
||
import { loginModuleRecord } from '@/constants/app';
|
||
import { useThemeStore } from '@/store/modules/theme';
|
||
import { $t } from '@/locales';
|
||
import PwdLogin from './modules/pwd-login.vue';
|
||
import ResetPwd from './modules/reset-pwd.vue';
|
||
|
||
defineOptions({ name: 'LoginPage' });
|
||
|
||
interface Props {
|
||
/** The login module */
|
||
module?: UnionKey.LoginModule;
|
||
}
|
||
|
||
const props = defineProps<Props>();
|
||
|
||
const themeStore = useThemeStore();
|
||
|
||
interface LoginModule {
|
||
label: App.I18n.I18nKey;
|
||
component: Component;
|
||
}
|
||
|
||
const moduleMap: Record<UnionKey.LoginModule, LoginModule> = {
|
||
'pwd-login': { label: loginModuleRecord['pwd-login'], component: PwdLogin },
|
||
'reset-pwd': { label: loginModuleRecord['reset-pwd'], component: ResetPwd }
|
||
};
|
||
|
||
const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
|
||
|
||
const currentYear = new Date().getFullYear();
|
||
|
||
/** 登录页品牌色:取自公司 logo 的湛蓝,不跟随系统主题色(主题色偏紫,与企业蓝不符) */
|
||
const LOGIN_BRAND = '#1e80df';
|
||
|
||
/** 鼠标视差:归一化指针位置,不同景深的层按系数反向位移 */
|
||
const pointer = reactive({ x: 0, y: 0 });
|
||
|
||
function onPointerMove(event: MouseEvent) {
|
||
pointer.x = (event.clientX / window.innerWidth - 0.5) * 2;
|
||
pointer.y = (event.clientY / window.innerHeight - 0.5) * 2;
|
||
}
|
||
|
||
function layerStyle(depth: number) {
|
||
return {
|
||
transform: `translate3d(${(-pointer.x * depth).toFixed(1)}px, ${(-pointer.y * depth).toFixed(1)}px, 0)`
|
||
};
|
||
}
|
||
|
||
/** 协作分支:角色 → 颜色 → 汇入主干的路径(git 分支汇流的意象),曲线两端均水平相切,过渡柔和 */
|
||
const branches = [
|
||
{
|
||
key: 'demand',
|
||
label: '需求',
|
||
color: '#f59e0b',
|
||
y0: 195,
|
||
mergeX: 660,
|
||
path: 'M -80,195 C 320,195 440,470 660,470',
|
||
dur: '7.5s',
|
||
begin: '0s'
|
||
},
|
||
{
|
||
key: 'design',
|
||
label: '设计',
|
||
color: '#ec4899',
|
||
y0: 330,
|
||
mergeX: 780,
|
||
path: 'M -80,330 C 360,330 540,470 780,470',
|
||
dur: '6.5s',
|
||
begin: '1.2s'
|
||
},
|
||
{
|
||
key: 'dev',
|
||
label: '开发',
|
||
color: '#0ea5e9',
|
||
y0: 615,
|
||
mergeX: 880,
|
||
path: 'M -80,615 C 380,615 560,470 880,470',
|
||
dur: '7s',
|
||
begin: '2.1s'
|
||
},
|
||
{
|
||
key: 'test',
|
||
label: '测试',
|
||
color: '#22c55e',
|
||
y0: 745,
|
||
mergeX: 970,
|
||
path: 'M -80,745 C 420,745 620,470 970,470',
|
||
dur: '8s',
|
||
begin: '0.6s'
|
||
}
|
||
];
|
||
|
||
/** 分支汇入主干的节点位置 */
|
||
const mergePoints = [
|
||
{ x: 660, color: '#f59e0b' },
|
||
{ x: 780, color: '#ec4899' },
|
||
{ x: 880, color: '#0ea5e9' },
|
||
{ x: 970, color: '#22c55e' }
|
||
];
|
||
|
||
/** 角色徽章在场景中的落位(跟随分支起始段) */
|
||
const roleChips: { label: string; color: string; style: CSSProperties }[] = [
|
||
{ label: '需求', color: '#f59e0b', style: { left: '5%', top: '20%', '--float-d': '0s' } },
|
||
{ label: '设计', color: '#ec4899', style: { left: '11%', top: '35%', '--float-d': '0.8s' } },
|
||
{ label: '开发', color: '#0ea5e9', style: { left: '8%', top: '66%', '--float-d': '1.6s' } },
|
||
{ label: '测试', color: '#22c55e', style: { left: '13%', top: '80%', '--float-d': '2.4s' } }
|
||
];
|
||
|
||
/**
|
||
* 电能质量波形(公司主营:电能质量监测)
|
||
*
|
||
* 主干汇流完成后,尾段"输出"为基波 + 谐波叠加的正弦波组,寓意协作成果守护电能质量。
|
||
* 用二次贝塞尔 Q/T 拼接出周期波形,CSS 平移一个整周期实现无缝流动。
|
||
*/
|
||
interface WaveShape {
|
||
/** 波形中线 y */
|
||
mid: number;
|
||
/** 振幅 */
|
||
amp: number;
|
||
/** 半周期(x 方向) */
|
||
half: number;
|
||
}
|
||
|
||
function buildWavePath(shape: WaveShape, from: number, to: number) {
|
||
const { mid, amp, half } = shape;
|
||
let d = `M ${from} ${mid} Q ${from + half / 2} ${mid - amp} ${from + half} ${mid}`;
|
||
for (let x = from + 2 * half; x <= to; x += half) {
|
||
d += ` T ${x} ${mid}`;
|
||
}
|
||
return d;
|
||
}
|
||
|
||
const waves = [
|
||
// 基波:主题色,振幅最大
|
||
{ key: 'fundamental', mid: 470, amp: 26, half: 110, color: 'var(--brand)', width: 2, opacity: 0.5, dur: '7s' },
|
||
// 高次谐波:短周期小振幅
|
||
{ key: 'harmonic', mid: 470, amp: 10, half: 55, color: '#0ea5e9', width: 1.5, opacity: 0.45, dur: '4.5s' },
|
||
// 低频包络:慢速衬底
|
||
{ key: 'flux', mid: 474, amp: 40, half: 220, color: '#60a5fa', width: 2, opacity: 0.22, dur: '14s' }
|
||
].map(wave => ({
|
||
...wave,
|
||
path: buildWavePath(wave, 900 - wave.half * 2, 2000 + wave.half * 2),
|
||
shift: `${-2 * wave.half}px`
|
||
}));
|
||
|
||
/** 电力场景剪影:输电铁塔(底部接地,局部坐标基点为塔脚中心) */
|
||
const towers = [
|
||
{ x: 150, s: 1 },
|
||
{ x: 540, s: 0.85 },
|
||
{ x: 1280, s: 0.7 }
|
||
];
|
||
|
||
/** 塔间悬垂导线(悬链线意象),坐标对应各塔最宽横担端点 */
|
||
const powerLines = [
|
||
{ path: 'M -60,762 Q 30,800 92,750' },
|
||
{ path: 'M 208,750 Q 350,819 491,768' },
|
||
{ path: 'M 589,768 Q 914,867 1239,786' },
|
||
{ path: 'M 1321,786 Q 1430,824 1520,804' }
|
||
];
|
||
|
||
/** 导线上滑过的电流光点 */
|
||
const lineSparks = [
|
||
{ key: 'spark-1', path: 'M 208,750 Q 350,819 491,768', dur: '5s', begin: '0s' },
|
||
{ key: 'spark-2', path: 'M 589,768 Q 914,867 1239,786', dur: '7s', begin: '2s' }
|
||
];
|
||
|
||
/** 风机(新能源应用场景),dur 为叶轮旋转周期 */
|
||
const turbines = [
|
||
{ key: 'turbine-1', x: 715, s: 0.9, dur: '9s' },
|
||
{ key: 'turbine-2', x: 828, s: 0.6, dur: '13s' }
|
||
];
|
||
</script>
|
||
|
||
<template>
|
||
<div class="login-scene" :style="{ '--brand': LOGIN_BRAND }" @mousemove="onPointerMove">
|
||
<!-- 远景:浮尘微粒 -->
|
||
<div class="scene-motes" :style="layerStyle(6)"></div>
|
||
|
||
<!-- 中景:协作汇流图(需求/设计/开发/测试 → 主干 → 登录入口) -->
|
||
<div class="scene-graph" :style="layerStyle(14)">
|
||
<svg class="scene-graph__svg" viewBox="0 0 1440 900" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
|
||
<defs>
|
||
<linearGradient id="trunk-grad" x1="0" y1="0" x2="1" y2="0">
|
||
<!-- presentation attribute 不解析 CSS var,必须用 style -->
|
||
<stop offset="0" style="stop-color: var(--brand); stop-opacity: 0" />
|
||
<stop offset="0.45" style="stop-color: var(--brand); stop-opacity: 0.85" />
|
||
<stop offset="1" style="stop-color: #0ea5e9; stop-opacity: 0.9" />
|
||
</linearGradient>
|
||
<!-- 每条分支一个渐变:起点透明、临近汇入处渐显,模拟光流自然汇聚 -->
|
||
<linearGradient
|
||
v-for="branch in branches"
|
||
:id="`branch-grad-${branch.key}`"
|
||
:key="`grad-${branch.key}`"
|
||
gradientUnits="userSpaceOnUse"
|
||
:x1="-80"
|
||
:y1="branch.y0"
|
||
:x2="branch.mergeX"
|
||
:y2="470"
|
||
>
|
||
<stop offset="0" :stop-color="branch.color" stop-opacity="0" />
|
||
<stop offset="0.45" :stop-color="branch.color" stop-opacity="0.2" />
|
||
<stop offset="1" :stop-color="branch.color" stop-opacity="0.65" />
|
||
</linearGradient>
|
||
<filter id="trunk-glow" x="-30%" y="-300%" width="160%" height="700%">
|
||
<feGaussianBlur stdDeviation="5" result="blur" />
|
||
<feMerge>
|
||
<feMergeNode in="blur" />
|
||
<feMergeNode in="SourceGraphic" />
|
||
</feMerge>
|
||
</filter>
|
||
<!-- 波形渐显遮罩:汇流完成后波形才"长出来",遮罩静止、波形在其下平移 -->
|
||
<linearGradient id="wave-fade" gradientUnits="userSpaceOnUse" x1="920" y1="0" x2="1980" y2="0">
|
||
<stop offset="0" stop-color="#fff" stop-opacity="0" />
|
||
<stop offset="0.3" stop-color="#fff" stop-opacity="0.9" />
|
||
<stop offset="1" stop-color="#fff" stop-opacity="1" />
|
||
</linearGradient>
|
||
<mask id="wave-mask">
|
||
<rect x="920" y="330" width="1100" height="300" fill="url(#wave-fade)" />
|
||
</mask>
|
||
</defs>
|
||
|
||
<!-- 主干 -->
|
||
<path class="trunk" d="M -60,470 L 1520,470" stroke="url(#trunk-grad)" filter="url(#trunk-glow)" />
|
||
|
||
<!-- 四条角色分支 -->
|
||
<path
|
||
v-for="(branch, index) in branches"
|
||
:key="branch.key"
|
||
class="branch"
|
||
:d="branch.path"
|
||
:stroke="`url(#branch-grad-${branch.key})`"
|
||
:style="{ '--breathe-d': `${index * 1.4}s` }"
|
||
/>
|
||
|
||
<!-- 分支上行进的光点 -->
|
||
<circle
|
||
v-for="branch in branches"
|
||
:key="`dot-${branch.key}`"
|
||
r="3.5"
|
||
:fill="branch.color"
|
||
class="travel-dot"
|
||
:style="{ color: branch.color }"
|
||
>
|
||
<animateMotion :dur="branch.dur" :begin="branch.begin" repeatCount="indefinite" :path="branch.path" />
|
||
</circle>
|
||
|
||
<!-- 主干上行进的光点 -->
|
||
<circle r="3" fill="#5db1f5" class="travel-dot" style="color: #5db1f5">
|
||
<animateMotion dur="4.5s" begin="0.5s" repeatCount="indefinite" path="M 660,470 L 1520,470" />
|
||
</circle>
|
||
<circle r="2.5" fill="#5db1f5" class="travel-dot" style="color: #5db1f5">
|
||
<animateMotion dur="5.5s" begin="2.6s" repeatCount="indefinite" path="M 660,470 L 1520,470" />
|
||
</circle>
|
||
|
||
<!-- 电能质量波形:基波 + 谐波 + 低频包络,沿主干尾段流出 -->
|
||
<g mask="url(#wave-mask)">
|
||
<path
|
||
v-for="wave in waves"
|
||
:key="wave.key"
|
||
class="wave"
|
||
:d="wave.path"
|
||
:stroke-width="wave.width"
|
||
:style="{
|
||
stroke: wave.color,
|
||
opacity: wave.opacity,
|
||
'--wave-shift': wave.shift,
|
||
'--wave-dur': wave.dur
|
||
}"
|
||
/>
|
||
</g>
|
||
|
||
<!-- 电力场景剪影:输电铁塔 + 悬垂导线 + 风机 -->
|
||
<g class="industry">
|
||
<!-- 输电铁塔 -->
|
||
<g
|
||
v-for="tower in towers"
|
||
:key="`tower-${tower.x}`"
|
||
:transform="`translate(${tower.x}, 870) scale(${tower.s})`"
|
||
>
|
||
<path d="M -32 0 L -10 -150 L 0 -178 L 10 -150 L 32 0" />
|
||
<path
|
||
d="M -28 -25 L 28 -45 M 28 -25 L -28 -45 M -24 -70 L 24 -88 M 24 -70 L -24 -88 M -19 -112 L 19 -126 M 19 -112 L -19 -126"
|
||
/>
|
||
<path d="M -58 -120 L 58 -120 M -46 -150 L 46 -150" />
|
||
<path d="M -58 -120 L -58 -110 M 58 -120 L 58 -110 M -46 -150 L -46 -140 M 46 -150 L 46 -140" />
|
||
</g>
|
||
|
||
<!-- 塔间导线 -->
|
||
<path v-for="line in powerLines" :key="line.path" :d="line.path" />
|
||
|
||
<!-- 风机 -->
|
||
<g
|
||
v-for="turbine in turbines"
|
||
:key="turbine.key"
|
||
:transform="`translate(${turbine.x}, 870) scale(${turbine.s})`"
|
||
>
|
||
<path d="M -3 0 L 0 -120 M 3 0 L 0 -120" />
|
||
<g transform="translate(0, -120)">
|
||
<g>
|
||
<path d="M 0 0 L 0 -52" />
|
||
<path d="M 0 0 L 0 -52" transform="rotate(120)" />
|
||
<path d="M 0 0 L 0 -52" transform="rotate(240)" />
|
||
<animateTransform
|
||
attributeName="transform"
|
||
type="rotate"
|
||
from="0 0 0"
|
||
to="360 0 0"
|
||
:dur="turbine.dur"
|
||
repeatCount="indefinite"
|
||
/>
|
||
</g>
|
||
</g>
|
||
<circle cx="0" cy="-120" r="3" class="industry__hub" />
|
||
</g>
|
||
</g>
|
||
|
||
<!-- 导线上的电流光点 -->
|
||
<circle v-for="spark in lineSparks" :key="spark.key" r="2.5" class="travel-dot industry__spark">
|
||
<animateMotion :dur="spark.dur" :begin="spark.begin" repeatCount="indefinite" :path="spark.path" />
|
||
</circle>
|
||
|
||
<!-- 汇入节点:脉冲 -->
|
||
<g v-for="point in mergePoints" :key="`merge-${point.x}`">
|
||
<circle :cx="point.x" cy="470" r="5" :fill="point.color" />
|
||
<circle :cx="point.x" cy="470" r="5" :stroke="point.color" class="merge-pulse" />
|
||
</g>
|
||
</svg>
|
||
|
||
<!-- 角色徽章 -->
|
||
<span v-for="chip in roleChips" :key="chip.label" class="role-chip" :style="chip.style">
|
||
<i class="role-chip__dot" :style="{ backgroundColor: chip.color }"></i>
|
||
{{ chip.label }}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- 顶部:品牌 -->
|
||
<header class="scene-header reveal" style="--d: 0s">
|
||
<SystemLogo class="text-36px" />
|
||
<span class="scene-header__name">{{ $t('system.title') }}</span>
|
||
</header>
|
||
|
||
<!-- 主文案 -->
|
||
<div class="scene-hero" :style="layerStyle(10)">
|
||
<p class="scene-hero__eyebrow reveal" style="--d: 0.15s">BUILD TOGETHER · GUARD POWER QUALITY</p>
|
||
<h1 class="scene-hero__slogan reveal" style="--d: 0.25s">
|
||
独行快
|
||
<span class="scene-hero__comma">,</span>
|
||
<br />
|
||
众行
|
||
<em>远</em>
|
||
</h1>
|
||
<p class="scene-hero__sub reveal" style="--d: 0.4s">每一次提交,都让电能质量的守护更进一步</p>
|
||
</div>
|
||
|
||
<!-- 登录卡片 -->
|
||
<div class="login-card">
|
||
<header class="login-card__header reveal" style="--d: 0.3s">
|
||
<SystemLogo class="login-card__logo text-52px" />
|
||
<h2 class="login-card__title">{{ $t('system.title') }}</h2>
|
||
<p class="login-card__subtitle">欢迎回来,开始今天的协作</p>
|
||
</header>
|
||
<main class="reveal" style="--d: 0.45s">
|
||
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
|
||
<component :is="activeModule.component" />
|
||
</Transition>
|
||
</main>
|
||
</div>
|
||
|
||
<footer class="scene-footer reveal" style="--d: 0.6s">
|
||
© {{ currentYear }} 南京灿能电力自动化股份有限公司 · {{ $t('system.title') }}
|
||
</footer>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped lang="scss">
|
||
.login-scene {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
width: 100%;
|
||
height: 100%;
|
||
padding-right: 9vw;
|
||
overflow: hidden;
|
||
background:
|
||
radial-gradient(90% 70% at 80% 42%, color-mix(in srgb, var(--brand) 10%, transparent) 0%, transparent 60%),
|
||
radial-gradient(80% 60% at 6% 92%, rgb(56 189 248 / 10%) 0%, transparent 60%),
|
||
radial-gradient(70% 50% at 18% 8%, rgb(14 165 233 / 6%) 0%, transparent 55%),
|
||
linear-gradient(160deg, #f5f9ff 0%, #ecf2fb 50%, #fafcff 100%);
|
||
|
||
@media (max-width: 1023px) {
|
||
justify-content: center;
|
||
padding-right: 0;
|
||
}
|
||
}
|
||
|
||
/* ---------- 远景浮尘微粒 ---------- */
|
||
.scene-motes {
|
||
position: absolute;
|
||
inset: -40px;
|
||
background-image:
|
||
radial-gradient(2px 2px at 12% 22%, rgb(30 128 223 / 30%) 50%, transparent 51%),
|
||
radial-gradient(1.5px 1.5px at 28% 68%, rgb(23 44 84 / 16%) 50%, transparent 51%),
|
||
radial-gradient(2.5px 2.5px at 44% 12%, rgb(30 128 223 / 22%) 50%, transparent 51%),
|
||
radial-gradient(1.5px 1.5px at 58% 44%, rgb(23 44 84 / 12%) 50%, transparent 51%),
|
||
radial-gradient(2px 2px at 72% 78%, rgb(56 189 248 / 25%) 50%, transparent 51%),
|
||
radial-gradient(1.5px 1.5px at 86% 28%, rgb(23 44 84 / 14%) 50%, transparent 51%),
|
||
radial-gradient(2px 2px at 94% 62%, rgb(30 128 223 / 20%) 50%, transparent 51%),
|
||
radial-gradient(1.5px 1.5px at 6% 86%, rgb(56 189 248 / 18%) 50%, transparent 51%);
|
||
background-size: 520px 520px;
|
||
background-repeat: repeat;
|
||
animation: motes-breathe 6s ease-in-out infinite alternate;
|
||
transition: transform 0.25s ease-out;
|
||
pointer-events: none;
|
||
}
|
||
|
||
@keyframes motes-breathe {
|
||
from {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* ---------- 协作汇流图 ---------- */
|
||
.scene-graph {
|
||
position: absolute;
|
||
inset: 0;
|
||
transition: transform 0.25s ease-out;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.scene-graph__svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.trunk {
|
||
fill: none;
|
||
stroke-width: 2.5;
|
||
stroke-linecap: round;
|
||
}
|
||
|
||
.branch {
|
||
fill: none;
|
||
stroke-width: 2;
|
||
stroke-linecap: round;
|
||
animation: branch-breathe 6s ease-in-out infinite alternate;
|
||
animation-delay: var(--breathe-d, 0s);
|
||
}
|
||
|
||
@keyframes branch-breathe {
|
||
from {
|
||
opacity: 0.55;
|
||
}
|
||
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.travel-dot {
|
||
filter: drop-shadow(0 0 6px currentColor);
|
||
}
|
||
|
||
.wave {
|
||
fill: none;
|
||
stroke-linecap: round;
|
||
animation: wave-drift var(--wave-dur) linear infinite;
|
||
}
|
||
|
||
@keyframes wave-drift {
|
||
to {
|
||
transform: translateX(var(--wave-shift));
|
||
}
|
||
}
|
||
|
||
/* 电力场景剪影 */
|
||
.industry {
|
||
fill: none;
|
||
stroke: #424a8c;
|
||
stroke-width: 1.5;
|
||
stroke-linecap: round;
|
||
opacity: 0.22;
|
||
}
|
||
|
||
.industry__hub {
|
||
fill: #424a8c;
|
||
stroke: none;
|
||
}
|
||
|
||
.industry__spark {
|
||
fill: var(--brand);
|
||
color: var(--brand);
|
||
opacity: 0.65;
|
||
}
|
||
|
||
.merge-pulse {
|
||
fill: none;
|
||
stroke-width: 1.5;
|
||
transform-box: fill-box;
|
||
transform-origin: center;
|
||
animation: merge-pulse 2.6s ease-out infinite;
|
||
}
|
||
|
||
@keyframes merge-pulse {
|
||
0% {
|
||
opacity: 0.8;
|
||
transform: scale(1);
|
||
}
|
||
|
||
70%,
|
||
100% {
|
||
opacity: 0;
|
||
transform: scale(3.2);
|
||
}
|
||
}
|
||
|
||
/* 角色徽章 */
|
||
.role-chip {
|
||
position: absolute;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 7px 16px;
|
||
border: 1px solid rgb(30 35 80 / 10%);
|
||
border-radius: 999px;
|
||
font-size: 13px;
|
||
letter-spacing: 0.14em;
|
||
color: rgb(30 35 80 / 72%);
|
||
background: rgb(255 255 255 / 65%);
|
||
box-shadow: 0 6px 18px -8px rgb(23 92 171 / 22%);
|
||
backdrop-filter: blur(8px);
|
||
animation: chip-float 5.5s ease-in-out infinite alternate;
|
||
animation-delay: var(--float-d, 0s);
|
||
}
|
||
|
||
.role-chip__dot {
|
||
width: 7px;
|
||
height: 7px;
|
||
border-radius: 50%;
|
||
box-shadow: 0 0 8px 1px currentcolor;
|
||
}
|
||
|
||
@keyframes chip-float {
|
||
from {
|
||
transform: translateY(-6px);
|
||
}
|
||
|
||
to {
|
||
transform: translateY(8px);
|
||
}
|
||
}
|
||
|
||
/* ---------- 品牌与文案 ---------- */
|
||
.scene-header {
|
||
position: absolute;
|
||
top: 40px;
|
||
left: 56px;
|
||
z-index: 2;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
color: #232850;
|
||
}
|
||
|
||
.scene-header__name {
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
.scene-hero {
|
||
position: absolute;
|
||
top: 24%;
|
||
left: 6.5%;
|
||
z-index: 2;
|
||
color: #1b2050;
|
||
transition: transform 0.25s ease-out;
|
||
pointer-events: none;
|
||
|
||
@media (max-width: 1023px) {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
.scene-hero__eyebrow {
|
||
margin-bottom: 26px;
|
||
font-family: Georgia, 'Times New Roman', serif;
|
||
font-size: 13px;
|
||
letter-spacing: 0.46em;
|
||
color: rgb(30 35 80 / 45%);
|
||
}
|
||
|
||
.scene-hero__slogan {
|
||
font-size: 64px;
|
||
font-weight: 600;
|
||
line-height: 1.3;
|
||
letter-spacing: 0.14em;
|
||
text-shadow: 0 8px 32px rgb(255 255 255 / 70%);
|
||
|
||
em {
|
||
font-style: normal;
|
||
background: linear-gradient(120deg, var(--brand) 0%, #0b66c3 55%, #38bdf8 100%);
|
||
-webkit-background-clip: text;
|
||
background-clip: text;
|
||
color: transparent;
|
||
}
|
||
}
|
||
|
||
.scene-hero__comma {
|
||
color: rgb(30 35 80 / 28%);
|
||
}
|
||
|
||
.scene-hero__sub {
|
||
margin-top: 26px;
|
||
font-size: 16px;
|
||
letter-spacing: 0.22em;
|
||
color: rgb(30 35 80 / 52%);
|
||
}
|
||
|
||
.scene-footer {
|
||
position: absolute;
|
||
bottom: 26px;
|
||
left: 56px;
|
||
z-index: 2;
|
||
font-size: 12px;
|
||
letter-spacing: 0.1em;
|
||
color: rgb(30 35 80 / 32%);
|
||
}
|
||
|
||
/* ---------- 登录卡片(白玻璃质感) ---------- */
|
||
.login-card {
|
||
position: relative;
|
||
z-index: 3;
|
||
width: 420px;
|
||
padding: 44px 40px 40px;
|
||
border: 1px solid rgb(255 255 255 / 75%);
|
||
border-radius: 20px;
|
||
background: linear-gradient(168deg, rgb(255 255 255 / 82%) 0%, rgb(248 250 255 / 88%) 100%);
|
||
backdrop-filter: blur(20px) saturate(140%);
|
||
box-shadow:
|
||
0 30px 70px -24px rgb(23 92 171 / 26%),
|
||
0 0 0 1px rgb(30 35 80 / 5%),
|
||
0 1px 0 rgb(255 255 255 / 90%) inset;
|
||
}
|
||
|
||
.login-card__header {
|
||
margin-bottom: 32px;
|
||
text-align: center;
|
||
}
|
||
|
||
.login-card__logo {
|
||
display: block;
|
||
margin: 0 auto 16px;
|
||
filter: drop-shadow(0 10px 26px color-mix(in srgb, var(--brand) 35%, transparent));
|
||
}
|
||
|
||
.login-card__title {
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.1em;
|
||
color: #20254d;
|
||
}
|
||
|
||
.login-card__subtitle {
|
||
margin-top: 10px;
|
||
font-size: 13.5px;
|
||
letter-spacing: 0.06em;
|
||
color: rgb(30 35 80 / 48%);
|
||
}
|
||
|
||
/* 卡片内表单:浅色质感统一覆盖(作用于子模块) */
|
||
.login-card :deep(.el-input__wrapper) {
|
||
height: 48px;
|
||
padding: 0 14px;
|
||
border-radius: 10px;
|
||
background-color: rgb(30 128 223 / 5%);
|
||
box-shadow: 0 0 0 1px rgb(30 35 80 / 12%) inset;
|
||
transition:
|
||
box-shadow 0.2s ease,
|
||
background-color 0.2s ease;
|
||
|
||
&:hover {
|
||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--brand) 55%, rgb(30 35 80 / 20%)) inset;
|
||
}
|
||
|
||
&.is-focus {
|
||
background-color: #fff;
|
||
box-shadow:
|
||
0 0 0 1.5px var(--brand) inset,
|
||
0 0 0 4px color-mix(in srgb, var(--brand) 14%, transparent);
|
||
}
|
||
|
||
.el-input__inner {
|
||
color: #1f244a;
|
||
caret-color: var(--brand);
|
||
|
||
&::placeholder {
|
||
color: rgb(30 35 80 / 34%);
|
||
}
|
||
}
|
||
|
||
.el-input__prefix,
|
||
.el-input__suffix {
|
||
font-size: 18px;
|
||
color: rgb(30 35 80 / 35%);
|
||
}
|
||
|
||
.el-input__prefix {
|
||
margin-right: 6px;
|
||
}
|
||
}
|
||
|
||
.login-card :deep(.el-form-item) {
|
||
margin-bottom: 22px;
|
||
}
|
||
|
||
.login-card :deep(.el-checkbox__label) {
|
||
letter-spacing: 0.04em;
|
||
color: rgb(30 35 80 / 58%);
|
||
}
|
||
|
||
/* 选中态跟随登录页品牌蓝,而非系统主题色 */
|
||
.login-card :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
|
||
border-color: var(--brand);
|
||
background-color: var(--brand);
|
||
}
|
||
|
||
.login-card :deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
|
||
color: var(--brand);
|
||
}
|
||
|
||
.login-card :deep(.login-submit-button) {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 48px;
|
||
margin-top: 4px;
|
||
border: none;
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.32em;
|
||
text-indent: 0.32em;
|
||
background: linear-gradient(135deg, var(--brand) 0%, color-mix(in srgb, var(--brand) 68%, #0a3f8f) 100%);
|
||
box-shadow: 0 12px 26px -10px color-mix(in srgb, var(--brand) 60%, transparent);
|
||
transition:
|
||
transform 0.2s ease,
|
||
box-shadow 0.2s ease,
|
||
filter 0.2s ease;
|
||
|
||
/* 流光扫过 */
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: -60%;
|
||
width: 40%;
|
||
height: 100%;
|
||
background: linear-gradient(100deg, transparent 0%, rgb(255 255 255 / 35%) 50%, transparent 100%);
|
||
transform: skewX(-20deg);
|
||
transition: left 0.55s ease;
|
||
}
|
||
|
||
&:hover {
|
||
transform: translateY(-1px);
|
||
filter: brightness(1.06);
|
||
box-shadow: 0 16px 32px -10px color-mix(in srgb, var(--brand) 70%, transparent);
|
||
|
||
&::after {
|
||
left: 130%;
|
||
}
|
||
}
|
||
|
||
&:active {
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.login-card :deep(.login-back-button) {
|
||
width: 100%;
|
||
height: 44px;
|
||
margin-top: 14px;
|
||
margin-left: 0;
|
||
border: 1px solid rgb(30 35 80 / 15%);
|
||
border-radius: 10px;
|
||
color: rgb(30 35 80 / 70%);
|
||
background: transparent;
|
||
|
||
&:hover {
|
||
border-color: color-mix(in srgb, var(--brand) 60%, transparent);
|
||
color: var(--brand);
|
||
background: color-mix(in srgb, var(--brand) 6%, transparent);
|
||
}
|
||
}
|
||
|
||
/* ---------- 入场动效 ---------- */
|
||
.reveal {
|
||
animation: reveal-up 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) both;
|
||
animation-delay: var(--d, 0s);
|
||
}
|
||
|
||
@keyframes reveal-up {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(18px);
|
||
}
|
||
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
</style>
|