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
4c1a23d6
authored
Nov 10, 2023
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
完善兑换码界面
parent
78e05b8e
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
480 additions
and
358 deletions
+480
-358
controller/token.go
+2
-1
model/token.go
+6
-2
web/src/components/RedemptionsTable.js
+344
-295
web/src/components/TokensTable.js
+13
-3
web/src/pages/Redemption/EditRedemption.js
+99
-46
web/src/pages/Redemption/index.js
+10
-4
web/src/pages/Token/EditToken.js
+2
-2
web/src/pages/Token/index.js
+4
-5
No files found.
controller/token.go
View file @
4c1a23d6
...
@@ -39,7 +39,8 @@ func GetAllTokens(c *gin.Context) {
...
@@ -39,7 +39,8 @@ func GetAllTokens(c *gin.Context) {
func
SearchTokens
(
c
*
gin
.
Context
)
{
func
SearchTokens
(
c
*
gin
.
Context
)
{
userId
:=
c
.
GetInt
(
"id"
)
userId
:=
c
.
GetInt
(
"id"
)
keyword
:=
c
.
Query
(
"keyword"
)
keyword
:=
c
.
Query
(
"keyword"
)
tokens
,
err
:=
model
.
SearchUserTokens
(
userId
,
keyword
)
token
:=
c
.
Query
(
"token"
)
tokens
,
err
:=
model
.
SearchUserTokens
(
userId
,
keyword
,
token
)
if
err
!=
nil
{
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
...
...
model/token.go
View file @
4c1a23d6
...
@@ -5,6 +5,7 @@ import (
...
@@ -5,6 +5,7 @@ import (
"fmt"
"fmt"
"gorm.io/gorm"
"gorm.io/gorm"
"one-api/common"
"one-api/common"
"strings"
)
)
type
Token
struct
{
type
Token
struct
{
...
@@ -28,8 +29,11 @@ func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) {
...
@@ -28,8 +29,11 @@ func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) {
return
tokens
,
err
return
tokens
,
err
}
}
func
SearchUserTokens
(
userId
int
,
keyword
string
)
(
tokens
[]
*
Token
,
err
error
)
{
func
SearchUserTokens
(
userId
int
,
keyword
string
,
token
string
)
(
tokens
[]
*
Token
,
err
error
)
{
err
=
DB
.
Where
(
"user_id = ?"
,
userId
)
.
Where
(
"name LIKE ?"
,
keyword
+
"%"
)
.
Find
(
&
tokens
)
.
Error
if
token
!=
""
{
token
=
strings
.
Trim
(
token
,
"sk-"
)
}
err
=
DB
.
Where
(
"user_id = ?"
,
userId
)
.
Where
(
"name LIKE ? or key LIKE ?"
,
keyword
+
"%"
,
token
+
"%"
)
.
Find
(
&
tokens
)
.
Error
return
tokens
,
err
return
tokens
,
err
}
}
...
...
web/src/components/RedemptionsTable.js
View file @
4c1a23d6
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Form
,
Label
,
Popup
,
Pagination
}
from
'semantic-ui-react'
;
import
{
Link
}
from
'react-router-dom'
;
import
{
Link
}
from
'react-router-dom'
;
import
{
API
,
copy
,
showError
,
showInfo
,
showSuccess
,
showWarning
,
timestamp2string
}
from
'../helpers'
;
import
{
API
,
copy
,
showError
,
showInfo
,
showSuccess
,
showWarning
,
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
,
Modal
,
Popconfirm
,
Popover
,
Table
,
Tag
}
from
"@douyinfe/semi-ui"
;
import
{
Button
,
Modal
,
Popconfirm
,
Popover
,
Table
,
Tag
,
Form
}
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>
;
}
}
}
}
const
RedemptionsTable
=
()
=>
{
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
>
<
div
>
{
renderStatus
(
text
)}
{
renderStatus
(
text
)}
<
/div
>
<
/div
>
);
);
},
},
},
},
{
{
title
:
'额度'
,
title
:
'额度'
,
dataIndex
:
'quota'
,
dataIndex
:
'quota'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
(
<
div
>
<
div
>
{
renderQuota
(
parseInt
(
text
))}
{
renderQuota
(
parseInt
(
text
))}
<
/div
>
<
/div
>
);
);
},
},
},
},
{
{
title
:
'创建时间'
,
title
:
'创建时间'
,
dataIndex
:
'created_time'
,
dataIndex
:
'created_time'
,
render
:
(
text
,
record
,
index
)
=>
{
render
:
(
text
,
record
,
index
)
=>
{
return
(
return
(
<
div
>
<
div
>
{
renderTimestamp
(
text
)}
{
renderTimestamp
(
text
)}
<
/div
>
<
/div
>
);
);
},
},
},
},
{
{
title
:
''
,
title
:
'兑换人ID'
,
dataIndex
:
'operate'
,
dataIndex
:
'used_user_id'
,
render
:
(
text
,
record
,
index
)
=>
(
render
:
(
text
,
record
,
index
)
=>
{
<
div
>
return
(
<
Popover
<
div
>
content
=
{
{
text
===
0
?
'无'
:
text
}
record
.
key
<
/div
>
}
);
style
=
{{
padding
:
20
}}
},
position
=
"top"
},
>
{
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
>
查看
<
/Button
>
title
:
''
,
<
/Popover
>
dataIndex
:
'operate'
,
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
render
:
(
text
,
record
,
index
)
=>
(
onClick
=
{
async
(
text
)
=>
{
<
div
>
await
copyText
(
record
.
key
)
<
Popover
}}
content
=
{
>
复制
<
/Button
>
record
.
key
<
Popconfirm
}
title
=
"确定是否要删除此令牌?"
style
=
{{
padding
:
20
}}
content
=
"此修改将不可逆"
position
=
"top"
okType
=
{
'danger'
}
>
position
=
{
'left'
}
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
>
查看
<
/Button
>
onConfirm
=
{()
=>
{
<
/Popover
>
manageRedemption
(
record
.
id
,
'delete'
,
record
).
then
(
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
()
=>
{
onClick
=
{
async
(
text
)
=>
{
removeRecord
(
record
.
key
);
await
copyText
(
record
.
key
)
}
}}
)
>
复制
<
/Button
>
}}
<
Popconfirm
>
title
=
"确定是否要删除此令牌?"
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
content
=
"此修改将不可逆"
<
/Popconfirm
>
okType
=
{
'danger'
}
{
position
=
{
'left'
}
record
.
status
===
1
?
onConfirm
=
{()
=>
{
<
Button
theme
=
'light'
type
=
'warning'
style
=
{{
marginRight
:
1
}}
onClick
=
{
manageRedemption
(
record
.
id
,
'delete'
,
record
).
then
(
async
()
=>
{
()
=>
{
manageRedemption
(
removeRecord
(
record
.
key
);
record
.
id
,
}
'disable'
,
)
record
}}
)
>
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
/Popconfirm
>
{
record
.
status
===
1
?
<
Button
theme
=
'light'
type
=
'warning'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageRedemption
(
record
.
id
,
'disable'
,
record
)
}
}
>
禁用
<
/Button>
:
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageRedemption
(
record
.
id
,
'enable'
,
record
);
}
}
disabled
=
{
record
.
status
===
3
}
>
启用
<
/Button
>
}
}
}
>
禁用
<
/Button>
:
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
()
=>
{
async
()
=>
{
setEditingRedemption
(
record
);
manageRedemption
(
setShowEdit
(
true
);
record
.
id
,
}
'enable'
,
}
disabled
=
{
record
.
status
!==
1
}
>
编辑
<
/Button
>
record
<
/div
>
);
),
}
},
}
disabled
=
{
record
.
status
===
3
}
>
启用
<
/Button
>
];
}
{
/*<Button theme='light' type='tertiary' style={{marginRight: 1}} onClick={*/
}
{
/* () => {*/
}
{
/* setEditingToken(record);*/
}
{
/* setShowEdit(true);*/
}
{
/* }*/
}
{
/*}>编辑</Button>*/
}
<
/div
>
),
},
];
const
[
redemptions
,
setRedemptions
]
=
useState
([]);
const
[
redemptions
,
setRedemptions
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
activePage
,
setActivePage
]
=
useState
(
1
);
const
[
activePage
,
setActivePage
]
=
useState
(
1
);
const
[
searchKeyword
,
setSearchKeyword
]
=
useState
(
''
);
const
[
searchKeyword
,
setSearchKeyword
]
=
useState
(
''
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
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
({
id
:
undefined
,
});
const
[
showEdit
,
setShowEdit
]
=
useState
(
false
);
const
loadRedemptions
=
async
(
startIdx
)
=>
{
const
closeEdit
=
()
=>
{
const
res
=
await
API
.
get
(
`/api/redemption/?p=
${
startIdx
}
`
);
setShowEdit
(
false
);
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
startIdx
===
0
)
{
setRedemptions
(
data
);
}
else
{
let
newRedemptions
=
redemptions
;
newRedemptions
.
push
(...
data
);
setRedemptions
(
newRedemptions
);
}
}
else
{
showError
(
message
);
}
}
setLoading
(
false
);
};
const
removeRecord
=
key
=>
{
const
loadRedemptions
=
async
(
startIdx
)
=>
{
let
newDataSource
=
[...
redemptions
];
const
res
=
await
API
.
get
(
`/api/redemption/?p=
${
startIdx
}
`
);
if
(
key
!=
null
)
{
const
{
success
,
message
,
data
}
=
res
.
data
;
let
idx
=
newDataSource
.
findIndex
(
data
=>
data
.
key
===
key
);
if
(
success
)
{
if
(
startIdx
===
0
)
{
setRedemptions
(
data
);
}
else
{
let
newRedemptions
=
redemptions
;
newRedemptions
.
push
(...
data
);
setRedemptions
(
newRedemptions
);
}
}
else
{
showError
(
message
);
}
setLoading
(
false
);
};
if
(
idx
>
-
1
)
{
const
removeRecord
=
key
=>
{
newDataSource
.
splice
(
idx
,
1
);
let
newDataSource
=
[...
redemptions
];
setRedemptions
(
newDataSource
);
if
(
key
!=
null
)
{
}
let
idx
=
newDataSource
.
findIndex
(
data
=>
data
.
key
===
key
);
}
};
const
copyText
=
async
(
text
)
=>
{
if
(
idx
>
-
1
)
{
if
(
await
copy
(
text
))
{
newDataSource
.
splice
(
idx
,
1
);
showSuccess
(
'已复制到剪贴板!'
);
setRedemptions
(
newDataSource
);
}
else
{
}
// setSearchKeyword(text);
}
Modal
.
error
({
title
:
'无法复制到剪贴板,请手动复制'
,
content
:
text
});
};
const
copyText
=
async
(
text
)
=>
{
if
(
await
copy
(
text
))
{
showSuccess
(
'已复制到剪贴板!'
);
}
else
{
// setSearchKeyword(text);
Modal
.
error
({
title
:
'无法复制到剪贴板,请手动复制'
,
content
:
text
});
}
}
}
}
const
onPaginationChange
=
(
e
,
{
activePage
})
=>
{
const
onPaginationChange
=
(
e
,
{
activePage
})
=>
{
(
async
()
=>
{
(
async
()
=>
{
if
(
activePage
===
Math
.
ceil
(
redemptions
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
if
(
activePage
===
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.
await
loadRedemptions
(
activePage
-
1
);
await
loadRedemptions
(
activePage
-
1
);
}
}
setActivePage
(
activePage
);
setActivePage
(
activePage
);
})();
})();
};
};
useEffect
(()
=>
{
useEffect
(()
=>
{
loadRedemptions
(
0
)
loadRedemptions
(
0
)
.
then
()
.
then
()
.
catch
((
reason
)
=>
{
.
catch
((
reason
)
=>
{
showError
(
reason
);
showError
(
reason
);
});
});
},
[]);
},
[]);
const
manageRedemption
=
async
(
id
,
action
,
record
)
=>
{
const
refresh
=
async
()
=>
{
let
data
=
{
id
};
await
loadRedemptions
(
activePage
-
1
);
let
res
;
};
switch
(
action
)
{
case
'delete'
:
res
=
await
API
.
delete
(
`/api/redemption/
${
id
}
/`
);
break
;
case
'enable'
:
data
.
status
=
1
;
res
=
await
API
.
put
(
'/api/redemption/?status_only=true'
,
data
);
break
;
case
'disable'
:
data
.
status
=
2
;
res
=
await
API
.
put
(
'/api/redemption/?status_only=true'
,
data
);
break
;
}
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
showSuccess
(
'操作成功完成!'
);
let
redemption
=
res
.
data
.
data
;
let
newRedemptions
=
[...
redemptions
];
// let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
if
(
action
===
'delete'
)
{
}
else
{
const
manageRedemption
=
async
(
id
,
action
,
record
)
=>
{
record
.
status
=
redemption
.
status
;
let
data
=
{
id
};
}
let
res
;
setRedemptions
(
newRedemptions
);
switch
(
action
)
{
}
else
{
case
'delete'
:
showError
(
message
);
res
=
await
API
.
delete
(
`/api/redemption/
${
id
}
/`
);
}
break
;
};
case
'enable'
:
data
.
status
=
1
;
res
=
await
API
.
put
(
'/api/redemption/?status_only=true'
,
data
);
break
;
case
'disable'
:
data
.
status
=
2
;
res
=
await
API
.
put
(
'/api/redemption/?status_only=true'
,
data
);
break
;
}
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
showSuccess
(
'操作成功完成!'
);
let
redemption
=
res
.
data
.
data
;
let
newRedemptions
=
[...
redemptions
];
// let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
if
(
action
===
'delete'
)
{
const
searchRedemptions
=
async
()
=>
{
}
else
{
if
(
searchKeyword
===
''
)
{
record
.
status
=
redemption
.
status
;
// if keyword is blank, load files instead.
}
await
loadRedemptions
(
0
);
setRedemptions
(
newRedemptions
);
setActivePage
(
1
);
}
else
{
return
;
showError
(
message
);
}
}
setSearching
(
true
);
};
const
res
=
await
API
.
get
(
`/api/redemption/search?keyword=
${
searchKeyword
}
`
);
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
setRedemptions
(
data
);
setActivePage
(
1
);
}
else
{
showError
(
message
);
}
setSearching
(
false
);
};
const
handleKeywordChange
=
async
(
e
,
{
value
})
=>
{
const
searchRedemptions
=
async
()
=>
{
setSearchKeyword
(
value
.
trim
());
if
(
searchKeyword
===
''
)
{
};
// if keyword is blank, load files instead.
await
loadRedemptions
(
0
);
setActivePage
(
1
);
return
;
}
setSearching
(
true
);
const
res
=
await
API
.
get
(
`/api/redemption/search?keyword=
${
searchKeyword
}
`
);
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
setRedemptions
(
data
);
setActivePage
(
1
);
}
else
{
showError
(
message
);
}
setSearching
(
false
);
};
const
sortRedemption
=
(
key
)
=>
{
const
handleKeywordChange
=
async
(
value
)
=>
{
if
(
redemptions
.
length
===
0
)
return
;
setSearchKeyword
(
value
.
trim
());
setLoading
(
true
);
};
let
sortedRedemptions
=
[...
redemptions
];
sortedRedemptions
.
sort
((
a
,
b
)
=>
{
return
(
''
+
a
[
key
]).
localeCompare
(
b
[
key
]);
});
if
(
sortedRedemptions
[
0
].
id
===
redemptions
[
0
].
id
)
{
sortedRedemptions
.
reverse
();
}
setRedemptions
(
sortedRedemptions
);
setLoading
(
false
);
};
const
handlePageChange
=
page
=>
{
const
sortRedemption
=
(
key
)
=>
{
setActivePage
(
page
);
if
(
redemptions
.
length
===
0
)
return
;
if
(
page
===
Math
.
ceil
(
redemptions
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
setLoading
(
true
);
// In this case we have to load more data and then append them.
let
sortedRedemptions
=
[...
redemptions
];
loadRedemptions
(
page
-
1
).
then
(
r
=>
{});
sortedRedemptions
.
sort
((
a
,
b
)
=>
{
}
return
(
''
+
a
[
key
]).
localeCompare
(
b
[
key
]);
};
});
if
(
sortedRedemptions
[
0
].
id
===
redemptions
[
0
].
id
)
{
sortedRedemptions
.
reverse
();
}
setRedemptions
(
sortedRedemptions
);
setLoading
(
false
);
};
const
handlePageChange
=
page
=>
{
setActivePage
(
page
);
if
(
page
===
Math
.
ceil
(
redemptions
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
// In this case we have to load more data and then append them.
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
);
},
},
};
};
return
(
return
(
<>
<>
<
Form
onSubmit
=
{
searchRedemptions
}
>
<
EditRedemption
refresh
=
{
refresh
}
editingRedemption
=
{
editingRedemption
}
visiable
=
{
showEdit
}
<
Form
.
Input
handleClose
=
{
closeEdit
}
><
/EditRedemption
>
icon
=
'search'
<
Form
onSubmit
=
{
searchRedemptions
}
>
fluid
<
Form
.
Input
iconPosition
=
'left'
label
=
'搜索关键字'
placeholder
=
'搜索兑换码的 ID 和名称 ...'
field
=
'keyword'
value
=
{
searchKeyword
}
icon
=
'search'
loading
=
{
searching
}
iconPosition
=
'left'
onChange
=
{
handleKeywordChange
}
placeholder
=
'关键字(id或者名称)'
/
>
value
=
{
searchKeyword
}
<
/Form
>
loading
=
{
searching
}
onChange
=
{
handleKeywordChange
}
/
>
<
/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
}
>
}}
loading
=
{
loading
}
rowSelection
=
{
rowSelection
}
>
<
/Table
>
<
/Table
>
<
/
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{
);
()
=>
{
setEditingRedemption
({
id
:
undefined
,
});
setShowEdit
(
true
);
}
}
>
添加兑换码
<
/Button
>
<
Button
label
=
'复制所选兑换码'
type
=
"warning"
onClick
=
{
async
()
=>
{
if
(
selectedKeys
.
length
===
0
)
{
showError
(
'请至少选择一个兑换码!'
);
return
;
}
let
keys
=
""
;
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
keys
+=
selectedKeys
[
i
].
name
+
" sk-"
+
selectedKeys
[
i
].
key
+
"\n"
;
}
await
copyText
(
keys
);
}
}
>
复制所选兑换码到剪贴板
<
/Button
>
<
/
>
);
};
};
export
default
RedemptionsTable
;
export
default
RedemptionsTable
;
web/src/components/TokensTable.js
View file @
4c1a23d6
...
@@ -179,6 +179,7 @@ const TokensTable = () => {
...
@@ -179,6 +179,7 @@ const TokensTable = () => {
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
activePage
,
setActivePage
]
=
useState
(
1
);
const
[
activePage
,
setActivePage
]
=
useState
(
1
);
const
[
searchKeyword
,
setSearchKeyword
]
=
useState
(
''
);
const
[
searchKeyword
,
setSearchKeyword
]
=
useState
(
''
);
const
[
searchToken
,
setSearchToken
]
=
useState
(
''
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
showTopUpModal
,
setShowTopUpModal
]
=
useState
(
false
);
const
[
showTopUpModal
,
setShowTopUpModal
]
=
useState
(
false
);
const
[
targetTokenIdx
,
setTargetTokenIdx
]
=
useState
(
0
);
const
[
targetTokenIdx
,
setTargetTokenIdx
]
=
useState
(
0
);
...
@@ -188,9 +189,6 @@ const TokensTable = () => {
...
@@ -188,9 +189,6 @@ const TokensTable = () => {
const
closeEdit
=
()
=>
{
const
closeEdit
=
()
=>
{
setShowEdit
(
false
);
setShowEdit
(
false
);
// setEditingToken({
// id: undefined,
// });
}
}
const
setTokensFormat
=
(
tokens
)
=>
{
const
setTokensFormat
=
(
tokens
)
=>
{
...
@@ -401,6 +399,10 @@ const TokensTable = () => {
...
@@ -401,6 +399,10 @@ const TokensTable = () => {
setSearchKeyword
(
value
.
trim
());
setSearchKeyword
(
value
.
trim
());
};
};
const
handleSearchTokenChange
=
async
(
value
)
=>
{
setSearchToken
(
value
.
trim
());
};
const
sortToken
=
(
key
)
=>
{
const
sortToken
=
(
key
)
=>
{
if
(
tokens
.
length
===
0
)
return
;
if
(
tokens
.
length
===
0
)
return
;
setLoading
(
true
);
setLoading
(
true
);
...
@@ -447,6 +449,14 @@ const TokensTable = () => {
...
@@ -447,6 +449,14 @@ const TokensTable = () => {
loading
=
{
searching
}
loading
=
{
searching
}
onChange
=
{
handleKeywordChange
}
onChange
=
{
handleKeywordChange
}
/
>
/
>
<
Form
.
Input
field
=
"token"
label
=
'Key'
placeholder
=
'密钥'
value
=
{
searchToken
}
loading
=
{
searching
}
onChange
=
{
handleSearchTokenChange
}
/
>
<
Button
label
=
'查询'
type
=
"primary"
htmlType
=
"submit"
className
=
"btn-margin-right"
<
Button
label
=
'查询'
type
=
"primary"
htmlType
=
"submit"
className
=
"btn-margin-right"
onClick
=
{
searchTokens
}
style
=
{{
marginRight
:
8
}}
>
查询
<
/Button
>
onClick
=
{
searchTokens
}
style
=
{{
marginRight
:
8
}}
>
查询
<
/Button
>
<
/Form
>
<
/Form
>
...
...
web/src/pages/Redemption/EditRedemption.js
View file @
4c1a23d6
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Button
,
Form
,
Header
,
Segment
}
from
'semantic-ui-react'
;
import
{
useParams
,
useNavigate
}
from
'react-router-dom'
;
import
{
useParams
,
useNavigate
}
from
'react-router-dom'
;
import
{
API
,
downloadTextAsFile
,
showError
,
showSuccess
}
from
'../../helpers'
;
import
{
API
,
downloadTextAsFile
,
isMobile
,
showError
,
showSuccess
}
from
'../../helpers'
;
import
{
renderQuota
,
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
renderQuota
,
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
SideSheet
,
Space
,
Spin
,
Button
,
Input
,
Typography
,
AutoComplete
,
Modal
}
from
"@douyinfe/semi-ui"
;
import
Title
from
"@douyinfe/semi-ui/lib/es/typography/title"
;
import
{
Divider
}
from
"semantic-ui-react"
;
const
EditRedemption
=
()
=>
{
const
EditRedemption
=
(
props
)
=>
{
const
params
=
useParams
();
const
isEdit
=
props
.
editingRedemption
.
id
!==
undefined
;
const
navigate
=
useNavigate
();
const
redemptionId
=
params
.
id
;
const
isEdit
=
redemptionId
!==
undefined
;
const
[
loading
,
setLoading
]
=
useState
(
isEdit
);
const
[
loading
,
setLoading
]
=
useState
(
isEdit
);
const
params
=
useParams
();
const
navigate
=
useNavigate
()
const
originInputs
=
{
const
originInputs
=
{
name
:
''
,
name
:
''
,
quota
:
100000
,
quota
:
100000
,
...
@@ -19,15 +21,16 @@ const EditRedemption = () => {
...
@@ -19,15 +21,16 @@ const EditRedemption = () => {
const
{
name
,
quota
,
count
}
=
inputs
;
const
{
name
,
quota
,
count
}
=
inputs
;
const
handleCancel
=
()
=>
{
const
handleCancel
=
()
=>
{
navigate
(
'/redemption'
);
props
.
handleClose
(
);
}
;
}
const
handleInputChange
=
(
e
,
{
name
,
value
}
)
=>
{
const
handleInputChange
=
(
name
,
value
)
=>
{
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
};
};
const
loadRedemption
=
async
()
=>
{
const
loadRedemption
=
async
()
=>
{
let
res
=
await
API
.
get
(
`/api/redemption/
${
redemptionId
}
`
);
setLoading
(
true
);
let
res
=
await
API
.
get
(
`/api/redemption/
${
props
.
editingRedemption
.
id
}
`
);
const
{
success
,
message
,
data
}
=
res
.
data
;
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
if
(
success
)
{
setInputs
(
data
);
setInputs
(
data
);
...
@@ -36,20 +39,28 @@ const EditRedemption = () => {
...
@@ -36,20 +39,28 @@ const EditRedemption = () => {
}
}
setLoading
(
false
);
setLoading
(
false
);
};
};
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
isEdit
)
{
if
(
isEdit
)
{
loadRedemption
().
then
();
loadRedemption
().
then
(
()
=>
{
// console.log(inputs);
}
);
}
else
{
setInputs
(
originInputs
);
}
}
},
[]);
},
[
props
.
editingRedemption
.
id
]);
const
submit
=
async
()
=>
{
const
submit
=
async
()
=>
{
if
(
!
isEdit
&&
inputs
.
name
===
''
)
return
;
if
(
!
isEdit
&&
inputs
.
name
===
''
)
return
;
setLoading
(
true
);
let
localInputs
=
inputs
;
let
localInputs
=
inputs
;
localInputs
.
count
=
parseInt
(
localInputs
.
count
);
localInputs
.
count
=
parseInt
(
localInputs
.
count
);
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
(
redemptionI
d
)
});
res
=
await
API
.
put
(
`/api/redemption/`
,
{
...
localInputs
,
id
:
parseInt
(
props
.
editingRedemption
.
i
d
)
});
}
else
{
}
else
{
res
=
await
API
.
post
(
`/api/redemption/`
,
{
res
=
await
API
.
post
(
`/api/redemption/`
,
{
...
localInputs
...
localInputs
...
@@ -59,9 +70,13 @@ const EditRedemption = () => {
...
@@ -59,9 +70,13 @@ const EditRedemption = () => {
if
(
success
)
{
if
(
success
)
{
if
(
isEdit
)
{
if
(
isEdit
)
{
showSuccess
(
'兑换码更新成功!'
);
showSuccess
(
'兑换码更新成功!'
);
props
.
refresh
();
props
.
handleClose
();
}
else
{
}
else
{
showSuccess
(
'兑换码创建成功!'
);
showSuccess
(
'兑换码创建成功!'
);
setInputs
(
originInputs
);
setInputs
(
originInputs
);
props
.
refresh
();
props
.
handleClose
();
}
}
}
else
{
}
else
{
showError
(
message
);
showError
(
message
);
...
@@ -71,56 +86,94 @@ const EditRedemption = () => {
...
@@ -71,56 +86,94 @@ const EditRedemption = () => {
for
(
let
i
=
0
;
i
<
data
.
length
;
i
++
)
{
for
(
let
i
=
0
;
i
<
data
.
length
;
i
++
)
{
text
+=
data
[
i
]
+
"\n"
;
text
+=
data
[
i
]
+
"\n"
;
}
}
downloadTextAsFile
(
text
,
`
${
inputs
.
name
}
.txt`
);
// downloadTextAsFile(text, `${inputs.name}.txt`);
Modal
.
confirm
({
title
:
'兑换码创建成功'
,
content
:
(
<
div
>
<
p
>
兑换码创建成功,是否下载兑换码?
<
/p
>
<
p
>
兑换码将以文本文件的形式下载,文件名为兑换码的名称。
<
/p
>
<
/div
>
),
onOk
:
()
=>
{
downloadTextAsFile
(
text
,
`
${
inputs
.
name
}
.txt`
);
}
});
}
}
setLoading
(
false
);
};
};
return
(
return
(
<>
<>
<
Segment
loading
=
{
loading
}
>
<
SideSheet
<
Header
as
=
'h3'
>
{
isEdit
?
'更新兑换码信息'
:
'创建新的兑换码'
}
<
/Header
>
placement
=
{
isEdit
?
'right'
:
'left'
}
<
Form
autoComplete
=
'new-password'
>
title
=
{
<
Title
level
=
{
3
}
>
{
isEdit
?
'更新兑换码信息'
:
'创建新的兑换码'
}
<
/Title>
}
<
Form
.
Field
>
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
<
Form
.
Input
bodyStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
visible
=
{
props
.
visiable
}
footer
=
{
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
Space
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/Space
>
<
/div
>
}
closeIcon
=
{
null
}
onCancel
=
{()
=>
handleCancel
()}
width
=
{
isMobile
()
?
'100%'
:
600
}
>
<
Spin
spinning
=
{
loading
}
>
<
Input
style
=
{{
marginTop
:
20
}}
label
=
'名称'
label
=
'名称'
name
=
'name'
name
=
'name'
placeholder
=
{
'请输入名称'
}
placeholder
=
{
'请输入名称'
}
onChange
=
{
handleInputChange
}
onChange
=
{
value
=>
handleInputChange
(
'name'
,
value
)
}
value
=
{
name
}
value
=
{
name
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
required
=
{
!
isEdit
}
required
=
{
!
isEdit
}
/
>
/
>
<
/Form.Field
>
<
Divider
/>
<
Form
.
Field
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Form
.
Input
<
Typography
.
Text
>
{
`额度
${
renderQuotaWithPrompt
(
quota
)}
`
}
<
/Typography.Text
>
label
=
{
`额度
${
renderQuotaWithPrompt
(
quota
)}
`
}
<
/div
>
<
AutoComplete
style
=
{{
marginTop
:
8
}}
name
=
'quota'
name
=
'quota'
placeholder
=
{
'请输入
单个兑换码中包含的
额度'
}
placeholder
=
{
'请输入额度'
}
onChange
=
{
handleInputChange
}
onChange
=
{
(
value
)
=>
handleInputChange
(
'quota'
,
value
)
}
value
=
{
quota
}
value
=
{
quota
}
autoComplete
=
'new-password'
autoComplete
=
'new-password'
type
=
'number'
type
=
'number'
/>
position
=
{
'bottom'
}
<
/Form.Field
>
data
=
{[
{
value
:
500000
,
label
:
'1$'
},
{
value
:
5000000
,
label
:
'10$'
},
{
value
:
25000000
,
label
:
'50$'
},
{
value
:
50000000
,
label
:
'100$'
},
{
value
:
250000000
,
label
:
'500$'
},
{
value
:
500000000
,
label
:
'1000$'
},
]}
/
>
{
{
!
isEdit
&&
<>
!
isEdit
&&
<>
<
Form
.
Field
>
<
Divider
/>
<
Form
.
Input
<
Typography
.
Text
>
生成数量
<
/Typography.Text
>
label
=
'生成数量'
<
Input
name
=
'count'
style
=
{{
marginTop
:
8
}}
placeholder
=
{
'请输入生成数量'
}
label
=
'生成数量'
onChange
=
{
handleInputChange
}
name
=
'count'
value
=
{
count
}
placeholder
=
{
'请输入生成数量'
}
autoComplete
=
'new-password'
onChange
=
{
value
=>
handleInputChange
(
'count'
,
value
)}
type
=
'number'
value
=
{
count
}
autoComplete
=
'new-password'
type
=
'number'
/>
/>
<
/Form.Field
>
<
/
>
<
/
>
}
}
<
Button
positive
onClick
=
{
submit
}
>
提交
<
/Button
>
<
/Spin
>
<
Button
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/SideSheet
>
<
/Form
>
<
/Segment
>
<
/
>
<
/
>
);
);
};
};
...
...
web/src/pages/Redemption/index.js
View file @
4c1a23d6
import
React
from
'react'
;
import
React
from
'react'
;
import
{
Segment
,
Header
}
from
'semantic-ui-react'
;
import
{
Segment
,
Header
}
from
'semantic-ui-react'
;
import
RedemptionsTable
from
'../../components/RedemptionsTable'
;
import
RedemptionsTable
from
'../../components/RedemptionsTable'
;
import
TokensTable
from
"../../components/TokensTable"
;
import
{
Layout
}
from
"@douyinfe/semi-ui"
;
const
Redemption
=
()
=>
(
const
Redemption
=
()
=>
(
<>
<>
<
Segment
>
<
Layout
>
<
Header
as
=
'h3'
>
管理兑换码
<
/Header
>
<
Layout
.
Header
>
<
RedemptionsTable
/>
<
h3
>
管理兑换码
<
/h3
>
<
/Segment
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
RedemptionsTable
/>
<
/Layout.Content
>
<
/Layout
>
<
/
>
<
/
>
);
);
...
...
web/src/pages/Token/EditToken.js
View file @
4c1a23d6
...
@@ -63,7 +63,7 @@ const EditToken = (props) => {
...
@@ -63,7 +63,7 @@ const EditToken = (props) => {
if
(
isEdit
)
{
if
(
isEdit
)
{
loadToken
().
then
(
loadToken
().
then
(
()
=>
{
()
=>
{
console
.
log
(
inputs
);
//
console.log(inputs);
}
}
);
);
}
else
{
}
else
{
...
@@ -72,8 +72,8 @@ const EditToken = (props) => {
...
@@ -72,8 +72,8 @@ const EditToken = (props) => {
},
[
props
.
editingToken
.
id
]);
},
[
props
.
editingToken
.
id
]);
const
submit
=
async
()
=>
{
const
submit
=
async
()
=>
{
setLoading
(
true
);
if
(
!
isEdit
&&
inputs
.
name
===
''
)
return
;
if
(
!
isEdit
&&
inputs
.
name
===
''
)
return
;
setLoading
(
true
);
let
localInputs
=
inputs
;
let
localInputs
=
inputs
;
localInputs
.
remain_quota
=
parseInt
(
localInputs
.
remain_quota
);
localInputs
.
remain_quota
=
parseInt
(
localInputs
.
remain_quota
);
if
(
localInputs
.
expired_time
!==
-
1
)
{
if
(
localInputs
.
expired_time
!==
-
1
)
{
...
...
web/src/pages/Token/index.js
View file @
4c1a23d6
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
{
Content
,
Header
}
=
Layout
;
const
Token
=
()
=>
(
const
Token
=
()
=>
(
<>
<>
<
Layout
>
<
Layout
>
<
Header
>
<
Layout
.
Header
>
<
h3
>
我的令牌
<
/h3
>
<
h3
>
我的令牌
<
/h3
>
<
/Header
>
<
/
Layout.
Header
>
<
Content
>
<
Layout
.
Content
>
<
TokensTable
/>
<
TokensTable
/>
<
/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