diff --git a/controller/user.go b/controller/user.go index 8fd10b8..f04f9ca 100644 --- a/controller/user.go +++ b/controller/user.go @@ -79,6 +79,7 @@ func setupLogin(user *model.User, c *gin.Context) { DisplayName: user.DisplayName, Role: user.Role, Status: user.Status, + Group: user.Group, } c.JSON(http.StatusOK, gin.H{ "message": "", @@ -284,6 +285,42 @@ func GenerateAccessToken(c *gin.Context) { return } +type TransferAffQuotaRequest struct { + Quota int `json:"quota" binding:"required"` +} + +func TransferAffQuota(c *gin.Context) { + id := c.GetInt("id") + user, err := model.GetUserById(id, true) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + tran := TransferAffQuotaRequest{} + if err := c.ShouldBindJSON(&tran); err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + err = user.TransferAffQuotaToQuota(tran.Quota) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "划转失败 " + err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "划转成功", + }) +} + func GetAffCode(c *gin.Context) { id := c.GetInt("id") user, err := model.GetUserById(id, true) @@ -330,6 +367,28 @@ func GetSelf(c *gin.Context) { return } +func GetUserModels(c *gin.Context) { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + id = c.GetInt("id") + } + user, err := model.GetUserById(id, true) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + models := model.GetGroupModels(user.Group) + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + "data": models, + }) + return +} + func UpdateUser(c *gin.Context) { var updatedUser model.User err := json.NewDecoder(c.Request.Body).Decode(&updatedUser) diff --git a/model/ability.go b/model/ability.go index 3da83be..b57ae19 100644 --- a/model/ability.go +++ b/model/ability.go @@ -13,6 +13,16 @@ type Ability struct { Priority *int64 `json:"priority" gorm:"bigint;default:0;index"` } +func GetGroupModels(group string) []string { + var abilities []Ability + DB.Where("`group` = ?", group).Find(&abilities) + models := make([]string, 0, len(abilities)) + for _, ability := range abilities { + models = append(models, ability.Model) + } + return models +} + func GetRandomSatisfiedChannel(group string, model string) (*Channel, error) { ability := Ability{} groupCol := "`group`" diff --git a/model/token.go b/model/token.go index ee6b4a9..66b8369 100644 --- a/model/token.go +++ b/model/token.go @@ -220,28 +220,30 @@ func PostConsumeTokenQuota(tokenId int, userQuota int, quota int, preConsumedQuo } if sendEmail { - quotaTooLow := userQuota >= common.QuotaRemindThreshold && userQuota-(quota+preConsumedQuota) < common.QuotaRemindThreshold - noMoreQuota := userQuota-(quota+preConsumedQuota) <= 0 - if quotaTooLow || noMoreQuota { - go func() { - email, err := GetUserEmail(token.UserId) - if err != nil { - common.SysError("failed to fetch user email: " + err.Error()) - } - prompt := "您的额度即将用尽" - if noMoreQuota { - prompt = "您的额度已用尽" - } - if email != "" { - topUpLink := fmt.Sprintf("%s/topup", common.ServerAddress) - err = common.SendEmail(prompt, email, - fmt.Sprintf("%s,当前剩余额度为 %d,为了不影响您的使用,请及时充值。
充值链接:%s", prompt, userQuota, topUpLink, topUpLink)) + if (quota + preConsumedQuota) != 0 { + quotaTooLow := userQuota >= common.QuotaRemindThreshold && userQuota-(quota+preConsumedQuota) < common.QuotaRemindThreshold + noMoreQuota := userQuota-(quota+preConsumedQuota) <= 0 + if quotaTooLow || noMoreQuota { + go func() { + email, err := GetUserEmail(token.UserId) if err != nil { - common.SysError("failed to send email" + err.Error()) + common.SysError("failed to fetch user email: " + err.Error()) } - common.SysLog("user quota is low, consumed quota: " + strconv.Itoa(quota) + ", user quota: " + strconv.Itoa(userQuota)) - } - }() + prompt := "您的额度即将用尽" + if noMoreQuota { + prompt = "您的额度已用尽" + } + if email != "" { + topUpLink := fmt.Sprintf("%s/topup", common.ServerAddress) + err = common.SendEmail(prompt, email, + fmt.Sprintf("%s,当前剩余额度为 %d,为了不影响您的使用,请及时充值。
充值链接:%s", prompt, userQuota, topUpLink, topUpLink)) + if err != nil { + common.SysError("failed to send email" + err.Error()) + } + common.SysLog("user quota is low, consumed quota: " + strconv.Itoa(quota) + ", user quota: " + strconv.Itoa(userQuota)) + } + }() + } } } diff --git a/model/user.go b/model/user.go index 31eae3b..422d268 100644 --- a/model/user.go +++ b/model/user.go @@ -27,6 +27,9 @@ type User struct { RequestCount int `json:"request_count" gorm:"type:int;default:0;"` // request number Group string `json:"group" gorm:"type:varchar(32);default:'default'"` AffCode string `json:"aff_code" gorm:"type:varchar(32);column:aff_code;uniqueIndex"` + AffCount int `json:"aff_count" gorm:"type:int;default:0;column:aff_count"` + AffQuota int `json:"aff_quota" gorm:"type:int;default:0;column:aff_quota"` // 邀请剩余额度 + AffHistoryQuota int `json:"aff_history_quota" gorm:"type:int;default:0;column:aff_history"` // 邀请历史额度 InviterId int `json:"inviter_id" gorm:"type:int;column:inviter_id;index"` } @@ -77,6 +80,54 @@ func DeleteUserById(id int) (err error) { return user.Delete() } +func inviteUser(inviterId int) (err error) { + user, err := GetUserById(inviterId, true) + if err != nil { + return err + } + user.AffCount++ + user.AffQuota += common.QuotaForInviter + user.AffHistoryQuota += common.QuotaForInviter + return DB.Save(user).Error +} + +func (user *User) TransferAffQuotaToQuota(quota int) error { + // 检查quota是否小于最小额度 + if float64(quota) < common.QuotaPerUnit { + return fmt.Errorf("转移额度最小为%s!", common.LogQuota(int(common.QuotaPerUnit))) + } + + // 开始数据库事务 + tx := DB.Begin() + if tx.Error != nil { + return tx.Error + } + defer tx.Rollback() // 确保在函数退出时事务能回滚 + + // 加锁查询用户以确保数据一致性 + err := tx.Set("gorm:query_option", "FOR UPDATE").First(&user, user.Id).Error + if err != nil { + return err + } + + // 再次检查用户的AffQuota是否足够 + if user.AffQuota < quota { + return errors.New("邀请额度不足!") + } + + // 更新用户额度 + user.AffQuota -= quota + user.Quota += quota + + // 保存用户状态 + if err := tx.Save(user).Error; err != nil { + return err + } + + // 提交事务 + return tx.Commit().Error +} + func (user *User) Insert(inviterId int) error { var err error if user.Password != "" { @@ -101,8 +152,9 @@ func (user *User) Insert(inviterId int) error { RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", common.LogQuota(common.QuotaForInvitee))) } if common.QuotaForInviter > 0 { - _ = IncreaseUserQuota(inviterId, common.QuotaForInviter) + //_ = IncreaseUserQuota(inviterId, common.QuotaForInviter) RecordLog(inviterId, LogTypeSystem, fmt.Sprintf("邀请用户赠送 %s", common.LogQuota(common.QuotaForInviter))) + _ = inviteUser(inviterId) } } return nil diff --git a/router/api-router.go b/router/api-router.go index 17758b0..038b750 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -39,6 +39,7 @@ func SetApiRouter(router *gin.Engine) { selfRoute.Use(middleware.UserAuth()) { selfRoute.GET("/self", controller.GetSelf) + selfRoute.GET("/models", controller.GetUserModels) selfRoute.PUT("/self", controller.UpdateSelf) selfRoute.DELETE("/self", controller.DeleteSelf) selfRoute.GET("/token", controller.GenerateAccessToken) @@ -46,6 +47,7 @@ func SetApiRouter(router *gin.Engine) { selfRoute.POST("/topup", controller.TopUp) selfRoute.POST("/pay", controller.RequestEpay) selfRoute.POST("/amount", controller.RequestAmount) + selfRoute.POST("/aff_transfer", controller.TransferAffQuota) } adminRoute := userRoute.Group("/") diff --git a/web/src/components/PersonalSetting.js b/web/src/components/PersonalSetting.js index 6baf1f3..41ff804 100644 --- a/web/src/components/PersonalSetting.js +++ b/web/src/components/PersonalSetting.js @@ -1,376 +1,548 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { Button, Divider, Form, Header, Image, Message, Modal } from 'semantic-ui-react'; -import { Link, useNavigate } from 'react-router-dom'; -import { API, copy, showError, showInfo, showNotice, showSuccess } from '../helpers'; +import React, {useContext, useEffect, useState} from 'react'; +import {Form, Image, Message} from 'semantic-ui-react'; +import {Link, useNavigate} from 'react-router-dom'; +import {API, copy, isRoot, showError, showInfo, showNotice, showSuccess} from '../helpers'; import Turnstile from 'react-turnstile'; -import { UserContext } from '../context/User'; -import { onGitHubOAuthClicked } from './utils'; +import {UserContext} from '../context/User'; +import {onGitHubOAuthClicked} from './utils'; +import { + Avatar, Banner, + Button, + Card, + Descriptions, + Divider, + Input, InputNumber, + Layout, + Modal, + Space, + Tag, + Typography +} from "@douyinfe/semi-ui"; +import {getQuotaPerUnit, renderQuota, renderQuotaWithPrompt, stringToColor} from "../helpers/render"; +import EditToken from "../pages/Token/EditToken"; +import EditUser from "../pages/User/EditUser"; const PersonalSetting = () => { - const [userState, userDispatch] = useContext(UserContext); - let navigate = useNavigate(); + const [userState, userDispatch] = useContext(UserContext); + let navigate = useNavigate(); - const [inputs, setInputs] = useState({ - wechat_verification_code: '', - email_verification_code: '', - email: '', - self_account_deletion_confirmation: '' - }); - const [status, setStatus] = useState({}); - const [showWeChatBindModal, setShowWeChatBindModal] = useState(false); - const [showEmailBindModal, setShowEmailBindModal] = useState(false); - const [showAccountDeleteModal, setShowAccountDeleteModal] = useState(false); - const [turnstileEnabled, setTurnstileEnabled] = useState(false); - const [turnstileSiteKey, setTurnstileSiteKey] = useState(''); - const [turnstileToken, setTurnstileToken] = useState(''); - const [loading, setLoading] = useState(false); - const [disableButton, setDisableButton] = useState(false); - const [countdown, setCountdown] = useState(30); - const [affLink, setAffLink] = useState(""); - const [systemToken, setSystemToken] = useState(""); + const [inputs, setInputs] = useState({ + wechat_verification_code: '', + email_verification_code: '', + email: '', + self_account_deletion_confirmation: '' + }); + const [status, setStatus] = useState({}); + const [showWeChatBindModal, setShowWeChatBindModal] = useState(false); + const [showEmailBindModal, setShowEmailBindModal] = useState(false); + const [showAccountDeleteModal, setShowAccountDeleteModal] = useState(false); + const [turnstileEnabled, setTurnstileEnabled] = useState(false); + const [turnstileSiteKey, setTurnstileSiteKey] = useState(''); + const [turnstileToken, setTurnstileToken] = useState(''); + const [loading, setLoading] = useState(false); + const [disableButton, setDisableButton] = useState(false); + const [countdown, setCountdown] = useState(30); + const [affLink, setAffLink] = useState(""); + const [systemToken, setSystemToken] = useState(""); + const [models, setModels] = useState([]); + const [openTransfer, setOpenTransfer] = useState(false); + const [transferAmount, setTransferAmount] = useState(0); - useEffect(() => { - let status = localStorage.getItem('status'); - if (status) { - status = JSON.parse(status); - setStatus(status); - if (status.turnstile_check) { - setTurnstileEnabled(true); - setTurnstileSiteKey(status.turnstile_site_key); - } - } - }, []); + useEffect(() => { + // let user = localStorage.getItem('user'); + // if (user) { + // userDispatch({ type: 'login', payload: user }); + // } + // console.log(localStorage.getItem('user')) - useEffect(() => { - let countdownInterval = null; - if (disableButton && countdown > 0) { - countdownInterval = setInterval(() => { - setCountdown(countdown - 1); - }, 1000); - } else if (countdown === 0) { - setDisableButton(false); - setCountdown(30); - } - return () => clearInterval(countdownInterval); // Clean up on unmount - }, [disableButton, countdown]); + let status = localStorage.getItem('status'); + if (status) { + status = JSON.parse(status); + setStatus(status); + if (status.turnstile_check) { + setTurnstileEnabled(true); + setTurnstileSiteKey(status.turnstile_site_key); + } + } + getUserData().then( + (res) => { + console.log(userState) + } + ); + loadModels().then(); + getAffLink().then(); + setTransferAmount(getQuotaPerUnit()) + }, []); - const handleInputChange = (e, { name, value }) => { - setInputs((inputs) => ({ ...inputs, [name]: value })); - }; + useEffect(() => { + let countdownInterval = null; + if (disableButton && countdown > 0) { + countdownInterval = setInterval(() => { + setCountdown(countdown - 1); + }, 1000); + } else if (countdown === 0) { + setDisableButton(false); + setCountdown(30); + } + return () => clearInterval(countdownInterval); // Clean up on unmount + }, [disableButton, countdown]); - const generateAccessToken = async () => { - const res = await API.get('/api/user/token'); - const { success, message, data } = res.data; - if (success) { - setSystemToken(data); - setAffLink(""); - await copy(data); - showSuccess(`令牌已重置并已复制到剪贴板`); - } else { - showError(message); - } - }; + const handleInputChange = (name, value) => { + setInputs((inputs) => ({...inputs, [name]: value})); + }; - const getAffLink = async () => { - const res = await API.get('/api/user/aff'); - const { success, message, data } = res.data; - if (success) { - let link = `${window.location.origin}/register?aff=${data}`; - setAffLink(link); - setSystemToken(""); - await copy(link); - showSuccess(`邀请链接已复制到剪切板`); - } else { - showError(message); - } - }; + const generateAccessToken = async () => { + const res = await API.get('/api/user/token'); + const {success, message, data} = res.data; + if (success) { + setSystemToken(data); + await copy(data); + showSuccess(`令牌已重置并已复制到剪贴板`); + } else { + showError(message); + } + }; - const handleAffLinkClick = async (e) => { - e.target.select(); - await copy(e.target.value); - showSuccess(`邀请链接已复制到剪切板`); - }; + const getAffLink = async () => { + const res = await API.get('/api/user/aff'); + const {success, message, data} = res.data; + if (success) { + let link = `${window.location.origin}/register?aff=${data}`; + setAffLink(link); + } else { + showError(message); + } + }; - const handleSystemTokenClick = async (e) => { - e.target.select(); - await copy(e.target.value); - showSuccess(`系统令牌已复制到剪切板`); - }; - - const deleteAccount = async () => { - if (inputs.self_account_deletion_confirmation !== userState.user.username) { - showError('请输入你的账户名以确认删除!'); - return; + const getUserData = async () => { + let res = await API.get(`/api/user/self`); + const {success, message, data} = res.data; + if (success) { + userDispatch({type: 'login', payload: data}); + } else { + showError(message); + } } - const res = await API.delete('/api/user/self'); - const { success, message } = res.data; - - if (success) { - showSuccess('账户已删除!'); - await API.get('/api/user/logout'); - userDispatch({ type: 'logout' }); - localStorage.removeItem('user'); - navigate('/login'); - } else { - showError(message); + const loadModels = async () => { + let res = await API.get(`/api/user/models`); + const {success, message, data} = res.data; + if (success) { + setModels(data); + console.log(data) + } else { + showError(message); + } } - }; - const bindWeChat = async () => { - if (inputs.wechat_verification_code === '') return; - const res = await API.get( - `/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}` + const handleAffLinkClick = async (e) => { + e.target.select(); + await copy(e.target.value); + showSuccess(`邀请链接已复制到剪切板`); + }; + + const handleSystemTokenClick = async (e) => { + e.target.select(); + await copy(e.target.value); + showSuccess(`系统令牌已复制到剪切板`); + }; + + const deleteAccount = async () => { + if (inputs.self_account_deletion_confirmation !== userState.user.username) { + showError('请输入你的账户名以确认删除!'); + return; + } + + const res = await API.delete('/api/user/self'); + const {success, message} = res.data; + + if (success) { + showSuccess('账户已删除!'); + await API.get('/api/user/logout'); + userDispatch({type: 'logout'}); + localStorage.removeItem('user'); + navigate('/login'); + } else { + showError(message); + } + }; + + const bindWeChat = async () => { + if (inputs.wechat_verification_code === '') return; + const res = await API.get( + `/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}` + ); + const {success, message} = res.data; + if (success) { + showSuccess('微信账户绑定成功!'); + setShowWeChatBindModal(false); + } else { + showError(message); + } + }; + + const transfer = async () => { + if (transferAmount < getQuotaPerUnit()) { + showError('划转金额最低为' + renderQuota(getQuotaPerUnit())); + return; + } + const res = await API.post( + `/api/user/aff_transfer`, + { + quota: transferAmount + } + ); + const {success, message} = res.data; + if (success) { + showSuccess(message); + setOpenTransfer(false); + getUserData().then(); + } else { + showError(message); + } + }; + + const sendVerificationCode = async () => { + if (inputs.email === '') { + showError('请输入邮箱!'); + return; + } + setDisableButton(true); + if (turnstileEnabled && turnstileToken === '') { + showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); + return; + } + setLoading(true); + const res = await API.get( + `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}` + ); + const {success, message} = res.data; + if (success) { + showSuccess('验证码发送成功,请检查邮箱!'); + } else { + showError(message); + } + setLoading(false); + }; + + const bindEmail = async () => { + if (inputs.email_verification_code === '') { + showError('请输入邮箱验证码!'); + return; + } + setLoading(true); + const res = await API.get( + `/api/oauth/email/bind?email=${inputs.email}&code=${inputs.email_verification_code}` + ); + const {success, message} = res.data; + if (success) { + showSuccess('邮箱账户绑定成功!'); + setShowEmailBindModal(false); + userState.user.email = inputs.email; + } else { + showError(message); + } + setLoading(false); + }; + + const getUsername = () => { + if (userState.user) { + return userState.user.username; + } else { + return 'null'; + } + } + + const handleCancel = () => { + setOpenTransfer(false); + } + + return ( +
+ + + +
+ {`可用额度${renderQuotaWithPrompt(userState?.user?.aff_quota)}`} + +
+
+ {`划转额度${renderQuotaWithPrompt(transferAmount)} 最低` + renderQuota(getQuotaPerUnit())} +
+ setTransferAmount(value)} disabled={false}> +
+
+
+
+ + {typeof getUsername() === 'string' && getUsername().slice(0, 1)} + } + title={{getUsername()}} + description={isRoot()?管理员:普通用户} + > + } + headerExtraContent={ + <> + + {'ID: ' + userState?.user?.id} + {userState?.user?.group} + + + } + footer={ + + {renderQuota(userState?.user?.quota)} + {renderQuota(userState?.user?.used_quota)} + {userState.user?.request_count} + + } + > + 可用模型 +
+ + {models.map((model) => ( + + {model} + + ))} + +
+ +
+ + 邀请链接 + +
+ } + > + 邀请信息 +
+ + + + { + renderQuota(userState?.user?.aff_quota) + } + + + + {renderQuota(userState?.user?.aff_history_quota)} + {userState?.user?.aff_count} + +
+ + + 个人信息 +
+ 邮箱 +
+
+ +
+
+ +
+
+
+
+ 微信 +
+
+ +
+
+ +
+
+
+
+ GitHub +
+
+ +
+
+ +
+
+
+ +
+ + + + + + {systemToken && ( + + )} + { + status.wechat_login && ( + + ) + } + setShowWeChatBindModal(false)} + // onOpen={() => setShowWeChatBindModal(true)} + visible={showWeChatBindModal} + size={'mini'} + > + +
+

+ 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效) +

+
+
+ + + +
+
+
+ setShowEmailBindModal(false)} + // onOpen={() => setShowEmailBindModal(true)} + onOk={bindEmail} + visible={showEmailBindModal} + size={'small'} + centered={true} + maskClosable={false} + > + 绑定邮箱地址 +
+ handleInputChange('email', value)} + name='email' + type='email' + /> + +
+
+ handleInputChange('email_verification_code', value)} + /> +
+ {turnstileEnabled ? ( + { + setTurnstileToken(token); + }} + /> + ) : ( + <> + )} +
+ setShowAccountDeleteModal(false)} + visible={showAccountDeleteModal} + size={'small'} + centered={true} + onOk={deleteAccount} + > +
+ +
+
+ handleInputChange('self_account_deletion_confirmation', value)} + /> + {turnstileEnabled ? ( + { + setTurnstileToken(token); + }} + /> + ) : ( + <> + )} +
+
+
+ + + + ); - const { success, message } = res.data; - if (success) { - showSuccess('微信账户绑定成功!'); - setShowWeChatBindModal(false); - } else { - showError(message); - } - }; - - const sendVerificationCode = async () => { - setDisableButton(true); - if (inputs.email === '') return; - if (turnstileEnabled && turnstileToken === '') { - showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); - return; - } - setLoading(true); - const res = await API.get( - `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}` - ); - const { success, message } = res.data; - if (success) { - showSuccess('验证码发送成功,请检查邮箱!'); - } else { - showError(message); - } - setLoading(false); - }; - - const bindEmail = async () => { - if (inputs.email_verification_code === '') return; - setLoading(true); - const res = await API.get( - `/api/oauth/email/bind?email=${inputs.email}&code=${inputs.email_verification_code}` - ); - const { success, message } = res.data; - if (success) { - showSuccess('邮箱账户绑定成功!'); - setShowEmailBindModal(false); - } else { - showError(message); - } - setLoading(false); - }; - - return ( -
-
通用设置
- - 注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。 - - - - - - - {systemToken && ( - - )} - {affLink && ( - - )} - -
账号绑定
- { - status.wechat_login && ( - - ) - } - setShowWeChatBindModal(false)} - onOpen={() => setShowWeChatBindModal(true)} - open={showWeChatBindModal} - size={'mini'} - > - - - -
-

- 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效) -

-
-
- - - -
-
-
- { - status.github_oauth && ( - - ) - } - - setShowEmailBindModal(false)} - onOpen={() => setShowEmailBindModal(true)} - open={showEmailBindModal} - size={'tiny'} - style={{ maxWidth: '450px' }} - > - 绑定邮箱地址 - - -
- - {disableButton ? `重新发送(${countdown})` : '获取验证码'} - - } - /> - - {turnstileEnabled ? ( - { - setTurnstileToken(token); - }} - /> - ) : ( - <> - )} -
- -
- -
- -
-
-
- setShowAccountDeleteModal(false)} - onOpen={() => setShowAccountDeleteModal(true)} - open={showAccountDeleteModal} - size={'tiny'} - style={{ maxWidth: '450px' }} - > - 危险操作 - - 您正在删除自己的帐户,将清空所有数据且不可恢复 - -
- - {turnstileEnabled ? ( - { - setTurnstileToken(token); - }} - /> - ) : ( - <> - )} -
- -
- -
- -
-
-
-
- ); }; export default PersonalSetting; diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 16d1ed9..2a7fd4a 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -37,6 +37,12 @@ export function renderNumber(num) { } } +export function getQuotaPerUnit() { + let quotaPerUnit = localStorage.getItem('quota_per_unit'); + quotaPerUnit = parseFloat(quotaPerUnit); + return quotaPerUnit; +} + export function renderQuota(quota, digits = 2) { let quotaPerUnit = localStorage.getItem('quota_per_unit'); let displayInCurrency = localStorage.getItem('display_in_currency'); diff --git a/web/src/pages/Setting/index.js b/web/src/pages/Setting/index.js index 30d0ef2..99b3e4c 100644 --- a/web/src/pages/Setting/index.js +++ b/web/src/pages/Setting/index.js @@ -1,55 +1,53 @@ import React from 'react'; -import { Segment, Tab } from 'semantic-ui-react'; import SystemSetting from '../../components/SystemSetting'; -import { isRoot } from '../../helpers'; +import {isRoot} from '../../helpers'; import OtherSetting from '../../components/OtherSetting'; import PersonalSetting from '../../components/PersonalSetting'; import OperationSetting from '../../components/OperationSetting'; +import {Layout, TabPane, Tabs} from "@douyinfe/semi-ui"; const Setting = () => { - let panes = [ - { - menuItem: '个人设置', - render: () => ( - - - - ) + let panes = [ + { + tab: '个人设置', + content: , + itemKey: '1' + } + ]; + + if (isRoot()) { + panes.push({ + tab: '运营设置', + content: , + itemKey: '2' + }); + panes.push({ + tab: '系统设置', + content: , + itemKey: '3' + }); + panes.push({ + tab: '其他设置', + content: , + itemKey: '4' + }); } - ]; - if (isRoot()) { - panes.push({ - menuItem: '运营设置', - render: () => ( - - - - ) - }); - panes.push({ - menuItem: '系统设置', - render: () => ( - - - - ) - }); - panes.push({ - menuItem: '其他设置', - render: () => ( - - - - ) - }); - } - - return ( - - - - ); + return ( +
+ + + + {panes.map(pane => ( + + {pane.content} + + ))} + + + +
+ ); }; export default Setting; diff --git a/web/src/pages/User/EditUser.js b/web/src/pages/User/EditUser.js index 8ae0e55..6ff88fc 100644 --- a/web/src/pages/User/EditUser.js +++ b/web/src/pages/User/EditUser.js @@ -1,10 +1,12 @@ import React, { useEffect, useState } from 'react'; import { Button, Form, Header, Segment } from 'semantic-ui-react'; import { useParams, useNavigate } from 'react-router-dom'; -import { API, showError, showSuccess } from '../../helpers'; +import {API, isMobile, showError, showSuccess} from '../../helpers'; import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render'; +import Title from "@douyinfe/semi-ui/lib/es/typography/title"; +import {SideSheet, Space} from "@douyinfe/semi-ui"; -const EditUser = () => { +const EditUser = (props) => { const params = useParams(); const userId = params.id; const [loading, setLoading] = useState(true); @@ -84,105 +86,122 @@ const EditUser = () => { return ( <> - -
更新用户信息
+ 更新用户信息} + headerStyle={{borderBottom: '1px solid var(--semi-color-border)'}} + bodyStyle={{borderBottom: '1px solid var(--semi-color-border)'}} + visible={props.visiable} + footer={ +
+ + + + +
+ } + closeIcon={null} + onCancel={() => handleCancel()} + width={isMobile() ? '100%' : 600} + >
{ - userId && <> - - - - - - - + userId && <> + + + + + + + }
-
+ + ); };