Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
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
eb955ae4
authored
Mar 23, 2024
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: lint fix
parent
d572a43f
Show whitespace changes
Inline
Side-by-side
Showing
55 changed files
with
3739 additions
and
2066 deletions
+3739
-2066
web/README.md
+0
-0
web/index.html
+1
-1
web/src/App.js
+23
-25
web/src/components/ChannelsTable.js
+293
-160
web/src/components/Footer.js
+16
-7
web/src/components/GitHubOAuth.js
+1
-1
web/src/components/HeaderBar.js
+43
-22
web/src/components/Loading.js
+3
-1
web/src/components/LoginForm.js
+102
-35
web/src/components/LogsTable.js
+386
-133
web/src/components/MjLogsTable.js
+285
-118
web/src/components/OperationSetting.js
+211
-170
web/src/components/OtherSetting.js
+68
-31
web/src/components/PasswordResetConfirm.js
+17
-17
web/src/components/PasswordResetForm.js
+13
-13
web/src/components/PersonalSetting.js
+220
-109
web/src/components/PrivateRoute.js
+1
-2
web/src/components/RedemptionsTable.js
+164
-119
web/src/components/RegisterForm.js
+41
-33
web/src/components/SiderBar.js
+67
-31
web/src/components/SystemSetting.js
+198
-157
web/src/components/TokensTable.js
+262
-148
web/src/components/UsersTable.js
+223
-97
web/src/components/WeChatIcon.js
+19
-7
web/src/components/utils.js
+1
-1
web/src/constants/channel.constants.js
+98
-20
web/src/constants/index.js
+0
-0
web/src/constants/toast.constants.js
+1
-1
web/src/constants/user.constants.js
+1
-1
web/src/context/Status/index.js
+0
-0
web/src/context/User/index.js
+9
-10
web/src/context/User/reducer.js
+3
-3
web/src/helpers/api.js
+4
-2
web/src/helpers/auth-header.js
+1
-1
web/src/helpers/history.js
+0
-0
web/src/helpers/index.js
+0
-0
web/src/helpers/render.js
+41
-14
web/src/helpers/utils.js
+6
-17
web/src/index.css
+14
-10
web/src/index.js
+12
-13
web/src/pages/About/index.js
+19
-14
web/src/pages/Channel/EditChannel.js
+265
-163
web/src/pages/Channel/index.js
+2
-2
web/src/pages/Chat/index.js
+0
-1
web/src/pages/Detail/index.js
+165
-106
web/src/pages/Home/index.js
+59
-22
web/src/pages/Redemption/EditRedemption.js
+58
-29
web/src/pages/Redemption/index.js
+2
-2
web/src/pages/Setting/index.js
+13
-13
web/src/pages/Token/EditToken.js
+114
-52
web/src/pages/Token/index.js
+2
-2
web/src/pages/TopUp/index.js
+95
-71
web/src/pages/User/AddUser.js
+24
-15
web/src/pages/User/EditUser.js
+71
-42
web/src/pages/User/index.js
+2
-2
No files found.
web/README.md
View file @
eb955ae4
web/index.html
View file @
eb955ae4
<!
DOCTYPE
html>
<!
doctype
html>
<html
lang=
"en"
>
<html
lang=
"en"
>
<head>
<head>
<meta
charset=
"utf-8"
/>
<meta
charset=
"utf-8"
/>
...
...
web/src/App.js
View file @
eb955ae4
...
@@ -48,7 +48,7 @@ function App() {
...
@@ -48,7 +48,7 @@ function App() {
}
}
let
logo
=
getLogo
();
let
logo
=
getLogo
();
if
(
logo
)
{
if
(
logo
)
{
let
linkElement
=
document
.
querySelector
(
'link[rel~=\'icon\']'
);
let
linkElement
=
document
.
querySelector
(
"link[rel~='icon']"
);
if
(
linkElement
)
{
if
(
linkElement
)
{
linkElement
.
href
=
logo
;
linkElement
.
href
=
logo
;
}
}
...
@@ -60,7 +60,7 @@ function App() {
...
@@ -60,7 +60,7 @@ function App() {
<
Layout
.
Content
>
<
Layout
.
Content
>
<
Routes
>
<
Routes
>
<
Route
<
Route
path
=
"/"
path
=
'/'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Home
/>
<
Home
/>
...
@@ -68,7 +68,7 @@ function App() {
...
@@ -68,7 +68,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/channel"
path
=
'/channel'
element
=
{
element
=
{
<
PrivateRoute
>
<
PrivateRoute
>
<
Channel
/>
<
Channel
/>
...
@@ -76,7 +76,7 @@ function App() {
...
@@ -76,7 +76,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/channel/edit/:id"
path
=
'/channel/edit/:id'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
EditChannel
/>
<
EditChannel
/>
...
@@ -84,7 +84,7 @@ function App() {
...
@@ -84,7 +84,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/channel/add"
path
=
'/channel/add'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
EditChannel
/>
<
EditChannel
/>
...
@@ -92,7 +92,7 @@ function App() {
...
@@ -92,7 +92,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/token"
path
=
'/token'
element
=
{
element
=
{
<
PrivateRoute
>
<
PrivateRoute
>
<
Token
/>
<
Token
/>
...
@@ -100,7 +100,7 @@ function App() {
...
@@ -100,7 +100,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/redemption"
path
=
'/redemption'
element
=
{
element
=
{
<
PrivateRoute
>
<
PrivateRoute
>
<
Redemption
/>
<
Redemption
/>
...
@@ -108,7 +108,7 @@ function App() {
...
@@ -108,7 +108,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/user"
path
=
'/user'
element
=
{
element
=
{
<
PrivateRoute
>
<
PrivateRoute
>
<
User
/>
<
User
/>
...
@@ -116,7 +116,7 @@ function App() {
...
@@ -116,7 +116,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/user/edit/:id"
path
=
'/user/edit/:id'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
EditUser
/>
<
EditUser
/>
...
@@ -124,7 +124,7 @@ function App() {
...
@@ -124,7 +124,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/user/edit"
path
=
'/user/edit'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
EditUser
/>
<
EditUser
/>
...
@@ -132,7 +132,7 @@ function App() {
...
@@ -132,7 +132,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/user/reset"
path
=
'/user/reset'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
PasswordResetConfirm
/>
<
PasswordResetConfirm
/>
...
@@ -140,7 +140,7 @@ function App() {
...
@@ -140,7 +140,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/login"
path
=
'/login'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
LoginForm
/>
<
LoginForm
/>
...
@@ -148,7 +148,7 @@ function App() {
...
@@ -148,7 +148,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/register"
path
=
'/register'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
RegisterForm
/>
<
RegisterForm
/>
...
@@ -156,7 +156,7 @@ function App() {
...
@@ -156,7 +156,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/reset"
path
=
'/reset'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
PasswordResetForm
/>
<
PasswordResetForm
/>
...
@@ -164,7 +164,7 @@ function App() {
...
@@ -164,7 +164,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/oauth/github"
path
=
'/oauth/github'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
GitHubOAuth
/>
<
GitHubOAuth
/>
...
@@ -172,7 +172,7 @@ function App() {
...
@@ -172,7 +172,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/setting"
path
=
'/setting'
element
=
{
element
=
{
<
PrivateRoute
>
<
PrivateRoute
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
...
@@ -182,7 +182,7 @@ function App() {
...
@@ -182,7 +182,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/topup"
path
=
'/topup'
element
=
{
element
=
{
<
PrivateRoute
>
<
PrivateRoute
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
...
@@ -192,7 +192,7 @@ function App() {
...
@@ -192,7 +192,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/log"
path
=
'/log'
element
=
{
element
=
{
<
PrivateRoute
>
<
PrivateRoute
>
<
Log
/>
<
Log
/>
...
@@ -200,7 +200,7 @@ function App() {
...
@@ -200,7 +200,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/detail"
path
=
'/detail'
element
=
{
element
=
{
<
PrivateRoute
>
<
PrivateRoute
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
...
@@ -210,7 +210,7 @@ function App() {
...
@@ -210,7 +210,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/midjourney"
path
=
'/midjourney'
element
=
{
element
=
{
<
PrivateRoute
>
<
PrivateRoute
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
...
@@ -220,7 +220,7 @@ function App() {
...
@@ -220,7 +220,7 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/about"
path
=
'/about'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
About
/>
<
About
/>
...
@@ -228,16 +228,14 @@ function App() {
...
@@ -228,16 +228,14 @@ function App() {
}
}
/
>
/
>
<
Route
<
Route
path
=
"/chat"
path
=
'/chat'
element
=
{
element
=
{
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Suspense
fallback
=
{
<
Loading
><
/Loading>}
>
<
Chat
/>
<
Chat
/>
<
/Suspense
>
<
/Suspense
>
}
}
/
>
/
>
<
Route
path
=
"*"
element
=
{
<
Route
path
=
'*'
element
=
{
<
NotFound
/>
}
/
>
<
NotFound
/>
}
/
>
<
/Routes
>
<
/Routes
>
<
/Layout.Content
>
<
/Layout.Content
>
<
/Layout
>
<
/Layout
>
...
...
web/src/components/ChannelsTable.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
API
,
isMobile
,
shouldShowPrompt
,
showError
,
showInfo
,
showSuccess
,
timestamp2string
}
from
'../helpers'
;
import
{
API
,
isMobile
,
shouldShowPrompt
,
showError
,
showInfo
,
showSuccess
,
timestamp2string
,
}
from
'../helpers'
;
import
{
CHANNEL_OPTIONS
,
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
CHANNEL_OPTIONS
,
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderGroup
,
renderNumberWithPoint
,
renderQuota
}
from
'../helpers/render'
;
import
{
renderGroup
,
renderNumberWithPoint
,
renderQuota
,
}
from
'../helpers/render'
;
import
{
import
{
Button
,
Button
,
Dropdown
,
Dropdown
,
...
@@ -15,17 +27,13 @@ import {
...
@@ -15,17 +27,13 @@ import {
Table
,
Table
,
Tag
,
Tag
,
Tooltip
,
Tooltip
,
Typography
Typography
,
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
EditChannel
from
'../pages/Channel/EditChannel'
;
import
EditChannel
from
'../pages/Channel/EditChannel'
;
import
{
IconTreeTriangleDown
}
from
'@douyinfe/semi-icons'
;
import
{
IconTreeTriangleDown
}
from
'@douyinfe/semi-icons'
;
function
renderTimestamp
(
timestamp
)
{
function
renderTimestamp
(
timestamp
)
{
return
(
return
<>
{
timestamp2string
(
timestamp
)}
<
/>
;
<>
{
timestamp2string
(
timestamp
)}
<
/
>
);
}
}
let
type2label
=
undefined
;
let
type2label
=
undefined
;
...
@@ -38,7 +46,11 @@ function renderType(type) {
...
@@ -38,7 +46,11 @@ function renderType(type) {
}
}
type2label
[
0
]
=
{
value
:
0
,
text
:
'未知类型'
,
color
:
'grey'
};
type2label
[
0
]
=
{
value
:
0
,
text
:
'未知类型'
,
color
:
'grey'
};
}
}
return
<
Tag
size
=
"large"
color
=
{
type2label
[
type
]?.
color
}
>
{
type2label
[
type
]?.
text
}
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
{
type2label
[
type
]?.
color
}
>
{
type2label
[
type
]?.
text
}
<
/Tag
>
);
}
}
const
ChannelsTable
=
()
=>
{
const
ChannelsTable
=
()
=>
{
...
@@ -50,11 +62,11 @@ const ChannelsTable = () => {
...
@@ -50,11 +62,11 @@ const ChannelsTable = () => {
// },
// },
{
{
title
:
'ID'
,
title
:
'ID'
,
dataIndex
:
'id'
dataIndex
:
'id'
,
},
},
{
{
title
:
'名称'
,
title
:
'名称'
,
dataIndex
:
'name'
dataIndex
:
'name'
,
},
},
{
{
title
:
'分组'
,
title
:
'分组'
,
...
@@ -63,48 +75,34 @@ const ChannelsTable = () => {
...
@@ -63,48 +75,34 @@ const ChannelsTable = () => {
return
(
return
(
<
div
>
<
div
>
<
Space
spacing
=
{
2
}
>
<
Space
spacing
=
{
2
}
>
{
{
text
.
split
(
','
).
map
((
item
,
index
)
=>
{
text
.
split
(
','
).
map
((
item
,
index
)
=>
{
return
renderGroup
(
item
);
return
(
renderGroup
(
item
));
})}
})
}
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
);
);
}
}
,
},
},
{
{
title
:
'类型'
,
title
:
'类型'
,
dataIndex
:
'type'
,
dataIndex
:
'type'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderType
(
text
)}
<
/div>
;
<
div
>
},
{
renderType
(
text
)}
<
/div
>
);
}
},
},
{
{
title
:
'状态'
,
title
:
'状态'
,
dataIndex
:
'status'
,
dataIndex
:
'status'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderStatus
(
text
)}
<
/div>
;
<
div
>
},
{
renderStatus
(
text
)}
<
/div
>
);
}
},
},
{
{
title
:
'响应时间'
,
title
:
'响应时间'
,
dataIndex
:
'response_time'
,
dataIndex
:
'response_time'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderResponseTime
(
text
)}
<
/div>
;
<
div
>
},
{
renderResponseTime
(
text
)}
<
/div
>
);
}
},
},
{
{
title
:
'已用/剩余'
,
title
:
'已用/剩余'
,
...
@@ -114,17 +112,26 @@ const ChannelsTable = () => {
...
@@ -114,17 +112,26 @@ const ChannelsTable = () => {
<
div
>
<
div
>
<
Space
spacing
=
{
1
}
>
<
Space
spacing
=
{
1
}
>
<
Tooltip
content
=
{
'已用额度'
}
>
<
Tooltip
content
=
{
'已用额度'
}
>
<
Tag
color
=
"white"
type
=
"ghost"
size
=
"large"
>
{
renderQuota
(
record
.
used_quota
)}
<
/Tag
>
<
Tag
color
=
'white'
type
=
'ghost'
size
=
'large'
>
{
renderQuota
(
record
.
used_quota
)}
<
/Tag
>
<
/Tooltip
>
<
/Tooltip
>
<
Tooltip
content
=
{
'剩余额度'
+
record
.
balance
+
',点击更新'
}
>
<
Tooltip
content
=
{
'剩余额度'
+
record
.
balance
+
',点击更新'
}
>
<
Tag
color
=
"white"
type
=
"ghost"
size
=
"large"
onClick
=
{()
=>
{
<
Tag
color
=
'white'
type
=
'ghost'
size
=
'large'
onClick
=
{()
=>
{
updateChannelBalance
(
record
);
updateChannelBalance
(
record
);
}}
>
$
{
renderNumberWithPoint
(
record
.
balance
)}
<
/Tag
>
}}
>
$
{
renderNumberWithPoint
(
record
.
balance
)}
<
/Tag
>
<
/Tooltip
>
<
/Tooltip
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
);
);
}
}
,
},
},
{
{
title
:
'优先级'
,
title
:
'优先级'
,
...
@@ -134,8 +141,8 @@ const ChannelsTable = () => {
...
@@ -134,8 +141,8 @@ const ChannelsTable = () => {
<
div
>
<
div
>
<
InputNumber
<
InputNumber
style
=
{{
width
:
70
}}
style
=
{{
width
:
70
}}
name
=
"priority"
name
=
'priority'
onBlur
=
{
e
=>
{
onBlur
=
{
(
e
)
=>
{
manageChannel
(
record
.
id
,
'priority'
,
record
,
e
.
target
.
value
);
manageChannel
(
record
.
id
,
'priority'
,
record
,
e
.
target
.
value
);
}}
}}
keepFocus
=
{
true
}
keepFocus
=
{
true
}
...
@@ -145,7 +152,7 @@ const ChannelsTable = () => {
...
@@ -145,7 +152,7 @@ const ChannelsTable = () => {
/
>
/
>
<
/div
>
<
/div
>
);
);
}
}
,
},
},
{
{
title
:
'权重'
,
title
:
'权重'
,
...
@@ -155,8 +162,8 @@ const ChannelsTable = () => {
...
@@ -155,8 +162,8 @@ const ChannelsTable = () => {
<
div
>
<
div
>
<
InputNumber
<
InputNumber
style
=
{{
width
:
70
}}
style
=
{{
width
:
70
}}
name
=
"weight"
name
=
'weight'
onBlur
=
{
e
=>
{
onBlur
=
{
(
e
)
=>
{
manageChannel
(
record
.
id
,
'weight'
,
record
,
e
.
target
.
value
);
manageChannel
(
record
.
id
,
'weight'
,
record
,
e
.
target
.
value
);
}}
}}
keepFocus
=
{
true
}
keepFocus
=
{
true
}
...
@@ -166,68 +173,90 @@ const ChannelsTable = () => {
...
@@ -166,68 +173,90 @@ const ChannelsTable = () => {
/
>
/
>
<
/div
>
<
/div
>
);
);
}
}
,
},
},
{
{
title
:
''
,
title
:
''
,
dataIndex
:
'operate'
,
dataIndex
:
'operate'
,
render
:
(
text
,
record
,
index
)
=>
(
render
:
(
text
,
record
,
index
)
=>
(
<
div
>
<
div
>
<
SplitButtonGroup
style
=
{{
marginRight
:
1
}}
aria
-
label
=
"测试操作项目组"
>
<
SplitButtonGroup
<
Button
theme
=
"light"
onClick
=
{()
=>
{
style
=
{{
marginRight
:
1
}}
aria
-
label
=
'测试操作项目组'
>
<
Button
theme
=
'light'
onClick
=
{()
=>
{
testChannel
(
record
,
''
);
testChannel
(
record
,
''
);
}}
>
测试
<
/Button
>
}}
<
Dropdown
trigger
=
"click"
position
=
"bottomRight"
menu
=
{
record
.
test_models
}
>
测试
<
/Button
>
<
Dropdown
trigger
=
'click'
position
=
'bottomRight'
menu
=
{
record
.
test_models
}
>
>
<
Button
style
=
{{
padding
:
'8px 4px'
}}
type
=
"primary"
icon
=
{
<
IconTreeTriangleDown
/>
}
><
/Button
>
<
Button
style
=
{{
padding
:
'8px 4px'
}}
type
=
'primary'
icon
=
{
<
IconTreeTriangleDown
/>
}
><
/Button
>
<
/Dropdown
>
<
/Dropdown
>
<
/SplitButtonGroup
>
<
/SplitButtonGroup
>
{
/*<Button theme='light' type='primary' style={{marginRight: 1}} onClick={()=>testChannel(record)}>测试</Button>*/
}
{
/*<Button theme='light' type='primary' style={{marginRight: 1}} onClick={()=>testChannel(record)}>测试</Button>*/
}
<
Popconfirm
<
Popconfirm
title
=
"确定是否要删除此渠道?"
title
=
'确定是否要删除此渠道?'
content
=
"此修改将不可逆"
content
=
'此修改将不可逆'
okType
=
{
'danger'
}
okType
=
{
'danger'
}
position
=
{
'left'
}
position
=
{
'left'
}
onConfirm
=
{()
=>
{
onConfirm
=
{()
=>
{
manageChannel
(
record
.
id
,
'delete'
,
record
).
then
(
manageChannel
(
record
.
id
,
'delete'
,
record
).
then
(()
=>
{
()
=>
{
removeRecord
(
record
.
id
);
removeRecord
(
record
.
id
);
}
});
);
}}
}}
>
>
<
Button
theme
=
"light"
type
=
"danger"
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
{
{
record
.
status
===
1
?
(
record
.
status
===
1
?
<
Button
<
Button
theme
=
"light"
type
=
"warning"
style
=
{{
marginRight
:
1
}}
onClick
=
{
theme
=
'light'
async
()
=>
{
type
=
'warning'
manageChannel
(
style
=
{{
marginRight
:
1
}}
record
.
id
,
onClick
=
{
async
()
=>
{
'disable'
,
manageChannel
(
record
.
id
,
'disable'
,
record
);
record
}}
);
>
}
禁用
}
>
禁用
<
/Button>
:
<
/Button
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
1
}}
onClick
=
{
)
:
(
async
()
=>
{
<
Button
manageChannel
(
theme
=
'light'
record
.
id
,
type
=
'secondary'
'enable'
,
style
=
{{
marginRight
:
1
}}
record
onClick
=
{
async
()
=>
{
);
manageChannel
(
record
.
id
,
'enable'
,
record
);
}
}}
}
>
启用
<
/Button
>
>
}
启用
<
Button
theme
=
"light"
type
=
"tertiary"
style
=
{{
marginRight
:
1
}}
onClick
=
{
<
/Button
>
()
=>
{
)}
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
onClick
=
{()
=>
{
setEditingChannel
(
record
);
setEditingChannel
(
record
);
setShowEdit
(
true
);
setShowEdit
(
true
);
}
}}
}
>
编辑
<
/Button
>
>
编辑
<
/Button
>
<
/div
>
<
/div
>
)
)
,
}
}
,
];
];
const
[
channels
,
setChannels
]
=
useState
([]);
const
[
channels
,
setChannels
]
=
useState
([]);
...
@@ -240,20 +269,22 @@ const ChannelsTable = () => {
...
@@ -240,20 +269,22 @@ const ChannelsTable = () => {
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
updatingBalance
,
setUpdatingBalance
]
=
useState
(
false
);
const
[
updatingBalance
,
setUpdatingBalance
]
=
useState
(
false
);
const
[
pageSize
,
setPageSize
]
=
useState
(
ITEMS_PER_PAGE
);
const
[
pageSize
,
setPageSize
]
=
useState
(
ITEMS_PER_PAGE
);
const
[
showPrompt
,
setShowPrompt
]
=
useState
(
shouldShowPrompt
(
'channel-test'
));
const
[
showPrompt
,
setShowPrompt
]
=
useState
(
shouldShowPrompt
(
'channel-test'
),
);
const
[
channelCount
,
setChannelCount
]
=
useState
(
pageSize
);
const
[
channelCount
,
setChannelCount
]
=
useState
(
pageSize
);
const
[
groupOptions
,
setGroupOptions
]
=
useState
([]);
const
[
groupOptions
,
setGroupOptions
]
=
useState
([]);
const
[
showEdit
,
setShowEdit
]
=
useState
(
false
);
const
[
showEdit
,
setShowEdit
]
=
useState
(
false
);
const
[
enableBatchDelete
,
setEnableBatchDelete
]
=
useState
(
false
);
const
[
enableBatchDelete
,
setEnableBatchDelete
]
=
useState
(
false
);
const
[
editingChannel
,
setEditingChannel
]
=
useState
({
const
[
editingChannel
,
setEditingChannel
]
=
useState
({
id
:
undefined
id
:
undefined
,
});
});
const
[
selectedChannels
,
setSelectedChannels
]
=
useState
([]);
const
[
selectedChannels
,
setSelectedChannels
]
=
useState
([]);
const
removeRecord
=
id
=>
{
const
removeRecord
=
(
id
)
=>
{
let
newDataSource
=
[...
channels
];
let
newDataSource
=
[...
channels
];
if
(
id
!=
null
)
{
if
(
id
!=
null
)
{
let
idx
=
newDataSource
.
findIndex
(
data
=>
data
.
id
===
id
);
let
idx
=
newDataSource
.
findIndex
(
(
data
)
=>
data
.
id
===
id
);
if
(
idx
>
-
1
)
{
if
(
idx
>
-
1
)
{
newDataSource
.
splice
(
idx
,
1
);
newDataSource
.
splice
(
idx
,
1
);
...
@@ -272,7 +303,7 @@ const ChannelsTable = () => {
...
@@ -272,7 +303,7 @@ const ChannelsTable = () => {
name
:
item
,
name
:
item
,
onClick
:
()
=>
{
onClick
:
()
=>
{
testChannel
(
channels
[
i
],
item
);
testChannel
(
channels
[
i
],
item
);
}
}
,
});
});
});
});
channels
[
i
].
test_models
=
test_models
;
channels
[
i
].
test_models
=
test_models
;
...
@@ -288,7 +319,9 @@ const ChannelsTable = () => {
...
@@ -288,7 +319,9 @@ const ChannelsTable = () => {
const
loadChannels
=
async
(
startIdx
,
pageSize
,
idSort
)
=>
{
const
loadChannels
=
async
(
startIdx
,
pageSize
,
idSort
)
=>
{
setLoading
(
true
);
setLoading
(
true
);
const
res
=
await
API
.
get
(
`/api/channel/?p=
${
startIdx
}
&page_size=
${
pageSize
}
&id_sort=
${
idSort
}
`
);
const
res
=
await
API
.
get
(
`/api/channel/?p=
${
startIdx
}
&page_size=
${
pageSize
}
&id_sort=
${
idSort
}
`
,
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
if
(
startIdx
===
0
)
{
if
(
startIdx
===
0
)
{
...
@@ -311,7 +344,8 @@ const ChannelsTable = () => {
...
@@ -311,7 +344,8 @@ const ChannelsTable = () => {
useEffect
(()
=>
{
useEffect
(()
=>
{
// console.log('default effect')
// console.log('default effect')
const
localIdSort
=
localStorage
.
getItem
(
'id-sort'
)
===
'true'
;
const
localIdSort
=
localStorage
.
getItem
(
'id-sort'
)
===
'true'
;
const
localPageSize
=
parseInt
(
localStorage
.
getItem
(
'page-size'
))
||
ITEMS_PER_PAGE
;
const
localPageSize
=
parseInt
(
localStorage
.
getItem
(
'page-size'
))
||
ITEMS_PER_PAGE
;
setIdSort
(
localIdSort
);
setIdSort
(
localIdSort
);
setPageSize
(
localPageSize
);
setPageSize
(
localPageSize
);
loadChannels
(
0
,
localPageSize
,
localIdSort
)
loadChannels
(
0
,
localPageSize
,
localIdSort
)
...
@@ -361,7 +395,6 @@ const ChannelsTable = () => {
...
@@ -361,7 +395,6 @@ const ChannelsTable = () => {
let
channel
=
res
.
data
.
data
;
let
channel
=
res
.
data
.
data
;
let
newChannels
=
[...
channels
];
let
newChannels
=
[...
channels
];
if
(
action
===
'delete'
)
{
if
(
action
===
'delete'
)
{
}
else
{
}
else
{
record
.
status
=
channel
.
status
;
record
.
status
=
channel
.
status
;
}
}
...
@@ -374,22 +407,26 @@ const ChannelsTable = () => {
...
@@ -374,22 +407,26 @@ const ChannelsTable = () => {
const
renderStatus
=
(
status
)
=>
{
const
renderStatus
=
(
status
)
=>
{
switch
(
status
)
{
switch
(
status
)
{
case
1
:
case
1
:
return
<
Tag
size
=
"large"
color
=
"green"
>
已启用
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
'green'
>
已启用
<
/Tag
>
);
case
2
:
case
2
:
return
(
return
(
<
Tag
size
=
"large"
color
=
"yellow"
>
<
Tag
size
=
'large'
color
=
'yellow'
>
已禁用
已禁用
<
/Tag
>
<
/Tag
>
);
);
case
3
:
case
3
:
return
(
return
(
<
Tag
size
=
"large"
color
=
"yellow"
>
<
Tag
size
=
'large'
color
=
'yellow'
>
自动禁用
自动禁用
<
/Tag
>
<
/Tag
>
);
);
default
:
default
:
return
(
return
(
<
Tag
size
=
"large"
color
=
"grey"
>
<
Tag
size
=
'large'
color
=
'grey'
>
未知状态
未知状态
<
/Tag
>
<
/Tag
>
);
);
...
@@ -400,15 +437,35 @@ const ChannelsTable = () => {
...
@@ -400,15 +437,35 @@ const ChannelsTable = () => {
let
time
=
responseTime
/
1000
;
let
time
=
responseTime
/
1000
;
time
=
time
.
toFixed
(
2
)
+
' 秒'
;
time
=
time
.
toFixed
(
2
)
+
' 秒'
;
if
(
responseTime
===
0
)
{
if
(
responseTime
===
0
)
{
return
<
Tag
size
=
"large"
color
=
"grey"
>
未测试
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
'grey'
>
未测试
<
/Tag
>
);
}
else
if
(
responseTime
<=
1000
)
{
}
else
if
(
responseTime
<=
1000
)
{
return
<
Tag
size
=
"large"
color
=
"green"
>
{
time
}
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
'green'
>
{
time
}
<
/Tag
>
);
}
else
if
(
responseTime
<=
3000
)
{
}
else
if
(
responseTime
<=
3000
)
{
return
<
Tag
size
=
"large"
color
=
"lime"
>
{
time
}
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
'lime'
>
{
time
}
<
/Tag
>
);
}
else
if
(
responseTime
<=
5000
)
{
}
else
if
(
responseTime
<=
5000
)
{
return
<
Tag
size
=
"large"
color
=
"yellow"
>
{
time
}
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
'yellow'
>
{
time
}
<
/Tag
>
);
}
else
{
}
else
{
return
<
Tag
size
=
"large"
color
=
"red"
>
{
time
}
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
'red'
>
{
time
}
<
/Tag
>
);
}
}
};
};
...
@@ -420,7 +477,9 @@ const ChannelsTable = () => {
...
@@ -420,7 +477,9 @@ const ChannelsTable = () => {
return
;
return
;
}
}
setSearching
(
true
);
setSearching
(
true
);
const
res
=
await
API
.
get
(
`/api/channel/search?keyword=
${
searchKeyword
}
&group=
${
searchGroup
}
&model=
${
searchModel
}
`
);
const
res
=
await
API
.
get
(
`/api/channel/search?keyword=
${
searchKeyword
}
&group=
${
searchGroup
}
&model=
${
searchModel
}
`
,
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
setChannels
(
data
);
setChannels
(
data
);
...
@@ -520,14 +579,16 @@ const ChannelsTable = () => {
...
@@ -520,14 +579,16 @@ const ChannelsTable = () => {
}
}
};
};
let
pageData
=
channels
.
slice
((
activePage
-
1
)
*
pageSize
,
activePage
*
pageSize
);
let
pageData
=
channels
.
slice
(
(
activePage
-
1
)
*
pageSize
,
activePage
*
pageSize
,
);
const
handlePageChange
=
page
=>
{
const
handlePageChange
=
(
page
)
=>
{
setActivePage
(
page
);
setActivePage
(
page
);
if
(
page
===
Math
.
ceil
(
channels
.
length
/
pageSize
)
+
1
)
{
if
(
page
===
Math
.
ceil
(
channels
.
length
/
pageSize
)
+
1
)
{
// In this case we have to load more data and then append them.
// In this case we have to load more data and then append them.
loadChannels
(
page
-
1
,
pageSize
,
idSort
).
then
(
r
=>
{
loadChannels
(
page
-
1
,
pageSize
,
idSort
).
then
((
r
)
=>
{});
});
}
}
};
};
...
@@ -547,10 +608,12 @@ const ChannelsTable = () => {
...
@@ -547,10 +608,12 @@ const ChannelsTable = () => {
let
res
=
await
API
.
get
(
`/api/group/`
);
let
res
=
await
API
.
get
(
`/api/group/`
);
// add 'all' option
// add 'all' option
// res.data.data.unshift('all');
// res.data.data.unshift('all');
setGroupOptions
(
res
.
data
.
data
.
map
((
group
)
=>
({
setGroupOptions
(
res
.
data
.
data
.
map
((
group
)
=>
({
label
:
group
,
label
:
group
,
value
:
group
value
:
group
,
})));
})),
);
}
catch
(
error
)
{
}
catch
(
error
)
{
showError
(
error
.
message
);
showError
(
error
.
message
);
}
}
...
@@ -564,27 +627,34 @@ const ChannelsTable = () => {
...
@@ -564,27 +627,34 @@ const ChannelsTable = () => {
if
(
record
.
status
!==
1
)
{
if
(
record
.
status
!==
1
)
{
return
{
return
{
style
:
{
style
:
{
background
:
'var(--semi-color-disabled-border)'
background
:
'var(--semi-color-disabled-border)'
,
}
}
,
};
};
}
else
{
}
else
{
return
{};
return
{};
}
}
};
};
return
(
return
(
<>
<>
<
EditChannel
refresh
=
{
refresh
}
visible
=
{
showEdit
}
handleClose
=
{
closeEdit
}
editingChannel
=
{
editingChannel
}
/
>
<
EditChannel
<
Form
onSubmit
=
{()
=>
{
refresh
=
{
refresh
}
visible
=
{
showEdit
}
handleClose
=
{
closeEdit
}
editingChannel
=
{
editingChannel
}
/
>
<
Form
onSubmit
=
{()
=>
{
searchChannels
(
searchKeyword
,
searchGroup
,
searchModel
);
searchChannels
(
searchKeyword
,
searchGroup
,
searchModel
);
}}
labelPosition
=
"left"
>
}}
labelPosition
=
'left'
>
<
div
style
=
{{
display
:
'flex'
}}
>
<
div
style
=
{{
display
:
'flex'
}}
>
<
Space
>
<
Space
>
<
Form
.
Input
<
Form
.
Input
field
=
"search_keyword"
field
=
'search_keyword'
label
=
"搜索渠道关键词"
label
=
'搜索渠道关键词'
placeholder
=
"ID,名称和密钥 ..."
placeholder
=
'ID,名称和密钥 ...'
value
=
{
searchKeyword
}
value
=
{
searchKeyword
}
loading
=
{
searching
}
loading
=
{
searching
}
onChange
=
{(
v
)
=>
{
onChange
=
{(
v
)
=>
{
...
@@ -592,21 +662,33 @@ const ChannelsTable = () => {
...
@@ -592,21 +662,33 @@ const ChannelsTable = () => {
}}
}}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
field
=
"search_model"
field
=
'search_model'
label
=
"模型"
label
=
'模型'
placeholder
=
"模型关键字"
placeholder
=
'模型关键字'
value
=
{
searchModel
}
value
=
{
searchModel
}
loading
=
{
searching
}
loading
=
{
searching
}
onChange
=
{(
v
)
=>
{
onChange
=
{(
v
)
=>
{
setSearchModel
(
v
.
trim
());
setSearchModel
(
v
.
trim
());
}}
}}
/
>
/
>
<
Form
.
Select
field
=
"group"
label
=
"分组"
optionList
=
{
groupOptions
}
onChange
=
{(
v
)
=>
{
<
Form
.
Select
field
=
'group'
label
=
'分组'
optionList
=
{
groupOptions
}
onChange
=
{(
v
)
=>
{
setSearchGroup
(
v
);
setSearchGroup
(
v
);
searchChannels
(
searchKeyword
,
v
,
searchModel
);
searchChannels
(
searchKeyword
,
v
,
searchModel
);
}}
/
>
}}
<
Button
label
=
"查询"
type
=
"primary"
htmlType
=
"submit"
className
=
"btn-margin-right"
/
>
style
=
{{
marginRight
:
8
}}
>
查询
<
/Button
>
<
Button
label
=
'查询'
type
=
'primary'
htmlType
=
'submit'
className
=
'btn-margin-right'
style
=
{{
marginRight
:
8
}}
>
查询
<
/Button
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
<
/Form
>
<
/Form
>
...
@@ -614,7 +696,12 @@ const ChannelsTable = () => {
...
@@ -614,7 +696,12 @@ const ChannelsTable = () => {
<
Space
>
<
Space
>
<
Space
>
<
Space
>
<
Typography
.
Text
strong
>
使用
ID
排序
<
/Typography.Text
>
<
Typography
.
Text
strong
>
使用
ID
排序
<
/Typography.Text
>
<
Switch
checked
=
{
idSort
}
label
=
"使用ID排序"
uncheckedText
=
"关"
aria
-
label
=
"是否用ID排序"
onChange
=
{(
v
)
=>
{
<
Switch
checked
=
{
idSort
}
label
=
'使用ID排序'
uncheckedText
=
'关'
aria
-
label
=
'是否用ID排序'
onChange
=
{(
v
)
=>
{
localStorage
.
setItem
(
'id-sort'
,
v
+
''
);
localStorage
.
setItem
(
'id-sort'
,
v
+
''
);
setIdSort
(
v
);
setIdSort
(
v
);
loadChannels
(
0
,
pageSize
,
v
)
loadChannels
(
0
,
pageSize
,
v
)
...
@@ -622,12 +709,18 @@ const ChannelsTable = () => {
...
@@ -622,12 +709,18 @@ const ChannelsTable = () => {
.
catch
((
reason
)
=>
{
.
catch
((
reason
)
=>
{
showError
(
reason
);
showError
(
reason
);
});
});
}}
><
/Switch
>
}}
><
/Switch
>
<
/Space
>
<
/Space
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
<
Table
className
=
{
'channel-table'
}
style
=
{{
marginTop
:
15
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
<
Table
className
=
{
'channel-table'
}
style
=
{{
marginTop
:
15
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
currentPage
:
activePage
,
pageSize
:
pageSize
,
pageSize
:
pageSize
,
total
:
channelCount
,
total
:
channelCount
,
...
@@ -637,57 +730,84 @@ const ChannelsTable = () => {
...
@@ -637,57 +730,84 @@ const ChannelsTable = () => {
onPageSizeChange
:
(
size
)
=>
{
onPageSizeChange
:
(
size
)
=>
{
handlePageSizeChange
(
size
).
then
();
handlePageSizeChange
(
size
).
then
();
},
},
onPageChange
:
handlePageChange
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
onRow
=
{
handleRow
}
rowSelection
=
{
}}
enableBatchDelete
?
loading
=
{
loading
}
{
onRow
=
{
handleRow
}
rowSelection
=
{
enableBatchDelete
?
{
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setSelectedChannels
(
selectedRows
);
setSelectedChannels
(
selectedRows
);
},
}
:
null
}
}
}
:
null
/
>
}
/
>
<
div
<
div
style
=
{{
style
=
{{
display
:
isMobile
()
?
''
:
'flex'
,
display
:
isMobile
()
?
''
:
'flex'
,
marginTop
:
isMobile
()
?
0
:
-
45
,
marginTop
:
isMobile
()
?
0
:
-
45
,
zIndex
:
999
,
zIndex
:
999
,
position
:
'relative'
,
position
:
'relative'
,
pointerEvents
:
'none'
pointerEvents
:
'none'
,
}}
>
}}
<
Space
style
=
{{
pointerEvents
:
'auto'
,
marginTop
:
isMobile
()
?
0
:
45
}}
>
>
<
Button
theme
=
"light"
type
=
"primary"
style
=
{{
marginRight
:
8
}}
onClick
=
{
<
Space
()
=>
{
style
=
{{
pointerEvents
:
'auto'
,
marginTop
:
isMobile
()
?
0
:
45
}}
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{()
=>
{
setEditingChannel
({
setEditingChannel
({
id
:
undefined
id
:
undefined
,
});
});
setShowEdit
(
true
);
setShowEdit
(
true
);
}
}}
}
>
添加渠道
<
/Button
>
>
添加渠道
<
/Button
>
<
Popconfirm
<
Popconfirm
title
=
"确定?"
title
=
'确定?'
okType
=
{
'warning'
}
okType
=
{
'warning'
}
onConfirm
=
{
testAllChannels
}
onConfirm
=
{
testAllChannels
}
position
=
{
isMobile
()
?
'top'
:
'top'
}
position
=
{
isMobile
()
?
'top'
:
'top'
}
>
>
<
Button
theme
=
"light"
type
=
"warning"
style
=
{{
marginRight
:
8
}}
>
测试所有通道
<
/Button
>
<
Button
theme
=
'light'
type
=
'warning'
style
=
{{
marginRight
:
8
}}
>
测试所有通道
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
<
Popconfirm
<
Popconfirm
title
=
"确定?"
title
=
'确定?'
okType
=
{
'secondary'
}
okType
=
{
'secondary'
}
onConfirm
=
{
updateAllChannelsBalance
}
onConfirm
=
{
updateAllChannelsBalance
}
>
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
8
}}
>
更新所有已启用通道余额
<
/Button
>
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
8
}}
>
更新所有已启用通道余额
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
<
Popconfirm
<
Popconfirm
title
=
"确定是否要删除禁用通道?"
title
=
'确定是否要删除禁用通道?'
content
=
"此修改将不可逆"
content
=
'此修改将不可逆'
okType
=
{
'danger'
}
okType
=
{
'danger'
}
onConfirm
=
{
deleteAllDisabledChannels
}
onConfirm
=
{
deleteAllDisabledChannels
}
>
>
<
Button
theme
=
"light"
type
=
"danger"
style
=
{{
marginRight
:
8
}}
>
删除禁用通道
<
/Button
>
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
8
}}
>
删除禁用通道
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
<
Button
theme
=
"light"
type
=
"primary"
style
=
{{
marginRight
:
8
}}
onClick
=
{
refresh
}
>
刷新
<
/Button
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{
refresh
}
>
刷新
<
/Button
>
<
/Space
>
<
/Space
>
{
/*<div style={{width: '100%', pointerEvents: 'none', position: 'absolute'}}>*/
}
{
/*<div style={{width: '100%', pointerEvents: 'none', position: 'absolute'}}>*/
}
...
@@ -696,28 +816,41 @@ const ChannelsTable = () => {
...
@@ -696,28 +816,41 @@ const ChannelsTable = () => {
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Space
>
<
Space
>
<
Typography
.
Text
strong
>
开启批量删除
<
/Typography.Text
>
<
Typography
.
Text
strong
>
开启批量删除
<
/Typography.Text
>
<
Switch
label
=
"开启批量删除"
uncheckedText
=
"关"
aria
-
label
=
"是否开启批量删除"
onChange
=
{(
v
)
=>
{
<
Switch
label
=
'开启批量删除'
uncheckedText
=
'关'
aria
-
label
=
'是否开启批量删除'
onChange
=
{(
v
)
=>
{
setEnableBatchDelete
(
v
);
setEnableBatchDelete
(
v
);
}}
><
/Switch
>
}}
><
/Switch
>
<
Popconfirm
<
Popconfirm
title
=
"确定是否要删除所选通道?"
title
=
'确定是否要删除所选通道?'
content
=
"此修改将不可逆"
content
=
'此修改将不可逆'
okType
=
{
'danger'
}
okType
=
{
'danger'
}
onConfirm
=
{
batchDeleteChannels
}
onConfirm
=
{
batchDeleteChannels
}
disabled
=
{
!
enableBatchDelete
}
disabled
=
{
!
enableBatchDelete
}
position
=
{
'top'
}
position
=
{
'top'
}
>
>
<
Button
disabled
=
{
!
enableBatchDelete
}
theme
=
"light"
type
=
"danger"
<
Button
style
=
{{
marginRight
:
8
}}
>
删除所选通道
<
/Button
>
disabled
=
{
!
enableBatchDelete
}
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
8
}}
>
删除所选通道
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
<
Popconfirm
<
Popconfirm
title
=
"确定是否要修复数据库一致性?"
title
=
'确定是否要修复数据库一致性?'
content
=
"进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用"
content
=
'进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用'
okType
=
{
'warning'
}
okType
=
{
'warning'
}
onConfirm
=
{
fixChannelsAbilities
}
onConfirm
=
{
fixChannelsAbilities
}
position
=
{
'top'
}
position
=
{
'top'
}
>
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
8
}}
>
修复数据库一致性
<
/Button
>
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
8
}}
>
修复数据库一致性
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
...
...
web/src/components/Footer.js
View file @
eb955ae4
...
@@ -32,27 +32,36 @@ const Footer = () => {
...
@@ -32,27 +32,36 @@ const Footer = () => {
<
Layout
.
Content
style
=
{{
textAlign
:
'center'
}}
>
<
Layout
.
Content
style
=
{{
textAlign
:
'center'
}}
>
{
footer
?
(
{
footer
?
(
<
div
<
div
className
=
"custom-footer"
className
=
'custom-footer'
dangerouslySetInnerHTML
=
{{
__html
:
footer
}}
dangerouslySetInnerHTML
=
{{
__html
:
footer
}}
><
/div
>
><
/div
>
)
:
(
)
:
(
<
div
className
=
"custom-footer"
>
<
div
className
=
'custom-footer'
>
<
a
<
a
href
=
"https://github.com/Calcium-Ion/new-api"
href
=
'https://github.com/Calcium-Ion/new-api'
target
=
"_blank"
rel
=
"noreferrer"
target
=
'_blank'
rel
=
'noreferrer'
>
>
New
API
{
import
.
meta
.
env
.
VITE_REACT_APP_VERSION
}{
' '
}
New
API
{
import
.
meta
.
env
.
VITE_REACT_APP_VERSION
}{
' '
}
<
/a
>
<
/a
>
由
{
' '
}
由
{
' '
}
<
a
href
=
"https://github.com/Calcium-Ion"
target
=
"_blank"
rel
=
"noreferrer"
>
<
a
href
=
'https://github.com/Calcium-Ion'
target
=
'_blank'
rel
=
'noreferrer'
>
Calcium
-
Ion
Calcium
-
Ion
<
/a>{' '
}
<
/a>{' '
}
开发,基于
{
' '
}
开发,基于
{
' '
}
<
a
href
=
"https://github.com/songquanpeng/one-api"
target
=
"_blank"
rel
=
"noreferrer"
>
<
a
href
=
'https://github.com/songquanpeng/one-api'
target
=
'_blank'
rel
=
'noreferrer'
>
One
API
v0
.
5.4
One
API
v0
.
5.4
<
/a>{' '
}
<
/a>{' '
}
,本项目根据
{
' '
}
,本项目根据
{
' '
}
<
a
href
=
"https://opensource.org/licenses/mit-license.php"
>
<
a
href
=
'https://opensource.org/licenses/mit-license.php'
>
MIT
许可证
MIT
许可证
<
/a>{' '
}
<
/a>{' '
}
授权
授权
...
...
web/src/components/GitHubOAuth.js
View file @
eb955ae4
...
@@ -49,7 +49,7 @@ const GitHubOAuth = () => {
...
@@ -49,7 +49,7 @@ const GitHubOAuth = () => {
return
(
return
(
<
Segment
style
=
{{
minHeight
:
'300px'
}}
>
<
Segment
style
=
{{
minHeight
:
'300px'
}}
>
<
Dimmer
active
inverted
>
<
Dimmer
active
inverted
>
<
Loader
size
=
"large"
>
{
prompt
}
<
/Loader
>
<
Loader
size
=
'large'
>
{
prompt
}
<
/Loader
>
<
/Dimmer
>
<
/Dimmer
>
<
/Segment
>
<
/Segment
>
);
);
...
...
web/src/components/HeaderBar.js
View file @
eb955ae4
...
@@ -17,15 +17,15 @@ let headerButtons = [
...
@@ -17,15 +17,15 @@ let headerButtons = [
text
:
'关于'
,
text
:
'关于'
,
itemKey
:
'about'
,
itemKey
:
'about'
,
to
:
'/about'
,
to
:
'/about'
,
icon
:
<
IconHelpCircle
/>
icon
:
<
IconHelpCircle
/>
,
}
}
,
];
];
if
(
localStorage
.
getItem
(
'chat_link'
))
{
if
(
localStorage
.
getItem
(
'chat_link'
))
{
headerButtons
.
splice
(
1
,
0
,
{
headerButtons
.
splice
(
1
,
0
,
{
name
:
'聊天'
,
name
:
'聊天'
,
to
:
'/chat'
,
to
:
'/chat'
,
icon
:
'comments'
icon
:
'comments'
,
});
});
}
}
...
@@ -40,7 +40,11 @@ const HeaderBar = () => {
...
@@ -40,7 +40,11 @@ const HeaderBar = () => {
var
themeMode
=
localStorage
.
getItem
(
'theme-mode'
);
var
themeMode
=
localStorage
.
getItem
(
'theme-mode'
);
const
currentDate
=
new
Date
();
const
currentDate
=
new
Date
();
// enable fireworks on new year(1.1 and 2.9-2.24)
// enable fireworks on new year(1.1 and 2.9-2.24)
const
isNewYear
=
(
currentDate
.
getMonth
()
===
0
&&
currentDate
.
getDate
()
===
1
)
||
(
currentDate
.
getMonth
()
===
1
&&
currentDate
.
getDate
()
>=
9
&&
currentDate
.
getDate
()
<=
24
);
const
isNewYear
=
(
currentDate
.
getMonth
()
===
0
&&
currentDate
.
getDate
()
===
1
)
||
(
currentDate
.
getMonth
()
===
1
&&
currentDate
.
getDate
()
>=
9
&&
currentDate
.
getDate
()
<=
24
);
async
function
logout
()
{
async
function
logout
()
{
setShowSidebar
(
false
);
setShowSidebar
(
false
);
...
@@ -93,7 +97,7 @@ const HeaderBar = () => {
...
@@ -93,7 +97,7 @@ const HeaderBar = () => {
const
routerMap
=
{
const
routerMap
=
{
about
:
'/about'
,
about
:
'/about'
,
login
:
'/login'
,
login
:
'/login'
,
register
:
'/register'
register
:
'/register'
,
};
};
return
(
return
(
<
Link
<
Link
...
@@ -106,52 +110,69 @@ const HeaderBar = () => {
...
@@ -106,52 +110,69 @@ const HeaderBar = () => {
}}
}}
selectedKeys
=
{[]}
selectedKeys
=
{[]}
// items={headerButtons}
// items={headerButtons}
onSelect
=
{
key
=>
{
onSelect
=
{(
key
)
=>
{}}
}}
footer
=
{
footer
=
{
<>
<>
{
isNewYear
&&
{
isNewYear
&&
(
// happy new year
// happy new year
<
Dropdown
<
Dropdown
position
=
"bottomRight"
position
=
'bottomRight'
render
=
{
render
=
{
<
Dropdown
.
Menu
>
<
Dropdown
.
Menu
>
<
Dropdown
.
Item
onClick
=
{
handleNewYearClick
}
>
Happy
New
Year
!!!<
/Dropdown.Item
>
<
Dropdown
.
Item
onClick
=
{
handleNewYearClick
}
>
Happy
New
Year
!!!
<
/Dropdown.Item
>
<
/Dropdown.Menu
>
<
/Dropdown.Menu
>
}
}
>
>
<
Nav
.
Item
itemKey
=
{
'new-year'
}
text
=
{
'🏮'
}
/
>
<
Nav
.
Item
itemKey
=
{
'new-year'
}
text
=
{
'🏮'
}
/
>
<
/Dropdown
>
<
/Dropdown
>
}
)
}
<
Nav
.
Item
itemKey
=
{
'about'
}
icon
=
{
<
IconHelpCircle
/>
}
/
>
<
Nav
.
Item
itemKey
=
{
'about'
}
icon
=
{
<
IconHelpCircle
/>
}
/
>
<
Switch
checkedText
=
"🌞"
size
=
{
'large'
}
checked
=
{
dark
}
uncheckedText
=
"🌙"
onChange
=
{
switchMode
}
/
>
<
Switch
{
userState
.
user
?
checkedText
=
'🌞'
size
=
{
'large'
}
checked
=
{
dark
}
uncheckedText
=
'🌙'
onChange
=
{
switchMode
}
/
>
{
userState
.
user
?
(
<>
<>
<
Dropdown
<
Dropdown
position
=
"bottomRight"
position
=
'bottomRight'
render
=
{
render
=
{
<
Dropdown
.
Menu
>
<
Dropdown
.
Menu
>
<
Dropdown
.
Item
onClick
=
{
logout
}
>
退出
<
/Dropdown.Item
>
<
Dropdown
.
Item
onClick
=
{
logout
}
>
退出
<
/Dropdown.Item
>
<
/Dropdown.Menu
>
<
/Dropdown.Menu
>
}
}
>
>
<
Avatar
size
=
"small"
color
=
{
stringToColor
(
userState
.
user
.
username
)}
style
=
{{
margin
:
4
}}
>
<
Avatar
size
=
'small'
color
=
{
stringToColor
(
userState
.
user
.
username
)}
style
=
{{
margin
:
4
}}
>
{
userState
.
user
.
username
[
0
]}
{
userState
.
user
.
username
[
0
]}
<
/Avatar
>
<
/Avatar
>
<
span
>
{
userState
.
user
.
username
}
<
/span
>
<
span
>
{
userState
.
user
.
username
}
<
/span
>
<
/Dropdown
>
<
/Dropdown
>
<
/
>
<
/
>
:
)
:
(
<>
<>
<
Nav
.
Item
itemKey
=
{
'login'
}
text
=
{
'登录'
}
icon
=
{
<
IconKey
/>
}
/
>
<
Nav
.
Item
<
Nav
.
Item
itemKey
=
{
'register'
}
text
=
{
'注册'
}
icon
=
{
<
IconUser
/>
}
/
>
itemKey
=
{
'login'
}
text
=
{
'登录'
}
icon
=
{
<
IconKey
/>
}
/
>
<
Nav
.
Item
itemKey
=
{
'register'
}
text
=
{
'注册'
}
icon
=
{
<
IconUser
/>
}
/
>
<
/
>
<
/
>
}
)
}
<
/
>
<
/
>
}
}
>
><
/Nav
>
<
/Nav
>
<
/div
>
<
/div
>
<
/Layout
>
<
/Layout
>
<
/
>
<
/
>
...
...
web/src/components/Loading.js
View file @
eb955ae4
...
@@ -3,7 +3,9 @@ import { Spin } from '@douyinfe/semi-ui';
...
@@ -3,7 +3,9 @@ import { Spin } from '@douyinfe/semi-ui';
const
Loading
=
({
prompt
:
name
=
'page'
})
=>
{
const
Loading
=
({
prompt
:
name
=
'page'
})
=>
{
return
(
return
(
<
Spin
style
=
{{
height
:
100
}}
spinning
=
{
true
}
>
加载
{
name
}
中
...
<
/Spin
>
<
Spin
style
=
{{
height
:
100
}}
spinning
=
{
true
}
>
加载
{
name
}
中
...
<
/Spin
>
);
);
};
};
...
...
web/src/components/LoginForm.js
View file @
eb955ae4
...
@@ -4,7 +4,15 @@ import { UserContext } from '../context/User';
...
@@ -4,7 +4,15 @@ import { UserContext } from '../context/User';
import
{
API
,
getLogo
,
showError
,
showInfo
,
showSuccess
}
from
'../helpers'
;
import
{
API
,
getLogo
,
showError
,
showInfo
,
showSuccess
}
from
'../helpers'
;
import
{
onGitHubOAuthClicked
}
from
'./utils'
;
import
{
onGitHubOAuthClicked
}
from
'./utils'
;
import
Turnstile
from
'react-turnstile'
;
import
Turnstile
from
'react-turnstile'
;
import
{
Button
,
Card
,
Divider
,
Form
,
Icon
,
Layout
,
Modal
}
from
'@douyinfe/semi-ui'
;
import
{
Button
,
Card
,
Divider
,
Form
,
Icon
,
Layout
,
Modal
,
}
from
'@douyinfe/semi-ui'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
Text
from
'@douyinfe/semi-ui/lib/es/typography/text'
;
import
Text
from
'@douyinfe/semi-ui/lib/es/typography/text'
;
import
TelegramLoginButton
from
'react-telegram-login'
;
import
TelegramLoginButton
from
'react-telegram-login'
;
...
@@ -16,7 +24,7 @@ const LoginForm = () => {
...
@@ -16,7 +24,7 @@ const LoginForm = () => {
const
[
inputs
,
setInputs
]
=
useState
({
const
[
inputs
,
setInputs
]
=
useState
({
username
:
''
,
username
:
''
,
password
:
''
,
password
:
''
,
wechat_verification_code
:
''
wechat_verification_code
:
''
,
});
});
const
[
searchParams
,
setSearchParams
]
=
useSearchParams
();
const
[
searchParams
,
setSearchParams
]
=
useSearchParams
();
const
[
submitted
,
setSubmitted
]
=
useState
(
false
);
const
[
submitted
,
setSubmitted
]
=
useState
(
false
);
...
@@ -56,7 +64,7 @@ const LoginForm = () => {
...
@@ -56,7 +64,7 @@ const LoginForm = () => {
return
;
return
;
}
}
const
res
=
await
API
.
get
(
const
res
=
await
API
.
get
(
`/api/oauth/wechat?code=
${
inputs
.
wechat_verification_code
}
`
`/api/oauth/wechat?code=
${
inputs
.
wechat_verification_code
}
`
,
);
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -81,17 +89,24 @@ const LoginForm = () => {
...
@@ -81,17 +89,24 @@ const LoginForm = () => {
}
}
setSubmitted
(
true
);
setSubmitted
(
true
);
if
(
username
&&
password
)
{
if
(
username
&&
password
)
{
const
res
=
await
API
.
post
(
`/api/user/login?turnstile=
${
turnstileToken
}
`
,
{
const
res
=
await
API
.
post
(
`/api/user/login?turnstile=
${
turnstileToken
}
`
,
{
username
,
username
,
password
password
,
});
},
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
userDispatch
({
type
:
'login'
,
payload
:
data
});
userDispatch
({
type
:
'login'
,
payload
:
data
});
localStorage
.
setItem
(
'user'
,
JSON
.
stringify
(
data
));
localStorage
.
setItem
(
'user'
,
JSON
.
stringify
(
data
));
showSuccess
(
'登录成功!'
);
showSuccess
(
'登录成功!'
);
if
(
username
===
'root'
&&
password
===
'123456'
)
{
if
(
username
===
'root'
&&
password
===
'123456'
)
{
Modal
.
error
({
title
:
'您正在使用默认密码!'
,
content
:
'请立刻修改默认密码!'
,
centered
:
true
});
Modal
.
error
({
title
:
'您正在使用默认密码!'
,
content
:
'请立刻修改默认密码!'
,
centered
:
true
,
});
}
}
navigate
(
'/token'
);
navigate
(
'/token'
);
}
else
{
}
else
{
...
@@ -104,7 +119,16 @@ const LoginForm = () => {
...
@@ -104,7 +119,16 @@ const LoginForm = () => {
// 添加Telegram登录处理函数
// 添加Telegram登录处理函数
const
onTelegramLoginClicked
=
async
(
response
)
=>
{
const
onTelegramLoginClicked
=
async
(
response
)
=>
{
const
fields
=
[
'id'
,
'first_name'
,
'last_name'
,
'username'
,
'photo_url'
,
'auth_date'
,
'hash'
,
'lang'
];
const
fields
=
[
'id'
,
'first_name'
,
'last_name'
,
'username'
,
'photo_url'
,
'auth_date'
,
'hash'
,
'lang'
,
];
const
params
=
{};
const
params
=
{};
fields
.
forEach
((
field
)
=>
{
fields
.
forEach
((
field
)
=>
{
if
(
response
[
field
])
{
if
(
response
[
field
])
{
...
@@ -126,10 +150,15 @@ const LoginForm = () => {
...
@@ -126,10 +150,15 @@ const LoginForm = () => {
return
(
return
(
<
div
>
<
div
>
<
Layout
>
<
Layout
>
<
Layout
.
Header
>
<
Layout
.
Header
><
/Layout.Header
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
div
style
=
{{
justifyContent
:
'center'
,
display
:
'flex'
,
marginTop
:
120
}}
>
<
div
style
=
{{
justifyContent
:
'center'
,
display
:
'flex'
,
marginTop
:
120
,
}}
>
<
div
style
=
{{
width
:
500
}}
>
<
div
style
=
{{
width
:
500
}}
>
<
Card
>
<
Card
>
<
Title
heading
=
{
2
}
style
=
{{
textAlign
:
'center'
}}
>
<
Title
heading
=
{
2
}
style
=
{{
textAlign
:
'center'
}}
>
...
@@ -139,50 +168,72 @@ const LoginForm = () => {
...
@@ -139,50 +168,72 @@ const LoginForm = () => {
<
Form
.
Input
<
Form
.
Input
field
=
{
'username'
}
field
=
{
'username'
}
label
=
{
'用户名'
}
label
=
{
'用户名'
}
placeholder
=
"用户名"
placeholder
=
'用户名'
name
=
"username"
name
=
'username'
onChange
=
{(
value
)
=>
handleChange
(
'username'
,
value
)}
onChange
=
{(
value
)
=>
handleChange
(
'username'
,
value
)}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
field
=
{
'password'
}
field
=
{
'password'
}
label
=
{
'密码'
}
label
=
{
'密码'
}
placeholder
=
"密码"
placeholder
=
'密码'
name
=
"password"
name
=
'password'
type
=
"password"
type
=
'password'
onChange
=
{(
value
)
=>
handleChange
(
'password'
,
value
)}
onChange
=
{(
value
)
=>
handleChange
(
'password'
,
value
)}
/
>
/
>
<
Button
theme
=
"solid"
style
=
{{
width
:
'100%'
}}
type
=
{
'primary'
}
size
=
"large"
<
Button
htmlType
=
{
'submit'
}
onClick
=
{
handleSubmit
}
>
theme
=
'solid'
style
=
{{
width
:
'100%'
}}
type
=
{
'primary'
}
size
=
'large'
htmlType
=
{
'submit'
}
onClick
=
{
handleSubmit
}
>
登录
登录
<
/Button
>
<
/Button
>
<
/Form
>
<
/Form
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'space-between'
,
marginTop
:
20
}}
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'space-between'
,
marginTop
:
20
,
}}
>
<
Text
>
<
Text
>
没有账号请先
<
Link
to
=
"/register"
>
注册账号
<
/Link
>
没有账号请先
<
Link
to
=
'/register'
>
注册账号
<
/Link
>
<
/Text
>
<
/Text
>
<
Text
>
<
Text
>
忘记密码
<
Link
to
=
"/reset"
>
点击重置
<
/Link
>
忘记密码
<
Link
to
=
'/reset'
>
点击重置
<
/Link
>
<
/Text
>
<
/Text
>
<
/div
>
<
/div
>
{
status
.
github_oauth
||
status
.
wechat_login
||
status
.
telegram_oauth
?
(
{
status
.
github_oauth
||
status
.
wechat_login
||
status
.
telegram_oauth
?
(
<>
<>
<
Divider
margin
=
"12px"
align
=
"center"
>
<
Divider
margin
=
'12px'
align
=
'center'
>
第三方登录
第三方登录
<
/Divider
>
<
/Divider
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'center'
,
marginTop
:
20
}}
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'center'
,
marginTop
:
20
,
}}
>
{
status
.
github_oauth
?
(
{
status
.
github_oauth
?
(
<
Button
<
Button
type
=
"primary"
type
=
'primary'
icon
=
{
<
IconGithubLogo
/>
}
icon
=
{
<
IconGithubLogo
/>
}
onClick
=
{()
=>
onGitHubOAuthClicked
(
status
.
github_client_id
)}
onClick
=
{()
=>
onGitHubOAuthClicked
(
status
.
github_client_id
)
}
/
>
/
>
)
:
(
)
:
(
<><
/
>
<><
/
>
)}
)}
{
status
.
wechat_login
?
(
{
status
.
wechat_login
?
(
<
Button
<
Button
type
=
"primary"
type
=
'primary'
style
=
{{
color
:
'rgba(var(--semi-green-5), 1)'
}}
style
=
{{
color
:
'rgba(var(--semi-green-5), 1)'
}}
icon
=
{
<
Icon
svg
=
{
<
WeChatIcon
/>
}
/>
}
icon
=
{
<
Icon
svg
=
{
<
WeChatIcon
/>
}
/>
}
onClick
=
{
onWeChatLoginClicked
}
onClick
=
{
onWeChatLoginClicked
}
...
@@ -192,7 +243,10 @@ const LoginForm = () => {
...
@@ -192,7 +243,10 @@ const LoginForm = () => {
)}
)}
{
status
.
telegram_oauth
?
(
{
status
.
telegram_oauth
?
(
<
TelegramLoginButton
dataOnauth
=
{
onTelegramLoginClicked
}
botName
=
{
status
.
telegram_bot_name
}
/
>
<
TelegramLoginButton
dataOnauth
=
{
onTelegramLoginClicked
}
botName
=
{
status
.
telegram_bot_name
}
/
>
)
:
(
)
:
(
<><
/
>
<><
/
>
)}
)}
...
@@ -202,7 +256,7 @@ const LoginForm = () => {
...
@@ -202,7 +256,7 @@ const LoginForm = () => {
<><
/
>
<><
/
>
)}
)}
<
Modal
<
Modal
title
=
"微信扫码登录"
title
=
'微信扫码登录'
visible
=
{
showWeChatLoginModal
}
visible
=
{
showWeChatLoginModal
}
maskClosable
=
{
true
}
maskClosable
=
{
true
}
onOk
=
{
onSubmitWeChatVerificationCode
}
onOk
=
{
onSubmitWeChatVerificationCode
}
...
@@ -211,7 +265,13 @@ const LoginForm = () => {
...
@@ -211,7 +265,13 @@ const LoginForm = () => {
size
=
{
'small'
}
size
=
{
'small'
}
centered
=
{
true
}
centered
=
{
true
}
>
>
<
div
style
=
{{
display
:
'flex'
,
alignItem
:
'center'
,
flexDirection
:
'column'
}}
>
<
div
style
=
{{
display
:
'flex'
,
alignItem
:
'center'
,
flexDirection
:
'column'
,
}}
>
<
img
src
=
{
status
.
wechat_qrcode
}
/
>
<
img
src
=
{
status
.
wechat_qrcode
}
/
>
<
/div
>
<
/div
>
<
div
style
=
{{
textAlign
:
'center'
}}
>
<
div
style
=
{{
textAlign
:
'center'
}}
>
...
@@ -219,19 +279,27 @@ const LoginForm = () => {
...
@@ -219,19 +279,27 @@ const LoginForm = () => {
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
<
/p
>
<
/p
>
<
/div
>
<
/div
>
<
Form
size
=
"large"
>
<
Form
size
=
'large'
>
<
Form
.
Input
<
Form
.
Input
field
=
{
'wechat_verification_code'
}
field
=
{
'wechat_verification_code'
}
placeholder
=
"验证码"
placeholder
=
'验证码'
label
=
{
'验证码'
}
label
=
{
'验证码'
}
value
=
{
inputs
.
wechat_verification_code
}
value
=
{
inputs
.
wechat_verification_code
}
onChange
=
{(
value
)
=>
handleChange
(
'wechat_verification_code'
,
value
)}
onChange
=
{(
value
)
=>
handleChange
(
'wechat_verification_code'
,
value
)
}
/
>
/
>
<
/Form
>
<
/Form
>
<
/Modal
>
<
/Modal
>
<
/Card
>
<
/Card
>
{
turnstileEnabled
?
(
{
turnstileEnabled
?
(
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'center'
,
marginTop
:
20
}}
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'center'
,
marginTop
:
20
,
}}
>
<
Turnstile
<
Turnstile
sitekey
=
{
turnstileSiteKey
}
sitekey
=
{
turnstileSiteKey
}
onVerify
=
{(
token
)
=>
{
onVerify
=
{(
token
)
=>
{
...
@@ -244,7 +312,6 @@ const LoginForm = () => {
...
@@ -244,7 +312,6 @@ const LoginForm = () => {
)}
)}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/Layout.Content
>
<
/Layout.Content
>
<
/Layout
>
<
/Layout
>
<
/div
>
<
/div
>
...
...
web/src/components/LogsTable.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
API
,
copy
,
isAdmin
,
showError
,
showSuccess
,
timestamp2string
}
from
'../helpers'
;
import
{
API
,
copy
,
isAdmin
,
showError
,
showSuccess
,
timestamp2string
,
}
from
'../helpers'
;
import
{
Avatar
,
Button
,
Form
,
Layout
,
Modal
,
Select
,
Space
,
Spin
,
Table
,
Tag
}
from
'@douyinfe/semi-ui'
;
import
{
Avatar
,
Button
,
Form
,
Layout
,
Modal
,
Select
,
Space
,
Spin
,
Table
,
Tag
,
}
from
'@douyinfe/semi-ui'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderNumber
,
renderQuota
,
stringToColor
}
from
'../helpers/render'
;
import
{
renderNumber
,
renderQuota
,
stringToColor
}
from
'../helpers/render'
;
import
Paragraph
from
'@douyinfe/semi-ui/lib/es/typography/paragraph'
;
import
Paragraph
from
'@douyinfe/semi-ui/lib/es/typography/paragraph'
;
...
@@ -9,131 +27,285 @@ import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
...
@@ -9,131 +27,285 @@ import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
const
{
Header
}
=
Layout
;
const
{
Header
}
=
Layout
;
function
renderTimestamp
(
timestamp
)
{
function
renderTimestamp
(
timestamp
)
{
return
(
<>
return
<>
{
timestamp2string
(
timestamp
)}
<
/>
;
{
timestamp2string
(
timestamp
)}
<
/>
)
;
}
}
const
MODE_OPTIONS
=
[{
key
:
'all'
,
text
:
'全部用户'
,
value
:
'all'
},
{
key
:
'self'
,
text
:
'当前用户'
,
value
:
'self'
}];
const
MODE_OPTIONS
=
[
{
key
:
'all'
,
text
:
'全部用户'
,
value
:
'all'
},
{
key
:
'self'
,
text
:
'当前用户'
,
value
:
'self'
},
];
const
colors
=
[
'amber'
,
'blue'
,
'cyan'
,
'green'
,
'grey'
,
'indigo'
,
'light-blue'
,
'lime'
,
'orange'
,
'pink'
,
'purple'
,
'red'
,
'teal'
,
'violet'
,
'yellow'
];
const
colors
=
[
'amber'
,
'blue'
,
'cyan'
,
'green'
,
'grey'
,
'indigo'
,
'light-blue'
,
'lime'
,
'orange'
,
'pink'
,
'purple'
,
'red'
,
'teal'
,
'violet'
,
'yellow'
,
];
function
renderType
(
type
)
{
function
renderType
(
type
)
{
switch
(
type
)
{
switch
(
type
)
{
case
1
:
case
1
:
return
<
Tag
color
=
"cyan"
size
=
"large"
>
充值
<
/Tag>
;
return
(
<
Tag
color
=
'cyan'
size
=
'large'
>
{
' '
}
充值
{
' '
}
<
/Tag
>
);
case
2
:
case
2
:
return
<
Tag
color
=
"lime"
size
=
"large"
>
消费
<
/Tag>
;
return
(
<
Tag
color
=
'lime'
size
=
'large'
>
{
' '
}
消费
{
' '
}
<
/Tag
>
);
case
3
:
case
3
:
return
<
Tag
color
=
"orange"
size
=
"large"
>
管理
<
/Tag>
;
return
(
<
Tag
color
=
'orange'
size
=
'large'
>
{
' '
}
管理
{
' '
}
<
/Tag
>
);
case
4
:
case
4
:
return
<
Tag
color
=
"purple"
size
=
"large"
>
系统
<
/Tag>
;
return
(
<
Tag
color
=
'purple'
size
=
'large'
>
{
' '
}
系统
{
' '
}
<
/Tag
>
);
default
:
default
:
return
<
Tag
color
=
"black"
size
=
"large"
>
未知
<
/Tag>
;
return
(
<
Tag
color
=
'black'
size
=
'large'
>
{
' '
}
未知
{
' '
}
<
/Tag
>
);
}
}
}
}
function
renderIsStream
(
bool
)
{
function
renderIsStream
(
bool
)
{
if
(
bool
)
{
if
(
bool
)
{
return
<
Tag
color
=
"blue"
size
=
"large"
>
流
<
/Tag>
;
return
(
<
Tag
color
=
'blue'
size
=
'large'
>
流
<
/Tag
>
);
}
else
{
}
else
{
return
<
Tag
color
=
"purple"
size
=
"large"
>
非流
<
/Tag>
;
return
(
<
Tag
color
=
'purple'
size
=
'large'
>
非流
<
/Tag
>
);
}
}
}
}
function
renderUseTime
(
type
)
{
function
renderUseTime
(
type
)
{
const
time
=
parseInt
(
type
);
const
time
=
parseInt
(
type
);
if
(
time
<
101
)
{
if
(
time
<
101
)
{
return
<
Tag
color
=
"green"
size
=
"large"
>
{
time
}
s
<
/Tag>
;
return
(
<
Tag
color
=
'green'
size
=
'large'
>
{
' '
}
{
time
}
s
{
' '
}
<
/Tag
>
);
}
else
if
(
time
<
300
)
{
}
else
if
(
time
<
300
)
{
return
<
Tag
color
=
"orange"
size
=
"large"
>
{
time
}
s
<
/Tag>
;
return
(
<
Tag
color
=
'orange'
size
=
'large'
>
{
' '
}
{
time
}
s
{
' '
}
<
/Tag
>
);
}
else
{
}
else
{
return
<
Tag
color
=
"red"
size
=
"large"
>
{
time
}
s
<
/Tag>
;
return
(
<
Tag
color
=
'red'
size
=
'large'
>
{
' '
}
{
time
}
s
{
' '
}
<
/Tag
>
);
}
}
}
}
const
LogsTable
=
()
=>
{
const
LogsTable
=
()
=>
{
const
columns
=
[{
const
columns
=
[
title
:
'时间'
,
dataIndex
:
'timestamp2string'
{
},
{
title
:
'时间'
,
dataIndex
:
'timestamp2string'
,
},
{
title
:
'渠道'
,
title
:
'渠道'
,
dataIndex
:
'channel'
,
dataIndex
:
'channel'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
isAdminUser
?
record
.
type
===
0
||
record
.
type
===
2
?
<
div
>
return
isAdminUser
?
(
{
<
Tag
color
=
{
colors
[
parseInt
(
text
)
%
colors
.
length
]}
size
=
"large"
>
{
text
}
<
/Tag>
}
record
.
type
===
0
||
record
.
type
===
2
?
(
<
/div> : <></
>
:
<><
/>
)
;
<
div
>
{
<
Tag
color
=
{
colors
[
parseInt
(
text
)
%
colors
.
length
]}
size
=
'large'
>
{
' '
}
{
text
}{
' '
}
<
/Tag
>
}
}
},
{
<
/div
>
)
:
(
<><
/
>
)
)
:
(
<><
/
>
);
},
},
{
title
:
'用户'
,
title
:
'用户'
,
dataIndex
:
'username'
,
dataIndex
:
'username'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
isAdminUser
?
<
div
>
return
isAdminUser
?
(
<
Avatar
size
=
"small"
color
=
{
stringToColor
(
text
)}
style
=
{{
marginRight
:
4
}}
<
div
>
onClick
=
{()
=>
showUserInfo
(
record
.
user_id
)}
>
<
Avatar
size
=
'small'
color
=
{
stringToColor
(
text
)}
style
=
{{
marginRight
:
4
}}
onClick
=
{()
=>
showUserInfo
(
record
.
user_id
)}
>
{
typeof
text
===
'string'
&&
text
.
slice
(
0
,
1
)}
{
typeof
text
===
'string'
&&
text
.
slice
(
0
,
1
)}
<
/Avatar
>
<
/Avatar
>
{
text
}
{
text
}
<
/div> : <></
>
);
<
/div
>
}
)
:
(
},
{
<><
/
>
title
:
'令牌'
,
dataIndex
:
'token_name'
,
render
:
(
text
,
record
,
index
)
=>
{
);
return
(
record
.
type
===
0
||
record
.
type
===
2
?
<
div
>
},
<
Tag
color
=
"grey"
size
=
"large"
onClick
=
{()
=>
{
},
{
title
:
'令牌'
,
dataIndex
:
'token_name'
,
render
:
(
text
,
record
,
index
)
=>
{
return
record
.
type
===
0
||
record
.
type
===
2
?
(
<
div
>
<
Tag
color
=
'grey'
size
=
'large'
onClick
=
{()
=>
{
copyText
(
text
);
copyText
(
text
);
}}
>
{
text
}
<
/Tag
>
}}
<
/div> : <></
>
);
>
}
{
' '
}
},
{
{
text
}{
' '
}
title
:
'类型'
,
dataIndex
:
'type'
,
render
:
(
text
,
record
,
index
)
=>
{
<
/Tag
>
return
(
<
div
>
<
/div
>
{
renderType
(
text
)}
)
:
(
<
/div>
)
;
<><
/
>
}
);
},
{
},
title
:
'模型'
,
dataIndex
:
'model_name'
,
render
:
(
text
,
record
,
index
)
=>
{
},
return
(
record
.
type
===
0
||
record
.
type
===
2
?
<
div
>
{
<
Tag
color
=
{
stringToColor
(
text
)}
size
=
"large"
onClick
=
{()
=>
{
title
:
'类型'
,
dataIndex
:
'type'
,
render
:
(
text
,
record
,
index
)
=>
{
return
<
div
>
{
renderType
(
text
)}
<
/div>
;
},
},
{
title
:
'模型'
,
dataIndex
:
'model_name'
,
render
:
(
text
,
record
,
index
)
=>
{
return
record
.
type
===
0
||
record
.
type
===
2
?
(
<
div
>
<
Tag
color
=
{
stringToColor
(
text
)}
size
=
'large'
onClick
=
{()
=>
{
copyText
(
text
);
copyText
(
text
);
}}
>
{
text
}
<
/Tag
>
}}
<
/div> : <></
>
);
>
}
{
' '
}
},
{
{
text
}{
' '
}
title
:
'用时'
,
dataIndex
:
'use_time'
,
render
:
(
text
,
record
,
index
)
=>
{
<
/Tag
>
return
(
<
div
>
<
/div
>
)
:
(
<><
/
>
);
},
},
{
title
:
'用时'
,
dataIndex
:
'use_time'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
<
Space
>
<
Space
>
{
renderUseTime
(
text
)}
{
renderUseTime
(
text
)}
{
renderIsStream
(
record
.
is_stream
)}
{
renderIsStream
(
record
.
is_stream
)}
<
/Space
>
<
/Space
>
<
/div>
)
;
<
/div
>
}
);
},
{
},
title
:
'提示'
,
dataIndex
:
'prompt_tokens'
,
render
:
(
text
,
record
,
index
)
=>
{
},
return
(
record
.
type
===
0
||
record
.
type
===
2
?
<
div
>
{
{
<
span
>
{
text
}
<
/span>
}
title
:
'提示'
,
<
/div> : <></
>
);
dataIndex
:
'prompt_tokens'
,
}
render
:
(
text
,
record
,
index
)
=>
{
},
{
return
record
.
type
===
0
||
record
.
type
===
2
?
(
title
:
'补全'
,
dataIndex
:
'completion_tokens'
,
render
:
(
text
,
record
,
index
)
=>
{
<
div
>
{
<
span
>
{
text
}
<
/span>}</
div
>
return
(
parseInt
(
text
)
>
0
&&
(
record
.
type
===
0
||
record
.
type
===
2
)
?
<
div
>
)
:
(
{
<
span
>
{
text
}
<
/span>
}
<><
/
>
<
/div> : <></
>
);
);
}
},
},
{
},
title
:
'花费'
,
dataIndex
:
'quota'
,
render
:
(
text
,
record
,
index
)
=>
{
{
return
(
record
.
type
===
0
||
record
.
type
===
2
?
<
div
>
title
:
'补全'
,
{
renderQuota
(
text
,
6
)}
dataIndex
:
'completion_tokens'
,
<
/div> : <></
>
);
render
:
(
text
,
record
,
index
)
=>
{
}
return
parseInt
(
text
)
>
0
&&
},
{
(
record
.
type
===
0
||
record
.
type
===
2
)
?
(
title
:
'详情'
,
dataIndex
:
'content'
,
render
:
(
text
,
record
,
index
)
=>
{
<
div
>
{
<
span
>
{
text
}
<
/span>}</
div
>
return
<
Paragraph
ellipsis
=
{{
rows
:
2
,
showTooltip
:
{
type
:
'popover'
,
opts
:
{
style
:
{
width
:
240
}
}
}
}}
)
:
(
style
=
{{
maxWidth
:
240
}}
>
<><
/
>
);
},
},
{
title
:
'花费'
,
dataIndex
:
'quota'
,
render
:
(
text
,
record
,
index
)
=>
{
return
record
.
type
===
0
||
record
.
type
===
2
?
(
<
div
>
{
renderQuota
(
text
,
6
)}
<
/div
>
)
:
(
<><
/
>
);
},
},
{
title
:
'详情'
,
dataIndex
:
'content'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
Paragraph
ellipsis
=
{{
rows
:
2
,
showTooltip
:
{
type
:
'popover'
,
opts
:
{
style
:
{
width
:
240
}
}
},
}}
style
=
{{
maxWidth
:
240
}}
>
{
text
}
{
text
}
<
/Paragraph>
;
<
/Paragraph
>
}
);
}];
},
},
];
const
[
logs
,
setLogs
]
=
useState
([]);
const
[
logs
,
setLogs
]
=
useState
([]);
const
[
showStat
,
setShowStat
]
=
useState
(
false
);
const
[
showStat
,
setShowStat
]
=
useState
(
false
);
...
@@ -154,12 +326,20 @@ const LogsTable = () => {
...
@@ -154,12 +326,20 @@ const LogsTable = () => {
model_name
:
''
,
model_name
:
''
,
start_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
-
86400
),
start_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
-
86400
),
end_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
+
3600
),
end_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
+
3600
),
channel
:
''
channel
:
''
,
});
});
const
{
username
,
token_name
,
model_name
,
start_timestamp
,
end_timestamp
,
channel
}
=
inputs
;
const
{
username
,
token_name
,
model_name
,
start_timestamp
,
end_timestamp
,
channel
,
}
=
inputs
;
const
[
stat
,
setStat
]
=
useState
({
const
[
stat
,
setStat
]
=
useState
({
quota
:
0
,
token
:
0
quota
:
0
,
token
:
0
,
});
});
const
handleInputChange
=
(
value
,
name
)
=>
{
const
handleInputChange
=
(
value
,
name
)
=>
{
...
@@ -169,7 +349,9 @@ const LogsTable = () => {
...
@@ -169,7 +349,9 @@ const LogsTable = () => {
const
getLogSelfStat
=
async
()
=>
{
const
getLogSelfStat
=
async
()
=>
{
let
localStartTimestamp
=
Date
.
parse
(
start_timestamp
)
/
1000
;
let
localStartTimestamp
=
Date
.
parse
(
start_timestamp
)
/
1000
;
let
localEndTimestamp
=
Date
.
parse
(
end_timestamp
)
/
1000
;
let
localEndTimestamp
=
Date
.
parse
(
end_timestamp
)
/
1000
;
let
res
=
await
API
.
get
(
`/api/log/self/stat?type=
${
logType
}
&token_name=
${
token_name
}
&model_name=
${
model_name
}
&start_timestamp=
${
localStartTimestamp
}
&end_timestamp=
${
localEndTimestamp
}
`
);
let
res
=
await
API
.
get
(
`/api/log/self/stat?type=
${
logType
}
&token_name=
${
token_name
}
&model_name=
${
model_name
}
&start_timestamp=
${
localStartTimestamp
}
&end_timestamp=
${
localEndTimestamp
}
`
,
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
setStat
(
data
);
setStat
(
data
);
...
@@ -181,7 +363,9 @@ const LogsTable = () => {
...
@@ -181,7 +363,9 @@ const LogsTable = () => {
const
getLogStat
=
async
()
=>
{
const
getLogStat
=
async
()
=>
{
let
localStartTimestamp
=
Date
.
parse
(
start_timestamp
)
/
1000
;
let
localStartTimestamp
=
Date
.
parse
(
start_timestamp
)
/
1000
;
let
localEndTimestamp
=
Date
.
parse
(
end_timestamp
)
/
1000
;
let
localEndTimestamp
=
Date
.
parse
(
end_timestamp
)
/
1000
;
let
res
=
await
API
.
get
(
`/api/log/stat?type=
${
logType
}
&username=
${
username
}
&token_name=
${
token_name
}
&model_name=
${
model_name
}
&start_timestamp=
${
localStartTimestamp
}
&end_timestamp=
${
localEndTimestamp
}
&channel=
${
channel
}
`
);
let
res
=
await
API
.
get
(
`/api/log/stat?type=
${
logType
}
&username=
${
username
}
&token_name=
${
token_name
}
&model_name=
${
model_name
}
&start_timestamp=
${
localStartTimestamp
}
&end_timestamp=
${
localEndTimestamp
}
&channel=
${
channel
}
`
,
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
setStat
(
data
);
setStat
(
data
);
...
@@ -209,12 +393,16 @@ const LogsTable = () => {
...
@@ -209,12 +393,16 @@ const LogsTable = () => {
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
Modal
.
info
({
Modal
.
info
({
title
:
'用户信息'
,
content
:
<
div
style
=
{{
padding
:
12
}}
>
title
:
'用户信息'
,
content
:
(
<
div
style
=
{{
padding
:
12
}}
>
<
p
>
用户名
:
{
data
.
username
}
<
/p
>
<
p
>
用户名
:
{
data
.
username
}
<
/p
>
<
p
>
余额
:
{
renderQuota
(
data
.
quota
)}
<
/p
>
<
p
>
余额
:
{
renderQuota
(
data
.
quota
)}
<
/p
>
<
p
>
已用额度:
{
renderQuota
(
data
.
used_quota
)}
<
/p
>
<
p
>
已用额度:
{
renderQuota
(
data
.
used_quota
)}
<
/p
>
<
p
>
请求次数:
{
renderNumber
(
data
.
request_count
)}
<
/p
>
<
p
>
请求次数:
{
renderNumber
(
data
.
request_count
)}
<
/p
>
<
/div>, centered: tru
e
<
/div
>
),
centered
:
true
,
});
});
}
else
{
}
else
{
showError
(
message
);
showError
(
message
);
...
@@ -259,14 +447,16 @@ const LogsTable = () => {
...
@@ -259,14 +447,16 @@ const LogsTable = () => {
setLoading
(
false
);
setLoading
(
false
);
};
};
const
pageData
=
logs
.
slice
((
activePage
-
1
)
*
pageSize
,
activePage
*
pageSize
);
const
pageData
=
logs
.
slice
(
(
activePage
-
1
)
*
pageSize
,
activePage
*
pageSize
,
);
const
handlePageChange
=
page
=>
{
const
handlePageChange
=
(
page
)
=>
{
setActivePage
(
page
);
setActivePage
(
page
);
if
(
page
===
Math
.
ceil
(
logs
.
length
/
pageSize
)
+
1
)
{
if
(
page
===
Math
.
ceil
(
logs
.
length
/
pageSize
)
+
1
)
{
// In this case we have to load more data and then append them.
// In this case we have to load more data and then append them.
loadLogs
(
page
-
1
,
pageSize
,
logType
).
then
(
r
=>
{
loadLogs
(
page
-
1
,
pageSize
,
logType
).
then
((
r
)
=>
{});
});
}
}
};
};
...
@@ -298,7 +488,8 @@ const LogsTable = () => {
...
@@ -298,7 +488,8 @@ const LogsTable = () => {
useEffect
(()
=>
{
useEffect
(()
=>
{
// console.log('default effect')
// console.log('default effect')
const
localPageSize
=
parseInt
(
localStorage
.
getItem
(
'page-size'
))
||
ITEMS_PER_PAGE
;
const
localPageSize
=
parseInt
(
localStorage
.
getItem
(
'page-size'
))
||
ITEMS_PER_PAGE
;
setPageSize
(
localPageSize
);
setPageSize
(
localPageSize
);
loadLogs
(
0
,
localPageSize
)
loadLogs
(
0
,
localPageSize
)
.
then
()
.
then
()
...
@@ -326,52 +517,108 @@ const LogsTable = () => {
...
@@ -326,52 +517,108 @@ const LogsTable = () => {
setSearching
(
false
);
setSearching
(
false
);
};
};
return
(
<>
return
(
<>
<
Layout
>
<
Layout
>
<
Header
>
<
Header
>
<
Spin
spinning
=
{
loadingStat
}
>
<
Spin
spinning
=
{
loadingStat
}
>
<
h3
>
使用明细(总消耗额度:
<
h3
>
<
span
onClick
=
{
handleEyeClick
}
style
=
{{
使用明细(总消耗额度:
cursor
:
'pointer'
,
color
:
'gray'
<
span
}}
>
{
showStat
?
renderQuota
(
stat
.
quota
)
:
'点击查看'
}
<
/span
>
onClick
=
{
handleEyeClick
}
style
=
{{
cursor
:
'pointer'
,
color
:
'gray'
,
}}
>
{
showStat
?
renderQuota
(
stat
.
quota
)
:
'点击查看'
}
<
/span
>
)
)
<
/h3
>
<
/h3
>
<
/Spin
>
<
/Spin
>
<
/Header
>
<
/Header
>
<
Form
layout
=
"horizontal"
style
=
{{
marginTop
:
10
}}
>
<
Form
layout
=
'horizontal'
style
=
{{
marginTop
:
10
}}
>
<>
<>
<
Form
.
Input
field
=
"token_name"
label
=
"令牌名称"
style
=
{{
width
:
176
}}
value
=
{
token_name
}
<
Form
.
Input
placeholder
=
{
'可选值'
}
name
=
"token_name"
field
=
'token_name'
onChange
=
{
value
=>
handleInputChange
(
value
,
'token_name'
)}
/
>
label
=
'令牌名称'
<
Form
.
Input
field
=
"model_name"
label
=
"模型名称"
style
=
{{
width
:
176
}}
value
=
{
model_name
}
style
=
{{
width
:
176
}}
placeholder
=
"可选值"
value
=
{
token_name
}
name
=
"model_name"
placeholder
=
{
'可选值'
}
onChange
=
{
value
=>
handleInputChange
(
value
,
'model_name'
)}
/
>
name
=
'token_name'
<
Form
.
DatePicker
field
=
"start_timestamp"
label
=
"起始时间"
style
=
{{
width
:
272
}}
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'token_name'
)}
/
>
<
Form
.
Input
field
=
'model_name'
label
=
'模型名称'
style
=
{{
width
:
176
}}
value
=
{
model_name
}
placeholder
=
'可选值'
name
=
'model_name'
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'model_name'
)}
/
>
<
Form
.
DatePicker
field
=
'start_timestamp'
label
=
'起始时间'
style
=
{{
width
:
272
}}
initValue
=
{
start_timestamp
}
initValue
=
{
start_timestamp
}
value
=
{
start_timestamp
}
type
=
"dateTime"
value
=
{
start_timestamp
}
name
=
"start_timestamp"
type
=
'dateTime'
onChange
=
{
value
=>
handleInputChange
(
value
,
'start_timestamp'
)}
/
>
name
=
'start_timestamp'
<
Form
.
DatePicker
field
=
"end_timestamp"
fluid
label
=
"结束时间"
style
=
{{
width
:
272
}}
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'start_timestamp'
)}
/
>
<
Form
.
DatePicker
field
=
'end_timestamp'
fluid
label
=
'结束时间'
style
=
{{
width
:
272
}}
initValue
=
{
end_timestamp
}
initValue
=
{
end_timestamp
}
value
=
{
end_timestamp
}
type
=
"dateTime"
value
=
{
end_timestamp
}
name
=
"end_timestamp"
type
=
'dateTime'
onChange
=
{
value
=>
handleInputChange
(
value
,
'end_timestamp'
)}
/
>
name
=
'end_timestamp'
{
isAdminUser
&&
<>
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'end_timestamp'
)}
<
Form
.
Input
field
=
"channel"
label
=
"渠道 ID"
style
=
{{
width
:
176
}}
value
=
{
channel
}
/
>
placeholder
=
"可选值"
name
=
"channel"
{
isAdminUser
&&
(
onChange
=
{
value
=>
handleInputChange
(
value
,
'channel'
)}
/
>
<>
<
Form
.
Input
field
=
"username"
label
=
"用户名称"
style
=
{{
width
:
176
}}
value
=
{
username
}
<
Form
.
Input
placeholder
=
{
'可选值'
}
name
=
"username"
field
=
'channel'
onChange
=
{
value
=>
handleInputChange
(
value
,
'username'
)}
/
>
label
=
'渠道 ID'
<
/>
}
style
=
{{
width
:
176
}}
value
=
{
channel
}
placeholder
=
'可选值'
name
=
'channel'
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'channel'
)}
/
>
<
Form
.
Input
field
=
'username'
label
=
'用户名称'
style
=
{{
width
:
176
}}
value
=
{
username
}
placeholder
=
{
'可选值'
}
name
=
'username'
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'username'
)}
/
>
<
/
>
)}
<
Form
.
Section
>
<
Form
.
Section
>
<
Button
label
=
"查询"
type
=
"primary"
htmlType
=
"submit"
className
=
"btn-margin-right"
<
Button
onClick
=
{
refresh
}
loading
=
{
loading
}
>
查询
<
/Button
>
label
=
'查询'
type
=
'primary'
htmlType
=
'submit'
className
=
'btn-margin-right'
onClick
=
{
refresh
}
loading
=
{
loading
}
>
查询
<
/Button
>
<
/Form.Section
>
<
/Form.Section
>
<
/
>
<
/
>
<
/Form
>
<
/Form
>
<
Table
style
=
{{
marginTop
:
5
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
<
Table
style
=
{{
marginTop
:
5
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
currentPage
:
activePage
,
pageSize
:
pageSize
,
pageSize
:
pageSize
,
total
:
logCount
,
total
:
logCount
,
...
@@ -380,20 +627,26 @@ const LogsTable = () => {
...
@@ -380,20 +627,26 @@ const LogsTable = () => {
onPageSizeChange
:
(
size
)
=>
{
onPageSizeChange
:
(
size
)
=>
{
handlePageSizeChange
(
size
).
then
();
handlePageSizeChange
(
size
).
then
();
},
},
onPageChange
:
handlePageChange
onPageChange
:
handlePageChange
,
}}
/
>
}}
<
Select
defaultValue
=
"0"
style
=
{{
width
:
120
}}
onChange
=
{(
value
)
=>
{
/
>
<
Select
defaultValue
=
'0'
style
=
{{
width
:
120
}}
onChange
=
{(
value
)
=>
{
setLogType
(
parseInt
(
value
));
setLogType
(
parseInt
(
value
));
refresh
(
parseInt
(
value
)).
then
();
refresh
(
parseInt
(
value
)).
then
();
}}
>
}}
<
Select
.
Option
value
=
"0"
>
全部
<
/Select.Option
>
>
<
Select
.
Option
value
=
"1"
>
充值
<
/Select.Option
>
<
Select
.
Option
value
=
'0'
>
全部
<
/Select.Option
>
<
Select
.
Option
value
=
"2"
>
消费
<
/Select.Option
>
<
Select
.
Option
value
=
'1'
>
充值
<
/Select.Option
>
<
Select
.
Option
value
=
"3"
>
管理
<
/Select.Option
>
<
Select
.
Option
value
=
'2'
>
消费
<
/Select.Option
>
<
Select
.
Option
value
=
"4"
>
系统
<
/Select.Option
>
<
Select
.
Option
value
=
'3'
>
管理
<
/Select.Option
>
<
Select
.
Option
value
=
'4'
>
系统
<
/Select.Option
>
<
/Select
>
<
/Select
>
<
/Layout
>
<
/Layout
>
<
/>
)
;
<
/
>
);
};
};
export
default
LogsTable
;
export
default
LogsTable
;
web/src/components/MjLogsTable.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
API
,
copy
,
isAdmin
,
showError
,
showSuccess
,
timestamp2string
}
from
'../helpers'
;
import
{
API
,
import
{
Banner
,
Button
,
Form
,
ImagePreview
,
Layout
,
Modal
,
Progress
,
Table
,
Tag
,
Typography
}
from
'@douyinfe/semi-ui'
;
copy
,
isAdmin
,
showError
,
showSuccess
,
timestamp2string
,
}
from
'../helpers'
;
import
{
Banner
,
Button
,
Form
,
ImagePreview
,
Layout
,
Modal
,
Progress
,
Table
,
Tag
,
Typography
,
}
from
'@douyinfe/semi-ui'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
const
colors
=
[
const
colors
=
[
'amber'
,
'blue'
,
'cyan'
,
'green'
,
'grey'
,
'indigo'
,
'amber'
,
'light-blue'
,
'lime'
,
'orange'
,
'pink'
,
'blue'
,
'purple'
,
'red'
,
'teal'
,
'violet'
,
'yellow'
'cyan'
,
'green'
,
'grey'
,
'indigo'
,
'light-blue'
,
'lime'
,
'orange'
,
'pink'
,
'purple'
,
'red'
,
'teal'
,
'violet'
,
'yellow'
,
];
];
function
renderType
(
type
)
{
function
renderType
(
type
)
{
switch
(
type
)
{
switch
(
type
)
{
case
'IMAGINE'
:
case
'IMAGINE'
:
return
<
Tag
color
=
"blue"
size
=
"large"
>
绘图
<
/Tag>
;
return
(
<
Tag
color
=
'blue'
size
=
'large'
>
绘图
<
/Tag
>
);
case
'UPSCALE'
:
case
'UPSCALE'
:
return
<
Tag
color
=
"orange"
size
=
"large"
>
放大
<
/Tag>
;
return
(
<
Tag
color
=
'orange'
size
=
'large'
>
放大
<
/Tag
>
);
case
'VARIATION'
:
case
'VARIATION'
:
return
<
Tag
color
=
"purple"
size
=
"large"
>
变换
<
/Tag>
;
return
(
<
Tag
color
=
'purple'
size
=
'large'
>
变换
<
/Tag
>
);
case
'HIGH_VARIATION'
:
case
'HIGH_VARIATION'
:
return
<
Tag
color
=
"purple"
size
=
"large"
>
强变换
<
/Tag>
;
return
(
<
Tag
color
=
'purple'
size
=
'large'
>
强变换
<
/Tag
>
);
case
'LOW_VARIATION'
:
case
'LOW_VARIATION'
:
return
<
Tag
color
=
"purple"
size
=
"large"
>
弱变换
<
/Tag>
;
return
(
<
Tag
color
=
'purple'
size
=
'large'
>
弱变换
<
/Tag
>
);
case
'PAN'
:
case
'PAN'
:
return
<
Tag
color
=
"cyan"
size
=
"large"
>
平移
<
/Tag>
;
return
(
<
Tag
color
=
'cyan'
size
=
'large'
>
平移
<
/Tag
>
);
case
'DESCRIBE'
:
case
'DESCRIBE'
:
return
<
Tag
color
=
"yellow"
size
=
"large"
>
图生文
<
/Tag>
;
return
(
<
Tag
color
=
'yellow'
size
=
'large'
>
图生文
<
/Tag
>
);
case
'BLEND'
:
case
'BLEND'
:
return
<
Tag
color
=
"lime"
size
=
"large"
>
图混合
<
/Tag>
;
return
(
<
Tag
color
=
'lime'
size
=
'large'
>
图混合
<
/Tag
>
);
case
'SHORTEN'
:
case
'SHORTEN'
:
return
<
Tag
color
=
"pink"
size
=
"large"
>
缩词
<
/Tag>
;
return
(
<
Tag
color
=
'pink'
size
=
'large'
>
缩词
<
/Tag
>
);
case
'REROLL'
:
case
'REROLL'
:
return
<
Tag
color
=
"indigo"
size
=
"large"
>
重绘
<
/Tag>
;
return
(
<
Tag
color
=
'indigo'
size
=
'large'
>
重绘
<
/Tag
>
);
case
'INPAINT'
:
case
'INPAINT'
:
return
<
Tag
color
=
"violet"
size
=
"large"
>
局部重绘
-
提交
<
/Tag>
;
return
(
<
Tag
color
=
'violet'
size
=
'large'
>
局部重绘
-
提交
<
/Tag
>
);
case
'ZOOM'
:
case
'ZOOM'
:
return
<
Tag
color
=
"teal"
size
=
"large"
>
变焦
<
/Tag>
;
return
(
<
Tag
color
=
'teal'
size
=
'large'
>
变焦
<
/Tag
>
);
case
'CUSTOM_ZOOM'
:
case
'CUSTOM_ZOOM'
:
return
<
Tag
color
=
"teal"
size
=
"large"
>
自定义变焦
-
提交
<
/Tag>
;
return
(
<
Tag
color
=
'teal'
size
=
'large'
>
自定义变焦
-
提交
<
/Tag
>
);
case
'MODAL'
:
case
'MODAL'
:
return
<
Tag
color
=
"green"
size
=
"large"
>
窗口处理
<
/Tag>
;
return
(
<
Tag
color
=
'green'
size
=
'large'
>
窗口处理
<
/Tag
>
);
case
'SWAP_FACE'
:
case
'SWAP_FACE'
:
return
<
Tag
color
=
"light-green"
size
=
"large"
>
换脸
<
/Tag>
;
return
(
<
Tag
color
=
'light-green'
size
=
'large'
>
换脸
<
/Tag
>
);
default
:
default
:
return
<
Tag
color
=
"white"
size
=
"large"
>
未知
<
/Tag>
;
return
(
<
Tag
color
=
'white'
size
=
'large'
>
未知
<
/Tag
>
);
}
}
}
}
function
renderCode
(
code
)
{
function
renderCode
(
code
)
{
switch
(
code
)
{
switch
(
code
)
{
case
1
:
case
1
:
return
<
Tag
color
=
"green"
size
=
"large"
>
已提交
<
/Tag>
;
return
(
<
Tag
color
=
'green'
size
=
'large'
>
已提交
<
/Tag
>
);
case
21
:
case
21
:
return
<
Tag
color
=
"lime"
size
=
"large"
>
等待中
<
/Tag>
;
return
(
<
Tag
color
=
'lime'
size
=
'large'
>
等待中
<
/Tag
>
);
case
22
:
case
22
:
return
<
Tag
color
=
"orange"
size
=
"large"
>
重复提交
<
/Tag>
;
return
(
<
Tag
color
=
'orange'
size
=
'large'
>
重复提交
<
/Tag
>
);
case
0
:
case
0
:
return
<
Tag
color
=
"yellow"
size
=
"large"
>
未提交
<
/Tag>
;
return
(
<
Tag
color
=
'yellow'
size
=
'large'
>
未提交
<
/Tag
>
);
default
:
default
:
return
<
Tag
color
=
"white"
size
=
"large"
>
未知
<
/Tag>
;
return
(
<
Tag
color
=
'white'
size
=
'large'
>
未知
<
/Tag
>
);
}
}
}
}
function
renderStatus
(
type
)
{
function
renderStatus
(
type
)
{
// Ensure all cases are string literals by adding quotes.
// Ensure all cases are string literals by adding quotes.
switch
(
type
)
{
switch
(
type
)
{
case
'SUCCESS'
:
case
'SUCCESS'
:
return
<
Tag
color
=
"green"
size
=
"large"
>
成功
<
/Tag>
;
return
(
<
Tag
color
=
'green'
size
=
'large'
>
成功
<
/Tag
>
);
case
'NOT_START'
:
case
'NOT_START'
:
return
<
Tag
color
=
"grey"
size
=
"large"
>
未启动
<
/Tag>
;
return
(
<
Tag
color
=
'grey'
size
=
'large'
>
未启动
<
/Tag
>
);
case
'SUBMITTED'
:
case
'SUBMITTED'
:
return
<
Tag
color
=
"yellow"
size
=
"large"
>
队列中
<
/Tag>
;
return
(
<
Tag
color
=
'yellow'
size
=
'large'
>
队列中
<
/Tag
>
);
case
'IN_PROGRESS'
:
case
'IN_PROGRESS'
:
return
<
Tag
color
=
"blue"
size
=
"large"
>
执行中
<
/Tag>
;
return
(
<
Tag
color
=
'blue'
size
=
'large'
>
执行中
<
/Tag
>
);
case
'FAILURE'
:
case
'FAILURE'
:
return
<
Tag
color
=
"red"
size
=
"large"
>
失败
<
/Tag>
;
return
(
<
Tag
color
=
'red'
size
=
'large'
>
失败
<
/Tag
>
);
case
'MODAL'
:
case
'MODAL'
:
return
<
Tag
color
=
"yellow"
size
=
"large"
>
窗口等待
<
/Tag>
;
return
(
<
Tag
color
=
'yellow'
size
=
'large'
>
窗口等待
<
/Tag
>
);
default
:
default
:
return
<
Tag
color
=
"white"
size
=
"large"
>
未知
<
/Tag>
;
return
(
<
Tag
color
=
'white'
size
=
'large'
>
未知
<
/Tag
>
);
}
}
}
}
...
@@ -97,7 +237,6 @@ const renderTimestamp = (timestampInSeconds) => {
...
@@ -97,7 +237,6 @@ const renderTimestamp = (timestampInSeconds) => {
return
`
${
year
}
-
${
month
}
-
${
day
}
${
hours
}
:
${
minutes
}
:
${
seconds
}
`
;
// 格式化输出
return
`
${
year
}
-
${
month
}
-
${
day
}
${
hours
}
:
${
minutes
}
:
${
seconds
}
`
;
// 格式化输出
};
};
const
LogsTable
=
()
=>
{
const
LogsTable
=
()
=>
{
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
false
);
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
false
);
const
[
modalContent
,
setModalContent
]
=
useState
(
''
);
const
[
modalContent
,
setModalContent
]
=
useState
(
''
);
...
@@ -106,12 +245,8 @@ const LogsTable = () => {
...
@@ -106,12 +245,8 @@ const LogsTable = () => {
title
:
'提交时间'
,
title
:
'提交时间'
,
dataIndex
:
'submit_time'
,
dataIndex
:
'submit_time'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderTimestamp
(
text
/
1000
)}
<
/div>
;
<
div
>
},
{
renderTimestamp
(
text
/
1000
)}
<
/div
>
);
}
},
},
{
{
title
:
'渠道'
,
title
:
'渠道'
,
...
@@ -119,61 +254,50 @@ const LogsTable = () => {
...
@@ -119,61 +254,50 @@ const LogsTable = () => {
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
(
<
div
>
<
div
>
<
Tag
color
=
{
colors
[
parseInt
(
text
)
%
colors
.
length
]}
size
=
"large"
onClick
=
{()
=>
{
<
Tag
color
=
{
colors
[
parseInt
(
text
)
%
colors
.
length
]}
size
=
'large'
onClick
=
{()
=>
{
copyText
(
text
);
// 假设copyText是用于文本复制的函数
copyText
(
text
);
// 假设copyText是用于文本复制的函数
}}
>
{
text
}
<
/Tag
>
}}
>
{
' '
}
{
text
}{
' '
}
<
/Tag
>
<
/div
>
<
/div
>
);
);
}
}
,
},
},
{
{
title
:
'类型'
,
title
:
'类型'
,
dataIndex
:
'action'
,
dataIndex
:
'action'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderType
(
text
)}
<
/div>
;
<
div
>
},
{
renderType
(
text
)}
<
/div
>
);
}
},
},
{
{
title
:
'任务ID'
,
title
:
'任务ID'
,
dataIndex
:
'mj_id'
,
dataIndex
:
'mj_id'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
text
}
<
/div>
;
<
div
>
},
{
text
}
<
/div
>
);
}
},
},
{
{
title
:
'提交结果'
,
title
:
'提交结果'
,
dataIndex
:
'code'
,
dataIndex
:
'code'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderCode
(
text
)}
<
/div>
;
<
div
>
},
{
renderCode
(
text
)}
<
/div
>
);
}
},
},
{
{
title
:
'任务状态'
,
title
:
'任务状态'
,
dataIndex
:
'status'
,
dataIndex
:
'status'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderStatus
(
text
)}
<
/div>
;
<
div
>
},
{
renderStatus
(
text
)}
<
/div
>
);
}
},
},
{
{
title
:
'进度'
,
title
:
'进度'
,
...
@@ -183,13 +307,20 @@ const LogsTable = () => {
...
@@ -183,13 +307,20 @@ const LogsTable = () => {
<
div
>
<
div
>
{
{
// 转换例如100%为数字100,如果text未定义,返回0
// 转换例如100%为数字100,如果text未定义,返回0
<
Progress
stroke
=
{
record
.
status
===
'FAILURE'
?
'var(--semi-color-warning)'
:
null
}
<
Progress
percent
=
{
text
?
parseInt
(
text
.
replace
(
'%'
,
''
))
:
0
}
showInfo
=
{
true
}
stroke
=
{
aria
-
label
=
"drawing progress"
/>
record
.
status
===
'FAILURE'
?
'var(--semi-color-warning)'
:
null
}
percent
=
{
text
?
parseInt
(
text
.
replace
(
'%'
,
''
))
:
0
}
showInfo
=
{
true
}
aria
-
label
=
'drawing progress'
/>
}
}
<
/div
>
<
/div
>
);
);
}
}
,
},
},
{
{
title
:
'结果图片'
,
title
:
'结果图片'
,
...
@@ -208,7 +339,7 @@ const LogsTable = () => {
...
@@ -208,7 +339,7 @@ const LogsTable = () => {
查看图片
查看图片
<
/Button
>
<
/Button
>
);
);
}
}
,
},
},
{
{
title
:
'Prompt'
,
title
:
'Prompt'
,
...
@@ -231,7 +362,7 @@ const LogsTable = () => {
...
@@ -231,7 +362,7 @@ const LogsTable = () => {
{
text
}
{
text
}
<
/Typography.Text
>
<
/Typography.Text
>
);
);
}
}
,
},
},
{
{
title
:
'PromptEn'
,
title
:
'PromptEn'
,
...
@@ -254,7 +385,7 @@ const LogsTable = () => {
...
@@ -254,7 +385,7 @@ const LogsTable = () => {
{
text
}
{
text
}
<
/Typography.Text
>
<
/Typography.Text
>
);
);
}
}
,
},
},
{
{
title
:
'失败原因'
,
title
:
'失败原因'
,
...
@@ -277,9 +408,8 @@ const LogsTable = () => {
...
@@ -277,9 +408,8 @@ const LogsTable = () => {
{
text
}
{
text
}
<
/Typography.Text
>
<
/Typography.Text
>
);
);
}
},
}
},
];
];
const
[
logs
,
setLogs
]
=
useState
([]);
const
[
logs
,
setLogs
]
=
useState
([]);
...
@@ -299,20 +429,19 @@ const LogsTable = () => {
...
@@ -299,20 +429,19 @@ const LogsTable = () => {
channel_id
:
''
,
channel_id
:
''
,
mj_id
:
''
,
mj_id
:
''
,
start_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
-
2592000
),
start_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
-
2592000
),
end_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
+
3600
)
end_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
+
3600
)
,
});
});
const
{
channel_id
,
mj_id
,
start_timestamp
,
end_timestamp
}
=
inputs
;
const
{
channel_id
,
mj_id
,
start_timestamp
,
end_timestamp
}
=
inputs
;
const
[
stat
,
setStat
]
=
useState
({
const
[
stat
,
setStat
]
=
useState
({
quota
:
0
,
quota
:
0
,
token
:
0
token
:
0
,
});
});
const
handleInputChange
=
(
value
,
name
)
=>
{
const
handleInputChange
=
(
value
,
name
)
=>
{
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
};
};
const
setLogsFormat
=
(
logs
)
=>
{
const
setLogsFormat
=
(
logs
)
=>
{
for
(
let
i
=
0
;
i
<
logs
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
logs
.
length
;
i
++
)
{
logs
[
i
].
timestamp2string
=
timestamp2string
(
logs
[
i
].
created_at
);
logs
[
i
].
timestamp2string
=
timestamp2string
(
logs
[
i
].
created_at
);
...
@@ -351,14 +480,16 @@ const LogsTable = () => {
...
@@ -351,14 +480,16 @@ const LogsTable = () => {
setLoading
(
false
);
setLoading
(
false
);
};
};
const
pageData
=
logs
.
slice
((
activePage
-
1
)
*
ITEMS_PER_PAGE
,
activePage
*
ITEMS_PER_PAGE
);
const
pageData
=
logs
.
slice
(
(
activePage
-
1
)
*
ITEMS_PER_PAGE
,
activePage
*
ITEMS_PER_PAGE
,
);
const
handlePageChange
=
page
=>
{
const
handlePageChange
=
(
page
)
=>
{
setActivePage
(
page
);
setActivePage
(
page
);
if
(
page
===
Math
.
ceil
(
logs
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
if
(
page
===
Math
.
ceil
(
logs
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
// In this case we have to load more data and then append them.
// In this case we have to load more data and then append them.
loadLogs
(
page
-
1
).
then
(
r
=>
{
loadLogs
(
page
-
1
).
then
((
r
)
=>
{});
});
}
}
};
};
...
@@ -390,46 +521,83 @@ const LogsTable = () => {
...
@@ -390,46 +521,83 @@ const LogsTable = () => {
return
(
return
(
<>
<>
<
Layout
>
<
Layout
>
{
isAdminUser
&&
showBanner
?
<
Banner
{
isAdminUser
&&
showBanner
?
(
type
=
"info"
<
Banner
description
=
"当前未开启Midjourney回调,部分项目可能无法获得绘图结果,可在运营设置中开启。"
type
=
'info'
/>
:
<><
/
>
description
=
'当前未开启Midjourney回调,部分项目可能无法获得绘图结果,可在运营设置中开启。'
}
/>
<
Form
layout
=
"horizontal"
style
=
{{
marginTop
:
10
}}
>
)
:
(
<><
/
>
)}
<
Form
layout
=
'horizontal'
style
=
{{
marginTop
:
10
}}
>
<>
<>
<
Form
.
Input
field
=
"channel_id"
label
=
"渠道 ID"
style
=
{{
width
:
176
}}
value
=
{
channel_id
}
<
Form
.
Input
placeholder
=
{
'可选值'
}
name
=
"channel_id"
field
=
'channel_id'
onChange
=
{
value
=>
handleInputChange
(
value
,
'channel_id'
)}
/
>
label
=
'渠道 ID'
<
Form
.
Input
field
=
"mj_id"
label
=
"任务 ID"
style
=
{{
width
:
176
}}
value
=
{
mj_id
}
style
=
{{
width
:
176
}}
placeholder
=
"可选值"
value
=
{
channel_id
}
name
=
"mj_id"
placeholder
=
{
'可选值'
}
onChange
=
{
value
=>
handleInputChange
(
value
,
'mj_id'
)}
/
>
name
=
'channel_id'
<
Form
.
DatePicker
field
=
"start_timestamp"
label
=
"起始时间"
style
=
{{
width
:
272
}}
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'channel_id'
)}
/
>
<
Form
.
Input
field
=
'mj_id'
label
=
'任务 ID'
style
=
{{
width
:
176
}}
value
=
{
mj_id
}
placeholder
=
'可选值'
name
=
'mj_id'
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'mj_id'
)}
/
>
<
Form
.
DatePicker
field
=
'start_timestamp'
label
=
'起始时间'
style
=
{{
width
:
272
}}
initValue
=
{
start_timestamp
}
initValue
=
{
start_timestamp
}
value
=
{
start_timestamp
}
type
=
"dateTime"
value
=
{
start_timestamp
}
name
=
"start_timestamp"
type
=
'dateTime'
onChange
=
{
value
=>
handleInputChange
(
value
,
'start_timestamp'
)}
/
>
name
=
'start_timestamp'
<
Form
.
DatePicker
field
=
"end_timestamp"
fluid
label
=
"结束时间"
style
=
{{
width
:
272
}}
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'start_timestamp'
)}
/
>
<
Form
.
DatePicker
field
=
'end_timestamp'
fluid
label
=
'结束时间'
style
=
{{
width
:
272
}}
initValue
=
{
end_timestamp
}
initValue
=
{
end_timestamp
}
value
=
{
end_timestamp
}
type
=
"dateTime"
value
=
{
end_timestamp
}
name
=
"end_timestamp"
type
=
'dateTime'
onChange
=
{
value
=>
handleInputChange
(
value
,
'end_timestamp'
)}
/
>
name
=
'end_timestamp'
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'end_timestamp'
)}
/
>
<
Form
.
Section
>
<
Form
.
Section
>
<
Button
label
=
"查询"
type
=
"primary"
htmlType
=
"submit"
className
=
"btn-margin-right"
<
Button
onClick
=
{
refresh
}
>
查询
<
/Button
>
label
=
'查询'
type
=
'primary'
htmlType
=
'submit'
className
=
'btn-margin-right'
onClick
=
{
refresh
}
>
查询
<
/Button
>
<
/Form.Section
>
<
/Form.Section
>
<
/
>
<
/
>
<
/Form
>
<
/Form
>
<
Table
style
=
{{
marginTop
:
5
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
<
Table
style
=
{{
marginTop
:
5
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
currentPage
:
activePage
,
pageSize
:
ITEMS_PER_PAGE
,
pageSize
:
ITEMS_PER_PAGE
,
total
:
logCount
,
total
:
logCount
,
pageSizeOpts
:
[
10
,
20
,
50
,
100
],
pageSizeOpts
:
[
10
,
20
,
50
,
100
],
onPageChange
:
handlePageChange
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
/
>
}}
loading
=
{
loading
}
/
>
<
Modal
<
Modal
visible
=
{
isModalOpen
}
visible
=
{
isModalOpen
}
onOk
=
{()
=>
setIsModalOpen
(
false
)}
onOk
=
{()
=>
setIsModalOpen
(
false
)}
...
@@ -445,7 +613,6 @@ const LogsTable = () => {
...
@@ -445,7 +613,6 @@ const LogsTable = () => {
visible
=
{
isModalOpenurl
}
visible
=
{
isModalOpenurl
}
onVisibleChange
=
{(
visible
)
=>
setIsModalOpenurl
(
visible
)}
onVisibleChange
=
{(
visible
)
=>
setIsModalOpenurl
(
visible
)}
/
>
/
>
<
/Layout
>
<
/Layout
>
<
/
>
<
/
>
);
);
...
...
web/src/components/OperationSetting.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Divider
,
Form
,
Grid
,
Header
}
from
'semantic-ui-react'
;
import
{
Divider
,
Form
,
Grid
,
Header
}
from
'semantic-ui-react'
;
import
{
API
,
showError
,
showSuccess
,
timestamp2string
,
verifyJSON
}
from
'../helpers'
;
import
{
API
,
showError
,
showSuccess
,
timestamp2string
,
verifyJSON
,
}
from
'../helpers'
;
const
OperationSetting
=
()
=>
{
const
OperationSetting
=
()
=>
{
let
now
=
new
Date
();
let
now
=
new
Date
();
...
@@ -35,16 +41,18 @@ const OperationSetting = () => {
...
@@ -35,16 +41,18 @@ const OperationSetting = () => {
DataExportDefaultTime
:
'hour'
,
DataExportDefaultTime
:
'hour'
,
DataExportInterval
:
5
,
DataExportInterval
:
5
,
DefaultCollapseSidebar
:
''
,
// 默认折叠侧边栏
DefaultCollapseSidebar
:
''
,
// 默认折叠侧边栏
RetryTimes
:
0
RetryTimes
:
0
,
});
});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
let
[
loading
,
setLoading
]
=
useState
(
false
);
let
[
loading
,
setLoading
]
=
useState
(
false
);
let
[
historyTimestamp
,
setHistoryTimestamp
]
=
useState
(
timestamp2string
(
now
.
getTime
()
/
1000
-
30
*
24
*
3600
));
// a month ago
let
[
historyTimestamp
,
setHistoryTimestamp
]
=
useState
(
timestamp2string
(
now
.
getTime
()
/
1000
-
30
*
24
*
3600
),
);
// a month ago
// 精确时间选项(小时,天,周)
// 精确时间选项(小时,天,周)
const
timeOptions
=
[
const
timeOptions
=
[
{
key
:
'hour'
,
text
:
'小时'
,
value
:
'hour'
},
{
key
:
'hour'
,
text
:
'小时'
,
value
:
'hour'
},
{
key
:
'day'
,
text
:
'天'
,
value
:
'day'
},
{
key
:
'day'
,
text
:
'天'
,
value
:
'day'
},
{
key
:
'week'
,
text
:
'周'
,
value
:
'week'
}
{
key
:
'week'
,
text
:
'周'
,
value
:
'week'
}
,
];
];
const
getOptions
=
async
()
=>
{
const
getOptions
=
async
()
=>
{
const
res
=
await
API
.
get
(
'/api/option/'
);
const
res
=
await
API
.
get
(
'/api/option/'
);
...
@@ -52,7 +60,11 @@ const OperationSetting = () => {
...
@@ -52,7 +60,11 @@ const OperationSetting = () => {
if
(
success
)
{
if
(
success
)
{
let
newInputs
=
{};
let
newInputs
=
{};
data
.
forEach
((
item
)
=>
{
data
.
forEach
((
item
)
=>
{
if
(
item
.
key
===
'ModelRatio'
||
item
.
key
===
'GroupRatio'
||
item
.
key
===
'ModelPrice'
)
{
if
(
item
.
key
===
'ModelRatio'
||
item
.
key
===
'GroupRatio'
||
item
.
key
===
'ModelPrice'
)
{
item
.
value
=
JSON
.
stringify
(
JSON
.
parse
(
item
.
value
),
null
,
2
);
item
.
value
=
JSON
.
stringify
(
JSON
.
parse
(
item
.
value
),
null
,
2
);
}
}
newInputs
[
item
.
key
]
=
item
.
value
;
newInputs
[
item
.
key
]
=
item
.
value
;
...
@@ -79,7 +91,7 @@ const OperationSetting = () => {
...
@@ -79,7 +91,7 @@ const OperationSetting = () => {
console
.
log
(
key
,
value
);
console
.
log
(
key
,
value
);
const
res
=
await
API
.
put
(
'/api/option/'
,
{
const
res
=
await
API
.
put
(
'/api/option/'
,
{
key
,
key
,
value
value
,
});
});
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -91,7 +103,12 @@ const OperationSetting = () => {
...
@@ -91,7 +103,12 @@ const OperationSetting = () => {
};
};
const
handleInputChange
=
async
(
e
,
{
name
,
value
})
=>
{
const
handleInputChange
=
async
(
e
,
{
name
,
value
})
=>
{
if
(
name
.
endsWith
(
'Enabled'
)
||
name
===
'DataExportInterval'
||
name
===
'DataExportDefaultTime'
||
name
===
'DefaultCollapseSidebar'
)
{
if
(
name
.
endsWith
(
'Enabled'
)
||
name
===
'DataExportInterval'
||
name
===
'DataExportDefaultTime'
||
name
===
'DefaultCollapseSidebar'
)
{
if
(
name
===
'DataExportDefaultTime'
)
{
if
(
name
===
'DataExportDefaultTime'
)
{
localStorage
.
setItem
(
'data_export_default_time'
,
value
);
localStorage
.
setItem
(
'data_export_default_time'
,
value
);
}
else
if
(
name
===
'MjNotifyEnabled'
)
{
}
else
if
(
name
===
'MjNotifyEnabled'
)
{
...
@@ -106,11 +123,22 @@ const OperationSetting = () => {
...
@@ -106,11 +123,22 @@ const OperationSetting = () => {
const
submitConfig
=
async
(
group
)
=>
{
const
submitConfig
=
async
(
group
)
=>
{
switch
(
group
)
{
switch
(
group
)
{
case
'monitor'
:
case
'monitor'
:
if
(
originInputs
[
'ChannelDisableThreshold'
]
!==
inputs
.
ChannelDisableThreshold
)
{
if
(
await
updateOption
(
'ChannelDisableThreshold'
,
inputs
.
ChannelDisableThreshold
);
originInputs
[
'ChannelDisableThreshold'
]
!==
inputs
.
ChannelDisableThreshold
)
{
await
updateOption
(
'ChannelDisableThreshold'
,
inputs
.
ChannelDisableThreshold
,
);
}
}
if
(
originInputs
[
'QuotaRemindThreshold'
]
!==
inputs
.
QuotaRemindThreshold
)
{
if
(
await
updateOption
(
'QuotaRemindThreshold'
,
inputs
.
QuotaRemindThreshold
);
originInputs
[
'QuotaRemindThreshold'
]
!==
inputs
.
QuotaRemindThreshold
)
{
await
updateOption
(
'QuotaRemindThreshold'
,
inputs
.
QuotaRemindThreshold
,
);
}
}
break
;
break
;
case
'ratio'
:
case
'ratio'
:
...
@@ -177,7 +205,9 @@ const OperationSetting = () => {
...
@@ -177,7 +205,9 @@ const OperationSetting = () => {
const
deleteHistoryLogs
=
async
()
=>
{
const
deleteHistoryLogs
=
async
()
=>
{
console
.
log
(
inputs
);
console
.
log
(
inputs
);
const
res
=
await
API
.
delete
(
`/api/log/?target_timestamp=
${
Date
.
parse
(
historyTimestamp
)
/
1000
}
`
);
const
res
=
await
API
.
delete
(
`/api/log/?target_timestamp=
${
Date
.
parse
(
historyTimestamp
)
/
1000
}
`
,
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
showSuccess
(
`
${
data
}
条日志已清理!`
);
showSuccess
(
`
${
data
}
条日志已清理!`
);
...
@@ -189,131 +219,129 @@ const OperationSetting = () => {
...
@@ -189,131 +219,129 @@ const OperationSetting = () => {
<
Grid
columns
=
{
1
}
>
<
Grid
columns
=
{
1
}
>
<
Grid
.
Column
>
<
Grid
.
Column
>
<
Form
loading
=
{
loading
}
>
<
Form
loading
=
{
loading
}
>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
通用设置
<
/Header
>
通用设置
<
/Header
>
<
Form
.
Group
widths
=
{
4
}
>
<
Form
.
Group
widths
=
{
4
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"充值链接"
label
=
'充值链接'
name
=
"TopUpLink"
name
=
'TopUpLink'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
TopUpLink
}
value
=
{
inputs
.
TopUpLink
}
type
=
"link"
type
=
'link'
placeholder
=
"例如发卡网站的购买链接"
placeholder
=
'例如发卡网站的购买链接'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"默认聊天页面链接"
label
=
'默认聊天页面链接'
name
=
"ChatLink"
name
=
'ChatLink'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
ChatLink
}
value
=
{
inputs
.
ChatLink
}
type
=
"link"
type
=
'link'
placeholder
=
"例如 ChatGPT Next Web 的部署地址"
placeholder
=
'例如 ChatGPT Next Web 的部署地址'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"聊天页面2链接"
label
=
'聊天页面2链接'
name
=
"ChatLink2"
name
=
'ChatLink2'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
ChatLink2
}
value
=
{
inputs
.
ChatLink2
}
type
=
"link"
type
=
'link'
placeholder
=
"例如 ChatGPT Web & Midjourney 的部署地址"
placeholder
=
'例如 ChatGPT Web & Midjourney 的部署地址'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"单位美元额度"
label
=
'单位美元额度'
name
=
"QuotaPerUnit"
name
=
'QuotaPerUnit'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
QuotaPerUnit
}
value
=
{
inputs
.
QuotaPerUnit
}
type
=
"number"
type
=
'number'
step
=
"0.01"
step
=
'0.01'
placeholder
=
"一单位货币能兑换的额度"
placeholder
=
'一单位货币能兑换的额度'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"失败重试次数"
label
=
'失败重试次数'
name
=
"RetryTimes"
name
=
'RetryTimes'
type
=
{
'number'
}
type
=
{
'number'
}
step
=
"1"
step
=
'1'
min
=
"0"
min
=
'0'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
RetryTimes
}
value
=
{
inputs
.
RetryTimes
}
placeholder
=
"失败重试次数"
placeholder
=
'失败重试次数'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
DisplayInCurrencyEnabled
===
'true'
}
checked
=
{
inputs
.
DisplayInCurrencyEnabled
===
'true'
}
label
=
"以货币形式显示额度"
label
=
'以货币形式显示额度'
name
=
"DisplayInCurrencyEnabled"
name
=
'DisplayInCurrencyEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
DisplayTokenStatEnabled
===
'true'
}
checked
=
{
inputs
.
DisplayTokenStatEnabled
===
'true'
}
label
=
"Billing 相关 API 显示令牌额度而非用户额度"
label
=
'Billing 相关 API 显示令牌额度而非用户额度'
name
=
"DisplayTokenStatEnabled"
name
=
'DisplayTokenStatEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
DefaultCollapseSidebar
===
'true'
}
checked
=
{
inputs
.
DefaultCollapseSidebar
===
'true'
}
label
=
"默认折叠侧边栏"
label
=
'默认折叠侧边栏'
name
=
"DefaultCollapseSidebar"
name
=
'DefaultCollapseSidebar'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{()
=>
{
<
Form
.
Button
onClick
=
{()
=>
{
submitConfig
(
'general'
).
then
();
submitConfig
(
'general'
).
then
();
}}
>
保存通用设置
<
/Form.Button
>
}}
>
保存通用设置
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
绘图设置
<
/Header
>
绘图设置
<
/Header
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
DrawingEnabled
===
'true'
}
checked
=
{
inputs
.
DrawingEnabled
===
'true'
}
label
=
"启用绘图功能"
label
=
'启用绘图功能'
name
=
"DrawingEnabled"
name
=
'DrawingEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
MjNotifyEnabled
===
'true'
}
checked
=
{
inputs
.
MjNotifyEnabled
===
'true'
}
label
=
"允许回调(会泄露服务器ip地址)"
label
=
'允许回调(会泄露服务器ip地址)'
name
=
"MjNotifyEnabled"
name
=
'MjNotifyEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
屏蔽词过滤设置
<
/Header
>
屏蔽词过滤设置
<
/Header
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
CheckSensitiveEnabled
===
'true'
}
checked
=
{
inputs
.
CheckSensitiveEnabled
===
'true'
}
label
=
"启用屏蔽词过滤功能"
label
=
'启用屏蔽词过滤功能'
name
=
"CheckSensitiveEnabled"
name
=
'CheckSensitiveEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
CheckSensitiveOnPromptEnabled
===
'true'
}
checked
=
{
inputs
.
CheckSensitiveOnPromptEnabled
===
'true'
}
label
=
"启用prompt检查"
label
=
'启用prompt检查'
name
=
"CheckSensitiveOnPromptEnabled"
name
=
'CheckSensitiveOnPromptEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
CheckSensitiveOnCompletionEnabled
===
'true'
}
checked
=
{
inputs
.
CheckSensitiveOnCompletionEnabled
===
'true'
}
label
=
"启用生成内容检查"
label
=
'启用生成内容检查'
name
=
"CheckSensitiveOnCompletionEnabled"
name
=
'CheckSensitiveOnCompletionEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
StopOnSensitiveEnabled
===
'true'
}
checked
=
{
inputs
.
StopOnSensitiveEnabled
===
'true'
}
label
=
"在检测到屏蔽词时,立刻停止生成,否则替换屏蔽词"
label
=
'在检测到屏蔽词时,立刻停止生成,否则替换屏蔽词'
name
=
"StopOnSensitiveEnabled"
name
=
'StopOnSensitiveEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
...
@@ -328,210 +356,223 @@ const OperationSetting = () => {
...
@@ -328,210 +356,223 @@ const OperationSetting = () => {
{
/* placeholder="例如:10"*/
}
{
/* placeholder="例如:10"*/
}
{
/* />*/
}
{
/* />*/
}
{
/*</Form.Group>*/
}
{
/*</Form.Group>*/
}
<
Form
.
Group
widths
=
"equal"
>
<
Form
.
Group
widths
=
'equal'
>
<
Form
.
TextArea
<
Form
.
TextArea
label
=
"屏蔽词列表,一行一个屏蔽词,不需要符号分割"
label
=
'屏蔽词列表,一行一个屏蔽词,不需要符号分割'
name
=
"SensitiveWords"
name
=
'SensitiveWords'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
value
=
{
inputs
.
SensitiveWords
}
value
=
{
inputs
.
SensitiveWords
}
placeholder
=
"一行一个屏蔽词"
placeholder
=
'一行一个屏蔽词'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{()
=>
{
<
Form
.
Button
onClick
=
{()
=>
{
submitConfig
(
'words'
).
then
();
submitConfig
(
'words'
).
then
();
}}
>
保存屏蔽词设置
<
/Form.Button
>
}}
>
保存屏蔽词设置
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
日志设置
<
/Header
>
日志设置
<
/Header
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
LogConsumeEnabled
===
'true'
}
checked
=
{
inputs
.
LogConsumeEnabled
===
'true'
}
label
=
"启用额度消费日志记录"
label
=
'启用额度消费日志记录'
name
=
"LogConsumeEnabled"
name
=
'LogConsumeEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
widths
=
{
4
}
>
<
Form
.
Group
widths
=
{
4
}
>
<
Form
.
Input
label
=
"目标时间"
value
=
{
historyTimestamp
}
type
=
"datetime-local"
<
Form
.
Input
name
=
"history_timestamp"
label
=
'目标时间'
value
=
{
historyTimestamp
}
type
=
'datetime-local'
name
=
'history_timestamp'
onChange
=
{(
e
,
{
name
,
value
})
=>
{
onChange
=
{(
e
,
{
name
,
value
})
=>
{
setHistoryTimestamp
(
value
);
setHistoryTimestamp
(
value
);
}}
/
>
}}
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{()
=>
{
<
Form
.
Button
onClick
=
{()
=>
{
deleteHistoryLogs
().
then
();
deleteHistoryLogs
().
then
();
}}
>
清理历史日志
<
/Form.Button
>
}}
>
清理历史日志
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
数据看板
<
/Header
>
数据看板
<
/Header
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
DataExportEnabled
===
'true'
}
checked
=
{
inputs
.
DataExportEnabled
===
'true'
}
label
=
"启用数据看板(实验性)"
label
=
'启用数据看板(实验性)'
name
=
"DataExportEnabled"
name
=
'DataExportEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Group
>
<
Form
.
Group
>
<
Form
.
Input
<
Form
.
Input
label
=
"数据看板更新间隔(分钟,设置过短会影响数据库性能)"
label
=
'数据看板更新间隔(分钟,设置过短会影响数据库性能)'
name
=
"DataExportInterval"
name
=
'DataExportInterval'
type
=
{
'number'
}
type
=
{
'number'
}
step
=
"1"
step
=
'1'
min
=
"1"
min
=
'1'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
DataExportInterval
}
value
=
{
inputs
.
DataExportInterval
}
placeholder
=
"数据看板更新间隔(分钟,设置过短会影响数据库性能)"
placeholder
=
'数据看板更新间隔(分钟,设置过短会影响数据库性能)'
/>
/>
<
Form
.
Select
<
Form
.
Select
label
=
"数据看板默认时间粒度(仅修改展示粒度,统计精确到小时)"
label
=
'数据看板默认时间粒度(仅修改展示粒度,统计精确到小时)'
options
=
{
timeOptions
}
options
=
{
timeOptions
}
name
=
"DataExportDefaultTime"
name
=
'DataExportDefaultTime'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
DataExportDefaultTime
}
value
=
{
inputs
.
DataExportDefaultTime
}
placeholder
=
"数据看板默认时间粒度"
placeholder
=
'数据看板默认时间粒度'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
监控设置
<
/Header
>
监控设置
<
/Header
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"最长响应时间"
label
=
'最长响应时间'
name
=
"ChannelDisableThreshold"
name
=
'ChannelDisableThreshold'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
ChannelDisableThreshold
}
value
=
{
inputs
.
ChannelDisableThreshold
}
type
=
"number"
type
=
'number'
min
=
"0"
min
=
'0'
placeholder
=
"单位秒,当运行通道全部测试时,超过此时间将自动禁用通道"
placeholder
=
'单位秒,当运行通道全部测试时,超过此时间将自动禁用通道'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"额度提醒阈值"
label
=
'额度提醒阈值'
name
=
"QuotaRemindThreshold"
name
=
'QuotaRemindThreshold'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
QuotaRemindThreshold
}
value
=
{
inputs
.
QuotaRemindThreshold
}
type
=
"number"
type
=
'number'
min
=
"0"
min
=
'0'
placeholder
=
"低于此额度时将发送邮件提醒用户"
placeholder
=
'低于此额度时将发送邮件提醒用户'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
AutomaticDisableChannelEnabled
===
'true'
}
checked
=
{
inputs
.
AutomaticDisableChannelEnabled
===
'true'
}
label
=
"失败时自动禁用通道"
label
=
'失败时自动禁用通道'
name
=
"AutomaticDisableChannelEnabled"
name
=
'AutomaticDisableChannelEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
AutomaticEnableChannelEnabled
===
'true'
}
checked
=
{
inputs
.
AutomaticEnableChannelEnabled
===
'true'
}
label
=
"成功时自动启用通道"
label
=
'成功时自动启用通道'
name
=
"AutomaticEnableChannelEnabled"
name
=
'AutomaticEnableChannelEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{()
=>
{
<
Form
.
Button
onClick
=
{()
=>
{
submitConfig
(
'monitor'
).
then
();
submitConfig
(
'monitor'
).
then
();
}}
>
保存监控设置
<
/Form.Button
>
}}
>
保存监控设置
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
额度设置
<
/Header
>
额度设置
<
/Header
>
<
Form
.
Group
widths
=
{
4
}
>
<
Form
.
Group
widths
=
{
4
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"新用户初始额度"
label
=
'新用户初始额度'
name
=
"QuotaForNewUser"
name
=
'QuotaForNewUser'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
QuotaForNewUser
}
value
=
{
inputs
.
QuotaForNewUser
}
type
=
"number"
type
=
'number'
min
=
"0"
min
=
'0'
placeholder
=
"例如:100"
placeholder
=
'例如:100'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"请求预扣费额度"
label
=
'请求预扣费额度'
name
=
"PreConsumedQuota"
name
=
'PreConsumedQuota'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
PreConsumedQuota
}
value
=
{
inputs
.
PreConsumedQuota
}
type
=
"number"
type
=
'number'
min
=
"0"
min
=
'0'
placeholder
=
"请求结束后多退少补"
placeholder
=
'请求结束后多退少补'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"邀请新用户奖励额度"
label
=
'邀请新用户奖励额度'
name
=
"QuotaForInviter"
name
=
'QuotaForInviter'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
QuotaForInviter
}
value
=
{
inputs
.
QuotaForInviter
}
type
=
"number"
type
=
'number'
min
=
"0"
min
=
'0'
placeholder
=
"例如:2000"
placeholder
=
'例如:2000'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"新用户使用邀请码奖励额度"
label
=
'新用户使用邀请码奖励额度'
name
=
"QuotaForInvitee"
name
=
'QuotaForInvitee'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
QuotaForInvitee
}
value
=
{
inputs
.
QuotaForInvitee
}
type
=
"number"
type
=
'number'
min
=
"0"
min
=
'0'
placeholder
=
"例如:1000"
placeholder
=
'例如:1000'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{()
=>
{
<
Form
.
Button
onClick
=
{()
=>
{
submitConfig
(
'quota'
).
then
();
submitConfig
(
'quota'
).
then
();
}}
>
保存额度设置
<
/Form.Button
>
}}
>
保存额度设置
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
倍率设置
<
/Header
>
倍率设置
<
Form
.
Group
widths
=
'equal'
>
<
/Header
>
<
Form
.
Group
widths
=
"equal"
>
<
Form
.
TextArea
<
Form
.
TextArea
label
=
"模型固定价格(一次调用消耗多少刀,优先级大于模型倍率)"
label
=
'模型固定价格(一次调用消耗多少刀,优先级大于模型倍率)'
name
=
"ModelPrice"
name
=
'ModelPrice'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
ModelPrice
}
value
=
{
inputs
.
ModelPrice
}
placeholder
=
'为一个 JSON 文本,键为模型名称,值为一次调用消耗多少刀,比如 "gpt-4-gizmo-*": 0.1,一次消耗0.1刀'
placeholder
=
'为一个 JSON 文本,键为模型名称,值为一次调用消耗多少刀,比如 "gpt-4-gizmo-*": 0.1,一次消耗0.1刀'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
widths
=
"equal"
>
<
Form
.
Group
widths
=
'equal'
>
<
Form
.
TextArea
<
Form
.
TextArea
label
=
"模型倍率"
label
=
'模型倍率'
name
=
"ModelRatio"
name
=
'ModelRatio'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
ModelRatio
}
value
=
{
inputs
.
ModelRatio
}
placeholder
=
"为一个 JSON 文本,键为模型名称,值为倍率"
placeholder
=
'为一个 JSON 文本,键为模型名称,值为倍率'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
widths
=
"equal"
>
<
Form
.
Group
widths
=
'equal'
>
<
Form
.
TextArea
<
Form
.
TextArea
label
=
"分组倍率"
label
=
'分组倍率'
name
=
"GroupRatio"
name
=
'GroupRatio'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
GroupRatio
}
value
=
{
inputs
.
GroupRatio
}
placeholder
=
"为一个 JSON 文本,键为分组名称,值为倍率"
placeholder
=
'为一个 JSON 文本,键为分组名称,值为倍率'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{()
=>
{
<
Form
.
Button
onClick
=
{()
=>
{
submitConfig
(
'ratio'
).
then
();
submitConfig
(
'ratio'
).
then
();
}}
>
保存倍率设置
<
/Form.Button
>
}}
>
保存倍率设置
<
/Form.Button
>
<
/Form
>
<
/Form
>
<
/Grid.Column
>
<
/Grid.Column
>
<
/Grid
>
<
/Grid
>
)
);
;
};
};
export
default
OperationSetting
;
export
default
OperationSetting
;
web/src/components/OtherSetting.js
View file @
eb955ae4
...
@@ -10,21 +10,20 @@ const OtherSetting = () => {
...
@@ -10,21 +10,20 @@ const OtherSetting = () => {
Logo
:
''
,
Logo
:
''
,
Footer
:
''
,
Footer
:
''
,
About
:
''
,
About
:
''
,
HomePageContent
:
''
HomePageContent
:
''
,
});
});
let
[
loading
,
setLoading
]
=
useState
(
false
);
let
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
showUpdateModal
,
setShowUpdateModal
]
=
useState
(
false
);
const
[
showUpdateModal
,
setShowUpdateModal
]
=
useState
(
false
);
const
[
updateData
,
setUpdateData
]
=
useState
({
const
[
updateData
,
setUpdateData
]
=
useState
({
tag_name
:
''
,
tag_name
:
''
,
content
:
''
content
:
''
,
});
});
const
updateOption
=
async
(
key
,
value
)
=>
{
const
updateOption
=
async
(
key
,
value
)
=>
{
setLoading
(
true
);
setLoading
(
true
);
const
res
=
await
API
.
put
(
'/api/option/'
,
{
const
res
=
await
API
.
put
(
'/api/option/'
,
{
key
,
key
,
value
value
,
});
});
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -41,7 +40,7 @@ const OtherSetting = () => {
...
@@ -41,7 +40,7 @@ const OtherSetting = () => {
Logo
:
false
,
Logo
:
false
,
HomePageContent
:
false
,
HomePageContent
:
false
,
About
:
false
,
About
:
false
,
Footer
:
false
Footer
:
false
,
});
});
const
handleInputChange
=
async
(
value
,
e
)
=>
{
const
handleInputChange
=
async
(
value
,
e
)
=>
{
const
name
=
e
.
target
.
id
;
const
name
=
e
.
target
.
id
;
...
@@ -68,14 +67,20 @@ const OtherSetting = () => {
...
@@ -68,14 +67,20 @@ const OtherSetting = () => {
// 个性化设置 - SystemName
// 个性化设置 - SystemName
const
submitSystemName
=
async
()
=>
{
const
submitSystemName
=
async
()
=>
{
try
{
try
{
setLoadingInput
((
loadingInput
)
=>
({
...
loadingInput
,
SystemName
:
true
}));
setLoadingInput
((
loadingInput
)
=>
({
...
loadingInput
,
SystemName
:
true
,
}));
await
updateOption
(
'SystemName'
,
inputs
.
SystemName
);
await
updateOption
(
'SystemName'
,
inputs
.
SystemName
);
showSuccess
(
'系统名称已更新'
);
showSuccess
(
'系统名称已更新'
);
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
'系统名称更新失败'
,
error
);
console
.
error
(
'系统名称更新失败'
,
error
);
showError
(
'系统名称更新失败'
);
showError
(
'系统名称更新失败'
);
}
finally
{
}
finally
{
setLoadingInput
((
loadingInput
)
=>
({
...
loadingInput
,
SystemName
:
false
}));
setLoadingInput
((
loadingInput
)
=>
({
...
loadingInput
,
SystemName
:
false
,
}));
}
}
};
};
...
@@ -95,14 +100,20 @@ const OtherSetting = () => {
...
@@ -95,14 +100,20 @@ const OtherSetting = () => {
// 个性化设置 - 首页内容
// 个性化设置 - 首页内容
const
submitOption
=
async
(
key
)
=>
{
const
submitOption
=
async
(
key
)
=>
{
try
{
try
{
setLoadingInput
((
loadingInput
)
=>
({
...
loadingInput
,
HomePageContent
:
true
}));
setLoadingInput
((
loadingInput
)
=>
({
...
loadingInput
,
HomePageContent
:
true
,
}));
await
updateOption
(
key
,
inputs
[
key
]);
await
updateOption
(
key
,
inputs
[
key
]);
showSuccess
(
'首页内容已更新'
);
showSuccess
(
'首页内容已更新'
);
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
'首页内容更新失败'
,
error
);
console
.
error
(
'首页内容更新失败'
,
error
);
showError
(
'首页内容更新失败'
);
showError
(
'首页内容更新失败'
);
}
finally
{
}
finally
{
setLoadingInput
((
loadingInput
)
=>
({
...
loadingInput
,
HomePageContent
:
false
}));
setLoadingInput
((
loadingInput
)
=>
({
...
loadingInput
,
HomePageContent
:
false
,
}));
}
}
};
};
// 个性化设置 - 关于
// 个性化设置 - 关于
...
@@ -132,15 +143,13 @@ const OtherSetting = () => {
...
@@ -132,15 +143,13 @@ const OtherSetting = () => {
}
}
};
};
const
openGitHubRelease
=
()
=>
{
const
openGitHubRelease
=
()
=>
{
window
.
location
=
window
.
location
=
'https://github.com/songquanpeng/one-api/releases/latest'
;
'https://github.com/songquanpeng/one-api/releases/latest'
;
};
};
const
checkUpdate
=
async
()
=>
{
const
checkUpdate
=
async
()
=>
{
const
res
=
await
API
.
get
(
const
res
=
await
API
.
get
(
'https://api.github.com/repos/songquanpeng/one-api/releases/latest'
'https://api.github.com/repos/songquanpeng/one-api/releases/latest'
,
);
);
const
{
tag_name
,
body
}
=
res
.
data
;
const
{
tag_name
,
body
}
=
res
.
data
;
if
(
tag_name
===
process
.
env
.
REACT_APP_VERSION
)
{
if
(
tag_name
===
process
.
env
.
REACT_APP_VERSION
)
{
...
@@ -148,7 +157,7 @@ const OtherSetting = () => {
...
@@ -148,7 +157,7 @@ const OtherSetting = () => {
}
else
{
}
else
{
setUpdateData
({
setUpdateData
({
tag_name
:
tag_name
,
tag_name
:
tag_name
,
content
:
marked
.
parse
(
body
)
content
:
marked
.
parse
(
body
)
,
});
});
setShowUpdateModal
(
true
);
setShowUpdateModal
(
true
);
}
}
...
@@ -175,13 +184,15 @@ const OtherSetting = () => {
...
@@ -175,13 +184,15 @@ const OtherSetting = () => {
getOptions
();
getOptions
();
},
[]);
},
[]);
return
(
return
(
<
Row
>
<
Row
>
<
Col
span
=
{
24
}
>
<
Col
span
=
{
24
}
>
{
/* 通用设置 */
}
{
/* 通用设置 */
}
<
Form
values
=
{
inputs
}
getFormApi
=
{
formAPI
=>
formAPISettingGeneral
.
current
=
formAPI
}
<
Form
style
=
{{
marginBottom
:
15
}}
>
values
=
{
inputs
}
getFormApi
=
{(
formAPI
)
=>
(
formAPISettingGeneral
.
current
=
formAPI
)}
style
=
{{
marginBottom
:
15
}}
>
<
Form
.
Section
text
=
{
'通用设置'
}
>
<
Form
.
Section
text
=
{
'通用设置'
}
>
<
Form
.
TextArea
<
Form
.
TextArea
label
=
{
'公告'
}
label
=
{
'公告'
}
...
@@ -191,12 +202,17 @@ const OtherSetting = () => {
...
@@ -191,12 +202,17 @@ const OtherSetting = () => {
style
=
{{
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
fontFamily
:
'JetBrains Mono, Consolas'
}}
autosize
=
{{
minRows
:
6
,
maxRows
:
12
}}
autosize
=
{{
minRows
:
6
,
maxRows
:
12
}}
/
>
/
>
<
Button
onClick
=
{
submitNotice
}
loading
=
{
loadingInput
[
'Notice'
]}
>
设置公告
<
/Button
>
<
Button
onClick
=
{
submitNotice
}
loading
=
{
loadingInput
[
'Notice'
]}
>
设置公告
<
/Button
>
<
/Form.Section
>
<
/Form.Section
>
<
/Form
>
<
/Form
>
{
/* 个性化设置 */
}
{
/* 个性化设置 */
}
<
Form
values
=
{
inputs
}
getFormApi
=
{
formAPI
=>
formAPIPersonalization
.
current
=
formAPI
}
<
Form
style
=
{{
marginBottom
:
15
}}
>
values
=
{
inputs
}
getFormApi
=
{(
formAPI
)
=>
(
formAPIPersonalization
.
current
=
formAPI
)}
style
=
{{
marginBottom
:
15
}}
>
<
Form
.
Section
text
=
{
'个性化设置'
}
>
<
Form
.
Section
text
=
{
'个性化设置'
}
>
<
Form
.
Input
<
Form
.
Input
label
=
{
'系统名称'
}
label
=
{
'系统名称'
}
...
@@ -204,48 +220,69 @@ const OtherSetting = () => {
...
@@ -204,48 +220,69 @@ const OtherSetting = () => {
field
=
{
'SystemName'
}
field
=
{
'SystemName'
}
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Button
onClick
=
{
submitSystemName
}
loading
=
{
loadingInput
[
'SystemName'
]}
>
设置系统名称
<
/Button
>
<
Button
onClick
=
{
submitSystemName
}
loading
=
{
loadingInput
[
'SystemName'
]}
>
设置系统名称
<
/Button
>
<
Form
.
Input
<
Form
.
Input
label
=
{
'Logo 图片地址'
}
label
=
{
'Logo 图片地址'
}
placeholder
=
{
'在此输入 Logo 图片地址'
}
placeholder
=
{
'在此输入 Logo 图片地址'
}
field
=
{
'Logo'
}
field
=
{
'Logo'
}
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Button
onClick
=
{
submitLogo
}
loading
=
{
loadingInput
[
'Logo'
]}
>
设置
Logo
<
/Button
>
<
Button
onClick
=
{
submitLogo
}
loading
=
{
loadingInput
[
'Logo'
]}
>
设置
Logo
<
/Button
>
<
Form
.
TextArea
<
Form
.
TextArea
label
=
{
'首页内容'
}
label
=
{
'首页内容'
}
placeholder
=
{
'在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'
}
placeholder
=
{
'在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页。'
}
field
=
{
'HomePageContent'
}
field
=
{
'HomePageContent'
}
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
style
=
{{
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
fontFamily
:
'JetBrains Mono, Consolas'
}}
autosize
=
{{
minRows
:
6
,
maxRows
:
12
}}
autosize
=
{{
minRows
:
6
,
maxRows
:
12
}}
/
>
/
>
<
Button
onClick
=
{()
=>
submitOption
(
'HomePageContent'
)}
<
Button
loading
=
{
loadingInput
[
'HomePageContent'
]}
>
设置首页内容
<
/Button
>
onClick
=
{()
=>
submitOption
(
'HomePageContent'
)}
loading
=
{
loadingInput
[
'HomePageContent'
]}
>
设置首页内容
<
/Button
>
<
Form
.
TextArea
<
Form
.
TextArea
label
=
{
'关于'
}
label
=
{
'关于'
}
placeholder
=
{
'在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'
}
placeholder
=
{
'在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面。'
}
field
=
{
'About'
}
field
=
{
'About'
}
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
style
=
{{
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
fontFamily
:
'JetBrains Mono, Consolas'
}}
autosize
=
{{
minRows
:
6
,
maxRows
:
12
}}
autosize
=
{{
minRows
:
6
,
maxRows
:
12
}}
/
>
/
>
<
Button
onClick
=
{
submitAbout
}
loading
=
{
loadingInput
[
'About'
]}
>
设置关于
<
/Button
>
<
Button
onClick
=
{
submitAbout
}
loading
=
{
loadingInput
[
'About'
]}
>
设置关于
<
/Button
>
{
/* */
}
{
/* */
}
<
Banner
<
Banner
fullMode
=
{
false
}
fullMode
=
{
false
}
type
=
"info"
type
=
'info'
description
=
"移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。"
description
=
'移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目。'
closeIcon
=
{
null
}
closeIcon
=
{
null
}
style
=
{{
marginTop
:
15
}}
style
=
{{
marginTop
:
15
}}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
label
=
{
'页脚'
}
label
=
{
'页脚'
}
placeholder
=
{
'在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'
}
placeholder
=
{
'在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码'
}
field
=
{
'Footer'
}
field
=
{
'Footer'
}
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Button
onClick
=
{
submitFooter
}
loading
=
{
loadingInput
[
'Footer'
]}
>
设置页脚
<
/Button
>
<
Button
onClick
=
{
submitFooter
}
loading
=
{
loadingInput
[
'Footer'
]}
>
设置页脚
<
/Button
>
<
/Form.Section
>
<
/Form.Section
>
<
/Form
>
<
/Form
>
<
/Col
>
<
/Col
>
...
...
web/src/components/PasswordResetConfirm.js
View file @
eb955ae4
...
@@ -6,7 +6,7 @@ import { useSearchParams } from 'react-router-dom';
...
@@ -6,7 +6,7 @@ import { useSearchParams } from 'react-router-dom';
const
PasswordResetConfirm
=
()
=>
{
const
PasswordResetConfirm
=
()
=>
{
const
[
inputs
,
setInputs
]
=
useState
({
const
[
inputs
,
setInputs
]
=
useState
({
email
:
''
,
email
:
''
,
token
:
''
token
:
''
,
});
});
const
{
email
,
token
}
=
inputs
;
const
{
email
,
token
}
=
inputs
;
...
@@ -23,7 +23,7 @@ const PasswordResetConfirm = () => {
...
@@ -23,7 +23,7 @@ const PasswordResetConfirm = () => {
let
email
=
searchParams
.
get
(
'email'
);
let
email
=
searchParams
.
get
(
'email'
);
setInputs
({
setInputs
({
token
,
token
,
email
email
,
});
});
},
[]);
},
[]);
...
@@ -46,7 +46,7 @@ const PasswordResetConfirm = () => {
...
@@ -46,7 +46,7 @@ const PasswordResetConfirm = () => {
setLoading
(
true
);
setLoading
(
true
);
const
res
=
await
API
.
post
(
`/api/user/reset`
,
{
const
res
=
await
API
.
post
(
`/api/user/reset`
,
{
email
,
email
,
token
token
,
});
});
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -61,29 +61,29 @@ const PasswordResetConfirm = () => {
...
@@ -61,29 +61,29 @@ const PasswordResetConfirm = () => {
}
}
return
(
return
(
<
Grid
textAlign
=
"center"
style
=
{{
marginTop
:
'48px'
}}
>
<
Grid
textAlign
=
'center'
style
=
{{
marginTop
:
'48px'
}}
>
<
Grid
.
Column
style
=
{{
maxWidth
:
450
}}
>
<
Grid
.
Column
style
=
{{
maxWidth
:
450
}}
>
<
Header
as
=
"h2"
color
=
""
textAlign
=
"center"
>
<
Header
as
=
'h2'
color
=
''
textAlign
=
'center'
>
<
Image
src
=
"/logo.png"
/>
密码重置确认
<
Image
src
=
'/logo.png'
/>
密码重置确认
<
/Header
>
<
/Header
>
<
Form
size
=
"large"
>
<
Form
size
=
'large'
>
<
Segment
>
<
Segment
>
<
Form
.
Input
<
Form
.
Input
fluid
fluid
icon
=
"mail"
icon
=
'mail'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"邮箱地址"
placeholder
=
'邮箱地址'
name
=
"email"
name
=
'email'
value
=
{
email
}
value
=
{
email
}
readOnly
readOnly
/>
/>
{
newPassword
&&
(
{
newPassword
&&
(
<
Form
.
Input
<
Form
.
Input
fluid
fluid
icon
=
"lock"
icon
=
'lock'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"新密码"
placeholder
=
'新密码'
name
=
"newPassword"
name
=
'newPassword'
value
=
{
newPassword
}
value
=
{
newPassword
}
readOnly
readOnly
onClick
=
{(
e
)
=>
{
onClick
=
{(
e
)
=>
{
...
@@ -94,9 +94,9 @@ const PasswordResetConfirm = () => {
...
@@ -94,9 +94,9 @@ const PasswordResetConfirm = () => {
/
>
/
>
)}
)}
<
Button
<
Button
color
=
"green"
color
=
'green'
fluid
fluid
size
=
"large"
size
=
'large'
onClick
=
{
handleSubmit
}
onClick
=
{
handleSubmit
}
loading
=
{
loading
}
loading
=
{
loading
}
disabled
=
{
disableButton
}
disabled
=
{
disableButton
}
...
...
web/src/components/PasswordResetForm.js
View file @
eb955ae4
...
@@ -5,7 +5,7 @@ import Turnstile from 'react-turnstile';
...
@@ -5,7 +5,7 @@ import Turnstile from 'react-turnstile';
const
PasswordResetForm
=
()
=>
{
const
PasswordResetForm
=
()
=>
{
const
[
inputs
,
setInputs
]
=
useState
({
const
[
inputs
,
setInputs
]
=
useState
({
email
:
''
email
:
''
,
});
});
const
{
email
}
=
inputs
;
const
{
email
}
=
inputs
;
...
@@ -31,7 +31,7 @@ const PasswordResetForm = () => {
...
@@ -31,7 +31,7 @@ const PasswordResetForm = () => {
function
handleChange
(
e
)
{
function
handleChange
(
e
)
{
const
{
name
,
value
}
=
e
.
target
;
const
{
name
,
value
}
=
e
.
target
;
setInputs
(
inputs
=>
({
...
inputs
,
[
name
]:
value
}));
setInputs
(
(
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
}
}
async
function
handleSubmit
(
e
)
{
async
function
handleSubmit
(
e
)
{
...
@@ -43,7 +43,7 @@ const PasswordResetForm = () => {
...
@@ -43,7 +43,7 @@ const PasswordResetForm = () => {
}
}
setLoading
(
true
);
setLoading
(
true
);
const
res
=
await
API
.
get
(
const
res
=
await
API
.
get
(
`/api/reset_password?email=
${
email
}
&turnstile=
${
turnstileToken
}
`
`/api/reset_password?email=
${
email
}
&turnstile=
${
turnstileToken
}
`
,
);
);
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -56,19 +56,19 @@ const PasswordResetForm = () => {
...
@@ -56,19 +56,19 @@ const PasswordResetForm = () => {
}
}
return
(
return
(
<
Grid
textAlign
=
"center"
style
=
{{
marginTop
:
'48px'
}}
>
<
Grid
textAlign
=
'center'
style
=
{{
marginTop
:
'48px'
}}
>
<
Grid
.
Column
style
=
{{
maxWidth
:
450
}}
>
<
Grid
.
Column
style
=
{{
maxWidth
:
450
}}
>
<
Header
as
=
"h2"
color
=
""
textAlign
=
"center"
>
<
Header
as
=
'h2'
color
=
''
textAlign
=
'center'
>
<
Image
src
=
"/logo.png"
/>
密码重置
<
Image
src
=
'/logo.png'
/>
密码重置
<
/Header
>
<
/Header
>
<
Form
size
=
"large"
>
<
Form
size
=
'large'
>
<
Segment
>
<
Segment
>
<
Form
.
Input
<
Form
.
Input
fluid
fluid
icon
=
"mail"
icon
=
'mail'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"邮箱地址"
placeholder
=
'邮箱地址'
name
=
"email"
name
=
'email'
value
=
{
email
}
value
=
{
email
}
onChange
=
{
handleChange
}
onChange
=
{
handleChange
}
/
>
/
>
...
@@ -83,9 +83,9 @@ const PasswordResetForm = () => {
...
@@ -83,9 +83,9 @@ const PasswordResetForm = () => {
<><
/
>
<><
/
>
)}
)}
<
Button
<
Button
color
=
"green"
color
=
'green'
fluid
fluid
size
=
"large"
size
=
'large'
onClick
=
{
handleSubmit
}
onClick
=
{
handleSubmit
}
loading
=
{
loading
}
loading
=
{
loading
}
disabled
=
{
disableButton
}
disabled
=
{
disableButton
}
...
...
web/src/components/PersonalSetting.js
View file @
eb955ae4
import
React
,
{
useContext
,
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useContext
,
useEffect
,
useState
}
from
'react'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
{
API
,
copy
,
isRoot
,
showError
,
showInfo
,
showSuccess
}
from
'../helpers'
;
import
{
API
,
copy
,
isRoot
,
showError
,
showInfo
,
showSuccess
,
}
from
'../helpers'
;
import
Turnstile
from
'react-turnstile'
;
import
Turnstile
from
'react-turnstile'
;
import
{
UserContext
}
from
'../context/User'
;
import
{
UserContext
}
from
'../context/User'
;
import
{
onGitHubOAuthClicked
}
from
'./utils'
;
import
{
onGitHubOAuthClicked
}
from
'./utils'
;
...
@@ -17,9 +24,14 @@ import {
...
@@ -17,9 +24,14 @@ import {
Modal
,
Modal
,
Space
,
Space
,
Tag
,
Tag
,
Typography
Typography
,
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
{
getQuotaPerUnit
,
renderQuota
,
renderQuotaWithPrompt
,
stringToColor
}
from
'../helpers/render'
;
import
{
getQuotaPerUnit
,
renderQuota
,
renderQuotaWithPrompt
,
stringToColor
,
}
from
'../helpers/render'
;
import
TelegramLoginButton
from
'react-telegram-login'
;
import
TelegramLoginButton
from
'react-telegram-login'
;
const
PersonalSetting
=
()
=>
{
const
PersonalSetting
=
()
=>
{
...
@@ -32,7 +44,7 @@ const PersonalSetting = () => {
...
@@ -32,7 +44,7 @@ const PersonalSetting = () => {
email
:
''
,
email
:
''
,
self_account_deletion_confirmation
:
''
,
self_account_deletion_confirmation
:
''
,
set_new_password
:
''
,
set_new_password
:
''
,
set_new_password_confirmation
:
''
set_new_password_confirmation
:
''
,
});
});
const
[
status
,
setStatus
]
=
useState
({});
const
[
status
,
setStatus
]
=
useState
({});
const
[
showChangePasswordModal
,
setShowChangePasswordModal
]
=
useState
(
false
);
const
[
showChangePasswordModal
,
setShowChangePasswordModal
]
=
useState
(
false
);
...
@@ -67,11 +79,9 @@ const PersonalSetting = () => {
...
@@ -67,11 +79,9 @@ const PersonalSetting = () => {
setTurnstileSiteKey
(
status
.
turnstile_site_key
);
setTurnstileSiteKey
(
status
.
turnstile_site_key
);
}
}
}
}
getUserData
().
then
(
getUserData
().
then
((
res
)
=>
{
(
res
)
=>
{
console
.
log
(
userState
);
console
.
log
(
userState
);
}
});
);
loadModels
().
then
();
loadModels
().
then
();
getAffLink
().
then
();
getAffLink
().
then
();
setTransferAmount
(
getQuotaPerUnit
());
setTransferAmount
(
getQuotaPerUnit
());
...
@@ -173,7 +183,7 @@ const PersonalSetting = () => {
...
@@ -173,7 +183,7 @@ const PersonalSetting = () => {
const
bindWeChat
=
async
()
=>
{
const
bindWeChat
=
async
()
=>
{
if
(
inputs
.
wechat_verification_code
===
''
)
return
;
if
(
inputs
.
wechat_verification_code
===
''
)
return
;
const
res
=
await
API
.
get
(
const
res
=
await
API
.
get
(
`/api/oauth/wechat/bind?code=
${
inputs
.
wechat_verification_code
}
`
`/api/oauth/wechat/bind?code=
${
inputs
.
wechat_verification_code
}
`
,
);
);
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -189,12 +199,9 @@ const PersonalSetting = () => {
...
@@ -189,12 +199,9 @@ const PersonalSetting = () => {
showError
(
'两次输入的密码不一致!'
);
showError
(
'两次输入的密码不一致!'
);
return
;
return
;
}
}
const
res
=
await
API
.
put
(
const
res
=
await
API
.
put
(
`/api/user/self`
,
{
`/api/user/self`
,
password
:
inputs
.
set_new_password
,
{
});
password
:
inputs
.
set_new_password
}
);
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
showSuccess
(
'密码修改成功!'
);
showSuccess
(
'密码修改成功!'
);
...
@@ -210,12 +217,9 @@ const PersonalSetting = () => {
...
@@ -210,12 +217,9 @@ const PersonalSetting = () => {
showError
(
'划转金额最低为'
+
renderQuota
(
getQuotaPerUnit
()));
showError
(
'划转金额最低为'
+
renderQuota
(
getQuotaPerUnit
()));
return
;
return
;
}
}
const
res
=
await
API
.
post
(
const
res
=
await
API
.
post
(
`/api/user/aff_transfer`
,
{
`/api/user/aff_transfer`
,
quota
:
transferAmount
,
{
});
quota
:
transferAmount
}
);
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
showSuccess
(
message
);
showSuccess
(
message
);
...
@@ -238,7 +242,7 @@ const PersonalSetting = () => {
...
@@ -238,7 +242,7 @@ const PersonalSetting = () => {
}
}
setLoading
(
true
);
setLoading
(
true
);
const
res
=
await
API
.
get
(
const
res
=
await
API
.
get
(
`/api/verification?email=
${
inputs
.
email
}
&turnstile=
${
turnstileToken
}
`
`/api/verification?email=
${
inputs
.
email
}
&turnstile=
${
turnstileToken
}
`
,
);
);
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -256,7 +260,7 @@ const PersonalSetting = () => {
...
@@ -256,7 +260,7 @@ const PersonalSetting = () => {
}
}
setLoading
(
true
);
setLoading
(
true
);
const
res
=
await
API
.
get
(
const
res
=
await
API
.
get
(
`/api/oauth/email/bind?email=
${
inputs
.
email
}
&code=
${
inputs
.
email_verification_code
}
`
`/api/oauth/email/bind?email=
${
inputs
.
email
}
&code=
${
inputs
.
email_verification_code
}
`
,
);
);
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -295,7 +299,7 @@ const PersonalSetting = () => {
...
@@ -295,7 +299,7 @@ const PersonalSetting = () => {
<
Layout
>
<
Layout
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
Modal
<
Modal
title
=
"请输入要划转的数量"
title
=
'请输入要划转的数量'
visible
=
{
openTransfer
}
visible
=
{
openTransfer
}
onOk
=
{
transfer
}
onOk
=
{
transfer
}
onCancel
=
{
handleCancel
}
onCancel
=
{
handleCancel
}
...
@@ -305,13 +309,25 @@ const PersonalSetting = () => {
...
@@ -305,13 +309,25 @@ const PersonalSetting = () => {
>
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
{
`可用额度
${
renderQuotaWithPrompt
(
userState
?.
user
?.
aff_quota
)}
`}</Typography.Text>
<
Typography
.
Text
>
{
`可用额度
${
renderQuotaWithPrompt
(
userState
?.
user
?.
aff_quota
)}
`}</Typography.Text>
<Input style={{ marginTop: 5 }} value={userState?.user?.aff_quota} disabled={true}></Input>
<Input
style={{ marginTop: 5 }}
value={userState?.user?.aff_quota}
disabled={true}
></Input>
</div>
</div>
<div style={{ marginTop: 20 }}>
<div style={{ marginTop: 20 }}>
<Typography.Text>{`
划转额度
$
{
renderQuotaWithPrompt
(
transferAmount
)}
最低
` + renderQuota(getQuotaPerUnit())}</Typography.Text>
<Typography.Text>
{`
划转额度
$
{
renderQuotaWithPrompt
(
transferAmount
)}
最低
` +
renderQuota(getQuotaPerUnit())}
</Typography.Text>
<div>
<div>
<InputNumber min={0} style={{ marginTop: 5 }} value={transferAmount}
<InputNumber
onChange={(value) => setTransferAmount(value)} disabled={false}></InputNumber>
min={0}
style={{ marginTop: 5 }}
value={transferAmount}
onChange={(value) => setTransferAmount(value)}
disabled={false}
></InputNumber>
</div>
</div>
</div>
</div>
</Modal>
</Modal>
...
@@ -319,27 +335,45 @@ const PersonalSetting = () => {
...
@@ -319,27 +335,45 @@ const PersonalSetting = () => {
<Card
<Card
title={
title={
<Card.Meta
<Card.Meta
avatar={<Avatar size="default" color={stringToColor(getUsername())}
avatar={
style={{ marginRight: 4 }}>
<Avatar
{typeof getUsername() === 'string' && getUsername().slice(0, 1)}
size='default'
</Avatar>}
color={stringToColor(getUsername())}
style={{ marginRight: 4 }}
>
{typeof getUsername() === 'string' &&
getUsername().slice(0, 1)}
</Avatar>
}
title={<Typography.Text>{getUsername()}</Typography.Text>}
title={<Typography.Text>{getUsername()}</Typography.Text>}
description={isRoot() ? <Tag color="red">管理员</Tag> : <Tag color="blue">普通用户</Tag>}
description={
isRoot() ? (
<Tag color='red'>管理员</Tag>
) : (
<Tag color='blue'>普通用户</Tag>
)
}
></Card.Meta>
></Card.Meta>
}
}
headerExtraContent={
headerExtraContent={
<>
<>
<Space vertical align=
"start"
>
<Space vertical align=
'start'
>
<Tag color=
"green"
>{'ID: ' + userState?.user?.id}</Tag>
<Tag color=
'green'
>{'ID: ' + userState?.user?.id}</Tag>
<Tag color=
"blue"
>{userState?.user?.group}</Tag>
<Tag color=
'blue'
>{userState?.user?.group}</Tag>
</Space>
</Space>
</>
</>
}
}
footer={
footer={
<Descriptions row>
<Descriptions row>
<Descriptions.Item itemKey="当前余额">{renderQuota(userState?.user?.quota)}</Descriptions.Item>
<Descriptions.Item itemKey='当前余额'>
<Descriptions.Item itemKey="历史消耗">{renderQuota(userState?.user?.used_quota)}</Descriptions.Item>
{renderQuota(userState?.user?.quota)}
<Descriptions.Item itemKey="请求次数">{userState.user?.request_count}</Descriptions.Item>
</Descriptions.Item>
<Descriptions.Item itemKey='历史消耗'>
{renderQuota(userState?.user?.used_quota)}
</Descriptions.Item>
<Descriptions.Item itemKey='请求次数'>
{userState.user?.request_count}
</Descriptions.Item>
</Descriptions>
</Descriptions>
}
}
>
>
...
@@ -347,15 +381,18 @@ const PersonalSetting = () => {
...
@@ -347,15 +381,18 @@ const PersonalSetting = () => {
<div style={{ marginTop: 10 }}>
<div style={{ marginTop: 10 }}>
<Space wrap>
<Space wrap>
{models.map((model) => (
{models.map((model) => (
<Tag key={model} color="cyan" onClick={() => {
<Tag
key={model}
color='cyan'
onClick={() => {
copyText(model);
copyText(model);
}}>
}}
>
{model}
{model}
</Tag>
</Tag>
))}
))}
</Space>
</Space>
</div>
</div>
</Card>
</Card>
<Card
<Card
footer={
footer={
...
@@ -373,18 +410,25 @@ const PersonalSetting = () => {
...
@@ -373,18 +410,25 @@ const PersonalSetting = () => {
<Typography.Title heading={6}>邀请信息</Typography.Title>
<Typography.Title heading={6}>邀请信息</Typography.Title>
<div style={{ marginTop: 10 }}>
<div style={{ marginTop: 10 }}>
<Descriptions row>
<Descriptions row>
<Descriptions.Item itemKey=
"待使用收益"
>
<Descriptions.Item itemKey=
'待使用收益'
>
<span style={{ color: 'rgba(var(--semi-red-5), 1)' }}>
<span style={{ color: 'rgba(var(--semi-red-5), 1)' }}>
{
{renderQuota(userState?.user?.aff_quota)}
renderQuota(userState?.user?.aff_quota)
}
</span>
</span>
<Button type={'secondary'} onClick={() => setOpenTransfer(true)} size={'small'}
<Button
style={{ marginLeft: 10 }}>划转</Button>
type={'secondary'}
onClick={() => setOpenTransfer(true)}
size={'small'}
style={{ marginLeft: 10 }}
>
划转
</Button>
</Descriptions.Item>
<Descriptions.Item itemKey='总收益'>
{renderQuota(userState?.user?.aff_history_quota)}
</Descriptions.Item>
<Descriptions.Item itemKey='邀请人数'>
{userState?.user?.aff_count}
</Descriptions.Item>
</Descriptions.Item>
<Descriptions.Item
itemKey="总收益">{renderQuota(userState?.user?.aff_history_quota)}</Descriptions.Item>
<Descriptions.Item itemKey="邀请人数">{userState?.user?.aff_count}</Descriptions.Item>
</Descriptions>
</Descriptions>
</div>
</div>
</Card>
</Card>
...
@@ -392,46 +436,71 @@ const PersonalSetting = () => {
...
@@ -392,46 +436,71 @@ const PersonalSetting = () => {
<Typography.Title heading={6}>个人信息</Typography.Title>
<Typography.Title heading={6}>个人信息</Typography.Title>
<div style={{ marginTop: 20 }}>
<div style={{ marginTop: 20 }}>
<Typography.Text strong>邮箱</Typography.Text>
<Typography.Text strong>邮箱</Typography.Text>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div
style={{ display: 'flex', justifyContent: 'space-between' }}
>
<div>
<div>
<Input
<Input
value={userState.user && userState.user.email !== '' ? userState.user.email : '未绑定'}
value={
userState.user && userState.user.email !== ''
? userState.user.email
: '未绑定'
}
readonly={true}
readonly={true}
></Input>
></Input>
</div>
</div>
<div>
<div>
<Button onClick={() => {
<Button
onClick={() => {
setShowEmailBindModal(true);
setShowEmailBindModal(true);
}}>{
}}
userState.user && userState.user.email !== '' ? '修改绑定' : '绑定邮箱'
>
}</Button>
{userState.user && userState.user.email !== ''
? '修改绑定'
: '绑定邮箱'}
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
<div style={{ marginTop: 10 }}>
<div style={{ marginTop: 10 }}>
<Typography.Text strong>微信</Typography.Text>
<Typography.Text strong>微信</Typography.Text>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div
style={{ display: 'flex', justifyContent: 'space-between' }}
>
<div>
<div>
<Input
<Input
value={userState.user && userState.user.wechat_id !== '' ? '已绑定' : '未绑定'}
value={
userState.user && userState.user.wechat_id !== ''
? '已绑定'
: '未绑定'
}
readonly={true}
readonly={true}
></Input>
></Input>
</div>
</div>
<div>
<div>
<Button disabled={(userState.user && userState.user.wechat_id !== '') || !status.wechat_login}>
<Button
{
disabled={
status.wechat_login ? '绑定' : '未启用'
(userState.user && userState.user.wechat_id !== '') ||
!status.wechat_login
}
}
>
{status.wechat_login ? '绑定' : '未启用'}
</Button>
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
<div style={{ marginTop: 10 }}>
<div style={{ marginTop: 10 }}>
<Typography.Text strong>GitHub</Typography.Text>
<Typography.Text strong>GitHub</Typography.Text>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div
style={{ display: 'flex', justifyContent: 'space-between' }}
>
<div>
<div>
<Input
<Input
value={userState.user && userState.user.github_id !== '' ? userState.user.github_id : '未绑定'}
value={
userState.user && userState.user.github_id !== ''
? userState.user.github_id
: '未绑定'
}
readonly={true}
readonly={true}
></Input>
></Input>
</div>
</div>
...
@@ -440,11 +509,12 @@ const PersonalSetting = () => {
...
@@ -440,11 +509,12 @@ const PersonalSetting = () => {
onClick={() => {
onClick={() => {
onGitHubOAuthClicked(status.github_client_id);
onGitHubOAuthClicked(status.github_client_id);
}}
}}
disabled={(userState.user && userState.user.github_id !== '') || !status.github_oauth}
disabled={
>
(userState.user && userState.user.github_id !== '') ||
{
!status.github_oauth
status.github_oauth ? '绑定' : '未启用'
}
}
>
{status.github_oauth ? '绑定' : '未启用'}
</Button>
</Button>
</div>
</div>
</div>
</div>
...
@@ -452,33 +522,56 @@ const PersonalSetting = () => {
...
@@ -452,33 +522,56 @@ const PersonalSetting = () => {
<div style={{ marginTop: 10 }}>
<div style={{ marginTop: 10 }}>
<Typography.Text strong>Telegram</Typography.Text>
<Typography.Text strong>Telegram</Typography.Text>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div
style={{ display: 'flex', justifyContent: 'space-between' }}
>
<div>
<div>
<Input
<Input
value={userState.user && userState.user.telegram_id !== '' ? userState.user.telegram_id : '未绑定'}
value={
userState.user && userState.user.telegram_id !== ''
? userState.user.telegram_id
: '未绑定'
}
readonly={true}
readonly={true}
></Input>
></Input>
</div>
</div>
<div>
<div>
{status.telegram_oauth ?
{status.telegram_oauth ? (
userState.user.telegram_id !== '' ? <Button disabled={true}>已绑定</Button>
userState.user.telegram_id !== '' ? (
: <TelegramLoginButton dataAuthUrl="/api/oauth/telegram/bind"
<Button disabled={true}>已绑定</Button>
botName={status.telegram_bot_name} />
) : (
: <Button disabled={true}>未启用</Button>
<TelegramLoginButton
}
dataAuthUrl='/api/oauth/telegram/bind'
botName={status.telegram_bot_name}
/>
)
) : (
<Button disabled={true}>未启用</Button>
)}
</div>
</div>
</div>
</div>
</div>
</div>
<div style={{ marginTop: 10 }}>
<div style={{ marginTop: 10 }}>
<Space>
<Space>
<Button onClick={generateAccessToken}>生成系统访问令牌</Button>
<Button onClick={generateAccessToken}>
<Button onClick={() => {
生成系统访问令牌
</Button>
<Button
onClick={() => {
setShowChangePasswordModal(true);
setShowChangePasswordModal(true);
}}>修改密码</Button>
}}
<Button type={'danger'} onClick={() => {
>
修改密码
</Button>
<Button
type={'danger'}
onClick={() => {
setShowAccountDeleteModal(true);
setShowAccountDeleteModal(true);
}}>删除个人账户</Button>
}}
>
删除个人账户
</Button>
</Space>
</Space>
{systemToken && (
{systemToken && (
...
@@ -489,8 +582,7 @@ const PersonalSetting = () => {
...
@@ -489,8 +582,7 @@ const PersonalSetting = () => {
style={{ marginTop: '10px' }}
style={{ marginTop: '10px' }}
/>
/>
)}
)}
{
{status.wechat_login && (
status.wechat_login && (
<Button
<Button
onClick={() => {
onClick={() => {
setShowWeChatBindModal(true);
setShowWeChatBindModal(true);
...
@@ -498,8 +590,7 @@ const PersonalSetting = () => {
...
@@ -498,8 +590,7 @@ const PersonalSetting = () => {
>
>
绑定微信账号
绑定微信账号
</Button>
</Button>
)
)}
}
<Modal
<Modal
onCancel={() => setShowWeChatBindModal(false)}
onCancel={() => setShowWeChatBindModal(false)}
// onOpen={() => setShowWeChatBindModal(true)}
// onOpen={() => setShowWeChatBindModal(true)}
...
@@ -513,12 +604,14 @@ const PersonalSetting = () => {
...
@@ -513,12 +604,14 @@ const PersonalSetting = () => {
</p>
</p>
</div>
</div>
<Input
<Input
placeholder=
"验证码"
placeholder=
'验证码'
name=
"wechat_verification_code"
name=
'wechat_verification_code'
value={inputs.wechat_verification_code}
value={inputs.wechat_verification_code}
onChange={(v) => handleInputChange('wechat_verification_code', v)}
onChange={(v) =>
handleInputChange('wechat_verification_code', v)
}
/>
/>
<Button color=
"" fluid size="large"
onClick={bindWeChat}>
<Button color=
'' fluid size='large'
onClick={bindWeChat}>
绑定
绑定
</Button>
</Button>
</Modal>
</Modal>
...
@@ -534,26 +627,36 @@ const PersonalSetting = () => {
...
@@ -534,26 +627,36 @@ const PersonalSetting = () => {
maskClosable={false}
maskClosable={false}
>
>
<Typography.Title heading={6}>绑定邮箱地址</Typography.Title>
<Typography.Title heading={6}>绑定邮箱地址</Typography.Title>
<div style={{ marginTop: 20, display: 'flex', justifyContent: 'space-between' }}>
<div
style={{
marginTop: 20,
display: 'flex',
justifyContent: 'space-between',
}}
>
<Input
<Input
fluid
fluid
placeholder=
"输入邮箱地址"
placeholder=
'输入邮箱地址'
onChange={(value) => handleInputChange('email', value)}
onChange={(value) => handleInputChange('email', value)}
name=
"email"
name=
'email'
type=
"email"
type=
'email'
/>
/>
<Button onClick={sendVerificationCode}
<Button
disabled={disableButton || loading}>
onClick={sendVerificationCode}
disabled={disableButton || loading}
>
{disableButton ? `
重新发送
(
$
{
countdown
})
` : '获取验证码'}
{disableButton ? `
重新发送
(
$
{
countdown
})
` : '获取验证码'}
</Button>
</Button>
</div>
</div>
<div style={{ marginTop: 10 }}>
<div style={{ marginTop: 10 }}>
<Input
<Input
fluid
fluid
placeholder=
"验证码"
placeholder=
'验证码'
name=
"email_verification_code"
name=
'email_verification_code'
value={inputs.email_verification_code}
value={inputs.email_verification_code}
onChange={(value) => handleInputChange('email_verification_code', value)}
onChange={(value) =>
handleInputChange('email_verification_code', value)
}
/>
/>
</div>
</div>
{turnstileEnabled ? (
{turnstileEnabled ? (
...
@@ -576,17 +679,22 @@ const PersonalSetting = () => {
...
@@ -576,17 +679,22 @@ const PersonalSetting = () => {
>
>
<div style={{ marginTop: 20 }}>
<div style={{ marginTop: 20 }}>
<Banner
<Banner
type=
"danger"
type=
'danger'
description=
"您正在删除自己的帐户,将清空所有数据且不可恢复"
description=
'您正在删除自己的帐户,将清空所有数据且不可恢复'
closeIcon={null}
closeIcon={null}
/>
/>
</div>
</div>
<div style={{ marginTop: 20 }}>
<div style={{ marginTop: 20 }}>
<Input
<Input
placeholder={`
输入你的账户名
$
{
userState
?.
user
?.
username
}
以确认删除
`}
placeholder={`
输入你的账户名
$
{
userState
?.
user
?.
username
}
以确认删除
`}
name=
"self_account_deletion_confirmation"
name=
'self_account_deletion_confirmation'
value={inputs.self_account_deletion_confirmation}
value={inputs.self_account_deletion_confirmation}
onChange={(value) => handleInputChange('self_account_deletion_confirmation', value)}
onChange={(value) =>
handleInputChange(
'self_account_deletion_confirmation',
value,
)
}
/>
/>
{turnstileEnabled ? (
{turnstileEnabled ? (
<Turnstile
<Turnstile
...
@@ -609,17 +717,21 @@ const PersonalSetting = () => {
...
@@ -609,17 +717,21 @@ const PersonalSetting = () => {
>
>
<div style={{ marginTop: 20 }}>
<div style={{ marginTop: 20 }}>
<Input
<Input
name=
"set_new_password"
name=
'set_new_password'
placeholder=
"新密码"
placeholder=
'新密码'
value={inputs.set_new_password}
value={inputs.set_new_password}
onChange={(value) => handleInputChange('set_new_password', value)}
onChange={(value) =>
handleInputChange('set_new_password', value)
}
/>
/>
<Input
<Input
style={{ marginTop: 20 }}
style={{ marginTop: 20 }}
name=
"set_new_password_confirmation"
name=
'set_new_password_confirmation'
placeholder=
"确认新密码"
placeholder=
'确认新密码'
value={inputs.set_new_password_confirmation}
value={inputs.set_new_password_confirmation}
onChange={(value) => handleInputChange('set_new_password_confirmation', value)}
onChange={(value) =>
handleInputChange('set_new_password_confirmation', value)
}
/>
/>
{turnstileEnabled ? (
{turnstileEnabled ? (
<Turnstile
<Turnstile
...
@@ -634,7 +746,6 @@ const PersonalSetting = () => {
...
@@ -634,7 +746,6 @@ const PersonalSetting = () => {
</div>
</div>
</Modal>
</Modal>
</div>
</div>
</Layout.Content>
</Layout.Content>
</Layout>
</Layout>
</div>
</div>
...
...
web/src/components/PrivateRoute.js
View file @
eb955ae4
...
@@ -2,10 +2,9 @@ import { Navigate } from 'react-router-dom';
...
@@ -2,10 +2,9 @@ import { Navigate } from 'react-router-dom';
import
{
history
}
from
'../helpers'
;
import
{
history
}
from
'../helpers'
;
function
PrivateRoute
({
children
})
{
function
PrivateRoute
({
children
})
{
if
(
!
localStorage
.
getItem
(
'user'
))
{
if
(
!
localStorage
.
getItem
(
'user'
))
{
return
<
Navigate
to
=
"/login"
state
=
{{
from
:
history
.
location
}}
/>
;
return
<
Navigate
to
=
'/login'
state
=
{{
from
:
history
.
location
}}
/>
;
}
}
return
children
;
return
children
;
}
}
...
...
web/src/components/RedemptionsTable.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
API
,
copy
,
showError
,
showSuccess
,
timestamp2string
}
from
'../helpers'
;
import
{
API
,
copy
,
showError
,
showSuccess
,
timestamp2string
,
}
from
'../helpers'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderQuota
}
from
'../helpers/render'
;
import
{
renderQuota
}
from
'../helpers/render'
;
import
{
Button
,
Form
,
Modal
,
Popconfirm
,
Popover
,
Table
,
Tag
}
from
'@douyinfe/semi-ui'
;
import
{
Button
,
Form
,
Modal
,
Popconfirm
,
Popover
,
Table
,
Tag
,
}
from
'@douyinfe/semi-ui'
;
import
EditRedemption
from
'../pages/Redemption/EditRedemption'
;
import
EditRedemption
from
'../pages/Redemption/EditRedemption'
;
function
renderTimestamp
(
timestamp
)
{
function
renderTimestamp
(
timestamp
)
{
return
(
return
<>
{
timestamp2string
(
timestamp
)}
<
/>
;
<>
{
timestamp2string
(
timestamp
)}
<
/
>
);
}
}
function
renderStatus
(
status
)
{
function
renderStatus
(
status
)
{
switch
(
status
)
{
switch
(
status
)
{
case
1
:
case
1
:
return
<
Tag
color
=
"green"
size
=
"large"
>
未使用
<
/Tag>
;
return
(
<
Tag
color
=
'green'
size
=
'large'
>
未使用
<
/Tag
>
);
case
2
:
case
2
:
return
<
Tag
color
=
"red"
size
=
"large"
>
已禁用
<
/Tag>
;
return
(
<
Tag
color
=
'red'
size
=
'large'
>
{
' '
}
已禁用
{
' '
}
<
/Tag
>
);
case
3
:
case
3
:
return
<
Tag
color
=
"grey"
size
=
"large"
>
已使用
<
/Tag>
;
return
(
<
Tag
color
=
'grey'
size
=
'large'
>
{
' '
}
已使用
{
' '
}
<
/Tag
>
);
default
:
default
:
return
<
Tag
color
=
"black"
size
=
"large"
>
未知状态
<
/Tag>
;
return
(
<
Tag
color
=
'black'
size
=
'large'
>
{
' '
}
未知状态
{
' '
}
<
/Tag
>
);
}
}
}
}
...
@@ -31,121 +60,115 @@ const RedemptionsTable = () => {
...
@@ -31,121 +60,115 @@ const RedemptionsTable = () => {
const
columns
=
[
const
columns
=
[
{
{
title
:
'ID'
,
title
:
'ID'
,
dataIndex
:
'id'
dataIndex
:
'id'
,
},
},
{
{
title
:
'名称'
,
title
:
'名称'
,
dataIndex
:
'name'
dataIndex
:
'name'
,
},
},
{
{
title
:
'状态'
,
title
:
'状态'
,
dataIndex
:
'status'
,
dataIndex
:
'status'
,
key
:
'status'
,
key
:
'status'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderStatus
(
text
)}
<
/div>
;
<
div
>
},
{
renderStatus
(
text
)}
<
/div
>
);
}
},
},
{
{
title
:
'额度'
,
title
:
'额度'
,
dataIndex
:
'quota'
,
dataIndex
:
'quota'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderQuota
(
parseInt
(
text
))}
<
/div>
;
<
div
>
},
{
renderQuota
(
parseInt
(
text
))}
<
/div
>
);
}
},
},
{
{
title
:
'创建时间'
,
title
:
'创建时间'
,
dataIndex
:
'created_time'
,
dataIndex
:
'created_time'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderTimestamp
(
text
)}
<
/div>
;
<
div
>
},
{
renderTimestamp
(
text
)}
<
/div
>
);
}
},
},
{
{
title
:
'兑换人ID'
,
title
:
'兑换人ID'
,
dataIndex
:
'used_user_id'
,
dataIndex
:
'used_user_id'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
text
===
0
?
'无'
:
text
}
<
/div>
;
<
div
>
},
{
text
===
0
?
'无'
:
text
}
<
/div
>
);
}
},
},
{
{
title
:
''
,
title
:
''
,
dataIndex
:
'operate'
,
dataIndex
:
'operate'
,
render
:
(
text
,
record
,
index
)
=>
(
render
:
(
text
,
record
,
index
)
=>
(
<
div
>
<
div
>
<
Popover
<
Popover
content
=
{
record
.
key
}
style
=
{{
padding
:
20
}}
position
=
'top'
>
content
=
{
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
>
record
.
key
查看
}
<
/Button
>
style
=
{{
padding
:
20
}}
position
=
"top"
>
<
Button
theme
=
"light"
type
=
"tertiary"
style
=
{{
marginRight
:
1
}}
>
查看
<
/Button
>
<
/Popover
>
<
/Popover
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
1
}}
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
(
text
)
=>
{
onClick
=
{
async
(
text
)
=>
{
await
copyText
(
record
.
key
);
await
copyText
(
record
.
key
);
}}
}}
>
复制
<
/Button
>
>
复制
<
/Button
>
<
Popconfirm
<
Popconfirm
title
=
"确定是否要删除此兑换码?"
title
=
'确定是否要删除此兑换码?'
content
=
"此修改将不可逆"
content
=
'此修改将不可逆'
okType
=
{
'danger'
}
okType
=
{
'danger'
}
position
=
{
'left'
}
position
=
{
'left'
}
onConfirm
=
{()
=>
{
onConfirm
=
{()
=>
{
manageRedemption
(
record
.
id
,
'delete'
,
record
).
then
(
manageRedemption
(
record
.
id
,
'delete'
,
record
).
then
(()
=>
{
()
=>
{
removeRecord
(
record
.
key
);
removeRecord
(
record
.
key
);
}
});
);
}}
}}
>
>
<
Button
theme
=
"light"
type
=
"danger"
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
{
{
record
.
status
===
1
?
(
record
.
status
===
1
?
<
Button
<
Button
theme
=
"light"
type
=
"warning"
style
=
{{
marginRight
:
1
}}
onClick
=
{
theme
=
'light'
async
()
=>
{
type
=
'warning'
manageRedemption
(
style
=
{{
marginRight
:
1
}}
record
.
id
,
onClick
=
{
async
()
=>
{
'disable'
,
manageRedemption
(
record
.
id
,
'disable'
,
record
);
record
}}
);
>
}
禁用
}
>
禁用
<
/Button>
:
<
/Button
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
1
}}
onClick
=
{
)
:
(
async
()
=>
{
<
Button
manageRedemption
(
theme
=
'light'
record
.
id
,
type
=
'secondary'
'enable'
,
style
=
{{
marginRight
:
1
}}
record
onClick
=
{
async
()
=>
{
);
manageRedemption
(
record
.
id
,
'enable'
,
record
);
}
}}
}
disabled
=
{
record
.
status
===
3
}
>
启用
<
/Button
>
disabled
=
{
record
.
status
===
3
}
}
>
<
Button
theme
=
"light"
type
=
"tertiary"
style
=
{{
marginRight
:
1
}}
onClick
=
{
启用
()
=>
{
<
/Button
>
)}
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
onClick
=
{()
=>
{
setEditingRedemption
(
record
);
setEditingRedemption
(
record
);
setShowEdit
(
true
);
setShowEdit
(
true
);
}
}}
}
disabled
=
{
record
.
status
!==
1
}
>
编辑
<
/Button
>
disabled
=
{
record
.
status
!==
1
}
>
编辑
<
/Button
>
<
/div
>
<
/div
>
)
)
,
}
}
,
];
];
const
[
redemptions
,
setRedemptions
]
=
useState
([]);
const
[
redemptions
,
setRedemptions
]
=
useState
([]);
...
@@ -156,7 +179,7 @@ const RedemptionsTable = () => {
...
@@ -156,7 +179,7 @@ const RedemptionsTable = () => {
const
[
tokenCount
,
setTokenCount
]
=
useState
(
ITEMS_PER_PAGE
);
const
[
tokenCount
,
setTokenCount
]
=
useState
(
ITEMS_PER_PAGE
);
const
[
selectedKeys
,
setSelectedKeys
]
=
useState
([]);
const
[
selectedKeys
,
setSelectedKeys
]
=
useState
([]);
const
[
editingRedemption
,
setEditingRedemption
]
=
useState
({
const
[
editingRedemption
,
setEditingRedemption
]
=
useState
({
id
:
undefined
id
:
undefined
,
});
});
const
[
showEdit
,
setShowEdit
]
=
useState
(
false
);
const
[
showEdit
,
setShowEdit
]
=
useState
(
false
);
...
@@ -178,7 +201,7 @@ const RedemptionsTable = () => {
...
@@ -178,7 +201,7 @@ const RedemptionsTable = () => {
// }
// }
// data.key = '' + data.id
// data.key = '' + data.id
setRedemptions
(
redeptions
);
setRedemptions
(
redeptions
);
if
(
redeptions
.
length
>=
(
activePage
)
*
ITEMS_PER_PAGE
)
{
if
(
redeptions
.
length
>=
activePage
*
ITEMS_PER_PAGE
)
{
setTokenCount
(
redeptions
.
length
+
1
);
setTokenCount
(
redeptions
.
length
+
1
);
}
else
{
}
else
{
setTokenCount
(
redeptions
.
length
);
setTokenCount
(
redeptions
.
length
);
...
@@ -202,10 +225,10 @@ const RedemptionsTable = () => {
...
@@ -202,10 +225,10 @@ const RedemptionsTable = () => {
setLoading
(
false
);
setLoading
(
false
);
};
};
const
removeRecord
=
key
=>
{
const
removeRecord
=
(
key
)
=>
{
let
newDataSource
=
[...
redemptions
];
let
newDataSource
=
[...
redemptions
];
if
(
key
!=
null
)
{
if
(
key
!=
null
)
{
let
idx
=
newDataSource
.
findIndex
(
data
=>
data
.
key
===
key
);
let
idx
=
newDataSource
.
findIndex
(
(
data
)
=>
data
.
key
===
key
);
if
(
idx
>
-
1
)
{
if
(
idx
>
-
1
)
{
newDataSource
.
splice
(
idx
,
1
);
newDataSource
.
splice
(
idx
,
1
);
...
@@ -268,7 +291,6 @@ const RedemptionsTable = () => {
...
@@ -268,7 +291,6 @@ const RedemptionsTable = () => {
let
newRedemptions
=
[...
redemptions
];
let
newRedemptions
=
[...
redemptions
];
// let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
// let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
if
(
action
===
'delete'
)
{
if
(
action
===
'delete'
)
{
}
else
{
}
else
{
record
.
status
=
redemption
.
status
;
record
.
status
=
redemption
.
status
;
}
}
...
@@ -286,7 +308,9 @@ const RedemptionsTable = () => {
...
@@ -286,7 +308,9 @@ const RedemptionsTable = () => {
return
;
return
;
}
}
setSearching
(
true
);
setSearching
(
true
);
const
res
=
await
API
.
get
(
`/api/redemption/search?keyword=
${
searchKeyword
}
`
);
const
res
=
await
API
.
get
(
`/api/redemption/search?keyword=
${
searchKeyword
}
`
,
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
setRedemptions
(
data
);
setRedemptions
(
data
);
...
@@ -315,32 +339,32 @@ const RedemptionsTable = () => {
...
@@ -315,32 +339,32 @@ const RedemptionsTable = () => {
setLoading
(
false
);
setLoading
(
false
);
};
};
const
handlePageChange
=
page
=>
{
const
handlePageChange
=
(
page
)
=>
{
setActivePage
(
page
);
setActivePage
(
page
);
if
(
page
===
Math
.
ceil
(
redemptions
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
if
(
page
===
Math
.
ceil
(
redemptions
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
// In this case we have to load more data and then append them.
// In this case we have to load more data and then append them.
loadRedemptions
(
page
-
1
).
then
(
r
=>
{
loadRedemptions
(
page
-
1
).
then
((
r
)
=>
{});
});
}
}
};
};
let
pageData
=
redemptions
.
slice
((
activePage
-
1
)
*
ITEMS_PER_PAGE
,
activePage
*
ITEMS_PER_PAGE
);
let
pageData
=
redemptions
.
slice
(
(
activePage
-
1
)
*
ITEMS_PER_PAGE
,
activePage
*
ITEMS_PER_PAGE
,
);
const
rowSelection
=
{
const
rowSelection
=
{
onSelect
:
(
record
,
selected
)
=>
{
onSelect
:
(
record
,
selected
)
=>
{},
},
onSelectAll
:
(
selected
,
selectedRows
)
=>
{},
onSelectAll
:
(
selected
,
selectedRows
)
=>
{
},
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
setSelectedKeys
(
selectedRows
);
setSelectedKeys
(
selectedRows
);
}
}
,
};
};
const
handleRow
=
(
record
,
index
)
=>
{
const
handleRow
=
(
record
,
index
)
=>
{
if
(
record
.
status
!==
1
)
{
if
(
record
.
status
!==
1
)
{
return
{
return
{
style
:
{
style
:
{
background
:
'var(--semi-color-disabled-border)'
background
:
'var(--semi-color-disabled-border)'
,
}
}
,
};
};
}
else
{
}
else
{
return
{};
return
{};
...
@@ -349,45 +373,64 @@ const RedemptionsTable = () => {
...
@@ -349,45 +373,64 @@ const RedemptionsTable = () => {
return
(
return
(
<>
<>
<
EditRedemption
refresh
=
{
refresh
}
editingRedemption
=
{
editingRedemption
}
visiable
=
{
showEdit
}
<
EditRedemption
handleClose
=
{
closeEdit
}
><
/EditRedemption
>
refresh
=
{
refresh
}
editingRedemption
=
{
editingRedemption
}
visiable
=
{
showEdit
}
handleClose
=
{
closeEdit
}
><
/EditRedemption
>
<
Form
onSubmit
=
{
searchRedemptions
}
>
<
Form
onSubmit
=
{
searchRedemptions
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"搜索关键字"
label
=
'搜索关键字'
field
=
"keyword"
field
=
'keyword'
icon
=
"search"
icon
=
'search'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"关键字(id或者名称)"
placeholder
=
'关键字(id或者名称)'
value
=
{
searchKeyword
}
value
=
{
searchKeyword
}
loading
=
{
searching
}
loading
=
{
searching
}
onChange
=
{
handleKeywordChange
}
onChange
=
{
handleKeywordChange
}
/
>
/
>
<
/Form
>
<
/Form
>
<
Table
style
=
{{
marginTop
:
20
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
<
Table
style
=
{{
marginTop
:
20
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
currentPage
:
activePage
,
pageSize
:
ITEMS_PER_PAGE
,
pageSize
:
ITEMS_PER_PAGE
,
total
:
tokenCount
,
total
:
tokenCount
,
// showSizeChanger: true,
// showSizeChanger: true,
// pageSizeOptions: [10, 20, 50, 100],
// pageSizeOptions: [10, 20, 50, 100],
formatPageText
:
(
page
)
=>
`第
${
page
.
currentStart
}
-
${
page
.
currentEnd
}
条,共
${
redemptions
.
length
}
条`
,
formatPageText
:
(
page
)
=>
`第
${
page
.
currentStart
}
-
${
page
.
currentEnd
}
条,共
${
redemptions
.
length
}
条`
,
// onPageSizeChange: (size) => {
// onPageSizeChange: (size) => {
// setPageSize(size);
// setPageSize(size);
// setActivePage(1);
// setActivePage(1);
// },
// },
onPageChange
:
handlePageChange
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
rowSelection
=
{
rowSelection
}
onRow
=
{
handleRow
}
>
}}
<
/Table
>
loading
=
{
loading
}
<
Button
theme
=
"light"
type
=
"primary"
style
=
{{
marginRight
:
8
}}
onClick
=
{
rowSelection
=
{
rowSelection
}
()
=>
{
onRow
=
{
handleRow
}
><
/Table
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{()
=>
{
setEditingRedemption
({
setEditingRedemption
({
id
:
undefined
id
:
undefined
,
});
});
setShowEdit
(
true
);
setShowEdit
(
true
);
}
}}
}
>
添加兑换码
<
/Button
>
>
<
Button
label
=
"复制所选兑换码"
type
=
"warning"
onClick
=
{
添加兑换码
async
()
=>
{
<
/Button
>
<
Button
label
=
'复制所选兑换码'
type
=
'warning'
onClick
=
{
async
()
=>
{
if
(
selectedKeys
.
length
===
0
)
{
if
(
selectedKeys
.
length
===
0
)
{
showError
(
'请至少选择一个兑换码!'
);
showError
(
'请至少选择一个兑换码!'
);
return
;
return
;
...
@@ -397,8 +440,10 @@ const RedemptionsTable = () => {
...
@@ -397,8 +440,10 @@ const RedemptionsTable = () => {
keys
+=
selectedKeys
[
i
].
name
+
' '
+
selectedKeys
[
i
].
key
+
'\n'
;
keys
+=
selectedKeys
[
i
].
name
+
' '
+
selectedKeys
[
i
].
key
+
'\n'
;
}
}
await
copyText
(
keys
);
await
copyText
(
keys
);
}
}}
}
>
复制所选兑换码到剪贴板
<
/Button
>
>
复制所选兑换码到剪贴板
<
/Button
>
<
/
>
<
/
>
);
);
};
};
...
...
web/src/components/RegisterForm.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Button
,
Form
,
Grid
,
Header
,
Image
,
Message
,
Segment
}
from
'semantic-ui-react'
;
import
{
Button
,
Form
,
Grid
,
Header
,
Image
,
Message
,
Segment
,
}
from
'semantic-ui-react'
;
import
{
Link
,
useNavigate
}
from
'react-router-dom'
;
import
{
Link
,
useNavigate
}
from
'react-router-dom'
;
import
{
API
,
getLogo
,
showError
,
showInfo
,
showSuccess
}
from
'../helpers'
;
import
{
API
,
getLogo
,
showError
,
showInfo
,
showSuccess
}
from
'../helpers'
;
import
Turnstile
from
'react-turnstile'
;
import
Turnstile
from
'react-turnstile'
;
...
@@ -10,7 +18,7 @@ const RegisterForm = () => {
...
@@ -10,7 +18,7 @@ const RegisterForm = () => {
password
:
''
,
password
:
''
,
password2
:
''
,
password2
:
''
,
email
:
''
,
email
:
''
,
verification_code
:
''
verification_code
:
''
,
});
});
const
{
username
,
password
,
password2
}
=
inputs
;
const
{
username
,
password
,
password2
}
=
inputs
;
const
[
showEmailVerification
,
setShowEmailVerification
]
=
useState
(
false
);
const
[
showEmailVerification
,
setShowEmailVerification
]
=
useState
(
false
);
...
@@ -65,7 +73,7 @@ const RegisterForm = () => {
...
@@ -65,7 +73,7 @@ const RegisterForm = () => {
inputs
.
aff_code
=
affCode
;
inputs
.
aff_code
=
affCode
;
const
res
=
await
API
.
post
(
const
res
=
await
API
.
post
(
`/api/user/register?turnstile=
${
turnstileToken
}
`
,
`/api/user/register?turnstile=
${
turnstileToken
}
`
,
inputs
inputs
,
);
);
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -86,7 +94,7 @@ const RegisterForm = () => {
...
@@ -86,7 +94,7 @@ const RegisterForm = () => {
}
}
setLoading
(
true
);
setLoading
(
true
);
const
res
=
await
API
.
get
(
const
res
=
await
API
.
get
(
`/api/verification?email=
${
inputs
.
email
}
&turnstile=
${
turnstileToken
}
`
`/api/verification?email=
${
inputs
.
email
}
&turnstile=
${
turnstileToken
}
`
,
);
);
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -98,49 +106,49 @@ const RegisterForm = () => {
...
@@ -98,49 +106,49 @@ const RegisterForm = () => {
};
};
return
(
return
(
<
Grid
textAlign
=
"center"
style
=
{{
marginTop
:
'48px'
}}
>
<
Grid
textAlign
=
'center'
style
=
{{
marginTop
:
'48px'
}}
>
<
Grid
.
Column
style
=
{{
maxWidth
:
450
}}
>
<
Grid
.
Column
style
=
{{
maxWidth
:
450
}}
>
<
Header
as
=
"h2"
color
=
""
textAlign
=
"center"
>
<
Header
as
=
'h2'
color
=
''
textAlign
=
'center'
>
<
Image
src
=
{
logo
}
/> 新用户注
册
<
Image
src
=
{
logo
}
/> 新用户注
册
<
/Header
>
<
/Header
>
<
Form
size
=
"large"
>
<
Form
size
=
'large'
>
<
Segment
>
<
Segment
>
<
Form
.
Input
<
Form
.
Input
fluid
fluid
icon
=
"user"
icon
=
'user'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"输入用户名,最长 12 位"
placeholder
=
'输入用户名,最长 12 位'
onChange
=
{
handleChange
}
onChange
=
{
handleChange
}
name
=
"username"
name
=
'username'
/>
/>
<
Form
.
Input
<
Form
.
Input
fluid
fluid
icon
=
"lock"
icon
=
'lock'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"输入密码,最短 8 位,最长 20 位"
placeholder
=
'输入密码,最短 8 位,最长 20 位'
onChange
=
{
handleChange
}
onChange
=
{
handleChange
}
name
=
"password"
name
=
'password'
type
=
"password"
type
=
'password'
/>
/>
<
Form
.
Input
<
Form
.
Input
fluid
fluid
icon
=
"lock"
icon
=
'lock'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"输入密码,最短 8 位,最长 20 位"
placeholder
=
'输入密码,最短 8 位,最长 20 位'
onChange
=
{
handleChange
}
onChange
=
{
handleChange
}
name
=
"password2"
name
=
'password2'
type
=
"password"
type
=
'password'
/>
/>
{
showEmailVerification
?
(
{
showEmailVerification
?
(
<>
<>
<
Form
.
Input
<
Form
.
Input
fluid
fluid
icon
=
"mail"
icon
=
'mail'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"输入邮箱地址"
placeholder
=
'输入邮箱地址'
onChange
=
{
handleChange
}
onChange
=
{
handleChange
}
name
=
"email"
name
=
'email'
type
=
"email"
type
=
'email'
action
=
{
action
=
{
<
Button
onClick
=
{
sendVerificationCode
}
disabled
=
{
loading
}
>
<
Button
onClick
=
{
sendVerificationCode
}
disabled
=
{
loading
}
>
获取验证码
获取验证码
...
@@ -149,11 +157,11 @@ const RegisterForm = () => {
...
@@ -149,11 +157,11 @@ const RegisterForm = () => {
/
>
/
>
<
Form
.
Input
<
Form
.
Input
fluid
fluid
icon
=
"lock"
icon
=
'lock'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"输入验证码"
placeholder
=
'输入验证码'
onChange
=
{
handleChange
}
onChange
=
{
handleChange
}
name
=
"verification_code"
name
=
'verification_code'
/>
/>
<
/
>
<
/
>
)
:
(
)
:
(
...
@@ -170,9 +178,9 @@ const RegisterForm = () => {
...
@@ -170,9 +178,9 @@ const RegisterForm = () => {
<><
/
>
<><
/
>
)}
)}
<
Button
<
Button
color
=
"green"
color
=
'green'
fluid
fluid
size
=
"large"
size
=
'large'
onClick
=
{
handleSubmit
}
onClick
=
{
handleSubmit
}
loading
=
{
loading
}
loading
=
{
loading
}
>
>
...
@@ -182,7 +190,7 @@ const RegisterForm = () => {
...
@@ -182,7 +190,7 @@ const RegisterForm = () => {
<
/Form
>
<
/Form
>
<
Message
>
<
Message
>
已有账户?
已有账户?
<
Link
to
=
"/login"
className
=
"btn btn-link"
>
<
Link
to
=
'/login'
className
=
'btn btn-link'
>
点击登录
点击登录
<
/Link
>
<
/Link
>
<
/Message
>
<
/Message
>
...
...
web/src/components/SiderBar.js
View file @
eb955ae4
...
@@ -3,7 +3,14 @@ import { Link, useNavigate } from 'react-router-dom';
...
@@ -3,7 +3,14 @@ import { Link, useNavigate } from 'react-router-dom';
import
{
UserContext
}
from
'../context/User'
;
import
{
UserContext
}
from
'../context/User'
;
import
{
StatusContext
}
from
'../context/Status'
;
import
{
StatusContext
}
from
'../context/Status'
;
import
{
API
,
getLogo
,
getSystemName
,
isAdmin
,
isMobile
,
showError
}
from
'../helpers'
;
import
{
API
,
getLogo
,
getSystemName
,
isAdmin
,
isMobile
,
showError
,
}
from
'../helpers'
;
import
'../index.css'
;
import
'../index.css'
;
import
{
import
{
...
@@ -17,7 +24,7 @@ import {
...
@@ -17,7 +24,7 @@ import {
IconKey
,
IconKey
,
IconLayers
,
IconLayers
,
IconSetting
,
IconSetting
,
IconUser
IconUser
,
}
from
'@douyinfe/semi-icons'
;
}
from
'@douyinfe/semi-icons'
;
import
{
Layout
,
Nav
}
from
'@douyinfe/semi-ui'
;
import
{
Layout
,
Nav
}
from
'@douyinfe/semi-ui'
;
...
@@ -26,7 +33,8 @@ import { Layout, Nav } from '@douyinfe/semi-ui';
...
@@ -26,7 +33,8 @@ import { Layout, Nav } from '@douyinfe/semi-ui';
const
SiderBar
=
()
=>
{
const
SiderBar
=
()
=>
{
const
[
userState
,
userDispatch
]
=
useContext
(
UserContext
);
const
[
userState
,
userDispatch
]
=
useContext
(
UserContext
);
const
[
statusState
,
statusDispatch
]
=
useContext
(
StatusContext
);
const
[
statusState
,
statusDispatch
]
=
useContext
(
StatusContext
);
const
defaultIsCollapsed
=
isMobile
()
||
localStorage
.
getItem
(
'default_collapse_sidebar'
)
===
'true'
;
const
defaultIsCollapsed
=
isMobile
()
||
localStorage
.
getItem
(
'default_collapse_sidebar'
)
===
'true'
;
let
navigate
=
useNavigate
();
let
navigate
=
useNavigate
();
const
[
selectedKeys
,
setSelectedKeys
]
=
useState
([
'home'
]);
const
[
selectedKeys
,
setSelectedKeys
]
=
useState
([
'home'
]);
...
@@ -46,89 +54,105 @@ const SiderBar = () => {
...
@@ -46,89 +54,105 @@ const SiderBar = () => {
setting
:
'/setting'
,
setting
:
'/setting'
,
about
:
'/about'
,
about
:
'/about'
,
chat
:
'/chat'
,
chat
:
'/chat'
,
detail
:
'/detail'
detail
:
'/detail'
,
};
};
const
headerButtons
=
useMemo
(()
=>
[
const
headerButtons
=
useMemo
(
()
=>
[
{
{
text
:
'首页'
,
text
:
'首页'
,
itemKey
:
'home'
,
itemKey
:
'home'
,
to
:
'/'
,
to
:
'/'
,
icon
:
<
IconHome
/>
icon
:
<
IconHome
/>
,
},
},
{
{
text
:
'渠道'
,
text
:
'渠道'
,
itemKey
:
'channel'
,
itemKey
:
'channel'
,
to
:
'/channel'
,
to
:
'/channel'
,
icon
:
<
IconLayers
/>
,
icon
:
<
IconLayers
/>
,
className
:
isAdmin
()
?
'semi-navigation-item-normal'
:
'tableHiddle'
className
:
isAdmin
()
?
'semi-navigation-item-normal'
:
'tableHiddle'
,
},
},
{
{
text
:
'聊天'
,
text
:
'聊天'
,
itemKey
:
'chat'
,
itemKey
:
'chat'
,
to
:
'/chat'
,
to
:
'/chat'
,
icon
:
<
IconComment
/>
,
icon
:
<
IconComment
/>
,
className
:
localStorage
.
getItem
(
'chat_link'
)
?
'semi-navigation-item-normal'
:
'tableHiddle'
className
:
localStorage
.
getItem
(
'chat_link'
)
?
'semi-navigation-item-normal'
:
'tableHiddle'
,
},
},
{
{
text
:
'令牌'
,
text
:
'令牌'
,
itemKey
:
'token'
,
itemKey
:
'token'
,
to
:
'/token'
,
to
:
'/token'
,
icon
:
<
IconKey
/>
icon
:
<
IconKey
/>
,
},
},
{
{
text
:
'兑换码'
,
text
:
'兑换码'
,
itemKey
:
'redemption'
,
itemKey
:
'redemption'
,
to
:
'/redemption'
,
to
:
'/redemption'
,
icon
:
<
IconGift
/>
,
icon
:
<
IconGift
/>
,
className
:
isAdmin
()
?
'semi-navigation-item-normal'
:
'tableHiddle'
className
:
isAdmin
()
?
'semi-navigation-item-normal'
:
'tableHiddle'
,
},
},
{
{
text
:
'钱包'
,
text
:
'钱包'
,
itemKey
:
'topup'
,
itemKey
:
'topup'
,
to
:
'/topup'
,
to
:
'/topup'
,
icon
:
<
IconCreditCard
/>
icon
:
<
IconCreditCard
/>
,
},
},
{
{
text
:
'用户管理'
,
text
:
'用户管理'
,
itemKey
:
'user'
,
itemKey
:
'user'
,
to
:
'/user'
,
to
:
'/user'
,
icon
:
<
IconUser
/>
,
icon
:
<
IconUser
/>
,
className
:
isAdmin
()
?
'semi-navigation-item-normal'
:
'tableHiddle'
className
:
isAdmin
()
?
'semi-navigation-item-normal'
:
'tableHiddle'
,
},
},
{
{
text
:
'日志'
,
text
:
'日志'
,
itemKey
:
'log'
,
itemKey
:
'log'
,
to
:
'/log'
,
to
:
'/log'
,
icon
:
<
IconHistogram
/>
icon
:
<
IconHistogram
/>
,
},
},
{
{
text
:
'数据看板'
,
text
:
'数据看板'
,
itemKey
:
'detail'
,
itemKey
:
'detail'
,
to
:
'/detail'
,
to
:
'/detail'
,
icon
:
<
IconCalendarClock
/>
,
icon
:
<
IconCalendarClock
/>
,
className
:
localStorage
.
getItem
(
'enable_data_export'
)
===
'true'
?
'semi-navigation-item-normal'
:
'tableHiddle'
className
:
localStorage
.
getItem
(
'enable_data_export'
)
===
'true'
?
'semi-navigation-item-normal'
:
'tableHiddle'
,
},
},
{
{
text
:
'绘图'
,
text
:
'绘图'
,
itemKey
:
'midjourney'
,
itemKey
:
'midjourney'
,
to
:
'/midjourney'
,
to
:
'/midjourney'
,
icon
:
<
IconImage
/>
,
icon
:
<
IconImage
/>
,
className
:
localStorage
.
getItem
(
'enable_drawing'
)
===
'true'
?
'semi-navigation-item-normal'
:
'tableHiddle'
className
:
localStorage
.
getItem
(
'enable_drawing'
)
===
'true'
?
'semi-navigation-item-normal'
:
'tableHiddle'
,
},
},
{
{
text
:
'设置'
,
text
:
'设置'
,
itemKey
:
'setting'
,
itemKey
:
'setting'
,
to
:
'/setting'
,
to
:
'/setting'
,
icon
:
<
IconSetting
/>
icon
:
<
IconSetting
/>
,
}
},
// {
// {
// text: '关于',
// text: '关于',
// itemKey: 'about',
// itemKey: 'about',
// to: '/about',
// to: '/about',
// icon: <IconAt/>
// icon: <IconAt/>
// }
// }
],
[
localStorage
.
getItem
(
'enable_data_export'
),
localStorage
.
getItem
(
'enable_drawing'
),
localStorage
.
getItem
(
'chat_link'
),
isAdmin
()]);
],
[
localStorage
.
getItem
(
'enable_data_export'
),
localStorage
.
getItem
(
'enable_drawing'
),
localStorage
.
getItem
(
'chat_link'
),
isAdmin
(),
],
);
const
loadStatus
=
async
()
=>
{
const
loadStatus
=
async
()
=>
{
const
res
=
await
API
.
get
(
'/api/status'
);
const
res
=
await
API
.
get
(
'/api/status'
);
...
@@ -143,8 +167,14 @@ const SiderBar = () => {
...
@@ -143,8 +167,14 @@ const SiderBar = () => {
localStorage
.
setItem
(
'display_in_currency'
,
data
.
display_in_currency
);
localStorage
.
setItem
(
'display_in_currency'
,
data
.
display_in_currency
);
localStorage
.
setItem
(
'enable_drawing'
,
data
.
enable_drawing
);
localStorage
.
setItem
(
'enable_drawing'
,
data
.
enable_drawing
);
localStorage
.
setItem
(
'enable_data_export'
,
data
.
enable_data_export
);
localStorage
.
setItem
(
'enable_data_export'
,
data
.
enable_data_export
);
localStorage
.
setItem
(
'data_export_default_time'
,
data
.
data_export_default_time
);
localStorage
.
setItem
(
localStorage
.
setItem
(
'default_collapse_sidebar'
,
data
.
default_collapse_sidebar
);
'data_export_default_time'
,
data
.
data_export_default_time
,
);
localStorage
.
setItem
(
'default_collapse_sidebar'
,
data
.
default_collapse_sidebar
,
);
localStorage
.
setItem
(
'mj_notify_enabled'
,
data
.
mj_notify_enabled
);
localStorage
.
setItem
(
'mj_notify_enabled'
,
data
.
mj_notify_enabled
);
if
(
data
.
chat_link
)
{
if
(
data
.
chat_link
)
{
localStorage
.
setItem
(
'chat_link'
,
data
.
chat_link
);
localStorage
.
setItem
(
'chat_link'
,
data
.
chat_link
);
...
@@ -163,11 +193,14 @@ const SiderBar = () => {
...
@@ -163,11 +193,14 @@ const SiderBar = () => {
useEffect
(()
=>
{
useEffect
(()
=>
{
loadStatus
().
then
(()
=>
{
loadStatus
().
then
(()
=>
{
setIsCollapsed
(
isMobile
()
||
localStorage
.
getItem
(
'default_collapse_sidebar'
)
===
'true'
);
setIsCollapsed
(
isMobile
()
||
localStorage
.
getItem
(
'default_collapse_sidebar'
)
===
'true'
,
);
});
});
let
localKey
=
window
.
location
.
pathname
.
split
(
'/'
)[
1
]
let
localKey
=
window
.
location
.
pathname
.
split
(
'/'
)[
1
]
;
if
(
localKey
===
''
)
{
if
(
localKey
===
''
)
{
localKey
=
'home'
localKey
=
'home'
;
}
}
setSelectedKeys
([
localKey
]);
setSelectedKeys
([
localKey
]);
},
[]);
},
[]);
...
@@ -179,9 +212,12 @@ const SiderBar = () => {
...
@@ -179,9 +212,12 @@ const SiderBar = () => {
<
Nav
<
Nav
// bodyStyle={{ maxWidth: 200 }}
// bodyStyle={{ maxWidth: 200 }}
style
=
{{
maxWidth
:
200
}}
style
=
{{
maxWidth
:
200
}}
defaultIsCollapsed
=
{
isMobile
()
||
localStorage
.
getItem
(
'default_collapse_sidebar'
)
===
'true'
}
defaultIsCollapsed
=
{
isMobile
()
||
localStorage
.
getItem
(
'default_collapse_sidebar'
)
===
'true'
}
isCollapsed
=
{
isCollapsed
}
isCollapsed
=
{
isCollapsed
}
onCollapseChange
=
{
collapsed
=>
{
onCollapseChange
=
{
(
collapsed
)
=>
{
setIsCollapsed
(
collapsed
);
setIsCollapsed
(
collapsed
);
}}
}}
selectedKeys
=
{
selectedKeys
}
selectedKeys
=
{
selectedKeys
}
...
@@ -196,20 +232,20 @@ const SiderBar = () => {
...
@@ -196,20 +232,20 @@ const SiderBar = () => {
);
);
}}
}}
items
=
{
headerButtons
}
items
=
{
headerButtons
}
onSelect
=
{
key
=>
{
onSelect
=
{
(
key
)
=>
{
setSelectedKeys
([
key
.
itemKey
]);
setSelectedKeys
([
key
.
itemKey
]);
}}
}}
header
=
{{
header
=
{{
logo
:
<
img
src
=
{
logo
}
alt
=
"logo"
style
=
{{
marginRight
:
'0.75em'
}}
/>
,
logo
:
(
text
:
systemName
<
img
src
=
{
logo
}
alt
=
'logo'
style
=
{{
marginRight
:
'0.75em'
}}
/
>
),
text
:
systemName
,
}}
}}
// footer={{
// footer={{
// text: '© 2021 NekoAPI',
// text: '© 2021 NekoAPI',
// }}
// }}
>
>
<
Nav
.
Footer
collapseButton
=
{
true
}
><
/Nav.Footer
>
<
Nav
.
Footer
collapseButton
=
{
true
}
>
<
/Nav.Footer
>
<
/Nav
>
<
/Nav
>
<
/div
>
<
/div
>
<
/Layout
>
<
/Layout
>
...
...
web/src/components/SystemSetting.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Button
,
Divider
,
Form
,
Grid
,
Header
,
Message
,
Modal
}
from
'semantic-ui-react'
;
import
{
Button
,
Divider
,
Form
,
Grid
,
Header
,
Message
,
Modal
,
}
from
'semantic-ui-react'
;
import
{
API
,
removeTrailingSlash
,
showError
,
verifyJSON
}
from
'../helpers'
;
import
{
API
,
removeTrailingSlash
,
showError
,
verifyJSON
}
from
'../helpers'
;
const
SystemSetting
=
()
=>
{
const
SystemSetting
=
()
=>
{
...
@@ -38,13 +46,14 @@ const SystemSetting = () => {
...
@@ -38,13 +46,14 @@ const SystemSetting = () => {
// telegram login
// telegram login
TelegramOAuthEnabled
:
''
,
TelegramOAuthEnabled
:
''
,
TelegramBotToken
:
''
,
TelegramBotToken
:
''
,
TelegramBotName
:
''
TelegramBotName
:
''
,
});
});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
const
[
originInputs
,
setOriginInputs
]
=
useState
({});
let
[
loading
,
setLoading
]
=
useState
(
false
);
let
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
EmailDomainWhitelist
,
setEmailDomainWhitelist
]
=
useState
([]);
const
[
EmailDomainWhitelist
,
setEmailDomainWhitelist
]
=
useState
([]);
const
[
restrictedDomainInput
,
setRestrictedDomainInput
]
=
useState
(
''
);
const
[
restrictedDomainInput
,
setRestrictedDomainInput
]
=
useState
(
''
);
const
[
showPasswordWarningModal
,
setShowPasswordWarningModal
]
=
useState
(
false
);
const
[
showPasswordWarningModal
,
setShowPasswordWarningModal
]
=
useState
(
false
);
const
getOptions
=
async
()
=>
{
const
getOptions
=
async
()
=>
{
const
res
=
await
API
.
get
(
'/api/option/'
);
const
res
=
await
API
.
get
(
'/api/option/'
);
...
@@ -59,13 +68,15 @@ const SystemSetting = () => {
...
@@ -59,13 +68,15 @@ const SystemSetting = () => {
});
});
setInputs
({
setInputs
({
...
newInputs
,
...
newInputs
,
EmailDomainWhitelist
:
newInputs
.
EmailDomainWhitelist
.
split
(
','
)
EmailDomainWhitelist
:
newInputs
.
EmailDomainWhitelist
.
split
(
','
)
,
});
});
setOriginInputs
(
newInputs
);
setOriginInputs
(
newInputs
);
setEmailDomainWhitelist
(
newInputs
.
EmailDomainWhitelist
.
split
(
','
).
map
((
item
)
=>
{
setEmailDomainWhitelist
(
newInputs
.
EmailDomainWhitelist
.
split
(
','
).
map
((
item
)
=>
{
return
{
key
:
item
,
text
:
item
,
value
:
item
};
return
{
key
:
item
,
text
:
item
,
value
:
item
};
}));
}),
);
}
else
{
}
else
{
showError
(
message
);
showError
(
message
);
}
}
...
@@ -94,7 +105,7 @@ const SystemSetting = () => {
...
@@ -94,7 +105,7 @@ const SystemSetting = () => {
}
}
const
res
=
await
API
.
put
(
'/api/option/'
,
{
const
res
=
await
API
.
put
(
'/api/option/'
,
{
key
,
key
,
value
value
,
});
});
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -105,7 +116,8 @@ const SystemSetting = () => {
...
@@ -105,7 +116,8 @@ const SystemSetting = () => {
value
=
parseFloat
(
value
);
value
=
parseFloat
(
value
);
}
}
setInputs
((
inputs
)
=>
({
setInputs
((
inputs
)
=>
({
...
inputs
,
[
key
]:
value
...
inputs
,
[
key
]:
value
,
}));
}));
}
else
{
}
else
{
showError
(
message
);
showError
(
message
);
...
@@ -197,13 +209,16 @@ const SystemSetting = () => {
...
@@ -197,13 +209,16 @@ const SystemSetting = () => {
}
}
};
};
const
submitEmailDomainWhitelist
=
async
()
=>
{
const
submitEmailDomainWhitelist
=
async
()
=>
{
if
(
if
(
originInputs
[
'EmailDomainWhitelist'
]
!==
inputs
.
EmailDomainWhitelist
.
join
(
','
)
&&
originInputs
[
'EmailDomainWhitelist'
]
!==
inputs
.
EmailDomainWhitelist
.
join
(
','
)
&&
inputs
.
SMTPToken
!==
''
inputs
.
SMTPToken
!==
''
)
{
)
{
await
updateOption
(
'EmailDomainWhitelist'
,
inputs
.
EmailDomainWhitelist
.
join
(
','
));
await
updateOption
(
'EmailDomainWhitelist'
,
inputs
.
EmailDomainWhitelist
.
join
(
','
),
);
}
}
};
};
...
@@ -211,7 +226,7 @@ const SystemSetting = () => {
...
@@ -211,7 +226,7 @@ const SystemSetting = () => {
if
(
originInputs
[
'WeChatServerAddress'
]
!==
inputs
.
WeChatServerAddress
)
{
if
(
originInputs
[
'WeChatServerAddress'
]
!==
inputs
.
WeChatServerAddress
)
{
await
updateOption
(
await
updateOption
(
'WeChatServerAddress'
,
'WeChatServerAddress'
,
removeTrailingSlash
(
inputs
.
WeChatServerAddress
)
removeTrailingSlash
(
inputs
.
WeChatServerAddress
)
,
);
);
}
}
if
(
if
(
...
@@ -220,7 +235,7 @@ const SystemSetting = () => {
...
@@ -220,7 +235,7 @@ const SystemSetting = () => {
)
{
)
{
await
updateOption
(
await
updateOption
(
'WeChatAccountQRCodeImageURL'
,
'WeChatAccountQRCodeImageURL'
,
inputs
.
WeChatAccountQRCodeImageURL
inputs
.
WeChatAccountQRCodeImageURL
,
);
);
}
}
if
(
if
(
...
@@ -263,17 +278,23 @@ const SystemSetting = () => {
...
@@ -263,17 +278,23 @@ const SystemSetting = () => {
const
submitNewRestrictedDomain
=
()
=>
{
const
submitNewRestrictedDomain
=
()
=>
{
const
localDomainList
=
inputs
.
EmailDomainWhitelist
;
const
localDomainList
=
inputs
.
EmailDomainWhitelist
;
if
(
restrictedDomainInput
!==
''
&&
!
localDomainList
.
includes
(
restrictedDomainInput
))
{
if
(
restrictedDomainInput
!==
''
&&
!
localDomainList
.
includes
(
restrictedDomainInput
)
)
{
setRestrictedDomainInput
(
''
);
setRestrictedDomainInput
(
''
);
setInputs
({
setInputs
({
...
inputs
,
...
inputs
,
EmailDomainWhitelist
:
[...
localDomainList
,
restrictedDomainInput
]
EmailDomainWhitelist
:
[...
localDomainList
,
restrictedDomainInput
]
,
});
});
setEmailDomainWhitelist
([...
EmailDomainWhitelist
,
{
setEmailDomainWhitelist
([
...
EmailDomainWhitelist
,
{
key
:
restrictedDomainInput
,
key
:
restrictedDomainInput
,
text
:
restrictedDomainInput
,
text
:
restrictedDomainInput
,
value
:
restrictedDomainInput
value
:
restrictedDomainInput
,
}]);
},
]);
}
}
};
};
...
@@ -281,13 +302,13 @@ const SystemSetting = () => {
...
@@ -281,13 +302,13 @@ const SystemSetting = () => {
<
Grid
columns
=
{
1
}
>
<
Grid
columns
=
{
1
}
>
<
Grid
.
Column
>
<
Grid
.
Column
>
<
Form
loading
=
{
loading
}
>
<
Form
loading
=
{
loading
}
>
<
Header
as
=
"h3"
>
通用设置
<
/Header
>
<
Header
as
=
'h3'
>
通用设置
<
/Header
>
<
Form
.
Group
widths
=
"equal"
>
<
Form
.
Group
widths
=
'equal'
>
<
Form
.
Input
<
Form
.
Input
label
=
"服务器地址"
label
=
'服务器地址'
placeholder
=
"例如:https://yourdomain.com"
placeholder
=
'例如:https://yourdomain.com'
value
=
{
inputs
.
ServerAddress
}
value
=
{
inputs
.
ServerAddress
}
name
=
"ServerAddress"
name
=
'ServerAddress'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
...
@@ -295,81 +316,79 @@ const SystemSetting = () => {
...
@@ -295,81 +316,79 @@ const SystemSetting = () => {
更新服务器地址
更新服务器地址
<
/Form.Button
>
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
<
/Header
>
<
Header
as
=
'h3'
>
<
Form
.
Group
widths
=
"equal"
>
支付设置(当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!)
<
/Header
>
<
Form
.
Group
widths
=
'equal'
>
<
Form
.
Input
<
Form
.
Input
label
=
"支付地址,不填写则不启用在线支付"
label
=
'支付地址,不填写则不启用在线支付'
placeholder
=
"例如:https://yourdomain.com"
placeholder
=
'例如:https://yourdomain.com'
value
=
{
inputs
.
PayAddress
}
value
=
{
inputs
.
PayAddress
}
name
=
"PayAddress"
name
=
'PayAddress'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
label
=
"易支付商户ID"
label
=
'易支付商户ID'
placeholder
=
"例如:0001"
placeholder
=
'例如:0001'
value
=
{
inputs
.
EpayId
}
value
=
{
inputs
.
EpayId
}
name
=
"EpayId"
name
=
'EpayId'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
label
=
"易支付商户密钥"
label
=
'易支付商户密钥'
placeholder
=
"例如:dejhfueqhujasjmndbjkqaw"
placeholder
=
'例如:dejhfueqhujasjmndbjkqaw'
value
=
{
inputs
.
EpayKey
}
value
=
{
inputs
.
EpayKey
}
name
=
"EpayKey"
name
=
'EpayKey'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
widths
=
"equal"
>
<
Form
.
Group
widths
=
'equal'
>
<
Form
.
Input
<
Form
.
Input
label
=
"回调地址,不填写则使用上方服务器地址作为回调地址"
label
=
'回调地址,不填写则使用上方服务器地址作为回调地址'
placeholder
=
"例如:https://yourdomain.com"
placeholder
=
'例如:https://yourdomain.com'
value
=
{
inputs
.
CustomCallbackAddress
}
value
=
{
inputs
.
CustomCallbackAddress
}
name
=
"CustomCallbackAddress"
name
=
'CustomCallbackAddress'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
label
=
"充值价格(x元/美金)"
label
=
'充值价格(x元/美金)'
placeholder
=
"例如:7,就是7元/美金"
placeholder
=
'例如:7,就是7元/美金'
value
=
{
inputs
.
Price
}
value
=
{
inputs
.
Price
}
name
=
"Price"
name
=
'Price'
min
=
{
0
}
min
=
{
0
}
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
label
=
"最低充值数量"
label
=
'最低充值数量'
placeholder
=
"例如:2,就是最低充值2$"
placeholder
=
'例如:2,就是最低充值2$'
value
=
{
inputs
.
MinTopUp
}
value
=
{
inputs
.
MinTopUp
}
name
=
"MinTopUp"
name
=
'MinTopUp'
min
=
{
1
}
min
=
{
1
}
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
widths
=
"equal"
>
<
Form
.
Group
widths
=
'equal'
>
<
Form
.
TextArea
<
Form
.
TextArea
label
=
"充值分组倍率"
label
=
'充值分组倍率'
name
=
"TopupGroupRatio"
name
=
'TopupGroupRatio'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
TopupGroupRatio
}
value
=
{
inputs
.
TopupGroupRatio
}
placeholder
=
"为一个 JSON 文本,键为组名称,值为倍率"
placeholder
=
'为一个 JSON 文本,键为组名称,值为倍率'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{
submitPayAddress
}
>
<
Form
.
Button
onClick
=
{
submitPayAddress
}
>
更新支付设置
<
/Form.Button
>
更新支付设置
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
配置登录注册
<
/Header
>
<
Header
as
=
'h3'
>
配置登录注册
<
/Header
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
PasswordLoginEnabled
===
'true'
}
checked
=
{
inputs
.
PasswordLoginEnabled
===
'true'
}
label
=
"允许通过密码进行登录"
label
=
'允许通过密码进行登录'
name
=
"PasswordLoginEnabled"
name
=
'PasswordLoginEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
{
{
showPasswordWarningModal
&&
(
showPasswordWarningModal
&&
<
Modal
<
Modal
open
=
{
showPasswordWarningModal
}
open
=
{
showPasswordWarningModal
}
onClose
=
{()
=>
setShowPasswordWarningModal
(
false
)}
onClose
=
{()
=>
setShowPasswordWarningModal
(
false
)}
...
@@ -378,12 +397,16 @@ const SystemSetting = () => {
...
@@ -378,12 +397,16 @@ const SystemSetting = () => {
>
>
<
Modal
.
Header
>
警告
<
/Modal.Header
>
<
Modal
.
Header
>
警告
<
/Modal.Header
>
<
Modal
.
Content
>
<
Modal
.
Content
>
<
p
>
取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?
<
/p
>
<
p
>
取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?
<
/p
>
<
/Modal.Content
>
<
/Modal.Content
>
<
Modal
.
Actions
>
<
Modal
.
Actions
>
<
Button
onClick
=
{()
=>
setShowPasswordWarningModal
(
false
)}
>
取消
<
/Button
>
<
Button
onClick
=
{()
=>
setShowPasswordWarningModal
(
false
)}
>
取消
<
/Button
>
<
Button
<
Button
color
=
"yellow"
color
=
'yellow'
onClick
=
{
async
()
=>
{
onClick
=
{
async
()
=>
{
setShowPasswordWarningModal
(
false
);
setShowPasswordWarningModal
(
false
);
await
updateOption
(
'PasswordLoginEnabled'
,
'false'
);
await
updateOption
(
'PasswordLoginEnabled'
,
'false'
);
...
@@ -393,157 +416,170 @@ const SystemSetting = () => {
...
@@ -393,157 +416,170 @@ const SystemSetting = () => {
<
/Button
>
<
/Button
>
<
/Modal.Actions
>
<
/Modal.Actions
>
<
/Modal
>
<
/Modal
>
}
)
}
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
PasswordRegisterEnabled
===
'true'
}
checked
=
{
inputs
.
PasswordRegisterEnabled
===
'true'
}
label
=
"允许通过密码进行注册"
label
=
'允许通过密码进行注册'
name
=
"PasswordRegisterEnabled"
name
=
'PasswordRegisterEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
EmailVerificationEnabled
===
'true'
}
checked
=
{
inputs
.
EmailVerificationEnabled
===
'true'
}
label
=
"通过密码注册时需要进行邮箱验证"
label
=
'通过密码注册时需要进行邮箱验证'
name
=
"EmailVerificationEnabled"
name
=
'EmailVerificationEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
GitHubOAuthEnabled
===
'true'
}
checked
=
{
inputs
.
GitHubOAuthEnabled
===
'true'
}
label
=
"允许通过 GitHub 账户登录 & 注册"
label
=
'允许通过 GitHub 账户登录 & 注册'
name
=
"GitHubOAuthEnabled"
name
=
'GitHubOAuthEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
WeChatAuthEnabled
===
'true'
}
checked
=
{
inputs
.
WeChatAuthEnabled
===
'true'
}
label
=
"允许通过微信登录 & 注册"
label
=
'允许通过微信登录 & 注册'
name
=
"WeChatAuthEnabled"
name
=
'WeChatAuthEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
TelegramOAuthEnabled
===
'true'
}
checked
=
{
inputs
.
TelegramOAuthEnabled
===
'true'
}
label
=
"允许通过 Telegram 进行登录"
label
=
'允许通过 Telegram 进行登录'
name
=
"TelegramOAuthEnabled"
name
=
'TelegramOAuthEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
RegisterEnabled
===
'true'
}
checked
=
{
inputs
.
RegisterEnabled
===
'true'
}
label
=
"允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)"
label
=
'允许新用户注册(此项为否时,新用户将无法以任何方式进行注册)'
name
=
"RegisterEnabled"
name
=
'RegisterEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
Form
.
Checkbox
<
Form
.
Checkbox
checked
=
{
inputs
.
TurnstileCheckEnabled
===
'true'
}
checked
=
{
inputs
.
TurnstileCheckEnabled
===
'true'
}
label
=
"启用 Turnstile 用户校验"
label
=
'启用 Turnstile 用户校验'
name
=
"TurnstileCheckEnabled"
name
=
'TurnstileCheckEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
配置邮箱域名白名单
配置邮箱域名白名单
<
Header
.
Subheader
>
用以防止恶意用户利用临时邮箱批量注册
<
/Header.Subheader
>
<
Header
.
Subheader
>
用以防止恶意用户利用临时邮箱批量注册
<
/Header.Subheader
>
<
/Header
>
<
/Header
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Checkbox
<
Form
.
Checkbox
label
=
"启用邮箱域名白名单"
label
=
'启用邮箱域名白名单'
name
=
"EmailDomainRestrictionEnabled"
name
=
'EmailDomainRestrictionEnabled'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
checked
=
{
inputs
.
EmailDomainRestrictionEnabled
===
'true'
}
checked
=
{
inputs
.
EmailDomainRestrictionEnabled
===
'true'
}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
widths
=
{
2
}
>
<
Form
.
Group
widths
=
{
2
}
>
<
Form
.
Dropdown
<
Form
.
Dropdown
label
=
"允许的邮箱域名"
label
=
'允许的邮箱域名'
placeholder
=
"允许的邮箱域名"
placeholder
=
'允许的邮箱域名'
name
=
"EmailDomainWhitelist"
name
=
'EmailDomainWhitelist'
required
required
fluid
fluid
multiple
multiple
selection
selection
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
value
=
{
inputs
.
EmailDomainWhitelist
}
value
=
{
inputs
.
EmailDomainWhitelist
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
options
=
{
EmailDomainWhitelist
}
options
=
{
EmailDomainWhitelist
}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
label
=
"添加新的允许的邮箱域名"
label
=
'添加新的允许的邮箱域名'
action
=
{
action
=
{
<
Button
type
=
"button"
onClick
=
{()
=>
{
<
Button
type
=
'button'
onClick
=
{()
=>
{
submitNewRestrictedDomain
();
submitNewRestrictedDomain
();
}}
>
填入
<
/Button
>
}}
>
填入
<
/Button
>
}
}
onKeyDown
=
{(
e
)
=>
{
onKeyDown
=
{(
e
)
=>
{
if
(
e
.
key
===
'Enter'
)
{
if
(
e
.
key
===
'Enter'
)
{
submitNewRestrictedDomain
();
submitNewRestrictedDomain
();
}
}
}}
}}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
placeholder
=
"输入新的允许的邮箱域名"
placeholder
=
'输入新的允许的邮箱域名'
value
=
{
restrictedDomainInput
}
value
=
{
restrictedDomainInput
}
onChange
=
{(
e
,
{
value
})
=>
{
onChange
=
{(
e
,
{
value
})
=>
{
setRestrictedDomainInput
(
value
);
setRestrictedDomainInput
(
value
);
}}
}}
/
>
/
>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{
submitEmailDomainWhitelist
}
>
保存邮箱域名白名单设置
<
/Form.Button
>
<
Form
.
Button
onClick
=
{
submitEmailDomainWhitelist
}
>
保存邮箱域名白名单设置
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
配置
SMTP
配置
SMTP
<
Header
.
Subheader
>
用以支持系统的邮件发送
<
/Header.Subheader
>
<
Header
.
Subheader
>
用以支持系统的邮件发送
<
/Header.Subheader
>
<
/Header
>
<
/Header
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"SMTP 服务器地址"
label
=
'SMTP 服务器地址'
name
=
"SMTPServer"
name
=
'SMTPServer'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
SMTPServer
}
value
=
{
inputs
.
SMTPServer
}
placeholder
=
"例如:smtp.qq.com"
placeholder
=
'例如:smtp.qq.com'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"SMTP 端口"
label
=
'SMTP 端口'
name
=
"SMTPPort"
name
=
'SMTPPort'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
SMTPPort
}
value
=
{
inputs
.
SMTPPort
}
placeholder
=
"默认: 587"
placeholder
=
'默认: 587'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"SMTP 账户"
label
=
'SMTP 账户'
name
=
"SMTPAccount"
name
=
'SMTPAccount'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
SMTPAccount
}
value
=
{
inputs
.
SMTPAccount
}
placeholder
=
"通常是邮箱地址"
placeholder
=
'通常是邮箱地址'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"SMTP 发送者邮箱"
label
=
'SMTP 发送者邮箱'
name
=
"SMTPFrom"
name
=
'SMTPFrom'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
SMTPFrom
}
value
=
{
inputs
.
SMTPFrom
}
placeholder
=
"通常和邮箱地址保持一致"
placeholder
=
'通常和邮箱地址保持一致'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"SMTP 访问凭证"
label
=
'SMTP 访问凭证'
name
=
"SMTPToken"
name
=
'SMTPToken'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
type
=
"password"
type
=
'password'
autoComplete
=
"new-password"
autoComplete
=
'new-password'
checked
=
{
inputs
.
RegisterEnabled
===
'true'
}
checked
=
{
inputs
.
RegisterEnabled
===
'true'
}
placeholder
=
"敏感信息不会发送到前端显示"
placeholder
=
'敏感信息不会发送到前端显示'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{
submitSMTP
}
>
保存
SMTP
设置
<
/Form.Button
>
<
Form
.
Button
onClick
=
{
submitSMTP
}
>
保存
SMTP
设置
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
配置
GitHub
OAuth
App
配置
GitHub
OAuth
App
<
Header
.
Subheader
>
<
Header
.
Subheader
>
用以支持通过
GitHub
进行登录注册,
用以支持通过
GitHub
进行登录注册,
<
a
href
=
"https://github.com/settings/developers"
target
=
"_blank"
rel
=
"noreferrer"
>
<
a
href
=
'https://github.com/settings/developers'
target
=
'_blank'
rel
=
'noreferrer'
>
点击此处
点击此处
<
/a
>
<
/a
>
管理你的
GitHub
OAuth
App
管理你的
GitHub
OAuth
App
...
@@ -556,34 +592,35 @@ const SystemSetting = () => {
...
@@ -556,34 +592,35 @@ const SystemSetting = () => {
<
/Message
>
<
/Message
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"GitHub Client ID"
label
=
'GitHub Client ID'
name
=
"GitHubClientId"
name
=
'GitHubClientId'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
GitHubClientId
}
value
=
{
inputs
.
GitHubClientId
}
placeholder
=
"输入你注册的 GitHub OAuth APP 的 ID"
placeholder
=
'输入你注册的 GitHub OAuth APP 的 ID'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"GitHub Client Secret"
label
=
'GitHub Client Secret'
name
=
"GitHubClientSecret"
name
=
'GitHubClientSecret'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
type
=
"password"
type
=
'password'
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
GitHubClientSecret
}
value
=
{
inputs
.
GitHubClientSecret
}
placeholder
=
"敏感信息不会发送到前端显示"
placeholder
=
'敏感信息不会发送到前端显示'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{
submitGitHubOAuth
}
>
<
Form
.
Button
onClick
=
{
submitGitHubOAuth
}
>
保存
GitHub
OAuth
设置
保存
GitHub
OAuth
设置
<
/Form.Button
>
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
配置
WeChat
Server
配置
WeChat
Server
<
Header
.
Subheader
>
<
Header
.
Subheader
>
用以支持通过微信进行登录注册,
用以支持通过微信进行登录注册,
<
a
<
a
href
=
"https://github.com/songquanpeng/wechat-server"
href
=
'https://github.com/songquanpeng/wechat-server'
target
=
"_blank"
rel
=
"noreferrer"
target
=
'_blank'
rel
=
'noreferrer'
>
>
点击此处
点击此处
<
/a
>
<
/a
>
...
@@ -592,61 +629,65 @@ const SystemSetting = () => {
...
@@ -592,61 +629,65 @@ const SystemSetting = () => {
<
/Header
>
<
/Header
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"WeChat Server 服务器地址"
label
=
'WeChat Server 服务器地址'
name
=
"WeChatServerAddress"
name
=
'WeChatServerAddress'
placeholder
=
"例如:https://yourdomain.com"
placeholder
=
'例如:https://yourdomain.com'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
WeChatServerAddress
}
value
=
{
inputs
.
WeChatServerAddress
}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
label
=
"WeChat Server 访问凭证"
label
=
'WeChat Server 访问凭证'
name
=
"WeChatServerToken"
name
=
'WeChatServerToken'
type
=
"password"
type
=
'password'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
WeChatServerToken
}
value
=
{
inputs
.
WeChatServerToken
}
placeholder
=
"敏感信息不会发送到前端显示"
placeholder
=
'敏感信息不会发送到前端显示'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"微信公众号二维码图片链接"
label
=
'微信公众号二维码图片链接'
name
=
"WeChatAccountQRCodeImageURL"
name
=
'WeChatAccountQRCodeImageURL'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
WeChatAccountQRCodeImageURL
}
value
=
{
inputs
.
WeChatAccountQRCodeImageURL
}
placeholder
=
"输入一个图片链接"
placeholder
=
'输入一个图片链接'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{
submitWeChat
}
>
<
Form
.
Button
onClick
=
{
submitWeChat
}
>
保存
WeChat
Server
设置
保存
WeChat
Server
设置
<
/Form.Button
>
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
配置
Telegram
登录
<
/Header
>
<
Header
as
=
'h3'
>
配置
Telegram
登录
<
/Header
>
<
Form
.
Group
inline
>
<
Form
.
Group
inline
>
<
Form
.
Input
<
Form
.
Input
label
=
"Telegram Bot Token"
label
=
'Telegram Bot Token'
name
=
"TelegramBotToken"
name
=
'TelegramBotToken'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
value
=
{
inputs
.
TelegramBotToken
}
value
=
{
inputs
.
TelegramBotToken
}
placeholder
=
"输入你的 Telegram Bot Token"
placeholder
=
'输入你的 Telegram Bot Token'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"Telegram Bot 名称"
label
=
'Telegram Bot 名称'
name
=
"TelegramBotName"
name
=
'TelegramBotName'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
value
=
{
inputs
.
TelegramBotName
}
value
=
{
inputs
.
TelegramBotName
}
placeholder
=
"输入你的 Telegram Bot 名称"
placeholder
=
'输入你的 Telegram Bot 名称'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{
submitTelegramSettings
}
>
<
Form
.
Button
onClick
=
{
submitTelegramSettings
}
>
保存
Telegram
登录设置
保存
Telegram
登录设置
<
/Form.Button
>
<
/Form.Button
>
<
Divider
/>
<
Divider
/>
<
Header
as
=
"h3"
>
<
Header
as
=
'h3'
>
配置
Turnstile
配置
Turnstile
<
Header
.
Subheader
>
<
Header
.
Subheader
>
用以支持用户校验,
用以支持用户校验,
<
a
href
=
"https://dash.cloudflare.com/"
target
=
"_blank"
rel
=
"noreferrer"
>
<
a
href
=
'https://dash.cloudflare.com/'
target
=
'_blank'
rel
=
'noreferrer'
>
点击此处
点击此处
<
/a
>
<
/a
>
管理你的
Turnstile
Sites
,推荐选择
Invisible
Widget
Type
管理你的
Turnstile
Sites
,推荐选择
Invisible
Widget
Type
...
@@ -654,21 +695,21 @@ const SystemSetting = () => {
...
@@ -654,21 +695,21 @@ const SystemSetting = () => {
<
/Header
>
<
/Header
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Group
widths
=
{
3
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"Turnstile Site Key"
label
=
'Turnstile Site Key'
name
=
"TurnstileSiteKey"
name
=
'TurnstileSiteKey'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
TurnstileSiteKey
}
value
=
{
inputs
.
TurnstileSiteKey
}
placeholder
=
"输入你注册的 Turnstile Site Key"
placeholder
=
'输入你注册的 Turnstile Site Key'
/>
/>
<
Form
.
Input
<
Form
.
Input
label
=
"Turnstile Secret Key"
label
=
'Turnstile Secret Key'
name
=
"TurnstileSecretKey"
name
=
'TurnstileSecretKey'
onChange
=
{
handleInputChange
}
onChange
=
{
handleInputChange
}
type
=
"password"
type
=
'password'
autoComplete
=
"new-password"
autoComplete
=
'new-password'
value
=
{
inputs
.
TurnstileSecretKey
}
value
=
{
inputs
.
TurnstileSecretKey
}
placeholder
=
"敏感信息不会发送到前端显示"
placeholder
=
'敏感信息不会发送到前端显示'
/>
/>
<
/Form.Group
>
<
/Form.Group
>
<
Form
.
Button
onClick
=
{
submitTurnstile
}
>
<
Form
.
Button
onClick
=
{
submitTurnstile
}
>
...
...
web/src/components/TokensTable.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
API
,
copy
,
showError
,
showSuccess
,
timestamp2string
}
from
'../helpers'
;
import
{
API
,
copy
,
showError
,
showSuccess
,
timestamp2string
,
}
from
'../helpers'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderQuota
}
from
'../helpers/render'
;
import
{
renderQuota
}
from
'../helpers/render'
;
import
{
Button
,
Dropdown
,
Form
,
Modal
,
Popconfirm
,
Popover
,
SplitButtonGroup
,
Table
,
Tag
}
from
'@douyinfe/semi-ui'
;
import
{
Button
,
Dropdown
,
Form
,
Modal
,
Popconfirm
,
Popover
,
SplitButtonGroup
,
Table
,
Tag
,
}
from
'@douyinfe/semi-ui'
;
import
{
IconTreeTriangleDown
}
from
'@douyinfe/semi-icons'
;
import
{
IconTreeTriangleDown
}
from
'@douyinfe/semi-icons'
;
import
EditToken
from
'../pages/Token/EditToken'
;
import
EditToken
from
'../pages/Token/EditToken'
;
...
@@ -11,85 +27,107 @@ import EditToken from '../pages/Token/EditToken';
...
@@ -11,85 +27,107 @@ import EditToken from '../pages/Token/EditToken';
const
COPY_OPTIONS
=
[
const
COPY_OPTIONS
=
[
{
key
:
'next'
,
text
:
'ChatGPT Next Web'
,
value
:
'next'
},
{
key
:
'next'
,
text
:
'ChatGPT Next Web'
,
value
:
'next'
},
{
key
:
'ama'
,
text
:
'ChatGPT Web & Midjourney'
,
value
:
'ama'
},
{
key
:
'ama'
,
text
:
'ChatGPT Web & Midjourney'
,
value
:
'ama'
},
{
key
:
'opencat'
,
text
:
'OpenCat'
,
value
:
'opencat'
}
{
key
:
'opencat'
,
text
:
'OpenCat'
,
value
:
'opencat'
}
,
];
];
const
OPEN_LINK_OPTIONS
=
[
const
OPEN_LINK_OPTIONS
=
[
{
key
:
'ama'
,
text
:
'ChatGPT Web & Midjourney'
,
value
:
'ama'
},
{
key
:
'ama'
,
text
:
'ChatGPT Web & Midjourney'
,
value
:
'ama'
},
{
key
:
'opencat'
,
text
:
'OpenCat'
,
value
:
'opencat'
}
{
key
:
'opencat'
,
text
:
'OpenCat'
,
value
:
'opencat'
}
,
];
];
function
renderTimestamp
(
timestamp
)
{
function
renderTimestamp
(
timestamp
)
{
return
(
return
<>
{
timestamp2string
(
timestamp
)}
<
/>
;
<>
{
timestamp2string
(
timestamp
)}
<
/
>
);
}
}
function
renderStatus
(
status
,
model_limits_enabled
=
false
)
{
function
renderStatus
(
status
,
model_limits_enabled
=
false
)
{
switch
(
status
)
{
switch
(
status
)
{
case
1
:
case
1
:
if
(
model_limits_enabled
)
{
if
(
model_limits_enabled
)
{
return
<
Tag
color
=
"green"
size
=
"large"
>
已启用:限制模型
<
/Tag>
;
return
(
<
Tag
color
=
'green'
size
=
'large'
>
已启用:限制模型
<
/Tag
>
);
}
else
{
}
else
{
return
<
Tag
color
=
"green"
size
=
"large"
>
已启用
<
/Tag>
;
return
(
<
Tag
color
=
'green'
size
=
'large'
>
已启用
<
/Tag
>
);
}
}
case
2
:
case
2
:
return
<
Tag
color
=
"red"
size
=
"large"
>
已禁用
<
/Tag>
;
return
(
<
Tag
color
=
'red'
size
=
'large'
>
{
' '
}
已禁用
{
' '
}
<
/Tag
>
);
case
3
:
case
3
:
return
<
Tag
color
=
"yellow"
size
=
"large"
>
已过期
<
/Tag>
;
return
(
<
Tag
color
=
'yellow'
size
=
'large'
>
{
' '
}
已过期
{
' '
}
<
/Tag
>
);
case
4
:
case
4
:
return
<
Tag
color
=
"grey"
size
=
"large"
>
已耗尽
<
/Tag>
;
return
(
<
Tag
color
=
'grey'
size
=
'large'
>
{
' '
}
已耗尽
{
' '
}
<
/Tag
>
);
default
:
default
:
return
<
Tag
color
=
"black"
size
=
"large"
>
未知状态
<
/Tag>
;
return
(
<
Tag
color
=
'black'
size
=
'large'
>
{
' '
}
未知状态
{
' '
}
<
/Tag
>
);
}
}
}
}
const
TokensTable
=
()
=>
{
const
TokensTable
=
()
=>
{
const
link_menu
=
[
const
link_menu
=
[
{
{
node
:
'item'
,
key
:
'next'
,
name
:
'ChatGPT Next Web'
,
onClick
:
()
=>
{
node
:
'item'
,
key
:
'next'
,
name
:
'ChatGPT Next Web'
,
onClick
:
()
=>
{
onOpenLink
(
'next'
);
onOpenLink
(
'next'
);
}
}
,
},
},
{
node
:
'item'
,
key
:
'ama'
,
name
:
'AMA 问天'
,
value
:
'ama'
},
{
node
:
'item'
,
key
:
'ama'
,
name
:
'AMA 问天'
,
value
:
'ama'
},
{
{
node
:
'item'
,
key
:
'next-mj'
,
name
:
'ChatGPT Web & Midjourney'
,
value
:
'next-mj'
,
onClick
:
()
=>
{
node
:
'item'
,
key
:
'next-mj'
,
name
:
'ChatGPT Web & Midjourney'
,
value
:
'next-mj'
,
onClick
:
()
=>
{
onOpenLink
(
'next-mj'
);
onOpenLink
(
'next-mj'
);
}
},
},
{
node
:
'item'
,
key
:
'opencat'
,
name
:
'OpenCat'
,
value
:
'opencat'
}
},
{
node
:
'item'
,
key
:
'opencat'
,
name
:
'OpenCat'
,
value
:
'opencat'
},
];
];
const
columns
=
[
const
columns
=
[
{
{
title
:
'名称'
,
title
:
'名称'
,
dataIndex
:
'name'
dataIndex
:
'name'
,
},
},
{
{
title
:
'状态'
,
title
:
'状态'
,
dataIndex
:
'status'
,
dataIndex
:
'status'
,
key
:
'status'
,
key
:
'status'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderStatus
(
text
,
record
.
model_limits_enabled
)}
<
/div>
;
<
div
>
},
{
renderStatus
(
text
,
record
.
model_limits_enabled
)}
<
/div
>
);
}
},
},
{
{
title
:
'已用额度'
,
title
:
'已用额度'
,
dataIndex
:
'used_quota'
,
dataIndex
:
'used_quota'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderQuota
(
parseInt
(
text
))}
<
/div>
;
<
div
>
},
{
renderQuota
(
parseInt
(
text
))}
<
/div
>
);
}
},
},
{
{
title
:
'剩余额度'
,
title
:
'剩余额度'
,
...
@@ -97,22 +135,25 @@ const TokensTable = () => {
...
@@ -97,22 +135,25 @@ const TokensTable = () => {
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
(
<
div
>
<
div
>
{
record
.
unlimited_quota
?
<
Tag
size
=
{
'large'
}
color
=
{
'white'
}
>
无限制
<
/Tag>
:
{
record
.
unlimited_quota
?
(
<
Tag
size
=
{
'large'
}
color
=
{
'light-blue'
}
>
{
renderQuota
(
parseInt
(
text
))}
<
/Tag>
}
<
Tag
size
=
{
'large'
}
color
=
{
'white'
}
>
无限制
<
/Tag
>
)
:
(
<
Tag
size
=
{
'large'
}
color
=
{
'light-blue'
}
>
{
renderQuota
(
parseInt
(
text
))}
<
/Tag
>
)}
<
/div
>
<
/div
>
);
);
}
}
,
},
},
{
{
title
:
'创建时间'
,
title
:
'创建时间'
,
dataIndex
:
'created_time'
,
dataIndex
:
'created_time'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
<
div
>
{
renderTimestamp
(
text
)}
<
/div>
;
<
div
>
},
{
renderTimestamp
(
text
)}
<
/div
>
);
}
},
},
{
{
title
:
'过期时间'
,
title
:
'过期时间'
,
...
@@ -123,7 +164,7 @@ const TokensTable = () => {
...
@@ -123,7 +164,7 @@ const TokensTable = () => {
{
record
.
expired_time
===
-
1
?
'永不过期'
:
renderTimestamp
(
text
)}
{
record
.
expired_time
===
-
1
?
'永不过期'
:
renderTimestamp
(
text
)}
<
/div
>
<
/div
>
);
);
}
}
,
},
},
{
{
title
:
''
,
title
:
''
,
...
@@ -131,25 +172,41 @@ const TokensTable = () => {
...
@@ -131,25 +172,41 @@ const TokensTable = () => {
render
:
(
text
,
record
,
index
)
=>
(
render
:
(
text
,
record
,
index
)
=>
(
<
div
>
<
div
>
<
Popover
<
Popover
content
=
{
content
=
{
'sk-'
+
record
.
key
}
'sk-'
+
record
.
key
}
style
=
{{
padding
:
20
}}
style
=
{{
padding
:
20
}}
position
=
"top"
position
=
'top'
>
>
<
Button
theme
=
"light"
type
=
"tertiary"
style
=
{{
marginRight
:
1
}}
>
查看
<
/Button
>
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
>
查看
<
/Button
>
<
/Popover
>
<
/Popover
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
1
}}
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
(
text
)
=>
{
onClick
=
{
async
(
text
)
=>
{
await
copyText
(
'sk-'
+
record
.
key
);
await
copyText
(
'sk-'
+
record
.
key
);
}}
}}
>
复制
<
/Button
>
>
<
SplitButtonGroup
style
=
{{
marginRight
:
1
}}
aria
-
label
=
"项目操作按钮组"
>
复制
<
Button
theme
=
"light"
style
=
{{
color
:
'rgba(var(--semi-teal-7), 1)'
}}
onClick
=
{()
=>
{
<
/Button
>
<
SplitButtonGroup
style
=
{{
marginRight
:
1
}}
aria
-
label
=
'项目操作按钮组'
>
<
Button
theme
=
'light'
style
=
{{
color
:
'rgba(var(--semi-teal-7), 1)'
}}
onClick
=
{()
=>
{
onOpenLink
(
'next'
,
record
.
key
);
onOpenLink
(
'next'
,
record
.
key
);
}}
>
聊天
<
/Button
>
}}
<
Dropdown
trigger
=
"click"
position
=
"bottomRight"
menu
=
{
>
[
聊天
<
/Button
>
<
Dropdown
trigger
=
'click'
position
=
'bottomRight'
menu
=
{[
{
{
node
:
'item'
,
node
:
'item'
,
key
:
'next'
,
key
:
'next'
,
...
@@ -157,7 +214,7 @@ const TokensTable = () => {
...
@@ -157,7 +214,7 @@ const TokensTable = () => {
name
:
'ChatGPT Next Web'
,
name
:
'ChatGPT Next Web'
,
onClick
:
()
=>
{
onClick
:
()
=>
{
onOpenLink
(
'next'
,
record
.
key
);
onOpenLink
(
'next'
,
record
.
key
);
}
}
,
},
},
{
{
node
:
'item'
,
node
:
'item'
,
...
@@ -166,70 +223,88 @@ const TokensTable = () => {
...
@@ -166,70 +223,88 @@ const TokensTable = () => {
name
:
'ChatGPT Web & Midjourney'
,
name
:
'ChatGPT Web & Midjourney'
,
onClick
:
()
=>
{
onClick
:
()
=>
{
onOpenLink
(
'next-mj'
,
record
.
key
);
onOpenLink
(
'next-mj'
,
record
.
key
);
}
}
,
},
},
{
{
node
:
'item'
,
key
:
'ama'
,
name
:
'AMA 问天(BotGem)'
,
onClick
:
()
=>
{
node
:
'item'
,
key
:
'ama'
,
name
:
'AMA 问天(BotGem)'
,
onClick
:
()
=>
{
onOpenLink
(
'ama'
,
record
.
key
);
onOpenLink
(
'ama'
,
record
.
key
);
}
}
,
},
},
{
{
node
:
'item'
,
key
:
'opencat'
,
name
:
'OpenCat'
,
onClick
:
()
=>
{
node
:
'item'
,
key
:
'opencat'
,
name
:
'OpenCat'
,
onClick
:
()
=>
{
onOpenLink
(
'opencat'
,
record
.
key
);
onOpenLink
(
'opencat'
,
record
.
key
);
}
},
}
},
]
]}
}
>
>
<
Button
style
=
{{
padding
:
'8px 4px'
,
color
:
'rgba(var(--semi-teal-7), 1)'
}}
type
=
"primary"
<
Button
icon
=
{
<
IconTreeTriangleDown
/>
}
><
/Button
>
style
=
{{
padding
:
'8px 4px'
,
color
:
'rgba(var(--semi-teal-7), 1)'
,
}}
type
=
'primary'
icon
=
{
<
IconTreeTriangleDown
/>
}
><
/Button
>
<
/Dropdown
>
<
/Dropdown
>
<
/SplitButtonGroup
>
<
/SplitButtonGroup
>
<
Popconfirm
<
Popconfirm
title
=
"确定是否要删除此令牌?"
title
=
'确定是否要删除此令牌?'
content
=
"此修改将不可逆"
content
=
'此修改将不可逆'
okType
=
{
'danger'
}
okType
=
{
'danger'
}
position
=
{
'left'
}
position
=
{
'left'
}
onConfirm
=
{()
=>
{
onConfirm
=
{()
=>
{
manageToken
(
record
.
id
,
'delete'
,
record
).
then
(
manageToken
(
record
.
id
,
'delete'
,
record
).
then
(()
=>
{
()
=>
{
removeRecord
(
record
.
key
);
removeRecord
(
record
.
key
);
}
});
);
}}
}}
>
>
<
Button
theme
=
"light"
type
=
"danger"
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
{
{
record
.
status
===
1
?
(
record
.
status
===
1
?
<
Button
<
Button
theme
=
"light"
type
=
"warning"
style
=
{{
marginRight
:
1
}}
onClick
=
{
theme
=
'light'
async
()
=>
{
type
=
'warning'
manageToken
(
style
=
{{
marginRight
:
1
}}
record
.
id
,
onClick
=
{
async
()
=>
{
'disable'
,
manageToken
(
record
.
id
,
'disable'
,
record
);
record
}}
);
>
}
禁用
}
>
禁用
<
/Button>
:
<
/Button
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
1
}}
onClick
=
{
)
:
(
async
()
=>
{
<
Button
manageToken
(
theme
=
'light'
record
.
id
,
type
=
'secondary'
'enable'
,
style
=
{{
marginRight
:
1
}}
record
onClick
=
{
async
()
=>
{
);
manageToken
(
record
.
id
,
'enable'
,
record
);
}
}}
}
>
启用
<
/Button
>
>
}
启用
<
Button
theme
=
"light"
type
=
"tertiary"
style
=
{{
marginRight
:
1
}}
onClick
=
{
<
/Button
>
()
=>
{
)}
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
onClick
=
{()
=>
{
setEditingToken
(
record
);
setEditingToken
(
record
);
setShowEdit
(
true
);
setShowEdit
(
true
);
}
}}
}
>
编辑
<
/Button
>
>
编辑
<
/Button
>
<
/div
>
<
/div
>
)
)
,
}
}
,
];
];
const
[
pageSize
,
setPageSize
]
=
useState
(
ITEMS_PER_PAGE
);
const
[
pageSize
,
setPageSize
]
=
useState
(
ITEMS_PER_PAGE
);
...
@@ -245,14 +320,14 @@ const TokensTable = () => {
...
@@ -245,14 +320,14 @@ const TokensTable = () => {
const
[
showTopUpModal
,
setShowTopUpModal
]
=
useState
(
false
);
const
[
showTopUpModal
,
setShowTopUpModal
]
=
useState
(
false
);
const
[
targetTokenIdx
,
setTargetTokenIdx
]
=
useState
(
0
);
const
[
targetTokenIdx
,
setTargetTokenIdx
]
=
useState
(
0
);
const
[
editingToken
,
setEditingToken
]
=
useState
({
const
[
editingToken
,
setEditingToken
]
=
useState
({
id
:
undefined
id
:
undefined
,
});
});
const
closeEdit
=
()
=>
{
const
closeEdit
=
()
=>
{
setShowEdit
(
false
);
setShowEdit
(
false
);
setTimeout
(()
=>
{
setTimeout
(()
=>
{
setEditingToken
({
setEditingToken
({
id
:
undefined
id
:
undefined
,
});
});
},
500
);
},
500
);
};
};
...
@@ -266,7 +341,10 @@ const TokensTable = () => {
...
@@ -266,7 +341,10 @@ const TokensTable = () => {
}
}
};
};
let
pageData
=
tokens
.
slice
((
activePage
-
1
)
*
pageSize
,
activePage
*
pageSize
);
let
pageData
=
tokens
.
slice
(
(
activePage
-
1
)
*
pageSize
,
activePage
*
pageSize
,
);
const
loadTokens
=
async
(
startIdx
)
=>
{
const
loadTokens
=
async
(
startIdx
)
=>
{
setLoading
(
true
);
setLoading
(
true
);
const
res
=
await
API
.
get
(
`/api/token/?p=
${
startIdx
}
&size=
${
pageSize
}
`
);
const
res
=
await
API
.
get
(
`/api/token/?p=
${
startIdx
}
&size=
${
pageSize
}
`
);
...
@@ -315,7 +393,8 @@ const TokensTable = () => {
...
@@ -315,7 +393,8 @@ const TokensTable = () => {
let
nextUrl
;
let
nextUrl
;
if
(
nextLink
)
{
if
(
nextLink
)
{
nextUrl
=
nextLink
+
`/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
nextUrl
=
nextLink
+
`/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
}
else
{
}
else
{
nextUrl
=
`https://chat.oneapi.pro/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
nextUrl
=
`https://chat.oneapi.pro/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
}
}
...
@@ -323,7 +402,8 @@ const TokensTable = () => {
...
@@ -323,7 +402,8 @@ const TokensTable = () => {
let
url
;
let
url
;
switch
(
type
)
{
switch
(
type
)
{
case
'ama'
:
case
'ama'
:
url
=
mjLink
+
`/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
url
=
mjLink
+
`/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
break
;
break
;
case
'opencat'
:
case
'opencat'
:
url
=
`opencat://team/join?domain=
${
encodedServerAddress
}
&token=sk-
${
key
}
`
;
url
=
`opencat://team/join?domain=
${
encodedServerAddress
}
&token=sk-
${
key
}
`
;
...
@@ -367,7 +447,8 @@ const TokensTable = () => {
...
@@ -367,7 +447,8 @@ const TokensTable = () => {
let
defaultUrl
;
let
defaultUrl
;
if
(
chatLink
)
{
if
(
chatLink
)
{
defaultUrl
=
chatLink
+
`/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
defaultUrl
=
chatLink
+
`/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
}
}
let
url
;
let
url
;
switch
(
type
)
{
switch
(
type
)
{
...
@@ -378,7 +459,8 @@ const TokensTable = () => {
...
@@ -378,7 +459,8 @@ const TokensTable = () => {
url
=
`opencat://team/join?domain=
${
encodedServerAddress
}
&token=sk-
${
key
}
`
;
url
=
`opencat://team/join?domain=
${
encodedServerAddress
}
&token=sk-
${
key
}
`
;
break
;
break
;
case
'next-mj'
:
case
'next-mj'
:
url
=
mjLink
+
`/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
url
=
mjLink
+
`/#/?settings={"key":"sk-
${
key
}
","url":"
${
serverAddress
}
"}`
;
break
;
break
;
default
:
default
:
if
(
!
chatLink
)
{
if
(
!
chatLink
)
{
...
@@ -399,10 +481,10 @@ const TokensTable = () => {
...
@@ -399,10 +481,10 @@ const TokensTable = () => {
});
});
},
[
pageSize
]);
},
[
pageSize
]);
const
removeRecord
=
key
=>
{
const
removeRecord
=
(
key
)
=>
{
let
newDataSource
=
[...
tokens
];
let
newDataSource
=
[...
tokens
];
if
(
key
!=
null
)
{
if
(
key
!=
null
)
{
let
idx
=
newDataSource
.
findIndex
(
data
=>
data
.
key
===
key
);
let
idx
=
newDataSource
.
findIndex
(
(
data
)
=>
data
.
key
===
key
);
if
(
idx
>
-
1
)
{
if
(
idx
>
-
1
)
{
newDataSource
.
splice
(
idx
,
1
);
newDataSource
.
splice
(
idx
,
1
);
...
@@ -435,7 +517,6 @@ const TokensTable = () => {
...
@@ -435,7 +517,6 @@ const TokensTable = () => {
let
newTokens
=
[...
tokens
];
let
newTokens
=
[...
tokens
];
// let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
// let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
if
(
action
===
'delete'
)
{
if
(
action
===
'delete'
)
{
}
else
{
}
else
{
record
.
status
=
token
.
status
;
record
.
status
=
token
.
status
;
// newTokens[realIdx].status = token.status;
// newTokens[realIdx].status = token.status;
...
@@ -455,7 +536,9 @@ const TokensTable = () => {
...
@@ -455,7 +536,9 @@ const TokensTable = () => {
return
;
return
;
}
}
setSearching
(
true
);
setSearching
(
true
);
const
res
=
await
API
.
get
(
`/api/token/search?keyword=
${
searchKeyword
}
&token=
${
searchToken
}
`
);
const
res
=
await
API
.
get
(
`/api/token/search?keyword=
${
searchKeyword
}
&token=
${
searchToken
}
`
,
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
setTokensFormat
(
data
);
setTokensFormat
(
data
);
...
@@ -488,32 +571,28 @@ const TokensTable = () => {
...
@@ -488,32 +571,28 @@ const TokensTable = () => {
setLoading
(
false
);
setLoading
(
false
);
};
};
const
handlePageChange
=
(
page
)
=>
{
const
handlePageChange
=
page
=>
{
setActivePage
(
page
);
setActivePage
(
page
);
if
(
page
===
Math
.
ceil
(
tokens
.
length
/
pageSize
)
+
1
)
{
if
(
page
===
Math
.
ceil
(
tokens
.
length
/
pageSize
)
+
1
)
{
// In this case we have to load more data and then append them.
// In this case we have to load more data and then append them.
loadTokens
(
page
-
1
).
then
(
r
=>
{
loadTokens
(
page
-
1
).
then
((
r
)
=>
{});
});
}
}
};
};
const
rowSelection
=
{
const
rowSelection
=
{
onSelect
:
(
record
,
selected
)
=>
{
onSelect
:
(
record
,
selected
)
=>
{},
},
onSelectAll
:
(
selected
,
selectedRows
)
=>
{},
onSelectAll
:
(
selected
,
selectedRows
)
=>
{
},
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
setSelectedKeys
(
selectedRows
);
setSelectedKeys
(
selectedRows
);
}
}
,
};
};
const
handleRow
=
(
record
,
index
)
=>
{
const
handleRow
=
(
record
,
index
)
=>
{
if
(
record
.
status
!==
1
)
{
if
(
record
.
status
!==
1
)
{
return
{
return
{
style
:
{
style
:
{
background
:
'var(--semi-color-disabled-border)'
background
:
'var(--semi-color-disabled-border)'
,
}
}
,
};
};
}
else
{
}
else
{
return
{};
return
{};
...
@@ -522,63 +601,98 @@ const TokensTable = () => {
...
@@ -522,63 +601,98 @@ const TokensTable = () => {
return
(
return
(
<>
<>
<
EditToken
refresh
=
{
refresh
}
editingToken
=
{
editingToken
}
visiable
=
{
showEdit
}
handleClose
=
{
closeEdit
}
><
/EditToken
>
<
EditToken
<
Form
layout
=
"horizontal"
style
=
{{
marginTop
:
10
}}
labelPosition
=
{
'left'
}
>
refresh
=
{
refresh
}
editingToken
=
{
editingToken
}
visiable
=
{
showEdit
}
handleClose
=
{
closeEdit
}
><
/EditToken
>
<
Form
layout
=
'horizontal'
style
=
{{
marginTop
:
10
}}
labelPosition
=
{
'left'
}
>
<
Form
.
Input
<
Form
.
Input
field
=
"keyword"
field
=
'keyword'
label
=
"搜索关键字"
label
=
'搜索关键字'
placeholder
=
"令牌名称"
placeholder
=
'令牌名称'
value
=
{
searchKeyword
}
value
=
{
searchKeyword
}
loading
=
{
searching
}
loading
=
{
searching
}
onChange
=
{
handleKeywordChange
}
onChange
=
{
handleKeywordChange
}
/
>
/
>
<
Form
.
Input
<
Form
.
Input
field
=
"token"
field
=
'token'
label
=
"Key"
label
=
'Key'
placeholder
=
"密钥"
placeholder
=
'密钥'
value
=
{
searchToken
}
value
=
{
searchToken
}
loading
=
{
searching
}
loading
=
{
searching
}
onChange
=
{
handleSearchTokenChange
}
onChange
=
{
handleSearchTokenChange
}
/
>
/
>
<
Button
label
=
"查询"
type
=
"primary"
htmlType
=
"submit"
className
=
"btn-margin-right"
<
Button
onClick
=
{
searchTokens
}
style
=
{{
marginRight
:
8
}}
>
查询
<
/Button
>
label
=
'查询'
type
=
'primary'
htmlType
=
'submit'
className
=
'btn-margin-right'
onClick
=
{
searchTokens
}
style
=
{{
marginRight
:
8
}}
>
查询
<
/Button
>
<
/Form
>
<
/Form
>
<
Table
style
=
{{
marginTop
:
20
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
<
Table
style
=
{{
marginTop
:
20
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
currentPage
:
activePage
,
pageSize
:
pageSize
,
pageSize
:
pageSize
,
total
:
tokenCount
,
total
:
tokenCount
,
showSizeChanger
:
true
,
showSizeChanger
:
true
,
pageSizeOptions
:
[
10
,
20
,
50
,
100
],
pageSizeOptions
:
[
10
,
20
,
50
,
100
],
formatPageText
:
(
page
)
=>
`第
${
page
.
currentStart
}
-
${
page
.
currentEnd
}
条,共
${
tokens
.
length
}
条`
,
formatPageText
:
(
page
)
=>
`第
${
page
.
currentStart
}
-
${
page
.
currentEnd
}
条,共
${
tokens
.
length
}
条`
,
onPageSizeChange
:
(
size
)
=>
{
onPageSizeChange
:
(
size
)
=>
{
setPageSize
(
size
);
setPageSize
(
size
);
setActivePage
(
1
);
setActivePage
(
1
);
},
},
onPageChange
:
handlePageChange
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
rowSelection
=
{
rowSelection
}
onRow
=
{
handleRow
}
>
}}
<
/Table
>
loading
=
{
loading
}
<
Button
theme
=
"light"
type
=
"primary"
style
=
{{
marginRight
:
8
}}
onClick
=
{
rowSelection
=
{
rowSelection
}
()
=>
{
onRow
=
{
handleRow
}
><
/Table
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{()
=>
{
setEditingToken
({
setEditingToken
({
id
:
undefined
id
:
undefined
,
});
});
setShowEdit
(
true
);
setShowEdit
(
true
);
}
}}
}
>
添加令牌
<
/Button
>
>
<
Button
label
=
"复制所选令牌"
type
=
"warning"
onClick
=
{
添加令牌
async
()
=>
{
<
/Button
>
<
Button
label
=
'复制所选令牌'
type
=
'warning'
onClick
=
{
async
()
=>
{
if
(
selectedKeys
.
length
===
0
)
{
if
(
selectedKeys
.
length
===
0
)
{
showError
(
'请至少选择一个令牌!'
);
showError
(
'请至少选择一个令牌!'
);
return
;
return
;
}
}
let
keys
=
''
;
let
keys
=
''
;
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
keys
+=
selectedKeys
[
i
].
name
+
' sk-'
+
selectedKeys
[
i
].
key
+
'\n'
;
keys
+=
selectedKeys
[
i
].
name
+
' sk-'
+
selectedKeys
[
i
].
key
+
'\n'
;
}
}
await
copyText
(
keys
);
await
copyText
(
keys
);
}
}}
}
>
复制所选令牌到剪贴板
<
/Button
>
>
复制所选令牌到剪贴板
<
/Button
>
<
/
>
<
/
>
);
);
};
};
...
...
web/src/components/UsersTable.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
API
,
showError
,
showSuccess
}
from
'../helpers'
;
import
{
API
,
showError
,
showSuccess
}
from
'../helpers'
;
import
{
Button
,
Form
,
Popconfirm
,
Space
,
Table
,
Tag
,
Tooltip
}
from
'@douyinfe/semi-ui'
;
import
{
Button
,
Form
,
Popconfirm
,
Space
,
Table
,
Tag
,
Tooltip
,
}
from
'@douyinfe/semi-ui'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderGroup
,
renderNumber
,
renderQuota
}
from
'../helpers/render'
;
import
{
renderGroup
,
renderNumber
,
renderQuota
}
from
'../helpers/render'
;
import
AddUser
from
'../pages/User/AddUser'
;
import
AddUser
from
'../pages/User/AddUser'
;
...
@@ -9,112 +17,202 @@ import EditUser from '../pages/User/EditUser';
...
@@ -9,112 +17,202 @@ import EditUser from '../pages/User/EditUser';
function
renderRole
(
role
)
{
function
renderRole
(
role
)
{
switch
(
role
)
{
switch
(
role
)
{
case
1
:
case
1
:
return
<
Tag
size
=
"large"
>
普通用户
<
/Tag>
;
return
<
Tag
size
=
'large'
>
普通用户
<
/Tag>
;
case
10
:
case
10
:
return
<
Tag
color
=
"yellow"
size
=
"large"
>
管理员
<
/Tag>
;
return
(
<
Tag
color
=
'yellow'
size
=
'large'
>
管理员
<
/Tag
>
);
case
100
:
case
100
:
return
<
Tag
color
=
"orange"
size
=
"large"
>
超级管理员
<
/Tag>
;
return
(
<
Tag
color
=
'orange'
size
=
'large'
>
超级管理员
<
/Tag
>
);
default
:
default
:
return
<
Tag
color
=
"red"
size
=
"large"
>
未知身份
<
/Tag>
;
return
(
<
Tag
color
=
'red'
size
=
'large'
>
未知身份
<
/Tag
>
);
}
}
}
}
const
UsersTable
=
()
=>
{
const
UsersTable
=
()
=>
{
const
columns
=
[{
const
columns
=
[
title
:
'ID'
,
dataIndex
:
'id'
{
},
{
title
:
'ID'
,
title
:
'用户名'
,
dataIndex
:
'username'
dataIndex
:
'id'
,
},
{
},
title
:
'分组'
,
dataIndex
:
'group'
,
render
:
(
text
,
record
,
index
)
=>
{
{
return
(
<
div
>
title
:
'用户名'
,
{
renderGroup
(
text
)}
dataIndex
:
'username'
,
<
/div>
)
;
},
}
{
},
{
title
:
'分组'
,
title
:
'统计信息'
,
dataIndex
:
'info'
,
render
:
(
text
,
record
,
index
)
=>
{
dataIndex
:
'group'
,
return
(
<
div
>
render
:
(
text
,
record
,
index
)
=>
{
return
<
div
>
{
renderGroup
(
text
)}
<
/div>
;
},
},
{
title
:
'统计信息'
,
dataIndex
:
'info'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
<
Space
spacing
=
{
1
}
>
<
Space
spacing
=
{
1
}
>
<
Tooltip
content
=
{
'剩余额度'
}
>
<
Tooltip
content
=
{
'剩余额度'
}
>
<
Tag
color
=
"white"
size
=
"large"
>
{
renderQuota
(
record
.
quota
)}
<
/Tag
>
<
Tag
color
=
'white'
size
=
'large'
>
{
renderQuota
(
record
.
quota
)}
<
/Tag
>
<
/Tooltip
>
<
/Tooltip
>
<
Tooltip
content
=
{
'已用额度'
}
>
<
Tooltip
content
=
{
'已用额度'
}
>
<
Tag
color
=
"white"
size
=
"large"
>
{
renderQuota
(
record
.
used_quota
)}
<
/Tag
>
<
Tag
color
=
'white'
size
=
'large'
>
{
renderQuota
(
record
.
used_quota
)}
<
/Tag
>
<
/Tooltip
>
<
/Tooltip
>
<
Tooltip
content
=
{
'调用次数'
}
>
<
Tooltip
content
=
{
'调用次数'
}
>
<
Tag
color
=
"white"
size
=
"large"
>
{
renderNumber
(
record
.
request_count
)}
<
/Tag
>
<
Tag
color
=
'white'
size
=
'large'
>
{
renderNumber
(
record
.
request_count
)}
<
/Tag
>
<
/Tooltip
>
<
/Tooltip
>
<
/Space
>
<
/Space
>
<
/div>
)
;
<
/div
>
}
);
},
{
},
title
:
'邀请信息'
,
dataIndex
:
'invite'
,
render
:
(
text
,
record
,
index
)
=>
{
},
return
(
<
div
>
{
title
:
'邀请信息'
,
dataIndex
:
'invite'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
<
Space
spacing
=
{
1
}
>
<
Space
spacing
=
{
1
}
>
<
Tooltip
content
=
{
'邀请人数'
}
>
<
Tooltip
content
=
{
'邀请人数'
}
>
<
Tag
color
=
"white"
size
=
"large"
>
{
renderNumber
(
record
.
aff_count
)}
<
/Tag
>
<
Tag
color
=
'white'
size
=
'large'
>
{
renderNumber
(
record
.
aff_count
)}
<
/Tag
>
<
/Tooltip
>
<
/Tooltip
>
<
Tooltip
content
=
{
'邀请总收益'
}
>
<
Tooltip
content
=
{
'邀请总收益'
}
>
<
Tag
color
=
"white"
size
=
"large"
>
{
renderQuota
(
record
.
aff_history_quota
)}
<
/Tag
>
<
Tag
color
=
'white'
size
=
'large'
>
{
renderQuota
(
record
.
aff_history_quota
)}
<
/Tag
>
<
/Tooltip
>
<
/Tooltip
>
<
Tooltip
content
=
{
'邀请人ID'
}
>
<
Tooltip
content
=
{
'邀请人ID'
}
>
{
record
.
inviter_id
===
0
?
<
Tag
color
=
"white"
size
=
"large"
>
无
<
/Tag>
:
{
record
.
inviter_id
===
0
?
(
<
Tag
color
=
"white"
size
=
"large"
>
{
record
.
inviter_id
}
<
/Tag>
}
<
Tag
color
=
'white'
size
=
'large'
>
无
<
/Tag
>
)
:
(
<
Tag
color
=
'white'
size
=
'large'
>
{
record
.
inviter_id
}
<
/Tag
>
)}
<
/Tooltip
>
<
/Tooltip
>
<
/Space
>
<
/Space
>
<
/div>
)
;
<
/div
>
}
);
},
{
},
title
:
'角色'
,
dataIndex
:
'role'
,
render
:
(
text
,
record
,
index
)
=>
{
},
return
(
<
div
>
{
{
renderRole
(
text
)}
title
:
'角色'
,
<
/div>
)
;
dataIndex
:
'role'
,
}
render
:
(
text
,
record
,
index
)
=>
{
},
{
return
<
div
>
{
renderRole
(
text
)}
<
/div>
;
title
:
'状态'
,
dataIndex
:
'status'
,
render
:
(
text
,
record
,
index
)
=>
{
},
return
(
<
div
>
},
{
record
.
DeletedAt
!==
null
?
<
Tag
color
=
"red"
>
已注销
<
/Tag> : renderStatus
(
text
)
}
<
/div>
)
;
}
},
{
title
:
''
,
dataIndex
:
'operate'
,
render
:
(
text
,
record
,
index
)
=>
(
<
div
>
{
{
record
.
DeletedAt
!==
null
?
<><
/>
:
title
:
'状态'
,
dataIndex
:
'status'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
{
record
.
DeletedAt
!==
null
?
(
<
Tag
color
=
'red'
>
已注销
<
/Tag
>
)
:
(
renderStatus
(
text
)
)}
<
/div
>
);
},
},
{
title
:
''
,
dataIndex
:
'operate'
,
render
:
(
text
,
record
,
index
)
=>
(
<
div
>
{
record
.
DeletedAt
!==
null
?
(
<><
/
>
)
:
(
<>
<>
<
Popconfirm
<
Popconfirm
title
=
"确定?"
title
=
'确定?'
okType
=
{
'warning'
}
okType
=
{
'warning'
}
onConfirm
=
{()
=>
{
onConfirm
=
{()
=>
{
manageUser
(
record
.
username
,
'promote'
,
record
);
manageUser
(
record
.
username
,
'promote'
,
record
);
}}
}}
>
>
<
Button
theme
=
"light"
type
=
"warning"
style
=
{{
marginRight
:
1
}}
>
提升
<
/Button
>
<
Button
theme
=
'light'
type
=
'warning'
style
=
{{
marginRight
:
1
}}
>
提升
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
<
Popconfirm
<
Popconfirm
title
=
"确定?"
title
=
'确定?'
okType
=
{
'warning'
}
okType
=
{
'warning'
}
onConfirm
=
{()
=>
{
onConfirm
=
{()
=>
{
manageUser
(
record
.
username
,
'demote'
,
record
);
manageUser
(
record
.
username
,
'demote'
,
record
);
}}
}}
>
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
1
}}
>
降级
<
/Button
>
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
>
降级
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
{
record
.
status
===
1
?
{
record
.
status
===
1
?
(
<
Button
theme
=
"light"
type
=
"warning"
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
<
Button
theme
=
'light'
type
=
'warning'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageUser
(
record
.
username
,
'disable'
,
record
);
manageUser
(
record
.
username
,
'disable'
,
record
);
}}
>
禁用
<
/Button>
:
}}
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
>
禁用
<
/Button
>
)
:
(
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageUser
(
record
.
username
,
'enable'
,
record
);
manageUser
(
record
.
username
,
'enable'
,
record
);
}}
disabled
=
{
record
.
status
===
3
}
>
启用
<
/Button>
}
}}
<
Button
theme
=
"light"
type
=
"tertiary"
style
=
{{
marginRight
:
1
}}
onClick
=
{()
=>
{
disabled
=
{
record
.
status
===
3
}
>
启用
<
/Button
>
)}
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
onClick
=
{()
=>
{
setEditingUser
(
record
);
setEditingUser
(
record
);
setShowEditUser
(
true
);
setShowEditUser
(
true
);
}}
>
编辑
<
/Button
>
}}
>
编辑
<
/Button
>
<
/
>
<
/
>
)}
}
<
Popconfirm
<
Popconfirm
title
=
"确定是否要删除此用户?"
title
=
'确定是否要删除此用户?'
content
=
"硬删除,此修改将不可逆"
content
=
'硬删除,此修改将不可逆'
okType
=
{
'danger'
}
okType
=
{
'danger'
}
position
=
{
'left'
}
position
=
{
'left'
}
onConfirm
=
{()
=>
{
onConfirm
=
{()
=>
{
...
@@ -123,10 +221,14 @@ const UsersTable = () => {
...
@@ -123,10 +221,14 @@ const UsersTable = () => {
});
});
}}
}}
>
>
<
Button
theme
=
"light"
type
=
"danger"
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
<
/div>
)
<
/div
>
}];
),
},
];
const
[
users
,
setUsers
]
=
useState
([]);
const
[
users
,
setUsers
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
...
@@ -137,22 +239,22 @@ const UsersTable = () => {
...
@@ -137,22 +239,22 @@ const UsersTable = () => {
const
[
showAddUser
,
setShowAddUser
]
=
useState
(
false
);
const
[
showAddUser
,
setShowAddUser
]
=
useState
(
false
);
const
[
showEditUser
,
setShowEditUser
]
=
useState
(
false
);
const
[
showEditUser
,
setShowEditUser
]
=
useState
(
false
);
const
[
editingUser
,
setEditingUser
]
=
useState
({
const
[
editingUser
,
setEditingUser
]
=
useState
({
id
:
undefined
id
:
undefined
,
});
});
const
setCount
=
(
data
)
=>
{
const
setCount
=
(
data
)
=>
{
if
(
data
.
length
>=
(
activePage
)
*
ITEMS_PER_PAGE
)
{
if
(
data
.
length
>=
activePage
*
ITEMS_PER_PAGE
)
{
setUserCount
(
data
.
length
+
1
);
setUserCount
(
data
.
length
+
1
);
}
else
{
}
else
{
setUserCount
(
data
.
length
);
setUserCount
(
data
.
length
);
}
}
};
};
const
removeRecord
=
key
=>
{
const
removeRecord
=
(
key
)
=>
{
console
.
log
(
key
);
console
.
log
(
key
);
let
newDataSource
=
[...
users
];
let
newDataSource
=
[...
users
];
if
(
key
!=
null
)
{
if
(
key
!=
null
)
{
let
idx
=
newDataSource
.
findIndex
(
data
=>
data
.
id
===
key
);
let
idx
=
newDataSource
.
findIndex
(
(
data
)
=>
data
.
id
===
key
);
if
(
idx
>
-
1
)
{
if
(
idx
>
-
1
)
{
newDataSource
.
splice
(
idx
,
1
);
newDataSource
.
splice
(
idx
,
1
);
...
@@ -200,7 +302,8 @@ const UsersTable = () => {
...
@@ -200,7 +302,8 @@ const UsersTable = () => {
const
manageUser
=
async
(
username
,
action
,
record
)
=>
{
const
manageUser
=
async
(
username
,
action
,
record
)
=>
{
const
res
=
await
API
.
post
(
'/api/user/manage'
,
{
const
res
=
await
API
.
post
(
'/api/user/manage'
,
{
username
,
action
username
,
action
,
});
});
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
...
@@ -208,7 +311,6 @@ const UsersTable = () => {
...
@@ -208,7 +311,6 @@ const UsersTable = () => {
let
user
=
res
.
data
.
data
;
let
user
=
res
.
data
.
data
;
let
newUsers
=
[...
users
];
let
newUsers
=
[...
users
];
if
(
action
===
'delete'
)
{
if
(
action
===
'delete'
)
{
}
else
{
}
else
{
record
.
status
=
user
.
status
;
record
.
status
=
user
.
status
;
record
.
role
=
user
.
role
;
record
.
role
=
user
.
role
;
...
@@ -222,15 +324,19 @@ const UsersTable = () => {
...
@@ -222,15 +324,19 @@ const UsersTable = () => {
const
renderStatus
=
(
status
)
=>
{
const
renderStatus
=
(
status
)
=>
{
switch
(
status
)
{
switch
(
status
)
{
case
1
:
case
1
:
return
<
Tag
size
=
"large"
>
已激活
<
/Tag>
;
return
<
Tag
size
=
'large'
>
已激活
<
/Tag>
;
case
2
:
case
2
:
return
(
<
Tag
size
=
"large"
color
=
"red"
>
return
(
<
Tag
size
=
'large'
color
=
'red'
>
已封禁
已封禁
<
/Tag>
)
;
<
/Tag
>
);
default
:
default
:
return
(
<
Tag
size
=
"large"
color
=
"grey"
>
return
(
<
Tag
size
=
'large'
color
=
'grey'
>
未知状态
未知状态
<
/Tag>
)
;
<
/Tag
>
);
}
}
};
};
...
@@ -271,16 +377,18 @@ const UsersTable = () => {
...
@@ -271,16 +377,18 @@ const UsersTable = () => {
setLoading
(
false
);
setLoading
(
false
);
};
};
const
handlePageChange
=
page
=>
{
const
handlePageChange
=
(
page
)
=>
{
setActivePage
(
page
);
setActivePage
(
page
);
if
(
page
===
Math
.
ceil
(
users
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
if
(
page
===
Math
.
ceil
(
users
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
// In this case we have to load more data and then append them.
// In this case we have to load more data and then append them.
loadUsers
(
page
-
1
).
then
(
r
=>
{
loadUsers
(
page
-
1
).
then
((
r
)
=>
{});
});
}
}
};
};
const
pageData
=
users
.
slice
((
activePage
-
1
)
*
ITEMS_PER_PAGE
,
activePage
*
ITEMS_PER_PAGE
);
const
pageData
=
users
.
slice
(
(
activePage
-
1
)
*
ITEMS_PER_PAGE
,
activePage
*
ITEMS_PER_PAGE
,
);
const
closeAddUser
=
()
=>
{
const
closeAddUser
=
()
=>
{
setShowAddUser
(
false
);
setShowAddUser
(
false
);
...
@@ -289,7 +397,7 @@ const UsersTable = () => {
...
@@ -289,7 +397,7 @@ const UsersTable = () => {
const
closeEditUser
=
()
=>
{
const
closeEditUser
=
()
=>
{
setShowEditUser
(
false
);
setShowEditUser
(
false
);
setEditingUser
({
setEditingUser
({
id
:
undefined
id
:
undefined
,
});
});
};
};
...
@@ -303,34 +411,52 @@ const UsersTable = () => {
...
@@ -303,34 +411,52 @@ const UsersTable = () => {
return
(
return
(
<>
<>
<
AddUser
refresh
=
{
refresh
}
visible
=
{
showAddUser
}
handleClose
=
{
closeAddUser
}
><
/AddUser
>
<
AddUser
<
EditUser
refresh
=
{
refresh
}
visible
=
{
showEditUser
}
handleClose
=
{
closeEditUser
}
refresh
=
{
refresh
}
editingUser
=
{
editingUser
}
><
/EditUser
>
visible
=
{
showAddUser
}
handleClose
=
{
closeAddUser
}
><
/AddUser
>
<
EditUser
refresh
=
{
refresh
}
visible
=
{
showEditUser
}
handleClose
=
{
closeEditUser
}
editingUser
=
{
editingUser
}
><
/EditUser
>
<
Form
onSubmit
=
{
searchUsers
}
>
<
Form
onSubmit
=
{
searchUsers
}
>
<
Form
.
Input
<
Form
.
Input
label
=
"搜索关键字"
label
=
'搜索关键字'
icon
=
"search"
icon
=
'search'
field
=
"keyword"
field
=
'keyword'
iconPosition
=
"left"
iconPosition
=
'left'
placeholder
=
"搜索用户的 ID,用户名,显示名称,以及邮箱地址 ..."
placeholder
=
'搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...'
value
=
{
searchKeyword
}
value
=
{
searchKeyword
}
loading
=
{
searching
}
loading
=
{
searching
}
onChange
=
{
value
=>
handleKeywordChange
(
value
)}
onChange
=
{
(
value
)
=>
handleKeywordChange
(
value
)}
/
>
/
>
<
/Form
>
<
/Form
>
<
Table
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
<
Table
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
currentPage
:
activePage
,
pageSize
:
ITEMS_PER_PAGE
,
pageSize
:
ITEMS_PER_PAGE
,
total
:
userCount
,
total
:
userCount
,
pageSizeOpts
:
[
10
,
20
,
50
,
100
],
pageSizeOpts
:
[
10
,
20
,
50
,
100
],
onPageChange
:
handlePageChange
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
/
>
}}
<
Button
theme
=
"light"
type
=
"primary"
style
=
{{
marginRight
:
8
}}
onClick
=
{
loading
=
{
loading
}
()
=>
{
/
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{()
=>
{
setShowAddUser
(
true
);
setShowAddUser
(
true
);
}
}}
}
>
添加用户
<
/Button
>
>
添加用户
<
/Button
>
<
/
>
<
/
>
);
);
};
};
...
...
web/src/components/WeChatIcon.js
View file @
eb955ae4
...
@@ -3,15 +3,27 @@ import { Icon } from '@douyinfe/semi-ui';
...
@@ -3,15 +3,27 @@ import { Icon } from '@douyinfe/semi-ui';
const
WeChatIcon
=
()
=>
{
const
WeChatIcon
=
()
=>
{
function
CustomIcon
()
{
function
CustomIcon
()
{
return
<
svg
t
=
"1709714447384"
className
=
"icon"
viewBox
=
"0 0 1024 1024"
version
=
"1.1"
return
(
xmlns
=
"http://www.w3.org/2000/svg"
p
-
id
=
"5091"
width
=
"16"
height
=
"16"
>
<
svg
t
=
'1709714447384'
className
=
'icon'
viewBox
=
'0 0 1024 1024'
version
=
'1.1'
xmlns
=
'http://www.w3.org/2000/svg'
p
-
id
=
'5091'
width
=
'16'
height
=
'16'
>
<
path
<
path
d
=
"M690.1 377.4c5.9 0 11.8 0.2 17.6 0.5-24.4-128.7-158.3-227.1-319.9-227.1C209 150.8 64 271.4 64 420.2c0 81.1 43.6 154.2 111.9 203.6 5.5 3.9 9.1 10.3 9.1 17.6 0 2.4-0.5 4.6-1.1 6.9-5.5 20.3-14.2 52.8-14.6 54.3-0.7 2.6-1.7 5.2-1.7 7.9 0 5.9 4.8 10.8 10.8 10.8 2.3 0 4.2-0.9 6.2-2l70.9-40.9c5.3-3.1 11-5 17.2-5 3.2 0 6.4 0.5 9.5 1.4 33.1 9.5 68.8 14.8 105.7 14.8 6 0 11.9-0.1 17.8-0.4-7.1-21-10.9-43.1-10.9-66 0-135.8 132.2-245.8 295.3-245.8z m-194.3-86.5c23.8 0 43.2 19.3 43.2 43.1s-19.3 43.1-43.2 43.1c-23.8 0-43.2-19.3-43.2-43.1s19.4-43.1 43.2-43.1z m-215.9 86.2c-23.8 0-43.2-19.3-43.2-43.1s19.3-43.1 43.2-43.1 43.2 19.3 43.2 43.1-19.4 43.1-43.2 43.1z"
d
=
'M690.1 377.4c5.9 0 11.8 0.2 17.6 0.5-24.4-128.7-158.3-227.1-319.9-227.1C209 150.8 64 271.4 64 420.2c0 81.1 43.6 154.2 111.9 203.6 5.5 3.9 9.1 10.3 9.1 17.6 0 2.4-0.5 4.6-1.1 6.9-5.5 20.3-14.2 52.8-14.6 54.3-0.7 2.6-1.7 5.2-1.7 7.9 0 5.9 4.8 10.8 10.8 10.8 2.3 0 4.2-0.9 6.2-2l70.9-40.9c5.3-3.1 11-5 17.2-5 3.2 0 6.4 0.5 9.5 1.4 33.1 9.5 68.8 14.8 105.7 14.8 6 0 11.9-0.1 17.8-0.4-7.1-21-10.9-43.1-10.9-66 0-135.8 132.2-245.8 295.3-245.8z m-194.3-86.5c23.8 0 43.2 19.3 43.2 43.1s-19.3 43.1-43.2 43.1c-23.8 0-43.2-19.3-43.2-43.1s19.4-43.1 43.2-43.1z m-215.9 86.2c-23.8 0-43.2-19.3-43.2-43.1s19.3-43.1 43.2-43.1 43.2 19.3 43.2 43.1-19.4 43.1-43.2 43.1z'
p
-
id
=
"5092"
><
/path
>
p
-
id
=
'5092'
><
/path
>
<
path
<
path
d
=
"M866.7 792.7c56.9-41.2 93.2-102 93.2-169.7 0-124-120.8-224.5-269.9-224.5-149 0-269.9 100.5-269.9 224.5S540.9 847.5 690 847.5c30.8 0 60.6-4.4 88.1-12.3 2.6-0.8 5.2-1.2 7.9-1.2 5.2 0 9.9 1.6 14.3 4.1l59.1 34c1.7 1 3.3 1.7 5.2 1.7 2.4 0 4.7-0.9 6.4-2.6 1.7-1.7 2.6-4 2.6-6.4 0-2.2-0.9-4.4-1.4-6.6-0.3-1.2-7.6-28.3-12.2-45.3-0.5-1.9-0.9-3.8-0.9-5.7 0.1-5.9 3.1-11.2 7.6-14.5zM600.2 587.2c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c0 19.8-16.2 35.9-36 35.9z m179.9 0c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c-0.1 19.8-16.2 35.9-36 35.9z"
d
=
'M866.7 792.7c56.9-41.2 93.2-102 93.2-169.7 0-124-120.8-224.5-269.9-224.5-149 0-269.9 100.5-269.9 224.5S540.9 847.5 690 847.5c30.8 0 60.6-4.4 88.1-12.3 2.6-0.8 5.2-1.2 7.9-1.2 5.2 0 9.9 1.6 14.3 4.1l59.1 34c1.7 1 3.3 1.7 5.2 1.7 2.4 0 4.7-0.9 6.4-2.6 1.7-1.7 2.6-4 2.6-6.4 0-2.2-0.9-4.4-1.4-6.6-0.3-1.2-7.6-28.3-12.2-45.3-0.5-1.9-0.9-3.8-0.9-5.7 0.1-5.9 3.1-11.2 7.6-14.5zM600.2 587.2c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c0 19.8-16.2 35.9-36 35.9z m179.9 0c-19.9 0-36-16.1-36-35.9 0-19.8 16.1-35.9 36-35.9s36 16.1 36 35.9c-0.1 19.8-16.2 35.9-36 35.9z'
p
-
id
=
"5093"
><
/path
>
p
-
id
=
'5093'
<
/svg>
;
><
/path
>
<
/svg
>
);
}
}
return
(
return
(
...
...
web/src/components/utils.js
View file @
eb955ae4
...
@@ -15,6 +15,6 @@ export async function onGitHubOAuthClicked(github_client_id) {
...
@@ -15,6 +15,6 @@ export async function onGitHubOAuthClicked(github_client_id) {
const
state
=
await
getOAuthState
();
const
state
=
await
getOAuthState
();
if
(
!
state
)
return
;
if
(
!
state
)
return
;
window
.
open
(
window
.
open
(
`https://github.com/login/oauth/authorize?client_id=
${
github_client_id
}
&state=
${
state
}
&scope=user:email`
`https://github.com/login/oauth/authorize?client_id=
${
github_client_id
}
&state=
${
state
}
&scope=user:email`
,
);
);
}
}
web/src/constants/channel.constants.js
View file @
eb955ae4
export
const
CHANNEL_OPTIONS
=
[
export
const
CHANNEL_OPTIONS
=
[
{
key
:
1
,
text
:
'OpenAI'
,
value
:
1
,
color
:
'green'
,
label
:
'OpenAI'
},
{
key
:
1
,
text
:
'OpenAI'
,
value
:
1
,
color
:
'green'
,
label
:
'OpenAI'
},
{
key
:
2
,
text
:
'Midjourney Proxy'
,
value
:
2
,
color
:
'light-blue'
,
label
:
'Midjourney Proxy'
},
{
{
key
:
5
,
text
:
'Midjourney Proxy Plus'
,
value
:
5
,
color
:
'blue'
,
label
:
'Midjourney Proxy Plus'
},
key
:
2
,
{
key
:
4
,
text
:
'Ollama'
,
value
:
4
,
color
:
'grey'
,
label
:
'Ollama'
},
text
:
'Midjourney Proxy'
,
{
key
:
14
,
text
:
'Anthropic Claude'
,
value
:
14
,
color
:
'indigo'
,
label
:
'Anthropic Claude'
},
value
:
2
,
{
key
:
3
,
text
:
'Azure OpenAI'
,
value
:
3
,
color
:
'teal'
,
label
:
'Azure OpenAI'
},
color
:
'light-blue'
,
{
key
:
11
,
text
:
'Google PaLM2'
,
value
:
11
,
color
:
'orange'
,
label
:
'Google PaLM2'
},
label
:
'Midjourney Proxy'
,
{
key
:
24
,
text
:
'Google Gemini'
,
value
:
24
,
color
:
'orange'
,
label
:
'Google Gemini'
},
},
{
key
:
15
,
text
:
'百度文心千帆'
,
value
:
15
,
color
:
'blue'
,
label
:
'百度文心千帆'
},
{
{
key
:
17
,
text
:
'阿里通义千问'
,
value
:
17
,
color
:
'orange'
,
label
:
'阿里通义千问'
},
key
:
5
,
{
key
:
18
,
text
:
'讯飞星火认知'
,
value
:
18
,
color
:
'blue'
,
label
:
'讯飞星火认知'
},
text
:
'Midjourney Proxy Plus'
,
{
key
:
16
,
text
:
'智谱 ChatGLM'
,
value
:
16
,
color
:
'violet'
,
label
:
'智谱 ChatGLM'
},
value
:
5
,
{
key
:
16
,
text
:
'智谱 GLM-4V'
,
value
:
26
,
color
:
'purple'
,
label
:
'智谱 GLM-4V'
},
color
:
'blue'
,
{
key
:
16
,
text
:
'Moonshot'
,
value
:
25
,
color
:
'green'
,
label
:
'Moonshot'
},
label
:
'Midjourney Proxy Plus'
,
{
key
:
19
,
text
:
'360 智脑'
,
value
:
19
,
color
:
'blue'
,
label
:
'360 智脑'
},
},
{
key
:
23
,
text
:
'腾讯混元'
,
value
:
23
,
color
:
'teal'
,
label
:
'腾讯混元'
},
{
key
:
4
,
text
:
'Ollama'
,
value
:
4
,
color
:
'grey'
,
label
:
'Ollama'
},
{
key
:
31
,
text
:
'零一万物'
,
value
:
31
,
color
:
'green'
,
label
:
'零一万物'
},
{
{
key
:
8
,
text
:
'自定义渠道'
,
value
:
8
,
color
:
'pink'
,
label
:
'自定义渠道'
},
key
:
14
,
{
key
:
22
,
text
:
'知识库:FastGPT'
,
value
:
22
,
color
:
'blue'
,
label
:
'知识库:FastGPT'
},
text
:
'Anthropic Claude'
,
{
key
:
21
,
text
:
'知识库:AI Proxy'
,
value
:
21
,
color
:
'purple'
,
label
:
'知识库:AI Proxy'
},
value
:
14
,
color
:
'indigo'
,
label
:
'Anthropic Claude'
,
},
{
key
:
3
,
text
:
'Azure OpenAI'
,
value
:
3
,
color
:
'teal'
,
label
:
'Azure OpenAI'
,
},
{
key
:
11
,
text
:
'Google PaLM2'
,
value
:
11
,
color
:
'orange'
,
label
:
'Google PaLM2'
,
},
{
key
:
24
,
text
:
'Google Gemini'
,
value
:
24
,
color
:
'orange'
,
label
:
'Google Gemini'
,
},
{
key
:
15
,
text
:
'百度文心千帆'
,
value
:
15
,
color
:
'blue'
,
label
:
'百度文心千帆'
,
},
{
key
:
17
,
text
:
'阿里通义千问'
,
value
:
17
,
color
:
'orange'
,
label
:
'阿里通义千问'
,
},
{
key
:
18
,
text
:
'讯飞星火认知'
,
value
:
18
,
color
:
'blue'
,
label
:
'讯飞星火认知'
,
},
{
key
:
16
,
text
:
'智谱 ChatGLM'
,
value
:
16
,
color
:
'violet'
,
label
:
'智谱 ChatGLM'
,
},
{
key
:
16
,
text
:
'智谱 GLM-4V'
,
value
:
26
,
color
:
'purple'
,
label
:
'智谱 GLM-4V'
,
},
{
key
:
16
,
text
:
'Moonshot'
,
value
:
25
,
color
:
'green'
,
label
:
'Moonshot'
},
{
key
:
19
,
text
:
'360 智脑'
,
value
:
19
,
color
:
'blue'
,
label
:
'360 智脑'
},
{
key
:
23
,
text
:
'腾讯混元'
,
value
:
23
,
color
:
'teal'
,
label
:
'腾讯混元'
},
{
key
:
31
,
text
:
'零一万物'
,
value
:
31
,
color
:
'green'
,
label
:
'零一万物'
},
{
key
:
8
,
text
:
'自定义渠道'
,
value
:
8
,
color
:
'pink'
,
label
:
'自定义渠道'
},
{
key
:
22
,
text
:
'知识库:FastGPT'
,
value
:
22
,
color
:
'blue'
,
label
:
'知识库:FastGPT'
,
},
{
key
:
21
,
text
:
'知识库:AI Proxy'
,
value
:
21
,
color
:
'purple'
,
label
:
'知识库:AI Proxy'
,
},
];
];
web/src/constants/index.js
View file @
eb955ae4
web/src/constants/toast.constants.js
View file @
eb955ae4
...
@@ -3,5 +3,5 @@ export const toastConstants = {
...
@@ -3,5 +3,5 @@ export const toastConstants = {
INFO_TIMEOUT
:
3000
,
INFO_TIMEOUT
:
3000
,
ERROR_TIMEOUT
:
5000
,
ERROR_TIMEOUT
:
5000
,
WARNING_TIMEOUT
:
10000
,
WARNING_TIMEOUT
:
10000
,
NOTICE_TIMEOUT
:
20000
NOTICE_TIMEOUT
:
20000
,
};
};
web/src/constants/user.constants.js
View file @
eb955ae4
...
@@ -15,5 +15,5 @@ export const userConstants = {
...
@@ -15,5 +15,5 @@ export const userConstants = {
DELETE_REQUEST
:
'USERS_DELETE_REQUEST'
,
DELETE_REQUEST
:
'USERS_DELETE_REQUEST'
,
DELETE_SUCCESS
:
'USERS_DELETE_SUCCESS'
,
DELETE_SUCCESS
:
'USERS_DELETE_SUCCESS'
,
DELETE_FAILURE
:
'USERS_DELETE_FAILURE'
DELETE_FAILURE
:
'USERS_DELETE_FAILURE'
,
};
};
web/src/context/Status/index.js
View file @
eb955ae4
web/src/context/User/index.js
View file @
eb955ae4
// contexts/User/index.jsx
// contexts/User/index.jsx
import
React
from
"react"
import
React
from
'react'
;
import
{
reducer
,
initialState
}
from
"./reducer"
import
{
reducer
,
initialState
}
from
'./reducer'
;
export
const
UserContext
=
React
.
createContext
({
export
const
UserContext
=
React
.
createContext
({
state
:
initialState
,
state
:
initialState
,
dispatch
:
()
=>
null
dispatch
:
()
=>
null
,
})
})
;
export
const
UserProvider
=
({
children
})
=>
{
export
const
UserProvider
=
({
children
})
=>
{
const
[
state
,
dispatch
]
=
React
.
useReducer
(
reducer
,
initialState
)
const
[
state
,
dispatch
]
=
React
.
useReducer
(
reducer
,
initialState
)
;
return
(
return
(
<
UserContext
.
Provider
value
=
{[
state
,
dispatch
]}
>
<
UserContext
.
Provider
value
=
{[
state
,
dispatch
]}
>
{
children
}
{
children
}
<
/UserContext.Provider
>
<
/UserContext.Provider
>
)
);
}
};
\ No newline at end of file
web/src/context/User/reducer.js
View file @
eb955ae4
...
@@ -3,12 +3,12 @@ export const reducer = (state, action) => {
...
@@ -3,12 +3,12 @@ export const reducer = (state, action) => {
case
'login'
:
case
'login'
:
return
{
return
{
...
state
,
...
state
,
user
:
action
.
payload
user
:
action
.
payload
,
};
};
case
'logout'
:
case
'logout'
:
return
{
return
{
...
state
,
...
state
,
user
:
undefined
user
:
undefined
,
};
};
default
:
default
:
...
@@ -17,5 +17,5 @@ export const reducer = (state, action) => {
...
@@ -17,5 +17,5 @@ export const reducer = (state, action) => {
};
};
export
const
initialState
=
{
export
const
initialState
=
{
user
:
undefined
user
:
undefined
,
};
};
web/src/helpers/api.js
View file @
eb955ae4
...
@@ -2,12 +2,14 @@ import { showError } from './utils';
...
@@ -2,12 +2,14 @@ import { showError } from './utils';
import
axios
from
'axios'
;
import
axios
from
'axios'
;
export
const
API
=
axios
.
create
({
export
const
API
=
axios
.
create
({
baseURL
:
import
.
meta
.
env
.
VITE_REACT_APP_SERVER_URL
?
import
.
meta
.
env
.
VITE_REACT_APP_SERVER_URL
:
''
,
baseURL
:
import
.
meta
.
env
.
VITE_REACT_APP_SERVER_URL
?
import
.
meta
.
env
.
VITE_REACT_APP_SERVER_URL
:
''
,
});
});
API
.
interceptors
.
response
.
use
(
API
.
interceptors
.
response
.
use
(
(
response
)
=>
response
,
(
response
)
=>
response
,
(
error
)
=>
{
(
error
)
=>
{
showError
(
error
);
showError
(
error
);
}
}
,
);
);
web/src/helpers/auth-header.js
View file @
eb955ae4
...
@@ -3,7 +3,7 @@ export function authHeader() {
...
@@ -3,7 +3,7 @@ export function authHeader() {
let
user
=
JSON
.
parse
(
localStorage
.
getItem
(
'user'
));
let
user
=
JSON
.
parse
(
localStorage
.
getItem
(
'user'
));
if
(
user
&&
user
.
token
)
{
if
(
user
&&
user
.
token
)
{
return
{
'Authorization'
:
'Bearer '
+
user
.
token
};
return
{
Authorization
:
'Bearer '
+
user
.
token
};
}
else
{
}
else
{
return
{};
return
{};
}
}
...
...
web/src/helpers/history.js
View file @
eb955ae4
web/src/helpers/index.js
View file @
eb955ae4
web/src/helpers/render.js
View file @
eb955ae4
import
{
Label
}
from
'semantic-ui-react'
;
import
{
Label
}
from
'semantic-ui-react'
;
import
{
Tag
}
from
"@douyinfe/semi-ui"
;
import
{
Tag
}
from
'@douyinfe/semi-ui'
;
export
function
renderText
(
text
,
limit
)
{
export
function
renderText
(
text
,
limit
)
{
if
(
text
.
length
>
limit
)
{
if
(
text
.
length
>
limit
)
{
...
@@ -14,20 +14,34 @@ export function renderGroup(group) {
...
@@ -14,20 +14,34 @@ export function renderGroup(group) {
}
}
let
groups
=
group
.
split
(
','
);
let
groups
=
group
.
split
(
','
);
groups
.
sort
();
groups
.
sort
();
return
<>
return
(
<>
{
groups
.
map
((
group
)
=>
{
{
groups
.
map
((
group
)
=>
{
if
(
group
===
'vip'
||
group
===
'pro'
)
{
if
(
group
===
'vip'
||
group
===
'pro'
)
{
return
<
Tag
size
=
'large'
color
=
'yellow'
>
{
group
}
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
'yellow'
>
{
group
}
<
/Tag
>
);
}
else
if
(
group
===
'svip'
||
group
===
'premium'
)
{
}
else
if
(
group
===
'svip'
||
group
===
'premium'
)
{
return
<
Tag
size
=
'large'
color
=
'red'
>
{
group
}
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
'red'
>
{
group
}
<
/Tag
>
);
}
}
if
(
group
===
'default'
)
{
if
(
group
===
'default'
)
{
return
<
Tag
size
=
'large'
>
{
group
}
<
/Tag>
;
return
<
Tag
size
=
'large'
>
{
group
}
<
/Tag>
;
}
else
{
}
else
{
return
<
Tag
size
=
'large'
color
=
{
stringToColor
(
group
)}
>
{
group
}
<
/Tag>
;
return
(
<
Tag
size
=
'large'
color
=
{
stringToColor
(
group
)}
>
{
group
}
<
/Tag
>
);
}
}
})}
})}
<
/>
;
<
/
>
);
}
}
export
function
renderNumber
(
num
)
{
export
function
renderNumber
(
num
)
{
...
@@ -111,16 +125,29 @@ export function renderQuotaWithPrompt(quota, digits) {
...
@@ -111,16 +125,29 @@ export function renderQuotaWithPrompt(quota, digits) {
return
''
;
return
''
;
}
}
const
colors
=
[
'amber'
,
'blue'
,
'cyan'
,
'green'
,
'grey'
,
'indigo'
,
const
colors
=
[
'light-blue'
,
'lime'
,
'orange'
,
'pink'
,
'amber'
,
'purple'
,
'red'
,
'teal'
,
'violet'
,
'yellow'
'blue'
,
]
'cyan'
,
'green'
,
'grey'
,
'indigo'
,
'light-blue'
,
'lime'
,
'orange'
,
'pink'
,
'purple'
,
'red'
,
'teal'
,
'violet'
,
'yellow'
,
];
export
const
modelColorMap
=
{
export
const
modelColorMap
=
{
'dall-e'
:
'rgb(147,112,219)'
,
// 深紫色
'dall-e'
:
'rgb(147,112,219)'
,
// 深紫色
'dall-e-2'
:
'rgb(147,112,219)'
,
// 介于紫色和蓝色之间的色调
'dall-e-2'
:
'rgb(147,112,219)'
,
// 介于紫色和蓝色之间的色调
'dall-e-3'
:
'rgb(153,50,204)'
,
// 介于紫罗兰和洋红之间的色调
'dall-e-3'
:
'rgb(153,50,204)'
,
// 介于紫罗兰和洋红之间的色调
'midjourney'
:
'rgb(136,43,180)'
,
// 介于紫罗兰和洋红之间的色调
midjourney
:
'rgb(136,43,180)'
,
// 介于紫罗兰和洋红之间的色调
'gpt-3.5-turbo'
:
'rgb(184,227,167)'
,
// 浅绿色
'gpt-3.5-turbo'
:
'rgb(184,227,167)'
,
// 浅绿色
'gpt-3.5-turbo-0301'
:
'rgb(131,220,131)'
,
// 亮绿色
'gpt-3.5-turbo-0301'
:
'rgb(131,220,131)'
,
// 亮绿色
'gpt-3.5-turbo-0613'
:
'rgb(60,179,113)'
,
// 海洋绿
'gpt-3.5-turbo-0613'
:
'rgb(60,179,113)'
,
// 海洋绿
...
@@ -154,8 +181,8 @@ export const modelColorMap = {
...
@@ -154,8 +181,8 @@ export const modelColorMap = {
'tts-1-1106'
:
'rgb(255,165,0)'
,
// 橙色
'tts-1-1106'
:
'rgb(255,165,0)'
,
// 橙色
'tts-1-hd'
:
'rgb(255,215,0)'
,
// 金色
'tts-1-hd'
:
'rgb(255,215,0)'
,
// 金色
'tts-1-hd-1106'
:
'rgb(255,223,0)'
,
// 金黄色(略有区别)
'tts-1-hd-1106'
:
'rgb(255,223,0)'
,
// 金黄色(略有区别)
'whisper-1'
:
'rgb(245,245,220)'
// 米色
'whisper-1'
:
'rgb(245,245,220)'
,
// 米色
}
}
;
export
function
stringToColor
(
str
)
{
export
function
stringToColor
(
str
)
{
let
sum
=
0
;
let
sum
=
0
;
...
...
web/src/helpers/utils.js
View file @
eb955ae4
import
{
Toast
}
from
'@douyinfe/semi-ui'
;
import
{
Toast
}
from
'@douyinfe/semi-ui'
;
import
{
toastConstants
}
from
'../constants'
;
import
{
toastConstants
}
from
'../constants'
;
import
React
from
'react'
;
import
React
from
'react'
;
import
{
toast
}
from
"react-toastify"
;
import
{
toast
}
from
'react-toastify'
;
const
HTMLToastContent
=
({
htmlContent
})
=>
{
const
HTMLToastContent
=
({
htmlContent
})
=>
{
return
<
div
dangerouslySetInnerHTML
=
{{
__html
:
htmlContent
}}
/>
;
return
<
div
dangerouslySetInnerHTML
=
{{
__html
:
htmlContent
}}
/>
;
...
@@ -30,7 +30,7 @@ export function getSystemName() {
...
@@ -30,7 +30,7 @@ export function getSystemName() {
export
function
getLogo
()
{
export
function
getLogo
()
{
let
logo
=
localStorage
.
getItem
(
'logo'
);
let
logo
=
localStorage
.
getItem
(
'logo'
);
if
(
!
logo
)
return
'/logo.png'
;
if
(
!
logo
)
return
'/logo.png'
;
return
logo
return
logo
;
}
}
export
function
getFooterHTML
()
{
export
function
getFooterHTML
()
{
...
@@ -157,17 +157,7 @@ export function timestamp2string(timestamp) {
...
@@ -157,17 +157,7 @@ export function timestamp2string(timestamp) {
second
=
'0'
+
second
;
second
=
'0'
+
second
;
}
}
return
(
return
(
year
+
year
+
'-'
+
month
+
'-'
+
day
+
' '
+
hour
+
':'
+
minute
+
':'
+
second
'-'
+
month
+
'-'
+
day
+
' '
+
hour
+
':'
+
minute
+
':'
+
second
);
);
}
}
...
@@ -186,9 +176,9 @@ export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
...
@@ -186,9 +176,9 @@ export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
if
(
hour
.
length
===
1
)
{
if
(
hour
.
length
===
1
)
{
hour
=
'0'
+
hour
;
hour
=
'0'
+
hour
;
}
}
let
str
=
month
+
'-'
+
day
let
str
=
month
+
'-'
+
day
;
if
(
dataExportDefaultTime
===
'hour'
)
{
if
(
dataExportDefaultTime
===
'hour'
)
{
str
+=
' '
+
hour
+
":00"
str
+=
' '
+
hour
+
':00'
;
}
else
if
(
dataExportDefaultTime
===
'week'
)
{
}
else
if
(
dataExportDefaultTime
===
'week'
)
{
let
nextWeek
=
new
Date
(
timestamp
*
1000
+
6
*
24
*
60
*
60
*
1000
);
let
nextWeek
=
new
Date
(
timestamp
*
1000
+
6
*
24
*
60
*
60
*
1000
);
let
nextMonth
=
(
nextWeek
.
getMonth
()
+
1
).
toString
();
let
nextMonth
=
(
nextWeek
.
getMonth
()
+
1
).
toString
();
...
@@ -199,7 +189,7 @@ export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
...
@@ -199,7 +189,7 @@ export function timestamp2string1(timestamp, dataExportDefaultTime = 'hour') {
if
(
nextDay
.
length
===
1
)
{
if
(
nextDay
.
length
===
1
)
{
nextDay
=
'0'
+
nextDay
;
nextDay
=
'0'
+
nextDay
;
}
}
str
+=
' - '
+
nextMonth
+
'-'
+
nextDay
str
+=
' - '
+
nextMonth
+
'-'
+
nextDay
;
}
}
return
str
;
return
str
;
}
}
...
@@ -225,7 +215,6 @@ export const verifyJSON = (str) => {
...
@@ -225,7 +215,6 @@ export const verifyJSON = (str) => {
export
function
shouldShowPrompt
(
id
)
{
export
function
shouldShowPrompt
(
id
)
{
let
prompt
=
localStorage
.
getItem
(
`prompt-
${
id
}
`
);
let
prompt
=
localStorage
.
getItem
(
`prompt-
${
id
}
`
);
return
!
prompt
;
return
!
prompt
;
}
}
export
function
setPromptShown
(
id
)
{
export
function
setPromptShown
(
id
)
{
...
...
web/src/index.css
View file @
eb955ae4
...
@@ -2,12 +2,13 @@ body {
...
@@ -2,12 +2,13 @@ body {
margin
:
0
;
margin
:
0
;
padding-top
:
55px
;
padding-top
:
55px
;
overflow-y
:
scroll
;
overflow-y
:
scroll
;
font-family
:
Lato
,
'Helvetica Neue'
,
Arial
,
Helvetica
,
"Microsoft YaHei"
,
sans-serif
;
font-family
:
Lato
,
'Helvetica Neue'
,
Arial
,
Helvetica
,
'Microsoft YaHei'
,
sans-serif
;
-webkit-font-smoothing
:
antialiased
;
-webkit-font-smoothing
:
antialiased
;
-moz-osx-font-smoothing
:
grayscale
;
-moz-osx-font-smoothing
:
grayscale
;
scrollbar-width
:
none
;
scrollbar-width
:
none
;
color
:
var
(
--semi-color-text-0
)
!important
;
color
:
var
(
--semi-color-text-0
)
!important
;
background-color
:
var
(
--semi-color-bg-0
)
!important
;
background-color
:
var
(
--semi-color-bg-0
)
!important
;
height
:
100%
;
height
:
100%
;
}
}
...
@@ -16,16 +17,18 @@ body {
...
@@ -16,16 +17,18 @@ body {
}
}
@media
only
screen
and
(
max-width
:
767px
)
{
@media
only
screen
and
(
max-width
:
767px
)
{
.semi-table-tbody
,
.semi-table-row
,
.semi-table-row-cell
{
.semi-table-tbody
,
display
:
block
!important
;
.semi-table-row
,
width
:
auto
!important
;
.semi-table-row-cell
{
padding
:
2px
!important
;
display
:
block
!important
;
width
:
auto
!important
;
padding
:
2px
!important
;
}
}
.semi-table-row-cell
{
.semi-table-row-cell
{
border-bottom
:
0
!important
;
border-bottom
:
0
!important
;
}
}
.semi-table-tbody
>
.semi-table-row
{
.semi-table-tbody
>
.semi-table-row
{
border-bottom
:
1px
solid
rgba
(
0
,
0
,
0
,
.1
);
border-bottom
:
1px
solid
rgba
(
0
,
0
,
0
,
0
.1
);
}
}
.semi-space
{
.semi-space
{
/*display: block!important;*/
/*display: block!important;*/
...
@@ -64,7 +67,8 @@ body::-webkit-scrollbar {
...
@@ -64,7 +67,8 @@ body::-webkit-scrollbar {
}
}
code
{
code
{
font-family
:
source-code-pro
,
Menlo
,
Monaco
,
Consolas
,
'Courier New'
,
monospace
;
font-family
:
source-code-pro
,
Menlo
,
Monaco
,
Consolas
,
'Courier New'
,
monospace
;
}
}
.semi-navigation-vertical
{
.semi-navigation-vertical
{
...
...
web/src/index.js
View file @
eb955ae4
import
React
from
'react'
;
import
React
from
'react'
;
import
ReactDOM
from
'react-dom/client'
;
import
ReactDOM
from
'react-dom/client'
;
import
{
BrowserRouter
}
from
'react-router-dom'
;
import
{
BrowserRouter
}
from
'react-router-dom'
;
import
App
from
'./App'
;
import
App
from
'./App'
;
import
HeaderBar
from
'./components/HeaderBar'
;
import
HeaderBar
from
'./components/HeaderBar'
;
import
Footer
from
'./components/Footer'
;
import
Footer
from
'./components/Footer'
;
import
'semantic-ui-offline/semantic.min.css'
;
import
'semantic-ui-offline/semantic.min.css'
;
import
'./index.css'
;
import
'./index.css'
;
import
{
UserProvider
}
from
'./context/User'
;
import
{
UserProvider
}
from
'./context/User'
;
import
{
ToastContainer
}
from
'react-toastify'
;
import
{
ToastContainer
}
from
'react-toastify'
;
import
'react-toastify/dist/ReactToastify.css'
;
import
'react-toastify/dist/ReactToastify.css'
;
import
{
StatusProvider
}
from
'./context/Status'
;
import
{
StatusProvider
}
from
'./context/Status'
;
import
{
Layout
}
from
"@douyinfe/semi-ui"
;
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
import
SiderBar
from
"./components/SiderBar"
;
import
SiderBar
from
'./components/SiderBar'
;
// initialization
// initialization
const
root
=
ReactDOM
.
createRoot
(
document
.
getElementById
(
'root'
));
const
root
=
ReactDOM
.
createRoot
(
document
.
getElementById
(
'root'
));
const
{
Sider
,
Content
,
Header
}
=
Layout
;
const
{
Sider
,
Content
,
Header
}
=
Layout
;
root
.
render
(
root
.
render
(
<
React
.
StrictMode
>
<
React
.
StrictMode
>
<
StatusProvider
>
<
StatusProvider
>
...
@@ -25,27 +24,27 @@ root.render(
...
@@ -25,27 +24,27 @@ root.render(
<
BrowserRouter
>
<
BrowserRouter
>
<
Layout
>
<
Layout
>
<
Sider
>
<
Sider
>
<
SiderBar
/>
<
SiderBar
/>
<
/Sider
>
<
/Sider
>
<
Layout
>
<
Layout
>
<
Header
>
<
Header
>
<
HeaderBar
/>
<
HeaderBar
/>
<
/Header
>
<
/Header
>
<
Content
<
Content
style
=
{{
style
=
{{
padding
:
'24px'
,
padding
:
'24px'
,
}}
}}
>
>
<
App
/>
<
App
/>
<
/Content
>
<
/Content
>
<
Layout
.
Footer
>
<
Layout
.
Footer
>
<
Footer
><
/Footer
>
<
Footer
><
/Footer
>
<
/Layout.Footer
>
<
/Layout.Footer
>
<
/Layout
>
<
/Layout
>
<
ToastContainer
/>
<
ToastContainer
/>
<
/Layout
>
<
/Layout
>
<
/BrowserRouter
>
<
/BrowserRouter
>
<
/UserProvider
>
<
/UserProvider
>
<
/StatusProvider
>
<
/StatusProvider
>
<
/React.StrictMode
>
<
/React.StrictMode>
,
);
);
web/src/pages/About/index.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
API
,
showError
}
from
'../../helpers'
;
import
{
API
,
showError
}
from
'../../helpers'
;
import
{
marked
}
from
'marked'
;
import
{
marked
}
from
'marked'
;
import
{
Layout
}
from
"@douyinfe/semi-ui"
;
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
const
About
=
()
=>
{
const
About
=
()
=>
{
const
[
about
,
setAbout
]
=
useState
(
''
);
const
[
about
,
setAbout
]
=
useState
(
''
);
...
@@ -31,37 +31,42 @@ const About = () => {
...
@@ -31,37 +31,42 @@ const About = () => {
return
(
return
(
<>
<>
{
{
aboutLoaded
&&
about
===
''
?
(
aboutLoaded
&&
about
===
''
?
<>
<>
<
Layout
>
<
Layout
>
<
Layout
.
Header
>
<
Layout
.
Header
>
<
h3
>
关于
<
/h3
>
<
h3
>
关于
<
/h3
>
<
/Layout.Header
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
p
>
<
p
>
可在设置页面设置关于内容,支持
HTML
&
Markdown
<
/p
>
可在设置页面设置关于内容,支持
HTML
&
Markdown
<
/p
>
new
-
api
项目仓库地址:
new
-
api
项目仓库地址:
<
a
href
=
'https://github.com/Calcium-Ion/new-api'
>
<
a
href
=
'https://github.com/Calcium-Ion/new-api'
>
https
:
//github.com/Calcium-Ion/new-api
https
:
//github.com/Calcium-Ion/new-api
<
/a
>
<
/a
>
<
p
>
<
p
>
NewAPI
©
2023
CalciumIon
|
基于
One
API
v0
.
5.4
©
2023
JustSong
。本项目根据
MIT
许可证授权。
NewAPI
©
2023
CalciumIon
|
基于
One
API
v0
.
5.4
©
2023
JustSong
。本项目根据
MIT
许可证授权。
<
/p
>
<
/p
>
<
/Layout.Content
>
<
/Layout.Content
>
<
/Layout
>
<
/Layout
>
<
/> : <
>
<
/
>
{
)
:
(
about
.
startsWith
(
'https://'
)
?
<
iframe
<>
{
about
.
startsWith
(
'https://'
)
?
(
<
iframe
src
=
{
about
}
src
=
{
about
}
style
=
{{
width
:
'100%'
,
height
:
'100vh'
,
border
:
'none'
}}
style
=
{{
width
:
'100%'
,
height
:
'100vh'
,
border
:
'none'
}}
/> : <div style={{ fontSize: 'larger' }} dangerouslySetInnerHTML={{ __html: about }}></
div
>
/
>
}
)
:
(
<
div
style
=
{{
fontSize
:
'larger'
}}
dangerouslySetInnerHTML
=
{{
__html
:
about
}}
><
/div
>
)}
<
/
>
<
/
>
}
)
}
<
/
>
<
/
>
);
);
};
};
export
default
About
;
export
default
About
;
web/src/pages/Channel/EditChannel.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
;
import
{
useNavigate
,
useParams
}
from
'react-router-dom'
;
import
{
useNavigate
,
useParams
}
from
'react-router-dom'
;
import
{
API
,
isMobile
,
showError
,
showInfo
,
showSuccess
,
verifyJSON
}
from
'../../helpers'
;
import
{
import
{
CHANNEL_OPTIONS
}
from
'../../constants'
;
API
,
import
Title
from
"@douyinfe/semi-ui/lib/es/typography/title"
;
isMobile
,
import
{
SideSheet
,
Space
,
Spin
,
Button
,
Input
,
Typography
,
Select
,
TextArea
,
Checkbox
,
Banner
}
from
"@douyinfe/semi-ui"
;
showError
,
showInfo
,
showSuccess
,
verifyJSON
,
}
from
'../../helpers'
;
import
{
CHANNEL_OPTIONS
}
from
'../../constants'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
{
SideSheet
,
Space
,
Spin
,
Button
,
Input
,
Typography
,
Select
,
TextArea
,
Checkbox
,
Banner
,
}
from
'@douyinfe/semi-ui'
;
const
MODEL_MAPPING_EXAMPLE
=
{
const
MODEL_MAPPING_EXAMPLE
=
{
'gpt-3.5-turbo-0301'
:
'gpt-3.5-turbo'
,
'gpt-3.5-turbo-0301'
:
'gpt-3.5-turbo'
,
'gpt-4-0314'
:
'gpt-4'
,
'gpt-4-0314'
:
'gpt-4'
,
'gpt-4-32k-0314'
:
'gpt-4-32k'
'gpt-4-32k-0314'
:
'gpt-4-32k'
,
};
};
function
type2secretPrompt
(
type
)
{
function
type2secretPrompt
(
type
)
{
...
@@ -33,7 +51,7 @@ const EditChannel = (props) => {
...
@@ -33,7 +51,7 @@ const EditChannel = (props) => {
const
isEdit
=
channelId
!==
undefined
;
const
isEdit
=
channelId
!==
undefined
;
const
[
loading
,
setLoading
]
=
useState
(
isEdit
);
const
[
loading
,
setLoading
]
=
useState
(
isEdit
);
const
handleCancel
=
()
=>
{
const
handleCancel
=
()
=>
{
props
.
handleClose
()
props
.
handleClose
();
};
};
const
originInputs
=
{
const
originInputs
=
{
name
:
''
,
name
:
''
,
...
@@ -45,7 +63,7 @@ const EditChannel = (props) => {
...
@@ -45,7 +63,7 @@ const EditChannel = (props) => {
model_mapping
:
''
,
model_mapping
:
''
,
models
:
[],
models
:
[],
auto_ban
:
1
,
auto_ban
:
1
,
groups
:
[
'default'
]
groups
:
[
'default'
],
};
};
const
[
batch
,
setBatch
]
=
useState
(
false
);
const
[
batch
,
setBatch
]
=
useState
(
false
);
const
[
autoBan
,
setAutoBan
]
=
useState
(
true
);
const
[
autoBan
,
setAutoBan
]
=
useState
(
true
);
...
@@ -58,30 +76,60 @@ const EditChannel = (props) => {
...
@@ -58,30 +76,60 @@ const EditChannel = (props) => {
const
[
fullModels
,
setFullModels
]
=
useState
([]);
const
[
fullModels
,
setFullModels
]
=
useState
([]);
const
[
customModel
,
setCustomModel
]
=
useState
(
''
);
const
[
customModel
,
setCustomModel
]
=
useState
(
''
);
const
handleInputChange
=
(
name
,
value
)
=>
{
const
handleInputChange
=
(
name
,
value
)
=>
{
setInputs
((
inputs
)
=>
({...
inputs
,
[
name
]:
value
}));
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
if
(
name
===
'type'
&&
inputs
.
models
.
length
===
0
)
{
if
(
name
===
'type'
&&
inputs
.
models
.
length
===
0
)
{
let
localModels
=
[];
let
localModels
=
[];
switch
(
value
)
{
switch
(
value
)
{
case
14
:
case
14
:
localModels
=
[
"claude-instant-1.2"
,
"claude-2"
,
"claude-2.0"
,
"claude-2.1"
,
"claude-3-opus-20240229"
,
"claude-3-sonnet-20240229"
,
"claude-3-haiku-20240307"
];
localModels
=
[
'claude-instant-1.2'
,
'claude-2'
,
'claude-2.0'
,
'claude-2.1'
,
'claude-3-opus-20240229'
,
'claude-3-sonnet-20240229'
,
'claude-3-haiku-20240307'
,
];
break
;
break
;
case
11
:
case
11
:
localModels
=
[
'PaLM-2'
];
localModels
=
[
'PaLM-2'
];
break
;
break
;
case
15
:
case
15
:
localModels
=
[
'ERNIE-Bot'
,
'ERNIE-Bot-turbo'
,
'ERNIE-Bot-4'
,
'Embedding-V1'
];
localModels
=
[
'ERNIE-Bot'
,
'ERNIE-Bot-turbo'
,
'ERNIE-Bot-4'
,
'Embedding-V1'
,
];
break
;
break
;
case
17
:
case
17
:
localModels
=
[
"qwen-turbo"
,
"qwen-plus"
,
"qwen-max"
,
"qwen-max-longcontext"
,
'text-embedding-v1'
];
localModels
=
[
'qwen-turbo'
,
'qwen-plus'
,
'qwen-max'
,
'qwen-max-longcontext'
,
'text-embedding-v1'
,
];
break
;
break
;
case
16
:
case
16
:
localModels
=
[
'chatglm_pro'
,
'chatglm_std'
,
'chatglm_lite'
];
localModels
=
[
'chatglm_pro'
,
'chatglm_std'
,
'chatglm_lite'
];
break
;
break
;
case
18
:
case
18
:
localModels
=
[
'SparkDesk'
,
'SparkDesk-v1.1'
,
'SparkDesk-v2.1'
,
'SparkDesk-v3.1'
,
'SparkDesk-v3.5'
];
localModels
=
[
'SparkDesk'
,
'SparkDesk-v1.1'
,
'SparkDesk-v2.1'
,
'SparkDesk-v3.1'
,
'SparkDesk-v3.5'
,
];
break
;
break
;
case
19
:
case
19
:
localModels
=
[
'360GPT_S2_V9'
,
'embedding-bert-512-v1'
,
'embedding_s1_v1'
,
'semantic_similarity_s1_v1'
];
localModels
=
[
'360GPT_S2_V9'
,
'embedding-bert-512-v1'
,
'embedding_s1_v1'
,
'semantic_similarity_s1_v1'
,
];
break
;
break
;
case
23
:
case
23
:
localModels
=
[
'hunyuan'
];
localModels
=
[
'hunyuan'
];
...
@@ -90,7 +138,11 @@ const EditChannel = (props) => {
...
@@ -90,7 +138,11 @@ const EditChannel = (props) => {
localModels
=
[
'gemini-pro'
,
'gemini-pro-vision'
];
localModels
=
[
'gemini-pro'
,
'gemini-pro-vision'
];
break
;
break
;
case
25
:
case
25
:
localModels
=
[
'moonshot-v1-8k'
,
'moonshot-v1-32k'
,
'moonshot-v1-128k'
];
localModels
=
[
'moonshot-v1-8k'
,
'moonshot-v1-32k'
,
'moonshot-v1-128k'
,
];
break
;
break
;
case
26
:
case
26
:
localModels
=
[
'glm-4'
,
'glm-4v'
,
'glm-3-turbo'
];
localModels
=
[
'glm-4'
,
'glm-4v'
,
'glm-3-turbo'
];
...
@@ -99,7 +151,14 @@ const EditChannel = (props) => {
...
@@ -99,7 +151,14 @@ const EditChannel = (props) => {
localModels
=
[
'yi-34b-chat-0205'
,
'yi-34b-chat-200k'
,
'yi-vl-plus'
];
localModels
=
[
'yi-34b-chat-0205'
,
'yi-34b-chat-200k'
,
'yi-vl-plus'
];
break
;
break
;
case
2
:
case
2
:
localModels
=
[
'mj_imagine'
,
'mj_variation'
,
'mj_reroll'
,
'mj_blend'
,
'mj_upscale'
,
'mj_describe'
];
localModels
=
[
'mj_imagine'
,
'mj_variation'
,
'mj_reroll'
,
'mj_blend'
,
'mj_upscale'
,
'mj_describe'
,
];
break
;
break
;
case
5
:
case
5
:
localModels
=
[
localModels
=
[
...
@@ -121,16 +180,15 @@ const EditChannel = (props) => {
...
@@ -121,16 +180,15 @@ const EditChannel = (props) => {
];
];
break
;
break
;
}
}
setInputs
((
inputs
)
=>
({...
inputs
,
models
:
localModels
}));
setInputs
((
inputs
)
=>
({
...
inputs
,
models
:
localModels
}));
}
}
//setAutoBan
//setAutoBan
};
};
const
loadChannel
=
async
()
=>
{
const
loadChannel
=
async
()
=>
{
setLoading
(
true
)
setLoading
(
true
);
let
res
=
await
API
.
get
(
`/api/channel/
${
channelId
}
`
);
let
res
=
await
API
.
get
(
`/api/channel/
${
channelId
}
`
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
if
(
data
.
models
===
''
)
{
if
(
data
.
models
===
''
)
{
data
.
models
=
[];
data
.
models
=
[];
...
@@ -143,7 +201,11 @@ const EditChannel = (props) => {
...
@@ -143,7 +201,11 @@ const EditChannel = (props) => {
data
.
groups
=
data
.
group
.
split
(
','
);
data
.
groups
=
data
.
group
.
split
(
','
);
}
}
if
(
data
.
model_mapping
!==
''
)
{
if
(
data
.
model_mapping
!==
''
)
{
data
.
model_mapping
=
JSON
.
stringify
(
JSON
.
parse
(
data
.
model_mapping
),
null
,
2
);
data
.
model_mapping
=
JSON
.
stringify
(
JSON
.
parse
(
data
.
model_mapping
),
null
,
2
,
);
}
}
setInputs
(
data
);
setInputs
(
data
);
if
(
data
.
auto_ban
===
0
)
{
if
(
data
.
auto_ban
===
0
)
{
...
@@ -163,13 +225,17 @@ const EditChannel = (props) => {
...
@@ -163,13 +225,17 @@ const EditChannel = (props) => {
let
res
=
await
API
.
get
(
`/api/channel/models`
);
let
res
=
await
API
.
get
(
`/api/channel/models`
);
let
localModelOptions
=
res
.
data
.
data
.
map
((
model
)
=>
({
let
localModelOptions
=
res
.
data
.
data
.
map
((
model
)
=>
({
label
:
model
.
id
,
label
:
model
.
id
,
value
:
model
.
id
value
:
model
.
id
,
}));
}));
setOriginModelOptions
(
localModelOptions
);
setOriginModelOptions
(
localModelOptions
);
setFullModels
(
res
.
data
.
data
.
map
((
model
)
=>
model
.
id
));
setFullModels
(
res
.
data
.
data
.
map
((
model
)
=>
model
.
id
));
setBasicModels
(
res
.
data
.
data
.
filter
((
model
)
=>
{
setBasicModels
(
res
.
data
.
data
.
filter
((
model
)
=>
{
return
model
.
id
.
startsWith
(
'gpt-3'
)
||
model
.
id
.
startsWith
(
'text-'
);
return
model
.
id
.
startsWith
(
'gpt-3'
)
||
model
.
id
.
startsWith
(
'text-'
);
}).
map
((
model
)
=>
model
.
id
));
})
.
map
((
model
)
=>
model
.
id
),
);
}
catch
(
error
)
{
}
catch
(
error
)
{
showError
(
error
.
message
);
showError
(
error
.
message
);
}
}
...
@@ -178,10 +244,12 @@ const EditChannel = (props) => {
...
@@ -178,10 +244,12 @@ const EditChannel = (props) => {
const
fetchGroups
=
async
()
=>
{
const
fetchGroups
=
async
()
=>
{
try
{
try
{
let
res
=
await
API
.
get
(
`/api/group/`
);
let
res
=
await
API
.
get
(
`/api/group/`
);
setGroupOptions
(
res
.
data
.
data
.
map
((
group
)
=>
({
setGroupOptions
(
res
.
data
.
data
.
map
((
group
)
=>
({
label
:
group
,
label
:
group
,
value
:
group
value
:
group
,
})));
})),
);
}
catch
(
error
)
{
}
catch
(
error
)
{
showError
(
error
.
message
);
showError
(
error
.
message
);
}
}
...
@@ -193,7 +261,7 @@ const EditChannel = (props) => {
...
@@ -193,7 +261,7 @@ const EditChannel = (props) => {
if
(
!
localModelOptions
.
find
((
option
)
=>
option
.
key
===
model
))
{
if
(
!
localModelOptions
.
find
((
option
)
=>
option
.
key
===
model
))
{
localModelOptions
.
push
({
localModelOptions
.
push
({
label
:
model
,
label
:
model
,
value
:
model
value
:
model
,
});
});
}
}
});
});
...
@@ -204,17 +272,12 @@ const EditChannel = (props) => {
...
@@ -204,17 +272,12 @@ const EditChannel = (props) => {
fetchModels
().
then
();
fetchModels
().
then
();
fetchGroups
().
then
();
fetchGroups
().
then
();
if
(
isEdit
)
{
if
(
isEdit
)
{
loadChannel
().
then
(
loadChannel
().
then
(()
=>
{});
()
=>
{
}
);
}
else
{
}
else
{
setInputs
(
originInputs
)
setInputs
(
originInputs
);
}
}
},
[
props
.
editingChannel
.
id
]);
},
[
props
.
editingChannel
.
id
]);
const
submit
=
async
()
=>
{
const
submit
=
async
()
=>
{
if
(
!
isEdit
&&
(
inputs
.
name
===
''
||
inputs
.
key
===
''
))
{
if
(
!
isEdit
&&
(
inputs
.
name
===
''
||
inputs
.
key
===
''
))
{
showInfo
(
'请填写渠道名称和渠道密钥!'
);
showInfo
(
'请填写渠道名称和渠道密钥!'
);
...
@@ -228,9 +291,12 @@ const EditChannel = (props) => {
...
@@ -228,9 +291,12 @@ const EditChannel = (props) => {
showInfo
(
'模型映射必须是合法的 JSON 格式!'
);
showInfo
(
'模型映射必须是合法的 JSON 格式!'
);
return
;
return
;
}
}
let
localInputs
=
{...
inputs
};
let
localInputs
=
{
...
inputs
};
if
(
localInputs
.
base_url
&&
localInputs
.
base_url
.
endsWith
(
'/'
))
{
if
(
localInputs
.
base_url
&&
localInputs
.
base_url
.
endsWith
(
'/'
))
{
localInputs
.
base_url
=
localInputs
.
base_url
.
slice
(
0
,
localInputs
.
base_url
.
length
-
1
);
localInputs
.
base_url
=
localInputs
.
base_url
.
slice
(
0
,
localInputs
.
base_url
.
length
-
1
,
);
}
}
if
(
localInputs
.
type
===
3
&&
localInputs
.
other
===
''
)
{
if
(
localInputs
.
type
===
3
&&
localInputs
.
other
===
''
)
{
localInputs
.
other
=
'2023-06-01-preview'
;
localInputs
.
other
=
'2023-06-01-preview'
;
...
@@ -248,11 +314,14 @@ const EditChannel = (props) => {
...
@@ -248,11 +314,14 @@ const EditChannel = (props) => {
localInputs
.
models
=
localInputs
.
models
.
join
(
','
);
localInputs
.
models
=
localInputs
.
models
.
join
(
','
);
localInputs
.
group
=
localInputs
.
groups
.
join
(
','
);
localInputs
.
group
=
localInputs
.
groups
.
join
(
','
);
if
(
isEdit
)
{
if
(
isEdit
)
{
res
=
await
API
.
put
(
`/api/channel/`
,
{...
localInputs
,
id
:
parseInt
(
channelId
)});
res
=
await
API
.
put
(
`/api/channel/`
,
{
...
localInputs
,
id
:
parseInt
(
channelId
),
});
}
else
{
}
else
{
res
=
await
API
.
post
(
`/api/channel/`
,
localInputs
);
res
=
await
API
.
post
(
`/api/channel/`
,
localInputs
);
}
}
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
if
(
isEdit
)
{
if
(
isEdit
)
{
showSuccess
(
'渠道更新成功!'
);
showSuccess
(
'渠道更新成功!'
);
...
@@ -269,16 +338,16 @@ const EditChannel = (props) => {
...
@@ -269,16 +338,16 @@ const EditChannel = (props) => {
const
addCustomModel
=
()
=>
{
const
addCustomModel
=
()
=>
{
if
(
customModel
.
trim
()
===
''
)
return
;
if
(
customModel
.
trim
()
===
''
)
return
;
if
(
inputs
.
models
.
includes
(
customModel
))
return
showError
(
"该模型已存在!"
);
if
(
inputs
.
models
.
includes
(
customModel
))
return
showError
(
'该模型已存在!'
);
let
localModels
=
[...
inputs
.
models
];
let
localModels
=
[...
inputs
.
models
];
localModels
.
push
(
customModel
);
localModels
.
push
(
customModel
);
let
localModelOptions
=
[];
let
localModelOptions
=
[];
localModelOptions
.
push
({
localModelOptions
.
push
({
key
:
customModel
,
key
:
customModel
,
text
:
customModel
,
text
:
customModel
,
value
:
customModel
value
:
customModel
,
});
});
setModelOptions
(
modelOptions
=>
{
setModelOptions
((
modelOptions
)
=>
{
return
[...
modelOptions
,
...
localModelOptions
];
return
[...
modelOptions
,
...
localModelOptions
];
});
});
setCustomModel
(
''
);
setCustomModel
(
''
);
...
@@ -290,15 +359,26 @@ const EditChannel = (props) => {
...
@@ -290,15 +359,26 @@ const EditChannel = (props) => {
<
SideSheet
<
SideSheet
maskClosable
=
{
false
}
maskClosable
=
{
false
}
placement
=
{
isEdit
?
'right'
:
'left'
}
placement
=
{
isEdit
?
'right'
:
'left'
}
title
=
{
<
Title
level
=
{
3
}
>
{
isEdit
?
'更新渠道信息'
:
'创建新的渠道'
}
<
/Title>
}
title
=
{
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
<
Title
level
=
{
3
}
>
{
isEdit
?
'更新渠道信息'
:
'创建新的渠道'
}
<
/Title
>
bodyStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
}
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
bodyStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
visible
=
{
props
.
visible
}
visible
=
{
props
.
visible
}
footer
=
{
footer
=
{
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
Space
>
<
Space
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
onClick
=
{
submit
}
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
}
}
...
@@ -307,7 +387,7 @@ const EditChannel = (props) => {
...
@@ -307,7 +387,7 @@ const EditChannel = (props) => {
width
=
{
isMobile
()
?
'100%'
:
600
}
width
=
{
isMobile
()
?
'100%'
:
600
}
>
>
<
Spin
spinning
=
{
loading
}
>
<
Spin
spinning
=
{
loading
}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
类型:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
类型:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Select
<
Select
...
@@ -315,84 +395,94 @@ const EditChannel = (props) => {
...
@@ -315,84 +395,94 @@ const EditChannel = (props) => {
required
required
optionList
=
{
CHANNEL_OPTIONS
}
optionList
=
{
CHANNEL_OPTIONS
}
value
=
{
inputs
.
type
}
value
=
{
inputs
.
type
}
onChange
=
{
value
=>
handleInputChange
(
'type'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'type'
,
value
)}
style
=
{{
width
:
'50%'
}}
style
=
{{
width
:
'50%'
}}
/
>
/
>
{
{
inputs
.
type
===
3
&&
(
inputs
.
type
===
3
&&
(
<>
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Banner
type
=
{
"warning"
}
description
=
{
<
Banner
type
=
{
'warning'
}
description
=
{
<>
<>
注意,
<
strong
>
模型部署名称必须和模型名称保持一致
<
/strong>,因为 One API 会把请求体中
的
注意,
<
strong
>
模型部署名称必须和模型名称保持一致
<
/strong
>
model
,因为
One
API
会把请求体中的
model
参数替换为你的部署名称(模型名称中的点会被剔除),
<
a
target
=
'_blank'
参数替换为你的部署名称(模型名称中的点会被剔除),
href
=
'https://github.com/songquanpeng/one-api/issues/133?notification_referrer_id=NT_kwDOAmJSYrM2NjIwMzI3NDgyOjM5OTk4MDUw#issuecomment-1571602271'
>
图片演示
<
/a>
。
<
a
target
=
'_blank'
href
=
'https://github.com/songquanpeng/one-api/issues/133?notification_referrer_id=NT_kwDOAmJSYrM2NjIwMzI3NDgyOjM5OTk4MDUw#issuecomment-1571602271'
>
图片演示
<
/a
>
。
<
/
>
<
/
>
}
>
}
<
/Banner
>
>
<
/Banner
>
<
/div
>
<
/div
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
AZURE_OPENAI_ENDPOINT
:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
AZURE_OPENAI_ENDPOINT
:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
label
=
'AZURE_OPENAI_ENDPOINT'
label
=
'AZURE_OPENAI_ENDPOINT'
name
=
'azure_base_url'
name
=
'azure_base_url'
placeholder
=
{
'请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com'
}
placeholder
=
{
onChange
=
{
value
=>
{
'请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com'
handleInputChange
(
'base_url'
,
value
)
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
);
}}
}}
value
=
{
inputs
.
base_url
}
value
=
{
inputs
.
base_url
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
默认
API
版本:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
默认
API
版本:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
label
=
'默认 API 版本'
label
=
'默认 API 版本'
name
=
'azure_other'
name
=
'azure_other'
placeholder
=
{
'请输入默认 API 版本,例如:2023-06-01-preview,该配置可以被实际的请求查询参数所覆盖'
}
placeholder
=
{
onChange
=
{
value
=>
{
'请输入默认 API 版本,例如:2023-06-01-preview,该配置可以被实际的请求查询参数所覆盖'
handleInputChange
(
'other'
,
value
)
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'other'
,
value
);
}}
}}
value
=
{
inputs
.
other
}
value
=
{
inputs
.
other
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
<
/
>
<
/
>
)
)}
}
{
inputs
.
type
===
8
&&
(
{
inputs
.
type
===
8
&&
(
<>
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
Base
URL
:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
Base
URL
:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
name
=
'base_url'
name
=
'base_url'
placeholder
=
{
'请输入自定义渠道的 Base URL'
}
placeholder
=
{
'请输入自定义渠道的 Base URL'
}
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
)
handleInputChange
(
'base_url'
,
value
);
}}
}}
value
=
{
inputs
.
base_url
}
value
=
{
inputs
.
base_url
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
<
/
>
<
/
>
)
)}
}
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
名称:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
名称:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
required
required
name
=
'name'
name
=
'name'
placeholder
=
{
'请为渠道命名'
}
placeholder
=
{
'请为渠道命名'
}
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'name'
,
value
)
handleInputChange
(
'name'
,
value
);
}}
}}
value
=
{
inputs
.
name
}
value
=
{
inputs
.
name
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
分组:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
分组:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Select
<
Select
...
@@ -403,51 +493,49 @@ const EditChannel = (props) => {
...
@@ -403,51 +493,49 @@ const EditChannel = (props) => {
selection
selection
allowAdditions
allowAdditions
additionLabel
=
{
'请在系统设置页面编辑分组倍率以添加新的分组:'
}
additionLabel
=
{
'请在系统设置页面编辑分组倍率以添加新的分组:'
}
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'groups'
,
value
)
handleInputChange
(
'groups'
,
value
);
}}
}}
value
=
{
inputs
.
groups
}
value
=
{
inputs
.
groups
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
optionList
=
{
groupOptions
}
optionList
=
{
groupOptions
}
/
>
/
>
{
{
inputs
.
type
===
18
&&
(
inputs
.
type
===
18
&&
(
<>
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
模型版本:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
模型版本:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
name
=
'other'
name
=
'other'
placeholder
=
{
'请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1'
}
placeholder
=
{
onChange
=
{
value
=>
{
'请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1'
handleInputChange
(
'other'
,
value
)
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'other'
,
value
);
}}
}}
value
=
{
inputs
.
other
}
value
=
{
inputs
.
other
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
<
/
>
<
/
>
)
)}
}
{
inputs
.
type
===
21
&&
(
{
inputs
.
type
===
21
&&
(
<>
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
知识库
ID
:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
知识库
ID
:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
label
=
'知识库 ID'
label
=
'知识库 ID'
name
=
'other'
name
=
'other'
placeholder
=
{
'请输入知识库 ID,例如:123456'
}
placeholder
=
{
'请输入知识库 ID,例如:123456'
}
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'other'
,
value
)
handleInputChange
(
'other'
,
value
);
}}
}}
value
=
{
inputs
.
other
}
value
=
{
inputs
.
other
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
<
/
>
<
/
>
)
)}
}
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
模型:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
模型:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Select
<
Select
...
@@ -456,28 +544,45 @@ const EditChannel = (props) => {
...
@@ -456,28 +544,45 @@ const EditChannel = (props) => {
required
required
multiple
multiple
selection
selection
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'models'
,
value
)
handleInputChange
(
'models'
,
value
);
}}
}}
value
=
{
inputs
.
models
}
value
=
{
inputs
.
models
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
optionList
=
{
modelOptions
}
optionList
=
{
modelOptions
}
/
>
/
>
<
div
style
=
{{
lineHeight
:
'40px'
,
marginBottom
:
'12px'
}}
>
<
div
style
=
{{
lineHeight
:
'40px'
,
marginBottom
:
'12px'
}}
>
<
Space
>
<
Space
>
<
Button
type
=
'primary'
onClick
=
{()
=>
{
<
Button
type
=
'primary'
onClick
=
{()
=>
{
handleInputChange
(
'models'
,
basicModels
);
handleInputChange
(
'models'
,
basicModels
);
}}
>
填入基础模型
<
/Button
>
}}
<
Button
type
=
'secondary'
onClick
=
{()
=>
{
>
填入基础模型
<
/Button
>
<
Button
type
=
'secondary'
onClick
=
{()
=>
{
handleInputChange
(
'models'
,
fullModels
);
handleInputChange
(
'models'
,
fullModels
);
}}
>
填入所有模型
<
/Button
>
}}
<
Button
type
=
'warning'
onClick
=
{()
=>
{
>
填入所有模型
<
/Button
>
<
Button
type
=
'warning'
onClick
=
{()
=>
{
handleInputChange
(
'models'
,
[]);
handleInputChange
(
'models'
,
[]);
}}
>
清除所有模型
<
/Button
>
}}
>
清除所有模型
<
/Button
>
<
/Space
>
<
/Space
>
<
Input
<
Input
addonAfter
=
{
addonAfter
=
{
<
Button
type
=
'primary'
onClick
=
{
addCustomModel
}
>
填入
<
/Button
>
<
Button
type
=
'primary'
onClick
=
{
addCustomModel
}
>
填入
<
/Button
>
}
}
placeholder
=
'输入自定义模型名称'
placeholder
=
'输入自定义模型名称'
value
=
{
customModel
}
value
=
{
customModel
}
...
@@ -486,92 +591,93 @@ const EditChannel = (props) => {
...
@@ -486,92 +591,93 @@ const EditChannel = (props) => {
}}
}}
/
>
/
>
<
/div
>
<
/div
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
模型重定向:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
模型重定向:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
TextArea
<
TextArea
placeholder
=
{
`此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:\n
${
JSON
.
stringify
(
MODEL_MAPPING_EXAMPLE
,
null
,
2
)}
`
}
placeholder
=
{
`此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:\n
${
JSON
.
stringify
(
MODEL_MAPPING_EXAMPLE
,
null
,
2
)}
`
}
name
=
'model_mapping'
name
=
'model_mapping'
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'model_mapping'
,
value
)
handleInputChange
(
'model_mapping'
,
value
);
}}
}}
autosize
autosize
value
=
{
inputs
.
model_mapping
}
value
=
{
inputs
.
model_mapping
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
<
Typography
.
Text
style
=
{{
<
Typography
.
Text
style
=
{{
color
:
'rgba(var(--semi-blue-5), 1)'
,
color
:
'rgba(var(--semi-blue-5), 1)'
,
userSelect
:
'none'
,
userSelect
:
'none'
,
cursor
:
'pointer'
cursor
:
'pointer'
,
}}
onClick
=
{
}}
()
=>
{
onClick
=
{()
=>
{
handleInputChange
(
'model_mapping'
,
JSON
.
stringify
(
MODEL_MAPPING_EXAMPLE
,
null
,
2
))
handleInputChange
(
}
'model_mapping'
,
}
>
JSON
.
stringify
(
MODEL_MAPPING_EXAMPLE
,
null
,
2
),
);
}}
>
填入模板
填入模板
<
/Typography.Text
>
<
/Typography.Text
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
密钥:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
密钥:
<
/Typography.Text
>
<
/div
>
<
/div
>
{
{
batch
?
(
batch
?
<
TextArea
<
TextArea
label
=
'密钥'
label
=
'密钥'
name
=
'key'
name
=
'key'
required
required
placeholder
=
{
'请输入密钥,一行一个'
}
placeholder
=
{
'请输入密钥,一行一个'
}
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'key'
,
value
)
handleInputChange
(
'key'
,
value
);
}}
}}
value
=
{
inputs
.
key
}
value
=
{
inputs
.
key
}
style
=
{{
minHeight
:
150
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
minHeight
:
150
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
:
)
:
(
<
Input
<
Input
label
=
'密钥'
label
=
'密钥'
name
=
'key'
name
=
'key'
required
required
placeholder
=
{
type2secretPrompt
(
inputs
.
type
)}
placeholder
=
{
type2secretPrompt
(
inputs
.
type
)}
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'key'
,
value
)
handleInputChange
(
'key'
,
value
);
}}
}}
value
=
{
inputs
.
key
}
value
=
{
inputs
.
key
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
}
)
}
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
组织:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
组织:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
label
=
'组织,可选,不填则为默认组织'
label
=
'组织,可选,不填则为默认组织'
name
=
'openai_organization'
name
=
'openai_organization'
placeholder
=
'请输入组织org-xxx'
placeholder
=
'请输入组织org-xxx'
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'openai_organization'
,
value
)
handleInputChange
(
'openai_organization'
,
value
);
}}
}}
value
=
{
inputs
.
openai_organization
}
value
=
{
inputs
.
openai_organization
}
/
>
/
>
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
Space
>
<
Space
>
<
Checkbox
<
Checkbox
name
=
'auto_ban'
name
=
'auto_ban'
checked
=
{
autoBan
}
checked
=
{
autoBan
}
onChange
=
{
onChange
=
{()
=>
{
()
=>
{
setAutoBan
(
!
autoBan
);
setAutoBan
(
!
autoBan
);
}
}}
}
// onChange={handleInputChange}
// onChange={handleInputChange}
/
>
/
>
<
Typography
.
Text
<
Typography
.
Text
strong
>
strong
>
是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道:
<
/Typography.Text
>
是否自动禁用(仅当自动禁用开启时有效),关闭后不会自动禁用该渠道:
<
/Typography.Text
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
{
{
!
isEdit
&&
(
!
isEdit
&&
(
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
Space
>
<
Space
>
<
Checkbox
<
Checkbox
checked
=
{
batch
}
checked
=
{
batch
}
...
@@ -582,46 +688,42 @@ const EditChannel = (props) => {
...
@@ -582,46 +688,42 @@ const EditChannel = (props) => {
<
Typography
.
Text
strong
>
批量创建
<
/Typography.Text
>
<
Typography
.
Text
strong
>
批量创建
<
/Typography.Text
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
)
)}
}
{
inputs
.
type
!==
3
&&
inputs
.
type
!==
8
&&
inputs
.
type
!==
22
&&
(
{
inputs
.
type
!==
3
&&
inputs
.
type
!==
8
&&
inputs
.
type
!==
22
&&
(
<>
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
代理:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
代理:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
label
=
'代理'
label
=
'代理'
name
=
'base_url'
name
=
'base_url'
placeholder
=
{
'此项可选,用于通过代理站来进行 API 调用'
}
placeholder
=
{
'此项可选,用于通过代理站来进行 API 调用'
}
onChange
=
{
value
=>
{
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
)
handleInputChange
(
'base_url'
,
value
);
}}
}}
value
=
{
inputs
.
base_url
}
value
=
{
inputs
.
base_url
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
<
/
>
<
/
>
)
)}
}
{
inputs
.
type
===
22
&&
(
{
inputs
.
type
===
22
&&
(
<>
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
私有部署地址:
<
/Typography.Text
>
<
Typography
.
Text
strong
>
私有部署地址:
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
name
=
'base_url'
name
=
'base_url'
placeholder
=
{
'请输入私有部署地址,格式为:https://fastgpt.run/api/openapi'
}
placeholder
=
{
onChange
=
{
value
=>
{
'请输入私有部署地址,格式为:https://fastgpt.run/api/openapi'
handleInputChange
(
'base_url'
,
value
)
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
);
}}
}}
value
=
{
inputs
.
base_url
}
value
=
{
inputs
.
base_url
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
/>
/>
<
/
>
<
/
>
)
)}
}
<
/Spin
>
<
/Spin
>
<
/SideSheet
>
<
/SideSheet
>
<
/
>
<
/
>
...
...
web/src/pages/Channel/index.js
View file @
eb955ae4
import
React
from
'react'
;
import
React
from
'react'
;
import
ChannelsTable
from
'../../components/ChannelsTable'
;
import
ChannelsTable
from
'../../components/ChannelsTable'
;
import
{
Layout
}
from
"@douyinfe/semi-ui"
;
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
const
File
=
()
=>
(
const
File
=
()
=>
(
<>
<>
...
@@ -9,7 +9,7 @@ const File = () => (
...
@@ -9,7 +9,7 @@ const File = () => (
<
h3
>
管理渠道
<
/h3
>
<
h3
>
管理渠道
<
/h3
>
<
/Layout.Header
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
ChannelsTable
/>
<
ChannelsTable
/>
<
/Layout.Content
>
<
/Layout.Content
>
<
/Layout
>
<
/Layout
>
<
/
>
<
/
>
...
...
web/src/pages/Chat/index.js
View file @
eb955ae4
...
@@ -11,5 +11,4 @@ const Chat = () => {
...
@@ -11,5 +11,4 @@ const Chat = () => {
);
);
};
};
export
default
Chat
;
export
default
Chat
;
web/src/pages/Detail/index.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'react'
;
import
{
initVChartSemiTheme
}
from
'@visactor/vchart-semi-theme'
;
import
{
initVChartSemiTheme
}
from
'@visactor/vchart-semi-theme'
;
import
{
Button
,
Col
,
Form
,
Layout
,
Row
,
Spin
}
from
"@douyinfe/semi-ui"
;
import
{
Button
,
Col
,
Form
,
Layout
,
Row
,
Spin
}
from
'@douyinfe/semi-ui'
;
import
VChart
from
'@visactor/vchart'
;
import
VChart
from
'@visactor/vchart'
;
import
{
API
,
isAdmin
,
showError
,
timestamp2string
,
timestamp2string1
}
from
"../../helpers"
;
import
{
import
{
getQuotaWithUnit
,
modelColorMap
,
API
,
isAdmin
,
showError
,
timestamp2string
,
timestamp2string1
,
}
from
'../../helpers'
;
import
{
getQuotaWithUnit
,
modelColorMap
,
renderNumber
,
renderNumber
,
renderQuota
,
renderQuota
,
renderQuotaNumberWithDigit
,
renderQuotaNumberWithDigit
,
stringToColor
stringToColor
,
}
from
"../../helpers/render"
;
}
from
'../../helpers/render'
;
const
Detail
=
(
props
)
=>
{
const
Detail
=
(
props
)
=>
{
const
formRef
=
useRef
();
const
formRef
=
useRef
();
...
@@ -19,28 +26,36 @@ const Detail = (props) => {
...
@@ -19,28 +26,36 @@ const Detail = (props) => {
username
:
''
,
username
:
''
,
token_name
:
''
,
token_name
:
''
,
model_name
:
''
,
model_name
:
''
,
start_timestamp
:
localStorage
.
getItem
(
'data_export_default_time'
)
===
'hour'
?
timestamp2string
(
now
.
getTime
()
/
1000
-
86400
)
:
(
localStorage
.
getItem
(
'data_export_default_time'
)
===
'week'
?
timestamp2string
(
now
.
getTime
()
/
1000
-
86400
*
30
)
:
timestamp2string
(
now
.
getTime
()
/
1000
-
86400
*
7
)),
start_timestamp
:
localStorage
.
getItem
(
'data_export_default_time'
)
===
'hour'
?
timestamp2string
(
now
.
getTime
()
/
1000
-
86400
)
:
localStorage
.
getItem
(
'data_export_default_time'
)
===
'week'
?
timestamp2string
(
now
.
getTime
()
/
1000
-
86400
*
30
)
:
timestamp2string
(
now
.
getTime
()
/
1000
-
86400
*
7
),
end_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
+
3600
),
end_timestamp
:
timestamp2string
(
now
.
getTime
()
/
1000
+
3600
),
channel
:
''
,
channel
:
''
,
data_export_default_time
:
''
data_export_default_time
:
''
,
});
});
const
{
username
,
model_name
,
start_timestamp
,
end_timestamp
,
channel
}
=
inputs
;
const
{
username
,
model_name
,
start_timestamp
,
end_timestamp
,
channel
}
=
inputs
;
const
isAdminUser
=
isAdmin
();
const
isAdminUser
=
isAdmin
();
const
initialized
=
useRef
(
false
)
const
initialized
=
useRef
(
false
);
const
[
modelDataChart
,
setModelDataChart
]
=
useState
(
null
);
const
[
modelDataChart
,
setModelDataChart
]
=
useState
(
null
);
const
[
modelDataPieChart
,
setModelDataPieChart
]
=
useState
(
null
);
const
[
modelDataPieChart
,
setModelDataPieChart
]
=
useState
(
null
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
quotaData
,
setQuotaData
]
=
useState
([]);
const
[
quotaData
,
setQuotaData
]
=
useState
([]);
const
[
consumeQuota
,
setConsumeQuota
]
=
useState
(
0
);
const
[
consumeQuota
,
setConsumeQuota
]
=
useState
(
0
);
const
[
times
,
setTimes
]
=
useState
(
0
);
const
[
times
,
setTimes
]
=
useState
(
0
);
const
[
dataExportDefaultTime
,
setDataExportDefaultTime
]
=
useState
(
localStorage
.
getItem
(
'data_export_default_time'
)
||
'hour'
);
const
[
dataExportDefaultTime
,
setDataExportDefaultTime
]
=
useState
(
localStorage
.
getItem
(
'data_export_default_time'
)
||
'hour'
,
);
const
handleInputChange
=
(
value
,
name
)
=>
{
const
handleInputChange
=
(
value
,
name
)
=>
{
if
(
name
===
'data_export_default_time'
)
{
if
(
name
===
'data_export_default_time'
)
{
setDataExportDefaultTime
(
value
);
setDataExportDefaultTime
(
value
);
return
return
;
}
}
setInputs
((
inputs
)
=>
({...
inputs
,
[
name
]:
value
}));
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
};
};
const
spec_line
=
{
const
spec_line
=
{
...
@@ -48,67 +63,71 @@ const Detail = (props) => {
...
@@ -48,67 +63,71 @@ const Detail = (props) => {
data
:
[
data
:
[
{
{
id
:
'barData'
,
id
:
'barData'
,
values
:
[]
values
:
[],
}
},
],
],
xField
:
'Time'
,
xField
:
'Time'
,
yField
:
'Usage'
,
yField
:
'Usage'
,
seriesField
:
'Model'
,
seriesField
:
'Model'
,
stack
:
true
,
stack
:
true
,
legends
:
{
legends
:
{
visible
:
true
visible
:
true
,
},
},
title
:
{
title
:
{
visible
:
true
,
visible
:
true
,
text
:
'模型消耗分布'
,
text
:
'模型消耗分布'
,
subtext
:
'0'
subtext
:
'0'
,
},
},
bar
:
{
bar
:
{
// The state style of bar
// The state style of bar
state
:
{
state
:
{
hover
:
{
hover
:
{
stroke
:
'#000'
,
stroke
:
'#000'
,
lineWidth
:
1
lineWidth
:
1
,
}
},
}
},
},
},
tooltip
:
{
tooltip
:
{
mark
:
{
mark
:
{
content
:
[
content
:
[
{
{
key
:
datum
=>
datum
[
'Model'
],
key
:
(
datum
)
=>
datum
[
'Model'
],
value
:
datum
=>
renderQuotaNumberWithDigit
(
parseFloat
(
datum
[
'Usage'
]),
4
)
value
:
(
datum
)
=>
}
renderQuotaNumberWithDigit
(
parseFloat
(
datum
[
'Usage'
]),
4
),
]
},
],
},
},
dimension
:
{
dimension
:
{
content
:
[
content
:
[
{
{
key
:
datum
=>
datum
[
'Model'
],
key
:
(
datum
)
=>
datum
[
'Model'
],
value
:
datum
=>
datum
[
'Usage'
]
value
:
(
datum
)
=>
datum
[
'Usage'
],
}
},
],
],
updateContent
:
array
=>
{
updateContent
:
(
array
)
=>
{
// sort by value
// sort by value
array
.
sort
((
a
,
b
)
=>
b
.
value
-
a
.
value
);
array
.
sort
((
a
,
b
)
=>
b
.
value
-
a
.
value
);
// add $
// add $
let
sum
=
0
;
let
sum
=
0
;
for
(
let
i
=
0
;
i
<
array
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
array
.
length
;
i
++
)
{
sum
+=
parseFloat
(
array
[
i
].
value
);
sum
+=
parseFloat
(
array
[
i
].
value
);
array
[
i
].
value
=
renderQuotaNumberWithDigit
(
parseFloat
(
array
[
i
].
value
),
4
);
array
[
i
].
value
=
renderQuotaNumberWithDigit
(
parseFloat
(
array
[
i
].
value
),
4
,
);
}
}
// add to first
// add to first
array
.
unshift
({
array
.
unshift
({
key
:
'总计'
,
key
:
'总计'
,
value
:
renderQuotaNumberWithDigit
(
sum
,
4
)
value
:
renderQuotaNumberWithDigit
(
sum
,
4
),
});
});
return
array
;
return
array
;
}
},
}
},
},
},
color
:
{
color
:
{
specified
:
modelColorMap
specified
:
modelColorMap
,
}
},
};
};
const
spec_pie
=
{
const
spec_pie
=
{
...
@@ -116,10 +135,8 @@ const Detail = (props) => {
...
@@ -116,10 +135,8 @@ const Detail = (props) => {
data
:
[
data
:
[
{
{
id
:
'id0'
,
id
:
'id0'
,
values
:
[
values
:
[{
type
:
'null'
,
value
:
'0'
}],
{
type
:
'null'
,
value
:
'0'
},
},
]
}
],
],
outerRadius
:
0.8
,
outerRadius
:
0.8
,
innerRadius
:
0.5
,
innerRadius
:
0.5
,
...
@@ -128,45 +145,45 @@ const Detail = (props) => {
...
@@ -128,45 +145,45 @@ const Detail = (props) => {
categoryField
:
'type'
,
categoryField
:
'type'
,
pie
:
{
pie
:
{
style
:
{
style
:
{
cornerRadius
:
10
cornerRadius
:
10
,
},
},
state
:
{
state
:
{
hover
:
{
hover
:
{
outerRadius
:
0.85
,
outerRadius
:
0.85
,
stroke
:
'#000'
,
stroke
:
'#000'
,
lineWidth
:
1
lineWidth
:
1
,
},
},
selected
:
{
selected
:
{
outerRadius
:
0.85
,
outerRadius
:
0.85
,
stroke
:
'#000'
,
stroke
:
'#000'
,
lineWidth
:
1
lineWidth
:
1
,
}
},
}
},
},
},
title
:
{
title
:
{
visible
:
true
,
visible
:
true
,
text
:
'模型调用次数占比'
text
:
'模型调用次数占比'
,
},
},
legends
:
{
legends
:
{
visible
:
true
,
visible
:
true
,
orient
:
'left'
orient
:
'left'
,
},
},
label
:
{
label
:
{
visible
:
true
visible
:
true
,
},
},
tooltip
:
{
tooltip
:
{
mark
:
{
mark
:
{
content
:
[
content
:
[
{
{
key
:
datum
=>
datum
[
'type'
],
key
:
(
datum
)
=>
datum
[
'type'
],
value
:
datum
=>
renderNumber
(
datum
[
'value'
])
value
:
(
datum
)
=>
renderNumber
(
datum
[
'value'
]),
}
},
]
],
}
},
},
},
color
:
{
color
:
{
specified
:
modelColorMap
specified
:
modelColorMap
,
}
},
};
};
const
loadQuotaData
=
async
(
lineChart
,
pieChart
)
=>
{
const
loadQuotaData
=
async
(
lineChart
,
pieChart
)
=>
{
...
@@ -181,16 +198,16 @@ const Detail = (props) => {
...
@@ -181,16 +198,16 @@ const Detail = (props) => {
url
=
`/api/data/self/?start_timestamp=
${
localStartTimestamp
}
&end_timestamp=
${
localEndTimestamp
}
&default_time=
${
dataExportDefaultTime
}
`
;
url
=
`/api/data/self/?start_timestamp=
${
localStartTimestamp
}
&end_timestamp=
${
localEndTimestamp
}
&default_time=
${
dataExportDefaultTime
}
`
;
}
}
const
res
=
await
API
.
get
(
url
);
const
res
=
await
API
.
get
(
url
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
setQuotaData
(
data
);
setQuotaData
(
data
);
if
(
data
.
length
===
0
)
{
if
(
data
.
length
===
0
)
{
data
.
push
({
data
.
push
({
'count'
:
0
,
count
:
0
,
'model_name'
:
'无数据'
,
model_name
:
'无数据'
,
'quota'
:
0
,
quota
:
0
,
'created_at'
:
now
.
getTime
()
/
1000
created_at
:
now
.
getTime
()
/
1000
,
})
});
}
}
// 根据dataExportDefaultTime重制时间粒度
// 根据dataExportDefaultTime重制时间粒度
let
timeGranularity
=
3600
;
let
timeGranularity
=
3600
;
...
@@ -199,8 +216,9 @@ const Detail = (props) => {
...
@@ -199,8 +216,9 @@ const Detail = (props) => {
}
else
if
(
dataExportDefaultTime
===
'week'
)
{
}
else
if
(
dataExportDefaultTime
===
'week'
)
{
timeGranularity
=
604800
;
timeGranularity
=
604800
;
}
}
data
.
forEach
(
item
=>
{
data
.
forEach
((
item
)
=>
{
item
[
'created_at'
]
=
Math
.
floor
(
item
[
'created_at'
]
/
timeGranularity
)
*
timeGranularity
;
item
[
'created_at'
]
=
Math
.
floor
(
item
[
'created_at'
]
/
timeGranularity
)
*
timeGranularity
;
});
});
updateChart
(
lineChart
,
pieChart
,
data
);
updateChart
(
lineChart
,
pieChart
,
data
);
}
else
{
}
else
{
...
@@ -214,21 +232,21 @@ const Detail = (props) => {
...
@@ -214,21 +232,21 @@ const Detail = (props) => {
};
};
const
initChart
=
async
()
=>
{
const
initChart
=
async
()
=>
{
let
lineChart
=
modelDataChart
let
lineChart
=
modelDataChart
;
if
(
!
modelDataChart
)
{
if
(
!
modelDataChart
)
{
lineChart
=
new
VChart
(
spec_line
,
{
dom
:
'model_data'
});
lineChart
=
new
VChart
(
spec_line
,
{
dom
:
'model_data'
});
setModelDataChart
(
lineChart
);
setModelDataChart
(
lineChart
);
lineChart
.
renderAsync
();
lineChart
.
renderAsync
();
}
}
let
pieChart
=
modelDataPieChart
let
pieChart
=
modelDataPieChart
;
if
(
!
modelDataPieChart
)
{
if
(
!
modelDataPieChart
)
{
pieChart
=
new
VChart
(
spec_pie
,
{
dom
:
'model_pie'
});
pieChart
=
new
VChart
(
spec_pie
,
{
dom
:
'model_pie'
});
setModelDataPieChart
(
pieChart
);
setModelDataPieChart
(
pieChart
);
pieChart
.
renderAsync
();
pieChart
.
renderAsync
();
}
}
console
.
log
(
'init vchart'
);
console
.
log
(
'init vchart'
);
await
loadQuotaData
(
lineChart
,
pieChart
)
await
loadQuotaData
(
lineChart
,
pieChart
);
}
};
const
updateChart
=
(
lineChart
,
pieChart
,
data
)
=>
{
const
updateChart
=
(
lineChart
,
pieChart
,
data
)
=>
{
if
(
isAdminUser
)
{
if
(
isAdminUser
)
{
...
@@ -243,26 +261,31 @@ const Detail = (props) => {
...
@@ -243,26 +261,31 @@ const Detail = (props) => {
consumeQuota
+=
item
.
quota
;
consumeQuota
+=
item
.
quota
;
times
+=
item
.
count
;
times
+=
item
.
count
;
// 合并model_name
// 合并model_name
let
pieItem
=
pieData
.
find
(
it
=>
it
.
type
===
item
.
model_name
);
let
pieItem
=
pieData
.
find
((
it
)
=>
it
.
type
===
item
.
model_name
);
if
(
pieItem
)
{
if
(
pieItem
)
{
pieItem
.
value
+=
item
.
count
;
pieItem
.
value
+=
item
.
count
;
}
else
{
}
else
{
pieData
.
push
({
pieData
.
push
({
"type"
:
item
.
model_name
,
type
:
item
.
model_name
,
"value"
:
item
.
count
value
:
item
.
count
,
});
});
}
}
// 合并created_at和model_name 为 lineData, created_at 数据类型是小时的时间戳
// 合并created_at和model_name 为 lineData, created_at 数据类型是小时的时间戳
// 转换日期格式
// 转换日期格式
let
createTime
=
timestamp2string1
(
item
.
created_at
,
dataExportDefaultTime
);
let
createTime
=
timestamp2string1
(
let
lineItem
=
lineData
.
find
(
it
=>
it
.
Time
===
createTime
&&
it
.
Model
===
item
.
model_name
);
item
.
created_at
,
dataExportDefaultTime
,
);
let
lineItem
=
lineData
.
find
(
(
it
)
=>
it
.
Time
===
createTime
&&
it
.
Model
===
item
.
model_name
,
);
if
(
lineItem
)
{
if
(
lineItem
)
{
lineItem
.
Usage
+=
parseFloat
(
getQuotaWithUnit
(
item
.
quota
));
lineItem
.
Usage
+=
parseFloat
(
getQuotaWithUnit
(
item
.
quota
));
}
else
{
}
else
{
lineData
.
push
({
lineData
.
push
({
"Time"
:
createTime
,
Time
:
createTime
,
"Model"
:
item
.
model_name
,
Model
:
item
.
model_name
,
"Usage"
:
parseFloat
(
getQuotaWithUnit
(
item
.
quota
))
Usage
:
parseFloat
(
getQuotaWithUnit
(
item
.
quota
)),
});
});
}
}
}
}
...
@@ -283,7 +306,7 @@ const Detail = (props) => {
...
@@ -283,7 +306,7 @@ const Detail = (props) => {
// lineChart.updateData('barData', lineData);
// lineChart.updateData('barData', lineData);
pieChart
.
reLayout
();
pieChart
.
reLayout
();
lineChart
.
reLayout
();
lineChart
.
reLayout
();
}
};
useEffect
(()
=>
{
useEffect
(()
=>
{
// setDataExportDefaultTime(localStorage.getItem('data_export_default_time'));
// setDataExportDefaultTime(localStorage.getItem('data_export_default_time'));
...
@@ -309,49 +332,86 @@ const Detail = (props) => {
...
@@ -309,49 +332,86 @@ const Detail = (props) => {
<
h3
>
数据看板
<
/h3
>
<
h3
>
数据看板
<
/h3
>
<
/Layout.Header
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
Form
ref
=
{
formRef
}
layout
=
'horizontal'
style
=
{{
marginTop
:
10
}}
>
<
Form
ref
=
{
formRef
}
layout
=
'horizontal'
style
=
{{
marginTop
:
10
}}
>
<>
<>
<
Form
.
DatePicker
field
=
"start_timestamp"
label
=
'起始时间'
style
=
{{
width
:
272
}}
<
Form
.
DatePicker
field
=
'start_timestamp'
label
=
'起始时间'
style
=
{{
width
:
272
}}
initValue
=
{
start_timestamp
}
initValue
=
{
start_timestamp
}
value
=
{
start_timestamp
}
type
=
'dateTime'
value
=
{
start_timestamp
}
type
=
'dateTime'
name
=
'start_timestamp'
name
=
'start_timestamp'
onChange
=
{
value
=>
handleInputChange
(
value
,
'start_timestamp'
)}
/
>
onChange
=
{(
value
)
=>
<
Form
.
DatePicker
field
=
"end_timestamp"
fluid
label
=
'结束时间'
style
=
{{
width
:
272
}}
handleInputChange
(
value
,
'start_timestamp'
)
}
/
>
<
Form
.
DatePicker
field
=
'end_timestamp'
fluid
label
=
'结束时间'
style
=
{{
width
:
272
}}
initValue
=
{
end_timestamp
}
initValue
=
{
end_timestamp
}
value
=
{
end_timestamp
}
type
=
'dateTime'
value
=
{
end_timestamp
}
type
=
'dateTime'
name
=
'end_timestamp'
name
=
'end_timestamp'
onChange
=
{
value
=>
handleInputChange
(
value
,
'end_timestamp'
)}
/
>
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'end_timestamp'
)}
<
Form
.
Select
field
=
"data_export_default_time"
label
=
'时间粒度'
style
=
{{
width
:
176
}}
/
>
<
Form
.
Select
field
=
'data_export_default_time'
label
=
'时间粒度'
style
=
{{
width
:
176
}}
initValue
=
{
dataExportDefaultTime
}
initValue
=
{
dataExportDefaultTime
}
placeholder
=
{
'时间粒度'
}
name
=
'data_export_default_time'
placeholder
=
{
'时间粒度'
}
optionList
=
{
name
=
'data_export_default_time'
[
optionList
=
{[
{
label
:
'小时'
,
value
:
'hour'
},
{
label
:
'小时'
,
value
:
'hour'
},
{
label
:
'天'
,
value
:
'day'
},
{
label
:
'天'
,
value
:
'day'
},
{
label
:
'周'
,
value
:
'week'
}
{
label
:
'周'
,
value
:
'week'
},
]
]}
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'data_export_default_time'
)
}
}
onChange
=
{
value
=>
handleInputChange
(
value
,
'data_export_default_time'
)}
>
><
/Form.Select
>
<
/Form.Select
>
{
isAdminUser
&&
(
{
<>
isAdminUser
&&
<>
<
Form
.
Input
<
Form
.
Input
field
=
"username"
label
=
'用户名称'
style
=
{{
width
:
176
}}
value
=
{
username
}
field
=
'username'
placeholder
=
{
'可选值'
}
name
=
'username'
label
=
'用户名称'
onChange
=
{
value
=>
handleInputChange
(
value
,
'username'
)}
/
>
style
=
{{
width
:
176
}}
value
=
{
username
}
placeholder
=
{
'可选值'
}
name
=
'username'
onChange
=
{(
value
)
=>
handleInputChange
(
value
,
'username'
)}
/
>
<
/
>
<
/
>
}
)
}
<
Form
.
Section
>
<
Form
.
Section
>
<
Button
label
=
'查询'
type
=
"primary"
htmlType
=
"submit"
className
=
"btn-margin-right"
<
Button
onClick
=
{
refresh
}
loading
=
{
loading
}
>
查询
<
/Button
>
label
=
'查询'
type
=
'primary'
htmlType
=
'submit'
className
=
'btn-margin-right'
onClick
=
{
refresh
}
loading
=
{
loading
}
>
查询
<
/Button
>
<
/Form.Section
>
<
/Form.Section
>
<
/
>
<
/
>
<
/Form
>
<
/Form
>
<
Spin
spinning
=
{
loading
}
>
<
Spin
spinning
=
{
loading
}
>
<
div
style
=
{{
height
:
500
}}
>
<
div
style
=
{{
height
:
500
}}
>
<
div
id
=
"model_pie"
style
=
{{
width
:
'100%'
,
minWidth
:
100
}}
><
/div
>
<
div
id
=
'model_pie'
style
=
{{
width
:
'100%'
,
minWidth
:
100
}}
><
/div
>
<
/div
>
<
/div
>
<
div
style
=
{{
height
:
500
}}
>
<
div
style
=
{{
height
:
500
}}
>
<
div
id
=
"model_data"
style
=
{{
width
:
'100%'
,
minWidth
:
100
}}
><
/div
>
<
div
id
=
'model_data'
style
=
{{
width
:
'100%'
,
minWidth
:
100
}}
><
/div
>
<
/div
>
<
/div
>
<
/Spin
>
<
/Spin
>
<
/Layout.Content
>
<
/Layout.Content
>
...
@@ -360,5 +420,4 @@ const Detail = (props) => {
...
@@ -360,5 +420,4 @@ const Detail = (props) => {
);
);
};
};
export
default
Detail
;
export
default
Detail
;
web/src/pages/Home/index.js
View file @
eb955ae4
...
@@ -53,8 +53,7 @@ const Home = () => {
...
@@ -53,8 +53,7 @@ const Home = () => {
},
[]);
},
[]);
return
(
return
(
<>
<>
{
{
homePageContentLoaded
&&
homePageContent
===
''
?
(
homePageContentLoaded
&&
homePageContent
===
''
?
<>
<>
<
Card
<
Card
bordered
=
{
false
}
bordered
=
{
false
}
...
@@ -66,15 +65,30 @@ const Home = () => {
...
@@ -66,15 +65,30 @@ const Home = () => {
<
Col
span
=
{
12
}
>
<
Col
span
=
{
12
}
>
<
Card
<
Card
title
=
'系统信息'
title
=
'系统信息'
headerExtraContent
=
{
<
span
headerExtraContent
=
{
style
=
{{
fontSize
:
'12px'
,
color
:
'var(--semi-color-text-1)'
}}
>
系统信息总览
<
/span>}
>
<
span
style
=
{{
fontSize
:
'12px'
,
color
:
'var(--semi-color-text-1)'
,
}}
>
系统信息总览
<
/span
>
}
>
<
p
>
名称:
{
statusState
?.
status
?.
system_name
}
<
/p
>
<
p
>
名称:
{
statusState
?.
status
?.
system_name
}
<
/p
>
<
p
>
版本:
{
statusState
?.
status
?.
version
?
statusState
?.
status
?.
version
:
'unknown'
}
<
/p
>
<
p
>
版本:
{
statusState
?.
status
?.
version
?
statusState
?.
status
?.
version
:
'unknown'
}
<
/p
>
<
p
>
<
p
>
源码:
源码:
<
a
<
a
href
=
'https://github.com/songquanpeng/one-api'
href
=
'https://github.com/songquanpeng/one-api'
target
=
'_blank'
rel
=
'noreferrer'
target
=
'_blank'
rel
=
'noreferrer'
>
>
https
:
//github.com/songquanpeng/one-api
https
:
//github.com/songquanpeng/one-api
<
/a
>
<
/a
>
...
@@ -85,44 +99,67 @@ const Home = () => {
...
@@ -85,44 +99,67 @@ const Home = () => {
<
Col
span
=
{
12
}
>
<
Col
span
=
{
12
}
>
<
Card
<
Card
title
=
'系统配置'
title
=
'系统配置'
headerExtraContent
=
{
<
span
headerExtraContent
=
{
style
=
{{
fontSize
:
'12px'
,
color
:
'var(--semi-color-text-1)'
}}
>
系统配置总览
<
/span>}
>
<
span
style
=
{{
fontSize
:
'12px'
,
color
:
'var(--semi-color-text-1)'
,
}}
>
系统配置总览
<
/span
>
}
>
<
p
>
<
p
>
邮箱验证:
邮箱验证:
{
statusState
?.
status
?.
email_verification
===
true
?
'已启用'
:
'未启用'
}
{
statusState
?.
status
?.
email_verification
===
true
?
'已启用'
:
'未启用'
}
<
/p
>
<
/p
>
<
p
>
<
p
>
GitHub
身份验证:
GitHub
身份验证:
{
statusState
?.
status
?.
github_oauth
===
true
?
'已启用'
:
'未启用'
}
{
statusState
?.
status
?.
github_oauth
===
true
?
'已启用'
:
'未启用'
}
<
/p
>
<
/p
>
<
p
>
<
p
>
微信身份验证:
微信身份验证:
{
statusState
?.
status
?.
wechat_login
===
true
?
'已启用'
:
'未启用'
}
{
statusState
?.
status
?.
wechat_login
===
true
?
'已启用'
:
'未启用'
}
<
/p
>
<
/p
>
<
p
>
<
p
>
Turnstile
用户校验:
Turnstile
用户校验:
{
statusState
?.
status
?.
turnstile_check
===
true
?
'已启用'
:
'未启用'
}
{
statusState
?.
status
?.
turnstile_check
===
true
?
'已启用'
:
'未启用'
}
<
/p
>
<
/p
>
<
p
>
<
p
>
Telegram
身份验证:
Telegram
身份验证:
{
statusState
?.
status
?.
telegram_oauth
===
true
{
statusState
?.
status
?.
telegram_oauth
===
true
?
'已启用'
:
'未启用'
}
?
'已启用'
:
'未启用'
}
<
/p
>
<
/p
>
<
/Card
>
<
/Card
>
<
/Col
>
<
/Col
>
<
/Row
>
<
/Row
>
<
/Card
>
<
/Card
>
<
/
>
<
/
>
:
<>
)
:
(
{
<>
homePageContent
.
startsWith
(
'https://'
)
?
{
homePageContent
.
startsWith
(
'https://'
)
?
(
<
iframe
src
=
{
homePageContent
}
style
=
{{
width
:
'100%'
,
height
:
'100vh'
,
border
:
'none'
}}
/>
:
<
iframe
<
div
style
=
{{
fontSize
:
'larger'
}}
dangerouslySetInnerHTML
=
{{
__html
:
homePageContent
}}
><
/div
>
src
=
{
homePageContent
}
}
style
=
{{
width
:
'100%'
,
height
:
'100vh'
,
border
:
'none'
}}
/
>
)
:
(
<
div
style
=
{{
fontSize
:
'larger'
}}
dangerouslySetInnerHTML
=
{{
__html
:
homePageContent
}}
><
/div
>
)}
<
/
>
<
/
>
}
)}
<
/
>
<
/
>
);
);
};
};
...
...
web/src/pages/Redemption/EditRedemption.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
useNavigate
,
useParams
}
from
'react-router-dom'
;
import
{
useNavigate
,
useParams
}
from
'react-router-dom'
;
import
{
API
,
downloadTextAsFile
,
isMobile
,
showError
,
showSuccess
}
from
'../../helpers'
;
import
{
API
,
downloadTextAsFile
,
isMobile
,
showError
,
showSuccess
,
}
from
'../../helpers'
;
import
{
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
AutoComplete
,
Button
,
Input
,
Modal
,
SideSheet
,
Space
,
Spin
,
Typography
}
from
'@douyinfe/semi-ui'
;
import
{
AutoComplete
,
Button
,
Input
,
Modal
,
SideSheet
,
Space
,
Spin
,
Typography
,
}
from
'@douyinfe/semi-ui'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
{
Divider
}
from
'semantic-ui-react'
;
import
{
Divider
}
from
'semantic-ui-react'
;
...
@@ -15,7 +30,7 @@ const EditRedemption = (props) => {
...
@@ -15,7 +30,7 @@ const EditRedemption = (props) => {
const
originInputs
=
{
const
originInputs
=
{
name
:
''
,
name
:
''
,
quota
:
100000
,
quota
:
100000
,
count
:
1
count
:
1
,
};
};
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
const
{
name
,
quota
,
count
}
=
inputs
;
const
{
name
,
quota
,
count
}
=
inputs
;
...
@@ -42,11 +57,9 @@ const EditRedemption = (props) => {
...
@@ -42,11 +57,9 @@ const EditRedemption = (props) => {
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
isEdit
)
{
if
(
isEdit
)
{
loadRedemption
().
then
(
loadRedemption
().
then
(()
=>
{
()
=>
{
// console.log(inputs);
// console.log(inputs);
}
});
);
}
else
{
}
else
{
setInputs
(
originInputs
);
setInputs
(
originInputs
);
}
}
...
@@ -60,10 +73,13 @@ const EditRedemption = (props) => {
...
@@ -60,10 +73,13 @@ const EditRedemption = (props) => {
localInputs
.
quota
=
parseInt
(
localInputs
.
quota
);
localInputs
.
quota
=
parseInt
(
localInputs
.
quota
);
let
res
;
let
res
;
if
(
isEdit
)
{
if
(
isEdit
)
{
res
=
await
API
.
put
(
`/api/redemption/`
,
{
...
localInputs
,
id
:
parseInt
(
props
.
editingRedemption
.
id
)
});
res
=
await
API
.
put
(
`/api/redemption/`
,
{
...
localInputs
,
id
:
parseInt
(
props
.
editingRedemption
.
id
),
});
}
else
{
}
else
{
res
=
await
API
.
post
(
`/api/redemption/`
,
{
res
=
await
API
.
post
(
`/api/redemption/`
,
{
...
localInputs
...
localInputs
,
});
});
}
}
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
...
@@ -97,7 +113,7 @@ const EditRedemption = (props) => {
...
@@ -97,7 +113,7 @@ const EditRedemption = (props) => {
),
),
onOk
:
()
=>
{
onOk
:
()
=>
{
downloadTextAsFile
(
text
,
`
${
inputs
.
name
}
.txt`
);
downloadTextAsFile
(
text
,
`
${
inputs
.
name
}
.txt`
);
}
}
,
});
});
}
}
setLoading
(
false
);
setLoading
(
false
);
...
@@ -107,15 +123,28 @@ const EditRedemption = (props) => {
...
@@ -107,15 +123,28 @@ const EditRedemption = (props) => {
<>
<>
<
SideSheet
<
SideSheet
placement
=
{
isEdit
?
'right'
:
'left'
}
placement
=
{
isEdit
?
'right'
:
'left'
}
title
=
{
<
Title
level
=
{
3
}
>
{
isEdit
?
'更新兑换码信息'
:
'创建新的兑换码'
}
<
/Title>
}
title
=
{
<
Title
level
=
{
3
}
>
{
isEdit
?
'更新兑换码信息'
:
'创建新的兑换码'
}
<
/Title
>
}
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
bodyStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
bodyStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
visible
=
{
props
.
visiable
}
visible
=
{
props
.
visiable
}
footer
=
{
footer
=
{
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
Space
>
<
Space
>
<
Button
theme
=
"solid"
size
=
{
'large'
}
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
onClick
=
{
submit
}
>
<
Button
theme
=
"solid"
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
}
}
...
@@ -126,12 +155,12 @@ const EditRedemption = (props) => {
...
@@ -126,12 +155,12 @@ const EditRedemption = (props) => {
<
Spin
spinning
=
{
loading
}
>
<
Spin
spinning
=
{
loading
}
>
<
Input
<
Input
style
=
{{
marginTop
:
20
}}
style
=
{{
marginTop
:
20
}}
label
=
"名称"
label
=
'名称'
name
=
"name"
name
=
'name'
placeholder
=
{
'请输入名称'
}
placeholder
=
{
'请输入名称'
}
onChange
=
{
value
=>
handleInputChange
(
'name'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'name'
,
value
)}
value
=
{
name
}
value
=
{
name
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
required
=
{
!
isEdit
}
required
=
{
!
isEdit
}
/
>
/
>
<
Divider
/>
<
Divider
/>
...
@@ -140,12 +169,12 @@ const EditRedemption = (props) => {
...
@@ -140,12 +169,12 @@ const EditRedemption = (props) => {
<
/div
>
<
/div
>
<
AutoComplete
<
AutoComplete
style
=
{{
marginTop
:
8
}}
style
=
{{
marginTop
:
8
}}
name
=
"quota"
name
=
'quota'
placeholder
=
{
'请输入额度'
}
placeholder
=
{
'请输入额度'
}
onChange
=
{(
value
)
=>
handleInputChange
(
'quota'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'quota'
,
value
)}
value
=
{
quota
}
value
=
{
quota
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
type
=
"number"
type
=
'number'
position
=
{
'bottom'
}
position
=
{
'bottom'
}
data
=
{[
data
=
{[
{
value
:
500000
,
label
:
'1$'
},
{
value
:
500000
,
label
:
'1$'
},
...
@@ -153,25 +182,25 @@ const EditRedemption = (props) => {
...
@@ -153,25 +182,25 @@ const EditRedemption = (props) => {
{
value
:
25000000
,
label
:
'50$'
},
{
value
:
25000000
,
label
:
'50$'
},
{
value
:
50000000
,
label
:
'100$'
},
{
value
:
50000000
,
label
:
'100$'
},
{
value
:
250000000
,
label
:
'500$'
},
{
value
:
250000000
,
label
:
'500$'
},
{
value
:
500000000
,
label
:
'1000$'
}
{
value
:
500000000
,
label
:
'1000$'
}
,
]}
]}
/
>
/
>
{
{
!
isEdit
&&
(
!
isEdit
&&
<>
<>
<
Divider
/>
<
Divider
/>
<
Typography
.
Text
>
生成数量
<
/Typography.Text
>
<
Typography
.
Text
>
生成数量
<
/Typography.Text
>
<
Input
<
Input
style
=
{{
marginTop
:
8
}}
style
=
{{
marginTop
:
8
}}
label
=
"生成数量"
label
=
'生成数量'
name
=
"count"
name
=
'count'
placeholder
=
{
'请输入生成数量'
}
placeholder
=
{
'请输入生成数量'
}
onChange
=
{
value
=>
handleInputChange
(
'count'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'count'
,
value
)}
value
=
{
count
}
value
=
{
count
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
type
=
"number"
type
=
'number'
/>
/>
<
/
>
<
/
>
}
)
}
<
/Spin
>
<
/Spin
>
<
/SideSheet
>
<
/SideSheet
>
<
/
>
<
/
>
...
...
web/src/pages/Redemption/index.js
View file @
eb955ae4
import
React
from
'react'
;
import
React
from
'react'
;
import
RedemptionsTable
from
'../../components/RedemptionsTable'
;
import
RedemptionsTable
from
'../../components/RedemptionsTable'
;
import
{
Layout
}
from
"@douyinfe/semi-ui"
;
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
const
Redemption
=
()
=>
(
const
Redemption
=
()
=>
(
<>
<>
...
@@ -9,7 +9,7 @@ const Redemption = () => (
...
@@ -9,7 +9,7 @@ const Redemption = () => (
<
h3
>
管理兑换码
<
/h3
>
<
h3
>
管理兑换码
<
/h3
>
<
/Layout.Header
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
RedemptionsTable
/>
<
RedemptionsTable
/>
<
/Layout.Content
>
<
/Layout.Content
>
<
/Layout
>
<
/Layout
>
<
/
>
<
/
>
...
...
web/src/pages/Setting/index.js
View file @
eb955ae4
import
React
from
'react'
;
import
React
from
'react'
;
import
SystemSetting
from
'../../components/SystemSetting'
;
import
SystemSetting
from
'../../components/SystemSetting'
;
import
{
isRoot
}
from
'../../helpers'
;
import
{
isRoot
}
from
'../../helpers'
;
import
OtherSetting
from
'../../components/OtherSetting'
;
import
OtherSetting
from
'../../components/OtherSetting'
;
import
PersonalSetting
from
'../../components/PersonalSetting'
;
import
PersonalSetting
from
'../../components/PersonalSetting'
;
import
OperationSetting
from
'../../components/OperationSetting'
;
import
OperationSetting
from
'../../components/OperationSetting'
;
import
{
Layout
,
TabPane
,
Tabs
}
from
"@douyinfe/semi-ui"
;
import
{
Layout
,
TabPane
,
Tabs
}
from
'@douyinfe/semi-ui'
;
const
Setting
=
()
=>
{
const
Setting
=
()
=>
{
let
panes
=
[
let
panes
=
[
{
{
tab
:
'个人设置'
,
tab
:
'个人设置'
,
content
:
<
PersonalSetting
/>
,
content
:
<
PersonalSetting
/>
,
itemKey
:
'1'
itemKey
:
'1'
,
}
},
];
];
if
(
isRoot
())
{
if
(
isRoot
())
{
panes
.
push
({
panes
.
push
({
tab
:
'运营设置'
,
tab
:
'运营设置'
,
content
:
<
OperationSetting
/>
,
content
:
<
OperationSetting
/>
,
itemKey
:
'2'
itemKey
:
'2'
,
});
});
panes
.
push
({
panes
.
push
({
tab
:
'系统设置'
,
tab
:
'系统设置'
,
content
:
<
SystemSetting
/>
,
content
:
<
SystemSetting
/>
,
itemKey
:
'3'
itemKey
:
'3'
,
});
});
panes
.
push
({
panes
.
push
({
tab
:
'其他设置'
,
tab
:
'其他设置'
,
content
:
<
OtherSetting
/>
,
content
:
<
OtherSetting
/>
,
itemKey
:
'4'
itemKey
:
'4'
,
});
});
}
}
...
@@ -37,8 +37,8 @@ const Setting = () => {
...
@@ -37,8 +37,8 @@ const Setting = () => {
<
div
>
<
div
>
<
Layout
>
<
Layout
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
Tabs
type
=
"line"
defaultActiveKey
=
"1"
>
<
Tabs
type
=
'line'
defaultActiveKey
=
'1'
>
{
panes
.
map
(
pane
=>
(
{
panes
.
map
((
pane
)
=>
(
<
TabPane
itemKey
=
{
pane
.
itemKey
}
tab
=
{
pane
.
tab
}
>
<
TabPane
itemKey
=
{
pane
.
itemKey
}
tab
=
{
pane
.
tab
}
>
{
pane
.
content
}
{
pane
.
content
}
<
/TabPane
>
<
/TabPane
>
...
...
web/src/pages/Token/EditToken.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
{
API
,
isMobile
,
showError
,
showSuccess
,
timestamp2string
}
from
'../../helpers'
;
import
{
API
,
isMobile
,
showError
,
showSuccess
,
timestamp2string
,
}
from
'../../helpers'
;
import
{
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
import
{
AutoComplete
,
AutoComplete
,
...
@@ -13,7 +19,7 @@ import {
...
@@ -13,7 +19,7 @@ import {
SideSheet
,
SideSheet
,
Space
,
Space
,
Spin
,
Spin
,
Typography
Typography
,
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
{
Divider
}
from
'semantic-ui-react'
;
import
{
Divider
}
from
'semantic-ui-react'
;
...
@@ -27,10 +33,17 @@ const EditToken = (props) => {
...
@@ -27,10 +33,17 @@ const EditToken = (props) => {
expired_time
:
-
1
,
expired_time
:
-
1
,
unlimited_quota
:
false
,
unlimited_quota
:
false
,
model_limits_enabled
:
false
,
model_limits_enabled
:
false
,
model_limits
:
[]
model_limits
:
[]
,
};
};
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
const
{
name
,
remain_quota
,
expired_time
,
unlimited_quota
,
model_limits_enabled
,
model_limits
}
=
inputs
;
const
{
name
,
remain_quota
,
expired_time
,
unlimited_quota
,
model_limits_enabled
,
model_limits
,
}
=
inputs
;
// const [visible, setVisible] = useState(false);
// const [visible, setVisible] = useState(false);
const
[
models
,
setModels
]
=
useState
({});
const
[
models
,
setModels
]
=
useState
({});
const
navigate
=
useNavigate
();
const
navigate
=
useNavigate
();
...
@@ -65,7 +78,7 @@ const EditToken = (props) => {
...
@@ -65,7 +78,7 @@ const EditToken = (props) => {
if
(
success
)
{
if
(
success
)
{
let
localModelOptions
=
data
.
map
((
model
)
=>
({
let
localModelOptions
=
data
.
map
((
model
)
=>
({
label
:
model
,
label
:
model
,
value
:
model
value
:
model
,
}));
}));
setModels
(
localModelOptions
);
setModels
(
localModelOptions
);
}
else
{
}
else
{
...
@@ -100,11 +113,9 @@ const EditToken = (props) => {
...
@@ -100,11 +113,9 @@ const EditToken = (props) => {
if
(
!
isEdit
)
{
if
(
!
isEdit
)
{
setInputs
(
originInputs
);
setInputs
(
originInputs
);
}
else
{
}
else
{
loadToken
().
then
(
loadToken
().
then
(()
=>
{
()
=>
{
// console.log(inputs);
// console.log(inputs);
}
});
);
}
}
loadModels
();
loadModels
();
},
[
isEdit
]);
},
[
isEdit
]);
...
@@ -123,10 +134,13 @@ const EditToken = (props) => {
...
@@ -123,10 +134,13 @@ const EditToken = (props) => {
// 生成一个随机的四位字母数字字符串
// 生成一个随机的四位字母数字字符串
const
generateRandomSuffix
=
()
=>
{
const
generateRandomSuffix
=
()
=>
{
const
characters
=
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
;
const
characters
=
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
;
let
result
=
''
;
let
result
=
''
;
for
(
let
i
=
0
;
i
<
6
;
i
++
)
{
for
(
let
i
=
0
;
i
<
6
;
i
++
)
{
result
+=
characters
.
charAt
(
Math
.
floor
(
Math
.
random
()
*
characters
.
length
));
result
+=
characters
.
charAt
(
Math
.
floor
(
Math
.
random
()
*
characters
.
length
),
);
}
}
return
result
;
return
result
;
};
};
...
@@ -147,7 +161,10 @@ const EditToken = (props) => {
...
@@ -147,7 +161,10 @@ const EditToken = (props) => {
localInputs
.
expired_time
=
Math
.
ceil
(
time
/
1000
);
localInputs
.
expired_time
=
Math
.
ceil
(
time
/
1000
);
}
}
localInputs
.
model_limits
=
localInputs
.
model_limits
.
join
(
','
);
localInputs
.
model_limits
=
localInputs
.
model_limits
.
join
(
','
);
let
res
=
await
API
.
put
(
`/api/token/`
,
{
...
localInputs
,
id
:
parseInt
(
props
.
editingToken
.
id
)
});
let
res
=
await
API
.
put
(
`/api/token/`
,
{
...
localInputs
,
id
:
parseInt
(
props
.
editingToken
.
id
),
});
const
{
success
,
message
}
=
res
.
data
;
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
showSuccess
(
'令牌更新成功!'
);
showSuccess
(
'令牌更新成功!'
);
...
@@ -189,7 +206,9 @@ const EditToken = (props) => {
...
@@ -189,7 +206,9 @@ const EditToken = (props) => {
}
}
if
(
successCount
>
0
)
{
if
(
successCount
>
0
)
{
showSuccess
(
`
${
successCount
}
个令牌创建成功,请在列表页面点击复制获取令牌!`
);
showSuccess
(
`
${
successCount
}
个令牌创建成功,请在列表页面点击复制获取令牌!`
,
);
props
.
refresh
();
props
.
refresh
();
props
.
handleClose
();
props
.
handleClose
();
}
}
...
@@ -199,20 +218,30 @@ const EditToken = (props) => {
...
@@ -199,20 +218,30 @@ const EditToken = (props) => {
setTokenCount
(
1
);
// 重置数量为默认值
setTokenCount
(
1
);
// 重置数量为默认值
};
};
return
(
return
(
<>
<>
<
SideSheet
<
SideSheet
placement
=
{
isEdit
?
'right'
:
'left'
}
placement
=
{
isEdit
?
'right'
:
'left'
}
title
=
{
<
Title
level
=
{
3
}
>
{
isEdit
?
'更新令牌信息'
:
'创建新的令牌'
}
<
/Title>
}
title
=
{
<
Title
level
=
{
3
}
>
{
isEdit
?
'更新令牌信息'
:
'创建新的令牌'
}
<
/Title
>
}
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
bodyStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
bodyStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
visible
=
{
props
.
visiable
}
visible
=
{
props
.
visiable
}
footer
=
{
footer
=
{
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
Space
>
<
Space
>
<
Button
theme
=
"solid"
size
=
{
'large'
}
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
onClick
=
{
submit
}
>
<
Button
theme
=
"solid"
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
}
}
...
@@ -223,55 +252,79 @@ const EditToken = (props) => {
...
@@ -223,55 +252,79 @@ const EditToken = (props) => {
<
Spin
spinning
=
{
loading
}
>
<
Spin
spinning
=
{
loading
}
>
<
Input
<
Input
style
=
{{
marginTop
:
20
}}
style
=
{{
marginTop
:
20
}}
label
=
"名称"
label
=
'名称'
name
=
"name"
name
=
'name'
placeholder
=
{
'请输入名称'
}
placeholder
=
{
'请输入名称'
}
onChange
=
{(
value
)
=>
handleInputChange
(
'name'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'name'
,
value
)}
value
=
{
name
}
value
=
{
name
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
required
=
{
!
isEdit
}
required
=
{
!
isEdit
}
/
>
/
>
<
Divider
/>
<
Divider
/>
<
DatePicker
<
DatePicker
label
=
"过期时间"
label
=
'过期时间'
name
=
"expired_time"
name
=
'expired_time'
placeholder
=
{
'请选择过期时间'
}
placeholder
=
{
'请选择过期时间'
}
onChange
=
{(
value
)
=>
handleInputChange
(
'expired_time'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'expired_time'
,
value
)}
value
=
{
expired_time
}
value
=
{
expired_time
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
type
=
"dateTime"
type
=
'dateTime'
/>
/>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Space
>
<
Space
>
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
setExpiredTime
(
0
,
0
,
0
,
0
);
setExpiredTime
(
0
,
0
,
0
,
0
);
}}
>
永不过期
<
/Button
>
}}
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
>
永不过期
<
/Button
>
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
setExpiredTime
(
0
,
0
,
1
,
0
);
setExpiredTime
(
0
,
0
,
1
,
0
);
}}
>
一小时
<
/Button
>
}}
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
>
一小时
<
/Button
>
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
setExpiredTime
(
1
,
0
,
0
,
0
);
setExpiredTime
(
1
,
0
,
0
,
0
);
}}
>
一个月
<
/Button
>
}}
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
>
一个月
<
/Button
>
<
Button
type
=
{
'tertiary'
}
onClick
=
{()
=>
{
setExpiredTime
(
0
,
1
,
0
,
0
);
setExpiredTime
(
0
,
1
,
0
,
0
);
}}
>
一天
<
/Button
>
}}
>
一天
<
/Button
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
<
Divider
/>
<
Divider
/>
<
Banner
type
=
{
'warning'
}
<
Banner
description
=
{
'注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。'
}
><
/Banner
>
type
=
{
'warning'
}
description
=
{
'注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。'
}
><
/Banner
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
{
`额度
${
renderQuotaWithPrompt
(
remain_quota
)}
`
}
<
/Typography.Text
>
<
Typography
.
Text
>
{
`额度
${
renderQuotaWithPrompt
(
remain_quota
)}
`
}
<
/Typography.Text
>
<
/div
>
<
/div
>
<
AutoComplete
<
AutoComplete
style
=
{{
marginTop
:
8
}}
style
=
{{
marginTop
:
8
}}
name
=
"remain_quota"
name
=
'remain_quota'
placeholder
=
{
'请输入额度'
}
placeholder
=
{
'请输入额度'
}
onChange
=
{(
value
)
=>
handleInputChange
(
'remain_quota'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'remain_quota'
,
value
)}
value
=
{
remain_quota
}
value
=
{
remain_quota
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
type
=
"number"
type
=
'number'
// position={'top'}
// position={'top'}
data
=
{[
data
=
{[
{
value
:
500000
,
label
:
'1$'
},
{
value
:
500000
,
label
:
'1$'
},
...
@@ -279,7 +332,7 @@ const EditToken = (props) => {
...
@@ -279,7 +332,7 @@ const EditToken = (props) => {
{
value
:
25000000
,
label
:
'50$'
},
{
value
:
25000000
,
label
:
'50$'
},
{
value
:
50000000
,
label
:
'100$'
},
{
value
:
50000000
,
label
:
'100$'
},
{
value
:
250000000
,
label
:
'500$'
},
{
value
:
250000000
,
label
:
'500$'
},
{
value
:
500000000
,
label
:
'1000$'
}
{
value
:
500000000
,
label
:
'1000$'
}
,
]}
]}
disabled
=
{
unlimited_quota
}
disabled
=
{
unlimited_quota
}
/
>
/
>
...
@@ -291,18 +344,18 @@ const EditToken = (props) => {
...
@@ -291,18 +344,18 @@ const EditToken = (props) => {
<
/div
>
<
/div
>
<
AutoComplete
<
AutoComplete
style
=
{{
marginTop
:
8
}}
style
=
{{
marginTop
:
8
}}
label
=
"数量"
label
=
'数量'
placeholder
=
{
'请选择或输入创建令牌的数量'
}
placeholder
=
{
'请选择或输入创建令牌的数量'
}
onChange
=
{(
value
)
=>
handleTokenCountChange
(
value
)}
onChange
=
{(
value
)
=>
handleTokenCountChange
(
value
)}
onSelect
=
{(
value
)
=>
handleTokenCountChange
(
value
)}
onSelect
=
{(
value
)
=>
handleTokenCountChange
(
value
)}
value
=
{
tokenCount
.
toString
()}
value
=
{
tokenCount
.
toString
()}
autoComplete
=
"off"
autoComplete
=
'off'
type
=
"number"
type
=
'number'
data
=
{[
data
=
{[
{
value
:
10
,
label
:
'10个'
},
{
value
:
10
,
label
:
'10个'
},
{
value
:
20
,
label
:
'20个'
},
{
value
:
20
,
label
:
'20个'
},
{
value
:
30
,
label
:
'30个'
},
{
value
:
30
,
label
:
'30个'
},
{
value
:
100
,
label
:
'100个'
}
{
value
:
100
,
label
:
'100个'
}
,
]}
]}
disabled
=
{
unlimited_quota
}
disabled
=
{
unlimited_quota
}
/
>
/
>
...
@@ -310,35 +363,44 @@ const EditToken = (props) => {
...
@@ -310,35 +363,44 @@ const EditToken = (props) => {
)}
)}
<
div
>
<
div
>
<
Button
style
=
{{
marginTop
:
8
}}
type
=
{
'warning'
}
onClick
=
{()
=>
{
<
Button
style
=
{{
marginTop
:
8
}}
type
=
{
'warning'
}
onClick
=
{()
=>
{
setUnlimitedQuota
();
setUnlimitedQuota
();
}}
>
{
unlimited_quota
?
'取消无限额度'
:
'设为无限额度'
}
<
/Button
>
}}
>
{
unlimited_quota
?
'取消无限额度'
:
'设为无限额度'
}
<
/Button
>
<
/div
>
<
/div
>
<
Divider
/>
<
Divider
/>
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
Space
>
<
Space
>
<
Checkbox
<
Checkbox
name
=
"model_limits_enabled"
name
=
'model_limits_enabled'
checked
=
{
model_limits_enabled
}
checked
=
{
model_limits_enabled
}
onChange
=
{(
e
)
=>
handleInputChange
(
'model_limits_enabled'
,
e
.
target
.
checked
)}
onChange
=
{(
e
)
=>
>
handleInputChange
(
'model_limits_enabled'
,
e
.
target
.
checked
)
<
/Checkbox
>
}
<
Typography
.
Text
>
启用模型限制(非必要,不建议启用)
<
/Typography.Text
>
><
/Checkbox
>
<
Typography
.
Text
>
启用模型限制(非必要,不建议启用)
<
/Typography.Text
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
<
Select
<
Select
style
=
{{
marginTop
:
8
}}
style
=
{{
marginTop
:
8
}}
placeholder
=
{
'请选择该渠道所支持的模型'
}
placeholder
=
{
'请选择该渠道所支持的模型'
}
name
=
"models"
name
=
'models'
required
required
multiple
multiple
selection
selection
onChange
=
{
value
=>
{
onChange
=
{
(
value
)
=>
{
handleInputChange
(
'model_limits'
,
value
);
handleInputChange
(
'model_limits'
,
value
);
}}
}}
value
=
{
inputs
.
model_limits
}
value
=
{
inputs
.
model_limits
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
optionList
=
{
models
}
optionList
=
{
models
}
disabled
=
{
!
model_limits_enabled
}
disabled
=
{
!
model_limits_enabled
}
/
>
/
>
...
...
web/src/pages/Token/index.js
View file @
eb955ae4
import
React
from
'react'
;
import
React
from
'react'
;
import
TokensTable
from
'../../components/TokensTable'
;
import
TokensTable
from
'../../components/TokensTable'
;
import
{
Layout
}
from
"@douyinfe/semi-ui"
;
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
const
Token
=
()
=>
(
const
Token
=
()
=>
(
<>
<>
<
Layout
>
<
Layout
>
...
@@ -8,7 +8,7 @@ const Token = () => (
...
@@ -8,7 +8,7 @@ const Token = () => (
<
h3
>
我的令牌
<
/h3
>
<
h3
>
我的令牌
<
/h3
>
<
/Layout.Header
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
TokensTable
/>
<
TokensTable
/>
<
/Layout.Content
>
<
/Layout.Content
>
<
/Layout
>
<
/Layout
>
<
/
>
<
/
>
...
...
web/src/pages/TopUp/index.js
View file @
eb955ae4
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
API
,
isMobile
,
showError
,
showInfo
,
showSuccess
}
from
'../../helpers'
;
import
{
API
,
isMobile
,
showError
,
showInfo
,
showSuccess
}
from
'../../helpers'
;
import
{
renderNumber
,
renderQuota
}
from
'../../helpers/render'
;
import
{
renderNumber
,
renderQuota
}
from
'../../helpers/render'
;
import
{
Col
,
Layout
,
Row
,
Typography
,
Card
,
Button
,
Form
,
Divider
,
Space
,
Modal
}
from
"@douyinfe/semi-ui"
;
import
{
import
Title
from
"@douyinfe/semi-ui/lib/es/typography/title"
;
Col
,
Layout
,
Row
,
Typography
,
Card
,
Button
,
Form
,
Divider
,
Space
,
Modal
,
}
from
'@douyinfe/semi-ui'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
Text
from
'@douyinfe/semi-ui/lib/es/typography/text'
;
import
Text
from
'@douyinfe/semi-ui/lib/es/typography/text'
;
import
{
Link
}
from
'react-router-dom'
;
import
{
Link
}
from
'react-router-dom'
;
...
@@ -22,18 +33,22 @@ const TopUp = () => {
...
@@ -22,18 +33,22 @@ const TopUp = () => {
const
topUp
=
async
()
=>
{
const
topUp
=
async
()
=>
{
if
(
redemptionCode
===
''
)
{
if
(
redemptionCode
===
''
)
{
showInfo
(
'请输入兑换码!'
)
showInfo
(
'请输入兑换码!'
);
return
;
return
;
}
}
setIsSubmitting
(
true
);
setIsSubmitting
(
true
);
try
{
try
{
const
res
=
await
API
.
post
(
'/api/user/topup'
,
{
const
res
=
await
API
.
post
(
'/api/user/topup'
,
{
key
:
redemptionCode
key
:
redemptionCode
,
});
});
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
showSuccess
(
'兑换成功!'
);
showSuccess
(
'兑换成功!'
);
Modal
.
success
({
title
:
'兑换成功!'
,
content
:
'成功兑换额度:'
+
renderQuota
(
data
),
centered
:
true
});
Modal
.
success
({
title
:
'兑换成功!'
,
content
:
'成功兑换额度:'
+
renderQuota
(
data
),
centered
:
true
,
});
setUserQuota
((
quota
)
=>
{
setUserQuota
((
quota
)
=>
{
return
quota
+
data
;
return
quota
+
data
;
});
});
...
@@ -68,9 +83,9 @@ const TopUp = () => {
...
@@ -68,9 +83,9 @@ const TopUp = () => {
showInfo
(
'充值数量不能小于'
+
minTopUp
);
showInfo
(
'充值数量不能小于'
+
minTopUp
);
return
;
return
;
}
}
setPayWay
(
payment
)
setPayWay
(
payment
);
setOpen
(
true
);
setOpen
(
true
);
}
};
const
onlineTopUp
=
async
()
=>
{
const
onlineTopUp
=
async
()
=>
{
if
(
amount
===
0
)
{
if
(
amount
===
0
)
{
...
@@ -85,33 +100,34 @@ const TopUp = () => {
...
@@ -85,33 +100,34 @@ const TopUp = () => {
const
res
=
await
API
.
post
(
'/api/user/pay'
,
{
const
res
=
await
API
.
post
(
'/api/user/pay'
,
{
amount
:
parseInt
(
topUpCount
),
amount
:
parseInt
(
topUpCount
),
top_up_code
:
topUpCode
,
top_up_code
:
topUpCode
,
payment_method
:
payWay
payment_method
:
payWay
,
});
});
if
(
res
!==
undefined
)
{
if
(
res
!==
undefined
)
{
const
{
message
,
data
}
=
res
.
data
;
const
{
message
,
data
}
=
res
.
data
;
// showInfo(message);
// showInfo(message);
if
(
message
===
'success'
)
{
if
(
message
===
'success'
)
{
let
params
=
data
;
let
params
=
data
let
url
=
res
.
data
.
url
;
let
url
=
res
.
data
.
url
let
form
=
document
.
createElement
(
'form'
);
let
form
=
document
.
createElement
(
'form'
)
form
.
action
=
url
;
form
.
action
=
url
form
.
method
=
'POST'
;
form
.
method
=
'POST'
// 判断是否为safari浏览器
// 判断是否为safari浏览器
let
isSafari
=
navigator
.
userAgent
.
indexOf
(
"Safari"
)
>
-
1
&&
navigator
.
userAgent
.
indexOf
(
"Chrome"
)
<
1
;
let
isSafari
=
navigator
.
userAgent
.
indexOf
(
'Safari'
)
>
-
1
&&
navigator
.
userAgent
.
indexOf
(
'Chrome'
)
<
1
;
if
(
!
isSafari
)
{
if
(
!
isSafari
)
{
form
.
target
=
'_blank'
form
.
target
=
'_blank'
;
}
}
for
(
let
key
in
params
)
{
for
(
let
key
in
params
)
{
let
input
=
document
.
createElement
(
'input'
)
let
input
=
document
.
createElement
(
'input'
);
input
.
type
=
'hidden'
input
.
type
=
'hidden'
;
input
.
name
=
key
input
.
name
=
key
;
input
.
value
=
params
[
key
]
input
.
value
=
params
[
key
];
form
.
appendChild
(
input
)
form
.
appendChild
(
input
);
}
}
document
.
body
.
appendChild
(
form
)
document
.
body
.
appendChild
(
form
);
form
.
submit
()
form
.
submit
();
document
.
body
.
removeChild
(
form
)
document
.
body
.
removeChild
(
form
);
}
else
{
}
else
{
showError
(
data
);
showError
(
data
);
// setTopUpCount(parseInt(res.data.count));
// setTopUpCount(parseInt(res.data.count));
...
@@ -124,17 +140,17 @@ const TopUp = () => {
...
@@ -124,17 +140,17 @@ const TopUp = () => {
console
.
log
(
err
);
console
.
log
(
err
);
}
finally
{
}
finally
{
}
}
}
};
const
getUserQuota
=
async
()
=>
{
const
getUserQuota
=
async
()
=>
{
let
res
=
await
API
.
get
(
`/api/user/self`
);
let
res
=
await
API
.
get
(
`/api/user/self`
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
setUserQuota
(
data
.
quota
);
setUserQuota
(
data
.
quota
);
}
else
{
}
else
{
showError
(
message
);
showError
(
message
);
}
}
}
};
useEffect
(()
=>
{
useEffect
(()
=>
{
let
status
=
localStorage
.
getItem
(
'status'
);
let
status
=
localStorage
.
getItem
(
'status'
);
...
@@ -156,7 +172,7 @@ const TopUp = () => {
...
@@ -156,7 +172,7 @@ const TopUp = () => {
const
renderAmount
=
()
=>
{
const
renderAmount
=
()
=>
{
// console.log(amount);
// console.log(amount);
return
amount
+
'元'
;
return
amount
+
'元'
;
}
};
const
getAmount
=
async
(
value
)
=>
{
const
getAmount
=
async
(
value
)
=>
{
if
(
value
===
undefined
)
{
if
(
value
===
undefined
)
{
...
@@ -165,10 +181,10 @@ const TopUp = () => {
...
@@ -165,10 +181,10 @@ const TopUp = () => {
try
{
try
{
const
res
=
await
API
.
post
(
'/api/user/amount'
,
{
const
res
=
await
API
.
post
(
'/api/user/amount'
,
{
amount
:
parseFloat
(
value
),
amount
:
parseFloat
(
value
),
top_up_code
:
topUpCode
top_up_code
:
topUpCode
,
});
});
if
(
res
!==
undefined
)
{
if
(
res
!==
undefined
)
{
const
{
message
,
data
}
=
res
.
data
;
const
{
message
,
data
}
=
res
.
data
;
// showInfo(message);
// showInfo(message);
if
(
message
===
'success'
)
{
if
(
message
===
'success'
)
{
setAmount
(
parseFloat
(
data
));
setAmount
(
parseFloat
(
data
));
...
@@ -184,11 +200,11 @@ const TopUp = () => {
...
@@ -184,11 +200,11 @@ const TopUp = () => {
console
.
log
(
err
);
console
.
log
(
err
);
}
finally
{
}
finally
{
}
}
}
};
const
handleCancel
=
()
=>
{
const
handleCancel
=
()
=>
{
setOpen
(
false
);
setOpen
(
false
);
}
};
return
(
return
(
<
div
>
<
div
>
...
@@ -198,7 +214,7 @@ const TopUp = () => {
...
@@ -198,7 +214,7 @@ const TopUp = () => {
<
/Layout.Header
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
Modal
<
Modal
title
=
"确定要充值吗"
title
=
'确定要充值吗'
visible
=
{
open
}
visible
=
{
open
}
onOk
=
{
onlineTopUp
}
onOk
=
{
onlineTopUp
}
onCancel
=
{
handleCancel
}
onCancel
=
{
handleCancel
}
...
@@ -210,15 +226,15 @@ const TopUp = () => {
...
@@ -210,15 +226,15 @@ const TopUp = () => {
<
p
>
实付金额:
{
renderAmount
()}
<
/p
>
<
p
>
实付金额:
{
renderAmount
()}
<
/p
>
<
p
>
是否确认充值?
<
/p
>
<
p
>
是否确认充值?
<
/p
>
<
/Modal
>
<
/Modal
>
<
div
style
=
{{
marginTop
:
20
,
display
:
'flex'
,
justifyContent
:
'center'
}}
>
<
div
<
Card
style
=
{{
marginTop
:
20
,
display
:
'flex'
,
justifyContent
:
'center'
}}
style
=
{{
width
:
'500px'
,
padding
:
'20px'
}}
>
>
<
Title
level
=
{
3
}
style
=
{{
textAlign
:
'center'
}}
>
余额
{
renderQuota
(
userQuota
)}
<
/Title
>
<
Card
style
=
{{
width
:
'500px'
,
padding
:
'20px'
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Title
level
=
{
3
}
style
=
{{
textAlign
:
'center'
}}
>
<
Divider
>
余额
{
renderQuota
(
userQuota
)}
兑换余额
<
/Title
>
<
/Divider
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Divider
>
兑换余额
<
/Divider
>
<
Form
>
<
Form
>
<
Form
.
Input
<
Form
.
Input
field
=
{
'redemptionCode'
}
field
=
{
'redemptionCode'
}
...
@@ -231,23 +247,28 @@ const TopUp = () => {
...
@@ -231,23 +247,28 @@ const TopUp = () => {
}}
}}
/
>
/
>
<
Space
>
<
Space
>
{
{
topUpLink
?
(
topUpLink
?
<
Button
<
Button
type
=
{
'primary'
}
theme
=
{
'solid'
}
onClick
=
{
openTopUpLink
}
>
type
=
{
'primary'
}
theme
=
{
'solid'
}
onClick
=
{
openTopUpLink
}
>
获取兑换码
获取兑换码
<
/Button> : nul
l
<
/Button
>
}
)
:
null
}
<
Button
type
=
{
"warning"
}
theme
=
{
'solid'
}
onClick
=
{
topUp
}
<
Button
disabled
=
{
isSubmitting
}
>
type
=
{
'warning'
}
theme
=
{
'solid'
}
onClick
=
{
topUp
}
disabled
=
{
isSubmitting
}
>
{
isSubmitting
?
'兑换中...'
:
'兑换'
}
{
isSubmitting
?
'兑换中...'
:
'兑换'
}
<
/Button
>
<
/Button
>
<
/Space
>
<
/Space
>
<
/Form
>
<
/Form
>
<
/div
>
<
/div
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Divider
>
<
Divider
>
在线充值
<
/Divider
>
在线充值
<
/Divider
>
<
Form
>
<
Form
>
<
Form
.
Input
<
Form
.
Input
disabled
=
{
!
enableOnlineTopUp
}
disabled
=
{
!
enableOnlineTopUp
}
...
@@ -273,20 +294,25 @@ const TopUp = () => {
...
@@ -273,20 +294,25 @@ const TopUp = () => {
}}
}}
/
>
/
>
<
Space
>
<
Space
>
<
Button
type
=
{
'primary'
}
theme
=
{
'solid'
}
onClick
=
{
<
Button
async
()
=>
{
type
=
{
'primary'
}
preTopUp
(
'zfb'
)
theme
=
{
'solid'
}
}
onClick
=
{
async
()
=>
{
}
>
preTopUp
(
'zfb'
);
}}
>
支付宝
支付宝
<
/Button
>
<
/Button
>
<
Button
style
=
{{
backgroundColor
:
'rgba(var(--semi-green-5), 1)'
}}
<
Button
style
=
{{
backgroundColor
:
'rgba(var(--semi-green-5), 1)'
,
}}
type
=
{
'primary'
}
type
=
{
'primary'
}
theme
=
{
'solid'
}
onClick
=
{
theme
=
{
'solid'
}
async
()
=>
{
onClick
=
{
async
()
=>
{
preTopUp
(
'wx'
)
preTopUp
(
'wx'
);
}
}
}
}
>
>
微信
微信
<
/Button
>
<
/Button
>
<
/Space
>
<
/Space
>
...
@@ -303,11 +329,9 @@ const TopUp = () => {
...
@@ -303,11 +329,9 @@ const TopUp = () => {
{
/*</div>*/
}
{
/*</div>*/
}
<
/Card
>
<
/Card
>
<
/div
>
<
/div
>
<
/Layout.Content
>
<
/Layout.Content
>
<
/Layout
>
<
/Layout
>
<
/div
>
<
/div
>
);
);
};
};
...
...
web/src/pages/User/AddUser.js
View file @
eb955ae4
...
@@ -7,7 +7,7 @@ const AddUser = (props) => {
...
@@ -7,7 +7,7 @@ const AddUser = (props) => {
const
originInputs
=
{
const
originInputs
=
{
username
:
''
,
username
:
''
,
display_name
:
''
,
display_name
:
''
,
password
:
''
password
:
''
,
};
};
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
...
@@ -48,8 +48,17 @@ const AddUser = (props) => {
...
@@ -48,8 +48,17 @@ const AddUser = (props) => {
footer
=
{
footer
=
{
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
Space
>
<
Space
>
<
Button
theme
=
"solid"
size
=
{
'large'
}
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
onClick
=
{
submit
}
>
<
Button
theme
=
"solid"
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
}
}
...
@@ -60,34 +69,34 @@ const AddUser = (props) => {
...
@@ -60,34 +69,34 @@ const AddUser = (props) => {
<
Spin
spinning
=
{
loading
}
>
<
Spin
spinning
=
{
loading
}
>
<
Input
<
Input
style
=
{{
marginTop
:
20
}}
style
=
{{
marginTop
:
20
}}
label
=
"用户名"
label
=
'用户名'
name
=
"username"
name
=
'username'
addonBefore
=
{
'用户名'
}
addonBefore
=
{
'用户名'
}
placeholder
=
{
'请输入用户名'
}
placeholder
=
{
'请输入用户名'
}
onChange
=
{
value
=>
handleInputChange
(
'username'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'username'
,
value
)}
value
=
{
username
}
value
=
{
username
}
autoComplete
=
"off"
autoComplete
=
'off'
/>
/>
<
Input
<
Input
style
=
{{
marginTop
:
20
}}
style
=
{{
marginTop
:
20
}}
addonBefore
=
{
'显示名'
}
addonBefore
=
{
'显示名'
}
label
=
"显示名称"
label
=
'显示名称'
name
=
"display_name"
name
=
'display_name'
autoComplete
=
"off"
autoComplete
=
'off'
placeholder
=
{
'请输入显示名称'
}
placeholder
=
{
'请输入显示名称'
}
onChange
=
{
value
=>
handleInputChange
(
'display_name'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'display_name'
,
value
)}
value
=
{
display_name
}
value
=
{
display_name
}
/
>
/
>
<
Input
<
Input
style
=
{{
marginTop
:
20
}}
style
=
{{
marginTop
:
20
}}
label
=
"密 码"
label
=
'密 码'
name
=
"password"
name
=
'password'
type
=
{
'password'
}
type
=
{
'password'
}
addonBefore
=
{
'密码'
}
addonBefore
=
{
'密码'
}
placeholder
=
{
'请输入密码'
}
placeholder
=
{
'请输入密码'
}
onChange
=
{
value
=>
handleInputChange
(
'password'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'password'
,
value
)}
value
=
{
password
}
value
=
{
password
}
autoComplete
=
"off"
autoComplete
=
'off'
/>
/>
<
/Spin
>
<
/Spin
>
<
/SideSheet
>
<
/SideSheet
>
...
...
web/src/pages/User/EditUser.js
View file @
eb955ae4
...
@@ -3,7 +3,16 @@ import { useNavigate } from 'react-router-dom';
...
@@ -3,7 +3,16 @@ import { useNavigate } from 'react-router-dom';
import
{
API
,
isMobile
,
showError
,
showSuccess
}
from
'../../helpers'
;
import
{
API
,
isMobile
,
showError
,
showSuccess
}
from
'../../helpers'
;
import
{
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
{
Button
,
Divider
,
Input
,
Select
,
SideSheet
,
Space
,
Spin
,
Typography
}
from
'@douyinfe/semi-ui'
;
import
{
Button
,
Divider
,
Input
,
Select
,
SideSheet
,
Space
,
Spin
,
Typography
,
}
from
'@douyinfe/semi-ui'
;
const
EditUser
=
(
props
)
=>
{
const
EditUser
=
(
props
)
=>
{
const
userId
=
props
.
editingUser
.
id
;
const
userId
=
props
.
editingUser
.
id
;
...
@@ -16,21 +25,32 @@ const EditUser = (props) => {
...
@@ -16,21 +25,32 @@ const EditUser = (props) => {
wechat_id
:
''
,
wechat_id
:
''
,
email
:
''
,
email
:
''
,
quota
:
0
,
quota
:
0
,
group
:
'default'
group
:
'default'
,
});
});
const
[
groupOptions
,
setGroupOptions
]
=
useState
([]);
const
[
groupOptions
,
setGroupOptions
]
=
useState
([]);
const
{
username
,
display_name
,
password
,
github_id
,
wechat_id
,
telegram_id
,
email
,
quota
,
group
}
=
const
{
inputs
;
username
,
display_name
,
password
,
github_id
,
wechat_id
,
telegram_id
,
email
,
quota
,
group
,
}
=
inputs
;
const
handleInputChange
=
(
name
,
value
)
=>
{
const
handleInputChange
=
(
name
,
value
)
=>
{
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
};
};
const
fetchGroups
=
async
()
=>
{
const
fetchGroups
=
async
()
=>
{
try
{
try
{
let
res
=
await
API
.
get
(
`/api/group/`
);
let
res
=
await
API
.
get
(
`/api/group/`
);
setGroupOptions
(
res
.
data
.
data
.
map
((
group
)
=>
({
setGroupOptions
(
res
.
data
.
data
.
map
((
group
)
=>
({
label
:
group
,
label
:
group
,
value
:
group
value
:
group
,
})));
})),
);
}
catch
(
error
)
{
}
catch
(
error
)
{
showError
(
error
.
message
);
showError
(
error
.
message
);
}
}
...
@@ -98,8 +118,17 @@ const EditUser = (props) => {
...
@@ -98,8 +118,17 @@ const EditUser = (props) => {
footer
=
{
footer
=
{
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
Space
>
<
Space
>
<
Button
theme
=
"solid"
size
=
{
'large'
}
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
onClick
=
{
submit
}
>
<
Button
theme
=
"solid"
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/Space
>
<
/Space
>
<
/div
>
<
/div
>
}
}
...
@@ -112,103 +141,103 @@ const EditUser = (props) => {
...
@@ -112,103 +141,103 @@ const EditUser = (props) => {
<
Typography
.
Text
>
用户名
<
/Typography.Text
>
<
Typography
.
Text
>
用户名
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
label
=
"用户名"
label
=
'用户名'
name
=
"username"
name
=
'username'
placeholder
=
{
'请输入新的用户名'
}
placeholder
=
{
'请输入新的用户名'
}
onChange
=
{
value
=>
handleInputChange
(
'username'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'username'
,
value
)}
value
=
{
username
}
value
=
{
username
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
/>
/>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
密码
<
/Typography.Text
>
<
Typography
.
Text
>
密码
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
label
=
"密码"
label
=
'密码'
name
=
"password"
name
=
'password'
type
=
{
'password'
}
type
=
{
'password'
}
placeholder
=
{
'请输入新的密码,最短 8 位'
}
placeholder
=
{
'请输入新的密码,最短 8 位'
}
onChange
=
{
value
=>
handleInputChange
(
'password'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'password'
,
value
)}
value
=
{
password
}
value
=
{
password
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
/>
/>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
显示名称
<
/Typography.Text
>
<
Typography
.
Text
>
显示名称
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
label
=
"显示名称"
label
=
'显示名称'
name
=
"display_name"
name
=
'display_name'
placeholder
=
{
'请输入新的显示名称'
}
placeholder
=
{
'请输入新的显示名称'
}
onChange
=
{
value
=>
handleInputChange
(
'display_name'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'display_name'
,
value
)}
value
=
{
display_name
}
value
=
{
display_name
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
/>
/>
{
{
userId
&&
(
userId
&&
<>
<>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
分组
<
/Typography.Text
>
<
Typography
.
Text
>
分组
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Select
<
Select
placeholder
=
{
'请选择分组'
}
placeholder
=
{
'请选择分组'
}
name
=
"group"
name
=
'group'
fluid
fluid
search
search
selection
selection
allowAdditions
allowAdditions
additionLabel
=
{
'请在系统设置页面编辑分组倍率以添加新的分组:'
}
additionLabel
=
{
'请在系统设置页面编辑分组倍率以添加新的分组:'
}
onChange
=
{
value
=>
handleInputChange
(
'group'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'group'
,
value
)}
value
=
{
inputs
.
group
}
value
=
{
inputs
.
group
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
optionList
=
{
groupOptions
}
optionList
=
{
groupOptions
}
/
>
/
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
{
`剩余额度
${
renderQuotaWithPrompt
(
quota
)}
`
}
<
/Typography.Text
>
<
Typography
.
Text
>
{
`剩余额度
${
renderQuotaWithPrompt
(
quota
)}
`
}
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
name
=
"quota"
name
=
'quota'
placeholder
=
{
'请输入新的剩余额度'
}
placeholder
=
{
'请输入新的剩余额度'
}
onChange
=
{
value
=>
handleInputChange
(
'quota'
,
value
)}
onChange
=
{
(
value
)
=>
handleInputChange
(
'quota'
,
value
)}
value
=
{
quota
}
value
=
{
quota
}
type
=
{
'number'
}
type
=
{
'number'
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
/>
/>
<
/
>
<
/
>
}
)
}
<
Divider
style
=
{{
marginTop
:
20
}}
>
以下信息不可修改
<
/Divider
>
<
Divider
style
=
{{
marginTop
:
20
}}
>
以下信息不可修改
<
/Divider
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
已绑定的
GitHub
账户
<
/Typography.Text
>
<
Typography
.
Text
>
已绑定的
GitHub
账户
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
name
=
"github_id"
name
=
'github_id'
value
=
{
github_id
}
value
=
{
github_id
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
placeholder
=
"此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改"
placeholder
=
'此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readonly
readonly
/>
/>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
已绑定的微信账户
<
/Typography.Text
>
<
Typography
.
Text
>
已绑定的微信账户
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
name
=
"wechat_id"
name
=
'wechat_id'
value
=
{
wechat_id
}
value
=
{
wechat_id
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
placeholder
=
"此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改"
placeholder
=
'此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readonly
readonly
/>
/>
<
Input
<
Input
name
=
"telegram_id"
name
=
'telegram_id'
value
=
{
telegram_id
}
value
=
{
telegram_id
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
placeholder
=
"此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改"
placeholder
=
'此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readonly
readonly
/>
/>
<
div
style
=
{{
marginTop
:
20
}}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
已绑定的邮箱账户
<
/Typography.Text
>
<
Typography
.
Text
>
已绑定的邮箱账户
<
/Typography.Text
>
<
/div
>
<
/div
>
<
Input
<
Input
name
=
"email"
name
=
'email'
value
=
{
email
}
value
=
{
email
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
placeholder
=
"此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改"
placeholder
=
'此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
readonly
readonly
/>
/>
<
/Spin
>
<
/Spin
>
...
...
web/src/pages/User/index.js
View file @
eb955ae4
import
React
from
'react'
;
import
React
from
'react'
;
import
UsersTable
from
'../../components/UsersTable'
;
import
UsersTable
from
'../../components/UsersTable'
;
import
{
Layout
}
from
"@douyinfe/semi-ui"
;
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
const
User
=
()
=>
(
const
User
=
()
=>
(
<>
<>
...
@@ -9,7 +9,7 @@ const User = () => (
...
@@ -9,7 +9,7 @@ const User = () => (
<
h3
>
管理用户
<
/h3
>
<
h3
>
管理用户
<
/h3
>
<
/Layout.Header
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
Layout
.
Content
>
<
UsersTable
/>
<
UsersTable
/>
<
/Layout.Content
>
<
/Layout.Content
>
<
/Layout
>
<
/Layout
>
<
/
>
<
/
>
...
...
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