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
ea2821d1
Commit
ea2821d1
authored
Dec 27, 2025
by
IanShaw027
Browse files
refactor(frontend): 优化用户视图和设置向导
- 改进API密钥管理界面 - 优化用户使用统计视图 - 完善初始化设置向导
parent
7a0de176
Changes
3
Hide whitespace changes
Inline
Side-by-side
frontend/src/views/setup/SetupWizardView.vue
View file @
ea2821d1
...
@@ -128,7 +128,7 @@
...
@@ -128,7 +128,7 @@
v-model=
"formData.database.password"
v-model=
"formData.database.password"
type=
"password"
type=
"password"
class=
"input"
class=
"input"
placeholder=
"
Password
"
:
placeholder=
"
t('setup.database.passwordPlaceholder')
"
/>
/>
</div>
</div>
</div>
</div>
...
@@ -234,7 +234,7 @@
...
@@ -234,7 +234,7 @@
v-model=
"formData.redis.password"
v-model=
"formData.redis.password"
type=
"password"
type=
"password"
class=
"input"
class=
"input"
placeholder=
"
Password
"
:
placeholder=
"
t('setup.redis.passwordPlaceholder')
"
/>
/>
</div>
</div>
<div>
<div>
...
@@ -320,7 +320,7 @@
...
@@ -320,7 +320,7 @@
v-model=
"formData.admin.password"
v-model=
"formData.admin.password"
type=
"password"
type=
"password"
class=
"input"
class=
"input"
placeholder=
"
Min 6 characters
"
:
placeholder=
"
t('setup.admin.passwordPlaceholder')
"
/>
/>
</div>
</div>
...
@@ -330,13 +330,13 @@
...
@@ -330,13 +330,13 @@
v-model=
"confirmPassword"
v-model=
"confirmPassword"
type=
"password"
type=
"password"
class=
"input"
class=
"input"
placeholder=
"
C
onfirm
p
assword"
:
placeholder=
"
t('setup.admin.c
onfirm
P
assword
Placeholder')
"
/>
/>
<p
<p
v-if=
"confirmPassword && formData.admin.password !== confirmPassword"
v-if=
"confirmPassword && formData.admin.password !== confirmPassword"
class=
"input-error-text"
class=
"input-error-text"
>
>
Passwords do not match
{{ t('setup.admin.passwordMismatch') }}
</p>
</p>
</div>
</div>
</div>
</div>
...
...
frontend/src/views/user/KeysView.vue
View file @
ea2821d1
...
@@ -154,8 +154,7 @@
...
@@ -154,8 +154,7 @@
<!-- Use Key Button -->
<!-- Use Key Button -->
<button
<button
@
click=
"openUseKeyModal(row)"
@
click=
"openUseKeyModal(row)"
class=
"rounded-lg p-2 text-gray-500 transition-colors hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400"
class=
"flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400"
:title=
"t('keys.useKey')"
>
>
<svg
<svg
class=
"h-4 w-4"
class=
"h-4 w-4"
...
@@ -170,12 +169,12 @@
...
@@ -170,12 +169,12 @@
d=
"M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z"
d=
"M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z"
/>
/>
</svg>
</svg>
<span
class=
"text-xs"
>
{{
t
(
'
keys.useKey
'
)
}}
</span>
</button>
</button>
<!-- Import to CC Switch Button -->
<!-- Import to CC Switch Button -->
<button
<button
@
click=
"importToCcswitch(row.key)"
@
click=
"importToCcswitch(row.key)"
class=
"rounded-lg p-2 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400"
class=
"flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400"
:title=
"t('keys.importToCcSwitch')"
>
>
<svg
<svg
class=
"h-4 w-4"
class=
"h-4 w-4"
...
@@ -190,17 +189,17 @@
...
@@ -190,17 +189,17 @@
d=
"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
d=
"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
/>
/>
</svg>
</svg>
<span
class=
"text-xs"
>
{{
t
(
'
keys.importToCcSwitch
'
)
}}
</span>
</button>
</button>
<!-- Toggle Status Button -->
<!-- Toggle Status Button -->
<button
<button
@
click=
"toggleKeyStatus(row)"
@
click=
"toggleKeyStatus(row)"
:class=
"[
:class=
"[
'rounded-lg p-
2
transition-colors',
'
flex flex-col items-center gap-0.5
rounded-lg p-
1.5
transition-colors',
row.status === 'active'
row.status === 'active'
? 'text-gray-500 hover:bg-yellow-50 hover:text-yellow-600 dark:hover:bg-yellow-900/20 dark:hover:text-yellow-400'
? 'text-gray-500 hover:bg-yellow-50 hover:text-yellow-600 dark:hover:bg-yellow-900/20 dark:hover:text-yellow-400'
: 'text-gray-500 hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400'
: 'text-gray-500 hover:bg-green-50 hover:text-green-600 dark:hover:bg-green-900/20 dark:hover:text-green-400'
]"
]"
:title=
"row.status === 'active' ? t('keys.disable') : t('keys.enable')"
>
>
<svg
<svg
v-if=
"row.status === 'active'"
v-if=
"row.status === 'active'"
...
@@ -230,12 +229,12 @@
...
@@ -230,12 +229,12 @@
d=
"M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
d=
"M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
/>
</svg>
</svg>
<span
class=
"text-xs"
>
{{
row
.
status
===
'
active
'
?
t
(
'
keys.disable
'
)
:
t
(
'
keys.enable
'
)
}}
</span>
</button>
</button>
<!-- Edit Button -->
<!-- Edit Button -->
<button
<button
@
click=
"editKey(row)"
@
click=
"editKey(row)"
class=
"rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400"
class=
"flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400"
:title=
"t('common.edit')"
>
>
<svg
<svg
class=
"h-4 w-4"
class=
"h-4 w-4"
...
@@ -250,12 +249,12 @@
...
@@ -250,12 +249,12 @@
d=
"M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
d=
"M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
/>
/>
</svg>
</svg>
<span
class=
"text-xs"
>
{{
t
(
'
common.edit
'
)
}}
</span>
</button>
</button>
<!-- Delete Button -->
<!-- Delete Button -->
<button
<button
@
click=
"confirmDelete(row)"
@
click=
"confirmDelete(row)"
class=
"rounded-lg p-2 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400"
class=
"flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400"
:title=
"t('common.delete')"
>
>
<svg
<svg
class=
"h-4 w-4"
class=
"h-4 w-4"
...
@@ -270,6 +269,7 @@
...
@@ -270,6 +269,7 @@
d=
"M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
d=
"M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
/>
</svg>
</svg>
<span
class=
"text-xs"
>
{{
t
(
'
common.delete
'
)
}}
</span>
</button>
</button>
</div>
</div>
</
template
>
</
template
>
...
...
frontend/src/views/user/UsageView.vue
View file @
ea2821d1
...
@@ -294,7 +294,11 @@
...
@@ -294,7 +294,11 @@
$
{{
row
.
actual_cost
.
toFixed
(
6
)
}}
$
{{
row
.
actual_cost
.
toFixed
(
6
)
}}
</span>
</span>
<!-- Cost Detail Tooltip -->
<!-- Cost Detail Tooltip -->
<div
class=
"group relative"
>
<div
class=
"group relative"
@
mouseenter=
"showTooltip($event, row)"
@
mouseleave=
"hideTooltip"
>
<div
<div
class=
"flex h-4 w-4 cursor-help items-center justify-center rounded-full bg-gray-100 transition-colors group-hover:bg-blue-100 dark:bg-gray-700 dark:group-hover:bg-blue-900/50"
class=
"flex h-4 w-4 cursor-help items-center justify-center rounded-full bg-gray-100 transition-colors group-hover:bg-blue-100 dark:bg-gray-700 dark:group-hover:bg-blue-900/50"
>
>
...
@@ -310,39 +314,6 @@
...
@@ -310,39 +314,6 @@
/>
/>
</svg>
</svg>
</div>
</div>
<!-- Tooltip Content (right side) -->
<div
class=
"invisible absolute left-full top-1/2 z-[100] ml-2 -translate-y-1/2 opacity-0 transition-all duration-200 group-hover:visible group-hover:opacity-100"
>
<div
class=
"whitespace-nowrap rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 text-xs text-white shadow-xl dark:border-gray-600 dark:bg-gray-800"
>
<div
class=
"space-y-1.5"
>
<div
class=
"flex items-center justify-between gap-6"
>
<span
class=
"text-gray-400"
>
{{
t
(
'
usage.rate
'
)
}}
</span>
<span
class=
"font-semibold text-blue-400"
>
{{
(
row
.
rate_multiplier
||
1
).
toFixed
(
2
)
}}
x
</span
>
</div>
<div
class=
"flex items-center justify-between gap-6"
>
<span
class=
"text-gray-400"
>
{{
t
(
'
usage.original
'
)
}}
</span>
<span
class=
"font-medium text-white"
>
$
{{
row
.
total_cost
.
toFixed
(
6
)
}}
</span>
</div>
<div
class=
"flex items-center justify-between gap-6 border-t border-gray-700 pt-1.5"
>
<span
class=
"text-gray-400"
>
{{
t
(
'
usage.billed
'
)
}}
</span>
<span
class=
"font-semibold text-green-400"
>
$
{{
row
.
actual_cost
.
toFixed
(
6
)
}}
</span
>
</div>
</div>
<!-- Tooltip Arrow (left side) -->
<div
class=
"absolute right-full top-1/2 h-0 w-0 -translate-y-1/2 border-b-[6px] border-r-[6px] border-t-[6px] border-b-transparent border-r-gray-900 border-t-transparent dark:border-r-gray-800"
></div>
</div>
</div>
</div>
</div>
</div>
</div>
</
template
>
</
template
>
...
@@ -399,6 +370,45 @@
...
@@ -399,6 +370,45 @@
</
template
>
</
template
>
</TablePageLayout>
</TablePageLayout>
</AppLayout>
</AppLayout>
<!-- Tooltip Portal -->
<Teleport
to=
"body"
>
<div
v-if=
"tooltipVisible"
class=
"fixed z-[9999] pointer-events-none -translate-y-1/2"
:style=
"{
left: tooltipPosition.x + 'px',
top: tooltipPosition.y + 'px'
}"
>
<div
class=
"whitespace-nowrap rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 text-xs text-white shadow-xl dark:border-gray-600 dark:bg-gray-800"
>
<div
class=
"space-y-1.5"
>
<div
class=
"flex items-center justify-between gap-6"
>
<span
class=
"text-gray-400"
>
{{ t('usage.rate') }}
</span>
<span
class=
"font-semibold text-blue-400"
>
{{ (tooltipData?.rate_multiplier || 1).toFixed(2) }}x
</span
>
</div>
<div
class=
"flex items-center justify-between gap-6"
>
<span
class=
"text-gray-400"
>
{{ t('usage.original') }}
</span>
<span
class=
"font-medium text-white"
>
${{ tooltipData?.total_cost.toFixed(6) }}
</span>
</div>
<div
class=
"flex items-center justify-between gap-6 border-t border-gray-700 pt-1.5"
>
<span
class=
"text-gray-400"
>
{{ t('usage.billed') }}
</span>
<span
class=
"font-semibold text-green-400"
>
${{ tooltipData?.actual_cost.toFixed(6) }}
</span
>
</div>
</div>
<!-- Tooltip Arrow (left side) -->
<div
class=
"absolute right-full top-1/2 h-0 w-0 -translate-y-1/2 border-b-[6px] border-r-[6px] border-t-[6px] border-b-transparent border-r-gray-900 border-t-transparent dark:border-r-gray-800"
></div>
</div>
</div>
</Teleport>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
...
@@ -420,6 +430,11 @@ import { formatDateTime } from '@/utils/format'
...
@@ -420,6 +430,11 @@ import { formatDateTime } from '@/utils/format'
const
{
t
}
=
useI18n
()
const
{
t
}
=
useI18n
()
const
appStore
=
useAppStore
()
const
appStore
=
useAppStore
()
// Tooltip state
const
tooltipVisible
=
ref
(
false
)
const
tooltipPosition
=
ref
({
x
:
0
,
y
:
0
})
const
tooltipData
=
ref
<
UsageLog
|
null
>
(
null
)
// Usage stats from API
// Usage stats from API
const
usageStats
=
ref
<
UsageStatsResponse
|
null
>
(
null
)
const
usageStats
=
ref
<
UsageStatsResponse
|
null
>
(
null
)
...
@@ -629,6 +644,23 @@ const exportToCSV = () => {
...
@@ -629,6 +644,23 @@ const exportToCSV = () => {
appStore
.
showSuccess
(
t
(
'
usage.exportSuccess
'
))
appStore
.
showSuccess
(
t
(
'
usage.exportSuccess
'
))
}
}
// Tooltip functions
const
showTooltip
=
(
event
:
MouseEvent
,
row
:
UsageLog
)
=>
{
const
target
=
event
.
currentTarget
as
HTMLElement
const
rect
=
target
.
getBoundingClientRect
()
tooltipData
.
value
=
row
// Position to the right of the icon, vertically centered
tooltipPosition
.
value
.
x
=
rect
.
right
+
8
tooltipPosition
.
value
.
y
=
rect
.
top
+
rect
.
height
/
2
tooltipVisible
.
value
=
true
}
const
hideTooltip
=
()
=>
{
tooltipVisible
.
value
=
false
tooltipData
.
value
=
null
}
onMounted
(()
=>
{
onMounted
(()
=>
{
initializeDateRange
()
initializeDateRange
()
loadApiKeys
()
loadApiKeys
()
...
...
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