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
a413fa3b
Unverified
Commit
a413fa3b
authored
Dec 27, 2025
by
程序猿MT
Committed by
GitHub
Dec 27, 2025
Browse files
Merge branch 'Wei-Shaw:main' into main
parents
3a8dbf5a
254f1254
Changes
45
Show whitespace changes
Inline
Side-by-side
frontend/src/views/user/KeysView.vue
View file @
a413fa3b
<
template
>
<AppLayout>
<
div
class=
"space-y-6"
>
<
!-- Page Header A
ctions
--
>
<
TablePageLayout
>
<
template
#a
ctions
>
<div
class=
"flex justify-end gap-3"
>
<button
@
click=
"loadApiKeys"
...
...
@@ -36,9 +36,9 @@
{{
t
(
'
keys.createKey
'
)
}}
</button>
</div>
</
template
>
<!-- API Keys Table -->
<div
class=
"card overflow-hidden"
>
<
template
#table
>
<DataTable
:columns=
"columns"
:data=
"apiKeys"
:loading=
"loading"
>
<template
#cell-key
="
{ value, row }">
<div
class=
"flex items-center gap-2"
>
...
...
@@ -146,7 +146,7 @@
</
template
>
<
template
#cell-created_at=
"{ value }"
>
<span
class=
"text-sm text-gray-500 dark:text-dark-400"
>
{{
formatDate
(
value
)
}}
</span>
<span
class=
"text-sm text-gray-500 dark:text-dark-400"
>
{{
formatDate
Time
(
value
)
}}
</span>
</
template
>
<
template
#cell-actions=
"{ row }"
>
...
...
@@ -235,7 +235,7 @@
<button
@
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"
title=
"
E
dit"
:
title=
"
t('common.e
dit
')
"
>
<svg
class=
"h-4 w-4"
...
...
@@ -255,7 +255,7 @@
<button
@
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"
title=
"
D
elete"
:
title=
"
t('common.d
elete
')
"
>
<svg
class=
"h-4 w-4"
...
...
@@ -283,9 +283,9 @@
/>
</
template
>
</DataTable>
</
div
>
</
template
>
<
!-- P
agination
--
>
<
template
#p
agination
>
<Pagination
v-if=
"pagination.total > 0"
:page=
"pagination.page"
...
...
@@ -293,7 +293,8 @@
:page-size=
"pagination.page_size"
@
update:page=
"handlePageChange"
/>
</div>
</
template
>
</TablePageLayout>
<!-- Create/Edit Modal -->
<Modal
...
...
@@ -496,6 +497,7 @@ import { useAppStore } from '@/stores/app'
const
{
t
}
=
useI18n
()
import
{
keysAPI
,
authAPI
,
usageAPI
,
userGroupsAPI
}
from
'
@/api
'
import
AppLayout
from
'
@/components/layout/AppLayout.vue
'
import
TablePageLayout
from
'
@/components/layout/TablePageLayout.vue
'
import
DataTable
from
'
@/components/common/DataTable.vue
'
import
Pagination
from
'
@/components/common/Pagination.vue
'
import
Modal
from
'
@/components/common/Modal.vue
'
...
...
@@ -507,6 +509,7 @@ import GroupBadge from '@/components/common/GroupBadge.vue'
import
type
{
ApiKey
,
Group
,
PublicSettings
,
SubscriptionType
,
GroupPlatform
}
from
'
@/types
'
import
type
{
Column
}
from
'
@/components/common/types
'
import
type
{
BatchApiKeyUsageStats
}
from
'
@/api/usage
'
import
{
formatDateTime
}
from
'
@/utils/format
'
interface
GroupOption
{
value
:
number
...
...
@@ -624,15 +627,6 @@ const copyToClipboard = async (text: string, keyId: number) => {
}
}
const
formatDate
=
(
dateString
:
string
):
string
=>
{
const
date
=
new
Date
(
dateString
)
return
date
.
toLocaleDateString
(
'
en-US
'
,
{
year
:
'
numeric
'
,
month
:
'
short
'
,
day
:
'
numeric
'
})
}
const
loadApiKeys
=
async
()
=>
{
loading
.
value
=
true
try
{
...
...
frontend/src/views/user/ProfileView.vue
View file @
a413fa3b
...
...
@@ -17,7 +17,7 @@
/>
<StatCard
:title=
"t('profile.memberSince')"
:value=
"format
MemberSinc
e(user?.created_at || '')"
:value=
"format
Dat
e(user?.created_at || ''
, 'YYYY-MM'
)"
:icon=
"CalendarIcon"
icon-variant=
"primary"
/>
...
...
@@ -267,6 +267,7 @@ import { ref, computed, h, onMounted } from 'vue'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
useAuthStore
}
from
'
@/stores/auth
'
import
{
useAppStore
}
from
'
@/stores/app
'
import
{
formatDate
}
from
'
@/utils/format
'
const
{
t
}
=
useI18n
()
import
{
userAPI
,
authAPI
}
from
'
@/api
'
...
...
@@ -358,15 +359,6 @@ const formatCurrency = (value: number): string => {
return
`$
${
value
.
toFixed
(
2
)}
`
}
const
formatMemberSince
=
(
dateString
:
string
):
string
=>
{
if
(
!
dateString
)
return
'
N/A
'
const
date
=
new
Date
(
dateString
)
return
date
.
toLocaleDateString
(
'
en-US
'
,
{
year
:
'
numeric
'
,
month
:
'
short
'
})
}
const
handleChangePassword
=
async
()
=>
{
// Validate password match
if
(
passwordForm
.
value
.
new_password
!==
passwordForm
.
value
.
confirm_password
)
{
...
...
frontend/src/views/user/RedeemView.vue
View file @
a413fa3b
...
...
@@ -377,7 +377,7 @@
{{
getHistoryItemTitle
(
item
)
}}
<
/p
>
<
p
class
=
"
text-xs text-gray-500 dark:text-dark-400
"
>
{{
formatDate
(
item
.
used_at
)
}}
{{
formatDate
Time
(
item
.
used_at
)
}}
<
/p
>
<
/div
>
<
/div
>
...
...
@@ -447,6 +447,7 @@ import { useAuthStore } from '@/stores/auth'
import
{
useAppStore
}
from
'
@/stores/app
'
import
{
redeemAPI
,
authAPI
,
type
RedeemHistoryItem
}
from
'
@/api
'
import
AppLayout
from
'
@/components/layout/AppLayout.vue
'
import
{
formatDateTime
}
from
'
@/utils/format
'
const
{
t
}
=
useI18n
()
const
authStore
=
useAuthStore
()
...
...
@@ -472,18 +473,6 @@ const history = ref<RedeemHistoryItem[]>([])
const
loadingHistory
=
ref
(
false
)
const
contactInfo
=
ref
(
''
)
const
formatDate
=
(
dateString
:
string
)
=>
{
if
(
!
dateString
)
return
'
-
'
const
date
=
new
Date
(
dateString
)
return
date
.
toLocaleDateString
(
'
en-US
'
,
{
year
:
'
numeric
'
,
month
:
'
short
'
,
day
:
'
numeric
'
,
hour
:
'
2-digit
'
,
minute
:
'
2-digit
'
}
)
}
// Helper functions for history display
const
isBalanceType
=
(
type
:
string
)
=>
{
return
type
===
'
balance
'
||
type
===
'
admin_balance
'
...
...
frontend/src/views/user/SubscriptionsView.vue
View file @
a413fa3b
...
...
@@ -257,6 +257,7 @@ import { useAppStore } from '@/stores/app'
import
subscriptionsAPI
from
'
@/api/subscriptions
'
import
type
{
UserSubscription
}
from
'
@/types
'
import
AppLayout
from
'
@/components/layout/AppLayout.vue
'
import
{
formatDateOnly
}
from
'
@/utils/format
'
const
{
t
}
=
useI18n
()
const
appStore
=
useAppStore
()
...
...
@@ -300,11 +301,7 @@ function formatExpirationDate(expiresAt: string): string {
return
t
(
'
userSubscriptions.status.expired
'
)
}
const
dateStr
=
expires
.
toLocaleDateString
(
undefined
,
{
year
:
'
numeric
'
,
month
:
'
short
'
,
day
:
'
numeric
'
}
)
const
dateStr
=
formatDateOnly
(
expires
)
if
(
days
===
0
)
{
return
`${dateStr
}
(Today)`
...
...
frontend/src/views/user/UsageView.vue
View file @
a413fa3b
<
template
>
<AppLayout>
<
div
class=
"space-y-6"
>
<
!-- Summary Stats Cards --
>
<
TablePageLayout
>
<
template
#actions
>
<div
class=
"grid grid-cols-2 gap-4 lg:grid-cols-4"
>
<!-- Total Requests -->
<div
class=
"card p-4"
>
...
...
@@ -132,8 +132,9 @@
</div>
</div>
</div>
</
template
>
<
!-- F
ilters
--
>
<
template
#f
ilters
>
<div
class=
"card"
>
<div
class=
"px-6 py-4"
>
<div
class=
"flex flex-wrap items-end gap-4"
>
...
...
@@ -170,10 +171,16 @@
</div>
</div>
</div>
</
template
>
<!-- Usage Table -->
<div
class=
"card overflow-hidden"
>
<
template
#table
>
<DataTable
:columns=
"columns"
:data=
"usageLogs"
:loading=
"loading"
>
<template
#cell-api_key
="
{ row }">
<span
class=
"text-sm text-gray-900 dark:text-white"
>
{{
row
.
api_key
?.
name
||
'
-
'
}}
</span>
</
template
>
<
template
#cell-model=
"{ value }"
>
<span
class=
"font-medium text-gray-900 dark:text-white"
>
{{
value
}}
</span>
</
template
>
...
...
@@ -379,9 +386,9 @@
<EmptyState
:message=
"t('usage.noRecords')"
/>
</
template
>
</DataTable>
</
div
>
</
template
>
<
!-- P
agination
--
>
<
template
#p
agination
>
<Pagination
v-if=
"pagination.total > 0"
:page=
"pagination.page"
...
...
@@ -389,7 +396,8 @@
:page-size=
"pagination.page_size"
@
update:page=
"handlePageChange"
/>
</div>
</
template
>
</TablePageLayout>
</AppLayout>
</template>
...
...
@@ -399,6 +407,7 @@ import { useI18n } from 'vue-i18n'
import
{
useAppStore
}
from
'
@/stores/app
'
import
{
usageAPI
,
keysAPI
}
from
'
@/api
'
import
AppLayout
from
'
@/components/layout/AppLayout.vue
'
import
TablePageLayout
from
'
@/components/layout/TablePageLayout.vue
'
import
DataTable
from
'
@/components/common/DataTable.vue
'
import
Pagination
from
'
@/components/common/Pagination.vue
'
import
EmptyState
from
'
@/components/common/EmptyState.vue
'
...
...
@@ -406,6 +415,7 @@ import Select from '@/components/common/Select.vue'
import
DateRangePicker
from
'
@/components/common/DateRangePicker.vue
'
import
type
{
UsageLog
,
ApiKey
,
UsageQueryParams
,
UsageStatsResponse
}
from
'
@/types
'
import
type
{
Column
}
from
'
@/components/common/types
'
import
{
formatDateTime
}
from
'
@/utils/format
'
const
{
t
}
=
useI18n
()
const
appStore
=
useAppStore
()
...
...
@@ -414,6 +424,7 @@ const appStore = useAppStore()
const
usageStats
=
ref
<
UsageStatsResponse
|
null
>
(
null
)
const
columns
=
computed
<
Column
[]
>
(()
=>
[
{
key
:
'
api_key
'
,
label
:
t
(
'
usage.apiKeyFilter
'
),
sortable
:
false
},
{
key
:
'
model
'
,
label
:
t
(
'
usage.model
'
),
sortable
:
true
},
{
key
:
'
stream
'
,
label
:
t
(
'
usage.type
'
),
sortable
:
false
},
{
key
:
'
tokens
'
,
label
:
t
(
'
usage.tokens
'
),
sortable
:
false
},
...
...
@@ -505,17 +516,6 @@ const formatCacheTokens = (value: number): string => {
return
value
.
toLocaleString
()
}
const
formatDateTime
=
(
dateString
:
string
):
string
=>
{
const
date
=
new
Date
(
dateString
)
return
date
.
toLocaleString
(
'
en-US
'
,
{
year
:
'
numeric
'
,
month
:
'
short
'
,
day
:
'
numeric
'
,
hour
:
'
2-digit
'
,
minute
:
'
2-digit
'
})
}
const
loadUsageLogs
=
async
()
=>
{
loading
.
value
=
true
try
{
...
...
Prev
1
2
3
Next
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