<template>
|
<div class="login-page">
|
<main class="page">
|
<section class="factory">
|
<div class="brand hero-brand">
|
<div class="logo hero-logo">
|
<img :src="brandLogoUrl"
|
:alt="`${companyName} logo`"
|
class="logo-image hero-logo-image"
|
@error="handleLogoError" />
|
</div>
|
</div>
|
<div class="hero">
|
<div class="chip">数字工厂 · 智能排产 · 设备互联 · 质量追溯</div>
|
<h1>数字工厂<br />MOM 智造平台</h1>
|
<p>
|
以实时数据驱动生产现场,把工单、设备、物料、质量、能耗与仓储连接成一张透明的制造运营网络。
|
</p>
|
</div>
|
<div class="scene"
|
aria-hidden="true">
|
<div class="floor"></div>
|
<svg class="factory-svg"
|
viewBox="0 0 920 360"
|
preserveAspectRatio="xMidYMid meet">
|
<defs>
|
<linearGradient id="g1"
|
x1="0"
|
y1="0"
|
x2="1"
|
y2="1">
|
<stop offset="0"
|
stop-color="#40e4ff"
|
stop-opacity=".9" />
|
<stop offset="1"
|
stop-color="#1f78ff"
|
stop-opacity=".68" />
|
</linearGradient>
|
<linearGradient id="g2"
|
x1="0"
|
y1="0"
|
x2="1"
|
y2="1">
|
<stop offset="0"
|
stop-color="#ffffff"
|
stop-opacity=".28" />
|
<stop offset="1"
|
stop-color="#ffffff"
|
stop-opacity=".06" />
|
</linearGradient>
|
</defs>
|
<path d="M45 255H830"
|
stroke="url(#g1)"
|
stroke-width="16"
|
stroke-linecap="round"
|
opacity=".55" />
|
<path class="belt"
|
d="M45 255H830"
|
stroke="#fff"
|
stroke-width="3"
|
stroke-linecap="round"
|
opacity=".75" />
|
<g class="box">
|
<rect x="60"
|
y="212"
|
width="54"
|
height="42"
|
rx="8"
|
fill="#ffb15f" />
|
<path d="M60 225h54"
|
stroke="#fff"
|
opacity=".45" />
|
</g>
|
<g class="box two">
|
<rect x="60"
|
y="212"
|
width="54"
|
height="42"
|
rx="8"
|
fill="#28d9cd" />
|
<path d="M60 225h54"
|
stroke="#fff"
|
opacity=".45" />
|
</g>
|
<g class="box three">
|
<rect x="60"
|
y="212"
|
width="54"
|
height="42"
|
rx="8"
|
fill="#8b5cf6" />
|
<path d="M60 225h54"
|
stroke="#fff"
|
opacity=".45" />
|
</g>
|
<g>
|
<rect x="120"
|
y="112"
|
width="138"
|
height="128"
|
rx="10"
|
fill="url(#g2)"
|
stroke="rgba(255,255,255,.42)" />
|
<path d="M145 185h88M145 210h58"
|
stroke="#40e4ff"
|
stroke-width="6"
|
stroke-linecap="round" />
|
<path d="M145 140h88"
|
stroke="#fff"
|
stroke-opacity=".5"
|
stroke-width="4"
|
stroke-linecap="round" />
|
</g>
|
<g>
|
<rect x="315"
|
y="76"
|
width="190"
|
height="164"
|
rx="12"
|
fill="url(#g2)"
|
stroke="rgba(255,255,255,.42)" />
|
<path d="M350 126h120M350 158h90M350 190h112"
|
stroke="#fff"
|
stroke-opacity=".5"
|
stroke-width="6"
|
stroke-linecap="round" />
|
<circle class="signal"
|
cx="472"
|
cy="104"
|
r="10"
|
fill="#20e0d2" />
|
<circle class="signal two"
|
cx="448"
|
cy="104"
|
r="10"
|
fill="#1f78ff" />
|
<circle class="signal three"
|
cx="424"
|
cy="104"
|
r="10"
|
fill="#ff8a3d" />
|
</g>
|
<g class="arm">
|
<path d="M612 124h92"
|
stroke="#40e4ff"
|
stroke-width="14"
|
stroke-linecap="round" />
|
<path d="M704 124l42 56"
|
stroke="#40e4ff"
|
stroke-width="14"
|
stroke-linecap="round" />
|
<circle cx="612"
|
cy="124"
|
r="25"
|
fill="#1f78ff"
|
stroke="#fff"
|
stroke-opacity=".45" />
|
<circle cx="704"
|
cy="124"
|
r="18"
|
fill="#20e0d2" />
|
<path d="M744 180v34M727 214h34"
|
stroke="#fff"
|
stroke-width="7"
|
stroke-linecap="round" />
|
</g>
|
<g>
|
<rect x="690"
|
y="82"
|
width="148"
|
height="158"
|
rx="12"
|
fill="url(#g2)"
|
stroke="rgba(255,255,255,.42)" />
|
<path d="M724 206V134M764 206V112M804 206V154"
|
stroke="#20e0d2"
|
stroke-width="12"
|
stroke-linecap="round" />
|
<path d="M720 206h92"
|
stroke="#fff"
|
stroke-opacity=".44"
|
stroke-width="5"
|
stroke-linecap="round" />
|
</g>
|
<path d="M190 112C265 42 348 48 410 76C502 118 568 76 612 124C654 170 700 74 764 82"
|
fill="none"
|
stroke="#20e0d2"
|
stroke-width="2"
|
stroke-dasharray="8 10"
|
opacity=".58">
|
<animate attributeName="stroke-dashoffset"
|
from="80"
|
to="0"
|
dur="2s"
|
repeatCount="indefinite" />
|
</path>
|
</svg>
|
</div>
|
</section>
|
<section class="login-wrap">
|
<div class="time">
|
<span>{{ todayLabel }}</span>
|
{{ clockLabel }}
|
</div>
|
<form class="login-card"
|
@submit.prevent="handleLogin">
|
<div class="brand card-brand">
|
<div class="logo">
|
<img :src="brandIconUrl"
|
:alt="`${companyName} icon`"
|
class="logo-image card-logo-image" />
|
</div>
|
<div class="brand-copy card-brand-copy">
|
<div class="brand-title">{{ companyName }}</div>
|
<small>数字工厂统一入口</small>
|
</div>
|
</div>
|
<h2>欢迎登录</h2>
|
<p class="sub">进入 MOM 数字工厂运营驾驶舱</p>
|
<div class="form-row">
|
<label>账号</label>
|
<div class="input">
|
<svg viewBox="0 0 24 24"
|
fill="none">
|
<path d="M20 21a8 8 0 0 0-16 0"
|
stroke="currentColor"
|
stroke-width="1.8"
|
stroke-linecap="round" />
|
<path d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z"
|
stroke="currentColor"
|
stroke-width="1.8" />
|
</svg>
|
<input v-model.trim="loginForm.username"
|
type="text"
|
placeholder="请输入管理员账号" />
|
</div>
|
</div>
|
<div class="form-row">
|
<label>密码</label>
|
<div class="input">
|
<svg viewBox="0 0 24 24"
|
fill="none">
|
<path d="M7 10V8a5 5 0 0 1 10 0v2"
|
stroke="currentColor"
|
stroke-width="1.8"
|
stroke-linecap="round" />
|
<path d="M6.8 10h10.4A1.8 1.8 0 0 1 19 11.8v6.4A1.8 1.8 0 0 1 17.2 20H6.8A1.8 1.8 0 0 1 5 18.2v-6.4A1.8 1.8 0 0 1 6.8 10Z"
|
stroke="currentColor"
|
stroke-width="1.8" />
|
</svg>
|
<input v-model="loginForm.password"
|
type="password"
|
placeholder="请输入登录密码"
|
autocomplete="current-password"
|
@keyup.enter="handleLogin" />
|
</div>
|
</div>
|
<div class="options">
|
<label class="check"><input v-model="loginForm.rememberMe"
|
type="checkbox" />记住账号</label>
|
</div>
|
<button class="login-btn"
|
type="submit"
|
:disabled="loading">
|
{{ loading ? "登录中..." : "登录数字工厂" }}
|
</button>
|
</form>
|
</section>
|
</main>
|
</div>
|
</template>
|
|
<script setup>
|
import { ElMessage } from "element-plus";
|
import Cookies from "js-cookie";
|
import { encrypt, decrypt } from "@/utils/jsencrypt";
|
import useUserStore from "@/store/modules/user";
|
import defaultBrandLogo from "@/assets/logo/logo.png";
|
|
const userStore = useUserStore();
|
const route = useRoute();
|
const router = useRouter();
|
|
const appTitle = String(
|
import.meta.env.VITE_APP_TITLE || "数字工厂 MOM 系统"
|
).trim();
|
const companySubtitle = String(
|
import.meta.env.VITE_LOGIN_SUBTITLE || "Digital Factory Operation Center"
|
).trim();
|
const configuredLogo = String(import.meta.env.VITE_APP_LOGO || "").trim();
|
const logoModules = import.meta.glob("/src/assets/logo/*.png", { eager: true });
|
const brandIconUrl = `${import.meta.env.BASE_URL}favicon.ico`;
|
|
const redirect = ref("");
|
const loading = ref(false);
|
const now = ref(new Date());
|
const brandLogoUrl = ref(defaultBrandLogo);
|
|
const loginForm = ref({
|
username: "",
|
password: "",
|
rememberMe: false,
|
});
|
|
const companyName = computed(() => {
|
const currentFactoryName = String(userStore.currentFactoryName || "").trim();
|
return currentFactoryName || appTitle;
|
});
|
|
const todayLabel = computed(() => {
|
const date = now.value;
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(
|
date.getDate()
|
)}`;
|
});
|
|
const clockLabel = computed(() => {
|
const date = now.value;
|
return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(
|
date.getSeconds()
|
)}`;
|
});
|
|
watch(
|
route,
|
newRoute => {
|
redirect.value = String(newRoute.query?.redirect || "");
|
},
|
{ immediate: true }
|
);
|
|
watch(
|
() => userStore.currentFactoryName,
|
() => updateBrandLogo(),
|
{ immediate: true }
|
);
|
|
let timer = 0;
|
onMounted(() => {
|
timer = window.setInterval(() => {
|
now.value = new Date();
|
}, 1000);
|
});
|
|
onBeforeUnmount(() => {
|
if (timer) {
|
window.clearInterval(timer);
|
timer = 0;
|
}
|
});
|
|
function pad(value) {
|
return String(value).padStart(2, "0");
|
}
|
|
function resolveConfiguredLogo() {
|
if (!configuredLogo) {
|
return "";
|
}
|
|
if (
|
/^(https?:)?\/\//.test(configuredLogo) ||
|
configuredLogo.startsWith("data:")
|
) {
|
return configuredLogo;
|
}
|
|
const cleanPath = configuredLogo.replace(/^\/+/, "");
|
const fullPath = cleanPath.startsWith("src/")
|
? `/${cleanPath}`
|
: `/src/${cleanPath}`;
|
const localLogo = logoModules[fullPath];
|
|
if (localLogo && localLogo.default) {
|
return localLogo.default;
|
}
|
|
if (configuredLogo.startsWith("/")) {
|
return configuredLogo;
|
}
|
|
return `${import.meta.env.BASE_URL}${cleanPath}`;
|
}
|
|
function updateBrandLogo() {
|
const logoFromConfig = resolveConfiguredLogo();
|
if (logoFromConfig) {
|
brandLogoUrl.value = logoFromConfig;
|
return;
|
}
|
|
const currentFactoryName = String(userStore.currentFactoryName || "").trim();
|
if (!currentFactoryName) {
|
brandLogoUrl.value = defaultBrandLogo;
|
return;
|
}
|
|
const factoryLogoPath = `/src/assets/logo/${currentFactoryName}.png`;
|
const matched = logoModules[factoryLogoPath];
|
brandLogoUrl.value =
|
matched && matched.default ? matched.default : defaultBrandLogo;
|
}
|
|
function handleLogoError() {
|
brandLogoUrl.value = defaultBrandLogo;
|
}
|
|
function handleRememberCookie() {
|
if (!loginForm.value.rememberMe) {
|
Cookies.remove("username");
|
Cookies.remove("password");
|
Cookies.remove("rememberMe");
|
return;
|
}
|
|
Cookies.set("username", loginForm.value.username, { expires: 30 });
|
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 });
|
Cookies.set("rememberMe", "true", { expires: 30 });
|
}
|
|
function getCookie() {
|
const username = Cookies.get("username");
|
const password = Cookies.get("password");
|
const rememberMe = Cookies.get("rememberMe");
|
|
loginForm.value.username = username || "";
|
loginForm.value.password = password ? decrypt(password) : "";
|
loginForm.value.rememberMe = rememberMe === "true";
|
}
|
|
function handleLogin() {
|
if (!loginForm.value.username) {
|
ElMessage.error("请输入账号");
|
return;
|
}
|
if (!loginForm.value.password) {
|
ElMessage.error("请输入密码");
|
return;
|
}
|
|
loading.value = true;
|
handleRememberCookie();
|
userStore
|
.loginCheckFactory(loginForm.value)
|
.then(() => {
|
const query = route.query;
|
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
|
if (cur !== "redirect") {
|
acc[cur] = query[cur];
|
}
|
return acc;
|
}, {});
|
router.push({ path: redirect.value || "/", query: otherQueryParams });
|
})
|
.catch(() => {})
|
.finally(() => {
|
loading.value = false;
|
});
|
}
|
|
getCookie();
|
</script>
|
|
<style scoped lang="scss">
|
.login-page {
|
--accent: var(--accent-primary, var(--el-color-primary, #374d77));
|
--accent-rgb: var(--el-color-primary-rgb, 22, 116, 88);
|
--text: #0f172a;
|
--muted: #64748b;
|
--border: rgba(15, 23, 42, 0.1);
|
--shadow: 0 24px 70px rgba(2, 6, 23, 0.12);
|
position: relative;
|
min-height: 100vh;
|
overflow: hidden;
|
background: radial-gradient(
|
circle at 12% 14%,
|
rgba(var(--accent-rgb), 0.14),
|
transparent 38%
|
),
|
radial-gradient(circle at 92% 8%, rgba(2, 132, 199, 0.12), transparent 36%),
|
conic-gradient(
|
from 220deg at 24% 18%,
|
rgba(var(--accent-rgb), 0.1),
|
rgba(56, 189, 248, 0.08),
|
rgba(99, 102, 241, 0.08),
|
rgba(var(--accent-rgb), 0.1)
|
),
|
linear-gradient(180deg, #f7f8fb 0%, #f3f5fa 100%);
|
}
|
|
.login-page * {
|
box-sizing: border-box;
|
}
|
|
.login-page::before {
|
content: "";
|
position: fixed;
|
inset: 0;
|
background-image: radial-gradient(rgba(15, 23, 42, 0.05) 1px, transparent 1px),
|
linear-gradient(rgba(15, 23, 42, 0.045) 1px, transparent 1px),
|
linear-gradient(90deg, rgba(15, 23, 42, 0.045) 1px, transparent 1px),
|
repeating-linear-gradient(
|
118deg,
|
transparent 0 84px,
|
rgba(var(--accent-rgb), 0.26) 84px 86px,
|
transparent 86px 168px
|
),
|
repeating-linear-gradient(
|
208deg,
|
transparent 0 132px,
|
rgba(2, 132, 199, 0.22) 132px 134px,
|
transparent 134px 264px
|
),
|
repeating-linear-gradient(
|
156deg,
|
transparent 0 112px,
|
rgba(99, 102, 241, 0.2) 112px 114px,
|
transparent 114px 224px
|
),
|
repeating-linear-gradient(
|
118deg,
|
transparent 0 84px,
|
rgba(var(--accent-rgb), 0.12) 83px 89px,
|
transparent 89px 168px
|
);
|
background-size: 22px 22px, 52px 52px, 52px 52px, auto, auto, auto, auto;
|
background-position: 0 0, 0 0, 0 0, 0 0, -120px 140px, 120px 0;
|
mask-image: radial-gradient(circle at 42% 42%, #000 0%, transparent 80%);
|
opacity: 1;
|
mix-blend-mode: normal;
|
filter: saturate(1.08);
|
animation: lineDrift 12s linear infinite;
|
pointer-events: none;
|
z-index: 0;
|
}
|
|
.login-page::after {
|
content: "";
|
position: fixed;
|
inset: -28vmax;
|
background: radial-gradient(
|
circle at 18% 28%,
|
rgba(var(--accent-rgb), 0.22),
|
transparent 54%
|
),
|
radial-gradient(
|
circle at 82% 22%,
|
rgba(56, 189, 248, 0.16),
|
transparent 50%
|
),
|
radial-gradient(
|
circle at 72% 82%,
|
rgba(99, 102, 241, 0.12),
|
transparent 52%
|
),
|
radial-gradient(circle at 26% 78%, rgba(244, 63, 94, 0.06), transparent 56%);
|
filter: blur(42px) saturate(1.05);
|
opacity: 0.9;
|
transform: translate3d(0, 0, 0);
|
animation: bgFloat 18s ease-in-out infinite;
|
pointer-events: none;
|
z-index: 0;
|
}
|
|
.page {
|
position: relative;
|
z-index: 1;
|
max-width: 1200px;
|
margin: 48px auto;
|
width: calc(100% - 48px);
|
min-height: calc(100vh - 96px);
|
display: grid;
|
grid-template-columns: 3fr 2fr;
|
gap: 0;
|
align-items: stretch;
|
border-radius: var(--content-radius);
|
background: rgba(255, 255, 255, 0.72);
|
border: 1px solid rgba(255, 255, 255, 0.72);
|
box-shadow: var(--shadow);
|
backdrop-filter: blur(18px);
|
overflow: hidden;
|
}
|
|
.page::before {
|
content: "";
|
position: absolute;
|
inset: 0;
|
border-radius: inherit;
|
background: radial-gradient(
|
circle at 22% 22%,
|
rgba(var(--accent-rgb), 0.34),
|
transparent 58%
|
)
|
left center / 60% 100% no-repeat,
|
radial-gradient(
|
circle at 76% 12%,
|
rgba(56, 189, 248, 0.22),
|
transparent 50%
|
)
|
left center / 60% 100% no-repeat,
|
linear-gradient(
|
135deg,
|
rgba(2, 6, 23, 0.96) 0%,
|
rgba(15, 35, 57, 0.94) 52%,
|
rgba(15, 35, 57, 0.88) 100%
|
)
|
left center / 60% 100% no-repeat;
|
pointer-events: none;
|
z-index: 0;
|
}
|
|
.page::after {
|
content: "";
|
position: absolute;
|
top: 0;
|
bottom: 0;
|
left: 60%;
|
width: 1px;
|
background: linear-gradient(
|
180deg,
|
transparent,
|
rgba(15, 23, 42, 0.16),
|
transparent
|
);
|
pointer-events: none;
|
z-index: 0;
|
}
|
|
.factory {
|
position: relative;
|
z-index: 1;
|
padding: 44px 48px 40px;
|
color: rgba(255, 255, 255, 0.92);
|
display: flex;
|
flex-direction: column;
|
justify-content: space-between;
|
}
|
|
.brand {
|
position: relative;
|
z-index: 1;
|
display: inline-flex;
|
align-items: center;
|
}
|
|
.logo {
|
width: 52px;
|
height: 52px;
|
border-radius: var(--radius-md);
|
display: grid;
|
place-items: center;
|
background: rgba(var(--accent-rgb), 0.12);
|
border: 1px solid rgba(var(--accent-rgb), 0.22);
|
overflow: hidden;
|
}
|
|
.logo-image {
|
width: 80%;
|
height: 80%;
|
object-fit: contain;
|
}
|
|
.hero-brand {
|
display: block;
|
}
|
|
.hero-logo {
|
width: 260px;
|
height: 72px;
|
border-radius: 0;
|
background: transparent;
|
border: none;
|
place-items: center start;
|
}
|
|
.hero-logo-image {
|
width: 100%;
|
height: 100%;
|
object-fit: contain;
|
filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.22));
|
}
|
|
.hero {
|
position: relative;
|
z-index: 1;
|
margin: 46px 0 0;
|
max-width: 640px;
|
}
|
|
.chip {
|
display: inline-flex;
|
align-items: center;
|
padding: 7px 12px;
|
border-radius: var(--radius-md);
|
border: 1px solid rgba(255, 255, 255, 0.16);
|
background: rgba(255, 255, 255, 0.08);
|
color: rgba(255, 255, 255, 0.78);
|
font-size: 13px;
|
letter-spacing: 0.04em;
|
text-transform: uppercase;
|
}
|
|
.hero h1 {
|
margin: 22px 0 14px;
|
font-size: clamp(44px, 5vw, 66px);
|
line-height: 1.06;
|
font-weight: 860;
|
letter-spacing: -0.02em;
|
color: #ffffff;
|
}
|
|
.hero p {
|
margin: 0;
|
max-width: 600px;
|
font-size: 16px;
|
line-height: 1.9;
|
color: rgba(255, 255, 255, 0.72);
|
}
|
|
.scene {
|
display: none;
|
}
|
|
.login-wrap {
|
position: relative;
|
z-index: 1;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
padding: 36px 44px;
|
}
|
|
.time {
|
position: absolute;
|
top: 18px;
|
right: 18px;
|
display: flex;
|
gap: 10px;
|
align-items: center;
|
color: rgba(15, 23, 42, 0.72);
|
font-size: 13px;
|
font-weight: 700;
|
}
|
|
.time span {
|
padding: 6px 10px;
|
border-radius: var(--radius-md);
|
border: 1px solid rgba(15, 23, 42, 0.12);
|
background: rgba(255, 255, 255, 0.7);
|
color: rgba(15, 23, 42, 0.6);
|
font-weight: 800;
|
}
|
|
.login-card {
|
width: min(420px, 100%);
|
padding: 38px 34px 34px;
|
border-radius: var(--radius-lg);
|
border: 1px solid rgba(15, 23, 42, 0.12);
|
background: rgba(255, 255, 255, 0.86);
|
backdrop-filter: blur(14px);
|
box-shadow: var(--shadow);
|
position: relative;
|
overflow: hidden;
|
}
|
|
.login-card::before {
|
content: "";
|
position: absolute;
|
left: -120px;
|
top: -120px;
|
width: 240px;
|
height: 240px;
|
border-radius: var(--radius-lg);
|
background: radial-gradient(
|
circle at 30% 30%,
|
rgba(var(--accent-rgb), 0.36),
|
transparent 60%
|
);
|
filter: blur(1px);
|
}
|
|
.login-card > * {
|
position: relative;
|
z-index: 1;
|
}
|
|
.card-brand {
|
margin-bottom: 18px;
|
gap: 12px;
|
}
|
|
.card-brand .logo {
|
width: 46px;
|
height: 46px;
|
border-radius: var(--radius-md);
|
background: rgba(var(--accent-rgb), 0.12);
|
border: 1px solid rgba(var(--accent-rgb), 0.22);
|
}
|
|
.card-brand-copy {
|
margin-left: 0;
|
}
|
|
.brand-title {
|
font-size: 15px;
|
font-weight: 900;
|
color: rgba(15, 23, 42, 0.92);
|
letter-spacing: 0.02em;
|
}
|
|
.card-brand-copy small {
|
display: block;
|
margin-top: 4px;
|
font-size: 12px;
|
color: rgba(15, 23, 42, 0.55);
|
font-weight: 700;
|
}
|
|
.login-card h2 {
|
margin: 10px 0 8px;
|
font-size: 28px;
|
line-height: 1.2;
|
color: rgba(15, 23, 42, 0.92);
|
font-weight: 900;
|
letter-spacing: -0.02em;
|
}
|
|
.sub {
|
margin: 0 0 22px;
|
color: var(--muted);
|
font-size: 13px;
|
font-weight: 700;
|
}
|
|
.form-row {
|
margin-bottom: 14px;
|
}
|
|
label {
|
display: block;
|
margin-bottom: 8px;
|
color: rgba(15, 23, 42, 0.72);
|
font-size: 12px;
|
font-weight: 800;
|
letter-spacing: 0.06em;
|
text-transform: uppercase;
|
}
|
|
.input {
|
position: relative;
|
}
|
|
.input svg {
|
position: absolute;
|
left: 14px;
|
top: 50%;
|
width: 18px;
|
height: 18px;
|
transform: translateY(-50%);
|
color: rgba(15, 23, 42, 0.45);
|
pointer-events: none;
|
}
|
|
input[type="text"],
|
input[type="password"] {
|
width: 100%;
|
height: 50px;
|
padding: 0 14px 0 44px;
|
border: 1px solid var(--border);
|
border-radius: var(--radius-md);
|
outline: none;
|
background: rgba(255, 255, 255, 0.92);
|
color: var(--text);
|
font-size: 15px;
|
transition: box-shadow 0.16s ease, border-color 0.16s ease,
|
transform 0.16s ease;
|
}
|
|
input[type="text"]::placeholder,
|
input[type="password"]::placeholder {
|
color: rgba(100, 116, 139, 0.88);
|
}
|
|
input[type="text"]:hover,
|
input[type="password"]:hover {
|
border-color: rgba(var(--accent-rgb), 0.28);
|
}
|
|
input[type="text"]:focus,
|
input[type="password"]:focus {
|
border-color: rgba(var(--accent-rgb), 0.6);
|
box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.14);
|
}
|
|
.options {
|
display: flex;
|
justify-content: flex-start;
|
align-items: center;
|
margin: 14px 0 22px;
|
font-size: 13px;
|
color: rgba(15, 23, 42, 0.62);
|
font-weight: 700;
|
}
|
|
.check {
|
display: inline-flex;
|
gap: 10px;
|
align-items: center;
|
cursor: pointer;
|
user-select: none;
|
}
|
|
.check input {
|
width: 16px;
|
height: 16px;
|
padding: 0;
|
accent-color: var(--accent);
|
}
|
|
.login-btn {
|
width: 100%;
|
height: 52px;
|
border: none;
|
border-radius: var(--radius-md);
|
cursor: pointer;
|
color: #ffffff;
|
font-size: 15px;
|
font-weight: 900;
|
letter-spacing: 0.02em;
|
background: var(--accent);
|
box-shadow: 0 18px 44px rgba(var(--accent-rgb), 0.22);
|
transition: transform 0.16s ease, box-shadow 0.16s ease, filter 0.16s ease;
|
}
|
|
.login-btn:hover:not(:disabled) {
|
transform: translateY(-1px);
|
box-shadow: 0 20px 52px rgba(var(--accent-rgb), 0.28);
|
filter: saturate(1.02);
|
}
|
|
.login-btn:active:not(:disabled) {
|
transform: translateY(0);
|
}
|
|
.login-btn:disabled {
|
opacity: 0.72;
|
cursor: not-allowed;
|
}
|
|
.login-btn:focus-visible {
|
outline: none;
|
box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.18),
|
0 18px 44px rgba(var(--accent-rgb), 0.22);
|
}
|
|
:global(html.dark) .login-page {
|
--text: rgba(248, 250, 252, 0.92);
|
--muted: rgba(226, 232, 240, 0.62);
|
--border: rgba(226, 232, 240, 0.16);
|
background: radial-gradient(
|
circle at 14% 18%,
|
rgba(var(--accent-rgb), 0.22),
|
transparent 40%
|
),
|
radial-gradient(
|
circle at 86% 12%,
|
rgba(56, 189, 248, 0.14),
|
transparent 42%
|
),
|
conic-gradient(
|
from 220deg at 22% 16%,
|
rgba(var(--accent-rgb), 0.16),
|
rgba(56, 189, 248, 0.12),
|
rgba(99, 102, 241, 0.12),
|
rgba(var(--accent-rgb), 0.16)
|
),
|
linear-gradient(180deg, #0b1220 0%, #070b13 100%);
|
}
|
|
:global(html.dark) .login-page::before {
|
background-image: radial-gradient(
|
rgba(226, 232, 240, 0.07) 1px,
|
transparent 1px
|
),
|
linear-gradient(rgba(226, 232, 240, 0.06) 1px, transparent 1px),
|
linear-gradient(90deg, rgba(226, 232, 240, 0.06) 1px, transparent 1px),
|
repeating-linear-gradient(
|
118deg,
|
transparent 0 84px,
|
rgba(var(--accent-rgb), 0.32) 84px 86px,
|
transparent 86px 168px
|
),
|
repeating-linear-gradient(
|
208deg,
|
transparent 0 132px,
|
rgba(56, 189, 248, 0.28) 132px 134px,
|
transparent 134px 264px
|
),
|
repeating-linear-gradient(
|
156deg,
|
transparent 0 112px,
|
rgba(99, 102, 241, 0.26) 112px 114px,
|
transparent 114px 224px
|
),
|
repeating-linear-gradient(
|
118deg,
|
transparent 0 84px,
|
rgba(var(--accent-rgb), 0.16) 83px 89px,
|
transparent 89px 168px
|
);
|
background-size: 22px 22px, 52px 52px, 52px 52px, auto, auto, auto, auto;
|
opacity: 0.96;
|
mix-blend-mode: screen;
|
}
|
|
:global(html.dark) .login-page::after {
|
opacity: 0.7;
|
filter: blur(46px) saturate(1.08);
|
background: radial-gradient(
|
circle at 18% 28%,
|
rgba(var(--accent-rgb), 0.28),
|
transparent 56%
|
),
|
radial-gradient(
|
circle at 84% 18%,
|
rgba(56, 189, 248, 0.18),
|
transparent 52%
|
),
|
radial-gradient(
|
circle at 70% 82%,
|
rgba(99, 102, 241, 0.14),
|
transparent 56%
|
),
|
radial-gradient(circle at 30% 84%, rgba(244, 63, 94, 0.06), transparent 60%);
|
}
|
|
:global(html.dark) .time {
|
color: rgba(226, 232, 240, 0.68);
|
}
|
|
:global(html.dark) .time span {
|
background: rgba(15, 23, 42, 0.5);
|
border-color: rgba(226, 232, 240, 0.16);
|
color: rgba(226, 232, 240, 0.72);
|
}
|
|
:global(html.dark) .login-card {
|
background: rgba(2, 6, 23, 0.56);
|
border-color: rgba(226, 232, 240, 0.16);
|
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.46);
|
}
|
|
:global(html.dark) .page {
|
background: rgba(2, 6, 23, 0.46);
|
border-color: rgba(226, 232, 240, 0.12);
|
box-shadow: 0 38px 96px rgba(0, 0, 0, 0.5);
|
}
|
|
:global(html.dark) .page::after {
|
background: linear-gradient(
|
180deg,
|
transparent,
|
rgba(226, 232, 240, 0.14),
|
transparent
|
);
|
}
|
|
:global(html.dark) .brand-title {
|
color: rgba(248, 250, 252, 0.92);
|
}
|
|
:global(html.dark) .card-brand-copy small {
|
color: rgba(226, 232, 240, 0.62);
|
}
|
|
:global(html.dark) .login-card h2 {
|
color: rgba(248, 250, 252, 0.92);
|
}
|
|
:global(html.dark) label {
|
color: rgba(226, 232, 240, 0.72);
|
}
|
|
:global(html.dark) .input svg {
|
color: rgba(226, 232, 240, 0.55);
|
}
|
|
:global(html.dark) input[type="text"],
|
:global(html.dark) input[type="password"] {
|
background: rgba(2, 6, 23, 0.38);
|
color: rgba(248, 250, 252, 0.92);
|
}
|
|
:global(html.dark) input[type="text"]::placeholder,
|
:global(html.dark) input[type="password"]::placeholder {
|
color: rgba(226, 232, 240, 0.55);
|
}
|
|
:global(html.dark) .options {
|
color: rgba(226, 232, 240, 0.68);
|
}
|
|
@keyframes bgFloat {
|
0% {
|
transform: translate3d(-1.8%, -1.2%, 0) scale(1.02);
|
}
|
50% {
|
transform: translate3d(1.6%, 1.4%, 0) scale(1.06);
|
}
|
100% {
|
transform: translate3d(-1.8%, -1.2%, 0) scale(1.02);
|
}
|
}
|
|
@keyframes lineDrift {
|
0% {
|
background-position: 0 0, 0 0, 0 0, 0 0, -120px 140px, 120px 0, 0 0;
|
}
|
100% {
|
background-position: 0 0, 220px 260px, -240px 210px, 760px -520px,
|
-980px 720px, 680px 860px, 760px -520px;
|
}
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
.login-page::before {
|
animation: none;
|
}
|
|
.login-page::after {
|
animation: none;
|
}
|
}
|
|
@media (max-width: 980px) {
|
.login-page {
|
overflow: auto;
|
}
|
|
.page {
|
grid-template-columns: 1fr;
|
gap: 18px;
|
margin: 18px auto;
|
width: calc(100% - 32px);
|
min-height: auto;
|
max-width: 560px;
|
}
|
|
.page::before,
|
.page::after {
|
display: none;
|
}
|
|
.factory {
|
display: none;
|
}
|
|
.login-wrap {
|
padding: 0;
|
}
|
|
.time {
|
display: none;
|
}
|
|
.login-card {
|
width: 100%;
|
padding: 30px 22px 22px;
|
}
|
}
|
</style>
|