Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
client
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
ad7da22d
authored
Jun 10, 2026
by
renyizhao
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
添加按次计费 登录可选获取令牌
parent
60a32cda
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
507 additions
and
31 deletions
+507
-31
src/api/console.js
+19
-0
src/router/index.js
+12
-0
src/views/computingResource/resourceList.vue
+33
-0
src/views/console/components/model-card.vue
+20
-4
src/views/console/tokenLog.vue
+387
-0
src/views/login.vue
+36
-27
No files found.
src/api/console.js
View file @
ad7da22d
...
@@ -61,3 +61,22 @@ export function getRecharge(id) {
...
@@ -61,3 +61,22 @@ export function getRecharge(id) {
params
:
{
id
}
params
:
{
id
}
})
})
}
}
// 获取个人token日志(消费/充值)
// type: 1=充值 2=消费
export
function
getTokenLog
(
params
)
{
return
request
({
url
:
'/app/ai-log/self'
,
method
:
'get'
,
params
})
}
// 获取个人token日志统计(quota / rpm / tpm)
export
function
getTokenLogStat
(
params
)
{
return
request
({
url
:
'/app/ai-log/stat'
,
method
:
'get'
,
params
})
}
src/router/index.js
View file @
ad7da22d
...
@@ -260,6 +260,18 @@ export const constantRoutes = [
...
@@ -260,6 +260,18 @@ export const constantRoutes = [
{
{
path
:
'/console'
,
path
:
'/console'
,
component
:
ManageLayout
,
component
:
ManageLayout
,
children
:
[
{
path
:
'tokenLog'
,
component
:
()
=>
import
(
'@/views/console/tokenLog.vue'
),
name
:
'TokenLog'
,
meta
:
{
title
:
'token使用记录'
,
icon
:
'instock'
}
}
]
},
{
path
:
'/console'
,
component
:
ManageLayout
,
hidden
:
true
,
hidden
:
true
,
children
:
[
children
:
[
{
{
...
...
src/views/computingResource/resourceList.vue
View file @
ad7da22d
...
@@ -279,6 +279,7 @@
...
@@ -279,6 +279,7 @@
<!-- 底部固定:未登录或未获取令牌时显示 -->
<!-- 底部固定:未登录或未获取令牌时显示 -->
<transition
name=
"el-fade-in"
v-if=
"tabActive === 999"
>
<transition
name=
"el-fade-in"
v-if=
"tabActive === 999"
>
<!-- 未获取令牌:引导用户获取 -->
<div
v-if=
"!hasToken"
class=
"bottom-cta"
>
<div
v-if=
"!hasToken"
class=
"bottom-cta"
>
<div
class=
"cta-card"
>
<div
class=
"cta-card"
>
<div
class=
"cta-text"
>
<div
class=
"cta-text"
>
...
@@ -294,6 +295,20 @@
...
@@ -294,6 +295,20 @@
>
{{ ctaButtonText }}
</el-button>
>
{{ ctaButtonText }}
</el-button>
</div>
</div>
</div>
</div>
<!-- 已有令牌:提示可在个人中心查看 -->
<div
v-else
class=
"bottom-cta"
>
<div
class=
"cta-card cta-card--info"
>
<div
class=
"cta-text"
>
<div
class=
"cta-title"
>
已获取 API 令牌
</div>
<div
class=
"cta-subtitle"
>
控制台个人中心可查看令牌
</div>
</div>
<el-button
size=
"large"
class=
"cta-btn"
@
click=
"$router.push('/console/overview')"
>
前往查看
</el-button>
</div>
</div>
</transition>
</transition>
</div>
</div>
...
@@ -1064,6 +1079,24 @@ onMounted(() => {
...
@@ -1064,6 +1079,24 @@ onMounted(() => {
0
0
0
1px
rgba
(
46
,
119
,
227
,
0.05
);
0
0
0
1px
rgba
(
46
,
119
,
227
,
0.05
);
}
}
//
已有令牌
:
信息提示样式
(
弱化主按钮
,
降低视觉权重
)
.cta-card--info
{
box-shadow
:
0
-2px
12px
rgba
(
0
,
0
,
0
,
0.03
),
0
6px
18px
rgba
(
46
,
119
,
227
,
0.06
),
0
0
0
1px
rgba
(
46
,
119
,
227
,
0.04
);
.cta-btn
{
color
:
var
(
--el-color-primary
);
background
:
#f0f5ff
;
border-color
:
#dbe5ff
;
}
.cta-btn
:hover
{
background
:
#e0ebff
;
}
}
.cta-text
{
.cta-text
{
flex
:
1
;
flex
:
1
;
min-width
:
0
;
min-width
:
0
;
...
...
src/views/console/components/model-card.vue
View file @
ad7da22d
<
template
>
<
template
>
<div
class=
"model-card"
>
<div
class=
"model-card"
>
<!-- 右上角:按量收费 -->
<!-- 右上角:按量
/按次
收费 -->
<div
class=
"tag-top"
>
按量收费
</div>
<div
class=
"tag-top"
>
{{
billingTagText
}}
</div>
<!-- 头部:图标 + 模型名 -->
<!-- 头部:图标 + 模型名 -->
<div
class=
"card-header"
>
<div
class=
"card-header"
>
...
@@ -20,15 +20,22 @@
...
@@ -20,15 +20,22 @@
{{
model
.
description
||
'暂无描述'
}}
{{
model
.
description
||
'暂无描述'
}}
</div>
</div>
<!-- 底部:收费价格 tag +
输入/输出价格
-->
<!-- 底部:收费价格 tag +
价格(按量/按次不同展示)
-->
<div
class=
"card-footer"
>
<div
class=
"card-footer"
>
<div
class=
"tag-price"
>
收费价格
</div>
<div
class=
"tag-price"
>
{{
priceTagText
}}
</div>
<div
class=
"price-text"
>
<div
class=
"price-text"
>
<!-- 按次计费:单次价格 -->
<template
v-if=
"isPerCall"
>
<span>
每次调用
<b>
¥
{{
formatPrice
(
model
.
inputPrice
)
}}
</b></span>
</
template
>
<!-- 按量计费:输入/输出 -->
<
template
v-else
>
<span>
输入:
<b>
¥
{{
formatPrice
(
model
.
inputPrice
)
}}
</b></span>
<span>
输入:
<b>
¥
{{
formatPrice
(
model
.
inputPrice
)
}}
</b></span>
<span
class=
"unit"
>
/ 1M tokens
</span>
<span
class=
"unit"
>
/ 1M tokens
</span>
<span
class=
"divider"
></span>
<span
class=
"divider"
></span>
<span>
输出:
<b>
¥
{{
formatPrice
(
model
.
outputPrice
)
}}
</b></span>
<span>
输出:
<b>
¥
{{
formatPrice
(
model
.
outputPrice
)
}}
</b></span>
<span
class=
"unit"
>
/ 1M tokens
</span>
<span
class=
"unit"
>
/ 1M tokens
</span>
</
template
>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -57,6 +64,15 @@ const iconInfo = computed(() => {
...
@@ -57,6 +64,15 @@ const iconInfo = computed(() => {
return
{
name
:
''
,
variant
:
'Color'
}
return
{
name
:
''
,
variant
:
'Color'
}
})
})
// 是否按次计费(quotaType === 1)
const
isPerCall
=
computed
(()
=>
Number
(
props
.
model
?.
quotaType
)
===
1
)
// 右上角 tag 文案
const
billingTagText
=
computed
(()
=>
(
isPerCall
.
value
?
'按次收费'
:
'按量收费'
))
// 底部 tag 文案
const
priceTagText
=
computed
(()
=>
(
isPerCall
.
value
?
'单次价格'
:
'收费价格'
))
function
formatPrice
(
price
)
{
function
formatPrice
(
price
)
{
if
(
price
===
null
||
price
===
undefined
)
return
'-'
if
(
price
===
null
||
price
===
undefined
)
return
'-'
const
num
=
Number
(
price
)
const
num
=
Number
(
price
)
...
...
src/views/console/tokenLog.vue
0 → 100644
View file @
ad7da22d
This diff is collapsed.
Click to expand it.
src/views/login.vue
View file @
ad7da22d
...
@@ -63,6 +63,10 @@
...
@@ -63,6 +63,10 @@
<
/el-tab-pane
>
<
/el-tab-pane
>
<
/el-tabs
>
<
/el-tabs
>
<
div
class
=
"mb20"
style
=
"height: 20px;"
>
<
el
-
checkbox
v
-
model
=
"loginForm.autoCreateNewApi"
>
若无
token
令牌,自动创建
<
/el-checkbox
>
<
/div
>
<
div
class
=
"flex-align-center flex-space-between mb20"
style
=
"height: 32px;"
>
<
div
class
=
"flex-align-center flex-space-between mb20"
style
=
"height: 32px;"
>
<
el
-
checkbox
v
-
show
=
"activeName === 1"
v
-
model
=
"loginForm.rememberMe"
>
记住密码
<
/el-checkbox
>
<
el
-
checkbox
v
-
show
=
"activeName === 1"
v
-
model
=
"loginForm.rememberMe"
>
记住密码
<
/el-checkbox
>
<
div
v
-
if
=
"register"
>
<
div
v
-
if
=
"register"
>
...
@@ -96,6 +100,7 @@ import {encrypt, decrypt} from '@/utils/jsencrypt'
...
@@ -96,6 +100,7 @@ import {encrypt, decrypt} from '@/utils/jsencrypt'
import
useUserStore
from
'@/store/modules/user'
import
useUserStore
from
'@/store/modules/user'
import
{
useRoute
,
useRouter
}
from
'vue-router'
import
{
useRoute
,
useRouter
}
from
'vue-router'
import
{
getInfo
,
sendCode
}
from
"@/api/login.js"
;
import
{
getInfo
,
sendCode
}
from
"@/api/login.js"
;
import
{
getToken
}
from
"@/api/console.js"
;
import
{
ElMessage
}
from
"element-plus"
;
import
{
ElMessage
}
from
"element-plus"
;
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
...
@@ -109,7 +114,8 @@ const loginForm = ref({
...
@@ -109,7 +114,8 @@ const loginForm = ref({
rememberMe
:
false
,
rememberMe
:
false
,
code
:
''
,
code
:
''
,
uuid
:
''
,
uuid
:
''
,
phoneNumber
:
''
phoneNumber
:
''
,
autoCreateNewApi
:
true
}
)
}
)
const
loginRules
=
{
const
loginRules
=
{
...
@@ -178,10 +184,19 @@ function handleClick() {
...
@@ -178,10 +184,19 @@ function handleClick() {
proxy
.
$refs
.
loginRef
.
resetFields
()
proxy
.
$refs
.
loginRef
.
resetFields
()
}
}
function
handleLogin
()
{
async
function
handleLogin
()
{
proxy
.
$refs
.
loginRef
.
validate
(
valid
=>
{
proxy
.
$refs
.
loginRef
.
validate
(
async
valid
=>
{
if
(
valid
)
{
if
(
!
valid
)
return
loading
.
value
=
true
loading
.
value
=
true
try
{
const
query
=
route
.
query
const
otherQueryParams
=
Object
.
keys
(
query
).
reduce
((
acc
,
cur
)
=>
{
if
(
cur
!==
'redirect'
)
{
acc
[
cur
]
=
query
[
cur
]
}
return
acc
}
,
{
}
)
if
(
activeName
.
value
===
1
)
{
if
(
activeName
.
value
===
1
)
{
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if
(
loginForm
.
value
.
rememberMe
)
{
if
(
loginForm
.
value
.
rememberMe
)
{
...
@@ -195,37 +210,30 @@ function handleLogin() {
...
@@ -195,37 +210,30 @@ function handleLogin() {
Cookies
.
remove
(
'rememberMe'
)
Cookies
.
remove
(
'rememberMe'
)
}
}
// 调用action的登录方法
// 调用action的登录方法
userStore
.
login
(
loginForm
.
value
).
then
(()
=>
{
await
userStore
.
login
(
loginForm
.
value
)
const
query
=
route
.
query
const
otherQueryParams
=
Object
.
keys
(
query
).
reduce
((
acc
,
cur
)
=>
{
if
(
cur
!==
'redirect'
)
{
acc
[
cur
]
=
query
[
cur
]
}
return
acc
}
,
{
}
)
useUserStore
().
getInfo
().
then
(
res
=>
{
useUserStore
().
getInfo
().
then
(
res
=>
{
localStorage
.
setItem
(
'avatar'
,
res
.
data
.
avatar
);
localStorage
.
setItem
(
'avatar'
,
res
.
data
.
avatar
);
}
)
}
)
router
.
push
({
path
:
redirect
.
value
||
'/'
,
query
:
otherQueryParams
}
)
}
).
catch
(()
=>
{
loading
.
value
=
false
}
)
}
else
{
}
else
{
userStore
.
cellPhoneLogin
({
mobile
:
loginForm
.
value
.
phoneNumber
,
code
:
loginForm
.
value
.
code
}
).
then
(
res
=>
{
await
userStore
.
cellPhoneLogin
({
const
query
=
route
.
query
mobile
:
loginForm
.
value
.
phoneNumber
,
const
otherQueryParams
=
Object
.
keys
(
query
).
reduce
((
acc
,
cur
)
=>
{
code
:
loginForm
.
value
.
code
if
(
cur
!==
'redirect'
)
{
}
)
acc
[
cur
]
=
query
[
cur
]
}
return
acc
}
,
{
}
)
useUserStore
().
getInfo
().
then
(
res
=>
{
useUserStore
().
getInfo
().
then
(
res
=>
{
}
)
}
)
}
// 关键:等 New API 令牌落库后再跳转,避免首页第一时间出现"无 apiKey"状态
if
(
loginForm
.
value
.
autoCreateNewApi
)
{
try
{
await
getToken
()
}
catch
(
err
)
{
console
.
warn
(
'[login] 自动创建 New API token 失败,不影响登录'
,
err
)
}
}
router
.
push
({
path
:
redirect
.
value
||
'/'
,
query
:
otherQueryParams
}
)
router
.
push
({
path
:
redirect
.
value
||
'/'
,
query
:
otherQueryParams
}
)
}
).
catch
(()
=>
{
}
catch
{
loading
.
value
=
false
loading
.
value
=
false
}
)
}
}
}
}
)
}
)
}
}
...
@@ -236,6 +244,7 @@ function getCookie() {
...
@@ -236,6 +244,7 @@ function getCookie() {
const
password
=
Cookies
.
get
(
'password'
)
const
password
=
Cookies
.
get
(
'password'
)
const
rememberMe
=
Cookies
.
get
(
'rememberMe'
)
const
rememberMe
=
Cookies
.
get
(
'rememberMe'
)
loginForm
.
value
=
{
loginForm
.
value
=
{
...
loginForm
.
value
,
mobile
:
mobile
===
undefined
?
loginForm
.
value
.
mobile
:
mobile
,
mobile
:
mobile
===
undefined
?
loginForm
.
value
.
mobile
:
mobile
,
password
:
password
===
undefined
?
loginForm
.
value
.
password
:
decrypt
(
password
),
password
:
password
===
undefined
?
loginForm
.
value
.
password
:
decrypt
(
password
),
rememberMe
:
rememberMe
===
undefined
?
false
:
Boolean
(
rememberMe
)
rememberMe
:
rememberMe
===
undefined
?
false
:
Boolean
(
rememberMe
)
...
...
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