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) {
func
SearchTokens
(
c
*
gin
.
Context
)
{
userId
:=
c
.
GetInt
(
"id"
)
keyword
:=
c
.
Query
(
"keyword"
)
tokens
,
err
:=
model
.
SearchUserTokens
(
userId
,
keyword
)
token
:=
c
.
Query
(
"token"
)
tokens
,
err
:=
model
.
SearchUserTokens
(
userId
,
keyword
,
token
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
...
...
model/token.go
View file @
4c1a23d6
...
...
@@ -5,6 +5,7 @@ import (
"fmt"
"gorm.io/gorm"
"one-api/common"
"strings"
)
type
Token
struct
{
...
...
@@ -28,8 +29,11 @@ func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) {
return
tokens
,
err
}
func
SearchUserTokens
(
userId
int
,
keyword
string
)
(
tokens
[]
*
Token
,
err
error
)
{
err
=
DB
.
Where
(
"user_id = ?"
,
userId
)
.
Where
(
"name LIKE ?"
,
keyword
+
"%"
)
.
Find
(
&
tokens
)
.
Error
func
SearchUserTokens
(
userId
int
,
keyword
string
,
token
string
)
(
tokens
[]
*
Token
,
err
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
}
...
...
web/src/components/RedemptionsTable.js
View file @
4c1a23d6
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Form
,
Label
,
Popup
,
Pagination
}
from
'semantic-ui-react'
;
import
{
Link
}
from
'react-router-dom'
;
import
{
API
,
copy
,
showError
,
showInfo
,
showSuccess
,
showWarning
,
timestamp2string
}
from
'../helpers'
;
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Link
}
from
'react-router-dom'
;
import
{
API
,
copy
,
showError
,
showInfo
,
showSuccess
,
showWarning
,
timestamp2string
}
from
'../helpers'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderQuota
}
from
'../helpers/render'
;
import
{
Button
,
Modal
,
Popconfirm
,
Popover
,
Table
,
Tag
}
from
"@douyinfe/semi-ui"
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderQuota
}
from
'../helpers/render'
;
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
)
{
return
(
<>
{
timestamp2string
(
timestamp
)}
<
/
>
);
return
(
<>
{
timestamp2string
(
timestamp
)}
<
/
>
);
}
function
renderStatus
(
status
)
{
switch
(
status
)
{
case
1
:
return
<
Tag
color
=
'green'
size
=
'large'
>
未使用
<
/Tag>
;
case
2
:
return
<
Tag
color
=
'red'
size
=
'large'
>
已禁用
<
/Tag>
;
case
3
:
return
<
Tag
color
=
'grey'
size
=
'large'
>
已使用
<
/Tag>
;
default
:
return
<
Tag
color
=
'black'
size
=
'large'
>
未知状态
<
/Tag>
;
}
switch
(
status
)
{
case
1
:
return
<
Tag
color
=
'green'
size
=
'large'
>
未使用
<
/Tag>
;
case
2
:
return
<
Tag
color
=
'red'
size
=
'large'
>
已禁用
<
/Tag>
;
case
3
:
return
<
Tag
color
=
'grey'
size
=
'large'
>
已使用
<
/Tag>
;
default
:
return
<
Tag
color
=
'black'
size
=
'large'
>
未知状态
<
/Tag>
;
}
}
const
RedemptionsTable
=
()
=>
{
const
columns
=
[
{
title
:
'ID'
,
dataIndex
:
'id'
,
},
{
title
:
'名称'
,
dataIndex
:
'name'
,
},
{
title
:
'状态'
,
dataIndex
:
'status'
,
key
:
'status'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
{
renderStatus
(
text
)}
<
/div
>
);
},
},
{
title
:
'额度'
,
dataIndex
:
'quota'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
{
renderQuota
(
parseInt
(
text
))}
<
/div
>
);
},
},
{
title
:
'创建时间'
,
dataIndex
:
'created_time'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
{
renderTimestamp
(
text
)}
<
/div
>
);
},
},
{
title
:
''
,
dataIndex
:
'operate'
,
render
:
(
text
,
record
,
index
)
=>
(
<
div
>
<
Popover
content
=
{
record
.
key
}
style
=
{{
padding
:
20
}}
position
=
"top"
>
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
>
查看
<
/Button
>
<
/Popover
>
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
(
text
)
=>
{
await
copyText
(
record
.
key
)
}}
>
复制
<
/Button
>
<
Popconfirm
title
=
"确定是否要删除此令牌?"
content
=
"此修改将不可逆"
okType
=
{
'danger'
}
position
=
{
'left'
}
onConfirm
=
{()
=>
{
manageRedemption
(
record
.
id
,
'delete'
,
record
).
then
(
()
=>
{
removeRecord
(
record
.
key
);
}
)
}}
>
<
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
)
const
columns
=
[
{
title
:
'ID'
,
dataIndex
:
'id'
,
},
{
title
:
'名称'
,
dataIndex
:
'name'
,
},
{
title
:
'状态'
,
dataIndex
:
'status'
,
key
:
'status'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
{
renderStatus
(
text
)}
<
/div
>
);
},
},
{
title
:
'额度'
,
dataIndex
:
'quota'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
{
renderQuota
(
parseInt
(
text
))}
<
/div
>
);
},
},
{
title
:
'创建时间'
,
dataIndex
:
'created_time'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
{
renderTimestamp
(
text
)}
<
/div
>
);
},
},
{
title
:
'兑换人ID'
,
dataIndex
:
'used_user_id'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
{
text
===
0
?
'无'
:
text
}
<
/div
>
);
},
},
{
title
:
''
,
dataIndex
:
'operate'
,
render
:
(
text
,
record
,
index
)
=>
(
<
div
>
<
Popover
content
=
{
record
.
key
}
style
=
{{
padding
:
20
}}
position
=
"top"
>
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
>
查看
<
/Button
>
<
/Popover
>
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
(
text
)
=>
{
await
copyText
(
record
.
key
)
}}
>
复制
<
/Button
>
<
Popconfirm
title
=
"确定是否要删除此令牌?"
content
=
"此修改将不可逆"
okType
=
{
'danger'
}
position
=
{
'left'
}
onConfirm
=
{()
=>
{
manageRedemption
(
record
.
id
,
'delete'
,
record
).
then
(
()
=>
{
removeRecord
(
record
.
key
);
}
)
}}
>
<
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
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageRedemption
(
record
.
id
,
'enable'
,
record
);
}
}
disabled
=
{
record
.
status
===
3
}
>
启用
<
/Button
>
}
{
/*<Button theme='light' type='tertiary' style={{marginRight: 1}} onClick={*/
}
{
/* () => {*/
}
{
/* setEditingToken(record);*/
}
{
/* setShowEdit(true);*/
}
{
/* }*/
}
{
/*}>编辑</Button>*/
}
<
/div
>
),
},
];
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
()
=>
{
setEditingRedemption
(
record
);
setShowEdit
(
true
);
}
}
disabled
=
{
record
.
status
!==
1
}
>
编辑
<
/Button
>
<
/div
>
),
},
];
const
[
redemptions
,
setRedemptions
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
activePage
,
setActivePage
]
=
useState
(
1
);
const
[
searchKeyword
,
setSearchKeyword
]
=
useState
(
''
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
tokenCount
,
setTokenCount
]
=
useState
(
ITEMS_PER_PAGE
);
const
[
selectedKeys
,
setSelectedKeys
]
=
useState
([]);
const
[
redemptions
,
setRedemptions
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
activePage
,
setActivePage
]
=
useState
(
1
);
const
[
searchKeyword
,
setSearchKeyword
]
=
useState
(
''
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
tokenCount
,
setTokenCount
]
=
useState
(
ITEMS_PER_PAGE
);
const
[
selectedKeys
,
setSelectedKeys
]
=
useState
([]);
const
[
editingRedemption
,
setEditingRedemption
]
=
useState
({
id
:
undefined
,
});
const
[
showEdit
,
setShowEdit
]
=
useState
(
false
);
const
loadRedemptions
=
async
(
startIdx
)
=>
{
const
res
=
await
API
.
get
(
`/api/redemption/?p=
${
startIdx
}
`
);
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
);
const
closeEdit
=
()
=>
{
setShowEdit
(
false
);
}
setLoading
(
false
);
};
const
removeRecord
=
key
=>
{
let
newDataSource
=
[...
redemptions
];
if
(
key
!=
null
)
{
let
idx
=
newDataSource
.
findIndex
(
data
=>
data
.
key
===
key
);
const
loadRedemptions
=
async
(
startIdx
)
=>
{
const
res
=
await
API
.
get
(
`/api/redemption/?p=
${
startIdx
}
`
);
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
);
};
if
(
idx
>
-
1
)
{
newDataSource
.
splice
(
idx
,
1
);
setRedemptions
(
newDataSource
);
}
}
};
const
removeRecord
=
key
=>
{
let
newDataSource
=
[...
redemptions
];
if
(
key
!=
null
)
{
let
idx
=
newDataSource
.
findIndex
(
data
=>
data
.
key
===
key
);
const
copyText
=
async
(
text
)
=>
{
if
(
await
copy
(
text
))
{
showSuccess
(
'已复制到剪贴板!'
);
}
else
{
// setSearchKeyword(text);
Modal
.
error
({
title
:
'无法复制到剪贴板,请手动复制'
,
content
:
text
});
if
(
idx
>
-
1
)
{
newDataSource
.
splice
(
idx
,
1
);
setRedemptions
(
newDataSource
);
}
}
};
const
copyText
=
async
(
text
)
=>
{
if
(
await
copy
(
text
))
{
showSuccess
(
'已复制到剪贴板!'
);
}
else
{
// setSearchKeyword(text);
Modal
.
error
({
title
:
'无法复制到剪贴板,请手动复制'
,
content
:
text
});
}
}
}
const
onPaginationChange
=
(
e
,
{
activePage
})
=>
{
(
async
()
=>
{
if
(
activePage
===
Math
.
ceil
(
redemptions
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
// In this case we have to load more data and then append them.
await
loadRedemptions
(
activePage
-
1
);
}
setActivePage
(
activePage
);
})();
};
const
onPaginationChange
=
(
e
,
{
activePage
})
=>
{
(
async
()
=>
{
if
(
activePage
===
Math
.
ceil
(
redemptions
.
length
/
ITEMS_PER_PAGE
)
+
1
)
{
// In this case we have to load more data and then append them.
await
loadRedemptions
(
activePage
-
1
);
}
setActivePage
(
activePage
);
})();
};
useEffect
(()
=>
{
loadRedemptions
(
0
)
.
then
()
.
catch
((
reason
)
=>
{
showError
(
reason
);
});
},
[]);
useEffect
(()
=>
{
loadRedemptions
(
0
)
.
then
()
.
catch
((
reason
)
=>
{
showError
(
reason
);
});
},
[]);
const
manageRedemption
=
async
(
id
,
action
,
record
)
=>
{
let
data
=
{
id
};
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'
)
{
const
refresh
=
async
()
=>
{
await
loadRedemptions
(
activePage
-
1
);
};
}
else
{
record
.
status
=
redemption
.
status
;
}
setRedemptions
(
newRedemptions
);
}
else
{
showError
(
message
);
}
};
const
manageRedemption
=
async
(
id
,
action
,
record
)
=>
{
let
data
=
{
id
};
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'
)
{
const
searchRedemptions
=
async
()
=>
{
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
);
};
}
else
{
record
.
status
=
redemption
.
status
;
}
setRedemptions
(
newRedemptions
);
}
else
{
showError
(
message
);
}
};
const
handleKeywordChange
=
async
(
e
,
{
value
})
=>
{
setSearchKeyword
(
value
.
trim
());
};
const
searchRedemptions
=
async
()
=>
{
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
)
=>
{
if
(
redemptions
.
length
===
0
)
return
;
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
handleKeywordChange
=
async
(
value
)
=>
{
setSearchKeyword
(
value
.
trim
());
};
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
=>
{});
}
};
const
sortRedemption
=
(
key
)
=>
{
if
(
redemptions
.
length
===
0
)
return
;
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
=>
{
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
);
const
rowSelection
=
{
onSelect
:
(
record
,
selected
)
=>
{
},
onSelectAll
:
(
selected
,
selectedRows
)
=>
{
},
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
setSelectedKeys
(
selectedRows
);
},
};
let
pageData
=
redemptions
.
slice
((
activePage
-
1
)
*
ITEMS_PER_PAGE
,
activePage
*
ITEMS_PER_PAGE
);
const
rowSelection
=
{
onSelect
:
(
record
,
selected
)
=>
{
},
onSelectAll
:
(
selected
,
selectedRows
)
=>
{
},
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
setSelectedKeys
(
selectedRows
);
},
};
return
(
<>
<
Form
onSubmit
=
{
searchRedemptions
}
>
<
Form
.
Input
icon
=
'search'
fluid
iconPosition
=
'left'
placeholder
=
'搜索兑换码的 ID 和名称 ...'
value
=
{
searchKeyword
}
loading
=
{
searching
}
onChange
=
{
handleKeywordChange
}
/
>
<
/Form
>
return
(
<>
<
EditRedemption
refresh
=
{
refresh
}
editingRedemption
=
{
editingRedemption
}
visiable
=
{
showEdit
}
handleClose
=
{
closeEdit
}
><
/EditRedemption
>
<
Form
onSubmit
=
{
searchRedemptions
}
>
<
Form
.
Input
label
=
'搜索关键字'
field
=
'keyword'
icon
=
'search'
iconPosition
=
'left'
placeholder
=
'关键字(id或者名称)'
value
=
{
searchKeyword
}
loading
=
{
searching
}
onChange
=
{
handleKeywordChange
}
/
>
<
/Form
>
<
Table
style
=
{{
marginTop
:
20
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
pageSize
:
ITEMS_PER_PAGE
,
total
:
tokenCount
,
// showSizeChanger: true,
// pageSizeOptions: [10, 20, 50, 100],
formatPageText
:
(
page
)
=>
`第
${
page
.
currentStart
}
-
${
page
.
currentEnd
}
条,共
${
redemptions
.
length
}
条`
,
// onPageSizeChange: (size) => {
// setPageSize(size);
// setActivePage(1);
// },
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
rowSelection
=
{
rowSelection
}
>
<
/Table
>
<
/
>
);
<
Table
style
=
{{
marginTop
:
20
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
pageSize
:
ITEMS_PER_PAGE
,
total
:
tokenCount
,
// showSizeChanger: true,
// pageSizeOptions: [10, 20, 50, 100],
formatPageText
:
(
page
)
=>
`第
${
page
.
currentStart
}
-
${
page
.
currentEnd
}
条,共
${
redemptions
.
length
}
条`
,
// onPageSizeChange: (size) => {
// setPageSize(size);
// setActivePage(1);
// },
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
rowSelection
=
{
rowSelection
}
>
<
/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
;
web/src/components/TokensTable.js
View file @
4c1a23d6
...
...
@@ -179,6 +179,7 @@ const TokensTable = () => {
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
activePage
,
setActivePage
]
=
useState
(
1
);
const
[
searchKeyword
,
setSearchKeyword
]
=
useState
(
''
);
const
[
searchToken
,
setSearchToken
]
=
useState
(
''
);
const
[
searching
,
setSearching
]
=
useState
(
false
);
const
[
showTopUpModal
,
setShowTopUpModal
]
=
useState
(
false
);
const
[
targetTokenIdx
,
setTargetTokenIdx
]
=
useState
(
0
);
...
...
@@ -188,9 +189,6 @@ const TokensTable = () => {
const
closeEdit
=
()
=>
{
setShowEdit
(
false
);
// setEditingToken({
// id: undefined,
// });
}
const
setTokensFormat
=
(
tokens
)
=>
{
...
...
@@ -401,6 +399,10 @@ const TokensTable = () => {
setSearchKeyword
(
value
.
trim
());
};
const
handleSearchTokenChange
=
async
(
value
)
=>
{
setSearchToken
(
value
.
trim
());
};
const
sortToken
=
(
key
)
=>
{
if
(
tokens
.
length
===
0
)
return
;
setLoading
(
true
);
...
...
@@ -447,6 +449,14 @@ const TokensTable = () => {
loading
=
{
searching
}
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"
onClick
=
{
searchTokens
}
style
=
{{
marginRight
:
8
}}
>
查询
<
/Button
>
<
/Form
>
...
...
web/src/pages/Redemption/EditRedemption.js
View file @
4c1a23d6
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
Button
,
Form
,
Header
,
Segment
}
from
'semantic-ui-react'
;
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
{
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
params
=
useParams
();
const
navigate
=
useNavigate
();
const
redemptionId
=
params
.
id
;
const
isEdit
=
redemptionId
!==
undefined
;
const
EditRedemption
=
(
props
)
=>
{
const
isEdit
=
props
.
editingRedemption
.
id
!==
undefined
;
const
[
loading
,
setLoading
]
=
useState
(
isEdit
);
const
params
=
useParams
();
const
navigate
=
useNavigate
()
const
originInputs
=
{
name
:
''
,
quota
:
100000
,
...
...
@@ -19,15 +21,16 @@ const EditRedemption = () => {
const
{
name
,
quota
,
count
}
=
inputs
;
const
handleCancel
=
()
=>
{
navigate
(
'/redemption'
);
}
;
props
.
handleClose
(
);
}
const
handleInputChange
=
(
e
,
{
name
,
value
}
)
=>
{
const
handleInputChange
=
(
name
,
value
)
=>
{
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
};
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
;
if
(
success
)
{
setInputs
(
data
);
...
...
@@ -36,20 +39,28 @@ const EditRedemption = () => {
}
setLoading
(
false
);
};
useEffect
(()
=>
{
if
(
isEdit
)
{
loadRedemption
().
then
();
loadRedemption
().
then
(
()
=>
{
// console.log(inputs);
}
);
}
else
{
setInputs
(
originInputs
);
}
},
[]);
},
[
props
.
editingRedemption
.
id
]);
const
submit
=
async
()
=>
{
if
(
!
isEdit
&&
inputs
.
name
===
''
)
return
;
setLoading
(
true
);
let
localInputs
=
inputs
;
localInputs
.
count
=
parseInt
(
localInputs
.
count
);
localInputs
.
quota
=
parseInt
(
localInputs
.
quota
);
let
res
;
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
{
res
=
await
API
.
post
(
`/api/redemption/`
,
{
...
localInputs
...
...
@@ -59,9 +70,13 @@ const EditRedemption = () => {
if
(
success
)
{
if
(
isEdit
)
{
showSuccess
(
'兑换码更新成功!'
);
props
.
refresh
();
props
.
handleClose
();
}
else
{
showSuccess
(
'兑换码创建成功!'
);
setInputs
(
originInputs
);
props
.
refresh
();
props
.
handleClose
();
}
}
else
{
showError
(
message
);
...
...
@@ -71,56 +86,94 @@ const EditRedemption = () => {
for
(
let
i
=
0
;
i
<
data
.
length
;
i
++
)
{
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
(
<>
<
Segment
loading
=
{
loading
}
>
<
Header
as
=
'h3'
>
{
isEdit
?
'更新兑换码信息'
:
'创建新的兑换码'
}
<
/Header
>
<
Form
autoComplete
=
'new-password'
>
<
Form
.
Field
>
<
Form
.
Input
<
SideSheet
placement
=
{
isEdit
?
'right'
:
'left'
}
title
=
{
<
Title
level
=
{
3
}
>
{
isEdit
?
'更新兑换码信息'
:
'创建新的兑换码'
}
<
/Title>
}
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
}}
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
=
'名称'
name
=
'name'
placeholder
=
{
'请输入名称'
}
onChange
=
{
handleInputChange
}
onChange
=
{
value
=>
handleInputChange
(
'name'
,
value
)
}
value
=
{
name
}
autoComplete
=
'new-password'
required
=
{
!
isEdit
}
/
>
<
/Form.Field
>
<
Form
.
Field
>
<
Form
.
Input
label
=
{
`额度
${
renderQuotaWithPrompt
(
quota
)}
`
}
/
>
<
Divider
/>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
{
`额度
${
renderQuotaWithPrompt
(
quota
)}
`
}
<
/Typography.Text
>
<
/div
>
<
AutoComplete
style
=
{{
marginTop
:
8
}}
name
=
'quota'
placeholder
=
{
'请输入
单个兑换码中包含的
额度'
}
onChange
=
{
handleInputChange
}
placeholder
=
{
'请输入额度'
}
onChange
=
{
(
value
)
=>
handleInputChange
(
'quota'
,
value
)
}
value
=
{
quota
}
autoComplete
=
'new-password'
type
=
'number'
/>
<
/Form.Field
>
position
=
{
'bottom'
}
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
&&
<>
<
Form
.
Field
>
<
Form
.
Input
label
=
'生成数量'
name
=
'count'
placeholder
=
{
'请输入生成数量'
}
onChange
=
{
handleInputChange
}
value
=
{
count
}
autoComplete
=
'new-password'
type
=
'number'
!
isEdit
&&
<>
<
Divider
/>
<
Typography
.
Text
>
生成数量
<
/Typography.Text
>
<
Input
style
=
{{
marginTop
:
8
}}
label
=
'生成数量'
name
=
'count'
placeholder
=
{
'请输入生成数量'
}
onChange
=
{
value
=>
handleInputChange
(
'count'
,
value
)}
value
=
{
count
}
autoComplete
=
'new-password'
type
=
'number'
/>
<
/Form.Field
>
<
/
>
<
/
>
}
<
Button
positive
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
onClick
=
{
handleCancel
}
>
取消
<
/Button
>
<
/Form
>
<
/Segment
>
<
/Spin
>
<
/SideSheet
>
<
/
>
);
};
...
...
web/src/pages/Redemption/index.js
View file @
4c1a23d6
import
React
from
'react'
;
import
{
Segment
,
Header
}
from
'semantic-ui-react'
;
import
RedemptionsTable
from
'../../components/RedemptionsTable'
;
import
TokensTable
from
"../../components/TokensTable"
;
import
{
Layout
}
from
"@douyinfe/semi-ui"
;
const
Redemption
=
()
=>
(
<>
<
Segment
>
<
Header
as
=
'h3'
>
管理兑换码
<
/Header
>
<
RedemptionsTable
/>
<
/Segment
>
<
Layout
>
<
Layout
.
Header
>
<
h3
>
管理兑换码
<
/h3
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
RedemptionsTable
/>
<
/Layout.Content
>
<
/Layout
>
<
/
>
);
...
...
web/src/pages/Token/EditToken.js
View file @
4c1a23d6
...
...
@@ -63,7 +63,7 @@ const EditToken = (props) => {
if
(
isEdit
)
{
loadToken
().
then
(
()
=>
{
console
.
log
(
inputs
);
//
console.log(inputs);
}
);
}
else
{
...
...
@@ -72,8 +72,8 @@ const EditToken = (props) => {
},
[
props
.
editingToken
.
id
]);
const
submit
=
async
()
=>
{
setLoading
(
true
);
if
(
!
isEdit
&&
inputs
.
name
===
''
)
return
;
setLoading
(
true
);
let
localInputs
=
inputs
;
localInputs
.
remain_quota
=
parseInt
(
localInputs
.
remain_quota
);
if
(
localInputs
.
expired_time
!==
-
1
)
{
...
...
web/src/pages/Token/index.js
View file @
4c1a23d6
import
React
from
'react'
;
import
TokensTable
from
'../../components/TokensTable'
;
import
{
Layout
}
from
"@douyinfe/semi-ui"
;
const
{
Content
,
Header
}
=
Layout
;
const
Token
=
()
=>
(
<>
<
Layout
>
<
Header
>
<
Layout
.
Header
>
<
h3
>
我的令牌
<
/h3
>
<
/Header
>
<
Content
>
<
/
Layout.
Header
>
<
Layout
.
Content
>
<
TokensTable
/>
<
/Content
>
<
/
Layout.
Content
>
<
/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