mirror of
				https://github.com/linux-do/new-api.git
				synced 2025-11-04 05:13:41 +08:00 
			
		
		
		
	@@ -50,10 +50,10 @@ var WeChatAccountQRCodeImageURL = ""
 | 
			
		||||
var TurnstileSiteKey = ""
 | 
			
		||||
var TurnstileSecretKey = ""
 | 
			
		||||
 | 
			
		||||
var QuotaForNewUser = 100
 | 
			
		||||
var QuotaForNewUser = 0
 | 
			
		||||
var ChannelDisableThreshold = 5.0
 | 
			
		||||
var AutomaticDisableChannelEnabled = false
 | 
			
		||||
var QuotaRemindThreshold = 1000 // TODO: QuotaRemindThreshold
 | 
			
		||||
var QuotaRemindThreshold = 1000
 | 
			
		||||
 | 
			
		||||
var RootUserEmail = ""
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -100,7 +100,6 @@ func GetTokenStatus(c *gin.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func AddToken(c *gin.Context) {
 | 
			
		||||
	isAdmin := c.GetInt("role") >= common.RoleAdminUser
 | 
			
		||||
	token := model.Token{}
 | 
			
		||||
	err := c.ShouldBindJSON(&token)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -118,27 +117,14 @@ func AddToken(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	cleanToken := model.Token{
 | 
			
		||||
		UserId:       c.GetInt("id"),
 | 
			
		||||
		Name:         token.Name,
 | 
			
		||||
		Key:          common.GetUUID(),
 | 
			
		||||
		CreatedTime:  common.GetTimestamp(),
 | 
			
		||||
		AccessedTime: common.GetTimestamp(),
 | 
			
		||||
		ExpiredTime:  token.ExpiredTime,
 | 
			
		||||
	}
 | 
			
		||||
	if isAdmin {
 | 
			
		||||
		cleanToken.RemainQuota = token.RemainQuota
 | 
			
		||||
		cleanToken.UnlimitedQuota = token.UnlimitedQuota
 | 
			
		||||
	} else {
 | 
			
		||||
		userId := c.GetInt("id")
 | 
			
		||||
		quota, err := model.GetUserQuota(userId)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
				"success": false,
 | 
			
		||||
				"message": err.Error(),
 | 
			
		||||
			})
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		cleanToken.RemainQuota = quota
 | 
			
		||||
		UserId:         c.GetInt("id"),
 | 
			
		||||
		Name:           token.Name,
 | 
			
		||||
		Key:            common.GetUUID(),
 | 
			
		||||
		CreatedTime:    common.GetTimestamp(),
 | 
			
		||||
		AccessedTime:   common.GetTimestamp(),
 | 
			
		||||
		ExpiredTime:    token.ExpiredTime,
 | 
			
		||||
		RemainQuota:    token.RemainQuota,
 | 
			
		||||
		UnlimitedQuota: token.UnlimitedQuota,
 | 
			
		||||
	}
 | 
			
		||||
	err = cleanToken.Insert()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -148,10 +134,6 @@ func AddToken(c *gin.Context) {
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !isAdmin {
 | 
			
		||||
		// update user quota
 | 
			
		||||
		err = model.DecreaseUserQuota(c.GetInt("id"), cleanToken.RemainQuota)
 | 
			
		||||
	}
 | 
			
		||||
	c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
		"success": true,
 | 
			
		||||
		"message": "",
 | 
			
		||||
@@ -240,34 +222,3 @@ func UpdateToken(c *gin.Context) {
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type topUpRequest struct {
 | 
			
		||||
	Id  int    `json:"id"`
 | 
			
		||||
	Key string `json:"key"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TopUp(c *gin.Context) {
 | 
			
		||||
	req := topUpRequest{}
 | 
			
		||||
	err := c.ShouldBindJSON(&req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
			"success": false,
 | 
			
		||||
			"message": err.Error(),
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	quota, err := model.Redeem(req.Key, req.Id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
			"success": false,
 | 
			
		||||
			"message": err.Error(),
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
		"success": true,
 | 
			
		||||
		"message": "",
 | 
			
		||||
		"data":    quota,
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -654,3 +654,34 @@ func EmailBind(c *gin.Context) {
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type topUpRequest struct {
 | 
			
		||||
	Key string `json:"key"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TopUp(c *gin.Context) {
 | 
			
		||||
	req := topUpRequest{}
 | 
			
		||||
	err := c.ShouldBindJSON(&req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
			"success": false,
 | 
			
		||||
			"message": err.Error(),
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	id := c.GetInt("id")
 | 
			
		||||
	quota, err := model.Redeem(req.Key, id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
			"success": false,
 | 
			
		||||
			"message": err.Error(),
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	c.JSON(http.StatusOK, gin.H{
 | 
			
		||||
		"success": true,
 | 
			
		||||
		"message": "",
 | 
			
		||||
		"data":    quota,
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,12 +40,12 @@ func GetRedemptionById(id int) (*Redemption, error) {
 | 
			
		||||
	return &redemption, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Redeem(key string, tokenId int) (quota int, err error) {
 | 
			
		||||
func Redeem(key string, userId int) (quota int, err error) {
 | 
			
		||||
	if key == "" {
 | 
			
		||||
		return 0, errors.New("未提供兑换码")
 | 
			
		||||
	}
 | 
			
		||||
	if tokenId == 0 {
 | 
			
		||||
		return 0, errors.New("未提供 token id")
 | 
			
		||||
	if userId == 0 {
 | 
			
		||||
		return 0, errors.New("无效的 user id")
 | 
			
		||||
	}
 | 
			
		||||
	redemption := &Redemption{}
 | 
			
		||||
	err = DB.Where("`key` = ?", key).First(redemption).Error
 | 
			
		||||
@@ -55,7 +55,7 @@ func Redeem(key string, tokenId int) (quota int, err error) {
 | 
			
		||||
	if redemption.Status != common.RedemptionCodeStatusEnabled {
 | 
			
		||||
		return 0, errors.New("该兑换码已被使用")
 | 
			
		||||
	}
 | 
			
		||||
	err = IncreaseTokenQuota(tokenId, redemption.Quota)
 | 
			
		||||
	err = IncreaseUserQuota(userId, redemption.Quota)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package model
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	_ "gorm.io/driver/sqlite"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	"one-api/common"
 | 
			
		||||
@@ -82,6 +83,16 @@ func GetTokenByIds(id int, userId int) (*Token, error) {
 | 
			
		||||
	return &token, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetTokenById(id int) (*Token, error) {
 | 
			
		||||
	if id == 0 {
 | 
			
		||||
		return nil, errors.New("id 为空!")
 | 
			
		||||
	}
 | 
			
		||||
	token := Token{Id: id}
 | 
			
		||||
	var err error = nil
 | 
			
		||||
	err = DB.First(&token, "id = ?", id).Error
 | 
			
		||||
	return &token, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (token *Token) Insert() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	err = DB.Create(token).Error
 | 
			
		||||
@@ -116,26 +127,53 @@ func DeleteTokenById(id int, userId int) (err error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	quota := token.RemainQuota
 | 
			
		||||
	if quota != 0 {
 | 
			
		||||
		if quota > 0 {
 | 
			
		||||
			err = IncreaseUserQuota(userId, quota)
 | 
			
		||||
		} else {
 | 
			
		||||
			err = DecreaseUserQuota(userId, -quota)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return token.Delete()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IncreaseTokenQuota(id int, quota int) (err error) {
 | 
			
		||||
	err = DB.Model(&Token{}).Where("id = ?", id).Update("remain_quota", gorm.Expr("remain_quota + ?", quota)).Error
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DecreaseTokenQuota(id int, quota int) (err error) {
 | 
			
		||||
	err = DB.Model(&Token{}).Where("id = ?", id).Update("remain_quota", gorm.Expr("remain_quota - ?", quota)).Error
 | 
			
		||||
func DecreaseTokenQuota(tokenId int, quota int) (err error) {
 | 
			
		||||
	if quota < 0 {
 | 
			
		||||
		return errors.New("quota 不能为负数!")
 | 
			
		||||
	}
 | 
			
		||||
	token, err := GetTokenById(tokenId)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if token.RemainQuota < quota {
 | 
			
		||||
		return errors.New("令牌额度不足")
 | 
			
		||||
	}
 | 
			
		||||
	userQuota, err := GetUserQuota(token.UserId)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if userQuota < quota {
 | 
			
		||||
		return errors.New("用户额度不足")
 | 
			
		||||
	}
 | 
			
		||||
	quotaTooLow := userQuota >= common.QuotaRemindThreshold && userQuota-quota < common.QuotaRemindThreshold
 | 
			
		||||
	noMoreQuota := userQuota-quota <= 0
 | 
			
		||||
	if quotaTooLow || noMoreQuota {
 | 
			
		||||
		go func() {
 | 
			
		||||
			email, err := GetUserEmail(token.UserId)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				common.SysError("获取用户邮箱失败:" + 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-quota, topUpLink, topUpLink))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					common.SysError("发送邮件失败:" + err.Error())
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
	err = DB.Model(&Token{}).Where("id = ?", tokenId).Update("remain_quota", gorm.Expr("remain_quota - ?", quota)).Error
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err = DecreaseUserQuota(token.UserId, quota)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -225,12 +225,23 @@ func GetUserQuota(id int) (quota int, err error) {
 | 
			
		||||
	return quota, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetUserEmail(id int) (email string, err error) {
 | 
			
		||||
	err = DB.Model(&User{}).Where("id = ?", id).Select("email").Find(&email).Error
 | 
			
		||||
	return email, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IncreaseUserQuota(id int, quota int) (err error) {
 | 
			
		||||
	if quota < 0 {
 | 
			
		||||
		return errors.New("quota 不能为负数!")
 | 
			
		||||
	}
 | 
			
		||||
	err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota + ?", quota)).Error
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DecreaseUserQuota(id int, quota int) (err error) {
 | 
			
		||||
	if quota < 0 {
 | 
			
		||||
		return errors.New("quota 不能为负数!")
 | 
			
		||||
	}
 | 
			
		||||
	err = DB.Model(&User{}).Where("id = ?", id).Update("quota", gorm.Expr("quota - ?", quota)).Error
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -37,6 +37,7 @@ func SetApiRouter(router *gin.Engine) {
 | 
			
		||||
				selfRoute.PUT("/self", controller.UpdateSelf)
 | 
			
		||||
				selfRoute.DELETE("/self", controller.DeleteSelf)
 | 
			
		||||
				selfRoute.GET("/token", controller.GenerateAccessToken)
 | 
			
		||||
				selfRoute.POST("/topup", controller.TopUp)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			adminRoute := userRoute.Group("/")
 | 
			
		||||
@@ -74,7 +75,6 @@ func SetApiRouter(router *gin.Engine) {
 | 
			
		||||
		{
 | 
			
		||||
			tokenRoute.GET("/", controller.GetAllTokens)
 | 
			
		||||
			tokenRoute.GET("/search", controller.SearchTokens)
 | 
			
		||||
			tokenRoute.POST("/topup", controller.TopUp)
 | 
			
		||||
			tokenRoute.GET("/:id", controller.GetToken)
 | 
			
		||||
			tokenRoute.POST("/", controller.AddToken)
 | 
			
		||||
			tokenRoute.PUT("/", controller.UpdateToken)
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import EditToken from './pages/Token/EditToken';
 | 
			
		||||
import EditChannel from './pages/Channel/EditChannel';
 | 
			
		||||
import Redemption from './pages/Redemption';
 | 
			
		||||
import EditRedemption from './pages/Redemption/EditRedemption';
 | 
			
		||||
import TopUp from './pages/TopUp';
 | 
			
		||||
 | 
			
		||||
const Home = lazy(() => import('./pages/Home'));
 | 
			
		||||
const About = lazy(() => import('./pages/About'));
 | 
			
		||||
@@ -239,6 +240,16 @@ function App() {
 | 
			
		||||
          </PrivateRoute>
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
      <Route
 | 
			
		||||
        path='/topup'
 | 
			
		||||
        element={
 | 
			
		||||
        <PrivateRoute>
 | 
			
		||||
          <Suspense fallback={<Loading></Loading>}>
 | 
			
		||||
            <TopUp />
 | 
			
		||||
          </Suspense>
 | 
			
		||||
        </PrivateRoute>
 | 
			
		||||
        }
 | 
			
		||||
      />
 | 
			
		||||
      <Route
 | 
			
		||||
        path='/about'
 | 
			
		||||
        element={
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,12 @@ const headerButtons = [
 | 
			
		||||
    icon: 'dollar sign',
 | 
			
		||||
    admin: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '充值',
 | 
			
		||||
    to: '/topup',
 | 
			
		||||
    icon: 'cart',
 | 
			
		||||
    admin: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    name: '用户',
 | 
			
		||||
    to: '/user',
 | 
			
		||||
 
 | 
			
		||||
@@ -36,8 +36,6 @@ const TokensTable = () => {
 | 
			
		||||
  const [searching, setSearching] = useState(false);
 | 
			
		||||
  const [showTopUpModal, setShowTopUpModal] = useState(false);
 | 
			
		||||
  const [targetTokenIdx, setTargetTokenIdx] = useState(0);
 | 
			
		||||
  const [redemptionCode, setRedemptionCode] = useState('');
 | 
			
		||||
  const [topUpLink, setTopUpLink] = useState('');
 | 
			
		||||
 | 
			
		||||
  const loadTokens = async (startIdx) => {
 | 
			
		||||
    const res = await API.get(`/api/token/?p=${startIdx}`);
 | 
			
		||||
@@ -77,13 +75,6 @@ const TokensTable = () => {
 | 
			
		||||
      .catch((reason) => {
 | 
			
		||||
        showError(reason);
 | 
			
		||||
      });
 | 
			
		||||
    let status = localStorage.getItem('status');
 | 
			
		||||
    if (status) {
 | 
			
		||||
      status = JSON.parse(status);
 | 
			
		||||
      if (status.top_up_link) {
 | 
			
		||||
        setTopUpLink(status.top_up_link);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const manageToken = async (id, action, idx) => {
 | 
			
		||||
@@ -156,28 +147,6 @@ const TokensTable = () => {
 | 
			
		||||
    setLoading(false);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const topUp = async () => {
 | 
			
		||||
    if (redemptionCode === '') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const res = await API.post('/api/token/topup/', {
 | 
			
		||||
      id: tokens[targetTokenIdx].id,
 | 
			
		||||
      key: redemptionCode
 | 
			
		||||
    });
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      showSuccess('充值成功!');
 | 
			
		||||
      let newTokens = [...tokens];
 | 
			
		||||
      let realIdx = (activePage - 1) * ITEMS_PER_PAGE + targetTokenIdx;
 | 
			
		||||
      newTokens[realIdx].remain_quota += data;
 | 
			
		||||
      setTokens(newTokens);
 | 
			
		||||
      setRedemptionCode('');
 | 
			
		||||
      setShowTopUpModal(false);
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Form onSubmit={searchTokens}>
 | 
			
		||||
@@ -279,15 +248,6 @@ const TokensTable = () => {
 | 
			
		||||
                      >
 | 
			
		||||
                        复制
 | 
			
		||||
                      </Button>
 | 
			
		||||
                      <Button
 | 
			
		||||
                        size={'small'}
 | 
			
		||||
                        color={'yellow'}
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          setTargetTokenIdx(idx);
 | 
			
		||||
                          setShowTopUpModal(true);
 | 
			
		||||
                        }}>
 | 
			
		||||
                        充值
 | 
			
		||||
                      </Button>
 | 
			
		||||
                      <Popup
 | 
			
		||||
                        trigger={
 | 
			
		||||
                          <Button size='small' negative>
 | 
			
		||||
@@ -355,39 +315,6 @@ const TokensTable = () => {
 | 
			
		||||
          </Table.Row>
 | 
			
		||||
        </Table.Footer>
 | 
			
		||||
      </Table>
 | 
			
		||||
 | 
			
		||||
      <Modal
 | 
			
		||||
        onClose={() => setShowTopUpModal(false)}
 | 
			
		||||
        onOpen={() => setShowTopUpModal(true)}
 | 
			
		||||
        open={showTopUpModal}
 | 
			
		||||
        size={'mini'}
 | 
			
		||||
      >
 | 
			
		||||
        <Modal.Header>通过兑换码为令牌「{tokens[targetTokenIdx]?.name}」充值</Modal.Header>
 | 
			
		||||
        <Modal.Content>
 | 
			
		||||
          <Modal.Description>
 | 
			
		||||
            {/*<Image src={status.wechat_qrcode} fluid />*/}
 | 
			
		||||
            {
 | 
			
		||||
              topUpLink && <p>
 | 
			
		||||
                  <a target='_blank' href={topUpLink}>点击此处获取兑换码</a>
 | 
			
		||||
              </p>
 | 
			
		||||
            }
 | 
			
		||||
            <Form size='large'>
 | 
			
		||||
              <Form.Input
 | 
			
		||||
                fluid
 | 
			
		||||
                placeholder='兑换码'
 | 
			
		||||
                name='redemptionCode'
 | 
			
		||||
                value={redemptionCode}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                  setRedemptionCode(e.target.value);
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
              <Button color='' fluid size='large' onClick={topUp}>
 | 
			
		||||
                充值
 | 
			
		||||
              </Button>
 | 
			
		||||
            </Form>
 | 
			
		||||
          </Modal.Description>
 | 
			
		||||
        </Modal.Content>
 | 
			
		||||
      </Modal>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { Button, Form, Header, Segment } from 'semantic-ui-react';
 | 
			
		||||
import { useParams } from 'react-router-dom';
 | 
			
		||||
import { API, isAdmin, showError, showSuccess, timestamp2string } from '../../helpers';
 | 
			
		||||
import { API, showError, showSuccess, timestamp2string } from '../../helpers';
 | 
			
		||||
 | 
			
		||||
const EditToken = () => {
 | 
			
		||||
  const params = useParams();
 | 
			
		||||
@@ -14,7 +14,6 @@ const EditToken = () => {
 | 
			
		||||
    expired_time: -1,
 | 
			
		||||
    unlimited_quota: false
 | 
			
		||||
  };
 | 
			
		||||
  const isAdminUser = isAdmin();
 | 
			
		||||
  const [inputs, setInputs] = useState(originInputs);
 | 
			
		||||
  const { name, remain_quota, expired_time, unlimited_quota } = inputs;
 | 
			
		||||
 | 
			
		||||
@@ -107,25 +106,21 @@ const EditToken = () => {
 | 
			
		||||
              required={!isEdit}
 | 
			
		||||
            />
 | 
			
		||||
          </Form.Field>
 | 
			
		||||
          {
 | 
			
		||||
            isAdminUser && <>
 | 
			
		||||
              <Form.Field>
 | 
			
		||||
                <Form.Input
 | 
			
		||||
                  label='额度'
 | 
			
		||||
                  name='remain_quota'
 | 
			
		||||
                  placeholder={'请输入额度'}
 | 
			
		||||
                  onChange={handleInputChange}
 | 
			
		||||
                  value={remain_quota}
 | 
			
		||||
                  autoComplete='new-password'
 | 
			
		||||
                  type='number'
 | 
			
		||||
                  disabled={unlimited_quota}
 | 
			
		||||
                />
 | 
			
		||||
              </Form.Field>
 | 
			
		||||
              <Button type={'button'} style={{marginBottom: '14px'}} onClick={() => {
 | 
			
		||||
                setUnlimitedQuota();
 | 
			
		||||
              }}>{unlimited_quota ? '取消无限额度' : '设置为无限额度'}</Button>
 | 
			
		||||
            </>
 | 
			
		||||
          }
 | 
			
		||||
          <Form.Field>
 | 
			
		||||
            <Form.Input
 | 
			
		||||
              label='额度'
 | 
			
		||||
              name='remain_quota'
 | 
			
		||||
              placeholder={'请输入额度'}
 | 
			
		||||
              onChange={handleInputChange}
 | 
			
		||||
              value={remain_quota}
 | 
			
		||||
              autoComplete='new-password'
 | 
			
		||||
              type='number'
 | 
			
		||||
              disabled={unlimited_quota}
 | 
			
		||||
            />
 | 
			
		||||
          </Form.Field>
 | 
			
		||||
          <Button type={'button'} style={{ marginBottom: '14px' }} onClick={() => {
 | 
			
		||||
            setUnlimitedQuota();
 | 
			
		||||
          }}>{unlimited_quota ? '取消无限额度' : '设置为无限额度'}</Button>
 | 
			
		||||
          <Form.Field>
 | 
			
		||||
            <Form.Input
 | 
			
		||||
              label='过期时间'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										94
									
								
								web/src/pages/TopUp/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								web/src/pages/TopUp/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { Button, Form, Grid, Header, Segment, Statistic } from 'semantic-ui-react';
 | 
			
		||||
import { API, showError, showSuccess } from '../../helpers';
 | 
			
		||||
 | 
			
		||||
const TopUp = () => {
 | 
			
		||||
  const [redemptionCode, setRedemptionCode] = useState('');
 | 
			
		||||
  const [topUpLink, setTopUpLink] = useState('');
 | 
			
		||||
  const [userQuota, setUserQuota] = useState(0);
 | 
			
		||||
 | 
			
		||||
  const topUp = async () => {
 | 
			
		||||
    if (redemptionCode === '') {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const res = await API.post('/api/user/topup', {
 | 
			
		||||
      key: redemptionCode
 | 
			
		||||
    });
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      showSuccess('充值成功!');
 | 
			
		||||
      setUserQuota((quota) => {
 | 
			
		||||
        return quota + data;
 | 
			
		||||
      });
 | 
			
		||||
      setRedemptionCode('');
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const openTopUpLink = () => {
 | 
			
		||||
    if (!topUpLink) {
 | 
			
		||||
      showError('超级管理员未设置充值链接!');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    window.open(topUpLink, '_blank');
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getUserQuota = async ()=>{
 | 
			
		||||
    let res  = await API.get(`/api/user/self`);
 | 
			
		||||
    const {success, message, data} = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      setUserQuota(data.quota);
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    let status = localStorage.getItem('status');
 | 
			
		||||
    if (status) {
 | 
			
		||||
      status = JSON.parse(status);
 | 
			
		||||
      if (status.top_up_link) {
 | 
			
		||||
        setTopUpLink(status.top_up_link);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    getUserQuota().then();
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Segment>
 | 
			
		||||
      <Header as='h3'>充值额度</Header>
 | 
			
		||||
      <Grid columns={2} stackable>
 | 
			
		||||
        <Grid.Column>
 | 
			
		||||
          <Form>
 | 
			
		||||
            <Form.Input
 | 
			
		||||
              placeholder='兑换码'
 | 
			
		||||
              name='redemptionCode'
 | 
			
		||||
              value={redemptionCode}
 | 
			
		||||
              onChange={(e) => {
 | 
			
		||||
                setRedemptionCode(e.target.value);
 | 
			
		||||
              }}
 | 
			
		||||
            />
 | 
			
		||||
            <Button color='green' onClick={openTopUpLink}>
 | 
			
		||||
              获取兑换码
 | 
			
		||||
            </Button>
 | 
			
		||||
            <Button color='yellow' onClick={topUp}>
 | 
			
		||||
              充值
 | 
			
		||||
            </Button>
 | 
			
		||||
          </Form>
 | 
			
		||||
        </Grid.Column>
 | 
			
		||||
        <Grid.Column>
 | 
			
		||||
          <Statistic.Group widths='one'>
 | 
			
		||||
            <Statistic>
 | 
			
		||||
              <Statistic.Value>{userQuota}</Statistic.Value>
 | 
			
		||||
              <Statistic.Label>剩余额度</Statistic.Label>
 | 
			
		||||
            </Statistic>
 | 
			
		||||
          </Statistic.Group>
 | 
			
		||||
        </Grid.Column>
 | 
			
		||||
      </Grid>
 | 
			
		||||
    </Segment>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default TopUp;
 | 
			
		||||
		Reference in New Issue
	
	Block a user