mirror of
				https://github.com/linux-do/new-api.git
				synced 2025-11-04 13:23:42 +08:00 
			
		
		
		
	完善个人中心
This commit is contained in:
		@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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`"
 | 
			
		||||
 
 | 
			
		||||
@@ -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,为了不影响您的使用,请及时充值。<br/>充值链接:<a href='%s'>%s</a>", 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,为了不影响您的使用,请及时充值。<br/>充值链接:<a href='%s'>%s</a>", 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))
 | 
			
		||||
					}
 | 
			
		||||
				}()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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("/")
 | 
			
		||||
 
 | 
			
		||||
@@ -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 (
 | 
			
		||||
        <div style={{lineHeight: '40px'}}>
 | 
			
		||||
            <Layout>
 | 
			
		||||
                <Layout.Content>
 | 
			
		||||
                    <Modal
 | 
			
		||||
                        title="请输入要划转的数量"
 | 
			
		||||
                        visible={openTransfer}
 | 
			
		||||
                        onOk={transfer}
 | 
			
		||||
                        onCancel={handleCancel}
 | 
			
		||||
                        maskClosable={false}
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        centered={true}
 | 
			
		||||
                    >
 | 
			
		||||
                        <div style={{marginTop: 20}}>
 | 
			
		||||
                            <Typography.Text>{`可用额度${renderQuotaWithPrompt(userState?.user?.aff_quota)}`}</Typography.Text>
 | 
			
		||||
                            <Input style={{marginTop: 5}} value={userState?.user?.aff_quota} disabled={true}></Input>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div style={{marginTop: 20}}>
 | 
			
		||||
                            <Typography.Text>{`划转额度${renderQuotaWithPrompt(transferAmount)} 最低` + renderQuota(getQuotaPerUnit())}</Typography.Text>
 | 
			
		||||
                            <div>
 | 
			
		||||
                                <InputNumber min={0} style={{marginTop: 5}} value={transferAmount} onChange={(value)=>setTransferAmount(value)} disabled={false}></InputNumber>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </Modal>
 | 
			
		||||
                    <div style={{marginTop: 20}}>
 | 
			
		||||
                        <Card
 | 
			
		||||
                            title={
 | 
			
		||||
                                <Card.Meta
 | 
			
		||||
                                    avatar={<Avatar size="default" color={stringToColor(getUsername())}
 | 
			
		||||
                                                    style={{marginRight: 4}}>
 | 
			
		||||
                                        {typeof getUsername() === 'string' && getUsername().slice(0, 1)}
 | 
			
		||||
                                    </Avatar>}
 | 
			
		||||
                                    title={<Typography.Text>{getUsername()}</Typography.Text>}
 | 
			
		||||
                                    description={isRoot()?<Tag color="red">管理员</Tag>:<Tag color="blue">普通用户</Tag>}
 | 
			
		||||
                                ></Card.Meta>
 | 
			
		||||
                            }
 | 
			
		||||
                            headerExtraContent={
 | 
			
		||||
                                <>
 | 
			
		||||
                                    <Space vertical align="start">
 | 
			
		||||
                                        <Tag color="green">{'ID: ' + userState?.user?.id}</Tag>
 | 
			
		||||
                                        <Tag color="blue">{userState?.user?.group}</Tag>
 | 
			
		||||
                                    </Space>
 | 
			
		||||
                                </>
 | 
			
		||||
                            }
 | 
			
		||||
                            footer={
 | 
			
		||||
                                <Descriptions row>
 | 
			
		||||
                                    <Descriptions.Item itemKey="当前余额">{renderQuota(userState?.user?.quota)}</Descriptions.Item>
 | 
			
		||||
                                    <Descriptions.Item itemKey="历史消耗">{renderQuota(userState?.user?.used_quota)}</Descriptions.Item>
 | 
			
		||||
                                    <Descriptions.Item itemKey="请求次数">{userState.user?.request_count}</Descriptions.Item>
 | 
			
		||||
                                </Descriptions>
 | 
			
		||||
                            }
 | 
			
		||||
                        >
 | 
			
		||||
                            <Typography.Title heading={6}>可用模型</Typography.Title>
 | 
			
		||||
                            <div style={{marginTop: 10}}>
 | 
			
		||||
                                <Space wrap>
 | 
			
		||||
                                    {models.map((model) => (
 | 
			
		||||
                                        <Tag key={model} color="cyan">
 | 
			
		||||
                                            {model}
 | 
			
		||||
                                        </Tag>
 | 
			
		||||
                                    ))}
 | 
			
		||||
                                </Space>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                        </Card>
 | 
			
		||||
                        <Card
 | 
			
		||||
                            footer={
 | 
			
		||||
                                <div>
 | 
			
		||||
                                    <Typography.Text>邀请链接</Typography.Text>
 | 
			
		||||
                                    <Input
 | 
			
		||||
                                        style={{marginTop: 10}}
 | 
			
		||||
                                        value={affLink}
 | 
			
		||||
                                        onClick={handleAffLinkClick}
 | 
			
		||||
                                        readOnly
 | 
			
		||||
                                    />
 | 
			
		||||
                                </div>
 | 
			
		||||
                            }
 | 
			
		||||
                        >
 | 
			
		||||
                            <Typography.Title heading={6}>邀请信息</Typography.Title>
 | 
			
		||||
                            <div style={{marginTop: 10}}>
 | 
			
		||||
                                <Descriptions row>
 | 
			
		||||
                                    <Descriptions.Item itemKey="待使用收益">
 | 
			
		||||
                                        <span style={{color: 'rgba(var(--semi-red-5), 1)'}}>
 | 
			
		||||
                                            {
 | 
			
		||||
                                                renderQuota(userState?.user?.aff_quota)
 | 
			
		||||
                                            }
 | 
			
		||||
                                        </span>
 | 
			
		||||
                                        <Button type={'secondary'} onClick={()=>setOpenTransfer(true)} size={'small'} style={{marginLeft: 10}}>划转</Button>
 | 
			
		||||
                                    </Descriptions.Item>
 | 
			
		||||
                                    <Descriptions.Item itemKey="总收益">{renderQuota(userState?.user?.aff_history_quota)}</Descriptions.Item>
 | 
			
		||||
                                    <Descriptions.Item itemKey="邀请人数">{userState?.user?.aff_count}</Descriptions.Item>
 | 
			
		||||
                                </Descriptions>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </Card>
 | 
			
		||||
                        <Card>
 | 
			
		||||
                            <Typography.Title heading={6}>个人信息</Typography.Title>
 | 
			
		||||
                            <div style={{marginTop: 20}}>
 | 
			
		||||
                                <Typography.Text strong>邮箱</Typography.Text>
 | 
			
		||||
                                <div style={{display: 'flex', justifyContent: 'space-between'}}>
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        <Input
 | 
			
		||||
                                            value={userState.user && userState.user.email !== ''?userState.user.email:'未绑定'}
 | 
			
		||||
                                            readonly={true}
 | 
			
		||||
                                        ></Input>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        <Button onClick={()=>{setShowEmailBindModal(true)}} disabled={userState.user && userState.user.email !== ''}>绑定邮箱</Button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div style={{marginTop: 10}}>
 | 
			
		||||
                                <Typography.Text strong>微信</Typography.Text>
 | 
			
		||||
                                <div style={{display: 'flex', justifyContent: 'space-between'}}>
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        <Input
 | 
			
		||||
                                            value={userState.user && userState.user.wechat_id !== ''?'已绑定':'未绑定'}
 | 
			
		||||
                                            readonly={true}
 | 
			
		||||
                                        ></Input>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        <Button disabled={(userState.user && userState.user.wechat_id !== '') || !status.wechat_login}>
 | 
			
		||||
                                            {
 | 
			
		||||
                                                status.wechat_login?'绑定':'未启用'
 | 
			
		||||
                                            }
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div style={{marginTop: 10}}>
 | 
			
		||||
                                <Typography.Text strong>GitHub</Typography.Text>
 | 
			
		||||
                                <div style={{display: 'flex', justifyContent: 'space-between'}}>
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        <Input
 | 
			
		||||
                                            value={userState.user && userState.user.github_id !== ''?userState.user.github_id:'未绑定'}
 | 
			
		||||
                                            readonly={true}
 | 
			
		||||
                                        ></Input>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div>
 | 
			
		||||
                                        <Button
 | 
			
		||||
                                            onClick={() => {onGitHubOAuthClicked(status.github_client_id)}}
 | 
			
		||||
                                            disabled={(userState.user && userState.user.github_id !== '') || !status.github_oauth}
 | 
			
		||||
                                        >
 | 
			
		||||
                                            {
 | 
			
		||||
                                                status.github_oauth?'绑定':'未启用'
 | 
			
		||||
                                            }
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div style={{marginTop: 10}}>
 | 
			
		||||
                                <Space>
 | 
			
		||||
                                    <Button onClick={generateAccessToken}>生成系统访问令牌</Button>
 | 
			
		||||
                                    <Button onClick={() => {
 | 
			
		||||
                                        setShowAccountDeleteModal(true);
 | 
			
		||||
                                    }}>删除个人账户</Button>
 | 
			
		||||
                                </Space>
 | 
			
		||||
 | 
			
		||||
                                {systemToken && (
 | 
			
		||||
                                    <Form.Input
 | 
			
		||||
                                        fluid
 | 
			
		||||
                                        readOnly
 | 
			
		||||
                                        value={systemToken}
 | 
			
		||||
                                        onClick={handleSystemTokenClick}
 | 
			
		||||
                                        style={{marginTop: '10px'}}
 | 
			
		||||
                                    />
 | 
			
		||||
                                )}
 | 
			
		||||
                                {
 | 
			
		||||
                                    status.wechat_login && (
 | 
			
		||||
                                        <Button
 | 
			
		||||
                                            onClick={() => {
 | 
			
		||||
                                                setShowWeChatBindModal(true);
 | 
			
		||||
                                            }}
 | 
			
		||||
                                        >
 | 
			
		||||
                                            绑定微信账号
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
                                <Modal
 | 
			
		||||
                                    onCancel={() => setShowWeChatBindModal(false)}
 | 
			
		||||
                                    // onOpen={() => setShowWeChatBindModal(true)}
 | 
			
		||||
                                    visible={showWeChatBindModal}
 | 
			
		||||
                                    size={'mini'}
 | 
			
		||||
                                >
 | 
			
		||||
                                    <Image src={status.wechat_qrcode} fluid/>
 | 
			
		||||
                                    <div style={{textAlign: 'center'}}>
 | 
			
		||||
                                        <p>
 | 
			
		||||
                                            微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
 | 
			
		||||
                                        </p>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <Form size='large'>
 | 
			
		||||
                                        <Form.Input
 | 
			
		||||
                                            fluid
 | 
			
		||||
                                            placeholder='验证码'
 | 
			
		||||
                                            name='wechat_verification_code'
 | 
			
		||||
                                            value={inputs.wechat_verification_code}
 | 
			
		||||
                                            onChange={handleInputChange}
 | 
			
		||||
                                        />
 | 
			
		||||
                                        <Button color='' fluid size='large' onClick={bindWeChat}>
 | 
			
		||||
                                            绑定
 | 
			
		||||
                                        </Button>
 | 
			
		||||
                                    </Form>
 | 
			
		||||
                                </Modal>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </Card>
 | 
			
		||||
                        <Modal
 | 
			
		||||
                            onCancel={() => setShowEmailBindModal(false)}
 | 
			
		||||
                            // onOpen={() => setShowEmailBindModal(true)}
 | 
			
		||||
                            onOk={bindEmail}
 | 
			
		||||
                            visible={showEmailBindModal}
 | 
			
		||||
                            size={'small'}
 | 
			
		||||
                            centered={true}
 | 
			
		||||
                            maskClosable={false}
 | 
			
		||||
                        >
 | 
			
		||||
                            <Typography.Title heading={6}>绑定邮箱地址</Typography.Title>
 | 
			
		||||
                            <div style={{marginTop: 20, display: 'flex', justifyContent: 'space-between'}}>
 | 
			
		||||
                                <Input
 | 
			
		||||
                                    fluid
 | 
			
		||||
                                    placeholder='输入邮箱地址'
 | 
			
		||||
                                    onChange={(value)=>handleInputChange('email', value)}
 | 
			
		||||
                                    name='email'
 | 
			
		||||
                                    type='email'
 | 
			
		||||
                                />
 | 
			
		||||
                                <Button onClick={sendVerificationCode}
 | 
			
		||||
                                        disabled={disableButton || loading}>
 | 
			
		||||
                                    {disableButton ? `重新发送(${countdown})` : '获取验证码'}
 | 
			
		||||
                                </Button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div style={{marginTop: 10}}>
 | 
			
		||||
                                <Input
 | 
			
		||||
                                    fluid
 | 
			
		||||
                                    placeholder='验证码'
 | 
			
		||||
                                    name='email_verification_code'
 | 
			
		||||
                                    value={inputs.email_verification_code}
 | 
			
		||||
                                    onChange={(value)=>handleInputChange('email_verification_code', value)}
 | 
			
		||||
                                />
 | 
			
		||||
                            </div>
 | 
			
		||||
                            {turnstileEnabled ? (
 | 
			
		||||
                                <Turnstile
 | 
			
		||||
                                    sitekey={turnstileSiteKey}
 | 
			
		||||
                                    onVerify={(token) => {
 | 
			
		||||
                                        setTurnstileToken(token);
 | 
			
		||||
                                    }}
 | 
			
		||||
                                />
 | 
			
		||||
                            ) : (
 | 
			
		||||
                                <></>
 | 
			
		||||
                            )}
 | 
			
		||||
                        </Modal>
 | 
			
		||||
                        <Modal
 | 
			
		||||
                            onCancel={() => setShowAccountDeleteModal(false)}
 | 
			
		||||
                            visible={showAccountDeleteModal}
 | 
			
		||||
                            size={'small'}
 | 
			
		||||
                            centered={true}
 | 
			
		||||
                            onOk={deleteAccount}
 | 
			
		||||
                        >
 | 
			
		||||
                            <div style={{marginTop: 20}}>
 | 
			
		||||
                                <Banner
 | 
			
		||||
                                    type="danger"
 | 
			
		||||
                                    description="您正在删除自己的帐户,将清空所有数据且不可恢复"
 | 
			
		||||
                                    closeIcon={null}
 | 
			
		||||
                                />
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div style={{marginTop: 20}}>
 | 
			
		||||
                                <Input
 | 
			
		||||
                                    placeholder={`输入你的账户名 ${userState?.user?.username} 以确认删除`}
 | 
			
		||||
                                    name='self_account_deletion_confirmation'
 | 
			
		||||
                                    value={inputs.self_account_deletion_confirmation}
 | 
			
		||||
                                    onChange={(value)=>handleInputChange('self_account_deletion_confirmation', value)}
 | 
			
		||||
                                />
 | 
			
		||||
                                {turnstileEnabled ? (
 | 
			
		||||
                                    <Turnstile
 | 
			
		||||
                                        sitekey={turnstileSiteKey}
 | 
			
		||||
                                        onVerify={(token) => {
 | 
			
		||||
                                            setTurnstileToken(token);
 | 
			
		||||
                                        }}
 | 
			
		||||
                                    />
 | 
			
		||||
                                ) : (
 | 
			
		||||
                                    <></>
 | 
			
		||||
                                )}
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </Modal>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                </Layout.Content>
 | 
			
		||||
            </Layout>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
    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 (
 | 
			
		||||
    <div style={{ lineHeight: '40px' }}>
 | 
			
		||||
      <Header as='h3'>通用设置</Header>
 | 
			
		||||
      <Message>
 | 
			
		||||
        注意,此处生成的令牌用于系统管理,而非用于请求 OpenAI 相关的服务,请知悉。
 | 
			
		||||
      </Message>
 | 
			
		||||
      <Button as={Link} to={`/user/edit/`}>
 | 
			
		||||
        更新个人信息
 | 
			
		||||
      </Button>
 | 
			
		||||
      <Button onClick={generateAccessToken}>生成系统访问令牌</Button>
 | 
			
		||||
      <Button onClick={getAffLink}>复制邀请链接</Button>
 | 
			
		||||
      <Button onClick={() => {
 | 
			
		||||
        setShowAccountDeleteModal(true);
 | 
			
		||||
      }}>删除个人账户</Button>
 | 
			
		||||
      
 | 
			
		||||
      {systemToken && (
 | 
			
		||||
        <Form.Input 
 | 
			
		||||
          fluid 
 | 
			
		||||
          readOnly 
 | 
			
		||||
          value={systemToken} 
 | 
			
		||||
          onClick={handleSystemTokenClick}
 | 
			
		||||
          style={{ marginTop: '10px' }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      {affLink && (
 | 
			
		||||
        <Form.Input 
 | 
			
		||||
          fluid 
 | 
			
		||||
          readOnly 
 | 
			
		||||
          value={affLink} 
 | 
			
		||||
          onClick={handleAffLinkClick}
 | 
			
		||||
          style={{ marginTop: '10px' }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      <Divider />
 | 
			
		||||
      <Header as='h3'>账号绑定</Header>
 | 
			
		||||
      {
 | 
			
		||||
        status.wechat_login && (
 | 
			
		||||
          <Button
 | 
			
		||||
            onClick={() => {
 | 
			
		||||
              setShowWeChatBindModal(true);
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            绑定微信账号
 | 
			
		||||
          </Button>
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      <Modal
 | 
			
		||||
        onClose={() => setShowWeChatBindModal(false)}
 | 
			
		||||
        onOpen={() => setShowWeChatBindModal(true)}
 | 
			
		||||
        open={showWeChatBindModal}
 | 
			
		||||
        size={'mini'}
 | 
			
		||||
      >
 | 
			
		||||
        <Modal.Content>
 | 
			
		||||
          <Modal.Description>
 | 
			
		||||
            <Image src={status.wechat_qrcode} fluid />
 | 
			
		||||
            <div style={{ textAlign: 'center' }}>
 | 
			
		||||
              <p>
 | 
			
		||||
                微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)
 | 
			
		||||
              </p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <Form size='large'>
 | 
			
		||||
              <Form.Input
 | 
			
		||||
                fluid
 | 
			
		||||
                placeholder='验证码'
 | 
			
		||||
                name='wechat_verification_code'
 | 
			
		||||
                value={inputs.wechat_verification_code}
 | 
			
		||||
                onChange={handleInputChange}
 | 
			
		||||
              />
 | 
			
		||||
              <Button color='' fluid size='large' onClick={bindWeChat}>
 | 
			
		||||
                绑定
 | 
			
		||||
              </Button>
 | 
			
		||||
            </Form>
 | 
			
		||||
          </Modal.Description>
 | 
			
		||||
        </Modal.Content>
 | 
			
		||||
      </Modal>
 | 
			
		||||
      {
 | 
			
		||||
        status.github_oauth && (
 | 
			
		||||
          <Button onClick={()=>{onGitHubOAuthClicked(status.github_client_id)}}>绑定 GitHub 账号</Button>
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      <Button
 | 
			
		||||
        onClick={() => {
 | 
			
		||||
          setShowEmailBindModal(true);
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        绑定邮箱地址
 | 
			
		||||
      </Button>
 | 
			
		||||
      <Modal
 | 
			
		||||
        onClose={() => setShowEmailBindModal(false)}
 | 
			
		||||
        onOpen={() => setShowEmailBindModal(true)}
 | 
			
		||||
        open={showEmailBindModal}
 | 
			
		||||
        size={'tiny'}
 | 
			
		||||
        style={{ maxWidth: '450px' }}
 | 
			
		||||
      >
 | 
			
		||||
        <Modal.Header>绑定邮箱地址</Modal.Header>
 | 
			
		||||
        <Modal.Content>
 | 
			
		||||
          <Modal.Description>
 | 
			
		||||
            <Form size='large'>
 | 
			
		||||
              <Form.Input
 | 
			
		||||
                fluid
 | 
			
		||||
                placeholder='输入邮箱地址'
 | 
			
		||||
                onChange={handleInputChange}
 | 
			
		||||
                name='email'
 | 
			
		||||
                type='email'
 | 
			
		||||
                action={
 | 
			
		||||
                  <Button onClick={sendVerificationCode} disabled={disableButton || loading}>
 | 
			
		||||
                    {disableButton ? `重新发送(${countdown})` : '获取验证码'}
 | 
			
		||||
                  </Button>
 | 
			
		||||
                }
 | 
			
		||||
              />
 | 
			
		||||
              <Form.Input
 | 
			
		||||
                fluid
 | 
			
		||||
                placeholder='验证码'
 | 
			
		||||
                name='email_verification_code'
 | 
			
		||||
                value={inputs.email_verification_code}
 | 
			
		||||
                onChange={handleInputChange}
 | 
			
		||||
              />
 | 
			
		||||
              {turnstileEnabled ? (
 | 
			
		||||
                <Turnstile
 | 
			
		||||
                  sitekey={turnstileSiteKey}
 | 
			
		||||
                  onVerify={(token) => {
 | 
			
		||||
                    setTurnstileToken(token);
 | 
			
		||||
                  }}
 | 
			
		||||
                />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <></>
 | 
			
		||||
              )}
 | 
			
		||||
              <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
 | 
			
		||||
              <Button
 | 
			
		||||
                color=''
 | 
			
		||||
                fluid
 | 
			
		||||
                size='large'
 | 
			
		||||
                onClick={bindEmail}
 | 
			
		||||
                loading={loading}
 | 
			
		||||
              >
 | 
			
		||||
                确认绑定
 | 
			
		||||
              </Button>
 | 
			
		||||
              <div style={{ width: '1rem' }}></div> 
 | 
			
		||||
              <Button
 | 
			
		||||
                fluid
 | 
			
		||||
                size='large'
 | 
			
		||||
                onClick={() => setShowEmailBindModal(false)}
 | 
			
		||||
              >
 | 
			
		||||
                取消
 | 
			
		||||
              </Button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </Form>
 | 
			
		||||
          </Modal.Description>
 | 
			
		||||
        </Modal.Content>
 | 
			
		||||
      </Modal>
 | 
			
		||||
      <Modal
 | 
			
		||||
        onClose={() => setShowAccountDeleteModal(false)}
 | 
			
		||||
        onOpen={() => setShowAccountDeleteModal(true)}
 | 
			
		||||
        open={showAccountDeleteModal}
 | 
			
		||||
        size={'tiny'}
 | 
			
		||||
        style={{ maxWidth: '450px' }}
 | 
			
		||||
      >
 | 
			
		||||
        <Modal.Header>危险操作</Modal.Header>
 | 
			
		||||
        <Modal.Content>
 | 
			
		||||
        <Message>您正在删除自己的帐户,将清空所有数据且不可恢复</Message>
 | 
			
		||||
          <Modal.Description>
 | 
			
		||||
            <Form size='large'>
 | 
			
		||||
              <Form.Input
 | 
			
		||||
                fluid
 | 
			
		||||
                placeholder={`输入你的账户名 ${userState?.user?.username} 以确认删除`}
 | 
			
		||||
                name='self_account_deletion_confirmation'
 | 
			
		||||
                value={inputs.self_account_deletion_confirmation}
 | 
			
		||||
                onChange={handleInputChange}
 | 
			
		||||
              />
 | 
			
		||||
              {turnstileEnabled ? (
 | 
			
		||||
                <Turnstile
 | 
			
		||||
                  sitekey={turnstileSiteKey}
 | 
			
		||||
                  onVerify={(token) => {
 | 
			
		||||
                    setTurnstileToken(token);
 | 
			
		||||
                  }}
 | 
			
		||||
                />
 | 
			
		||||
              ) : (
 | 
			
		||||
                <></>
 | 
			
		||||
              )}
 | 
			
		||||
              <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '1rem' }}>
 | 
			
		||||
                <Button
 | 
			
		||||
                  color='red'
 | 
			
		||||
                  fluid
 | 
			
		||||
                  size='large'
 | 
			
		||||
                  onClick={deleteAccount}
 | 
			
		||||
                  loading={loading}
 | 
			
		||||
                >
 | 
			
		||||
                  确认删除
 | 
			
		||||
                </Button>
 | 
			
		||||
                <div style={{ width: '1rem' }}></div>
 | 
			
		||||
                <Button
 | 
			
		||||
                  fluid
 | 
			
		||||
                  size='large'
 | 
			
		||||
                  onClick={() => setShowAccountDeleteModal(false)}
 | 
			
		||||
                >
 | 
			
		||||
                  取消
 | 
			
		||||
                </Button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </Form>
 | 
			
		||||
          </Modal.Description>
 | 
			
		||||
        </Modal.Content>
 | 
			
		||||
      </Modal>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default PersonalSetting;
 | 
			
		||||
 
 | 
			
		||||
@@ -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');
 | 
			
		||||
 
 | 
			
		||||
@@ -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: () => (
 | 
			
		||||
        <Tab.Pane attached={false}>
 | 
			
		||||
          <PersonalSetting />
 | 
			
		||||
        </Tab.Pane>
 | 
			
		||||
      )
 | 
			
		||||
    let panes = [
 | 
			
		||||
        {
 | 
			
		||||
            tab: '个人设置',
 | 
			
		||||
            content: <PersonalSetting/>,
 | 
			
		||||
            itemKey: '1'
 | 
			
		||||
        }
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    if (isRoot()) {
 | 
			
		||||
        panes.push({
 | 
			
		||||
            tab: '运营设置',
 | 
			
		||||
            content: <OperationSetting/>,
 | 
			
		||||
            itemKey: '2'
 | 
			
		||||
        });
 | 
			
		||||
        panes.push({
 | 
			
		||||
            tab: '系统设置',
 | 
			
		||||
            content: <SystemSetting/>,
 | 
			
		||||
            itemKey: '3'
 | 
			
		||||
        });
 | 
			
		||||
        panes.push({
 | 
			
		||||
            tab: '其他设置',
 | 
			
		||||
            content: <OtherSetting/>,
 | 
			
		||||
            itemKey: '4'
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  if (isRoot()) {
 | 
			
		||||
    panes.push({
 | 
			
		||||
      menuItem: '运营设置',
 | 
			
		||||
      render: () => (
 | 
			
		||||
        <Tab.Pane attached={false}>
 | 
			
		||||
          <OperationSetting />
 | 
			
		||||
        </Tab.Pane>
 | 
			
		||||
      )
 | 
			
		||||
    });
 | 
			
		||||
    panes.push({
 | 
			
		||||
      menuItem: '系统设置',
 | 
			
		||||
      render: () => (
 | 
			
		||||
        <Tab.Pane attached={false}>
 | 
			
		||||
          <SystemSetting />
 | 
			
		||||
        </Tab.Pane>
 | 
			
		||||
      )
 | 
			
		||||
    });
 | 
			
		||||
    panes.push({
 | 
			
		||||
      menuItem: '其他设置',
 | 
			
		||||
      render: () => (
 | 
			
		||||
        <Tab.Pane attached={false}>
 | 
			
		||||
          <OtherSetting />
 | 
			
		||||
        </Tab.Pane>
 | 
			
		||||
      )
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Segment>
 | 
			
		||||
      <Tab menu={{ secondary: true, pointing: true }} panes={panes} />
 | 
			
		||||
    </Segment>
 | 
			
		||||
  );
 | 
			
		||||
    return (
 | 
			
		||||
        <div>
 | 
			
		||||
            <Layout>
 | 
			
		||||
                <Layout.Content>
 | 
			
		||||
                    <Tabs type="line" defaultActiveKey="1">
 | 
			
		||||
                        {panes.map(pane => (
 | 
			
		||||
                            <TabPane itemKey={pane.itemKey} tab={pane.tab}>
 | 
			
		||||
                                {pane.content}
 | 
			
		||||
                            </TabPane>
 | 
			
		||||
                        ))}
 | 
			
		||||
                    </Tabs>
 | 
			
		||||
                </Layout.Content>
 | 
			
		||||
            </Layout>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Setting;
 | 
			
		||||
 
 | 
			
		||||
@@ -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 (
 | 
			
		||||
    <>
 | 
			
		||||
      <Segment loading={loading}>
 | 
			
		||||
        <Header as='h3'>更新用户信息</Header>
 | 
			
		||||
      <SideSheet
 | 
			
		||||
          placement={'left'}
 | 
			
		||||
          title={<Title level={3}>更新用户信息</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}
 | 
			
		||||
      >
 | 
			
		||||
        <Form autoComplete='new-password'>
 | 
			
		||||
          <Form.Field>
 | 
			
		||||
            <Form.Input
 | 
			
		||||
              label='用户名'
 | 
			
		||||
              name='username'
 | 
			
		||||
              placeholder={'请输入新的用户名'}
 | 
			
		||||
              onChange={handleInputChange}
 | 
			
		||||
              value={username}
 | 
			
		||||
              autoComplete='new-password'
 | 
			
		||||
                label='用户名'
 | 
			
		||||
                name='username'
 | 
			
		||||
                placeholder={'请输入新的用户名'}
 | 
			
		||||
                onChange={handleInputChange}
 | 
			
		||||
                value={username}
 | 
			
		||||
                autoComplete='new-password'
 | 
			
		||||
            />
 | 
			
		||||
          </Form.Field>
 | 
			
		||||
          <Form.Field>
 | 
			
		||||
            <Form.Input
 | 
			
		||||
              label='密码'
 | 
			
		||||
              name='password'
 | 
			
		||||
              type={'password'}
 | 
			
		||||
              placeholder={'请输入新的密码,最短 8 位'}
 | 
			
		||||
              onChange={handleInputChange}
 | 
			
		||||
              value={password}
 | 
			
		||||
              autoComplete='new-password'
 | 
			
		||||
                label='密码'
 | 
			
		||||
                name='password'
 | 
			
		||||
                type={'password'}
 | 
			
		||||
                placeholder={'请输入新的密码,最短 8 位'}
 | 
			
		||||
                onChange={handleInputChange}
 | 
			
		||||
                value={password}
 | 
			
		||||
                autoComplete='new-password'
 | 
			
		||||
            />
 | 
			
		||||
          </Form.Field>
 | 
			
		||||
          <Form.Field>
 | 
			
		||||
            <Form.Input
 | 
			
		||||
              label='显示名称'
 | 
			
		||||
              name='display_name'
 | 
			
		||||
              placeholder={'请输入新的显示名称'}
 | 
			
		||||
              onChange={handleInputChange}
 | 
			
		||||
              value={display_name}
 | 
			
		||||
              autoComplete='new-password'
 | 
			
		||||
                label='显示名称'
 | 
			
		||||
                name='display_name'
 | 
			
		||||
                placeholder={'请输入新的显示名称'}
 | 
			
		||||
                onChange={handleInputChange}
 | 
			
		||||
                value={display_name}
 | 
			
		||||
                autoComplete='new-password'
 | 
			
		||||
            />
 | 
			
		||||
          </Form.Field>
 | 
			
		||||
          {
 | 
			
		||||
            userId && <>
 | 
			
		||||
              <Form.Field>
 | 
			
		||||
                <Form.Dropdown
 | 
			
		||||
                  label='分组'
 | 
			
		||||
                  placeholder={'请选择分组'}
 | 
			
		||||
                  name='group'
 | 
			
		||||
                  fluid
 | 
			
		||||
                  search
 | 
			
		||||
                  selection
 | 
			
		||||
                  allowAdditions
 | 
			
		||||
                  additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
 | 
			
		||||
                  onChange={handleInputChange}
 | 
			
		||||
                  value={inputs.group}
 | 
			
		||||
                  autoComplete='new-password'
 | 
			
		||||
                  options={groupOptions}
 | 
			
		||||
                />
 | 
			
		||||
              </Form.Field>
 | 
			
		||||
              <Form.Field>
 | 
			
		||||
                <Form.Input
 | 
			
		||||
                  label={`剩余额度${renderQuotaWithPrompt(quota)}`}
 | 
			
		||||
                  name='quota'
 | 
			
		||||
                  placeholder={'请输入新的剩余额度'}
 | 
			
		||||
                  onChange={handleInputChange}
 | 
			
		||||
                  value={quota}
 | 
			
		||||
                  type={'number'}
 | 
			
		||||
                  autoComplete='new-password'
 | 
			
		||||
                />
 | 
			
		||||
              </Form.Field>
 | 
			
		||||
            </>
 | 
			
		||||
              userId && <>
 | 
			
		||||
                <Form.Field>
 | 
			
		||||
                  <Form.Dropdown
 | 
			
		||||
                      label='分组'
 | 
			
		||||
                      placeholder={'请选择分组'}
 | 
			
		||||
                      name='group'
 | 
			
		||||
                      fluid
 | 
			
		||||
                      search
 | 
			
		||||
                      selection
 | 
			
		||||
                      allowAdditions
 | 
			
		||||
                      additionLabel={'请在系统设置页面编辑分组倍率以添加新的分组:'}
 | 
			
		||||
                      onChange={handleInputChange}
 | 
			
		||||
                      value={inputs.group}
 | 
			
		||||
                      autoComplete='new-password'
 | 
			
		||||
                      options={groupOptions}
 | 
			
		||||
                  />
 | 
			
		||||
                </Form.Field>
 | 
			
		||||
                <Form.Field>
 | 
			
		||||
                  <Form.Input
 | 
			
		||||
                      label={`剩余额度${renderQuotaWithPrompt(quota)}`}
 | 
			
		||||
                      name='quota'
 | 
			
		||||
                      placeholder={'请输入新的剩余额度'}
 | 
			
		||||
                      onChange={handleInputChange}
 | 
			
		||||
                      value={quota}
 | 
			
		||||
                      type={'number'}
 | 
			
		||||
                      autoComplete='new-password'
 | 
			
		||||
                  />
 | 
			
		||||
                </Form.Field>
 | 
			
		||||
              </>
 | 
			
		||||
          }
 | 
			
		||||
          <Form.Field>
 | 
			
		||||
            <Form.Input
 | 
			
		||||
              label='已绑定的 GitHub 账户'
 | 
			
		||||
              name='github_id'
 | 
			
		||||
              value={github_id}
 | 
			
		||||
              autoComplete='new-password'
 | 
			
		||||
              placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
			
		||||
              readOnly
 | 
			
		||||
                label='已绑定的 GitHub 账户'
 | 
			
		||||
                name='github_id'
 | 
			
		||||
                value={github_id}
 | 
			
		||||
                autoComplete='new-password'
 | 
			
		||||
                placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
			
		||||
                readOnly
 | 
			
		||||
            />
 | 
			
		||||
          </Form.Field>
 | 
			
		||||
          <Form.Field>
 | 
			
		||||
            <Form.Input
 | 
			
		||||
              label='已绑定的微信账户'
 | 
			
		||||
              name='wechat_id'
 | 
			
		||||
              value={wechat_id}
 | 
			
		||||
              autoComplete='new-password'
 | 
			
		||||
              placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
			
		||||
              readOnly
 | 
			
		||||
                label='已绑定的微信账户'
 | 
			
		||||
                name='wechat_id'
 | 
			
		||||
                value={wechat_id}
 | 
			
		||||
                autoComplete='new-password'
 | 
			
		||||
                placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
			
		||||
                readOnly
 | 
			
		||||
            />
 | 
			
		||||
          </Form.Field>
 | 
			
		||||
          <Form.Field>
 | 
			
		||||
            <Form.Input
 | 
			
		||||
              label='已绑定的邮箱账户'
 | 
			
		||||
              name='email'
 | 
			
		||||
              value={email}
 | 
			
		||||
              autoComplete='new-password'
 | 
			
		||||
              placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
			
		||||
              readOnly
 | 
			
		||||
                label='已绑定的邮箱账户'
 | 
			
		||||
                name='email'
 | 
			
		||||
                value={email}
 | 
			
		||||
                autoComplete='new-password'
 | 
			
		||||
                placeholder='此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改'
 | 
			
		||||
                readOnly
 | 
			
		||||
            />
 | 
			
		||||
          </Form.Field>
 | 
			
		||||
          <Button onClick={handleCancel}>取消</Button>
 | 
			
		||||
          <Button positive onClick={submit}>提交</Button>
 | 
			
		||||
        </Form>
 | 
			
		||||
      </Segment>
 | 
			
		||||
 | 
			
		||||
      </SideSheet>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user