|
|
@@ -8,61 +8,20 @@
|
|
|
|
|
|
<template #main>
|
|
|
<div class="page">
|
|
|
- <div class="ai-cloud-ring" aria-hidden="true"></div>
|
|
|
- <div class="split-door" :class="{ opening: isDoorOpening }" aria-hidden="true">
|
|
|
- <div class="door left"></div>
|
|
|
- <div class="door right" @animationend="handleDoorEnd"></div>
|
|
|
- </div>
|
|
|
<div class="content">
|
|
|
<!-- 左侧酷炫区域 -->
|
|
|
<div class="left">
|
|
|
- <div class="ellipse-area">
|
|
|
- <img class="ellipse" :src="require('@/assets/ellipse-line.png')" alt="ellipse" />
|
|
|
- <!-- 沿椭圆的流光:不旋转椭圆图,只做覆盖层沿线跑动 -->
|
|
|
- <div class="ellipse-glow"></div>
|
|
|
- <div class="icon icon-1"><img :src="require('@/assets/icon-upload.png')" width="87px" height="87px" /></div>
|
|
|
- <div class="icon icon-2"><img :src="require('@/assets/icon-webcam.png')" width="87px" height="87px" /></div>
|
|
|
- <div class="icon icon-3"><img :src="require('@/assets/icon-shield.png')" width="87px" height="87px" /></div>
|
|
|
- <div class="icon icon-4"><img :src="require('@/assets/icon-setting.png')" width="87px" height="87px" /></div>
|
|
|
- </div>
|
|
|
+
|
|
|
</div>
|
|
|
|
|
|
<!-- 右侧登录面板 -->
|
|
|
<div class="right">
|
|
|
- <div class="panel">
|
|
|
- <div class="panel-inner">
|
|
|
-
|
|
|
- <div class="panel-title">账号登录</div>
|
|
|
-
|
|
|
- <div class="field">
|
|
|
- <img class="i" :src="require('@/assets/i_user.png')" />
|
|
|
- <span class="field-label">账号</span>
|
|
|
- <input class="inp" v-model.trim="username" placeholder="请输入账号" />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="field">
|
|
|
- <img class="i" :src="require('@/assets/i_lock.png')" />
|
|
|
- <span class="field-label">密码</span>
|
|
|
- <input class="inp" type="password" v-model.trim="password" placeholder="请输入密码" />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="row" v-if="showCaptcha">
|
|
|
- <div class="field cap-field">
|
|
|
- <img class="i" :src="require('@/assets/i_captcha.png')" />
|
|
|
- <span class="field-label">验证码</span>
|
|
|
- <input class="inp" v-model.trim="captchaInput" placeholder="请输入验证码" />
|
|
|
- </div>
|
|
|
- <CaptchaCanvas v-model="captchaCode" />
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="hint" v-if="hint">{{ hint }}</div>
|
|
|
- <button class="btn" @click="onLogin">立即登录</button>
|
|
|
-
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div class="login-container">
|
|
|
+ <LoginForm></LoginForm>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="page-dim" :class="{ opening: isDoorOpening }" aria-hidden="true"></div>
|
|
|
+
|
|
|
<div class="copyright">
|
|
|
<img class="copyright-logo" :src="require('@/assets/images/logo.png')" />
|
|
|
<div>北京东土正创科技有限公司</div>
|
|
|
@@ -74,110 +33,37 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import CaptchaCanvas from "@/components/CaptchaCanvas.vue";
|
|
|
import LoginLayout from "@/layouts/LoginLayout.vue";
|
|
|
-import { apiLogin, apiGetCaptcha } from "@/api";
|
|
|
+import LoginForm from "@/components/ui/LoginForm.vue";
|
|
|
|
|
|
export default {
|
|
|
name: "LoginPage",
|
|
|
- components: { CaptchaCanvas, LoginLayout },
|
|
|
+ components: {
|
|
|
+ LoginLayout,
|
|
|
+ LoginForm
|
|
|
+ },
|
|
|
created() {
|
|
|
// 提前预加载 Cesium 瓦片
|
|
|
import('@/utils/cesiumPreloader').then(m => m.default.start());
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
- baseW: 1920,
|
|
|
- baseH: 1080,
|
|
|
- scale: 1,
|
|
|
- username: "admin",
|
|
|
- password: "123456",
|
|
|
- captchaCode: "",
|
|
|
- captchaInput: "",
|
|
|
- hint: "",
|
|
|
- isDoorOpening: false,
|
|
|
- doorNavigated: false,
|
|
|
- loginFailedCount: Number(sessionStorage.getItem('loginFailedCount')) || 0,
|
|
|
+
|
|
|
};
|
|
|
},
|
|
|
computed: {
|
|
|
- showCaptcha() {
|
|
|
- return this.loginFailedCount >= 3;
|
|
|
- }
|
|
|
+
|
|
|
},
|
|
|
mounted() {
|
|
|
- this.updateScale();
|
|
|
- window.addEventListener('resize', this.updateScale, { passive: true });
|
|
|
+
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
- window.removeEventListener('resize', this.updateScale);
|
|
|
+
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
- updateScale() {
|
|
|
- const w = window.innerWidth || this.baseW;
|
|
|
- const h = window.innerHeight || this.baseH;
|
|
|
- const s = Math.min(w / this.baseW, h / this.baseH);
|
|
|
- this.scale = s;
|
|
|
- this.$el && this.$el.style.setProperty('--s', s.toFixed(6));
|
|
|
- },
|
|
|
- async onLogin() {
|
|
|
- this.hint = "";
|
|
|
-
|
|
|
- // 如果验证码已显示,则进行前端拦截与校验
|
|
|
- if (this.showCaptcha) {
|
|
|
- if (!this.captchaInput) {
|
|
|
- this.hint = "请输入验证码";
|
|
|
- return;
|
|
|
- }
|
|
|
- // 前端比对验证码 (忽略大小写)
|
|
|
- if (this.captchaInput.toLowerCase() !== this.captchaCode.toLowerCase()) {
|
|
|
- this.hint = "验证码错误";
|
|
|
- this.captchaInput = ""; // 验证码错误后清空输入框
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let data;
|
|
|
- try {
|
|
|
- data = await apiLogin({
|
|
|
- username: this.username,
|
|
|
- password: this.password,
|
|
|
- captcha: this.captchaInput
|
|
|
- });
|
|
|
-
|
|
|
- // 登录成功,重置次数并清空缓存
|
|
|
- this.loginFailedCount = 0;
|
|
|
- sessionStorage.removeItem('loginFailedCount');
|
|
|
- } catch (e) {
|
|
|
- this.hint = e.message || '登录失败';
|
|
|
- // 登录失败,次数 +1 并存入缓存
|
|
|
- this.loginFailedCount++;
|
|
|
- sessionStorage.setItem('loginFailedCount', this.loginFailedCount);
|
|
|
- this.captchaInput = "";
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- localStorage.setItem('token', data.token);
|
|
|
-
|
|
|
-
|
|
|
- this.doorNavigated = false;
|
|
|
- this.isDoorOpening = false;
|
|
|
- this.$nextTick(() => {
|
|
|
- this.isDoorOpening = true;
|
|
|
- setTimeout(() => {
|
|
|
- if (this.doorNavigated) return;
|
|
|
- this.doorNavigated = true;
|
|
|
- this.$router.push("/transition");
|
|
|
- }, 1000);
|
|
|
- });
|
|
|
- },
|
|
|
- handleDoorEnd() {
|
|
|
- if (!this.isDoorOpening || this.doorNavigated) return;
|
|
|
- this.doorNavigated = true;
|
|
|
- this.$router.push("/transition");
|
|
|
- },
|
|
|
+
|
|
|
+
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
@@ -187,6 +73,27 @@ export default {
|
|
|
background: url('@/assets/images/login-background.png') no-repeat center/cover;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+}
|
|
|
+.login-container {
|
|
|
+ /* * 【自适应魔法】:计算当前屏幕与 1920 宽度的比例。
|
|
|
+ * clamp(最小缩放比例, 动态计算比例, 最大缩放比例)
|
|
|
+ * 当屏幕小于 1920 时,--s 会小于 1;当屏幕等于 1920 时,--s 为 1。
|
|
|
+ */
|
|
|
+ --s: clamp(0.5, 100vw / 1920, 1.2);
|
|
|
+
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-end;
|
|
|
+
|
|
|
+ /* 右侧边距也用 clamp 做动态响应:最小 20px,推荐 8vw,最大 150px */
|
|
|
+ padding-right: clamp(20px, 8vw, 150px);
|
|
|
+ box-sizing: border-box;
|
|
|
+ pointer-events: auto;
|
|
|
}
|
|
|
|
|
|
.page{
|
|
|
@@ -210,408 +117,13 @@ export default {
|
|
|
|
|
|
/* 左侧 */
|
|
|
.left{ min-width: 0; }
|
|
|
-.ellipse-area{
|
|
|
- position: relative;
|
|
|
- width: 65vw;
|
|
|
- max-width: calc(var(--s) * 1150px);
|
|
|
- height: 70vh;
|
|
|
-}
|
|
|
|
|
|
-.ellipse{
|
|
|
- position: absolute;
|
|
|
- left: 0;
|
|
|
- top: 20%;
|
|
|
- width: 88%;
|
|
|
- height: auto;
|
|
|
- opacity: 0.75;
|
|
|
-}
|
|
|
-
|
|
|
-.ellipse-glow{
|
|
|
- position:absolute;
|
|
|
- left: 0;
|
|
|
- top: 14%;
|
|
|
- width: 92%;
|
|
|
- height: 60%;
|
|
|
- pointer-events:none;
|
|
|
-
|
|
|
- -webkit-mask: url("~@/assets/ellipse-line.png") center/contain no-repeat;
|
|
|
- mask: url("~@/assets/ellipse-line.png") center/contain no-repeat;
|
|
|
-
|
|
|
- background: linear-gradient(
|
|
|
- 90deg,
|
|
|
- rgba(0,0,0,0) 0%,
|
|
|
- rgba(70,220,255,0) 40%,
|
|
|
- rgba(70,220,255,0.85) 50%,
|
|
|
- rgba(70,220,255,0) 60%,
|
|
|
- rgba(0,0,0,0) 100%
|
|
|
- );
|
|
|
- background-size: 200% 100%;
|
|
|
- animation: ellipse-flow 2.2s linear infinite;
|
|
|
- filter: drop-shadow(0 0 calc(var(--s) * 10px) rgba(43,220,255,0.18));
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes ellipse-flow{
|
|
|
- 0%{ background-position: 0% 0%; }
|
|
|
- 100%{ background-position: 200% 0%; }
|
|
|
-}
|
|
|
-
|
|
|
-.icon{
|
|
|
- position:absolute;
|
|
|
- width: clamp(calc(var(--s) * 54px), 4.5vw, calc(var(--s) * 78px));
|
|
|
- height: clamp(calc(var(--s) * 54px), 4.5vw, calc(var(--s) * 78px));
|
|
|
- border-radius: 50%;
|
|
|
- /* background: radial-gradient(circle at 30% 30%, rgba(120,220,255,0.4), rgba(10,35,80,0.4)); */
|
|
|
- border: calc(var(--s) * 1px) solid rgba(60,180,255,0.35);
|
|
|
- box-shadow:
|
|
|
- 0 0 calc(var(--s) * 18px) rgba(43,220,255,0.25),
|
|
|
- inset 0 0 calc(var(--s) * 12px) rgba(120,220,255,0.25);
|
|
|
- transform: translate(-50%, -50%);
|
|
|
- animation: icon-float 3s ease-in-out infinite;
|
|
|
-}
|
|
|
-
|
|
|
-/* 按你给的比例调过 */
|
|
|
-
|
|
|
-.icon-1{ left: 16.8%; top: 87%; } /* 盾牌 */
|
|
|
-.icon-2{ left: 41.5%; top: 84%; } /* 人像 */
|
|
|
-.icon-3{ left: 61.5%;top: 73%;} /* 中间图标 */
|
|
|
-.icon-4{ left: 80.5%; top: 56%;} /* 齿轮 */
|
|
|
-
|
|
|
-
|
|
|
-/* 单独给每个球不同浮动幅度(可选,效果更高级) */
|
|
|
-.icon-1{ --dy: calc(var(--s) * 7px); }
|
|
|
-.icon-2{ --dy: calc(var(--s) * 9px); }
|
|
|
-.icon-3{ --dy: calc(var(--s) * 8px); }
|
|
|
-.icon-4{ --dy: calc(var(--s) * 6px); }
|
|
|
-
|
|
|
-@keyframes icon-float{
|
|
|
- 0%,100%{ transform: translate(-50%,-50%) translateY(0); }
|
|
|
- 50%{ transform: translate(-50%,-50%) translateY(calc(var(--dy, calc(var(--s) * 8px)) * -1)); }
|
|
|
-}
|
|
|
-.icon-1{ animation-duration: 3.2s; animation-delay: -0.2s; }
|
|
|
-.icon-2{ animation-duration: 3.8s; animation-delay: -1.1s; }
|
|
|
-.icon-3{ animation-duration: 3.4s; animation-delay: -2.0s; }
|
|
|
-.icon-4{ animation-duration: 4.2s; animation-delay: -2.7s; }
|
|
|
-
|
|
|
-.icon{
|
|
|
- animation-name: icon-float, icon-glow;
|
|
|
- animation-timing-function: ease-in-out, ease-in-out;
|
|
|
- animation-iteration-count: infinite, infinite;
|
|
|
-}
|
|
|
-
|
|
|
-.icon-1{ animation-duration: 3.2s, 2.6s; animation-delay: -0.2s, -0.8s; }
|
|
|
-.icon-2{ animation-duration: 3.8s, 3.1s; animation-delay: -1.1s, -1.4s; }
|
|
|
-.icon-3{ animation-duration: 3.4s, 2.8s; animation-delay: -2.0s, -2.2s; }
|
|
|
-.icon-4{ animation-duration: 4.2s, 3.5s; animation-delay: -2.7s, -3.0s; }
|
|
|
-
|
|
|
-@keyframes icon-glow{
|
|
|
- 0%,100%{ filter: drop-shadow(0 0 calc(var(--s) * 8px) rgba(43,220,255,0.16)); }
|
|
|
- 50%{ filter: drop-shadow(0 0 calc(var(--s) * 16px) rgba(43,220,255,0.36)); }
|
|
|
-}
|
|
|
/* 右侧面板 */
|
|
|
.right{
|
|
|
min-width: 0;
|
|
|
display:flex;
|
|
|
justify-content:center;
|
|
|
}
|
|
|
-.panel{
|
|
|
- width: min(calc(var(--s) * 680px), 30vw);
|
|
|
- aspect-ratio: 900 / 680;
|
|
|
- position: relative;
|
|
|
- background: url("~@/assets/panel_frame.png") center/100% 100% no-repeat;
|
|
|
- display:flex;
|
|
|
- justify-content:center;
|
|
|
- align-items:center;
|
|
|
-}
|
|
|
-
|
|
|
-/* 用绝对比例控制内容区域 */
|
|
|
-.panel-inner{
|
|
|
- width: 88%; /* 输入框主宽度比例 */
|
|
|
- display:flex;
|
|
|
- flex-direction:column;
|
|
|
- align-items:center;
|
|
|
-}
|
|
|
-.panel-title{
|
|
|
- width: 85%;
|
|
|
- font-size: var(--fs-title);
|
|
|
- color: rgba(220,250,255,0.92);
|
|
|
- margin-bottom: calc(var(--s) * 30px);
|
|
|
- font-weight: 600;
|
|
|
-}
|
|
|
-.field{
|
|
|
- width: 85%;
|
|
|
- height: calc(var(--s) * 54px);
|
|
|
- display:flex;
|
|
|
- align-items:center;
|
|
|
- gap: calc(var(--s) * 10px);
|
|
|
- padding: 0 calc(var(--s) * 16px);
|
|
|
- background: linear-gradient(270deg, rgba(26,117,255,0.11) 0%, rgba(71,120,255,0.07) 100%);
|
|
|
- border-radius: calc(var(--s) * 8px);
|
|
|
- border: calc(var(--s) * 1px) solid #3D72B8;
|
|
|
- margin-bottom: calc(var(--s) * 18px);
|
|
|
-}
|
|
|
-
|
|
|
-.field{
|
|
|
- position: relative;
|
|
|
- transition: box-shadow .22s ease, border-color .22s ease, background .22s ease;
|
|
|
-}
|
|
|
-
|
|
|
-.field:focus-within{
|
|
|
- border-color: rgba(90, 220, 255, 0.95);
|
|
|
- background: linear-gradient(270deg, rgba(26,117,255,0.16) 0%, rgba(71,120,255,0.10) 100%);
|
|
|
- box-shadow:
|
|
|
- 0 0 0 calc(var(--s) * 1px) rgba(110, 230, 255, 0.55),
|
|
|
- 0 0 calc(var(--s) * 18px) rgba(43,220,255,0.28),
|
|
|
- inset 0 0 calc(var(--s) * 14px) rgba(120,220,255,0.18);
|
|
|
-}
|
|
|
-
|
|
|
-/* 可选:让左侧小图标也略微被点亮 */
|
|
|
-.field:focus-within .i{
|
|
|
- opacity: 1;
|
|
|
- filter: drop-shadow(0 0 calc(var(--s) * 8px) rgba(43,220,255,0.35));
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-.field-label{
|
|
|
- flex: 0 0 calc(var(--s) * 44px); /* “账号/密码/验证码”宽度固定 */
|
|
|
- color: rgba(220,245,255,0.75);
|
|
|
- font-size: calc(var(--s) * 13px);
|
|
|
- letter-spacing: calc(var(--s) * 1px);
|
|
|
- margin-right: calc(var(--s) * 5px);
|
|
|
- user-select: none;
|
|
|
-}
|
|
|
-/* 关键:等比、居中、不拉伸 */
|
|
|
-.field .i{
|
|
|
- width: calc(var(--s) * 20px);
|
|
|
- height: calc(var(--s) * 20px);
|
|
|
- flex: 0 0 calc(var(--s) * 20px);
|
|
|
- object-fit: contain;
|
|
|
- opacity: 0.92;
|
|
|
-}
|
|
|
-
|
|
|
-.inp{
|
|
|
- flex:1;
|
|
|
- height: 100%;
|
|
|
- border: none;
|
|
|
- outline: none;
|
|
|
- background: transparent;
|
|
|
- color: rgba(235,255,255,0.92);
|
|
|
- font-size: calc(var(--s) * 14px);
|
|
|
-}
|
|
|
-
|
|
|
-.inp::placeholder{
|
|
|
- color: rgba(190,225,255,0.55);
|
|
|
-}
|
|
|
-
|
|
|
-.row{
|
|
|
- width:85%;
|
|
|
- display:flex;
|
|
|
- gap: calc(var(--s) * 14px);
|
|
|
- margin-bottom: calc(var(--s) * 10px);
|
|
|
-}
|
|
|
-
|
|
|
-.cap-field{
|
|
|
- flex: 0 0 61.13%;
|
|
|
-}
|
|
|
-
|
|
|
-.captcha{
|
|
|
- flex: 1;
|
|
|
-}
|
|
|
-
|
|
|
-.btn{
|
|
|
- width: 85%;
|
|
|
- height: calc(var(--s) * 54px);
|
|
|
- border: none;
|
|
|
- border-radius: calc(var(--s) * 8px);
|
|
|
- color: rgba(235,255,255,0.95);
|
|
|
- font-size: var(--fs-base);
|
|
|
- font-weight: 600;
|
|
|
- cursor: pointer;
|
|
|
- background: linear-gradient( 180deg, rgba(119,161,255,0) 0%, #77A1FF 100%);
|
|
|
- border-radius: calc(var(--s) * 8px);
|
|
|
- border: calc(var(--s) * 1px) solid rgba(161,190,255,0.7);
|
|
|
-}
|
|
|
-.btn:hover{ filter: brightness(1.08); }
|
|
|
-
|
|
|
-.hint{
|
|
|
- margin-top: calc(var(--s) * 12px);
|
|
|
- margin-bottom: calc(var(--s) * 12px);
|
|
|
- color: rgba(255, 0, 0, 0.92);
|
|
|
- font-size: var(--fs-base);
|
|
|
- width: 85%;
|
|
|
- text-align: left;
|
|
|
-}
|
|
|
-
|
|
|
-/* 响应式重排:窄屏时上下布局 */
|
|
|
-@media (max-width: calc(var(--s) * 980px)){
|
|
|
- .content{
|
|
|
- grid-template-columns: 1fr;
|
|
|
- padding: calc(var(--s) * 18px);
|
|
|
- align-items: start;
|
|
|
- }
|
|
|
- .ellipse-area{ width: 100%; height: 46vh; }
|
|
|
- .right{ justify-content: flex-start; }
|
|
|
-}
|
|
|
-.ai-cloud-ring{
|
|
|
- position: absolute;
|
|
|
- inset: 0;
|
|
|
- pointer-events: none;
|
|
|
- z-index: 1; /* 在 bg(0) 上面,但不压住 header/title 可再调 */
|
|
|
-
|
|
|
- /* ✅必须和 .bg 使用同一张图 + 同样的 size/position,才能精确对齐 */
|
|
|
- background-image: url('~@/assets/images/login-background.png'); /* ← 改成你真实路径 */
|
|
|
- background-repeat: no-repeat;
|
|
|
- background-size: cover;
|
|
|
- background-position: center center;
|
|
|
-
|
|
|
- /* === 你只需要调这三个:云彩环的中心点与半径 === */
|
|
|
- --cx: 27%; /* 云彩环中心 x(大概在左侧 1/4) */
|
|
|
- --cy: 58%; /* 云彩环中心 y(大概在中下) */
|
|
|
- --r1: calc(var(--s) * 145px); /* 内圈挖空半径(越大,AI 字越干净) */
|
|
|
- --r2: calc(var(--s) * 188px); /* 云彩环最亮区域半径 */
|
|
|
- --r3: calc(var(--s) * 288px); /* 外圈渐隐半径(越大,环越厚/越散) */
|
|
|
-
|
|
|
- /* ✅只保留“环形”区域:透明(内) -> 显示(环) -> 透明(外) */
|
|
|
- -webkit-mask-image: radial-gradient(circle at var(--cx) var(--cy),
|
|
|
- rgba(0,0,0,0) var(--r1),
|
|
|
- rgba(0,0,0,1) var(--r2),
|
|
|
- rgba(0,0,0,0) var(--r3)
|
|
|
- );
|
|
|
- mask-image: radial-gradient(circle at var(--cx) var(--cy),
|
|
|
- rgba(0,0,0,0) var(--r1),
|
|
|
- rgba(0,0,0,1) var(--r2),
|
|
|
- rgba(0,0,0,0) var(--r3)
|
|
|
- );
|
|
|
-
|
|
|
- /* ✅增强可见度(科技蓝) */
|
|
|
- mix-blend-mode: lighten;
|
|
|
- filter: drop-shadow(0 0 calc(var(--s) * 40px) rgba(60,220,255,.55)) drop-shadow(0 0 calc(var(--s) * 110px) rgba(60,220,255,.25));
|
|
|
-
|
|
|
- /* ✅慢旋转 + 呼吸淡入淡出 */
|
|
|
- opacity: .35;
|
|
|
- transform-origin: var(--cx) var(--cy);
|
|
|
- animation: cloud-rotate 180s linear infinite, cloud-breathe 7.8s ease-in-out infinite;
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes cloud-rotate{
|
|
|
- from{ transform: rotate(0deg); }
|
|
|
- to{ transform: rotate(360deg); }
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes cloud-breathe{
|
|
|
- 0%{ opacity: .18; filter: drop-shadow(0 0 calc(var(--s) * 22px) rgba(60,220,255,.35)) drop-shadow(0 0 calc(var(--s) * 70px) rgba(60,220,255,.18)); }
|
|
|
- 50%{ opacity: .55; filter: drop-shadow(0 0 calc(var(--s) * 55px) rgba(60,220,255,.70)) drop-shadow(0 0 calc(var(--s) * 140px) rgba(60,220,255,.35)); }
|
|
|
- 100%{ opacity: .22; filter: drop-shadow(0 0 calc(var(--s) * 28px) rgba(60,220,255,.40)) drop-shadow(0 0 calc(var(--s) * 85px) rgba(60,220,255,.20)); }
|
|
|
-}
|
|
|
-
|
|
|
-/* 开门层:覆盖在 bg 上方(z-index 0),不挡 content(content 是 1) */
|
|
|
-.split-door{
|
|
|
- position: absolute;
|
|
|
- inset: 0;
|
|
|
- z-index: 0;
|
|
|
- pointer-events: none;
|
|
|
- opacity: 1;
|
|
|
- perspective: calc(var(--s) * 1200px);
|
|
|
-}
|
|
|
-.door{
|
|
|
- transform-origin: center;
|
|
|
- backface-visibility: hidden;
|
|
|
-}
|
|
|
-/* 两扇门:都用全屏做 cover,再裁半屏(关键:避免中间断裂) */
|
|
|
-.split-door .door{
|
|
|
- position: absolute;
|
|
|
- inset: 0;
|
|
|
-
|
|
|
- background: url("~@/assets/images/login-background.png") center/cover no-repeat; /* 跟 .bg 完全一致 */
|
|
|
- filter: brightness(1.03) contrast(1.04);
|
|
|
- will-change: transform;
|
|
|
-}
|
|
|
-
|
|
|
-/* 左半屏 */
|
|
|
-.split-door .door.left{
|
|
|
- clip-path: inset(0 50% 0 0);
|
|
|
-}
|
|
|
-
|
|
|
-/* 右半屏 */
|
|
|
-.split-door .door.right{
|
|
|
- clip-path: inset(0 0 0 50%);
|
|
|
-}
|
|
|
-
|
|
|
-/* 开门动作更慢更清晰 */
|
|
|
-.split-door.opening .door.left{
|
|
|
- animation: door-left 1.5s cubic-bezier(.18,.85,.22,1) forwards;
|
|
|
-}
|
|
|
-.split-door.opening .door.right{
|
|
|
- animation: door-right 1.5s cubic-bezier(.18,.85,.22,1) forwards;
|
|
|
-}
|
|
|
-.split-door.opening .door.left,
|
|
|
-.split-door.opening .door.right{
|
|
|
- animation-delay: .12s;
|
|
|
-}
|
|
|
-/* 中间能量缝(升级:更亮、更柔) */
|
|
|
-.split-door::after{
|
|
|
- content:"";
|
|
|
- position:absolute;
|
|
|
- left:50%;
|
|
|
- top:0;
|
|
|
- width:calc(var(--s) * 2px);
|
|
|
- height:100%;
|
|
|
- transform: translateX(-50%);
|
|
|
- opacity: 0;
|
|
|
-
|
|
|
- background: linear-gradient(to bottom,
|
|
|
- rgba(0,0,0,0),
|
|
|
- rgba(120,240,255,0.95),
|
|
|
- rgba(0,0,0,0)
|
|
|
- );
|
|
|
- filter: drop-shadow(0 0 calc(var(--s) * 18px) rgba(60,220,255,.85))
|
|
|
- drop-shadow(0 0 calc(var(--s) * 55px) rgba(60,220,255,.35));
|
|
|
-}
|
|
|
-
|
|
|
-.split-door.opening::after{
|
|
|
- opacity: 1;
|
|
|
- animation: seam-fade 0.95s ease forwards;
|
|
|
-}
|
|
|
-
|
|
|
-/* 可选:开门瞬间整体轻微“相机拉近”(更高级) */
|
|
|
-.split-door.opening{
|
|
|
- animation: camera-zoom 0.95s ease forwards;
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes door-left{
|
|
|
- from{ transform: translateX(0) skewY(0deg); }
|
|
|
- to { transform: translateX(-54vw) skewY(-0.6deg); }
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes door-right{
|
|
|
- from{ transform: translateX(0) skewY(0deg); }
|
|
|
- to { transform: translateX(54vw) skewY(0.6deg); }
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes seam-fade{
|
|
|
- from{ opacity: 1; }
|
|
|
- to { opacity: 0; }
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes camera-zoom{
|
|
|
- from{ transform: scale(1); }
|
|
|
- to { transform: scale(1.015); }
|
|
|
-}
|
|
|
-.page-dim{
|
|
|
- position: absolute;
|
|
|
- inset: 0;
|
|
|
- pointer-events: none;
|
|
|
- z-index: 999;
|
|
|
- background: #000;
|
|
|
- opacity: 0;
|
|
|
-}
|
|
|
-/* 开门时整体渐黑 */
|
|
|
-.page-dim.opening{
|
|
|
- animation: page-dim-in 1s ease forwards;
|
|
|
-}
|
|
|
-@keyframes page-dim-in{
|
|
|
- from{ opacity: 0; }
|
|
|
- to{ opacity: 0.95; } /* 想更暗就 0.95 */
|
|
|
-}
|
|
|
|
|
|
/* 版权信息 */
|
|
|
.copyright {
|