[x] 当用户充值达到5刀时,自动提升为vip分组,具体金额可在web\src\pages\TopUp\index.js
调整;
[x] 修改颜色 [x] 日志页面新增快速筛选日期、修复渠道ID查询、自动更新消耗额度、删除日志详情(因为没有太大意义); [x] 用户管理界面,支持快速设置用户组,在`web\src\components\UsersTable.js`处修改相关值; [x] 令牌界面,删除无用的多种复制、聊天等按钮; [x] 删除 系统访问令牌;
This commit is contained in:
parent
eb82fb79a2
commit
825374c334
14
README.md
14
README.md
@ -1,3 +1,17 @@
|
||||
## 自定义部分
|
||||
|
||||
[x] 当用户充值达到5刀时,自动提升为vip分组,具体金额可在`web\src\pages\TopUp\index.js`调整;
|
||||
[x] 修改颜色
|
||||
[x] 日志页面新增快速筛选日期、修复渠道ID查询、自动更新消耗额度、删除日志详情(因为没有太大意义);
|
||||
[x] 用户管理界面,支持快速设置用户组,在`web\src\components\UsersTable.js`处修改相关值;
|
||||
[x] 令牌界面,删除无用的多种复制、聊天等按钮;
|
||||
[x] 删除 系统访问令牌;
|
||||
[ ] 在个人设置页面显示分组;
|
||||
[ ] 渠道管理处,已启用渠道和禁用渠道, 启用/禁用 按钮改为不同颜色;
|
||||
|
||||
[x] 等
|
||||
|
||||
---
|
||||
<p align="right">
|
||||
<strong>中文</strong> | <a href="./README.en.md">English</a> | <a href="./README.ja.md">日本語</a>
|
||||
</p>
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"one-api/common"
|
||||
"one-api/model"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -554,10 +553,128 @@ func CreateUser(c *gin.Context) {
|
||||
|
||||
type ManageRequest struct {
|
||||
Username string `json:"username"`
|
||||
Action string `json:"action"`
|
||||
NewGroup string `json:"newGroup"`
|
||||
}
|
||||
|
||||
// ManageUser Only admin user can do this
|
||||
// func ManageUser(c *gin.Context) {
|
||||
// var req ManageRequest
|
||||
// err := json.NewDecoder(c.Request.Body).Decode(&req)
|
||||
|
||||
// if err != nil {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": "无效的参数",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// user := model.User{
|
||||
// Username: req.Username,
|
||||
// }
|
||||
// // Fill attributes
|
||||
// model.DB.Where(&user).First(&user)
|
||||
// if user.Id == 0 {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": "用户不存在",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// myRole := c.GetInt("role")
|
||||
// if myRole <= user.Role && myRole != common.RoleRootUser {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": "无权更新同权限等级或更高权限等级的用户信息",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// switch req.Action {
|
||||
// case "disable":
|
||||
// user.Status = common.UserStatusDisabled
|
||||
// if user.Role == common.RoleRootUser {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": "无法禁用超级管理员用户",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// case "enable":
|
||||
// user.Status = common.UserStatusEnabled
|
||||
// case "delete":
|
||||
// if user.Role == common.RoleRootUser {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": "无法删除超级管理员用户",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// if err := user.Delete(); err != nil {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": err.Error(),
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// case "promote":
|
||||
// if myRole != common.RoleRootUser {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": "普通管理员用户无法提升其他用户为管理员",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// if user.Role >= common.RoleAdminUser {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": "该用户已经是管理员",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// user.Role = common.RoleAdminUser
|
||||
// case "demote":
|
||||
// if user.Role == common.RoleRootUser {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": "无法降级超级管理员用户",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// if user.Role == common.RoleCommonUser {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": "该用户已经是普通用户",
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
// user.Role = common.RoleCommonUser
|
||||
// }
|
||||
|
||||
// if err := user.Update(false); err != nil {
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": false,
|
||||
// "message": err.Error(),
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
// user.Group = req.NewGroup
|
||||
|
||||
|
||||
// clearUser := model.User{
|
||||
// Group: user.Group,
|
||||
// Role: user.Role,
|
||||
// Status: user.Status,
|
||||
// }
|
||||
// c.JSON(http.StatusOK, gin.H{
|
||||
// "success": true,
|
||||
// "message": "",
|
||||
// "data": clearUser,
|
||||
// })
|
||||
// return
|
||||
|
||||
|
||||
|
||||
// }
|
||||
func ManageUser(c *gin.Context) {
|
||||
var req ManageRequest
|
||||
err := json.NewDecoder(c.Request.Body).Decode(&req)
|
||||
@ -569,10 +686,11 @@ func ManageUser(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user := model.User{
|
||||
Username: req.Username,
|
||||
}
|
||||
// Fill attributes
|
||||
|
||||
model.DB.Where(&user).First(&user)
|
||||
if user.Id == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@ -581,6 +699,7 @@ func ManageUser(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
myRole := c.GetInt("role")
|
||||
if myRole <= user.Role && myRole != common.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@ -589,66 +708,9 @@ func ManageUser(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
switch req.Action {
|
||||
case "disable":
|
||||
user.Status = common.UserStatusDisabled
|
||||
if user.Role == common.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法禁用超级管理员用户",
|
||||
})
|
||||
return
|
||||
}
|
||||
case "enable":
|
||||
user.Status = common.UserStatusEnabled
|
||||
case "delete":
|
||||
if user.Role == common.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法删除超级管理员用户",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err := user.Delete(); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
case "promote":
|
||||
if myRole != common.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "普通管理员用户无法提升其他用户为管理员",
|
||||
})
|
||||
return
|
||||
}
|
||||
if user.Role >= common.RoleAdminUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该用户已经是管理员",
|
||||
})
|
||||
return
|
||||
}
|
||||
user.Role = common.RoleAdminUser
|
||||
case "demote":
|
||||
if user.Role == common.RoleRootUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "无法降级超级管理员用户",
|
||||
})
|
||||
return
|
||||
}
|
||||
if user.Role == common.RoleCommonUser {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "该用户已经是普通用户",
|
||||
})
|
||||
return
|
||||
}
|
||||
user.Role = common.RoleCommonUser
|
||||
}
|
||||
|
||||
// 更新用户分组
|
||||
user.Group = req.NewGroup
|
||||
|
||||
if err := user.Update(false); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
@ -657,10 +719,13 @@ func ManageUser(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
clearUser := model.User{
|
||||
Group: user.Group,
|
||||
Role: user.Role,
|
||||
Status: user.Status,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@ -669,6 +734,7 @@ func ManageUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func EmailBind(c *gin.Context) {
|
||||
email := c.Query("email")
|
||||
code := c.Query("code")
|
||||
|
@ -30,21 +30,22 @@ function renderType(type) {
|
||||
function renderBalance(type, balance) {
|
||||
switch (type) {
|
||||
case 1: // OpenAI
|
||||
return <span>${balance.toFixed(2)}</span>;
|
||||
return <span style={{ color: 'var(--czl-primary-color)' }}>${balance.toFixed(2)}</span>;
|
||||
case 4: // CloseAI
|
||||
return <span>¥{balance.toFixed(2)}</span>;
|
||||
return <span style={{ color: 'var(--czl-primary-color-hover)' }}>¥{balance.toFixed(2)}</span>;
|
||||
case 8: // 自定义
|
||||
return <span>${balance.toFixed(2)}</span>;
|
||||
return <span style={{ color: 'var(--czl-primary-color-pressed)' }}>${balance.toFixed(2)}</span>;
|
||||
case 5: // OpenAI-SB
|
||||
return <span>¥{(balance / 10000).toFixed(2)}</span>;
|
||||
return <span style={{ color: 'var(--czl-primary-color-suppl)' }}>¥{(balance / 10000).toFixed(2)}</span>;
|
||||
case 10: // AI Proxy
|
||||
return <span>{renderNumber(balance)}</span>;
|
||||
return <span style={{ color: 'var(--czl-success-color)' }}>{renderNumber(balance)}</span>;
|
||||
case 12: // API2GPT
|
||||
return <span>¥{balance.toFixed(2)}</span>;
|
||||
return <span style={{ color: 'var(--czl-error-color)' }}>¥{balance.toFixed(2)}</span>;
|
||||
case 13: // AIGC2D
|
||||
return <span>{renderNumber(balance)}</span>;
|
||||
return <span style={{ color: 'var(--czl-warning-color)' }}>{renderNumber(balance)}</span>;
|
||||
default:
|
||||
return <span>不支持</span>;
|
||||
return <span style={{ color: 'var(--czl-info-color)' }}>不支持</span>;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,11 +151,11 @@ const ChannelsTable = () => {
|
||||
const renderStatus = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return <Label basic color='green'>已启用</Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-success-color)' }}>已启用</Label>;
|
||||
case 2:
|
||||
return (
|
||||
<Popup
|
||||
trigger={<Label basic color='red'>
|
||||
trigger={<Label basic style={{ color: 'var(--czl-error-color)' }}>
|
||||
已禁用
|
||||
</Label>}
|
||||
content='本渠道被手动禁用'
|
||||
@ -164,7 +165,7 @@ const ChannelsTable = () => {
|
||||
case 3:
|
||||
return (
|
||||
<Popup
|
||||
trigger={<Label basic color='yellow'>
|
||||
trigger={<Label basic style={{ color: 'var(--czl-warning-color)' }}>
|
||||
已禁用
|
||||
</Label>}
|
||||
content='本渠道被程序自动禁用'
|
||||
@ -173,10 +174,11 @@ const ChannelsTable = () => {
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Label basic color='grey'>
|
||||
<Label basic style={{ color: 'var(--czl-grayC)' }}>
|
||||
未知状态
|
||||
</Label>
|
||||
);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -184,15 +186,15 @@ const ChannelsTable = () => {
|
||||
let time = responseTime / 1000;
|
||||
time = time.toFixed(2) + ' 秒';
|
||||
if (responseTime === 0) {
|
||||
return <Label basic color='grey'>未测试</Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-grayA)' }}>未测试</Label>;
|
||||
} else if (responseTime <= 1000) {
|
||||
return <Label basic color='green'>{time}</Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-success-color)' }}>{time}</Label>;
|
||||
} else if (responseTime <= 3000) {
|
||||
return <Label basic color='olive'>{time}</Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-primary-color)' }}>{time}</Label>;
|
||||
} else if (responseTime <= 5000) {
|
||||
return <Label basic color='yellow'>{time}</Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-warning-color)' }}>{time}</Label>;
|
||||
} else {
|
||||
return <Label basic color='red'>{time}</Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-error-color)' }}>{time}</Label>;
|
||||
}
|
||||
};
|
||||
|
||||
@ -416,8 +418,8 @@ const ChannelsTable = () => {
|
||||
trigger={<span onClick={() => {
|
||||
updateChannelBalance(channel.id, channel.name, idx);
|
||||
}} style={{ cursor: 'pointer' }}>
|
||||
{renderBalance(channel.type, channel.balance)}
|
||||
</span>}
|
||||
{renderBalance(channel.type, channel.balance)}
|
||||
</span>}
|
||||
content='点击更新'
|
||||
basic
|
||||
/>
|
||||
@ -443,6 +445,7 @@ const ChannelsTable = () => {
|
||||
<Button
|
||||
size={'small'}
|
||||
positive
|
||||
style={{ backgroundColor: 'var(--czl-success-color)' }}
|
||||
onClick={() => {
|
||||
testChannel(channel.id, channel.name, idx);
|
||||
}}
|
||||
@ -451,7 +454,7 @@ const ChannelsTable = () => {
|
||||
</Button>
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative>
|
||||
<Button size='small' negative style={{ backgroundColor: 'var(--czl-error-color)' }}>
|
||||
删除
|
||||
</Button>
|
||||
}
|
||||
@ -461,6 +464,7 @@ const ChannelsTable = () => {
|
||||
>
|
||||
<Button
|
||||
negative
|
||||
style={{ backgroundColor: 'var(--czl-error-color)' }}
|
||||
onClick={() => {
|
||||
manageChannel(channel.id, 'delete', idx);
|
||||
}}
|
||||
@ -469,7 +473,9 @@ const ChannelsTable = () => {
|
||||
</Button>
|
||||
</Popup>
|
||||
<Button
|
||||
negative
|
||||
size={'small'}
|
||||
style={{ backgroundColor: 'var(--czl-warning-color)' }}
|
||||
onClick={() => {
|
||||
manageChannel(
|
||||
channel.id,
|
||||
@ -481,9 +487,11 @@ const ChannelsTable = () => {
|
||||
{channel.status === 1 ? '禁用' : '启用'}
|
||||
</Button>
|
||||
<Button
|
||||
negative
|
||||
size={'small'}
|
||||
as={Link}
|
||||
to={'/channel/edit/' + channel.id}
|
||||
style={{ backgroundColor: 'var(--czl-primary-color)' }}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
@ -504,7 +512,7 @@ const ChannelsTable = () => {
|
||||
测试所有已启用通道
|
||||
</Button>
|
||||
<Button size='small' onClick={updateAllChannelsBalance}
|
||||
loading={loading || updatingBalance}>更新所有已启用通道余额</Button>
|
||||
loading={loading || updatingBalance}>更新所有已启用通道余额</Button>
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' loading={loading}>
|
||||
|
@ -11,53 +11,63 @@ let headerButtons = [
|
||||
{
|
||||
name: '首页',
|
||||
to: '/',
|
||||
icon: 'home'
|
||||
icon: 'home',
|
||||
color: 'var(--czl-primary-color)'
|
||||
},
|
||||
{
|
||||
name: '渠道',
|
||||
to: '/channel',
|
||||
icon: 'sitemap',
|
||||
color: 'var(--czl-primary-color)',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '令牌',
|
||||
to: '/token',
|
||||
icon: 'key'
|
||||
icon: 'key',
|
||||
color: 'var(--czl-primary-color)'
|
||||
},
|
||||
{
|
||||
name: '兑换',
|
||||
to: '/redemption',
|
||||
icon: 'dollar sign',
|
||||
color: 'var(--czl-primary-color)',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '充值',
|
||||
to: '/topup',
|
||||
icon: 'cart'
|
||||
icon: 'cart',
|
||||
color: 'var(--czl-primary-color)'
|
||||
},
|
||||
{
|
||||
name: '用户',
|
||||
to: '/user',
|
||||
icon: 'user',
|
||||
color: 'var(--czl-primary-color)',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '日志',
|
||||
to: '/log',
|
||||
icon: 'book'
|
||||
icon: 'book',
|
||||
color: 'var(--czl-primary-color)'
|
||||
},
|
||||
{
|
||||
name: '设置',
|
||||
to: '/setting',
|
||||
icon: 'setting'
|
||||
icon: 'setting',
|
||||
color: 'var(--czl-primary-color)'
|
||||
},
|
||||
{
|
||||
name: '关于',
|
||||
to: '/about',
|
||||
icon: 'info circle'
|
||||
icon: 'info circle',
|
||||
color: 'var(--czl-primary-color)'
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
if (localStorage.getItem('chat_link')) {
|
||||
headerButtons.splice(1, 0, {
|
||||
name: '聊天',
|
||||
@ -148,11 +158,11 @@ const Header = () => {
|
||||
</Menu>
|
||||
{showSidebar ? (
|
||||
<Segment style={{ marginTop: 0, borderTop: '0' }}>
|
||||
<Menu secondary vertical style={{ width: '100%', margin: 0 }}>
|
||||
<Menu secondary vertical style={{ width: '100%', margin: 0, backgroundColor: 'var(--czl-main)' }}>
|
||||
{renderButtons(true)}
|
||||
<Menu.Item>
|
||||
{userState.user ? (
|
||||
<Button onClick={logout}>注销</Button>
|
||||
<Button onClick={logout} style={{ backgroundColor: 'var(--czl-primary-color)' }}>注销</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
@ -160,6 +170,7 @@ const Header = () => {
|
||||
setShowSidebar(false);
|
||||
navigate('/login');
|
||||
}}
|
||||
style={{ backgroundColor: 'var(--czl-primary-color)' }}
|
||||
>
|
||||
登录
|
||||
</Button>
|
||||
@ -168,6 +179,7 @@ const Header = () => {
|
||||
setShowSidebar(false);
|
||||
navigate('/register');
|
||||
}}
|
||||
style={{ backgroundColor: 'var(--czl-primary-color)' }}
|
||||
>
|
||||
注册
|
||||
</Button>
|
||||
@ -175,6 +187,7 @@ const Header = () => {
|
||||
)}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
|
||||
</Segment>
|
||||
) : (
|
||||
<></>
|
||||
|
@ -98,6 +98,7 @@ const LoginForm = () => {
|
||||
name='username'
|
||||
value={username}
|
||||
onChange={handleChange}
|
||||
style={{ backgroundColor: 'var(--czl-main)', color: 'var(--czl-grayA)' }}
|
||||
/>
|
||||
<Form.Input
|
||||
fluid
|
||||
@ -108,22 +109,29 @@ const LoginForm = () => {
|
||||
type='password'
|
||||
value={password}
|
||||
onChange={handleChange}
|
||||
style={{ backgroundColor: 'var(--czl-main)', color: 'var(--czl-grayA)' }}
|
||||
/>
|
||||
<Button color='green' fluid size='large' onClick={handleSubmit}>
|
||||
<Button
|
||||
fluid
|
||||
size='large'
|
||||
onClick={handleSubmit}
|
||||
style={{ backgroundColor: 'var(--czl-success-color)', color: 'var(--czl-main)' }}
|
||||
>
|
||||
登录
|
||||
</Button>
|
||||
</Segment>
|
||||
</Form>
|
||||
<Message>
|
||||
忘记密码?
|
||||
<Link to='/reset' className='btn btn-link'>
|
||||
<Link to='/reset' className='btn btn-link' style={{ color: 'var(--czl-link-color)' }}>
|
||||
点击重置
|
||||
</Link>
|
||||
; 没有账户?
|
||||
<Link to='/register' className='btn btn-link'>
|
||||
<Link to='/register' className='btn btn-link' style={{ color: 'var(--czl-link-color)' }}>
|
||||
点击注册
|
||||
</Link>
|
||||
</Message>
|
||||
|
||||
{status.github_oauth || status.wechat_login ? (
|
||||
<>
|
||||
<Divider horizontal>Or</Divider>
|
||||
|
@ -29,15 +29,15 @@ const LOG_OPTIONS = [
|
||||
function renderType(type) {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return <Label basic color='green'> 充值 </Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-success-color)' }}> 充值 </Label>;
|
||||
case 2:
|
||||
return <Label basic color='olive'> 消费 </Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-primary-color)' }}> 消费 </Label>;
|
||||
case 3:
|
||||
return <Label basic color='orange'> 管理 </Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-warning-color)' }}> 管理 </Label>;
|
||||
case 4:
|
||||
return <Label basic color='purple'> 系统 </Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-primary-color-suppl-dark)' }}> 系统 </Label>;
|
||||
default:
|
||||
return <Label basic color='black'> 未知 </Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-primary-color-dark)' }}> 未知 </Label>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +70,79 @@ const LogsTable = () => {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
};
|
||||
|
||||
// 快速选时间
|
||||
const handleTimePresetClick = (preset) => {
|
||||
let start, end;
|
||||
|
||||
switch (preset) {
|
||||
case 'today':
|
||||
start = new Date();
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end = new Date();
|
||||
end.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case 'yesterday':
|
||||
start = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case 'week':
|
||||
start = new Date();
|
||||
start.setDate(start.getDate() - start.getDay() + (start.getDay() === 0 ? -6 : 1));
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end = new Date();
|
||||
end.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case 'lastWeek':
|
||||
start = new Date();
|
||||
start.setDate(start.getDate() - start.getDay() + (start.getDay() === 0 ? -13 : -6));
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end = new Date(start);
|
||||
end.setDate(end.getDate() + 6);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case '30days':
|
||||
start = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end = new Date();
|
||||
end.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case 'month':
|
||||
start = new Date();
|
||||
start.setDate(1);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end = new Date();
|
||||
end.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case 'lastMonth':
|
||||
start = new Date();
|
||||
start.setDate(1);
|
||||
start.setMonth(start.getMonth() - 1);
|
||||
start.setHours(0, 0, 0, 0);
|
||||
end = new Date(start);
|
||||
end.setMonth(end.getMonth() + 1);
|
||||
end.setDate(end.getDate() - 1);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
break;
|
||||
case 'reset':
|
||||
start = new Date(0);
|
||||
end = new Date(now.getTime() + 3600 * 1000);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
setInputs((inputs) => ({
|
||||
...inputs,
|
||||
start_timestamp: timestamp2string(start.getTime() / 1000),
|
||||
end_timestamp: timestamp2string(end.getTime() / 1000),
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
|
||||
const getLogSelfStat = async () => {
|
||||
let localStartTimestamp = Date.parse(start_timestamp) / 1000;
|
||||
let localEndTimestamp = Date.parse(end_timestamp) / 1000;
|
||||
@ -146,7 +219,7 @@ const LogsTable = () => {
|
||||
|
||||
useEffect(() => {
|
||||
refresh().then();
|
||||
}, [logType,username, token_name, model_name, channel]);
|
||||
}, [logType, username, token_name, model_name, channel, start_timestamp, end_timestamp]);
|
||||
|
||||
const searchLogs = async () => {
|
||||
if (searchKeyword === '') {
|
||||
@ -198,7 +271,7 @@ const LogsTable = () => {
|
||||
<Segment>
|
||||
<Header as='h3'>
|
||||
使用明细【消耗额度:
|
||||
{renderQuota(stat.quota)}】
|
||||
<span style={{ color: 'var(--czl-primary-color)' }}>{renderQuota(stat.quota)}</span>】
|
||||
</Header>
|
||||
|
||||
<Form>
|
||||
@ -215,20 +288,36 @@ const LogsTable = () => {
|
||||
name='end_timestamp'
|
||||
onChange={handleInputChange} />
|
||||
<Form.Button fluid label='操作' width={2} onClick={refresh}>查询</Form.Button>
|
||||
|
||||
|
||||
</Form.Group>
|
||||
{
|
||||
isAdminUser && <>
|
||||
<Form.Group>
|
||||
<Form.Group>
|
||||
{isAdminUser && (
|
||||
<>
|
||||
<Form.Input fluid label={'渠道 ID'} width={3} value={channel}
|
||||
placeholder='可选值' name='channel'
|
||||
onChange={handleInputChange} />
|
||||
<Form.Input fluid label={'用户名称'} width={3} value={username}
|
||||
placeholder={'可选值'} name='username'
|
||||
onChange={handleInputChange} />
|
||||
</>
|
||||
)}
|
||||
{/* 将按钮组移动到这里 */}
|
||||
<Form.Field>
|
||||
<label>筛选时间</label>
|
||||
<Button.Group>
|
||||
<Button onClick={() => handleTimePresetClick('today')}>今天</Button>
|
||||
<Button onClick={() => handleTimePresetClick('yesterday')}>昨天</Button>
|
||||
<Button onClick={() => handleTimePresetClick('week')}>本周</Button>
|
||||
<Button onClick={() => handleTimePresetClick('lastWeek')}>上周</Button>
|
||||
<Button onClick={() => handleTimePresetClick('month')}>本月</Button>
|
||||
<Button onClick={() => handleTimePresetClick('lastMonth')}>上月</Button>
|
||||
<Button onClick={() => handleTimePresetClick('30days')}>30天内</Button>
|
||||
<Button onClick={() => handleTimePresetClick('reset')}>重置</Button>
|
||||
|
||||
</Form.Group>
|
||||
</>
|
||||
}
|
||||
</Button.Group>
|
||||
</Form.Field>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
<Table basic compact size='small'>
|
||||
<Table.Header>
|
||||
|
@ -13,11 +13,6 @@ const OtherSetting = () => {
|
||||
HomePageContent: ''
|
||||
});
|
||||
let [loading, setLoading] = useState(false);
|
||||
const [showUpdateModal, setShowUpdateModal] = useState(false);
|
||||
const [updateData, setUpdateData] = useState({
|
||||
tag_name: '',
|
||||
content: ''
|
||||
});
|
||||
|
||||
const getOptions = async () => {
|
||||
const res = await API.get('/api/option/');
|
||||
@ -82,33 +77,12 @@ const OtherSetting = () => {
|
||||
await updateOption(key, inputs[key]);
|
||||
};
|
||||
|
||||
const openGitHubRelease = () => {
|
||||
window.location =
|
||||
'https://github.com/songquanpeng/one-api/releases/latest';
|
||||
};
|
||||
|
||||
const checkUpdate = async () => {
|
||||
const res = await API.get(
|
||||
'https://api.github.com/repos/songquanpeng/one-api/releases/latest'
|
||||
);
|
||||
const { tag_name, body } = res.data;
|
||||
if (tag_name === process.env.REACT_APP_VERSION) {
|
||||
showSuccess(`已是最新版本:${tag_name}`);
|
||||
} else {
|
||||
setUpdateData({
|
||||
tag_name: tag_name,
|
||||
content: marked.parse(body)
|
||||
});
|
||||
setShowUpdateModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid columns={1}>
|
||||
<Grid.Column>
|
||||
<Form loading={loading}>
|
||||
<Header as='h3'>通用设置</Header>
|
||||
<Form.Button onClick={checkUpdate}>检查更新</Form.Button>
|
||||
<Form.Group widths='equal'>
|
||||
<Form.TextArea
|
||||
label='公告'
|
||||
@ -178,28 +152,6 @@ const OtherSetting = () => {
|
||||
<Form.Button onClick={submitFooter}>设置页脚</Form.Button>
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
<Modal
|
||||
onClose={() => setShowUpdateModal(false)}
|
||||
onOpen={() => setShowUpdateModal(true)}
|
||||
open={showUpdateModal}
|
||||
>
|
||||
<Modal.Header>新版本:{updateData.tag_name}</Modal.Header>
|
||||
<Modal.Content>
|
||||
<Modal.Description>
|
||||
<div dangerouslySetInnerHTML={{ __html: updateData.content }}></div>
|
||||
</Modal.Description>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => setShowUpdateModal(false)}>关闭</Button>
|
||||
<Button
|
||||
content='详情'
|
||||
onClick={() => {
|
||||
setShowUpdateModal(false);
|
||||
openGitHubRelease();
|
||||
}}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
@ -94,7 +94,7 @@ const PasswordResetConfirm = () => {
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
color='green'
|
||||
color='var(--czl-success-color)'
|
||||
fluid
|
||||
size='large'
|
||||
onClick={handleSubmit}
|
||||
|
@ -83,7 +83,7 @@ const PasswordResetForm = () => {
|
||||
<></>
|
||||
)}
|
||||
<Button
|
||||
color='green'
|
||||
style={{backgroundColor: 'var(--czl-success-color)', color: 'white'}}
|
||||
fluid
|
||||
size='large'
|
||||
onClick={handleSubmit}
|
||||
|
@ -17,13 +17,13 @@ function renderTimestamp(timestamp) {
|
||||
function renderStatus(status) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return <Label basic color='green'>未使用</Label>;
|
||||
return <Label basic style={{color: 'var(--czl-primary-color)'}}>未使用</Label>;
|
||||
case 2:
|
||||
return <Label basic color='red'> 已禁用 </Label>;
|
||||
return <Label basic style={{color: 'var(--czl-error-color)'}}> 已禁用 </Label>;
|
||||
case 3:
|
||||
return <Label basic color='grey'> 已使用 </Label>;
|
||||
return <Label basic style={{color: 'var(--czl-success-color)'}}> 已使用 </Label>;
|
||||
default:
|
||||
return <Label basic color='black'> 未知状态 </Label>;
|
||||
return <Label basic style={{color: 'var(--czl-grayD)'}}> 未知状态 </Label>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,59 +226,64 @@ const RedemptionsTable = () => {
|
||||
<Table.Cell>{redemption.redeemed_time ? renderTimestamp(redemption.redeemed_time) : "尚未兑换"} </Table.Cell>
|
||||
<Table.Cell>
|
||||
<div>
|
||||
<Button
|
||||
size={'small'}
|
||||
positive
|
||||
onClick={async () => {
|
||||
if (await copy(redemption.key)) {
|
||||
showSuccess('已复制到剪贴板!');
|
||||
} else {
|
||||
showWarning('无法复制到剪贴板,请手动复制,已将兑换码填入搜索框。')
|
||||
setSearchKeyword(redemption.key);
|
||||
}
|
||||
}}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative>
|
||||
删除
|
||||
</Button>
|
||||
}
|
||||
on='click'
|
||||
flowing
|
||||
hoverable
|
||||
>
|
||||
<Button
|
||||
negative
|
||||
onClick={() => {
|
||||
manageRedemption(redemption.id, 'delete', idx);
|
||||
}}
|
||||
>
|
||||
确认删除
|
||||
</Button>
|
||||
</Popup>
|
||||
<Button
|
||||
size={'small'}
|
||||
disabled={redemption.status === 3} // used
|
||||
onClick={() => {
|
||||
manageRedemption(
|
||||
redemption.id,
|
||||
redemption.status === 1 ? 'disable' : 'enable',
|
||||
idx
|
||||
);
|
||||
}}
|
||||
>
|
||||
{redemption.status === 1 ? '禁用' : '启用'}
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
as={Link}
|
||||
to={'/redemption/edit/' + redemption.id}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
positive
|
||||
onClick={async () => {
|
||||
if (await copy(redemption.key)) {
|
||||
showSuccess('已复制到剪贴板!');
|
||||
} else {
|
||||
showWarning('无法复制到剪贴板,请手动复制,已将兑换码填入搜索框。')
|
||||
setSearchKeyword(redemption.key);
|
||||
}
|
||||
}}
|
||||
style={{ backgroundColor: "var(--czl-success-color)", color: "white"}}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative style={{ backgroundColor: "var(--czl-error-color)", color: "white"}}>
|
||||
删除
|
||||
</Button>
|
||||
}
|
||||
on='click'
|
||||
flowing
|
||||
hoverable
|
||||
>
|
||||
<Button
|
||||
negative
|
||||
onClick={() => {
|
||||
manageRedemption(redemption.id, 'delete', idx);
|
||||
}}
|
||||
style={{ backgroundColor: "var(--czl-error-color)", color: "white"}}
|
||||
>
|
||||
确认删除
|
||||
</Button>
|
||||
</Popup>
|
||||
<Button
|
||||
size={'small'}
|
||||
disabled={redemption.status === 3} // used
|
||||
onClick={() => {
|
||||
manageRedemption(
|
||||
redemption.id,
|
||||
redemption.status === 1 ? 'disable' : 'enable',
|
||||
idx
|
||||
);
|
||||
}}
|
||||
style={{ backgroundColor: "var(--czl-link-color)", color: "white"}}
|
||||
>
|
||||
{redemption.status === 1 ? '禁用' : '启用'}
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
as={Link}
|
||||
to={'/redemption/edit/' + redemption.id}
|
||||
style={{ backgroundColor: "var(--czl-primary-color)", color: "white"}}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
|
@ -142,7 +142,7 @@ const RegisterForm = () => {
|
||||
name='email'
|
||||
type='email'
|
||||
action={
|
||||
<Button onClick={sendVerificationCode} disabled={loading}>
|
||||
<Button onClick={sendVerificationCode} disabled={loading} style={{ backgroundColor: 'var(--czl-primary-color)', color: '#fff' }}>
|
||||
获取验证码
|
||||
</Button>
|
||||
}
|
||||
@ -170,7 +170,7 @@ const RegisterForm = () => {
|
||||
<></>
|
||||
)}
|
||||
<Button
|
||||
color='green'
|
||||
style={{ backgroundColor: 'var(--czl-success-color)', color: '#fff' }}
|
||||
fluid
|
||||
size='large'
|
||||
onClick={handleSubmit}
|
||||
@ -182,7 +182,7 @@ const RegisterForm = () => {
|
||||
</Form>
|
||||
<Message>
|
||||
已有账户?
|
||||
<Link to='/login' className='btn btn-link'>
|
||||
<Link to='/login' className='btn btn-link' style={{ color: 'var(--czl-link-color)' }}>
|
||||
点击登录
|
||||
</Link>
|
||||
</Message>
|
||||
|
@ -18,15 +18,16 @@ function renderTimestamp(timestamp) {
|
||||
function renderStatus(status) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return <Label basic color='green'>已启用</Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-success-color)' }}>已启用</Label>;
|
||||
case 2:
|
||||
return <Label basic color='red'> 已禁用 </Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-error-color)' }}> 已禁用 </Label>;
|
||||
case 3:
|
||||
return <Label basic color='yellow'> 已过期 </Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-warning-color)' }}> 已过期 </Label>;
|
||||
case 4:
|
||||
return <Label basic color='grey'> 已耗尽 </Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-grayB)' }}> 已耗尽 </Label>;
|
||||
default:
|
||||
return <Label basic color='black'> 未知状态 </Label>;
|
||||
return <Label basic style={{ color: 'var(--czl-grayD)' }}> 未知状态 </Label>;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,19 +315,20 @@ const TokensTable = () => {
|
||||
<Table.Cell>{token.expired_time === -1 ? '永不过期' : renderTimestamp(token.expired_time)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div>
|
||||
<Button
|
||||
size={'small'}
|
||||
positive
|
||||
onClick={async () => {
|
||||
await onCopy('', token.key);
|
||||
}}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
<Button
|
||||
size={'small'}
|
||||
positive
|
||||
onClick={async () => {
|
||||
await onCopy('', token.key);
|
||||
}}
|
||||
style={{ backgroundColor: 'var(--czl-success-color)', borderColor: 'var(--czl-success-color)' }}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
{' '}
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative>
|
||||
<Button size='small' negative style={{ backgroundColor: 'var(--czl-error-color)', borderColor: 'var(--czl-error-color)' }}>
|
||||
删除
|
||||
</Button>
|
||||
}
|
||||
@ -339,6 +341,7 @@ const TokensTable = () => {
|
||||
onClick={() => {
|
||||
manageToken(token.id, 'delete', idx);
|
||||
}}
|
||||
style={{ backgroundColor: 'var(--czl-error-color)', borderColor: 'var(--czl-error-color)' }}
|
||||
>
|
||||
删除令牌 {token.name}
|
||||
</Button>
|
||||
@ -356,9 +359,11 @@ const TokensTable = () => {
|
||||
{token.status === 1 ? '禁用' : '启用'}
|
||||
</Button>
|
||||
<Button
|
||||
negative
|
||||
size={'small'}
|
||||
as={Link}
|
||||
to={'/token/edit/' + token.id}
|
||||
style={{ backgroundColor: 'var(--czl-primary-color)', borderColor: 'var(--czl-primary-color)' }}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
@ -372,10 +377,17 @@ const TokensTable = () => {
|
||||
<Table.Footer>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell colSpan='7'>
|
||||
<Button size='small' as={Link} to='/token/add' loading={loading}>
|
||||
<Button
|
||||
size='small'
|
||||
as={Link}
|
||||
to='/token/add'
|
||||
loading={loading}
|
||||
style={{ color: "var(--czl-main)", backgroundColor: "var(--czl-link-color)" }}
|
||||
>
|
||||
添加新的令牌
|
||||
</Button>
|
||||
<Button size='small' onClick={refresh} loading={loading}>刷新</Button>
|
||||
|
||||
<Pagination
|
||||
floated='right'
|
||||
activePage={activePage}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form, Label, Pagination, Popup, Table } from 'semantic-ui-react';
|
||||
import { Button, Form, Label, Pagination, Popup, Table, Dropdown } from 'semantic-ui-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { API, showError, showSuccess } from '../helpers';
|
||||
|
||||
@ -11,11 +11,11 @@ function renderRole(role) {
|
||||
case 1:
|
||||
return <Label>普通用户</Label>;
|
||||
case 10:
|
||||
return <Label color='yellow'>管理员</Label>;
|
||||
return <Label color='var(--czl-warning-color)'>管理员</Label>;
|
||||
case 100:
|
||||
return <Label color='orange'>超级管理员</Label>;
|
||||
return <Label color='var(--czl-error-color)'>超级管理员</Label>;
|
||||
default:
|
||||
return <Label color='red'>未知身份</Label>;
|
||||
return <Label color='var(--czl-error-color)'>未知身份</Label>;
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,24 +61,20 @@ const UsersTable = () => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const manageUser = (username, action, idx) => {
|
||||
const manageUser = (username, idx, newGroup) => {
|
||||
(async () => {
|
||||
const res = await API.post('/api/user/manage', {
|
||||
username,
|
||||
action
|
||||
newGroup
|
||||
});
|
||||
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('操作成功完成!');
|
||||
let user = res.data.data;
|
||||
let newUsers = [...users];
|
||||
let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
|
||||
if (action === 'delete') {
|
||||
newUsers[realIdx].deleted = true;
|
||||
} else {
|
||||
newUsers[realIdx].status = user.status;
|
||||
newUsers[realIdx].role = user.role;
|
||||
}
|
||||
newUsers[realIdx].group = user.group; // 用新的 user.group 更新用户分组
|
||||
setUsers(newUsers);
|
||||
} else {
|
||||
showError(message);
|
||||
@ -86,6 +82,20 @@ const UsersTable = () => {
|
||||
})();
|
||||
};
|
||||
|
||||
|
||||
const groupOptions = [
|
||||
{ key: 'default', value: 'default', text: '默认', color: 'var(--czl-grayA)' },
|
||||
{ key: 'vip', value: 'vip', text: 'VIP', color: 'var(--czl-success-color)' },
|
||||
{ key: 'svip', value: 'svip', text: '超级VIP', color: 'var(--czl-error-color)' },
|
||||
];
|
||||
|
||||
const groupColor = (userGroup) => {
|
||||
const group = groupOptions.find((option) => option.value === userGroup);
|
||||
return group ? group.color : 'inherit'; // 如果未找到分组,则返回默认颜色
|
||||
};
|
||||
|
||||
|
||||
|
||||
const renderStatus = (status) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
@ -175,14 +185,14 @@ const UsersTable = () => {
|
||||
>
|
||||
用户名
|
||||
</Table.HeaderCell>
|
||||
<Table.HeaderCell
|
||||
{/* <Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
sortUser('group');
|
||||
}}
|
||||
>
|
||||
分组
|
||||
</Table.HeaderCell>
|
||||
</Table.HeaderCell> */}
|
||||
<Table.HeaderCell
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
@ -231,10 +241,7 @@ const UsersTable = () => {
|
||||
hoverable
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{renderGroup(user.group)}</Table.Cell>
|
||||
{/*<Table.Cell>*/}
|
||||
{/* {user.email ? <Popup hoverable content={user.email} trigger={<span>{renderText(user.email, 24)}</span>} /> : '无'}*/}
|
||||
{/*</Table.Cell>*/}
|
||||
{/* <Table.Cell>{renderGroup(user.group)}</Table.Cell> */}
|
||||
<Table.Cell>
|
||||
<Popup content='剩余额度' trigger={<Label basic>{renderQuota(user.quota)}</Label>} />
|
||||
<Popup content='已用额度' trigger={<Label basic>{renderQuota(user.used_quota)}</Label>} />
|
||||
@ -244,6 +251,30 @@ const UsersTable = () => {
|
||||
<Table.Cell>{renderStatus(user.status)}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div>
|
||||
<Button.Group size={'small'} style={{marginRight: '10px'}}>
|
||||
<Button
|
||||
positive
|
||||
size={'small'}
|
||||
className={`group-button ${user.group}`}
|
||||
style={{
|
||||
backgroundColor: groupColor(user.group), // 设置透明背景颜色
|
||||
width: '100px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{user.group}
|
||||
</Button>
|
||||
<Dropdown
|
||||
className="button icon"
|
||||
style={{
|
||||
backgroundColor: groupColor(user.group),}}
|
||||
floating
|
||||
options={groupOptions}
|
||||
trigger={<></>}
|
||||
onChange={(e, { value }) => manageUser(user.username, idx, value)}
|
||||
/>
|
||||
</Button.Group>
|
||||
<Popup
|
||||
trigger={
|
||||
<Button size='small' negative disabled={user.role === 100}>
|
||||
|
@ -1,3 +1,65 @@
|
||||
:root {
|
||||
--toastify-color-light: var(--czl-main) !important;
|
||||
--toastify-color-dark: var(--czl-main-dark) !important;
|
||||
--toastify-color-info: var(--czl-info-color) !important;
|
||||
--toastify-color-success: var(--czl-success-color) !important;
|
||||
--toastify-color-warning: var(--czl-warning-color) !important;
|
||||
--toastify-color-error: var(--czl-error-color) !important;
|
||||
--toastify-icon-color-info: var(--czl-info-color) !important;
|
||||
--toastify-icon-color-success: var(--czl-success-color) !important;
|
||||
--toastify-icon-color-warning: var(--czl-warning-color) !important;
|
||||
--toastify-icon-color-error: var(--czl-error-color) !important;
|
||||
--toastify-toast-background: var(--czl-main) !important;
|
||||
--toastify-text-color-light: var(--czl-seat) !important;
|
||||
--toastify-text-color-dark: var(--czl-grayD) !important;
|
||||
--toastify-text-color-info: var(--czl-info-color) !important;
|
||||
--toastify-text-color-success: var(--czl-success-color) !important;
|
||||
--toastify-text-color-warning: var(--czl-warning-color) !important;
|
||||
--toastify-text-color-error: var(--czl-error-color) !important;
|
||||
--toastify-spinner-color: var(--czl-seat) !important;
|
||||
--toastify-spinner-color-empty-area: var(--czl-minor) !important;
|
||||
--toastify-color-progress-light: linear-gradient(to right, var(--czl-success-color), var(--czl-primary-color-hover), var(--czl-primary-color), var(--czl-primary-color-suppl), var(--czl-chat-bg-color), var(--czl-error-color)) !important;
|
||||
--toastify-color-progress-dark: var(--czl-primary-color-dark) !important;
|
||||
--toastify-color-progress-info: var(--czl-info-color) !important;
|
||||
--toastify-color-progress-success: var(--czl-success-color) !important;
|
||||
--toastify-color-progress-warning: var(--czl-warning-color) !important;
|
||||
--toastify-color-progress-error: var(--czl-error-color) !important;
|
||||
|
||||
|
||||
/* Light Mode Colors */
|
||||
--czl-primary-color: #2EA7E0;
|
||||
--czl-primary-color-hover: #58c3ed;
|
||||
--czl-primary-color-pressed: #1e83ba;
|
||||
--czl-primary-color-suppl: #58c3ed;
|
||||
--czl-chat-bg-color: #84ddfa;
|
||||
--czl-success-color: #22BB33;
|
||||
--czl-error-color: #FF3333;
|
||||
--czl-warning-color: #FFAA00;
|
||||
--czl-info-color: #3366FF;
|
||||
--czl-link-color: #0645AD;
|
||||
--czl-main: #f5f5f7;
|
||||
--czl-routine: #DCDFE6;
|
||||
--czl-minor: #C0C4CC;
|
||||
--czl-seat: #666;
|
||||
--czl-grayA: #515253;
|
||||
--czl-grayB: #454545;
|
||||
--czl-grayC: #414243;
|
||||
--czl-grayD: #303030;
|
||||
|
||||
/* Dark Mode Colors */
|
||||
--czl-primary-color-dark: #84ddfa;
|
||||
--czl-primary-color-hover-dark: #b0eeff;
|
||||
--czl-primary-color-pressed-dark: #58c3ed;
|
||||
--czl-primary-color-suppl-dark: #b0eeff;
|
||||
--czl-chat-bg-color-dark: #d9f8ff;
|
||||
--czl-success-color-dark: #22BB33;
|
||||
--czl-error-color-dark: #FF3333;
|
||||
--czl-warning-color-dark: #FFAA00;
|
||||
--czl-info-color-dark: #3366FF;
|
||||
--czl-link-color-dark: #0645AD;
|
||||
--czl-main-dark: #181818;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'CZL';
|
||||
src: url('https://cdn-r2.czl.net/fonts/CZL/CZL_Sans_SC_Thin.woff2') format('woff2');
|
||||
@ -8,7 +70,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: 'CZL';
|
||||
src: url('https://cdn-r2.czl.net/fonts/CZL/CZL_Sans_SC_Black.woff2') format('woff2');
|
||||
src: url('https://cdn-r2.czl.net/fonts/CZL/CZL_Sans_SC_gray.woff2') format('woff2');
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
|
@ -117,7 +117,7 @@ const EditRedemption = () => {
|
||||
</Form.Field>
|
||||
</>
|
||||
}
|
||||
<Button positive onClick={submit}>提交</Button>
|
||||
<Button positive onClick={submit} style={{ background: 'var(--czl-success-color)' }}>提交</Button>
|
||||
<Button onClick={handleCancel}>取消</Button>
|
||||
</Form>
|
||||
</Segment>
|
||||
|
@ -7,8 +7,31 @@ const TopUp = () => {
|
||||
const [redemptionCode, setRedemptionCode] = useState('');
|
||||
const [topUpLink, setTopUpLink] = useState('');
|
||||
const [userQuota, setUserQuota] = useState(0);
|
||||
const [userGroup, setUserGroup] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// 升级用户组
|
||||
const updateUserGroupIfNecessary = async (quota) => {
|
||||
if (userGroup === 'vip') return; // 添加这一行
|
||||
|
||||
if (quota >= 5*500000) {
|
||||
try {
|
||||
const res = await API.post('/api/user/manage', {
|
||||
username: localStorage.getItem('username'),
|
||||
newGroup: 'vip'
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
showSuccess('已成功升级为 VIP 会员!');
|
||||
} else {
|
||||
showError('请右下角联系客服');
|
||||
}
|
||||
} catch (err) {
|
||||
showError('请右下角联系客服');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const topUp = async () => {
|
||||
if (redemptionCode === '') {
|
||||
showInfo('请输入充值码!')
|
||||
@ -23,14 +46,16 @@ const TopUp = () => {
|
||||
if (success) {
|
||||
showSuccess('充值成功!');
|
||||
setUserQuota((quota) => {
|
||||
return quota + data;
|
||||
const newQuota = quota + data;
|
||||
updateUserGroupIfNecessary(newQuota);
|
||||
return newQuota;
|
||||
});
|
||||
setRedemptionCode('');
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
} catch (err) {
|
||||
showError('请求失败');
|
||||
showError('请右下角联系客服');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@ -44,11 +69,12 @@ const TopUp = () => {
|
||||
window.open(topUpLink, '_blank');
|
||||
};
|
||||
|
||||
const getUserQuota = async ()=>{
|
||||
let res = await API.get(`/api/user/self`);
|
||||
const {success, message, data} = res.data;
|
||||
const getUserQuota = async () => {
|
||||
let res = await API.get(`/api/user/self`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setUserQuota(data.quota);
|
||||
setUserGroup(data.group); // 添加这一行
|
||||
} else {
|
||||
showError(message);
|
||||
}
|
||||
@ -79,19 +105,21 @@ const TopUp = () => {
|
||||
setRedemptionCode(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button color='green' onClick={openTopUpLink}>
|
||||
<Button negative style={{ backgroundColor: 'var(--czl-primary-color)' }} onClick={openTopUpLink}>
|
||||
获取兑换码
|
||||
</Button>
|
||||
<Button color='yellow' onClick={topUp} disabled={isSubmitting}>
|
||||
{isSubmitting ? '兑换中...' : '兑换'}
|
||||
<Button negative style={{ backgroundColor: 'var(--czl-success-color)' }} onClick={topUp} disabled={isSubmitting}>
|
||||
{isSubmitting ? '兑换中...' : '兑换'}
|
||||
</Button>
|
||||
|
||||
|
||||
</Form>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Statistic.Group widths='one'>
|
||||
<Statistic>
|
||||
<Statistic.Value>{renderQuota(userQuota)}</Statistic.Value>
|
||||
<Statistic.Label>剩余额度</Statistic.Label>
|
||||
<Statistic.Value style={{ color: 'var(--czl-error-color)' }}>{renderQuota(userQuota)}</Statistic.Value>
|
||||
<Statistic.Label style={{ color: 'var(--czl-error-color)' }}>剩余额度</Statistic.Label>
|
||||
</Statistic>
|
||||
</Statistic.Group>
|
||||
</Grid.Column>
|
||||
|
Loading…
Reference in New Issue
Block a user