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
"backend/vscode:/vscode.git/clone" did not exist on "f42c8f2abe4a3bc197b34e635d8af94331776b4e"
Commit
8d3f79cf
authored
Apr 03, 2026
by
陈曦
Browse files
继续修改首页、登录页、后台的所有设计风格
parent
b4412353
Changes
10
Expand all
Show 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
"
>
<div
class=
"auth-
split min-h-screen
"
>
<!-- Background -->
<!-- ══════════════════════════════════════════
LEFT PANEL — AI Visual (hidden on mobile)
══════════════════════════════════════════ -->
<div
class=
"auth-left hidden lg:flex"
>
<!-- Background layers -->
<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
class=
"left-aurora left-aurora-1"
></div>
<div
class=
"left-aurora left-aurora-2"
></div>
</div>
<!-- Content -->
<div
class=
"relative z-10 w-full max-w-md"
>
<div
class=
"relative z-10 flex h-full flex-col px-12 py-10"
>
<!-- Brand -->
<div
class=
"
mb-8 text
-center"
>
<!--
Logo +
Brand -->
<div
class=
"
flex items
-center
gap-3
"
>
<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"
>
<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>
<!-- Site name -->
<h1
class=
"auth-site-name mb-2 text-3xl font-black tracking-tight"
>
{{
siteName
}}
</h1>
<!-- 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>
<!-- Subtitle -->
<p
class=
"auth-subtitle font-mono text-xs uppercase tracking-widest"
>
//
{{
siteSubtitle
}}
<!-- 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>
<!-- Copyright -->
<div
class=
"pt-6 font-mono text-[10px] text-slate-700"
>
©
{{ currentYear }} {{ siteName }}
</div>
</div>
</div>
<!-- ══════════════════════════════════════════
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
;
}
/* ── Background layers ───────────────────── */
.auth-orb
{
/* ── LEFT PANEL ────────────────────────────── */
.auth-left
{
position
:
relative
;
width
:
55%
;
max-width
:
600px
;
flex-shrink
:
0
;
background
:
#060c1a
;
overflow
:
hidden
;
flex-direction
:
column
;
}
/* Aurora blobs on left panel */
.left-aurora
{
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
);
}
}
.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
;
}
.auth-grid
{
/* ── 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
;
}
.hub-ring
{
position
:
absolute
;
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
;
}
.auth-scan
{
@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
;
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 ── */
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