mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-08 17:07:28 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef4c1a2e48 | ||
|
|
ba1aad8ac4 | ||
|
|
42bf95bd54 | ||
|
|
bf9a492f25 | ||
|
|
16725d1226 | ||
|
|
e6ea5e59c0 | ||
|
|
4f196a62e1 | ||
|
|
014fb7edab | ||
|
|
be0b2f6a64 | ||
|
|
687f07bc10 | ||
|
|
a7e5f1e509 | ||
|
|
87d5e286d5 |
11
README.md
11
README.md
@@ -159,15 +159,14 @@ docker run --name new-api -d --restart always -p 3000:3000 -e SQL_DSN="root:1234
|
||||
[对接文档](Suno.md)
|
||||
|
||||
## 界面截图
|
||||

|
||||

|
||||
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
夜间模式
|
||||

|
||||

|
||||

|
||||
|
||||
## 交流群
|
||||
<img src="https://github.com/user-attachments/assets/9ca0bc82-e057-4230-a28d-9f198fa022e3" width="200">
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/bytedance/gopkg/util/gopool"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
@@ -24,6 +23,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/gopkg/util/gopool"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -32,6 +33,9 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
|
||||
if channel.Type == common.ChannelTypeMidjourney {
|
||||
return errors.New("midjourney channel test is not supported"), nil
|
||||
}
|
||||
if channel.Type == common.ChannelTypeMidjourneyPlus {
|
||||
return errors.New("midjourney plus channel test is not supported!!!"), nil
|
||||
}
|
||||
if channel.Type == common.ChannelTypeSunoAPI {
|
||||
return errors.New("suno channel test is not supported"), nil
|
||||
}
|
||||
|
||||
@@ -1,19 +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"))
|
||||
pageSize, _ := strconv.Atoi(c.Query("page_size"))
|
||||
if p < 0 {
|
||||
p = 0
|
||||
}
|
||||
redemptions, err := model.GetAllRedemptions(p*common.ItemsPerPage, common.ItemsPerPage)
|
||||
if pageSize < 1 {
|
||||
pageSize = common.ItemsPerPage
|
||||
}
|
||||
redemptions, total, err := model.GetAllRedemptions((p-1)*pageSize, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
@@ -24,14 +29,27 @@ func GetAllRedemptions(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": redemptions,
|
||||
"data": gin.H{
|
||||
"items": redemptions,
|
||||
"total": total,
|
||||
"page": p,
|
||||
"page_size": pageSize,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func SearchRedemptions(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
redemptions, err := model.SearchRedemptions(keyword)
|
||||
p, _ := strconv.Atoi(c.Query("p"))
|
||||
pageSize, _ := strconv.Atoi(c.Query("page_size"))
|
||||
if p < 0 {
|
||||
p = 0
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = common.ItemsPerPage
|
||||
}
|
||||
redemptions, total, err := model.SearchRedemptions(keyword, (p-1)*pageSize, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
@@ -42,7 +60,12 @@ func SearchRedemptions(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": redemptions,
|
||||
"data": gin.H{
|
||||
"items": redemptions,
|
||||
"total": total,
|
||||
"page": p,
|
||||
"page_size": pageSize,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"one-api/constant"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"one-api/constant"
|
||||
)
|
||||
|
||||
type LoginRequest struct {
|
||||
@@ -242,10 +243,14 @@ func Register(c *gin.Context) {
|
||||
|
||||
func GetAllUsers(c *gin.Context) {
|
||||
p, _ := strconv.Atoi(c.Query("p"))
|
||||
if p < 0 {
|
||||
p = 0
|
||||
pageSize, _ := strconv.Atoi(c.Query("page_size"))
|
||||
if p < 1 {
|
||||
p = 1
|
||||
}
|
||||
users, err := model.GetAllUsers(p*common.ItemsPerPage, common.ItemsPerPage)
|
||||
if pageSize < 0 {
|
||||
pageSize = common.ItemsPerPage
|
||||
}
|
||||
users, total, err := model.GetAllUsers((p-1)*pageSize, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
@@ -256,7 +261,12 @@ func GetAllUsers(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": users,
|
||||
"data": gin.H{
|
||||
"items": users,
|
||||
"total": total,
|
||||
"page": p,
|
||||
"page_size": pageSize,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -264,7 +274,16 @@ func GetAllUsers(c *gin.Context) {
|
||||
func SearchUsers(c *gin.Context) {
|
||||
keyword := c.Query("keyword")
|
||||
group := c.Query("group")
|
||||
users, err := model.SearchUsers(keyword, group)
|
||||
p, _ := strconv.Atoi(c.Query("p"))
|
||||
pageSize, _ := strconv.Atoi(c.Query("page_size"))
|
||||
if p < 1 {
|
||||
p = 1
|
||||
}
|
||||
if pageSize < 0 {
|
||||
pageSize = common.ItemsPerPage
|
||||
}
|
||||
startIdx := (p - 1) * pageSize
|
||||
users, total, err := model.SearchUsers(keyword, group, startIdx, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
@@ -275,7 +294,12 @@ func SearchUsers(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": users,
|
||||
"data": gin.H{
|
||||
"items": users,
|
||||
"total": total,
|
||||
"page": p,
|
||||
"page_size": pageSize,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -23,10 +23,6 @@ type Ability struct {
|
||||
func GetGroupModels(group string) []string {
|
||||
var models []string
|
||||
// Find distinct models
|
||||
groupCol := "`group`"
|
||||
if common.UsingPostgreSQL {
|
||||
groupCol = `"group"`
|
||||
}
|
||||
DB.Table("abilities").Where(groupCol+" = ? and enabled = ?", group, true).Distinct("model").Pluck("model", &models)
|
||||
return models
|
||||
}
|
||||
@@ -45,10 +41,8 @@ func GetAllEnableAbilities() []Ability {
|
||||
}
|
||||
|
||||
func getPriority(group string, model string, retry int) (int, error) {
|
||||
groupCol := "`group`"
|
||||
trueVal := "1"
|
||||
if common.UsingPostgreSQL {
|
||||
groupCol = `"group"`
|
||||
trueVal = "true"
|
||||
}
|
||||
|
||||
@@ -81,10 +75,8 @@ func getPriority(group string, model string, retry int) (int, error) {
|
||||
}
|
||||
|
||||
func getChannelQuery(group string, model string, retry int) *gorm.DB {
|
||||
groupCol := "`group`"
|
||||
trueVal := "1"
|
||||
if common.UsingPostgreSQL {
|
||||
groupCol = `"group"`
|
||||
trueVal = "true"
|
||||
}
|
||||
maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(groupCol+" = ? and model = ? and enabled = "+trueVal, group, model)
|
||||
|
||||
@@ -114,14 +114,11 @@ func GetChannelsByTag(tag string, idSort bool) ([]*Channel, error) {
|
||||
|
||||
func SearchChannels(keyword string, group string, model string, idSort bool) ([]*Channel, error) {
|
||||
var channels []*Channel
|
||||
keyCol := "`key`"
|
||||
groupCol := "`group`"
|
||||
modelsCol := "`models`"
|
||||
|
||||
// 如果是 PostgreSQL,使用双引号
|
||||
if common.UsingPostgreSQL {
|
||||
keyCol = `"key"`
|
||||
groupCol = `"group"`
|
||||
modelsCol = `"models"`
|
||||
}
|
||||
|
||||
@@ -437,14 +434,10 @@ func GetPaginatedTags(offset int, limit int) ([]*string, error) {
|
||||
|
||||
func SearchTags(keyword string, group string, model string, idSort bool) ([]*string, error) {
|
||||
var tags []*string
|
||||
keyCol := "`key`"
|
||||
groupCol := "`group`"
|
||||
modelsCol := "`models`"
|
||||
|
||||
// 如果是 PostgreSQL,使用双引号
|
||||
if common.UsingPostgreSQL {
|
||||
keyCol = `"key"`
|
||||
groupCol = `"group"`
|
||||
modelsCol = `"models"`
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func formatUserLogs(logs []*Log) {
|
||||
func GetLogByKey(key string) (logs []*Log, err error) {
|
||||
if os.Getenv("LOG_SQL_DSN") != "" {
|
||||
var tk Token
|
||||
if err = DB.Model(&Token{}).Where("`key`=?", strings.TrimPrefix(key, "sk-")).First(&tk).Error; err != nil {
|
||||
if err = DB.Model(&Token{}).Where(keyCol+"=?", strings.TrimPrefix(key, "sk-")).First(&tk).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = LOG_DB.Model(&Log{}).Where("token_id=?", tk.Id).Find(&logs).Error
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
var groupCol string
|
||||
var keyCol string
|
||||
|
||||
func init() {
|
||||
func initCol() {
|
||||
if common.UsingPostgreSQL {
|
||||
groupCol = `"group"`
|
||||
keyCol = `"key"`
|
||||
@@ -55,6 +55,9 @@ func createRootAccountIfNeed() error {
|
||||
}
|
||||
|
||||
func chooseDB(envName string) (*gorm.DB, error) {
|
||||
defer func() {
|
||||
initCol()
|
||||
}()
|
||||
dsn := os.Getenv(envName)
|
||||
if dsn != "" {
|
||||
if strings.HasPrefix(dsn, "postgres://") {
|
||||
|
||||
@@ -3,8 +3,10 @@ package model
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"one-api/common"
|
||||
"strconv"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Redemption struct {
|
||||
@@ -21,16 +23,80 @@ type Redemption struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
}
|
||||
|
||||
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
|
||||
func GetAllRedemptions(startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
|
||||
// 开始事务
|
||||
tx := DB.Begin()
|
||||
if tx.Error != nil {
|
||||
return nil, 0, tx.Error
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// 获取总数
|
||||
err = tx.Model(&Redemption{}).Count(&total).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取分页数据
|
||||
err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return redemptions, total, nil
|
||||
}
|
||||
|
||||
func SearchRedemptions(keyword string) (redemptions []*Redemption, err error) {
|
||||
err = DB.Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&redemptions).Error
|
||||
return redemptions, err
|
||||
func SearchRedemptions(keyword string, startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
|
||||
tx := DB.Begin()
|
||||
if tx.Error != nil {
|
||||
return nil, 0, tx.Error
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// Build query based on keyword type
|
||||
query := tx.Model(&Redemption{})
|
||||
|
||||
// Only try to convert to ID if the string represents a valid integer
|
||||
if id, err := strconv.Atoi(keyword); err == nil {
|
||||
query = query.Where("id = ? OR name LIKE ?", id, keyword+"%")
|
||||
} else {
|
||||
query = query.Where("name LIKE ?", keyword+"%")
|
||||
}
|
||||
|
||||
// Get total count
|
||||
err = query.Count(&total).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get paginated data
|
||||
err = query.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return redemptions, total, nil
|
||||
}
|
||||
|
||||
func GetRedemptionById(id int) (*Redemption, error) {
|
||||
|
||||
@@ -68,7 +68,7 @@ func SearchUserTokens(userId int, keyword string, token string) (tokens []*Token
|
||||
if token != "" {
|
||||
token = strings.Trim(token, "sk-")
|
||||
}
|
||||
err = DB.Where("user_id = ?", userId).Where("name LIKE ?", "%"+keyword+"%").Where("`key` LIKE ?", "%"+token+"%").Find(&tokens).Error
|
||||
err = DB.Where("user_id = ?", userId).Where("name LIKE ?", "%"+keyword+"%").Where(keyCol+" LIKE ?", "%"+token+"%").Find(&tokens).Error
|
||||
return tokens, err
|
||||
}
|
||||
|
||||
|
||||
101
model/user.go
101
model/user.go
@@ -81,41 +81,100 @@ func GetMaxUserId() int {
|
||||
return user.Id
|
||||
}
|
||||
|
||||
func GetAllUsers(startIdx int, num int) (users []*User, err error) {
|
||||
err = DB.Unscoped().Order("id desc").Limit(num).Offset(startIdx).Omit("password").Find(&users).Error
|
||||
return users, err
|
||||
func GetAllUsers(startIdx int, num int) (users []*User, total int64, err error) {
|
||||
// Start transaction
|
||||
tx := DB.Begin()
|
||||
if tx.Error != nil {
|
||||
return nil, 0, tx.Error
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// Get total count within transaction
|
||||
err = tx.Unscoped().Model(&User{}).Count(&total).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Get paginated users within same transaction
|
||||
err = tx.Unscoped().Order("id desc").Limit(num).Offset(startIdx).Omit("password").Find(&users).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
func SearchUsers(keyword string, group string) ([]*User, error) {
|
||||
func SearchUsers(keyword string, group string, startIdx int, num int) ([]*User, int64, error) {
|
||||
var users []*User
|
||||
var total int64
|
||||
var err error
|
||||
|
||||
// 开始事务
|
||||
tx := DB.Begin()
|
||||
if tx.Error != nil {
|
||||
return nil, 0, tx.Error
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// 构建基础查询
|
||||
query := tx.Unscoped().Model(&User{})
|
||||
|
||||
// 尝试将关键字转换为整数ID
|
||||
keywordInt, err := strconv.Atoi(keyword)
|
||||
if err == nil {
|
||||
// 如果转换成功,按照ID和可选的组别搜索用户
|
||||
query := DB.Unscoped().Omit("password").Where("id = ?", keywordInt)
|
||||
if group != "" {
|
||||
query = query.Where(groupCol+" = ?", group) // 使用反引号包围group
|
||||
query = query.Where("id = ? AND "+groupCol+" = ?", keywordInt, group)
|
||||
} else {
|
||||
query = query.Where("id = ?", keywordInt)
|
||||
}
|
||||
err = query.Find(&users).Error
|
||||
if err != nil || len(users) > 0 {
|
||||
return users, err
|
||||
}
|
||||
}
|
||||
|
||||
err = nil
|
||||
|
||||
query := DB.Unscoped().Omit("password")
|
||||
likeCondition := "username LIKE ? OR email LIKE ? OR display_name LIKE ?"
|
||||
if group != "" {
|
||||
query = query.Where("("+likeCondition+") AND "+groupCol+" = ?", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", group)
|
||||
} else {
|
||||
query = query.Where(likeCondition, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
|
||||
// 如果不是ID搜索,则使用模糊匹配
|
||||
likeCondition := "username LIKE ? OR email LIKE ? OR display_name LIKE ?"
|
||||
if group != "" {
|
||||
query = query.Where("("+likeCondition+") AND "+groupCol+" = ?",
|
||||
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", group)
|
||||
} else {
|
||||
query = query.Where(likeCondition,
|
||||
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
|
||||
}
|
||||
}
|
||||
err = query.Find(&users).Error
|
||||
|
||||
return users, err
|
||||
// 获取总数
|
||||
err = query.Count(&total).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取分页数据
|
||||
err = query.Omit("password").Order("id desc").Limit(num).Offset(startIdx).Find(&users).Error
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err = tx.Commit().Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
func GetUserById(id int, selectAll bool) (*User, error) {
|
||||
|
||||
@@ -112,6 +112,7 @@ func TextHelper(c *gin.Context) (openaiErr *dto.OpenAIErrorWithStatusCode) {
|
||||
var promptTokens int
|
||||
if value, exists := c.Get("prompt_tokens"); exists {
|
||||
promptTokens = value.(int)
|
||||
relayInfo.PromptTokens = promptTokens
|
||||
} else {
|
||||
promptTokens, err = getPromptTokens(textRequest, relayInfo)
|
||||
// count messages token error 计算promptTokens错误
|
||||
|
||||
@@ -92,13 +92,6 @@ func getImageToken(info *relaycommon.RelayInfo, imageUrl *dto.MessageImageUrl, m
|
||||
if !constant.GetMediaTokenNotStream && !stream {
|
||||
return 256, nil
|
||||
}
|
||||
// 是否统计图片token
|
||||
if !constant.GetMediaToken {
|
||||
return 256, nil
|
||||
}
|
||||
if info.ChannelType == common.ChannelTypeGemini || info.ChannelType == common.ChannelTypeVertexAi || info.ChannelType == common.ChannelTypeAnthropic {
|
||||
return 256, nil
|
||||
}
|
||||
// 同步One API的图片计费逻辑
|
||||
if imageUrl.Detail == "auto" || imageUrl.Detail == "" {
|
||||
imageUrl.Detail = "high"
|
||||
@@ -109,6 +102,13 @@ func getImageToken(info *relaycommon.RelayInfo, imageUrl *dto.MessageImageUrl, m
|
||||
tileTokens = 5667
|
||||
baseTokens = 2833
|
||||
}
|
||||
// 是否统计图片token
|
||||
if !constant.GetMediaToken {
|
||||
return 3 * baseTokens, nil
|
||||
}
|
||||
if info.ChannelType == common.ChannelTypeGemini || info.ChannelType == common.ChannelTypeVertexAi || info.ChannelType == common.ChannelTypeAnthropic {
|
||||
return 3 * baseTokens, nil
|
||||
}
|
||||
var config image.Config
|
||||
var err error
|
||||
var format string
|
||||
|
||||
5127
web/pnpm-lock.yaml
generated
5127
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -825,6 +825,12 @@ const LogsTable = () => {
|
||||
dataSource={logs}
|
||||
rowKey="key"
|
||||
pagination={{
|
||||
formatPageText: (page) =>
|
||||
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
|
||||
start: page.currentStart,
|
||||
end: page.currentEnd,
|
||||
total: logs.length
|
||||
}),
|
||||
currentPage: activePage,
|
||||
pageSize: pageSize,
|
||||
total: logCount,
|
||||
|
||||
@@ -178,6 +178,7 @@ const RedemptionsTable = () => {
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE);
|
||||
const [selectedKeys, setSelectedKeys] = useState([]);
|
||||
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
|
||||
const [editingRedemption, setEditingRedemption] = useState({
|
||||
id: undefined,
|
||||
});
|
||||
@@ -187,40 +188,20 @@ const RedemptionsTable = () => {
|
||||
setShowEdit(false);
|
||||
};
|
||||
|
||||
// const setCount = (data) => {
|
||||
// if (data.length >= (activePage) * ITEMS_PER_PAGE) {
|
||||
// setTokenCount(data.length + 1);
|
||||
// } else {
|
||||
// setTokenCount(data.length);
|
||||
// }
|
||||
// }
|
||||
|
||||
const setRedemptionFormat = (redeptions) => {
|
||||
// for (let i = 0; i < redeptions.length; i++) {
|
||||
// redeptions[i].key = '' + redeptions[i].id;
|
||||
// }
|
||||
// data.key = '' + data.id
|
||||
setRedemptions(redeptions);
|
||||
if (redeptions.length >= activePage * ITEMS_PER_PAGE) {
|
||||
setTokenCount(redeptions.length + 1);
|
||||
} else {
|
||||
setTokenCount(redeptions.length);
|
||||
}
|
||||
};
|
||||
|
||||
const loadRedemptions = async (startIdx) => {
|
||||
const res = await API.get(`/api/redemption/?p=${startIdx}`);
|
||||
const loadRedemptions = async (startIdx, pageSize) => {
|
||||
const res = await API.get(`/api/redemption/?p=${startIdx}&page_size=${pageSize}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (startIdx === 0) {
|
||||
setRedemptionFormat(data);
|
||||
} else {
|
||||
let newRedemptions = redemptions;
|
||||
newRedemptions.push(...data);
|
||||
setRedemptionFormat(newRedemptions);
|
||||
}
|
||||
const newPageData = data.items;
|
||||
setActivePage(data.page);
|
||||
setTokenCount(data.total);
|
||||
setRedemptionFormat(newPageData);
|
||||
} else {
|
||||
showError(message);
|
||||
showError(message);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
@@ -248,16 +229,15 @@ const RedemptionsTable = () => {
|
||||
|
||||
const onPaginationChange = (e, { activePage }) => {
|
||||
(async () => {
|
||||
if (activePage === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) {
|
||||
// In this case we have to load more data and then append them.
|
||||
await loadRedemptions(activePage - 1);
|
||||
if (activePage === Math.ceil(redemptions.length / pageSize) + 1) {
|
||||
await loadRedemptions(activePage - 1, pageSize);
|
||||
}
|
||||
setActivePage(activePage);
|
||||
})();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadRedemptions(0)
|
||||
loadRedemptions(0, pageSize)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
@@ -265,7 +245,7 @@ const RedemptionsTable = () => {
|
||||
}, []);
|
||||
|
||||
const refresh = async () => {
|
||||
await loadRedemptions(activePage - 1);
|
||||
await loadRedemptions(activePage - 1, pageSize);
|
||||
};
|
||||
|
||||
const manageRedemption = async (id, action, record) => {
|
||||
@@ -300,23 +280,21 @@ const RedemptionsTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const searchRedemptions = async () => {
|
||||
const searchRedemptions = async (keyword, page, pageSize) => {
|
||||
if (searchKeyword === '') {
|
||||
// if keyword is blank, load files instead.
|
||||
await loadRedemptions(0);
|
||||
setActivePage(1);
|
||||
return;
|
||||
await loadRedemptions(page, pageSize);
|
||||
return;
|
||||
}
|
||||
setSearching(true);
|
||||
const res = await API.get(
|
||||
`/api/redemption/search?keyword=${searchKeyword}`,
|
||||
);
|
||||
const res = await API.get(`/api/redemption/search?keyword=${keyword}&p=${page}&page_size=${pageSize}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setRedemptions(data);
|
||||
setActivePage(1);
|
||||
const newPageData = data.items;
|
||||
setActivePage(data.page);
|
||||
setTokenCount(data.total);
|
||||
setRedemptionFormat(newPageData);
|
||||
} else {
|
||||
showError(message);
|
||||
showError(message);
|
||||
}
|
||||
setSearching(false);
|
||||
};
|
||||
@@ -341,16 +319,14 @@ const RedemptionsTable = () => {
|
||||
|
||||
const handlePageChange = (page) => {
|
||||
setActivePage(page);
|
||||
if (page === Math.ceil(redemptions.length / ITEMS_PER_PAGE) + 1) {
|
||||
// In this case we have to load more data and then append them.
|
||||
loadRedemptions(page - 1).then((r) => {});
|
||||
if (searchKeyword === '') {
|
||||
loadRedemptions(page, pageSize).then();
|
||||
} else {
|
||||
searchRedemptions(searchKeyword, page, pageSize).then();
|
||||
}
|
||||
};
|
||||
|
||||
let pageData = redemptions.slice(
|
||||
(activePage - 1) * ITEMS_PER_PAGE,
|
||||
activePage * ITEMS_PER_PAGE,
|
||||
);
|
||||
let pageData = redemptions;
|
||||
const rowSelection = {
|
||||
onSelect: (record, selected) => {},
|
||||
onSelectAll: (selected, selectedRows) => {},
|
||||
@@ -379,7 +355,9 @@ const RedemptionsTable = () => {
|
||||
visiable={showEdit}
|
||||
handleClose={closeEdit}
|
||||
></EditRedemption>
|
||||
<Form onSubmit={searchRedemptions}>
|
||||
<Form onSubmit={()=> {
|
||||
searchRedemptions(searchKeyword, activePage, pageSize).then();
|
||||
}}>
|
||||
<Form.Input
|
||||
label={t('搜索关键字')}
|
||||
field='keyword'
|
||||
@@ -431,20 +409,25 @@ const RedemptionsTable = () => {
|
||||
dataSource={pageData}
|
||||
pagination={{
|
||||
currentPage: activePage,
|
||||
pageSize: ITEMS_PER_PAGE,
|
||||
pageSize: pageSize,
|
||||
total: tokenCount,
|
||||
// showSizeChanger: true,
|
||||
// pageSizeOptions: [10, 20, 50, 100],
|
||||
showSizeChanger: true,
|
||||
pageSizeOpts: [10, 20, 50, 100],
|
||||
formatPageText: (page) =>
|
||||
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
|
||||
start: page.currentStart,
|
||||
end: page.currentEnd,
|
||||
total: redemptions.length
|
||||
total: tokenCount
|
||||
}),
|
||||
// onPageSizeChange: (size) => {
|
||||
// setPageSize(size);
|
||||
// setActivePage(1);
|
||||
// },
|
||||
onPageSizeChange: (size) => {
|
||||
setPageSize(size);
|
||||
setActivePage(1);
|
||||
if (searchKeyword === '') {
|
||||
loadRedemptions(1, size).then();
|
||||
} else {
|
||||
searchRedemptions(searchKeyword, 1, size).then();
|
||||
}
|
||||
},
|
||||
onPageChange: handlePageChange,
|
||||
}}
|
||||
loading={loading}
|
||||
|
||||
@@ -231,6 +231,7 @@ const UsersTable = () => {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [searchGroup, setSearchGroup] = useState('');
|
||||
@@ -242,14 +243,6 @@ const UsersTable = () => {
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
const setCount = (data) => {
|
||||
if (data.length >= activePage * ITEMS_PER_PAGE) {
|
||||
setUserCount(data.length + 1);
|
||||
} else {
|
||||
setUserCount(data.length);
|
||||
}
|
||||
};
|
||||
|
||||
const removeRecord = (key) => {
|
||||
let newDataSource = [...users];
|
||||
if (key != null) {
|
||||
@@ -263,37 +256,30 @@ const UsersTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadUsers = async (startIdx) => {
|
||||
const res = await API.get(`/api/user/?p=${startIdx}`);
|
||||
const setUserFormat = (users) => {
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
users[i].key = users[i].id;
|
||||
}
|
||||
setUsers(users);
|
||||
}
|
||||
|
||||
const loadUsers = async (startIdx, pageSize) => {
|
||||
const res = await API.get(`/api/user/?p=${startIdx}&page_size=${pageSize}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
if (startIdx === 0) {
|
||||
setUsers(data);
|
||||
setCount(data);
|
||||
} else {
|
||||
let newUsers = users;
|
||||
newUsers.push(...data);
|
||||
setUsers(newUsers);
|
||||
setCount(newUsers);
|
||||
}
|
||||
const newPageData = data.items;
|
||||
setActivePage(data.page);
|
||||
setUserCount(data.total);
|
||||
setUserFormat(newPageData);
|
||||
} 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)
|
||||
loadUsers(0, pageSize)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
@@ -341,21 +327,22 @@ const UsersTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const searchUsers = async (searchKeyword, searchGroup) => {
|
||||
const searchUsers = async (startIdx, pageSize, searchKeyword, searchGroup) => {
|
||||
if (searchKeyword === '' && searchGroup === '') {
|
||||
// if keyword is blank, load files instead.
|
||||
await loadUsers(0);
|
||||
setActivePage(1);
|
||||
return;
|
||||
// if keyword is blank, load files instead.
|
||||
await loadUsers(startIdx, pageSize);
|
||||
return;
|
||||
}
|
||||
setSearching(true);
|
||||
const res = await API.get(`/api/user/search?keyword=${searchKeyword}&group=${searchGroup}`);
|
||||
const res = await API.get(`/api/user/search?keyword=${searchKeyword}&group=${searchGroup}&p=${startIdx}&page_size=${pageSize}`);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
setUsers(data);
|
||||
setActivePage(1);
|
||||
const newPageData = data.items;
|
||||
setActivePage(data.page);
|
||||
setUserCount(data.total);
|
||||
setUserFormat(newPageData);
|
||||
} else {
|
||||
showError(message);
|
||||
showError(message);
|
||||
}
|
||||
setSearching(false);
|
||||
};
|
||||
@@ -364,33 +351,15 @@ const UsersTable = () => {
|
||||
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);
|
||||
};
|
||||
|
||||
const handlePageChange = (page) => {
|
||||
setActivePage(page);
|
||||
if (page === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) {
|
||||
// In this case we have to load more data and then append them.
|
||||
loadUsers(page - 1).then((r) => {});
|
||||
if (searchKeyword === '' && searchGroup === '') {
|
||||
loadUsers(page, pageSize).then();
|
||||
} else {
|
||||
searchUsers(page, pageSize, searchKeyword, searchGroup).then();
|
||||
}
|
||||
};
|
||||
|
||||
const pageData = users.slice(
|
||||
(activePage - 1) * ITEMS_PER_PAGE,
|
||||
activePage * ITEMS_PER_PAGE,
|
||||
);
|
||||
|
||||
const closeAddUser = () => {
|
||||
setShowAddUser(false);
|
||||
};
|
||||
@@ -403,8 +372,9 @@ const UsersTable = () => {
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
setActivePage(1)
|
||||
if (searchKeyword === '') {
|
||||
await loadUsers(activePage - 1);
|
||||
await loadUsers(activePage, pageSize);
|
||||
} else {
|
||||
await searchUsers(searchKeyword, searchGroup);
|
||||
}
|
||||
@@ -429,6 +399,17 @@ const UsersTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePageSizeChange = async (size) => {
|
||||
localStorage.setItem('page-size', size + '');
|
||||
setPageSize(size);
|
||||
setActivePage(1);
|
||||
loadUsers(activePage, size)
|
||||
.then()
|
||||
.catch((reason) => {
|
||||
showError(reason);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<AddUser
|
||||
@@ -444,29 +425,32 @@ const UsersTable = () => {
|
||||
></EditUser>
|
||||
<Form
|
||||
onSubmit={() => {
|
||||
searchUsers(searchKeyword, searchGroup);
|
||||
searchUsers(activePage, pageSize, searchKeyword, searchGroup);
|
||||
}}
|
||||
labelPosition='left'
|
||||
>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Space>
|
||||
<Form.Input
|
||||
label={t('搜索关键字')}
|
||||
icon='search'
|
||||
field='keyword'
|
||||
iconPosition='left'
|
||||
placeholder={t('搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...')}
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={(value) => handleKeywordChange(value)}
|
||||
/>
|
||||
<Tooltip content={t('支持搜索用户的 ID、用户名、显示名称和邮箱地址')}>
|
||||
<Form.Input
|
||||
label={t('搜索关键字')}
|
||||
icon='search'
|
||||
field='keyword'
|
||||
iconPosition='left'
|
||||
placeholder={t('搜索关键字')}
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={(value) => handleKeywordChange(value)}
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
<Form.Select
|
||||
field='group'
|
||||
label={t('分组')}
|
||||
optionList={groupOptions}
|
||||
onChange={(value) => {
|
||||
setSearchGroup(value);
|
||||
searchUsers(searchKeyword, value);
|
||||
searchUsers(activePage, pageSize, searchKeyword, value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -492,7 +476,7 @@ const UsersTable = () => {
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={pageData}
|
||||
dataSource={users}
|
||||
pagination={{
|
||||
formatPageText: (page) =>
|
||||
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
|
||||
@@ -501,9 +485,13 @@ const UsersTable = () => {
|
||||
total: users.length
|
||||
}),
|
||||
currentPage: activePage,
|
||||
pageSize: ITEMS_PER_PAGE,
|
||||
pageSize: pageSize,
|
||||
total: userCount,
|
||||
pageSizeOpts: [10, 20, 50, 100],
|
||||
showSizeChanger: true,
|
||||
onPageSizeChange: (size) => {
|
||||
handlePageSizeChange(size);
|
||||
},
|
||||
onPageChange: handlePageChange,
|
||||
}}
|
||||
loading={loading}
|
||||
|
||||
@@ -1240,5 +1240,7 @@
|
||||
"时间范围": "Time range",
|
||||
"批量设置标签": "Batch set tag",
|
||||
"请输入要设置的标签名称": "Please enter the tag name to be set",
|
||||
"请输入标签名称": "Please enter the tag name"
|
||||
"请输入标签名称": "Please enter the tag name",
|
||||
"支持搜索用户的 ID、用户名、显示名称和邮箱地址": "Support searching for user ID, username, display name, and email address",
|
||||
"已注销": "Logged out"
|
||||
}
|
||||
@@ -19,7 +19,11 @@ const AddUser = (props) => {
|
||||
|
||||
const submit = async () => {
|
||||
setLoading(true);
|
||||
if (inputs.username === '' || inputs.password === '') return;
|
||||
if (inputs.username === '' || inputs.password === '') {
|
||||
setLoading(false);
|
||||
showError('用户名和密码不能为空!');
|
||||
return;
|
||||
}
|
||||
const res = await API.post(`/api/user/`, inputs);
|
||||
const { success, message } = res.data;
|
||||
if (success) {
|
||||
|
||||
Reference in New Issue
Block a user