style(web): polish login glass form

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-06-30 17:49:40 +08:00
co-authored by Codex
parent 821244bf27
commit 03df5120ed
2 changed files with 190 additions and 265 deletions
+2 -2
View File
@@ -52,14 +52,14 @@ const sizeClasses: Record<ButtonSize, string> = {
sm: 'h-7 gap-1.5 px-3 text-xs',
default: 'h-9 gap-2 px-4 text-[13px]',
md: 'h-11 gap-2 px-4 text-[13px]',
lg: 'h-10.5 gap-2.5 px-5 text-[15px]',
lg: 'h-11.5 gap-2.5 px-5 text-[15px]',
}
const iconOnlySizeClasses: Record<ButtonSize, string> = {
sm: 'size-8 rounded-(--cp-icon-button-radius)',
default: 'size-9 rounded-(--cp-icon-button-radius)',
md: 'size-11 rounded-(--cp-icon-button-radius)',
lg: 'size-10.5 rounded-(--cp-icon-button-radius)',
lg: 'size-11.5 rounded-(--cp-icon-button-radius)',
}
const loadingIconSize: Record<ButtonSize, number> = {
+188 -263
View File
@@ -1,18 +1,33 @@
<script setup lang="ts">
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { computed, shallowRef } from 'vue'
import { useRouter } from 'vue-router'
import { ArrowRight, Cat, KeyRound, Mail } from '@lucide/vue'
import { ArrowRight, Cat, Eye, EyeOff, KeyRound, Mail, Moon, Sun } from '@lucide/vue'
import BaseButton from '@/components/base/BaseButton.vue'
import BaseCard from '@/components/base/BaseCard.vue'
import BaseInput from '@/components/base/BaseInput.vue'
import { useAuthStore } from '@/stores/modules/auth'
import { useUiStore } from '@/stores/modules/ui'
const router = useRouter()
const authStore = useAuthStore()
const uiStore = useUiStore()
const { effectiveTheme } = storeToRefs(uiStore)
const { toggleTheme } = uiStore
const username = ref('')
const password = ref('')
const username = shallowRef('')
const password = shallowRef('')
const passwordVisible = shallowRef(false)
const passwordType = computed(() => (passwordVisible.value ? 'text' : 'password'))
const passwordToggleLabel = computed(() => (passwordVisible.value ? '隐藏密码' : '显示密码'))
const themeToggleLabel = computed(() =>
effectiveTheme.value === 'dark' ? '切换浅色模式' : '切换暗黑模式',
)
function togglePasswordVisible() {
passwordVisible.value = !passwordVisible.value
}
async function handleSubmit() {
if (!username.value.trim() || !password.value.trim()) {
@@ -32,44 +47,19 @@ async function handleSubmit() {
<template>
<main class="login-page">
<div class="login-backdrop" aria-hidden="true">
<svg class="login-flow-map" viewBox="0 0 1200 720" fill="none" preserveAspectRatio="none">
<defs>
<linearGradient id="login-flow-primary" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#2563eb" stop-opacity="0.02" />
<stop offset="0.34" stop-color="#2563eb" stop-opacity="0.18" />
<stop offset="0.68" stop-color="#0f9f9a" stop-opacity="0.28" />
<stop offset="1" stop-color="#6d5dfc" stop-opacity="0.04" />
</linearGradient>
<linearGradient id="login-flow-muted" x1="0" x2="1" y1="0" y2="0">
<stop offset="0" stop-color="#0f9f9a" stop-opacity="0.02" />
<stop offset="0.38" stop-color="#0f9f9a" stop-opacity="0.16" />
<stop offset="0.78" stop-color="#2563eb" stop-opacity="0.18" />
<stop offset="1" stop-color="#2563eb" stop-opacity="0.02" />
</linearGradient>
</defs>
<path
class="login-flow-line login-flow-line-a"
d="M-96 452 C96 338 260 332 430 410 C628 502 744 458 914 316 C1044 208 1132 190 1296 246"
stroke="url(#login-flow-primary)"
stroke-width="2"
/>
<path
class="login-flow-line login-flow-line-b"
d="M-96 268 C86 346 252 386 438 294 C620 204 788 190 970 304 C1084 376 1176 386 1296 328"
stroke="url(#login-flow-muted)"
stroke-width="1.35"
/>
<path
class="login-flow-line login-flow-line-c"
d="M-96 584 C104 514 260 494 438 558 C626 626 786 650 984 512 C1112 422 1190 420 1296 468"
stroke="url(#login-flow-primary)"
stroke-width="1.15"
/>
</svg>
<div class="login-page-wash" aria-hidden="true" />
<span class="login-runner login-runner-a" />
</div>
<BaseButton
icon-only
variant="ghost"
size="default"
class="login-theme-toggle"
:label="themeToggleLabel"
@click="toggleTheme($event)"
>
<Sun v-if="effectiveTheme === 'dark'" :size="19" />
<Moon v-else :size="19" />
</BaseButton>
<section class="login-stage" aria-label="Codex Proxy RS 登录">
<header class="login-brand">
@@ -84,7 +74,7 @@ async function handleSubmit() {
<section class="login-copy" aria-labelledby="login-title">
<h1 id="login-title">更轻的模型网关</h1>
<p>安全转发 OpenAI / Codex 请求,账号、密钥、记录清晰可查</p>
<p>安全的转发 OpenAI / Codex 请求, 风控指纹对齐,链路轻量安全</p>
<div class="login-scope" aria-label="管理范围">
<span>轻量部署</span>
<span>安全转发</span>
@@ -130,12 +120,26 @@ async function handleSubmit() {
v-model="password"
class="login-field"
placeholder="输入会话密钥"
type="password"
:type="passwordType"
autocomplete="current-password"
>
<template #prefix>
<KeyRound :size="17" />
</template>
<template #suffix>
<BaseButton
icon-only
variant="ghost"
size="sm"
class="login-password-toggle"
:label="passwordToggleLabel"
@mousedown.prevent
@click="togglePasswordVisible"
>
<EyeOff v-if="passwordVisible" :size="16" />
<Eye v-else :size="16" />
</BaseButton>
</template>
</BaseInput>
</label>
@@ -156,161 +160,92 @@ async function handleSubmit() {
<style scoped>
.login-page {
--login-glow-primary: #e6f4ff;
--login-glow-normal: #e9fbf7;
--login-bg-start: #f8fbff;
--login-bg-mid: #ffffff;
--login-bg-end: #eef4ff;
--login-wash-line: #ffffffd6;
--login-wash-a: #2563eb18;
--login-wash-b: #0f9f9a18;
--login-wash-c: #64748b10;
--login-grid-y: #0e17260a;
--login-grid-x: #0e172608;
--login-runner-a: #2563ebcc;
--login-runner-b: #0f9f9acc;
--login-runner-shadow: #2563eb24;
--login-form-top: #fffffff5;
--login-form-bottom: #fffffff0;
--login-form-edge-a: #ffffff;
--login-form-edge-b: #dbeafe80;
--login-form-edge-c: #c7efee66;
--login-form-shadow: #0e17264d;
--login-form-highlight-a: #93c5fd;
--login-form-highlight-b: #a7dfdd;
--login-bg-a: #f7fafc;
--login-bg-b: #eef6ff;
--login-bg-c: #f8fbf5;
--login-grid-line: #0e17260a;
--login-rail-line: #2563eb0d;
--login-wash-line: #ffffff8f;
--login-depth-x: 0px;
--login-depth-y: 0px;
--login-form-glass-a: #ffffff42;
--login-form-glass-mid: #f5fbff20;
--login-form-glass-b: #ffffff16;
--login-form-sheen: #ffffff24;
--login-form-shadow: #0e17262f;
--login-form-highlight-a: #60a5fa26;
--login-form-highlight-b: #2dd4bf1f;
--login-control-height: 46px;
position: relative;
isolation: isolate;
min-height: 100dvh;
height: 100dvh;
overflow-x: hidden;
overflow-y: auto;
overflow-y: hidden;
color: var(--cp-text-primary);
background:
radial-gradient(circle at 12% 20%, var(--login-glow-primary) 0, transparent 30%),
radial-gradient(circle at 88% 24%, var(--login-glow-normal) 0, transparent 26%),
linear-gradient(
116deg,
var(--login-bg-start) 0%,
var(--login-bg-mid) 50%,
var(--login-bg-end) 100%
);
linear-gradient(90deg, var(--login-grid-line) 1px, transparent 1px),
linear-gradient(var(--login-grid-line) 1px, transparent 1px),
linear-gradient(135deg, var(--login-bg-a) 0%, var(--login-bg-b) 52%, var(--login-bg-c) 100%);
background-size:
72px 72px,
72px 72px,
auto;
}
:global(html[data-theme='dark'] .login-page) {
--login-glow-primary: #123153;
--login-glow-normal: #0b3a38;
--login-bg-start: #08101b;
--login-bg-mid: #0b111c;
--login-bg-end: #101a2a;
--login-wash-line: #dbeafe12;
--login-wash-a: #6ea8ff20;
--login-wash-b: #2dd4bf1f;
--login-wash-c: #8fa0b714;
--login-grid-y: #e6edf70b;
--login-grid-x: #e6edf708;
--login-runner-a: #6ea8ffcc;
--login-runner-b: #2dd4bfcc;
--login-runner-shadow: #6ea8ff30;
--login-form-top: #121b2af2;
--login-form-bottom: #101827f0;
--login-form-edge-a: #ffffff10;
--login-form-edge-b: #6ea8ff42;
--login-form-edge-c: #2dd4bf36;
--login-form-shadow: #000000a3;
--login-form-highlight-a: #6ea8ff;
--login-form-highlight-b: #2dd4bf;
--login-bg-a: #08101b;
--login-bg-b: #101827;
--login-bg-c: #0d1819;
--login-grid-line: #e6edf70a;
--login-rail-line: #6ea8ff12;
--login-wash-line: #ffffff12;
--login-form-glass-a: #31466526;
--login-form-glass-mid: #1427431a;
--login-form-glass-b: #06132624;
--login-form-sheen: #ffffff0c;
--login-form-shadow: #0000005c;
--login-form-highlight-a: #6ea8ff18;
--login-form-highlight-b: #2dd4bf14;
}
.login-backdrop {
pointer-events: none;
.login-page::before {
position: absolute;
inset: 0;
z-index: -1;
overflow: hidden;
}
.login-backdrop::before {
position: absolute;
inset: -20%;
z-index: -2;
content: '';
background:
linear-gradient(112deg, #ffffff00 22%, var(--login-wash-line) 50%, #ffffff00 74%),
conic-gradient(
from 145deg at 46% 48%,
var(--login-wash-a),
var(--login-wash-b),
var(--login-wash-c),
var(--login-wash-a)
);
filter: blur(42px);
opacity: 0.58;
animation: login-wash 20s ease-in-out infinite alternate;
linear-gradient(112deg, transparent 18%, var(--login-wash-line) 44%, transparent 64%),
repeating-linear-gradient(135deg, transparent 0 30px, var(--login-rail-line) 31px 32px);
mask-image: linear-gradient(90deg, transparent 0%, #000 18%, #000 88%, transparent 100%);
}
.login-backdrop::after {
.login-page-wash {
position: absolute;
inset: -1px;
content: '';
background-image:
linear-gradient(var(--login-grid-y) 1px, transparent 1px),
linear-gradient(90deg, var(--login-grid-x) 1px, transparent 1px);
background-size: 58px 58px;
mask-image: linear-gradient(90deg, transparent 0%, #000 14%, #000 78%, transparent 100%);
opacity: 0.32;
animation: login-grid-drift 28s linear infinite;
inset: -18%;
z-index: -1;
pointer-events: none;
background:
conic-gradient(from 210deg at 48% 42%, #2563eb18, #0f9f9a16, #f59e0b12, #2563eb18),
linear-gradient(90deg, transparent, #ffffff54, transparent);
filter: blur(38px);
opacity: 0.66;
transform: rotate(-2deg);
}
.login-flow-map {
position: absolute;
inset: -4% -12%;
width: 124%;
height: 112%;
opacity: 0.68;
}
.login-flow-line {
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 14 30;
animation: login-line-flow 22s linear infinite;
}
.login-flow-line-b {
animation-direction: reverse;
}
.login-flow-line-c {
stroke-dasharray: 7 24;
opacity: 0.56;
}
.login-runner {
position: absolute;
width: 42px;
height: 2px;
border-radius: var(--cp-radius-circle);
background: linear-gradient(
90deg,
transparent,
var(--login-runner-a),
var(--login-runner-b),
transparent
);
box-shadow: 0 0 18px var(--login-runner-shadow);
opacity: 0;
}
.login-runner-a {
top: 46%;
left: 10%;
animation: login-runner-a 9s cubic-bezier(0.45, 0, 0.2, 1) infinite;
:global(html[data-theme='dark'] .login-page-wash) {
background:
conic-gradient(from 210deg at 48% 42%, #6ea8ff22, #2dd4bf1a, #fbbf2412, #6ea8ff22),
linear-gradient(90deg, transparent, #ffffff0c, transparent);
opacity: 0.72;
}
.login-stage {
position: relative;
z-index: 1;
box-sizing: border-box;
display: grid;
width: min(calc(100% - 40px), 520px);
min-height: 100dvh;
height: 100dvh;
margin: 0 auto;
padding: clamp(24px, 5vh, 56px) 0;
align-content: center;
@@ -318,9 +253,10 @@ async function handleSubmit() {
}
.login-brand {
position: absolute;
top: clamp(28px, 5vh, 56px);
left: 0;
position: fixed;
top: clamp(22px, 4vh, 40px);
left: clamp(20px, 4vw, 56px);
z-index: 2;
display: flex;
min-width: 0;
align-items: center;
@@ -360,18 +296,38 @@ async function handleSubmit() {
line-height: 1.15;
}
.login-theme-toggle {
position: fixed;
top: clamp(24px, 4vh, 42px);
right: clamp(20px, 4vw, 56px);
z-index: 2;
color: var(--cp-text-secondary);
background: color-mix(in srgb, var(--cp-bg-surface) 54%, transparent);
box-shadow: 0 18px 34px -28px #0e1726;
backdrop-filter: blur(10px) saturate(1.2);
-webkit-backdrop-filter: blur(10px) saturate(1.2);
}
.login-theme-toggle:hover {
color: var(--cp-text-primary);
background: color-mix(in srgb, var(--cp-bg-surface) 72%, transparent);
}
.login-copy {
display: grid;
gap: 13px;
}
.login-copy::before {
width: 42px;
width: 20px;
height: 2px;
content: '';
border-radius: var(--cp-radius-circle);
background: linear-gradient(90deg, #2563eb, #0f9f9a);
opacity: 0.72;
position: relative;
left: 72px;
top: 2px;
}
.login-copy h1 {
@@ -427,42 +383,46 @@ async function handleSubmit() {
}
.login-form {
--cp-bg-surface: transparent;
position: relative;
isolation: isolate;
display: grid;
width: 100%;
gap: 23px;
padding: clamp(32px, 7vw, 42px);
border: 1px solid transparent;
background-color: transparent;
background:
linear-gradient(var(--login-form-top), var(--login-form-bottom)) padding-box,
radial-gradient(circle at 16% 0%, var(--login-form-sheen) 0%, transparent 34%),
radial-gradient(circle at 96% 8%, var(--login-form-highlight-b) 0%, transparent 36%),
linear-gradient(
135deg,
var(--login-form-edge-a),
var(--login-form-edge-b),
var(--login-form-edge-c)
)
border-box;
145deg,
var(--login-form-glass-a),
var(--login-form-glass-mid) 48%,
var(--login-form-glass-b)
);
box-shadow:
0 1px 0 var(--login-form-edge-a) inset,
0 28px 64px -42px var(--login-form-shadow);
backdrop-filter: blur(20px);
0 34px 78px -54px var(--login-form-shadow),
0 24px 64px -58px var(--login-form-highlight-a) inset,
0 -18px 42px -40px var(--login-form-highlight-b) inset;
backdrop-filter: blur(9px) saturate(1.45);
-webkit-backdrop-filter: blur(9px) saturate(1.45);
}
.login-form::before {
position: absolute;
top: 0;
right: 34px;
left: 34px;
height: 1px;
inset: 0;
z-index: 0;
pointer-events: none;
content: '';
background: linear-gradient(
90deg,
transparent,
var(--login-form-highlight-a),
var(--login-form-highlight-b),
transparent
);
opacity: 0.7;
background:
linear-gradient(120deg, var(--login-form-sheen), transparent 32%),
radial-gradient(circle at 72% 0%, var(--login-form-highlight-a), transparent 36%);
opacity: 0.12;
}
.login-form > * {
position: relative;
z-index: 1;
}
.login-form-header {
@@ -488,7 +448,6 @@ async function handleSubmit() {
}
.login-error {
border: 1px solid var(--cp-danger-border);
border-radius: var(--cp-input-radius-base);
background: var(--cp-danger-bg);
padding: 12px 16px;
@@ -514,52 +473,22 @@ async function handleSubmit() {
}
.login-field {
--cp-input-height-default: 46px;
--cp-input-height-default: var(--login-control-height);
}
.login-password-toggle {
flex: 0 0 auto;
color: var(--cp-text-muted);
}
.login-password-toggle:hover {
color: var(--cp-text-primary);
}
.login-submit {
width: 100%;
}
@keyframes login-wash {
0% {
transform: translate3d(-3%, 1%, 0) rotate(-3deg) scale(1);
}
100% {
transform: translate3d(3%, -2%, 0) rotate(3deg) scale(1.045);
}
}
@keyframes login-grid-drift {
to {
background-position: 58px 58px;
}
}
@keyframes login-line-flow {
to {
stroke-dashoffset: -240;
}
}
@keyframes login-runner-a {
0% {
opacity: 0;
transform: translate3d(0, 0, 0) rotate(-8deg);
}
18%,
72% {
opacity: 0.72;
}
100% {
opacity: 0;
transform: translate3d(54vw, -18vh, 0) rotate(-17deg);
}
}
@media (min-width: 720px) {
.login-stage {
width: min(calc(100% - 80px), 640px);
@@ -568,7 +497,7 @@ async function handleSubmit() {
@media (min-width: 1100px) {
.login-stage {
width: min(calc(100% - 112px), 1240px);
width: min(calc(100% - 112px), 1280px);
grid-template-columns: minmax(0, 1fr) minmax(452px, 500px);
grid-template-areas: 'copy form';
column-gap: clamp(82px, 9vw, 148px);
@@ -594,38 +523,34 @@ async function handleSubmit() {
@media (max-width: 520px) {
.login-page {
background:
radial-gradient(circle at 18% 12%, var(--login-glow-primary) 0, transparent 40%),
linear-gradient(
116deg,
var(--login-bg-start) 0%,
var(--login-bg-mid) 50%,
var(--login-bg-end) 100%
);
linear-gradient(90deg, var(--login-grid-line) 1px, transparent 1px),
linear-gradient(var(--login-grid-line) 1px, transparent 1px),
linear-gradient(135deg, var(--login-bg-a) 0%, var(--login-bg-b) 52%, var(--login-bg-c) 100%);
background-size:
48px 48px,
48px 48px,
auto;
}
.login-backdrop::after {
background-size: 42px 42px;
opacity: 0.28;
.login-stage {
gap: clamp(18px, 3.4vh, 28px);
padding: clamp(18px, 3.6vh, 32px) 0;
}
.login-flow-map {
inset: 8% -52%;
width: 204%;
height: 86%;
opacity: 0.66;
.login-brand {
top: 18px;
left: 20px;
}
.login-runner {
display: none;
.login-theme-toggle {
top: 18px;
right: 20px;
}
}
@media (prefers-reduced-motion: reduce) {
.login-backdrop::before,
.login-backdrop::after,
.login-flow-line,
.login-runner {
animation: none;
.login-page-wash {
transform: none;
}
}
</style>