From 57cd68e7e30a475c0cad2cbec65ce24728db3f68 Mon Sep 17 00:00:00 2001
From: Buer <42402987+MartialBE@users.noreply.github.com>
Date: Thu, 8 Feb 2024 03:41:51 +0800
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20support=20paging=20/=20sort?=
=?UTF-8?q?ing=20(#64)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* ✨ feat: support paging / sorting
* 🔥 del: delete unused files
---
common/gin.go | 7 +
controller/channel-billing.go | 2 +-
controller/channel-test.go | 2 +-
controller/channel.go | 30 +--
controller/log.go | 49 ++--
controller/redemption.go | 39 +--
controller/token.go | 31 +--
controller/user.go | 30 +--
model/channel.go | 40 +--
model/common.go | 83 +++++++
model/log.go | 105 ++++----
model/redemption.go | 23 +-
model/token.go | 25 +-
model/user.go | 19 +-
router/api-router.go | 20 +-
web/src/ui-component/TableHead.js | 47 ++++
web/src/ui-component/TableToolBar.js | 5 +-
web/src/views/Channel/component/TableHead.js | 21 --
web/src/views/Channel/index.js | 156 ++++++------
web/src/views/Log/component/TableHead.js | 28 ---
web/src/views/Log/index.js | 229 ++++++++++++------
.../views/Redemption/component/TableHead.js | 19 --
web/src/views/Redemption/index.js | 145 +++++------
web/src/views/Token/component/TableHead.js | 19 --
web/src/views/Token/index.js | 140 ++++++-----
web/src/views/User/component/TableHead.js | 20 --
web/src/views/User/component/TableRow.js | 4 +-
web/src/views/User/index.js | 149 ++++++------
28 files changed, 796 insertions(+), 691 deletions(-)
create mode 100644 web/src/ui-component/TableHead.js
delete mode 100644 web/src/views/Channel/component/TableHead.js
delete mode 100644 web/src/views/Log/component/TableHead.js
delete mode 100644 web/src/views/Redemption/component/TableHead.js
delete mode 100644 web/src/views/Token/component/TableHead.js
delete mode 100644 web/src/views/User/component/TableHead.js
diff --git a/common/gin.go b/common/gin.go
index 7e865f67..56f3a341 100644
--- a/common/gin.go
+++ b/common/gin.go
@@ -67,3 +67,10 @@ func AbortWithMessage(c *gin.Context, statusCode int, message string) {
c.Abort()
LogError(c.Request.Context(), message)
}
+
+func APIRespondWithError(c *gin.Context, status int, err error) {
+ c.JSON(status, gin.H{
+ "success": false,
+ "message": err.Error(),
+ })
+}
diff --git a/controller/channel-billing.go b/controller/channel-billing.go
index f6bb523b..83bfce88 100644
--- a/controller/channel-billing.go
+++ b/controller/channel-billing.go
@@ -104,7 +104,7 @@ func UpdateChannelBalance(c *gin.Context) {
}
func updateAllChannelsBalance() error {
- channels, err := model.GetAllChannels(0, 0, true)
+ channels, err := model.GetAllChannels()
if err != nil {
return err
}
diff --git a/controller/channel-test.go b/controller/channel-test.go
index 3f675184..37d544c3 100644
--- a/controller/channel-test.go
+++ b/controller/channel-test.go
@@ -167,7 +167,7 @@ func testAllChannels(notify bool) error {
}
testAllChannelsRunning = true
testAllChannelsLock.Unlock()
- channels, err := model.GetAllChannels(0, 0, true)
+ channels, err := model.GetAllChannels()
if err != nil {
return err
}
diff --git a/controller/channel.go b/controller/channel.go
index 7b2a45f7..c05909eb 100644
--- a/controller/channel.go
+++ b/controller/channel.go
@@ -10,34 +10,16 @@ import (
"github.com/gin-gonic/gin"
)
-func GetAllChannels(c *gin.Context) {
- p, _ := strconv.Atoi(c.Query("p"))
- if p < 0 {
- p = 0
- }
- channels, err := model.GetAllChannels(p*common.ItemsPerPage, common.ItemsPerPage, false)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
+func GetChannelsList(c *gin.Context) {
+ var params model.GenericParams
+ if err := c.ShouldBindQuery(¶ms); err != nil {
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": channels,
- })
-}
-func SearchChannels(c *gin.Context) {
- keyword := c.Query("keyword")
- channels, err := model.SearchChannels(keyword)
+ channels, err := model.GetChannelsList(¶ms)
if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
c.JSON(http.StatusOK, gin.H{
diff --git a/controller/log.go b/controller/log.go
index 6327a6a8..bc30fea7 100644
--- a/controller/log.go
+++ b/controller/log.go
@@ -9,24 +9,16 @@ import (
"github.com/gin-gonic/gin"
)
-func GetAllLogs(c *gin.Context) {
- p, _ := strconv.Atoi(c.Query("p"))
- if p < 0 {
- p = 0
+func GetLogsList(c *gin.Context) {
+ var params model.LogsListParams
+ if err := c.ShouldBindQuery(¶ms); err != nil {
+ common.APIRespondWithError(c, http.StatusOK, err)
+ return
}
- logType, _ := strconv.Atoi(c.Query("type"))
- startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
- endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
- username := c.Query("username")
- tokenName := c.Query("token_name")
- modelName := c.Query("model_name")
- channel, _ := strconv.Atoi(c.Query("channel"))
- logs, err := model.GetAllLogs(logType, startTimestamp, endTimestamp, modelName, username, tokenName, p*common.ItemsPerPage, common.ItemsPerPage, channel)
+
+ logs, err := model.GetLogsList(¶ms)
if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
c.JSON(http.StatusOK, gin.H{
@@ -36,23 +28,18 @@ func GetAllLogs(c *gin.Context) {
})
}
-func GetUserLogs(c *gin.Context) {
- p, _ := strconv.Atoi(c.Query("p"))
- if p < 0 {
- p = 0
- }
+func GetUserLogsList(c *gin.Context) {
userId := c.GetInt("id")
- logType, _ := strconv.Atoi(c.Query("type"))
- startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
- endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
- tokenName := c.Query("token_name")
- modelName := c.Query("model_name")
- logs, err := model.GetUserLogs(userId, logType, startTimestamp, endTimestamp, modelName, tokenName, p*common.ItemsPerPage, common.ItemsPerPage)
+
+ var params model.LogsListParams
+ if err := c.ShouldBindQuery(¶ms); err != nil {
+ common.APIRespondWithError(c, http.StatusOK, err)
+ return
+ }
+
+ logs, err := model.GetUserLogsList(userId, ¶ms)
if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
c.JSON(http.StatusOK, gin.H{
diff --git a/controller/redemption.go b/controller/redemption.go
index 0f656be0..ee111f88 100644
--- a/controller/redemption.go
+++ b/controller/redemption.go
@@ -1,42 +1,24 @@
package controller
import (
- "github.com/gin-gonic/gin"
"net/http"
"one-api/common"
"one-api/model"
"strconv"
+
+ "github.com/gin-gonic/gin"
)
-func GetAllRedemptions(c *gin.Context) {
- p, _ := strconv.Atoi(c.Query("p"))
- if p < 0 {
- p = 0
- }
- redemptions, err := model.GetAllRedemptions(p*common.ItemsPerPage, common.ItemsPerPage)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
+func GetRedemptionsList(c *gin.Context) {
+ var params model.GenericParams
+ if err := c.ShouldBindQuery(¶ms); err != nil {
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": redemptions,
- })
- return
-}
-func SearchRedemptions(c *gin.Context) {
- keyword := c.Query("keyword")
- redemptions, err := model.SearchRedemptions(keyword)
+ redemptions, err := model.GetRedemptionsList(¶ms)
if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
c.JSON(http.StatusOK, gin.H{
@@ -44,7 +26,6 @@ func SearchRedemptions(c *gin.Context) {
"message": "",
"data": redemptions,
})
- return
}
func GetRedemption(c *gin.Context) {
@@ -69,7 +50,6 @@ func GetRedemption(c *gin.Context) {
"message": "",
"data": redemption,
})
- return
}
func AddRedemption(c *gin.Context) {
@@ -129,7 +109,6 @@ func AddRedemption(c *gin.Context) {
"message": "",
"data": keys,
})
- return
}
func DeleteRedemption(c *gin.Context) {
@@ -146,7 +125,6 @@ func DeleteRedemption(c *gin.Context) {
"success": true,
"message": "",
})
- return
}
func UpdateRedemption(c *gin.Context) {
@@ -188,5 +166,4 @@ func UpdateRedemption(c *gin.Context) {
"message": "",
"data": cleanRedemption,
})
- return
}
diff --git a/controller/token.go b/controller/token.go
index a4e3a235..608d630b 100644
--- a/controller/token.go
+++ b/controller/token.go
@@ -9,36 +9,17 @@ import (
"github.com/gin-gonic/gin"
)
-func GetAllTokens(c *gin.Context) {
+func GetUserTokensList(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(),
- })
+ var params model.GenericParams
+ if err := c.ShouldBindQuery(¶ms); err != nil {
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": tokens,
- })
-}
-func SearchTokens(c *gin.Context) {
- userId := c.GetInt("id")
- keyword := c.Query("keyword")
- tokens, err := model.SearchUserTokens(userId, keyword)
+ tokens, err := model.GetUserTokensList(userId, ¶ms)
if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
c.JSON(http.StatusOK, gin.H{
diff --git a/controller/user.go b/controller/user.go
index e854093f..42faad3e 100644
--- a/controller/user.go
+++ b/controller/user.go
@@ -176,34 +176,16 @@ func Register(c *gin.Context) {
})
}
-func GetAllUsers(c *gin.Context) {
- p, _ := strconv.Atoi(c.Query("p"))
- if p < 0 {
- p = 0
- }
- users, err := model.GetAllUsers(p*common.ItemsPerPage, common.ItemsPerPage)
- if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
+func GetUsersList(c *gin.Context) {
+ var params model.GenericParams
+ if err := c.ShouldBindQuery(¶ms); err != nil {
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
- c.JSON(http.StatusOK, gin.H{
- "success": true,
- "message": "",
- "data": users,
- })
-}
-func SearchUsers(c *gin.Context) {
- keyword := c.Query("keyword")
- users, err := model.SearchUsers(keyword)
+ users, err := model.GetUsersList(¶ms)
if err != nil {
- c.JSON(http.StatusOK, gin.H{
- "success": false,
- "message": err.Error(),
- })
+ common.APIRespondWithError(c, http.StatusOK, err)
return
}
c.JSON(http.StatusOK, gin.H{
diff --git a/model/channel.go b/model/channel.go
index 98e22c10..29c5b038 100644
--- a/model/channel.go
+++ b/model/channel.go
@@ -29,23 +29,35 @@ type Channel struct {
TestModel string `json:"test_model" gorm:"type:varchar(50);default:''"`
}
-func GetAllChannels(startIdx int, num int, selectAll bool) ([]*Channel, error) {
- var channels []*Channel
- var err error
- if selectAll {
- err = DB.Order("id desc").Find(&channels).Error
- } else {
- err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("key").Find(&channels).Error
- }
- return channels, err
+var allowedChannelOrderFields = map[string]bool{
+ "id": true,
+ "name": true,
+ "group": true,
+ "type": true,
+ "status": true,
+ "response_time": true,
+ "balance": true,
+ "priority": true,
}
-func SearchChannels(keyword string) (channels []*Channel, err error) {
- keyCol := "`key`"
- if common.UsingPostgreSQL {
- keyCol = `"key"`
+func GetChannelsList(params *GenericParams) (*DataResult, error) {
+ var channels []*Channel
+
+ db := DB.Omit("key")
+ if params.Keyword != "" {
+ keyCol := "`key`"
+ if common.UsingPostgreSQL {
+ keyCol = `"key"`
+ }
+ db = db.Where("id = ? or name LIKE ? or "+keyCol+" = ?", common.String2Int(params.Keyword), params.Keyword+"%", params.Keyword)
}
- err = DB.Omit("key").Where("id = ? or name LIKE ? or "+keyCol+" = ?", common.String2Int(keyword), keyword+"%", keyword).Find(&channels).Error
+
+ return PaginateAndOrder(db, ¶ms.PaginationParams, &channels, allowedChannelOrderFields)
+}
+
+func GetAllChannels() ([]*Channel, error) {
+ var channels []*Channel
+ err := DB.Order("id desc").Find(&channels).Error
return channels, err
}
diff --git a/model/common.go b/model/common.go
index 4257b097..37fcd739 100644
--- a/model/common.go
+++ b/model/common.go
@@ -3,8 +3,91 @@ package model
import (
"fmt"
"one-api/common"
+ "strings"
+
+ "gorm.io/gorm"
)
+type GenericParams struct {
+ PaginationParams
+ Keyword string `form:"keyword"`
+}
+
+type PaginationParams struct {
+ Page int `form:"page"`
+ Size int `form:"size"`
+ Order string `form:"order"`
+}
+
+type DataResult struct {
+ Data interface{} `json:"data"`
+ Page int `json:"page"`
+ Size int `json:"size"`
+ TotalCount int64 `json:"total_count"`
+}
+
+func PaginateAndOrder(db *gorm.DB, params *PaginationParams, result interface{}, allowedOrderFields map[string]bool) (*DataResult, error) {
+ // 获取总数
+ var totalCount int64
+ err := db.Model(result).Count(&totalCount).Error
+ if err != nil {
+ return nil, err
+ }
+
+ fmt.Println("totalCount", totalCount)
+
+ // 分页
+ if params.Page < 1 {
+ params.Page = 1
+ }
+ if params.Size < 1 {
+ params.Size = common.ItemsPerPage
+ }
+
+ if params.Size > common.MaxRecentItems {
+ return nil, fmt.Errorf("size 参数不能超过 %d", common.MaxRecentItems)
+ }
+
+ offset := (params.Page - 1) * params.Size
+ db = db.Offset(offset).Limit(params.Size)
+
+ // 排序
+ if params.Order != "" {
+ orderFields := strings.Split(params.Order, ",")
+ for _, field := range orderFields {
+ field = strings.TrimSpace(field)
+ desc := strings.HasPrefix(field, "-")
+ if desc {
+ field = field[1:]
+ }
+ if !allowedOrderFields[field] {
+ return nil, fmt.Errorf("不允许对字段 '%s' 进行排序", field)
+ }
+ if desc {
+ field = field + " DESC"
+ }
+ db = db.Order(field)
+ }
+ } else {
+ // 默认排序
+ db = db.Order("id DESC")
+ }
+
+ // 查询
+ err = db.Find(result).Error
+ if err != nil {
+ return nil, err
+ }
+
+ // 返回结果
+ return &DataResult{
+ Data: result,
+ Page: params.Page,
+ Size: params.Size,
+ TotalCount: totalCount,
+ }, nil
+}
+
func getDateFormat(groupType string) string {
var dateFormat string
if groupType == "day" {
diff --git a/model/log.go b/model/log.go
index ca5eb5e9..a4141952 100644
--- a/model/log.go
+++ b/model/log.go
@@ -74,56 +74,79 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke
}
}
-func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, startIdx int, num int, channel int) (logs []*Log, err error) {
- var tx *gorm.DB
- if logType == LogTypeUnknown {
- tx = DB
- } else {
- tx = DB.Where("type = ?", logType)
- }
- if modelName != "" {
- tx = tx.Where("model_name = ?", modelName)
- }
- if username != "" {
- tx = tx.Where("username = ?", username)
- }
- if tokenName != "" {
- tx = tx.Where("token_name = ?", tokenName)
- }
- if startTimestamp != 0 {
- tx = tx.Where("created_at >= ?", startTimestamp)
- }
- if endTimestamp != 0 {
- tx = tx.Where("created_at <= ?", endTimestamp)
- }
- if channel != 0 {
- tx = tx.Where("channel_id = ?", channel)
- }
- err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&logs).Error
- return logs, err
+type LogsListParams struct {
+ PaginationParams
+ LogType int `form:"log_type"`
+ StartTimestamp int64 `form:"start_timestamp"`
+ EndTimestamp int64 `form:"end_timestamp"`
+ ModelName string `form:"model_name"`
+ Username string `form:"username"`
+ TokenName string `form:"token_name"`
+ Channel int `form:"channel"`
}
-func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int) (logs []*Log, err error) {
+var allowedLogsOrderFields = map[string]bool{
+ "created_at": true,
+ "channel_id": true,
+ "user_id": true,
+ "token_name": true,
+ "model_name": true,
+ "type": true,
+}
+
+func GetLogsList(params *LogsListParams) (*DataResult, error) {
var tx *gorm.DB
- if logType == LogTypeUnknown {
- tx = DB.Where("user_id = ?", userId)
+ var logs []*Log
+
+ if params.LogType == LogTypeUnknown {
+ tx = DB
} else {
- tx = DB.Where("user_id = ? and type = ?", userId, logType)
+ tx = DB.Where("type = ?", params.LogType)
}
- if modelName != "" {
- tx = tx.Where("model_name = ?", modelName)
+ if params.ModelName != "" {
+ tx = tx.Where("model_name = ?", params.ModelName)
}
- if tokenName != "" {
- tx = tx.Where("token_name = ?", tokenName)
+ if params.Username != "" {
+ tx = tx.Where("username = ?", params.Username)
}
- if startTimestamp != 0 {
- tx = tx.Where("created_at >= ?", startTimestamp)
+ if params.TokenName != "" {
+ tx = tx.Where("token_name = ?", params.TokenName)
}
- if endTimestamp != 0 {
- tx = tx.Where("created_at <= ?", endTimestamp)
+ if params.StartTimestamp != 0 {
+ tx = tx.Where("created_at >= ?", params.StartTimestamp)
}
- err = tx.Order("id desc").Limit(num).Offset(startIdx).Omit("id").Find(&logs).Error
- return logs, err
+ if params.EndTimestamp != 0 {
+ tx = tx.Where("created_at <= ?", params.EndTimestamp)
+ }
+ if params.Channel != 0 {
+ tx = tx.Where("channel_id = ?", params.Channel)
+ }
+
+ return PaginateAndOrder(tx, ¶ms.PaginationParams, &logs, allowedLogsOrderFields)
+}
+
+func GetUserLogsList(userId int, params *LogsListParams) (*DataResult, error) {
+ var logs []*Log
+
+ tx := DB.Where("user_id = ?", userId).Omit("id")
+
+ if params.LogType != LogTypeUnknown {
+ tx = DB.Where("type = ?", params.LogType)
+ }
+ if params.ModelName != "" {
+ tx = tx.Where("model_name = ?", params.ModelName)
+ }
+ if params.TokenName != "" {
+ tx = tx.Where("token_name = ?", params.TokenName)
+ }
+ if params.StartTimestamp != 0 {
+ tx = tx.Where("created_at >= ?", params.StartTimestamp)
+ }
+ if params.EndTimestamp != 0 {
+ tx = tx.Where("created_at <= ?", params.EndTimestamp)
+ }
+
+ return PaginateAndOrder(tx, ¶ms.PaginationParams, &logs, allowedLogsOrderFields)
}
func SearchAllLogs(keyword string) (logs []*Log, err error) {
diff --git a/model/redemption.go b/model/redemption.go
index 6cf6be43..a5389102 100644
--- a/model/redemption.go
+++ b/model/redemption.go
@@ -20,16 +20,23 @@ type Redemption struct {
Count int `json:"count" gorm:"-:all"` // only for api request
}
-func GetAllRedemptions(startIdx int, num int) ([]*Redemption, error) {
- var redemptions []*Redemption
- var err error
- err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
- return redemptions, err
+var allowedRedemptionslOrderFields = map[string]bool{
+ "id": true,
+ "name": true,
+ "status": true,
+ "quota": true,
+ "created_time": true,
+ "redeemed_time": true,
}
-func SearchRedemptions(keyword string) (redemptions []*Redemption, err error) {
- err = DB.Where("id = ? or name LIKE ?", common.String2Int(keyword), keyword+"%").Find(&redemptions).Error
- return redemptions, err
+func GetRedemptionsList(params *GenericParams) (*DataResult, error) {
+ var redemptions []*Redemption
+ db := DB
+ if params.Keyword != "" {
+ db = db.Where("id = ? or name LIKE ?", common.String2Int(params.Keyword), params.Keyword+"%")
+ }
+
+ return PaginateAndOrder(db, ¶ms.PaginationParams, &redemptions, allowedRedemptionslOrderFields)
}
func GetRedemptionById(id int) (*Redemption, error) {
diff --git a/model/token.go b/model/token.go
index f1699f49..74eae34b 100644
--- a/model/token.go
+++ b/model/token.go
@@ -22,16 +22,25 @@ type Token struct {
UsedQuota int `json:"used_quota" gorm:"default:0"` // used quota
}
-func GetAllUserTokens(userId int, startIdx int, num int) ([]*Token, error) {
- var tokens []*Token
- var err error
- err = DB.Where("user_id = ?", userId).Order("id desc").Limit(num).Offset(startIdx).Find(&tokens).Error
- return tokens, err
+var allowedTokenOrderFields = map[string]bool{
+ "id": true,
+ "name": true,
+ "status": true,
+ "expired_time": true,
+ "created_time": true,
+ "remain_quota": true,
+ "used_quota": true,
}
-func SearchUserTokens(userId int, keyword string) (tokens []*Token, err error) {
- err = DB.Where("user_id = ?", userId).Where("name LIKE ?", keyword+"%").Find(&tokens).Error
- return tokens, err
+func GetUserTokensList(userId int, params *GenericParams) (*DataResult, error) {
+ var tokens []*Token
+ db := DB.Where("user_id = ?", userId)
+
+ if params.Keyword != "" {
+ db = db.Where("name LIKE ?", params.Keyword+"%")
+ }
+
+ return PaginateAndOrder(db, ¶ms.PaginationParams, &tokens, allowedTokenOrderFields)
}
func ValidateUserToken(key string) (token *Token, err error) {
diff --git a/model/user.go b/model/user.go
index d908ab93..50a6d5a8 100644
--- a/model/user.go
+++ b/model/user.go
@@ -38,15 +38,22 @@ func GetMaxUserId() int {
return user.Id
}
-func GetAllUsers(startIdx int, num int) (users []*User, err error) {
- err = DB.Order("id desc").Limit(num).Offset(startIdx).Omit("password").Find(&users).Error
- return users, err
+var allowedUserOrderFields = map[string]bool{
+ "id": true,
+ "username": true,
+ "role": true,
+ "status": true,
+ "created_time": true,
}
-func SearchUsers(keyword string) (users []*User, err error) {
- err = DB.Omit("password").Where("id = ? or username LIKE ? or email LIKE ? or display_name LIKE ?", common.String2Int(keyword), keyword+"%", keyword+"%", keyword+"%").Find(&users).Error
+func GetUsersList(params *GenericParams) (*DataResult, error) {
+ var users []*User
+ db := DB.Omit("password")
+ if params.Keyword != "" {
+ db = db.Where("id = ? or username LIKE ? or email LIKE ? or display_name LIKE ?", common.String2Int(params.Keyword), params.Keyword+"%", params.Keyword+"%", params.Keyword+"%")
+ }
- return users, err
+ return PaginateAndOrder(db, ¶ms.PaginationParams, &users, allowedUserOrderFields)
}
func GetUserById(id int, selectAll bool) (*User, error) {
diff --git a/router/api-router.go b/router/api-router.go
index 9a1f9b6c..f602a68a 100644
--- a/router/api-router.go
+++ b/router/api-router.go
@@ -48,8 +48,7 @@ func SetApiRouter(router *gin.Engine) {
adminRoute := userRoute.Group("/")
adminRoute.Use(middleware.AdminAuth())
{
- adminRoute.GET("/", controller.GetAllUsers)
- adminRoute.GET("/search", controller.SearchUsers)
+ adminRoute.GET("/", controller.GetUsersList)
adminRoute.GET("/:id", controller.GetUser)
adminRoute.POST("/", controller.CreateUser)
adminRoute.POST("/manage", controller.ManageUser)
@@ -66,8 +65,7 @@ func SetApiRouter(router *gin.Engine) {
channelRoute := apiRouter.Group("/channel")
channelRoute.Use(middleware.AdminAuth())
{
- channelRoute.GET("/", controller.GetAllChannels)
- channelRoute.GET("/search", controller.SearchChannels)
+ channelRoute.GET("/", controller.GetChannelsList)
channelRoute.GET("/models", controller.ListModelsForAdmin)
channelRoute.GET("/:id", controller.GetChannel)
channelRoute.GET("/test", controller.TestAllChannels)
@@ -82,8 +80,7 @@ func SetApiRouter(router *gin.Engine) {
tokenRoute := apiRouter.Group("/token")
tokenRoute.Use(middleware.UserAuth())
{
- tokenRoute.GET("/", controller.GetAllTokens)
- tokenRoute.GET("/search", controller.SearchTokens)
+ tokenRoute.GET("/", controller.GetUserTokensList)
tokenRoute.GET("/:id", controller.GetToken)
tokenRoute.POST("/", controller.AddToken)
tokenRoute.PUT("/", controller.UpdateToken)
@@ -92,21 +89,20 @@ func SetApiRouter(router *gin.Engine) {
redemptionRoute := apiRouter.Group("/redemption")
redemptionRoute.Use(middleware.AdminAuth())
{
- redemptionRoute.GET("/", controller.GetAllRedemptions)
- redemptionRoute.GET("/search", controller.SearchRedemptions)
+ redemptionRoute.GET("/", controller.GetRedemptionsList)
redemptionRoute.GET("/:id", controller.GetRedemption)
redemptionRoute.POST("/", controller.AddRedemption)
redemptionRoute.PUT("/", controller.UpdateRedemption)
redemptionRoute.DELETE("/:id", controller.DeleteRedemption)
}
logRoute := apiRouter.Group("/log")
- logRoute.GET("/", middleware.AdminAuth(), controller.GetAllLogs)
+ logRoute.GET("/", middleware.AdminAuth(), controller.GetLogsList)
logRoute.DELETE("/", middleware.AdminAuth(), controller.DeleteHistoryLogs)
logRoute.GET("/stat", middleware.AdminAuth(), controller.GetLogsStat)
logRoute.GET("/self/stat", middleware.UserAuth(), controller.GetLogsSelfStat)
- logRoute.GET("/search", middleware.AdminAuth(), controller.SearchAllLogs)
- logRoute.GET("/self", middleware.UserAuth(), controller.GetUserLogs)
- logRoute.GET("/self/search", middleware.UserAuth(), controller.SearchUserLogs)
+ // logRoute.GET("/search", middleware.AdminAuth(), controller.SearchAllLogs)
+ logRoute.GET("/self", middleware.UserAuth(), controller.GetUserLogsList)
+ // logRoute.GET("/self/search", middleware.UserAuth(), controller.SearchUserLogs)
groupRoute := apiRouter.Group("/group")
groupRoute.Use(middleware.AdminAuth())
{
diff --git a/web/src/ui-component/TableHead.js b/web/src/ui-component/TableHead.js
new file mode 100644
index 00000000..400efc50
--- /dev/null
+++ b/web/src/ui-component/TableHead.js
@@ -0,0 +1,47 @@
+import PropTypes from 'prop-types';
+import { TableCell, TableHead, TableRow, TableSortLabel } from '@mui/material';
+
+const KeywordTableHead = ({ order, orderBy, headLabel, onRequestSort }) => {
+ const onSort = (property) => (event) => {
+ onRequestSort(event, property);
+ };
+
+ return (
+
+
+ {headLabel.map((headCell) =>
+ headCell.hide && headCell.hide === true ? null : (
+
+ {headCell.disableSort ? (
+ headCell.label
+ ) : (
+
+ {headCell.label}
+
+ )}
+
+ )
+ )}
+
+
+ );
+};
+
+export default KeywordTableHead;
+
+KeywordTableHead.propTypes = {
+ order: PropTypes.oneOf(['asc', 'desc']),
+ orderBy: PropTypes.string,
+ onRequestSort: PropTypes.func,
+ headLabel: PropTypes.array
+};
diff --git a/web/src/ui-component/TableToolBar.js b/web/src/ui-component/TableToolBar.js
index b31d0ef4..e1f90a78 100644
--- a/web/src/ui-component/TableToolBar.js
+++ b/web/src/ui-component/TableToolBar.js
@@ -9,7 +9,7 @@ import { IconSearch } from '@tabler/icons-react';
// ----------------------------------------------------------------------
-export default function TableToolBar({ filterName, handleFilterName, placeholder }) {
+export default function TableToolBar({ placeholder }) {
const theme = useTheme();
const grey500 = theme.palette.grey[500];
@@ -24,11 +24,10 @@ export default function TableToolBar({ filterName, handleFilterName, placeholder
>
diff --git a/web/src/views/Channel/component/TableHead.js b/web/src/views/Channel/component/TableHead.js
deleted file mode 100644
index 736dd8aa..00000000
--- a/web/src/views/Channel/component/TableHead.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { TableCell, TableHead, TableRow } from '@mui/material';
-
-const ChannelTableHead = () => {
- return (
-
-
- ID
- 名称
- 分组
- 类型
- 状态
- 响应时间
- 余额
- 优先级
- 操作
-
-
- );
-};
-
-export default ChannelTableHead;
diff --git a/web/src/views/Channel/index.js b/web/src/views/Channel/index.js
index c397f2c9..631595fd 100644
--- a/web/src/views/Channel/index.js
+++ b/web/src/views/Channel/index.js
@@ -15,83 +15,53 @@ import useMediaQuery from '@mui/material/useMediaQuery';
import { Button, IconButton, Card, Box, Stack, Container, Typography, Divider } from '@mui/material';
import ChannelTableRow from './component/TableRow';
-import ChannelTableHead from './component/TableHead';
+import KeywordTableHead from 'ui-component/TableHead';
import TableToolBar from 'ui-component/TableToolBar';
import { API } from 'utils/api';
-import { ITEMS_PER_PAGE } from 'constants';
import { IconRefresh, IconHttpDelete, IconPlus, IconBrandSpeedtest, IconCoinYuan } from '@tabler/icons-react';
import EditeModal from './component/EditModal';
+import { ITEMS_PER_PAGE } from 'constants';
// ----------------------------------------------------------------------
// CHANNEL_OPTIONS,
export default function ChannelPage() {
- const [channels, setChannels] = useState([]);
- const [activePage, setActivePage] = useState(0);
- const [searching, setSearching] = useState(false);
+ const [page, setPage] = useState(0);
+ const [order, setOrder] = useState('desc');
+ const [orderBy, setOrderBy] = useState('id');
+ const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
+ const [listCount, setListCount] = useState(0);
const [searchKeyword, setSearchKeyword] = useState('');
+ const [searching, setSearching] = useState(false);
+ const [channels, setChannels] = useState([]);
+ const [refreshFlag, setRefreshFlag] = useState(false);
+
const theme = useTheme();
const matchUpMd = useMediaQuery(theme.breakpoints.up('sm'));
const [openModal, setOpenModal] = useState(false);
const [editChannelId, setEditChannelId] = useState(0);
- const loadChannels = async (startIdx) => {
- setSearching(true);
- try {
- const res = await API.get(`/api/channel/?p=${startIdx}`);
- const { success, message, data } = res.data;
- if (success) {
- if (startIdx === 0) {
- setChannels(data);
- } else {
- let newChannels = [...channels];
- newChannels.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
- setChannels(newChannels);
- }
- } else {
- showError(message);
- }
- } catch (error) {
- console.error(error);
+ const handleSort = (event, id) => {
+ const isAsc = orderBy === id && order === 'asc';
+ if (id !== '') {
+ setOrder(isAsc ? 'desc' : 'asc');
+ setOrderBy(id);
}
-
- setSearching(false);
};
- const onPaginationChange = (event, activePage) => {
- (async () => {
- if (activePage === Math.ceil(channels.length / ITEMS_PER_PAGE)) {
- // In this case we have to load more data and then append them.
- await loadChannels(activePage);
- }
- setActivePage(activePage);
- })();
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setPage(0);
+ setRowsPerPage(parseInt(event.target.value, 10));
};
const searchChannels = async (event) => {
event.preventDefault();
- if (searchKeyword === '') {
- await loadChannels(0);
- setActivePage(0);
- return;
- }
- setSearching(true);
- try {
- const res = await API.get(`/api/channel/search?keyword=${searchKeyword}`);
- const { success, message, data } = res.data;
- if (success) {
- setChannels(data);
- setActivePage(0);
- } else {
- showError(message);
- }
- } catch (error) {
- console.error(error);
- }
- setSearching(false);
- };
-
- const handleSearchKeyword = (event) => {
- setSearchKeyword(event.target.value);
+ const formData = new FormData(event.target);
+ setPage(0);
+ setSearchKeyword(formData.get('keyword'));
};
const manageChannel = async (id, action, value) => {
@@ -141,7 +111,9 @@ export default function ChannelPage() {
// 处理刷新
const handleRefresh = async () => {
- await loadChannels(activePage);
+ setOrderBy('id');
+ setOrder('desc');
+ setRefreshFlag(!refreshFlag);
};
// 处理测试所有启用渠道
@@ -210,14 +182,36 @@ export default function ChannelPage() {
}
};
- useEffect(() => {
- loadChannels(0)
- .then()
- .catch((reason) => {
- showError(reason);
+ const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
+ setSearching(true);
+ try {
+ if (orderBy) {
+ orderBy = order === 'desc' ? '-' + orderBy : orderBy;
+ }
+ const res = await API.get(`/api/channel/`, {
+ params: {
+ page: page + 1,
+ size: rowsPerPage,
+ keyword: keyword,
+ order: orderBy
+ }
});
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ const { success, message, data } = res.data;
+ if (success) {
+ setListCount(data.total_count);
+ setChannels(data.data);
+ } else {
+ showError(message);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ setSearching(false);
+ };
+
+ useEffect(() => {
+ fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
+ }, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
return (
<>
@@ -237,7 +231,7 @@ export default function ChannelPage() {
-
+
-
+
- {channels.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
+ {channels.map((row) => (
diff --git a/web/src/views/Log/component/TableHead.js b/web/src/views/Log/component/TableHead.js
deleted file mode 100644
index 102986ef..00000000
--- a/web/src/views/Log/component/TableHead.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import PropTypes from 'prop-types';
-import { TableCell, TableHead, TableRow } from '@mui/material';
-
-const LogTableHead = ({ userIsAdmin }) => {
- return (
-
-
- 时间
- {userIsAdmin && 渠道}
- {userIsAdmin && 用户}
- 令牌
- 类型
- 模型
- 耗时
- 提示
- 补全
- 额度
- 详情
-
-
- );
-};
-
-export default LogTableHead;
-
-LogTableHead.propTypes = {
- userIsAdmin: PropTypes.bool
-};
diff --git a/web/src/views/Log/index.js b/web/src/views/Log/index.js
index 43567531..b4b6e973 100644
--- a/web/src/views/Log/index.js
+++ b/web/src/views/Log/index.js
@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useCallback } from 'react';
import { showError } from 'utils/common';
import Table from '@mui/material/Table';
@@ -12,12 +12,13 @@ import Toolbar from '@mui/material/Toolbar';
import { Button, Card, Stack, Container, Typography, Box } from '@mui/material';
import LogTableRow from './component/TableRow';
-import LogTableHead from './component/TableHead';
+import KeywordTableHead from 'ui-component/TableHead';
import TableToolBar from './component/TableToolBar';
import { API } from 'utils/api';
import { isAdmin } from 'utils/common';
import { ITEMS_PER_PAGE } from 'constants';
import { IconRefresh, IconSearch } from '@tabler/icons-react';
+import dayjs from 'dayjs';
export default function Log() {
const originalKeyword = {
@@ -26,86 +27,98 @@ export default function Log() {
token_name: '',
model_name: '',
start_timestamp: 0,
- end_timestamp: new Date().getTime() / 1000 + 3600,
+ end_timestamp: dayjs().unix() + 3600,
type: 0,
channel: ''
};
- const [logs, setLogs] = useState([]);
- const [activePage, setActivePage] = useState(0);
+
+ const [page, setPage] = useState(0);
+ const [order, setOrder] = useState('desc');
+ const [orderBy, setOrderBy] = useState('created_at');
+ const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
+ const [listCount, setListCount] = useState(0);
const [searching, setSearching] = useState(false);
+ const [toolBarValue, setToolBarValue] = useState(originalKeyword);
const [searchKeyword, setSearchKeyword] = useState(originalKeyword);
- const [initPage, setInitPage] = useState(true);
+ const [refreshFlag, setRefreshFlag] = useState(false);
+
+ const [logs, setLogs] = useState([]);
const userIsAdmin = isAdmin();
- const loadLogs = async (startIdx) => {
- setSearching(true);
- const url = userIsAdmin ? '/api/log/' : '/api/log/self/';
- const query = searchKeyword;
-
- query.p = startIdx;
- if (!userIsAdmin) {
- delete query.username;
- delete query.channel;
+ const handleSort = (event, id) => {
+ const isAsc = orderBy === id && order === 'asc';
+ if (id !== '') {
+ setOrder(isAsc ? 'desc' : 'asc');
+ setOrderBy(id);
}
+ };
- try {
- const res = await API.get(url, { params: query });
- const { success, message, data } = res.data;
- if (success) {
- if (startIdx === 0) {
- setLogs(data);
- } else {
- let newLogs = [...logs];
- newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
- setLogs(newLogs);
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setPage(0);
+ setRowsPerPage(parseInt(event.target.value, 10));
+ };
+
+ const searchLogs = async () => {
+ setPage(0);
+ setSearchKeyword(toolBarValue);
+ };
+
+ const handleToolBarValue = (event) => {
+ setToolBarValue({ ...toolBarValue, [event.target.name]: event.target.value });
+ };
+
+ const fetchData = useCallback(
+ async (page, rowsPerPage, keyword, order, orderBy) => {
+ setSearching(true);
+ try {
+ if (orderBy) {
+ orderBy = order === 'desc' ? '-' + orderBy : orderBy;
}
- } else {
- showError(message);
+ const url = userIsAdmin ? '/api/log/' : '/api/log/self/';
+ if (!userIsAdmin) {
+ delete keyword.username;
+ delete keyword.channel;
+ }
+
+ const res = await API.get(url, {
+ params: {
+ page: page + 1,
+ size: rowsPerPage,
+ order: orderBy,
+ ...keyword
+ }
+ });
+ const { success, message, data } = res.data;
+ if (success) {
+ setListCount(data.total_count);
+ setLogs(data.data);
+ } else {
+ showError(message);
+ }
+ } catch (error) {
+ console.error(error);
}
- } catch (error) {
- console.log(error);
- }
-
- setSearching(false);
- };
-
- const onPaginationChange = (event, activePage) => {
- (async () => {
- if (activePage === Math.ceil(logs.length / ITEMS_PER_PAGE)) {
- // In this case we have to load more data and then append them.
- await loadLogs(activePage);
- }
- setActivePage(activePage);
- })();
- };
-
- const searchLogs = async (event) => {
- event.preventDefault();
- await loadLogs(0);
- setActivePage(0);
- return;
- };
-
- const handleSearchKeyword = (event) => {
- setSearchKeyword({ ...searchKeyword, [event.target.name]: event.target.value });
- };
+ setSearching(false);
+ },
+ [userIsAdmin]
+ );
// 处理刷新
- const handleRefresh = () => {
- setInitPage(true);
+ const handleRefresh = async () => {
+ setOrderBy('created_at');
+ setOrder('desc');
+ setToolBarValue(originalKeyword);
+ setSearchKeyword(originalKeyword);
+ setRefreshFlag(!refreshFlag);
};
useEffect(() => {
- setSearchKeyword(originalKeyword);
- setActivePage(0);
- loadLogs(0)
- .then()
- .catch((reason) => {
- showError(reason);
- });
- setInitPage(false);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [initPage]);
+ fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
+ }, [page, rowsPerPage, searchKeyword, order, orderBy, fetchData, refreshFlag]);
return (
<>
@@ -113,8 +126,8 @@ export default function Log() {
日志
-
-
+
+
-
+
- {logs.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row, index) => (
+ {logs.map((row, index) => (
))}
@@ -151,12 +227,15 @@ export default function Log() {
>
diff --git a/web/src/views/Redemption/component/TableHead.js b/web/src/views/Redemption/component/TableHead.js
deleted file mode 100644
index f5606937..00000000
--- a/web/src/views/Redemption/component/TableHead.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { TableCell, TableHead, TableRow } from '@mui/material';
-
-const RedemptionTableHead = () => {
- return (
-
-
- ID
- 名称
- 状态
- 额度
- 创建时间
- 兑换时间
- 操作
-
-
- );
-};
-
-export default RedemptionTableHead;
diff --git a/web/src/views/Redemption/index.js b/web/src/views/Redemption/index.js
index 9842c079..245c0fea 100644
--- a/web/src/views/Redemption/index.js
+++ b/web/src/views/Redemption/index.js
@@ -12,7 +12,7 @@ import Toolbar from '@mui/material/Toolbar';
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
import RedemptionTableRow from './component/TableRow';
-import RedemptionTableHead from './component/TableHead';
+import KeywordTableHead from 'ui-component/TableHead';
import TableToolBar from 'ui-component/TableToolBar';
import { API } from 'utils/api';
import { ITEMS_PER_PAGE } from 'constants';
@@ -21,74 +21,81 @@ import EditeModal from './component/EditModal';
// ----------------------------------------------------------------------
export default function Redemption() {
- const [redemptions, setRedemptions] = useState([]);
- const [activePage, setActivePage] = useState(0);
- const [searching, setSearching] = useState(false);
+ const [page, setPage] = useState(0);
+ const [order, setOrder] = useState('desc');
+ const [orderBy, setOrderBy] = useState('id');
+ const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
+ const [listCount, setListCount] = useState(0);
const [searchKeyword, setSearchKeyword] = useState('');
+ const [searching, setSearching] = useState(false);
+ const [redemptions, setRedemptions] = useState([]);
+ const [refreshFlag, setRefreshFlag] = useState(false);
+
const [openModal, setOpenModal] = useState(false);
const [editRedemptionId, setEditRedemptionId] = useState(0);
- const loadRedemptions = async (startIdx) => {
- setSearching(true);
- try {
- const res = await API.get(`/api/redemption/?p=${startIdx}`);
- const { success, message, data } = res.data;
- if (success) {
- if (startIdx === 0) {
- setRedemptions(data);
- } else {
- let newRedemptions = [...redemptions];
- newRedemptions.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
- setRedemptions(newRedemptions);
- }
- } else {
- showError(message);
- }
- } catch (error) {
- console.log(error);
+ const handleSort = (event, id) => {
+ const isAsc = orderBy === id && order === 'asc';
+ if (id !== '') {
+ setOrder(isAsc ? 'desc' : 'asc');
+ setOrderBy(id);
}
- setSearching(false);
};
- const onPaginationChange = (event, activePage) => {
- (async () => {
- if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE)) {
- // In this case we have to load more data and then append them.
- await loadRedemptions(activePage);
- }
- setActivePage(activePage);
- })();
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setPage(0);
+ setRowsPerPage(parseInt(event.target.value, 10));
};
const searchRedemptions = async (event) => {
event.preventDefault();
- if (searchKeyword === '') {
- await loadRedemptions(0);
- setActivePage(0);
- return;
- }
- setSearching(true);
+ const formData = new FormData(event.target);
+ setPage(0);
+ setSearchKeyword(formData.get('keyword'));
+ };
+ const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
+ setSearching(true);
try {
- const res = await API.get(`/api/redemption/search?keyword=${searchKeyword}`);
+ if (orderBy) {
+ orderBy = order === 'desc' ? '-' + orderBy : orderBy;
+ }
+ const res = await API.get(`/api/redemption/`, {
+ params: {
+ page: page + 1,
+ size: rowsPerPage,
+ keyword: keyword,
+ order: orderBy
+ }
+ });
const { success, message, data } = res.data;
if (success) {
- setRedemptions(data);
- setActivePage(0);
+ setListCount(data.total_count);
+ setRedemptions(data.data);
} else {
showError(message);
}
} catch (error) {
- console.log(error);
+ console.error(error);
}
-
setSearching(false);
};
- const handleSearchKeyword = (event) => {
- setSearchKeyword(event.target.value);
+ // 处理刷新
+ const handleRefresh = async () => {
+ setOrderBy('id');
+ setOrder('desc');
+ setRefreshFlag(!refreshFlag);
};
+ useEffect(() => {
+ fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
+ }, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
+
const manageRedemptions = async (id, action, value) => {
const url = '/api/redemption/';
let data = { id };
@@ -110,7 +117,7 @@ export default function Redemption() {
if (success) {
showSuccess('操作成功完成!');
if (action === 'delete') {
- await loadRedemptions(0);
+ await handleRefresh();
}
} else {
showError(message);
@@ -122,13 +129,6 @@ export default function Redemption() {
}
};
- // 处理刷新
- const handleRefresh = async () => {
- await loadRedemptions(0);
- setActivePage(0);
- setSearchKeyword('');
- };
-
const handleOpenModal = (redemptionId) => {
setEditRedemptionId(redemptionId);
setOpenModal(true);
@@ -146,15 +146,6 @@ export default function Redemption() {
}
};
- useEffect(() => {
- loadRedemptions(0)
- .then()
- .catch((reason) => {
- showError(reason);
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
return (
<>
@@ -166,7 +157,7 @@ export default function Redemption() {
-
+
-
+
- {redemptions.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
+ {redemptions.map((row) => (
diff --git a/web/src/views/Token/component/TableHead.js b/web/src/views/Token/component/TableHead.js
deleted file mode 100644
index aadf1f6e..00000000
--- a/web/src/views/Token/component/TableHead.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { TableCell, TableHead, TableRow } from '@mui/material';
-
-const TokenTableHead = () => {
- return (
-
-
- 名称
- 状态
- 已用额度
- 剩余额度
- 创建时间
- 过期时间
- 操作
-
-
- );
-};
-
-export default TokenTableHead;
diff --git a/web/src/views/Token/index.js b/web/src/views/Token/index.js
index 215c5bc0..aeb6d599 100644
--- a/web/src/views/Token/index.js
+++ b/web/src/views/Token/index.js
@@ -13,94 +13,91 @@ import Toolbar from '@mui/material/Toolbar';
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
import TokensTableRow from './component/TableRow';
-import TokenTableHead from './component/TableHead';
+import KeywordTableHead from 'ui-component/TableHead';
import TableToolBar from 'ui-component/TableToolBar';
import { API } from 'utils/api';
-import { ITEMS_PER_PAGE } from 'constants';
import { IconRefresh, IconPlus } from '@tabler/icons-react';
import EditeModal from './component/EditModal';
import { useSelector } from 'react-redux';
+import { ITEMS_PER_PAGE } from 'constants';
export default function Token() {
- const [tokens, setTokens] = useState([]);
- const [activePage, setActivePage] = useState(0);
+ const [page, setPage] = useState(0);
+ const [order, setOrder] = useState('desc');
+ const [orderBy, setOrderBy] = useState('id');
+ const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
+ const [listCount, setListCount] = useState(0);
const [searching, setSearching] = useState(false);
const [searchKeyword, setSearchKeyword] = useState('');
+ const [tokens, setTokens] = useState([]);
+ const [refreshFlag, setRefreshFlag] = useState(false);
+
const [openModal, setOpenModal] = useState(false);
const [editTokenId, setEditTokenId] = useState(0);
const siteInfo = useSelector((state) => state.siteInfo);
- const loadTokens = async (startIdx) => {
- setSearching(true);
- try {
- const res = await API.get(`/api/token/?p=${startIdx}`);
- const { success, message, data } = res.data;
- if (success) {
- if (startIdx === 0) {
- setTokens(data);
- } else {
- let newTokens = [...tokens];
- newTokens.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
- setTokens(newTokens);
- }
- } else {
- showError(message);
- }
- } catch (error) {
- console.log(error);
+ const handleSort = (event, id) => {
+ const isAsc = orderBy === id && order === 'asc';
+ if (id !== '') {
+ setOrder(isAsc ? 'desc' : 'asc');
+ setOrderBy(id);
}
-
- setSearching(false);
};
- useEffect(() => {
- loadTokens(0)
- .then()
- .catch((reason) => {
- showError(reason);
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
- const onPaginationChange = (event, activePage) => {
- (async () => {
- if (activePage === Math.ceil(tokens.length / ITEMS_PER_PAGE)) {
- // In this case we have to load more data and then append them.
- await loadTokens(activePage);
- }
- setActivePage(activePage);
- })();
+ const handleChangeRowsPerPage = (event) => {
+ setPage(0);
+ setRowsPerPage(parseInt(event.target.value, 10));
};
const searchTokens = async (event) => {
event.preventDefault();
- if (searchKeyword === '') {
- await loadTokens(0);
- setActivePage(0);
- return;
- }
- setSearching(true);
+ const formData = new FormData(event.target);
+ setPage(0);
+ setSearchKeyword(formData.get('keyword'));
+ };
+ const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
+ setSearching(true);
try {
- const res = await API.get(`/api/token/search?keyword=${searchKeyword}`);
+ if (orderBy) {
+ orderBy = order === 'desc' ? '-' + orderBy : orderBy;
+ }
+ const res = await API.get(`/api/token/`, {
+ params: {
+ page: page + 1,
+ size: rowsPerPage,
+ keyword: keyword,
+ order: orderBy
+ }
+ });
const { success, message, data } = res.data;
if (success) {
- setTokens(data);
- setActivePage(0);
+ setListCount(data.total_count);
+ setTokens(data.data);
} else {
showError(message);
}
} catch (error) {
- return;
+ console.error(error);
}
-
setSearching(false);
};
- const handleSearchKeyword = (event) => {
- setSearchKeyword(event.target.value);
+ // 处理刷新
+ const handleRefresh = async () => {
+ setOrderBy('id');
+ setOrder('desc');
+ setRefreshFlag(!refreshFlag);
};
+ useEffect(() => {
+ fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
+ }, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
+
const manageToken = async (id, action, value) => {
const url = '/api/token/';
let data = { id };
@@ -133,11 +130,6 @@ export default function Token() {
}
};
- // 处理刷新
- const handleRefresh = async () => {
- await loadTokens(activePage);
- };
-
const handleOpenModal = (tokenId) => {
setEditTokenId(tokenId);
setOpenModal(true);
@@ -178,7 +170,7 @@ export default function Token() {
-
+
-
+
- {tokens.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
+ {tokens.map((row) => (
diff --git a/web/src/views/User/component/TableHead.js b/web/src/views/User/component/TableHead.js
deleted file mode 100644
index b7a04165..00000000
--- a/web/src/views/User/component/TableHead.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { TableCell, TableHead, TableRow } from '@mui/material';
-
-const UsersTableHead = () => {
- return (
-
-
- ID
- 用户名
- 分组
- 统计信息
- 用户角色
- 绑定
- 状态
- 操作
-
-
- );
-};
-
-export default UsersTableHead;
diff --git a/web/src/views/User/component/TableRow.js b/web/src/views/User/component/TableRow.js
index 5722a751..ae3bf734 100644
--- a/web/src/views/User/component/TableRow.js
+++ b/web/src/views/User/component/TableRow.js
@@ -19,7 +19,7 @@ import {
import Label from 'ui-component/Label';
import TableSwitch from 'ui-component/Switch';
-import { renderQuota, renderNumber } from 'utils/common';
+import { renderQuota, renderNumber, timestamp2string } from 'utils/common';
import { IconDotsVertical, IconEdit, IconTrash, IconUser, IconBrandWechat, IconBrandGithub, IconMail } from '@tabler/icons-react';
import { useTheme } from '@mui/material/styles';
@@ -119,7 +119,7 @@ export default function UsersTableRow({ item, manageUser, handleOpenModal, setMo
-
+ {item.created_time === 0 ? '未知' : timestamp2string(item.created_time)}
{' '}
diff --git a/web/src/views/User/index.js b/web/src/views/User/index.js
index 1983992f..4863d4cb 100644
--- a/web/src/views/User/index.js
+++ b/web/src/views/User/index.js
@@ -12,7 +12,7 @@ import Toolbar from '@mui/material/Toolbar';
import { Button, Card, Box, Stack, Container, Typography } from '@mui/material';
import UsersTableRow from './component/TableRow';
-import UsersTableHead from './component/TableHead';
+import KeywordTableHead from 'ui-component/TableHead';
import TableToolBar from 'ui-component/TableToolBar';
import { API } from 'utils/api';
import { ITEMS_PER_PAGE } from 'constants';
@@ -21,74 +21,81 @@ import EditeModal from './component/EditModal';
// ----------------------------------------------------------------------
export default function Users() {
- const [users, setUsers] = useState([]);
- const [activePage, setActivePage] = useState(0);
+ const [page, setPage] = useState(0);
+ const [order, setOrder] = useState('desc');
+ const [orderBy, setOrderBy] = useState('id');
+ const [rowsPerPage, setRowsPerPage] = useState(ITEMS_PER_PAGE);
+ const [listCount, setListCount] = useState(0);
const [searching, setSearching] = useState(false);
const [searchKeyword, setSearchKeyword] = useState('');
+ const [users, setUsers] = useState([]);
+ const [refreshFlag, setRefreshFlag] = useState(false);
+
const [openModal, setOpenModal] = useState(false);
const [editUserId, setEditUserId] = useState(0);
- const loadUsers = async (startIdx) => {
- setSearching(true);
- try {
- 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.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data);
- setUsers(newUsers);
- }
- } else {
- showError(message);
- }
- setSearching(false);
- } catch (error) {
- setSearching(false);
- return;
+ const handleSort = (event, id) => {
+ const isAsc = orderBy === id && order === 'asc';
+ if (id !== '') {
+ setOrder(isAsc ? 'desc' : 'asc');
+ setOrderBy(id);
}
};
- const onPaginationChange = (event, activePage) => {
- (async () => {
- if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE)) {
- // In this case we have to load more data and then append them.
- await loadUsers(activePage);
- }
- setActivePage(activePage);
- })();
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setPage(0);
+ setRowsPerPage(parseInt(event.target.value, 10));
};
const searchUsers = async (event) => {
event.preventDefault();
- if (searchKeyword === '') {
- await loadUsers(0);
- setActivePage(0);
- return;
- }
+ const formData = new FormData(event.target);
+ setPage(0);
+ setSearchKeyword(formData.get('keyword'));
+ };
+
+ const fetchData = async (page, rowsPerPage, keyword, order, orderBy) => {
setSearching(true);
try {
- const res = await API.get(`/api/user/search?keyword=${searchKeyword}`);
+ if (orderBy) {
+ orderBy = order === 'desc' ? '-' + orderBy : orderBy;
+ }
+ const res = await API.get(`/api/user/`, {
+ params: {
+ page: page + 1,
+ size: rowsPerPage,
+ keyword: keyword,
+ order: orderBy
+ }
+ });
const { success, message, data } = res.data;
if (success) {
- setUsers(data);
- setActivePage(0);
+ setListCount(data.total_count);
+ setUsers(data.data);
} else {
showError(message);
}
- setSearching(false);
} catch (error) {
- setSearching(false);
- return;
+ console.error(error);
}
+ setSearching(false);
};
- const handleSearchKeyword = (event) => {
- setSearchKeyword(event.target.value);
+ // 处理刷新
+ const handleRefresh = async () => {
+ setOrderBy('id');
+ setOrder('desc');
+ setRefreshFlag(!refreshFlag);
};
+ useEffect(() => {
+ fetchData(page, rowsPerPage, searchKeyword, order, orderBy);
+ }, [page, rowsPerPage, searchKeyword, order, orderBy, refreshFlag]);
+
const manageUser = async (username, action, value) => {
const url = '/api/user/manage';
let data = { username: username, action: '' };
@@ -110,7 +117,7 @@ export default function Users() {
const { success, message } = res.data;
if (success) {
showSuccess('操作成功完成!');
- await loadUsers(activePage);
+ await handleRefresh();
} else {
showError(message);
}
@@ -121,11 +128,6 @@ export default function Users() {
}
};
- // 处理刷新
- const handleRefresh = async () => {
- await loadUsers(activePage);
- };
-
const handleOpenModal = (userId) => {
setEditUserId(userId);
setOpenModal(true);
@@ -143,15 +145,6 @@ export default function Users() {
}
};
- useEffect(() => {
- loadUsers(0)
- .then()
- .catch((reason) => {
- showError(reason);
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
return (
<>
@@ -163,11 +156,7 @@ export default function Users() {
-
+
-
+
- {users.slice(activePage * ITEMS_PER_PAGE, (activePage + 1) * ITEMS_PER_PAGE).map((row) => (
+ {users.map((row) => (