mirror of
				https://github.com/songquanpeng/one-api.git
				synced 2025-11-04 15:53:42 +08:00 
			
		
		
		
	Token API done without verification
This commit is contained in:
		
							
								
								
									
										143
									
								
								controller/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								controller/token.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
				
			|||||||
 | 
					package controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"one-api/common"
 | 
				
			||||||
 | 
						"one-api/model"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetAllTokens(c *gin.Context) {
 | 
				
			||||||
 | 
						userId := c.GetInt("id")
 | 
				
			||||||
 | 
						p, _ := strconv.Atoi(c.Query("p"))
 | 
				
			||||||
 | 
						if p < 0 {
 | 
				
			||||||
 | 
							p = 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tokens, err := model.GetAllUserTokens(userId, p*common.ItemsPerPage, common.ItemsPerPage)
 | 
				
			||||||
 | 
						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":    tokens,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SearchTokens(c *gin.Context) {
 | 
				
			||||||
 | 
						userId := c.GetInt("id")
 | 
				
			||||||
 | 
						keyword := c.Query("keyword")
 | 
				
			||||||
 | 
						tokens, err := model.SearchUserTokens(userId, keyword)
 | 
				
			||||||
 | 
						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":    tokens,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetToken(c *gin.Context) {
 | 
				
			||||||
 | 
						id, err := strconv.Atoi(c.Param("id"))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						token, err := model.GetTokenById(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":    token,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AddToken(c *gin.Context) {
 | 
				
			||||||
 | 
						token := model.Token{}
 | 
				
			||||||
 | 
						err := c.ShouldBindJSON(&token)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = token.Insert()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
							"success": true,
 | 
				
			||||||
 | 
							"message": "",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DeleteToken(c *gin.Context) {
 | 
				
			||||||
 | 
						id, _ := strconv.Atoi(c.Param("id"))
 | 
				
			||||||
 | 
						token := model.Token{Id: id}
 | 
				
			||||||
 | 
						err := token.Delete()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
							"success": true,
 | 
				
			||||||
 | 
							"message": "",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func UpdateToken(c *gin.Context) {
 | 
				
			||||||
 | 
						token := model.Token{}
 | 
				
			||||||
 | 
						err := c.ShouldBindJSON(&token)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = token.Update()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
								"success": false,
 | 
				
			||||||
 | 
								"message": err.Error(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						c.JSON(http.StatusOK, gin.H{
 | 
				
			||||||
 | 
							"success": true,
 | 
				
			||||||
 | 
							"message": "",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,12 +4,10 @@ import (
 | 
				
			|||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"github.com/gin-contrib/sessions"
 | 
						"github.com/gin-contrib/sessions"
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
	"github.com/google/uuid"
 | 
					 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"one-api/common"
 | 
						"one-api/common"
 | 
				
			||||||
	"one-api/model"
 | 
						"one-api/model"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type LoginRequest struct {
 | 
					type LoginRequest struct {
 | 
				
			||||||
@@ -245,43 +243,6 @@ func GetUser(c *gin.Context) {
 | 
				
			|||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GenerateToken(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
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	user.Token = uuid.New().String()
 | 
					 | 
				
			||||||
	user.Token = strings.Replace(user.Token, "-", "", -1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if model.DB.Where("token = ?", user.Token).First(user).RowsAffected != 0 {
 | 
					 | 
				
			||||||
		c.JSON(http.StatusOK, gin.H{
 | 
					 | 
				
			||||||
			"success": false,
 | 
					 | 
				
			||||||
			"message": "请重试,系统生成的 UUID 竟然重复了!",
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := user.Update(false); err != nil {
 | 
					 | 
				
			||||||
		c.JSON(http.StatusOK, gin.H{
 | 
					 | 
				
			||||||
			"success": false,
 | 
					 | 
				
			||||||
			"message": err.Error(),
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	c.JSON(http.StatusOK, gin.H{
 | 
					 | 
				
			||||||
		"success": true,
 | 
					 | 
				
			||||||
		"message": "",
 | 
					 | 
				
			||||||
		"data":    user.Token,
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func GetSelf(c *gin.Context) {
 | 
					func GetSelf(c *gin.Context) {
 | 
				
			||||||
	id := c.GetInt("id")
 | 
						id := c.GetInt("id")
 | 
				
			||||||
	user, err := model.GetUserById(id, false)
 | 
						user, err := model.GetUserById(id, false)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,28 +5,32 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Channel struct {
 | 
					type Channel struct {
 | 
				
			||||||
	Id     int    `json:"id"`
 | 
						Id           int    `json:"id"`
 | 
				
			||||||
	Type   int    `json:"type" gorm:"default:0"`
 | 
						Type         int    `json:"type" gorm:"default:0"`
 | 
				
			||||||
	Key    string `json:"key"`
 | 
						Key          string `json:"key"`
 | 
				
			||||||
	Status int    `json:"status" gorm:"default:1"`
 | 
						Status       int    `json:"status" gorm:"default:1"`
 | 
				
			||||||
 | 
						Name         string `json:"name" gorm:"unique;index"`
 | 
				
			||||||
 | 
						Weight       int    `json:"weight"`
 | 
				
			||||||
 | 
						CreatedTime  int64  `json:"created_time" gorm:"bigint"`
 | 
				
			||||||
 | 
						AccessedTime int64  `json:"accessed_time" gorm:"bigint"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetAllChannels(startIdx int, num int) ([]*Channel, error) {
 | 
					func GetAllChannels(startIdx int, num int) ([]*Channel, error) {
 | 
				
			||||||
	var channels []*Channel
 | 
						var channels []*Channel
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
	err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&channels).Error
 | 
						err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("key").Find(&channels).Error
 | 
				
			||||||
	return channels, err
 | 
						return channels, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func SearchChannels(keyword string) (channels []*Channel, err error) {
 | 
					func SearchChannels(keyword string) (channels []*Channel, err error) {
 | 
				
			||||||
	err = DB.Select([]string{"id", "key"}, keyword, keyword).Find(&channels).Error
 | 
						err = DB.Omit("key").Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&channels).Error
 | 
				
			||||||
	return channels, err
 | 
						return channels, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetChannelById(id int) (*Channel, error) {
 | 
					func GetChannelById(id int) (*Channel, error) {
 | 
				
			||||||
	channel := Channel{Id: id}
 | 
						channel := Channel{Id: id}
 | 
				
			||||||
	var err error = nil
 | 
						var err error = nil
 | 
				
			||||||
	err = DB.Select([]string{"id", "type"}).First(&channel, "id = ?", id).Error
 | 
						err = DB.Omit("key").First(&channel, "id = ?", id).Error
 | 
				
			||||||
	return &channel, err
 | 
						return &channel, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -42,7 +46,6 @@ func (channel *Channel) Update() error {
 | 
				
			|||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Delete Make sure link is valid! Because we will use os.Remove to delete it!
 | 
					 | 
				
			||||||
func (channel *Channel) Delete() error {
 | 
					func (channel *Channel) Delete() error {
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
	err = DB.Delete(channel).Error
 | 
						err = DB.Delete(channel).Error
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								model/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								model/token.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						_ "gorm.io/driver/sqlite"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Token struct {
 | 
				
			||||||
 | 
						Id           int    `json:"id"`
 | 
				
			||||||
 | 
						UserId       int    `json:"user_id"`
 | 
				
			||||||
 | 
						Key          string `json:"key"`
 | 
				
			||||||
 | 
						Status       int    `json:"status" gorm:"default:1"`
 | 
				
			||||||
 | 
						Name         string `json:"name" gorm:"unique;index"`
 | 
				
			||||||
 | 
						CreatedTime  int64  `json:"created_time" gorm:"bigint"`
 | 
				
			||||||
 | 
						AccessedTime int64  `json:"accessed_time" gorm:"bigint"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) {
 | 
				
			||||||
 | 
						var tokens []*Token
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						err = DB.Where("userId = ?", userId).Order("id desc").Limit(num).Offset(startIdx).Omit("key").Find(&tokens).Error
 | 
				
			||||||
 | 
						return tokens, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SearchUserTokens(userId int, keyword string) (tokens []*Token, err error) {
 | 
				
			||||||
 | 
						err = DB.Where("userId = ?", userId).Omit("key").Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&tokens).Error
 | 
				
			||||||
 | 
						return tokens, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetTokenById(id int) (*Token, error) {
 | 
				
			||||||
 | 
						token := Token{Id: id}
 | 
				
			||||||
 | 
						var err error = nil
 | 
				
			||||||
 | 
						err = DB.Omit("key").Select([]string{"id", "type"}).First(&token, "id = ?", id).Error
 | 
				
			||||||
 | 
						return &token, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (token *Token) Insert() error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						err = DB.Create(token).Error
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (token *Token) Update() error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						err = DB.Model(token).Updates(token).Error
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (token *Token) Delete() error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						err = DB.Delete(token).Error
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,11 +15,11 @@ type User struct {
 | 
				
			|||||||
	DisplayName      string `json:"display_name" gorm:"index" validate:"max=20"`
 | 
						DisplayName      string `json:"display_name" gorm:"index" validate:"max=20"`
 | 
				
			||||||
	Role             int    `json:"role" gorm:"type:int;default:1"`   // admin, common
 | 
						Role             int    `json:"role" gorm:"type:int;default:1"`   // admin, common
 | 
				
			||||||
	Status           int    `json:"status" gorm:"type:int;default:1"` // enabled, disabled
 | 
						Status           int    `json:"status" gorm:"type:int;default:1"` // enabled, disabled
 | 
				
			||||||
	Token            string `json:"token" gorm:"index"`
 | 
					 | 
				
			||||||
	Email            string `json:"email" gorm:"index" validate:"max=50"`
 | 
						Email            string `json:"email" gorm:"index" validate:"max=50"`
 | 
				
			||||||
	GitHubId         string `json:"github_id" gorm:"column:github_id;index"`
 | 
						GitHubId         string `json:"github_id" gorm:"column:github_id;index"`
 | 
				
			||||||
	WeChatId         string `json:"wechat_id" gorm:"column:wechat_id;index"`
 | 
						WeChatId         string `json:"wechat_id" gorm:"column:wechat_id;index"`
 | 
				
			||||||
	VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database!
 | 
						VerificationCode string `json:"verification_code" gorm:"-:all"` // this field is only for Email verification, don't save it to database!
 | 
				
			||||||
 | 
						Balance          int    `json:"balance" gorm:"type:int;default:0"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetMaxUserId() int {
 | 
					func GetMaxUserId() int {
 | 
				
			||||||
@@ -29,12 +29,12 @@ func GetMaxUserId() int {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetAllUsers(startIdx int, num int) (users []*User, err error) {
 | 
					func GetAllUsers(startIdx int, num int) (users []*User, err error) {
 | 
				
			||||||
	err = DB.Order("id desc").Limit(num).Offset(startIdx).Select([]string{"id", "username", "display_name", "role", "status", "email"}).Find(&users).Error
 | 
						err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("password").Find(&users).Error
 | 
				
			||||||
	return users, err
 | 
						return users, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func SearchUsers(keyword string) (users []*User, err error) {
 | 
					func SearchUsers(keyword string) (users []*User, err error) {
 | 
				
			||||||
	err = DB.Select([]string{"id", "username", "display_name", "role", "status", "email"}).Where("id = ? or username LIKE ? or email LIKE ? or display_name LIKE ?", keyword, keyword+"%", keyword+"%", keyword+"%").Find(&users).Error
 | 
						err = DB.Omit("password").Where("id = ? or username LIKE ? or email LIKE ? or display_name LIKE ?", keyword, keyword+"%", keyword+"%", keyword+"%").Find(&users).Error
 | 
				
			||||||
	return users, err
 | 
						return users, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,7 +47,7 @@ func GetUserById(id int, selectAll bool) (*User, error) {
 | 
				
			|||||||
	if selectAll {
 | 
						if selectAll {
 | 
				
			||||||
		err = DB.First(&user, "id = ?", id).Error
 | 
							err = DB.First(&user, "id = ?", id).Error
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		err = DB.Select([]string{"id", "username", "display_name", "role", "status", "email", "wechat_id", "github_id"}).First(&user, "id = ?", id).Error
 | 
							err = DB.Omit("password").First(&user, "id = ?", id).Error
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return &user, err
 | 
						return &user, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,6 @@ func SetApiRouter(router *gin.Engine) {
 | 
				
			|||||||
				selfRoute.GET("/self", controller.GetSelf)
 | 
									selfRoute.GET("/self", controller.GetSelf)
 | 
				
			||||||
				selfRoute.PUT("/self", controller.UpdateSelf)
 | 
									selfRoute.PUT("/self", controller.UpdateSelf)
 | 
				
			||||||
				selfRoute.DELETE("/self", controller.DeleteSelf)
 | 
									selfRoute.DELETE("/self", controller.DeleteSelf)
 | 
				
			||||||
				selfRoute.GET("/token", controller.GenerateToken)
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			adminRoute := userRoute.Group("/")
 | 
								adminRoute := userRoute.Group("/")
 | 
				
			||||||
@@ -64,5 +63,15 @@ func SetApiRouter(router *gin.Engine) {
 | 
				
			|||||||
			channelRoute.PUT("/", controller.UpdateChannel)
 | 
								channelRoute.PUT("/", controller.UpdateChannel)
 | 
				
			||||||
			channelRoute.DELETE("/:id", controller.DeleteChannel)
 | 
								channelRoute.DELETE("/:id", controller.DeleteChannel)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							tokenRoute := apiRouter.Group("/token")
 | 
				
			||||||
 | 
							tokenRoute.Use(middleware.UserAuth())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								tokenRoute.GET("/", controller.GetAllTokens)
 | 
				
			||||||
 | 
								tokenRoute.GET("/search", controller.SearchTokens)
 | 
				
			||||||
 | 
								tokenRoute.GET("/:id", controller.GetToken)
 | 
				
			||||||
 | 
								tokenRoute.POST("/", controller.AddToken)
 | 
				
			||||||
 | 
								tokenRoute.PUT("/", controller.UpdateToken)
 | 
				
			||||||
 | 
								tokenRoute.DELETE("/:id", controller.DeleteToken)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ import GitHubOAuth from './components/GitHubOAuth';
 | 
				
			|||||||
import PasswordResetConfirm from './components/PasswordResetConfirm';
 | 
					import PasswordResetConfirm from './components/PasswordResetConfirm';
 | 
				
			||||||
import { UserContext } from './context/User';
 | 
					import { UserContext } from './context/User';
 | 
				
			||||||
import Channel from './pages/Channel';
 | 
					import Channel from './pages/Channel';
 | 
				
			||||||
 | 
					import Token from './pages/Token';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Home = lazy(() => import('./pages/Home'));
 | 
					const Home = lazy(() => import('./pages/Home'));
 | 
				
			||||||
const About = lazy(() => import('./pages/About'));
 | 
					const About = lazy(() => import('./pages/About'));
 | 
				
			||||||
@@ -72,6 +73,12 @@ function App() {
 | 
				
			|||||||
          </PrivateRoute>
 | 
					          </PrivateRoute>
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					      <Route
 | 
				
			||||||
 | 
					        path='/token'
 | 
				
			||||||
 | 
					        element={
 | 
				
			||||||
 | 
					          <Token />
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
      <Route
 | 
					      <Route
 | 
				
			||||||
        path='/user'
 | 
					        path='/user'
 | 
				
			||||||
        element={
 | 
					        element={
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,11 @@ const headerButtons = [
 | 
				
			|||||||
    icon: 'sitemap',
 | 
					    icon: 'sitemap',
 | 
				
			||||||
    admin: true,
 | 
					    admin: true,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: '令牌',
 | 
				
			||||||
 | 
					    to: '/token',
 | 
				
			||||||
 | 
					    icon: 'key',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    name: '用户',
 | 
					    name: '用户',
 | 
				
			||||||
    to: '/user',
 | 
					    to: '/user',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,17 +34,6 @@ const PersonalSetting = () => {
 | 
				
			|||||||
    setInputs((inputs) => ({ ...inputs, [name]: value }));
 | 
					    setInputs((inputs) => ({ ...inputs, [name]: value }));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const generateToken = async () => {
 | 
					 | 
				
			||||||
    const res = await API.get('/api/user/token');
 | 
					 | 
				
			||||||
    const { success, message, data } = res.data;
 | 
					 | 
				
			||||||
    if (success) {
 | 
					 | 
				
			||||||
      await copy(data);
 | 
					 | 
				
			||||||
      showSuccess(`令牌已重置并已复制到剪贴板:${data}`);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      showError(message);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const bindWeChat = async () => {
 | 
					  const bindWeChat = async () => {
 | 
				
			||||||
    if (inputs.wechat_verification_code === '') return;
 | 
					    if (inputs.wechat_verification_code === '') return;
 | 
				
			||||||
    const res = await API.get(
 | 
					    const res = await API.get(
 | 
				
			||||||
@@ -106,7 +95,6 @@ const PersonalSetting = () => {
 | 
				
			|||||||
      <Button as={Link} to={`/user/edit/`}>
 | 
					      <Button as={Link} to={`/user/edit/`}>
 | 
				
			||||||
        更新个人信息
 | 
					        更新个人信息
 | 
				
			||||||
      </Button>
 | 
					      </Button>
 | 
				
			||||||
      <Button onClick={generateToken}>生成访问令牌</Button>
 | 
					 | 
				
			||||||
      <Divider />
 | 
					      <Divider />
 | 
				
			||||||
      <Header as='h3'>账号绑定</Header>
 | 
					      <Header as='h3'>账号绑定</Header>
 | 
				
			||||||
      <Button
 | 
					      <Button
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										300
									
								
								web/src/components/TokensTable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								web/src/components/TokensTable.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,300 @@
 | 
				
			|||||||
 | 
					import React, { useEffect, useState } from 'react';
 | 
				
			||||||
 | 
					import { Button, Form, Label, Pagination, Table } from 'semantic-ui-react';
 | 
				
			||||||
 | 
					import { Link } from 'react-router-dom';
 | 
				
			||||||
 | 
					import { API, showError, showSuccess } from '../helpers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { ITEMS_PER_PAGE } from '../constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function renderRole(role) {
 | 
				
			||||||
 | 
					  switch (role) {
 | 
				
			||||||
 | 
					    case 1:
 | 
				
			||||||
 | 
					      return <Label>普通用户</Label>;
 | 
				
			||||||
 | 
					    case 10:
 | 
				
			||||||
 | 
					      return <Label color='yellow'>管理员</Label>;
 | 
				
			||||||
 | 
					    case 100:
 | 
				
			||||||
 | 
					      return <Label color='orange'>超级管理员</Label>;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      return <Label color='red'>未知身份</Label>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TokensTable = () => {
 | 
				
			||||||
 | 
					  const [users, setUsers] = useState([]);
 | 
				
			||||||
 | 
					  const [loading, setLoading] = useState(true);
 | 
				
			||||||
 | 
					  const [activePage, setActivePage] = useState(1);
 | 
				
			||||||
 | 
					  const [searchKeyword, setSearchKeyword] = useState('');
 | 
				
			||||||
 | 
					  const [searching, setSearching] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const loadUsers = async (startIdx) => {
 | 
				
			||||||
 | 
					    const res = await API.get(`/api/user/?p=${startIdx}`);
 | 
				
			||||||
 | 
					    const { success, message, data } = res.data;
 | 
				
			||||||
 | 
					    if (success) {
 | 
				
			||||||
 | 
					      if (startIdx === 0) {
 | 
				
			||||||
 | 
					        setUsers(data);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        let newUsers = users;
 | 
				
			||||||
 | 
					        newUsers.push(...data);
 | 
				
			||||||
 | 
					        setUsers(newUsers);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      showError(message);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setLoading(false);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onPaginationChange = (e, { activePage }) => {
 | 
				
			||||||
 | 
					    (async () => {
 | 
				
			||||||
 | 
					      if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
 | 
				
			||||||
 | 
					        // In this case we have to load more data and then append them.
 | 
				
			||||||
 | 
					        await loadUsers(activePage - 1);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      setActivePage(activePage);
 | 
				
			||||||
 | 
					    })();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    loadUsers(0)
 | 
				
			||||||
 | 
					      .then()
 | 
				
			||||||
 | 
					      .catch((reason) => {
 | 
				
			||||||
 | 
					        showError(reason);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const manageUser = (username, action, idx) => {
 | 
				
			||||||
 | 
					    (async () => {
 | 
				
			||||||
 | 
					      const res = await API.post('/api/user/manage', {
 | 
				
			||||||
 | 
					        username,
 | 
				
			||||||
 | 
					        action,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      const { success, message } = res.data;
 | 
				
			||||||
 | 
					      if (success) {
 | 
				
			||||||
 | 
					        showSuccess('操作成功完成!');
 | 
				
			||||||
 | 
					        let user = res.data.data;
 | 
				
			||||||
 | 
					        let newUsers = [...users];
 | 
				
			||||||
 | 
					        let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx;
 | 
				
			||||||
 | 
					        if (action === 'delete') {
 | 
				
			||||||
 | 
					          newUsers[realIdx].deleted = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          newUsers[realIdx].status = user.status;
 | 
				
			||||||
 | 
					          newUsers[realIdx].role = user.role;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        setUsers(newUsers);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        showError(message);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const renderStatus = (status) => {
 | 
				
			||||||
 | 
					    switch (status) {
 | 
				
			||||||
 | 
					      case 1:
 | 
				
			||||||
 | 
					        return <Label basic>已激活</Label>;
 | 
				
			||||||
 | 
					      case 2:
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <Label basic color='red'>
 | 
				
			||||||
 | 
					            已封禁
 | 
				
			||||||
 | 
					          </Label>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					          <Label basic color='grey'>
 | 
				
			||||||
 | 
					            未知状态
 | 
				
			||||||
 | 
					          </Label>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const searchUsers = async () => {
 | 
				
			||||||
 | 
					    if (searchKeyword === '') {
 | 
				
			||||||
 | 
					      // if keyword is blank, load files instead.
 | 
				
			||||||
 | 
					      await loadUsers(0);
 | 
				
			||||||
 | 
					      setActivePage(1);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setSearching(true);
 | 
				
			||||||
 | 
					    const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
 | 
				
			||||||
 | 
					    const { success, message, data } = res.data;
 | 
				
			||||||
 | 
					    if (success) {
 | 
				
			||||||
 | 
					      setUsers(data);
 | 
				
			||||||
 | 
					      setActivePage(1);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      showError(message);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setSearching(false);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleKeywordChange = async (e, { value }) => {
 | 
				
			||||||
 | 
					    setSearchKeyword(value.trim());
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const sortUser = (key) => {
 | 
				
			||||||
 | 
					    if (users.length === 0) return;
 | 
				
			||||||
 | 
					    setLoading(true);
 | 
				
			||||||
 | 
					    let sortedUsers = [...users];
 | 
				
			||||||
 | 
					    sortedUsers.sort((a, b) => {
 | 
				
			||||||
 | 
					      return ('' + a[key]).localeCompare(b[key]);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (sortedUsers[0].id === users[0].id) {
 | 
				
			||||||
 | 
					      sortedUsers.reverse();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setUsers(sortedUsers);
 | 
				
			||||||
 | 
					    setLoading(false);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Form onSubmit={searchUsers}>
 | 
				
			||||||
 | 
					        <Form.Input
 | 
				
			||||||
 | 
					          icon='search'
 | 
				
			||||||
 | 
					          fluid
 | 
				
			||||||
 | 
					          iconPosition='left'
 | 
				
			||||||
 | 
					          placeholder='搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...'
 | 
				
			||||||
 | 
					          value={searchKeyword}
 | 
				
			||||||
 | 
					          loading={searching}
 | 
				
			||||||
 | 
					          onChange={handleKeywordChange}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </Form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <Table basic>
 | 
				
			||||||
 | 
					        <Table.Header>
 | 
				
			||||||
 | 
					          <Table.Row>
 | 
				
			||||||
 | 
					            <Table.HeaderCell
 | 
				
			||||||
 | 
					              style={{ cursor: 'pointer' }}
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                sortUser('username');
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              用户名
 | 
				
			||||||
 | 
					            </Table.HeaderCell>
 | 
				
			||||||
 | 
					            <Table.HeaderCell
 | 
				
			||||||
 | 
					              style={{ cursor: 'pointer' }}
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                sortUser('display_name');
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              显示名称
 | 
				
			||||||
 | 
					            </Table.HeaderCell>
 | 
				
			||||||
 | 
					            <Table.HeaderCell
 | 
				
			||||||
 | 
					              style={{ cursor: 'pointer' }}
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                sortUser('email');
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              邮箱地址
 | 
				
			||||||
 | 
					            </Table.HeaderCell>
 | 
				
			||||||
 | 
					            <Table.HeaderCell
 | 
				
			||||||
 | 
					              style={{ cursor: 'pointer' }}
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                sortUser('role');
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              用户角色
 | 
				
			||||||
 | 
					            </Table.HeaderCell>
 | 
				
			||||||
 | 
					            <Table.HeaderCell
 | 
				
			||||||
 | 
					              style={{ cursor: 'pointer' }}
 | 
				
			||||||
 | 
					              onClick={() => {
 | 
				
			||||||
 | 
					                sortUser('status');
 | 
				
			||||||
 | 
					              }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              状态
 | 
				
			||||||
 | 
					            </Table.HeaderCell>
 | 
				
			||||||
 | 
					            <Table.HeaderCell>操作</Table.HeaderCell>
 | 
				
			||||||
 | 
					          </Table.Row>
 | 
				
			||||||
 | 
					        </Table.Header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Table.Body>
 | 
				
			||||||
 | 
					          {users
 | 
				
			||||||
 | 
					            .slice(
 | 
				
			||||||
 | 
					              (activePage - 1) * ITEMS_PER_PAGE,
 | 
				
			||||||
 | 
					              activePage * ITEMS_PER_PAGE
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .map((user, idx) => {
 | 
				
			||||||
 | 
					              if (user.deleted) return <></>;
 | 
				
			||||||
 | 
					              return (
 | 
				
			||||||
 | 
					                <Table.Row key={user.id}>
 | 
				
			||||||
 | 
					                  <Table.Cell>{user.username}</Table.Cell>
 | 
				
			||||||
 | 
					                  <Table.Cell>{user.display_name}</Table.Cell>
 | 
				
			||||||
 | 
					                  <Table.Cell>{user.email ? user.email : '无'}</Table.Cell>
 | 
				
			||||||
 | 
					                  <Table.Cell>{renderRole(user.role)}</Table.Cell>
 | 
				
			||||||
 | 
					                  <Table.Cell>{renderStatus(user.status)}</Table.Cell>
 | 
				
			||||||
 | 
					                  <Table.Cell>
 | 
				
			||||||
 | 
					                    <div>
 | 
				
			||||||
 | 
					                      <Button
 | 
				
			||||||
 | 
					                        size={'small'}
 | 
				
			||||||
 | 
					                        positive
 | 
				
			||||||
 | 
					                        onClick={() => {
 | 
				
			||||||
 | 
					                          manageUser(user.username, 'promote', idx);
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        提升
 | 
				
			||||||
 | 
					                      </Button>
 | 
				
			||||||
 | 
					                      <Button
 | 
				
			||||||
 | 
					                        size={'small'}
 | 
				
			||||||
 | 
					                        color={'yellow'}
 | 
				
			||||||
 | 
					                        onClick={() => {
 | 
				
			||||||
 | 
					                          manageUser(user.username, 'demote', idx);
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        降级
 | 
				
			||||||
 | 
					                      </Button>
 | 
				
			||||||
 | 
					                      <Button
 | 
				
			||||||
 | 
					                        size={'small'}
 | 
				
			||||||
 | 
					                        negative
 | 
				
			||||||
 | 
					                        onClick={() => {
 | 
				
			||||||
 | 
					                          manageUser(user.username, 'delete', idx);
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        删除
 | 
				
			||||||
 | 
					                      </Button>
 | 
				
			||||||
 | 
					                      <Button
 | 
				
			||||||
 | 
					                        size={'small'}
 | 
				
			||||||
 | 
					                        onClick={() => {
 | 
				
			||||||
 | 
					                          manageUser(
 | 
				
			||||||
 | 
					                            user.username,
 | 
				
			||||||
 | 
					                            user.status === 1 ? 'disable' : 'enable',
 | 
				
			||||||
 | 
					                            idx
 | 
				
			||||||
 | 
					                          );
 | 
				
			||||||
 | 
					                        }}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        {user.status === 1 ? '禁用' : '启用'}
 | 
				
			||||||
 | 
					                      </Button>
 | 
				
			||||||
 | 
					                      <Button
 | 
				
			||||||
 | 
					                        size={'small'}
 | 
				
			||||||
 | 
					                        as={Link}
 | 
				
			||||||
 | 
					                        to={'/user/edit/' + user.id}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        编辑
 | 
				
			||||||
 | 
					                      </Button>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  </Table.Cell>
 | 
				
			||||||
 | 
					                </Table.Row>
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            })}
 | 
				
			||||||
 | 
					        </Table.Body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Table.Footer>
 | 
				
			||||||
 | 
					          <Table.Row>
 | 
				
			||||||
 | 
					            <Table.HeaderCell colSpan='6'>
 | 
				
			||||||
 | 
					              <Button size='small' as={Link} to='/user/add' loading={loading}>
 | 
				
			||||||
 | 
					                添加新的用户
 | 
				
			||||||
 | 
					              </Button>
 | 
				
			||||||
 | 
					              <Pagination
 | 
				
			||||||
 | 
					                floated='right'
 | 
				
			||||||
 | 
					                activePage={activePage}
 | 
				
			||||||
 | 
					                onPageChange={onPaginationChange}
 | 
				
			||||||
 | 
					                size='small'
 | 
				
			||||||
 | 
					                siblingRange={1}
 | 
				
			||||||
 | 
					                totalPages={
 | 
				
			||||||
 | 
					                  Math.ceil(users.length / ITEMS_PER_PAGE) +
 | 
				
			||||||
 | 
					                  (users.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </Table.HeaderCell>
 | 
				
			||||||
 | 
					          </Table.Row>
 | 
				
			||||||
 | 
					        </Table.Footer>
 | 
				
			||||||
 | 
					      </Table>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default TokensTable;
 | 
				
			||||||
							
								
								
									
										14
									
								
								web/src/pages/Token/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								web/src/pages/Token/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import { Segment, Header } from 'semantic-ui-react';
 | 
				
			||||||
 | 
					import TokensTable from '../../components/TokensTable';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Token = () => (
 | 
				
			||||||
 | 
					  <>
 | 
				
			||||||
 | 
					    <Segment>
 | 
				
			||||||
 | 
					      <Header as='h3'>我的令牌</Header>
 | 
				
			||||||
 | 
					      <TokensTable/>
 | 
				
			||||||
 | 
					    </Segment>
 | 
				
			||||||
 | 
					  </>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Token;
 | 
				
			||||||
		Reference in New Issue
	
	Block a user