Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
8d3f79cf
Commit
8d3f79cf
authored
Apr 03, 2026
by
陈曦
Browse files
继续修改首页、登录页、后台的所有设计风格
parent
b4412353
Changes
10
Expand all
Hide whitespace changes
Inline
Side-by-side
frontend/src/components/layout/AppHeader.vue
View file @
8d3f79cf
<
template
>
<header
class=
"
glass
sticky top-0 z-30 border-b border-gray-200/
5
0 dark:border-
dark-700/50
"
>
<header
class=
"
app-header
sticky top-0 z-30 border-b border-gray-200/
4
0 dark:border-
white/[0.05]
"
>
<div
class=
"flex h-16 items-center justify-between px-4 md:px-6"
>
<!-- Left: Mobile Menu Toggle + Page Title -->
<div
class=
"flex items-center gap-4"
>
...
...
@@ -335,4 +335,13 @@ onBeforeUnmount(() => {
opacity
:
0
;
transform
:
scale
(
0.95
)
translateY
(
-4px
);
}
.app-header
{
background
:
rgba
(
241
,
245
,
251
,
0.85
);
backdrop-filter
:
blur
(
20px
);
}
:global
(
.dark
)
.app-header
{
background
:
rgba
(
8
,
14
,
28
,
0.85
);
backdrop-filter
:
blur
(
20px
);
}
</
style
>
frontend/src/components/layout/AppLayout.vue
View file @
8d3f79cf
<
template
>
<div
class=
"min-h-screen
bg-gray-50 dark:bg-dark-950
"
>
<div
class=
"
app-root
min-h-screen"
>
<!-- Background Decoration -->
<div
class=
"pointer-events-none fixed inset-0
bg-mesh-gradient
"
></div>
<div
class=
"pointer-events-none fixed inset-0
app-bg-layer
"
></div>
<!-- Sidebar -->
<AppSidebar
/>
...
...
@@ -50,3 +50,25 @@ onMounted(() => {
defineExpose
({
replayTour
})
</
script
>
<
style
scoped
>
.app-root
{
background
:
#f1f5fb
;
}
:global
(
.dark
)
.app-root
{
background
:
#080e1c
;
}
.app-bg-layer
{
background
:
radial-gradient
(
at
15%
30%
,
rgba
(
99
,
102
,
241
,
0.06
)
0px
,
transparent
50%
),
radial-gradient
(
at
85%
10%
,
rgba
(
20
,
184
,
166
,
0.05
)
0px
,
transparent
50%
),
radial-gradient
(
at
60%
80%
,
rgba
(
139
,
92
,
246
,
0.04
)
0px
,
transparent
50%
);
}
:global
(
.dark
)
.app-bg-layer
{
background
:
radial-gradient
(
at
15%
30%
,
rgba
(
99
,
102
,
241
,
0.08
)
0px
,
transparent
50%
),
radial-gradient
(
at
85%
10%
,
rgba
(
20
,
184
,
166
,
0.06
)
0px
,
transparent
50%
),
radial-gradient
(
at
60%
80%
,
rgba
(
139
,
92
,
246
,
0.05
)
0px
,
transparent
50%
);
}
</
style
>
frontend/src/components/layout/AppSidebar.vue
View file @
8d3f79cf
...
...
@@ -60,7 +60,7 @@
<div
v-if=
"!sidebarCollapsed"
class=
"sidebar-section-title"
>
{{
t
(
'
nav.myAccount
'
)
}}
</div>
<div
v-else
class=
"mx-3 my-3 h-px
bg-gray-200 dark:bg-dark-700
"
></div>
<div
v-else
class=
"mx-3 my-3 h-px
"
style=
"background: rgba(255,255,255,0.06)
"
></div>
<router-link
v-for=
"item in personalNavItems"
...
...
@@ -105,7 +105,7 @@
</nav>
<!-- Bottom Section -->
<div
class=
"mt-auto
border-t border-gray-100 p-3 dark:border-dark-800
"
>
<div
class=
"mt-auto
p-3"
style=
"border-top: 1px solid rgba(255,255,255,0.05)
"
>
<!-- Theme Toggle -->
<button
@
click=
"toggleTheme"
...
...
frontend/src/components/layout/AuthLayout.vue
View file @
8d3f79cf
<
template
>
<div
class=
"auth-root relative flex min-h-screen items-center justify-center overflow-hidden p-4"
>
<!-- Background -->
<div
class=
"pointer-events-none absolute inset-0 overflow-hidden"
>
<div
class=
"auth-orb auth-orb-tr"
></div>
<div
class=
"auth-orb auth-orb-bl"
></div>
<div
class=
"auth-grid"
></div>
<div
class=
"auth-scan"
></div>
</div>
<div
class=
"auth-split min-h-screen"
>
<!-- Content -->
<div
class=
"relative z-10 w-full max-w-md"
>
<!-- ══════════════════════════════════════════
LEFT PANEL — AI Visual (hidden on mobile)
══════════════════════════════════════════ -->
<div
class=
"auth-left hidden lg:flex"
>
<!-- Brand -->
<div
class=
"mb-8 text-center"
>
<template
v-if=
"settingsLoaded"
>
<!-- Logo -->
<div
class=
"auth-logo-wrap mb-5 inline-flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl"
>
<img
:src=
"siteLogo || '/logo.png'"
alt=
"Logo"
class=
"h-full w-full object-contain"
/>
<!-- Background layers -->
<div
class=
"pointer-events-none absolute inset-0 overflow-hidden"
>
<div
class=
"left-aurora left-aurora-1"
></div>
<div
class=
"left-aurora left-aurora-2"
></div>
</div>
<div
class=
"relative z-10 flex h-full flex-col px-12 py-10"
>
<!-- Logo + Brand -->
<div
class=
"flex items-center gap-3"
>
<template
v-if=
"settingsLoaded"
>
<div
class=
"left-logo-wrap"
>
<img
:src=
"siteLogo || '/logo.png'"
alt=
"Logo"
class=
"h-full w-full object-contain"
/>
</div>
<div>
<div
class=
"text-base font-bold text-white"
>
{{
siteName
}}
</div>
<div
class=
"font-mono text-[10px] uppercase tracking-widest text-violet-400/60"
>
// AI Gateway
</div>
</div>
</
template
>
</div>
<!-- Center: Orbital AI Visualization -->
<div
class=
"flex flex-1 flex-col items-center justify-center"
>
<div
class=
"orbital-viz"
>
<!-- Hub -->
<div
class=
"hub-outer"
>
<div
class=
"hub-ring hub-ring-1"
></div>
<div
class=
"hub-ring hub-ring-2"
></div>
<div
class=
"hub-core"
>
<svg
class=
"h-8 w-8 text-white"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
stroke-width=
"1.5"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
d=
"M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
/>
</svg>
</div>
</div>
<!-- Orbiting model chips -->
<div
class=
"orbit-chip chip-0"
>
<span
class=
"chip-dot"
style=
"background: #f97316"
></span>
<span>
Claude
</span>
</div>
<div
class=
"orbit-chip chip-1"
>
<span
class=
"chip-dot"
style=
"background: #22c55e"
></span>
<span>
GPT-4o
</span>
</div>
<div
class=
"orbit-chip chip-2"
>
<span
class=
"chip-dot"
style=
"background: #3b82f6"
></span>
<span>
Gemini
</span>
</div>
<div
class=
"orbit-chip chip-3"
>
<span
class=
"chip-dot"
style=
"background: #f43f5e"
></span>
<span>
DeepSeek
</span>
</div>
<div
class=
"orbit-chip chip-4"
>
<span
class=
"chip-dot"
style=
"background: #a855f7"
></span>
<span>
Llama
</span>
</div>
<!-- Connecting lines (SVG) -->
<svg
class=
"orbit-lines"
viewBox=
"0 0 340 340"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<!-- Lines from center (170,170) to chip positions -->
<line
x1=
"170"
y1=
"170"
x2=
"275"
y2=
"90"
stroke=
"rgba(139,92,246,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"1.2s"
repeatCount=
"indefinite"
/>
</line>
<line
x1=
"170"
y1=
"170"
x2=
"300"
y2=
"200"
stroke=
"rgba(20,184,166,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"1.6s"
repeatCount=
"indefinite"
/>
</line>
<line
x1=
"170"
y1=
"170"
x2=
"200"
y2=
"295"
stroke=
"rgba(59,130,246,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"2.0s"
repeatCount=
"indefinite"
/>
</line>
<line
x1=
"170"
y1=
"170"
x2=
"65"
y2=
"270"
stroke=
"rgba(244,63,94,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"1.4s"
repeatCount=
"indefinite"
/>
</line>
<line
x1=
"170"
y1=
"170"
x2=
"55"
y2=
"130"
stroke=
"rgba(168,85,247,0.18)"
stroke-width=
"1"
stroke-dasharray=
"4 4"
>
<animate
attributeName=
"stroke-dashoffset"
values=
"0;-16"
dur=
"1.8s"
repeatCount=
"indefinite"
/>
</line>
</svg>
</div>
<!-- Tagline -->
<div
class=
"mt-10 text-center"
>
<h2
class=
"mb-3 text-2xl font-bold text-white"
>
One Key.
<br>
Every Model.
</h2>
<p
class=
"max-w-xs text-sm leading-relaxed text-slate-400"
>
{{ siteSubtitle }}
</p>
</div>
</div>
<!-- Feature bullets -->
<div
class=
"space-y-4 pb-4"
>
<div
v-for=
"f in leftFeatures"
:key=
"f.text"
class=
"left-feature-row"
>
<div
class=
"left-feature-icon"
:style=
"{ background: f.iconBg }"
>
<component
:is=
"f.icon"
/>
</div>
<div>
<div
class=
"text-sm font-medium text-slate-200"
>
{{ f.text }}
</div>
<div
class=
"text-xs text-slate-500"
>
{{ f.desc }}
</div>
</div>
</div>
</div>
<!-- Site name -->
<h1
class=
"auth-site-name mb-2 text-3xl font-black tracking-tight"
>
{{
siteName
}}
</h1>
<!-- Copyright -->
<div
class=
"pt-6 font-mono text-[10px] text-slate-700"
>
©
{{ currentYear }} {{ siteName }}
</div>
</div>
</div>
<!-- Subtitle -->
<p
class=
"auth-subtitle font-mono text-xs uppercase tracking-widest"
>
//
{{
siteSubtitle
}}
</p>
<!-- ══════════════════════════════════════════
RIGHT PANEL — Form
══════════════════════════════════════════ -->
<div
class=
"auth-right"
>
<!-- Mobile: tiny logo strip -->
<div
class=
"mb-8 flex items-center gap-2.5 lg:hidden"
>
<
template
v-if=
"settingsLoaded"
>
<div
class=
"h-8 w-8 overflow-hidden rounded-lg shadow-glow"
>
<img
:src=
"siteLogo || '/logo.png'"
alt=
"Logo"
class=
"h-full w-full object-contain"
/>
</div>
<span
class=
"text-base font-bold text-gray-900 dark:text-white"
>
{{
siteName
}}
</span>
</
template
>
</div>
<!--
C
ard -->
<div
class=
"auth-
card rounded-2xl p-8
"
>
<!--
Form c
ard -->
<div
class=
"auth-
form-card
"
>
<slot
/>
</div>
<!-- Footer links -->
<div
class=
"mt-
6
text-center text-sm"
>
<div
class=
"mt-
5
text-center text-sm"
>
<slot
name=
"footer"
/>
</div>
<!-- Copyright -->
<div
class=
"mt-8 text-center font-mono text-xs text-slate-
6
00"
>
<!-- Copyright
(mobile only)
-->
<div
class=
"mt-8 text-center font-mono text-xs
text-gray-400 dark:
text-slate-
7
00
lg:hidden
"
>
©
{{ currentYear }} {{ siteName }}. All rights reserved.
</div>
</div>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
computed
,
onMounted
}
from
'
vue
'
import
{
computed
,
defineComponent
,
h
,
onMounted
}
from
'
vue
'
import
{
useAppStore
}
from
'
@/stores
'
import
{
sanitizeUrl
}
from
'
@/utils/url
'
...
...
@@ -61,117 +157,295 @@ const siteName = computed(() => appStore.siteName || 'TrafficAPI')
const
siteLogo
=
computed
(()
=>
sanitizeUrl
(
appStore
.
siteLogo
||
''
,
{
allowRelative
:
true
,
allowDataUrl
:
true
}))
const
siteSubtitle
=
computed
(()
=>
appStore
.
cachedPublicSettings
?.
site_subtitle
||
'
Intelligent AI Traffic Routing & Management
'
)
const
settingsLoaded
=
computed
(()
=>
appStore
.
publicSettingsLoaded
)
const
currentYear
=
computed
(()
=>
new
Date
().
getFullYear
())
// Inline icon components for feature bullets
const
ShieldIcon
=
defineComponent
({
render
:
()
=>
h
(
'
svg
'
,
{
class
:
'
h-4 w-4 text-white
'
,
fill
:
'
none
'
,
viewBox
:
'
0 0 24 24
'
,
stroke
:
'
currentColor
'
,
'
stroke-width
'
:
'
1.5
'
},
[
h
(
'
path
'
,
{
'
stroke-linecap
'
:
'
round
'
,
'
stroke-linejoin
'
:
'
round
'
,
d
:
'
M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z
'
})
])
})
const
BoltIcon
=
defineComponent
({
render
:
()
=>
h
(
'
svg
'
,
{
class
:
'
h-4 w-4 text-white
'
,
fill
:
'
none
'
,
viewBox
:
'
0 0 24 24
'
,
stroke
:
'
currentColor
'
,
'
stroke-width
'
:
'
1.5
'
},
[
h
(
'
path
'
,
{
'
stroke-linecap
'
:
'
round
'
,
'
stroke-linejoin
'
:
'
round
'
,
d
:
'
M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z
'
})
])
})
const
ChartIcon
=
defineComponent
({
render
:
()
=>
h
(
'
svg
'
,
{
class
:
'
h-4 w-4 text-white
'
,
fill
:
'
none
'
,
viewBox
:
'
0 0 24 24
'
,
stroke
:
'
currentColor
'
,
'
stroke-width
'
:
'
1.5
'
},
[
h
(
'
path
'
,
{
'
stroke-linecap
'
:
'
round
'
,
'
stroke-linejoin
'
:
'
round
'
,
d
:
'
M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z
'
})
])
})
const
leftFeatures
=
[
{
text
:
'
统一 API 接入点
'
,
desc
:
'
一个 Key 打通所有 AI 服务商,无需多套凭证
'
,
icon
:
ShieldIcon
,
iconBg
:
'
linear-gradient(135deg,#6366f1,#8b5cf6)
'
,
},
{
text
:
'
智能负载均衡
'
,
desc
:
'
自动分发流量,账户异常时即时切换,零中断
'
,
icon
:
BoltIcon
,
iconBg
:
'
linear-gradient(135deg,#14b8a6,#06b6d4)
'
,
},
{
text
:
'
实时流量分析
'
,
desc
:
'
请求量、延迟、费用全透明,按用户精细管控
'
,
icon
:
ChartIcon
,
iconBg
:
'
linear-gradient(135deg,#a855f7,#ec4899)
'
,
},
]
onMounted
(()
=>
{
appStore
.
fetchPublicSettings
()
})
</
script
>
<
style
scoped
>
/* ── Root ────────────────────────────────── */
.auth-root
{
background
:
#182a40
;
color
:
white
;
/* ══════════════════════════════════════════════
SPLIT LAYOUT
══════════════════════════════════════════════ */
.auth-split
{
display
:
flex
;
min-height
:
100vh
;
}
/* ── LEFT PANEL ────────────────────────────── */
.auth-left
{
position
:
relative
;
width
:
55%
;
max-width
:
600px
;
flex-shrink
:
0
;
background
:
#060c1a
;
overflow
:
hidden
;
flex-direction
:
column
;
}
/*
── Background layers ─────────────────────
*/
.
auth-
or
b
{
/*
Aurora blobs on left panel
*/
.
left-aur
or
a
{
position
:
absolute
;
border-radius
:
50%
;
filter
:
blur
(
9
0px
);
filter
:
blur
(
10
0px
);
pointer-events
:
none
;
}
.auth-orb-tr
{
top
:
-10%
;
right
:
-10%
;
width
:
420px
;
height
:
420px
;
background
:
radial-gradient
(
ellipse
,
rgba
(
20
,
184
,
166
,
0.18
)
0%
,
transparent
70%
);
.left-aurora-1
{
top
:
-15%
;
right
:
-15%
;
width
:
450px
;
height
:
450px
;
background
:
radial-gradient
(
ellipse
,
rgba
(
139
,
92
,
246
,
0.22
)
0%
,
transparent
65%
);
animation
:
la-drift-1
20s
ease-in-out
infinite
alternate
;
}
.
auth-orb-bl
{
.
left-aurora-2
{
bottom
:
-10%
;
left
:
-10%
;
width
:
380px
;
height
:
380px
;
background
:
radial-gradient
(
ellipse
,
rgba
(
6
,
182
,
212
,
0.13
)
0%
,
transparent
70%
);
width
:
350px
;
height
:
350px
;
background
:
radial-gradient
(
ellipse
,
rgba
(
20
,
184
,
166
,
0.18
)
0%
,
transparent
65%
);
animation
:
la-drift-2
24s
ease-in-out
infinite
alternate
;
}
@keyframes
la-drift-1
{
from
{
transform
:
translate
(
0
,
0
)
scale
(
1
);
}
to
{
transform
:
translate
(
-20px
,
20px
)
scale
(
1.06
);
}
}
@keyframes
la-drift-2
{
from
{
transform
:
translate
(
0
,
0
)
scale
(
1
);
}
to
{
transform
:
translate
(
20px
,
-20px
)
scale
(
1.04
);
}
}
.auth-grid
{
.left-logo-wrap
{
width
:
36px
;
height
:
36px
;
border-radius
:
9px
;
overflow
:
hidden
;
box-shadow
:
0
0
0
1px
rgba
(
139
,
92
,
246
,
0.3
),
0
0
16px
rgba
(
139
,
92
,
246
,
0.15
);
flex-shrink
:
0
;
}
/* ── ORBITAL VISUALIZATION ──────────────────── */
.orbital-viz
{
position
:
relative
;
width
:
340px
;
height
:
340px
;
}
/* Hub */
.hub-outer
{
position
:
absolute
;
inset
:
0
;
background-image
:
linear-gradient
(
rgba
(
20
,
184
,
166
,
0.07
)
1px
,
transparent
1px
),
linear-gradient
(
90deg
,
rgba
(
20
,
184
,
166
,
0.07
)
1px
,
transparent
1px
);
background-size
:
48px
48px
;
top
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.
auth-scan
{
.
hub-ring
{
position
:
absolute
;
left
:
0
;
right
:
0
;
height
:
1px
;
background
:
linear-gradient
(
90deg
,
transparent
0%
,
rgba
(
20
,
184
,
166
,
0.45
)
30%
,
rgba
(
20
,
184
,
166
,
0.7
)
50%
,
rgba
(
20
,
184
,
166
,
0.45
)
70%
,
transparent
100%
);
animation
:
auth-scan
12s
linear
infinite
;
box-shadow
:
0
0
8px
rgba
(
20
,
184
,
166
,
0.35
);
}
@keyframes
auth-scan
{
0
%
{
top
:
0%
;
opacity
:
0
;
}
4
%
{
opacity
:
1
;
}
96
%
{
opacity
:
0.4
;
}
100
%
{
top
:
100%
;
opacity
:
0
;
}
}
/* ── Logo ────────────────────────────────── */
.auth-logo-wrap
{
box-shadow
:
0
0
0
1px
rgba
(
20
,
184
,
166
,
0.3
),
0
0
20px
rgba
(
20
,
184
,
166
,
0.18
),
0
8px
24px
rgba
(
0
,
0
,
0
,
0.4
);
}
/* ── Brand text ──────────────────────────── */
.auth-site-name
{
background
:
linear-gradient
(
135deg
,
#ffffff
0%
,
#7dd3c8
45%
,
#14b8a6
100%
);
-webkit-background-clip
:
text
;
-webkit-text-fill-color
:
transparent
;
background-clip
:
text
;
}
.auth-subtitle
{
color
:
rgba
(
20
,
184
,
166
,
0.5
);
}
/* ── Card ────────────────────────────────── */
.auth-card
{
background
:
rgba
(
26
,
46
,
72
,
0.72
);
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.09
);
backdrop-filter
:
blur
(
18px
);
box-shadow
:
0
0
0
1px
rgba
(
20
,
184
,
166
,
0.08
),
0
24px
60px
rgba
(
0
,
0
,
0
,
0.45
),
0
0
50px
rgba
(
20
,
184
,
166
,
0.05
);
}
/* ── Slot overrides: make child inputs/links readable ── */
border-radius
:
50%
;
border
:
1px
solid
rgba
(
139
,
92
,
246
,
0.15
);
animation
:
hub-pulse
3s
ease-in-out
infinite
;
}
.hub-ring-1
{
width
:
80px
;
height
:
80px
;
animation-delay
:
0s
;
}
.hub-ring-2
{
width
:
110px
;
height
:
110px
;
border-color
:
rgba
(
20
,
184
,
166
,
0.1
);
animation-delay
:
0.8s
;
}
@keyframes
hub-pulse
{
0
%,
100
%
{
transform
:
scale
(
1
);
opacity
:
0.6
;
}
50
%
{
transform
:
scale
(
1.06
);
opacity
:
1
;
}
}
.hub-core
{
width
:
64px
;
height
:
64px
;
border-radius
:
16px
;
background
:
linear-gradient
(
135deg
,
#3730a3
,
#4f46e5
,
#7c3aed
);
box-shadow
:
0
0
32px
rgba
(
99
,
102
,
241
,
0.5
),
0
0
64px
rgba
(
99
,
102
,
241
,
0.2
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
position
:
relative
;
z-index
:
2
;
}
/* Orbit chips — positioned around center */
.orbit-chip
{
position
:
absolute
;
display
:
flex
;
align-items
:
center
;
gap
:
6px
;
padding
:
5px
11px
5px
8px
;
border-radius
:
20px
;
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.08
);
background
:
rgba
(
15
,
22
,
40
,
0.85
);
backdrop-filter
:
blur
(
8px
);
font-size
:
11px
;
font-weight
:
500
;
color
:
#cbd5e1
;
white-space
:
nowrap
;
animation
:
chip-float
4s
ease-in-out
infinite
;
}
.chip-dot
{
width
:
7px
;
height
:
7px
;
border-radius
:
50%
;
flex-shrink
:
0
;
}
/* Position each chip around the orbital */
.chip-0
{
top
:
6%
;
left
:
62%
;
animation-delay
:
0.0s
;
}
.chip-1
{
top
:
44%
;
left
:
80%
;
animation-delay
:
0.6s
;
}
.chip-2
{
top
:
78%
;
left
:
52%
;
animation-delay
:
1.2s
;
}
.chip-3
{
top
:
72%
;
left
:
6%
;
animation-delay
:
1.8s
;
}
.chip-4
{
top
:
12%
;
left
:
2%
;
animation-delay
:
2.4s
;
}
@keyframes
chip-float
{
0
%,
100
%
{
transform
:
translateY
(
0
);
}
50
%
{
transform
:
translateY
(
-6px
);
}
}
/* SVG connecting lines */
.orbit-lines
{
position
:
absolute
;
inset
:
0
;
width
:
100%
;
height
:
100%
;
pointer-events
:
none
;
}
/* Feature rows */
.left-feature-row
{
display
:
flex
;
align-items
:
flex-start
;
gap
:
12px
;
}
.left-feature-icon
{
width
:
34px
;
height
:
34px
;
border-radius
:
8px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
}
/* ── RIGHT PANEL ─────────────────────────── */
.auth-right
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
padding
:
40px
32px
;
background
:
#f8fafc
;
min-height
:
100vh
;
}
/* Dark mode right panel */
:global
(
.dark
)
.auth-right
{
background
:
#0f172a
;
}
.auth-form-card
{
width
:
100%
;
max-width
:
400px
;
background
:
white
;
border-radius
:
20px
;
border
:
1px
solid
rgba
(
0
,
0
,
0
,
0.06
);
box-shadow
:
0
4px
32px
rgba
(
0
,
0
,
0
,
0.08
),
0
1px
4px
rgba
(
0
,
0
,
0
,
0.04
);
padding
:
36px
32px
;
}
:global
(
.dark
)
.auth-form-card
{
background
:
#1e293b
;
border-color
:
rgba
(
255
,
255
,
255
,
0.08
);
box-shadow
:
0
4px
32px
rgba
(
0
,
0
,
0
,
0.4
);
}
/* Override slot content for right panel (light mode) */
:deep
(
h2
)
{
color
:
#0f172a
!important
;
}
:global
(
.dark
)
:deep
(
h2
)
{
color
:
white
!important
;
}
:deep
(
.input-label
)
{
color
:
#374151
;
}
:global
(
.dark
)
:deep
(
.input-label
)
{
color
:
#94a3b8
;
}
:deep
(
.input
)
{
background
:
#f9fafb
;
border-color
:
#e5e7eb
;
color
:
#0f172a
;
}
:global
(
.dark
)
:deep
(
.input
)
{
background
:
rgba
(
15
,
28
,
48
,
0.7
);
border-color
:
rgba
(
255
,
255
,
255
,
0.1
);
color
:
white
;
}
:deep
(
.input
:focus
)
{
border-color
:
rgba
(
20
,
184
,
166
,
0.5
)
;
box-shadow
:
0
0
0
3px
rgba
(
20
,
184
,
16
6
,
0.1
2
);
border-color
:
#8b5cf6
;
box-shadow
:
0
0
0
3px
rgba
(
139
,
92
,
24
6
,
0.1
5
);
}
:deep
(
.input
::placeholder
)
{
color
:
#
475569
;
color
:
#
9ca3af
;
}
:
deep
(
h2
)
{
color
:
white
!important
;
:
global
(
.dark
)
:deep
(
.input
::placeholder
)
{
color
:
#475569
;
}
:deep
(
.text-gray-500
),
:deep
(
.dark
\
:text-dark-400
)
{
color
:
#6b7280
!important
;
}
:global
(
.dark
)
:deep
(
.text-gray-500
),
:global
(
.dark
)
:deep
(
.dark
\
:text-dark-400
)
{
color
:
#64748b
!important
;
}
</
style
>
frontend/src/i18n/locales/en.ts
View file @
8d3f79cf
...
...
@@ -40,6 +40,13 @@ export default {
}
}
},
// Pipeline visualization labels
pipeline
:
{
gateway
:
'
Gateway
'
,
balancer
:
'
Balancer
'
,
aiPool
:
'
AI Pool
'
,
response
:
'
Response
'
,
},
// Solutions section
solutions
:
{
title
:
'
TrafficAPI Solves This
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
8d3f79cf
...
...
@@ -40,6 +40,13 @@ export default {
}
}
},
// 管道可视化标签
pipeline
:
{
gateway
:
'
网关
'
,
balancer
:
'
均衡器
'
,
aiPool
:
'
AI 池
'
,
response
:
'
响应
'
,
},
// 解决方案区块
solutions
:
{
title
:
'
TrafficAPI 为你解决
'
,
...
...
frontend/src/style.css
View file @
8d3f79cf
...
...
@@ -96,16 +96,26 @@
@apply
inline-flex
items-center
justify-center
gap-2;
@apply
rounded-xl
px-4
py-2.5
text-sm
font-medium;
@apply
transition-all
duration-200
ease-out;
@apply
focus
:
outline-none
focus
:
ring-2
focus
:
ring-primary-500
/
50
focus
:
ring-offset-2
;
@apply
focus
:
outline-none
;
@apply
disabled
:
transform-none
disabled
:
cursor-not-allowed
disabled
:
opacity-50
;
@apply
active
:
scale-
[
0.98
];
}
.btn-primary
{
@apply
bg-gradient-to-r
from-primary-500
to-primary-600;
@apply
text-white
shadow-md
shadow-primary-500/25;
@apply
hover
:
from-primary-600
hover
:
to-primary-700
hover
:
shadow-lg
hover
:
shadow-primary-500
/
30
;
@apply
dark
:
shadow-primary-500
/
20
;
background
:
linear-gradient
(
135deg
,
#7c3aed
,
#8b5cf6
);
color
:
white
;
box-shadow
:
0
4px
14px
rgba
(
139
,
92
,
246
,
0.35
);
}
.btn-primary
:hover
{
background
:
linear-gradient
(
135deg
,
#6d28d9
,
#7c3aed
);
box-shadow
:
0
6px
20px
rgba
(
139
,
92
,
246
,
0.45
);
transform
:
translateY
(
-1px
);
}
.btn-primary
:focus
{
outline
:
none
;
box-shadow
:
0
0
0
3px
rgba
(
139
,
92
,
246
,
0.35
),
0
4px
14px
rgba
(
139
,
92
,
246
,
0.3
);
}
.btn-secondary
{
...
...
@@ -165,7 +175,7 @@
@apply
text-gray-900
dark
:
text-gray-100
;
@apply
placeholder
:
text-gray-400
dark
:
placeholder
:
text-dark-400
;
@apply
transition-all
duration-200;
@apply
focus
:
border-primary-500
focus
:
outline-none
focus
:
ring-2
focus
:
ring-
primary-500
/
3
0
;
@apply
focus
:
outline-none
focus
:
border-
[
#8b5cf6
]
focus
:
ring-2
focus
:
ring-
[
#8b5cf6
]/
2
0
;
@apply
disabled
:
cursor-not-allowed
disabled
:
bg-gray-100
dark
:
disabled
:
bg-dark-900
;
}
...
...
@@ -522,11 +532,12 @@
@apply
border-l-primary-500;
}
/* ============ 侧边栏 ============ */
/* ============ 侧边栏
— 恒暗风格
============ */
.sidebar
{
@apply
fixed
inset-y-0
left-0
z-40;
@apply
w-64
bg-white
dark
:
bg-dark-900
;
@apply
border-r
border-gray-200
dark
:
border-dark-800
;
@apply
w-64;
background
:
#0c1220
;
border-right
:
1px
solid
rgba
(
139
,
92
,
246
,
0.08
);
@apply
flex
flex-col;
@apply
transition-transform
duration-300;
}
...
...
@@ -534,7 +545,7 @@
.sidebar-header
{
@apply
h-16
px-6;
@apply
flex
items-center
gap-3;
@apply
border-b
border-gray-100
dark
:
border-dark-800
;
border-bottom
:
1px
solid
rgba
(
255
,
255
,
255
,
0.05
)
;
}
.sidebar-nav
{
...
...
@@ -544,16 +555,26 @@
.sidebar-link
{
@apply
flex
items-center
gap-3
rounded-xl
px-3
py-2.5;
@apply
text-sm
font-medium;
@apply
text-gray-600
dark
:
text-dark-300
;
color
:
rgba
(
148
,
163
,
184
,
0.85
)
;
@apply
transition-all
duration-200;
@apply
hover
:
bg-gray-100
dark
:
hover
:
bg-dark-800
;
@apply
hover
:
text-gray-900
dark
:
hover
:
text-white
;
}
.sidebar-link
:hover
{
background
:
rgba
(
255
,
255
,
255
,
0.055
);
color
:
rgba
(
226
,
232
,
240
,
1
);
}
.sidebar-link-active
{
@apply
bg-primary-50
dark
:
bg-primary-900
/
20
;
@apply
text-primary-600
dark
:
text-primary-400
;
@apply
hover
:
bg-primary-100
dark
:
hover
:
bg-primary-900
/
30
;
background
:
linear-gradient
(
135deg
,
rgba
(
99
,
102
,
241
,
0.18
),
rgba
(
20
,
184
,
166
,
0.12
));
border-left
:
2px
solid
#14b8a6
;
padding-left
:
calc
(
0.75rem
-
2px
);
color
:
#5eead4
!important
;
box-shadow
:
inset
0
0
16px
rgba
(
20
,
184
,
166
,
0.04
);
}
.sidebar-link-active
:hover
{
background
:
linear-gradient
(
135deg
,
rgba
(
99
,
102
,
241
,
0.24
),
rgba
(
20
,
184
,
166
,
0.18
));
color
:
#5eead4
!important
;
}
.sidebar-section
{
...
...
@@ -563,7 +584,7 @@
.sidebar-section-title
{
@apply
mb-2
px-3;
@apply
text-xs
font-semibold
uppercase
tracking-wider;
@apply
text-gray-400
dark
:
text-dark-500
;
color
:
rgba
(
100
,
116
,
139
,
0.7
)
;
}
/* ============ 页面头部 ============ */
...
...
frontend/src/views/HomeView.vue
View file @
8d3f79cf
This diff is collapsed.
Click to expand it.
frontend/src/views/auth/LoginView.vue
View file @
8d3f79cf
...
...
@@ -80,7 +80,7 @@
<router-link
v-if=
"passwordResetEnabled && !backendModeEnabled"
to=
"/forgot-password"
class=
"text-sm font-medium t
ext-primary-600 transition-colors hover:text-primary-500 dark:text-primary-400 dark:hover:text-primary-300
"
class=
"text-sm font-medium t
ransition-colors"
style=
"color: #8b5cf6;
"
>
{{
t
(
'
auth.forgotPassword
'
)
}}
</router-link>
...
...
frontend/tailwind.config.js
View file @
8d3f79cf
...
...
@@ -67,8 +67,8 @@ export default {
boxShadow
:
{
glass
:
'
0 8px 32px rgba(0, 0, 0, 0.08)
'
,
'
glass-sm
'
:
'
0 4px 16px rgba(0, 0, 0, 0.06)
'
,
glow
:
'
0 0
2
0px rgba(
20, 184, 166
, 0.
2
5)
'
,
'
glow-lg
'
:
'
0 0 40px rgba(
20
, 1
84, 166
, 0.35)
'
,
glow
:
'
0 0 0
1
px rgba(
99, 102, 241, 0.25), 0 0 20px rgba(99, 102, 241
, 0.
1
5)
'
,
'
glow-lg
'
:
'
0 0 40px rgba(
99
, 1
02, 241
, 0.35)
'
,
card
:
'
0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06)
'
,
'
card-hover
'
:
'
0 10px 40px rgba(0, 0, 0, 0.08)
'
,
'
inner-glow
'
:
'
inset 0 1px 0 rgba(255, 255, 255, 0.1)
'
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment