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
04ba012f
authored
May 23, 2025
by
Apple\Apple
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
♻
️Refactor: Redemptions Page
parent
07d87d60
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
242 additions
and
164 deletions
+242
-164
web/src/components/RedemptionsTable.js
+237
-150
web/src/i18n/locales/en.json
+3
-1
web/src/pages/Redemption/index.js
+1
-11
web/src/pages/Token/index.js
+1
-2
No files found.
web/src/components/RedemptionsTable.js
View file @
04ba012f
...
...
@@ -11,17 +11,33 @@ import { ITEMS_PER_PAGE } from '../constants';
import
{
renderQuota
}
from
'../helpers/render'
;
import
{
Button
,
Card
,
Divider
,
Form
,
Dropdown
,
Input
,
Modal
,
Popconfirm
,
Popover
,
Space
,
Table
,
Tag
,
Typography
,
}
from
'@douyinfe/semi-ui'
;
import
{
IconPlus
,
IconCopy
,
IconSearch
,
IconEyeOpened
,
IconEdit
,
IconDelete
,
IconStop
,
IconPlay
,
IconMore
,
}
from
'@douyinfe/semi-icons'
;
import
EditRedemption
from
'../pages/Redemption/EditRedemption'
;
import
{
useTranslation
}
from
'react-i18next'
;
const
{
Text
}
=
Typography
;
function
renderTimestamp
(
timestamp
)
{
return
<>
{
timestamp2string
(
timestamp
)}
<
/>
;
}
...
...
@@ -33,25 +49,25 @@ const RedemptionsTable = () => {
switch
(
status
)
{
case
1
:
return
(
<
Tag
color
=
'green'
size
=
'large'
>
<
Tag
color
=
'green'
size
=
'large'
shape
=
'circle'
>
{
t
(
'未使用'
)}
<
/Tag
>
);
case
2
:
return
(
<
Tag
color
=
'red'
size
=
'large'
>
<
Tag
color
=
'red'
size
=
'large'
shape
=
'circle'
>
{
t
(
'已禁用'
)}
<
/Tag
>
);
case
3
:
return
(
<
Tag
color
=
'grey'
size
=
'large'
>
<
Tag
color
=
'grey'
size
=
'large'
shape
=
'circle'
>
{
t
(
'已使用'
)}
<
/Tag
>
);
default
:
return
(
<
Tag
color
=
'black'
size
=
'large'
>
<
Tag
color
=
'black'
size
=
'large'
shape
=
'circle'
>
{
t
(
'未知状态'
)}
<
/Tag
>
);
...
...
@@ -99,76 +115,107 @@ const RedemptionsTable = () => {
{
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
}}
>
{
t
(
'查看'
)}
<
/Button
>
<
/Popover
>
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
(
text
)
=>
{
await
copyText
(
record
.
key
);
}}
>
{
t
(
'复制'
)}
<
/Button
>
<
Popconfirm
title
=
{
t
(
'确定是否要删除此兑换码?'
)}
content
=
{
t
(
'此修改将不可逆'
)}
okType
=
{
'danger'
}
position
=
{
'left'
}
onConfirm
=
{()
=>
{
manageRedemption
(
record
.
id
,
'delete'
,
record
).
then
(()
=>
{
removeRecord
(
record
.
key
);
render
:
(
text
,
record
,
index
)
=>
{
// 创建更多操作的下拉菜单项
const
moreMenuItems
=
[
{
node
:
'item'
,
name
:
t
(
'删除'
),
icon
:
<
IconDelete
/>
,
type
:
'danger'
,
onClick
:
()
=>
{
Modal
.
confirm
({
title
:
t
(
'确定是否要删除此兑换码?'
),
content
:
t
(
'此修改将不可逆'
),
onOk
:
()
=>
{
manageRedemption
(
record
.
id
,
'delete'
,
record
).
then
(()
=>
{
removeRecord
(
record
.
key
);
});
},
});
}}
>
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
1
}}
>
{
t
(
'删除'
)}
<
/Button
>
<
/Popconfirm
>
{
record
.
status
===
1
?
(
},
}
];
// 动态添加启用/禁用按钮
if
(
record
.
status
===
1
)
{
moreMenuItems
.
push
({
node
:
'item'
,
name
:
t
(
'禁用'
),
icon
:
<
IconStop
/>
,
type
:
'warning'
,
onClick
:
()
=>
{
manageRedemption
(
record
.
id
,
'disable'
,
record
);
},
});
}
else
{
moreMenuItems
.
push
({
node
:
'item'
,
name
:
t
(
'启用'
),
icon
:
<
IconPlay
/>
,
type
:
'secondary'
,
onClick
:
()
=>
{
manageRedemption
(
record
.
id
,
'enable'
,
record
);
},
disabled
:
record
.
status
===
3
,
});
}
return
(
<
Space
>
<
Popover
content
=
{
record
.
key
}
style
=
{{
padding
:
20
}}
position
=
'top'
>
<
Button
icon
=
{
<
IconEyeOpened
/>
}
theme
=
'light'
type
=
'tertiary'
size
=
"small"
className
=
"!rounded-full"
>
{
t
(
'查看'
)}
<
/Button
>
<
/Popover
>
<
Button
icon
=
{
<
IconCopy
/>
}
theme
=
'light'
type
=
'warning'
style
=
{{
marginRight
:
1
}}
type
=
'secondary'
size
=
"small"
className
=
"!rounded-full"
onClick
=
{
async
()
=>
{
manageRedemption
(
record
.
id
,
'disable'
,
record
);
await
copyText
(
record
.
key
);
}}
>
{
t
(
'
禁用
'
)}
{
t
(
'
复制
'
)}
<
/Button
>
)
:
(
<
Button
icon
=
{
<
IconEdit
/>
}
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageRedemption
(
record
.
id
,
'enable'
,
record
);
type
=
'tertiary'
size
=
"small"
className
=
"!rounded-full"
onClick
=
{()
=>
{
setEditingRedemption
(
record
);
setShowEdit
(
true
);
}}
disabled
=
{
record
.
status
===
3
}
disabled
=
{
record
.
status
!==
1
}
>
{
t
(
'
启用
'
)}
{
t
(
'
编辑
'
)}
<
/Button
>
)}
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
onClick
=
{()
=>
{
setEditingRedemption
(
record
);
setShowEdit
(
true
);
}}
disabled
=
{
record
.
status
!==
1
}
>
{
t
(
'编辑'
)}
<
/Button
>
<
/div
>
),
<
Dropdown
trigger
=
'click'
position
=
'bottomRight'
menu
=
{
moreMenuItems
}
>
<
Button
icon
=
{
<
IconMore
/>
}
theme
=
'light'
type
=
'tertiary'
size
=
"small"
className
=
"!rounded-full"
/>
<
/Dropdown
>
<
/Space
>
);
},
},
];
...
...
@@ -187,6 +234,11 @@ const RedemptionsTable = () => {
const
closeEdit
=
()
=>
{
setShowEdit
(
false
);
setTimeout
(()
=>
{
setEditingRedemption
({
id
:
undefined
,
});
},
500
);
};
const
setRedemptionFormat
=
(
redeptions
)
=>
{
...
...
@@ -225,8 +277,11 @@ const RedemptionsTable = () => {
if
(
await
copy
(
text
))
{
showSuccess
(
t
(
'已复制到剪贴板!'
));
}
else
{
// setSearchKeyword(text);
Modal
.
error
({
title
:
t
(
'无法复制到剪贴板,请手动复制'
),
content
:
text
});
Modal
.
error
({
title
:
t
(
'无法复制到剪贴板,请手动复制'
),
content
:
text
,
size
:
'large'
});
}
};
...
...
@@ -245,13 +300,14 @@ const RedemptionsTable = () => {
.
catch
((
reason
)
=>
{
showError
(
reason
);
});
},
[]);
},
[
pageSize
]);
const
refresh
=
async
()
=>
{
await
loadRedemptions
(
activePage
-
1
,
pageSize
);
};
const
manageRedemption
=
async
(
id
,
action
,
record
)
=>
{
setLoading
(
true
);
let
data
=
{
id
};
let
res
;
switch
(
action
)
{
...
...
@@ -272,7 +328,6 @@ const RedemptionsTable = () => {
showSuccess
(
t
(
'操作成功完成!'
));
let
redemption
=
res
.
data
.
data
;
let
newRedemptions
=
[...
redemptions
];
// let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
if
(
action
===
'delete'
)
{
}
else
{
record
.
status
=
redemption
.
status
;
...
...
@@ -281,6 +336,7 @@ const RedemptionsTable = () => {
}
else
{
showError
(
message
);
}
setLoading
(
false
);
};
const
searchRedemptions
=
async
(
keyword
,
page
,
pageSize
)
=>
{
...
...
@@ -333,8 +389,8 @@ const RedemptionsTable = () => {
let
pageData
=
redemptions
;
const
rowSelection
=
{
onSelect
:
(
record
,
selected
)
=>
{},
onSelectAll
:
(
selected
,
selectedRows
)
=>
{},
onSelect
:
(
record
,
selected
)
=>
{
},
onSelectAll
:
(
selected
,
selectedRows
)
=>
{
},
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
setSelectedKeys
(
selectedRows
);
},
...
...
@@ -352,6 +408,80 @@ const RedemptionsTable = () => {
}
};
const
renderHeader
=
()
=>
(
<
div
className
=
"flex flex-col w-full"
>
<
div
className
=
"mb-2"
>
<
div
className
=
"flex items-center text-orange-500"
>
<
IconEyeOpened
className
=
"mr-2"
/>
<
Text
>
{
t
(
'兑换码可以批量生成和分发,适合用于推广活动或批量充值。'
)}
<
/Text
>
<
/div
>
<
/div
>
<
Divider
margin
=
"12px"
/>
<
div
className
=
"flex flex-col md:flex-row justify-between items-center gap-4 w-full"
>
<
div
className
=
"flex gap-2 w-full md:w-auto order-2 md:order-1"
>
<
Button
theme
=
'light'
type
=
'primary'
icon
=
{
<
IconPlus
/>
}
className
=
"!rounded-full w-full md:w-auto"
onClick
=
{()
=>
{
setEditingRedemption
({
id
:
undefined
,
});
setShowEdit
(
true
);
}}
>
{
t
(
'添加兑换码'
)}
<
/Button
>
<
Button
type
=
'warning'
icon
=
{
<
IconCopy
/>
}
className
=
"!rounded-full w-full md:w-auto"
onClick
=
{
async
()
=>
{
if
(
selectedKeys
.
length
===
0
)
{
showError
(
t
(
'请至少选择一个兑换码!'
));
return
;
}
let
keys
=
''
;
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
keys
+=
selectedKeys
[
i
].
name
+
' '
+
selectedKeys
[
i
].
key
+
'\n'
;
}
await
copyText
(
keys
);
}}
>
{
t
(
'复制所选兑换码到剪贴板'
)}
<
/Button
>
<
/div
>
<
div
className
=
"flex flex-col md:flex-row items-center gap-4 w-full md:w-auto order-1 md:order-2"
>
<
div
className
=
"relative w-full md:w-64"
>
<
Input
prefix
=
{
<
IconSearch
/>
}
placeholder
=
{
t
(
'关键字(id或者名称)'
)}
value
=
{
searchKeyword
}
onChange
=
{
handleKeywordChange
}
className
=
"!rounded-full"
showClear
/>
<
/div
>
<
Button
type
=
"primary"
onClick
=
{()
=>
{
searchRedemptions
(
searchKeyword
,
1
,
pageSize
).
then
();
}}
loading
=
{
searching
}
className
=
"!rounded-full w-full md:w-auto"
>
{
t
(
'查询'
)}
<
/Button
>
<
/div
>
<
/div
>
<
/div
>
);
return
(
<>
<
EditRedemption
...
...
@@ -360,88 +490,45 @@ const RedemptionsTable = () => {
visiable
=
{
showEdit
}
handleClose
=
{
closeEdit
}
><
/EditRedemption
>
<
Form
onSubmit
=
{()
=>
{
searchRedemptions
(
searchKeyword
,
activePage
,
pageSize
).
then
();
}}
<
Card
className
=
"!rounded-2xl overflow-hidden"
title
=
{
renderHeader
()}
shadows
=
'hover'
>
<
Form
.
Input
label
=
{
t
(
'搜索关键字'
)}
field
=
'keyword'
icon
=
'search'
iconPosition
=
'left'
placeholder
=
{
t
(
'关键字(id或者名称)'
)}
value
=
{
searchKeyword
}
loading
=
{
searching
}
onChange
=
{
handleKeywordChange
}
/
>
<
/Form
>
<
Divider
style
=
{{
margin
:
'5px 0 15px 0'
}}
/
>
<
div
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{()
=>
{
setEditingRedemption
({
id
:
undefined
,
});
setShowEdit
(
true
);
}}
>
{
t
(
'添加兑换码'
)}
<
/Button
>
<
Button
label
=
{
t
(
'复制所选兑换码'
)}
type
=
'warning'
onClick
=
{
async
()
=>
{
if
(
selectedKeys
.
length
===
0
)
{
showError
(
t
(
'请至少选择一个兑换码!'
));
return
;
}
let
keys
=
''
;
for
(
let
i
=
0
;
i
<
selectedKeys
.
length
;
i
++
)
{
keys
+=
selectedKeys
[
i
].
name
+
' '
+
selectedKeys
[
i
].
key
+
'\n'
;
}
await
copyText
(
keys
);
<
Table
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
pageSize
:
pageSize
,
total
:
tokenCount
,
showSizeChanger
:
true
,
pageSizeOptions
:
[
10
,
20
,
50
,
100
],
formatPageText
:
(
page
)
=>
t
(
'第 {{start}} - {{end}} 条,共 {{total}} 条'
,
{
start
:
page
.
currentStart
,
end
:
page
.
currentEnd
,
total
:
tokenCount
,
}),
onPageSizeChange
:
(
size
)
=>
{
setPageSize
(
size
);
setActivePage
(
1
);
if
(
searchKeyword
===
''
)
{
loadRedemptions
(
1
,
size
).
then
();
}
else
{
searchRedemptions
(
searchKeyword
,
1
,
size
).
then
();
}
},
onPageChange
:
handlePageChange
,
}}
>
{
t
(
'复制所选兑换码到剪贴板'
)}
<
/Button
>
<
/div
>
<
Table
style
=
{{
marginTop
:
20
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
pageSize
:
pageSize
,
total
:
tokenCount
,
showSizeChanger
:
true
,
pageSizeOpts
:
[
10
,
20
,
50
,
100
],
formatPageText
:
(
page
)
=>
t
(
'第 {{start}} - {{end}} 条,共 {{total}} 条'
,
{
start
:
page
.
currentStart
,
end
:
page
.
currentEnd
,
total
:
tokenCount
,
}),
onPageSizeChange
:
(
size
)
=>
{
setPageSize
(
size
);
setActivePage
(
1
);
if
(
searchKeyword
===
''
)
{
loadRedemptions
(
1
,
size
).
then
();
}
else
{
searchRedemptions
(
searchKeyword
,
1
,
size
).
then
();
}
},
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
rowSelection
=
{
rowSelection
}
onRow
=
{
handleRow
}
><
/Table
>
loading
=
{
loading
}
rowSelection
=
{
rowSelection
}
onRow
=
{
handleRow
}
className
=
"rounded-xl overflow-hidden"
size
=
"middle"
><
/Table
>
<
/Card
>
<
/
>
);
};
...
...
web/src/i18n/locales/en.json
View file @
04ba012f
...
...
@@ -1432,5 +1432,6 @@
"30个"
:
"30 items"
,
"100个"
:
"100 items"
,
"Midjourney 任务记录"
:
"Midjourney Task Records"
,
"任务记录"
:
"Task Records"
"任务记录"
:
"Task Records"
,
"兑换码可以批量生成和分发,适合用于推广活动或批量充值。"
:
"Redemption codes can be batch generated and distributed, suitable for promotion activities or bulk recharge."
}
\ No newline at end of file
web/src/pages/Redemption/index.js
View file @
04ba012f
import
React
from
'react'
;
import
RedemptionsTable
from
'../../components/RedemptionsTable'
;
import
{
Layout
}
from
'@douyinfe/semi-ui'
;
import
{
useTranslation
}
from
'react-i18next'
;
const
Redemption
=
()
=>
{
const
{
t
}
=
useTranslation
();
return
(
<>
<
Layout
>
<
Layout
.
Header
>
<
h3
>
{
t
(
'管理兑换码'
)}
<
/h3
>
<
/Layout.Header
>
<
Layout
.
Content
>
<
RedemptionsTable
/>
<
/Layout.Content
>
<
/Layout
>
<
RedemptionsTable
/>
<
/
>
);
};
...
...
web/src/pages/Token/index.js
View file @
04ba012f
import
React
from
'react'
;
import
TokensTable
from
'../../components/TokensTable'
;
import
{
useTranslation
}
from
'react-i18next'
;
const
Token
=
()
=>
{
const
{
t
}
=
useTranslation
();
return
(
<>
<
TokensTable
/>
...
...
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