Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
dc8deb0c
authored
May 06, 2026
by
yyhhyyyyyy
Committed by
GitHub
May 06, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: enable channel table server-side sorting (#4600)
parent
f8cf9c57
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
136 additions
and
35 deletions
+136
-35
controller/channel.go
+6
-9
model/channel.go
+71
-19
web/default/src/features/channels/components/channels-table.tsx
+40
-2
web/default/src/features/channels/types.ts
+14
-0
web/default/src/i18n/locales/fr.json
+1
-1
web/default/src/i18n/locales/ja.json
+1
-1
web/default/src/i18n/locales/ru.json
+1
-1
web/default/src/i18n/locales/vi.json
+1
-1
web/default/src/i18n/locales/zh.json
+1
-1
No files found.
controller/channel.go
View file @
dc8deb0c
...
...
@@ -72,6 +72,7 @@ func GetAllChannels(c *gin.Context) {
pageInfo
:=
common
.
GetPageQuery
(
c
)
channelData
:=
make
([]
*
model
.
Channel
,
0
)
idSort
,
_
:=
strconv
.
ParseBool
(
c
.
Query
(
"id_sort"
))
sortOptions
:=
model
.
NewChannelSortOptions
(
c
.
Query
(
"sort_by"
),
c
.
Query
(
"sort_order"
),
idSort
)
enableTagMode
,
_
:=
strconv
.
ParseBool
(
c
.
Query
(
"tag_mode"
))
statusParam
:=
c
.
Query
(
"status"
)
// statusFilter: -1 all, 1 enabled, 0 disabled (include auto & manual)
...
...
@@ -98,7 +99,7 @@ func GetAllChannels(c *gin.Context) {
if
tag
==
nil
||
*
tag
==
""
{
continue
}
tagChannels
,
err
:=
model
.
GetChannelsByTag
(
*
tag
,
idSort
,
false
)
tagChannels
,
err
:=
model
.
GetChannelsByTag
(
*
tag
,
idSort
,
false
,
sortOptions
)
if
err
!=
nil
{
continue
}
...
...
@@ -131,12 +132,7 @@ func GetAllChannels(c *gin.Context) {
baseQuery
.
Count
(
&
total
)
order
:=
"priority desc"
if
idSort
{
order
=
"id desc"
}
err
:=
baseQuery
.
Order
(
order
)
.
Limit
(
pageInfo
.
GetPageSize
())
.
Offset
(
pageInfo
.
GetStartIdx
())
.
Omit
(
"key"
)
.
Find
(
&
channelData
)
.
Error
err
:=
sortOptions
.
Apply
(
baseQuery
)
.
Limit
(
pageInfo
.
GetPageSize
())
.
Offset
(
pageInfo
.
GetStartIdx
())
.
Omit
(
"key"
)
.
Find
(
&
channelData
)
.
Error
if
err
!=
nil
{
common
.
SysError
(
"failed to get channels: "
+
err
.
Error
())
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"获取渠道列表失败,请稍后重试"
})
...
...
@@ -252,6 +248,7 @@ func SearchChannels(c *gin.Context) {
statusParam
:=
c
.
Query
(
"status"
)
statusFilter
:=
parseStatusFilter
(
statusParam
)
idSort
,
_
:=
strconv
.
ParseBool
(
c
.
Query
(
"id_sort"
))
sortOptions
:=
model
.
NewChannelSortOptions
(
c
.
Query
(
"sort_by"
),
c
.
Query
(
"sort_order"
),
idSort
)
enableTagMode
,
_
:=
strconv
.
ParseBool
(
c
.
Query
(
"tag_mode"
))
channelData
:=
make
([]
*
model
.
Channel
,
0
)
if
enableTagMode
{
...
...
@@ -265,14 +262,14 @@ func SearchChannels(c *gin.Context) {
}
for
_
,
tag
:=
range
tags
{
if
tag
!=
nil
&&
*
tag
!=
""
{
tagChannel
,
err
:=
model
.
GetChannelsByTag
(
*
tag
,
idSort
,
false
)
tagChannel
,
err
:=
model
.
GetChannelsByTag
(
*
tag
,
idSort
,
false
,
sortOptions
)
if
err
==
nil
{
channelData
=
append
(
channelData
,
tagChannel
...
)
}
}
}
}
else
{
channels
,
err
:=
model
.
SearchChannels
(
keyword
,
group
,
modelKeyword
,
idSort
)
channels
,
err
:=
model
.
SearchChannels
(
keyword
,
group
,
modelKeyword
,
idSort
,
sortOptions
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
...
...
model/channel.go
View file @
dc8deb0c
...
...
@@ -16,6 +16,7 @@ import (
"github.com/samber/lo"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type
Channel
struct
{
...
...
@@ -67,6 +68,66 @@ type ChannelInfo struct {
MultiKeyMode
constant
.
MultiKeyMode
`json:"multi_key_mode"`
}
type
ChannelSortOptions
struct
{
SortBy
string
SortOrder
string
IDSort
bool
}
var
channelSortColumns
=
map
[
string
]
string
{
"id"
:
"id"
,
"name"
:
"name"
,
"priority"
:
"priority"
,
"balance"
:
"balance"
,
"response_time"
:
"response_time"
,
"test_time"
:
"test_time"
,
}
func
NewChannelSortOptions
(
sortBy
string
,
sortOrder
string
,
idSort
bool
)
ChannelSortOptions
{
normalizedSortBy
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
sortBy
))
normalizedSortOrder
:=
strings
.
ToLower
(
strings
.
TrimSpace
(
sortOrder
))
if
_
,
ok
:=
channelSortColumns
[
normalizedSortBy
];
!
ok
{
normalizedSortBy
=
""
normalizedSortOrder
=
""
}
else
if
normalizedSortOrder
!=
"asc"
{
normalizedSortOrder
=
"desc"
}
return
ChannelSortOptions
{
SortBy
:
normalizedSortBy
,
SortOrder
:
normalizedSortOrder
,
IDSort
:
idSort
,
}
}
func
(
options
ChannelSortOptions
)
Apply
(
query
*
gorm
.
DB
)
*
gorm
.
DB
{
if
columnName
,
ok
:=
channelSortColumns
[
options
.
SortBy
];
ok
{
return
query
.
Order
(
clause
.
OrderByColumn
{
Column
:
clause
.
Column
{
Name
:
columnName
},
Desc
:
options
.
SortOrder
!=
"asc"
,
})
}
if
options
.
IDSort
{
return
query
.
Order
(
clause
.
OrderByColumn
{
Column
:
clause
.
Column
{
Name
:
"id"
},
Desc
:
true
,
})
}
return
query
.
Order
(
clause
.
OrderByColumn
{
Column
:
clause
.
Column
{
Name
:
"priority"
},
Desc
:
true
,
})
}
func
resolveChannelSortOptions
(
idSort
bool
,
sortOptions
[]
ChannelSortOptions
)
ChannelSortOptions
{
if
len
(
sortOptions
)
==
0
{
return
NewChannelSortOptions
(
""
,
""
,
idSort
)
}
options
:=
sortOptions
[
0
]
options
.
IDSort
=
options
.
IDSort
||
idSort
return
options
}
// Value implements driver.Valuer interface
func
(
c
ChannelInfo
)
Value
()
(
driver
.
Value
,
error
)
{
return
common
.
Marshal
(
&
c
)
...
...
@@ -260,28 +321,22 @@ func (channel *Channel) SaveWithoutKey() error {
return
DB
.
Omit
(
"key"
)
.
Save
(
channel
)
.
Error
}
func
GetAllChannels
(
startIdx
int
,
num
int
,
selectAll
bool
,
idSort
bool
)
([]
*
Channel
,
error
)
{
func
GetAllChannels
(
startIdx
int
,
num
int
,
selectAll
bool
,
idSort
bool
,
sortOptions
...
ChannelSortOptions
)
([]
*
Channel
,
error
)
{
var
channels
[]
*
Channel
var
err
error
order
:=
"priority desc"
if
idSort
{
order
=
"id desc"
}
order
:=
resolveChannelSortOptions
(
idSort
,
sortOptions
)
if
selectAll
{
err
=
DB
.
Order
(
order
)
.
Find
(
&
channels
)
.
Error
err
=
order
.
Apply
(
DB
)
.
Find
(
&
channels
)
.
Error
}
else
{
err
=
DB
.
Order
(
order
)
.
Limit
(
num
)
.
Offset
(
startIdx
)
.
Omit
(
"key"
)
.
Find
(
&
channels
)
.
Error
err
=
order
.
Apply
(
DB
)
.
Limit
(
num
)
.
Offset
(
startIdx
)
.
Omit
(
"key"
)
.
Find
(
&
channels
)
.
Error
}
return
channels
,
err
}
func
GetChannelsByTag
(
tag
string
,
idSort
bool
,
selectAll
bool
)
([]
*
Channel
,
error
)
{
func
GetChannelsByTag
(
tag
string
,
idSort
bool
,
selectAll
bool
,
sortOptions
...
ChannelSortOptions
)
([]
*
Channel
,
error
)
{
var
channels
[]
*
Channel
order
:=
"priority desc"
if
idSort
{
order
=
"id desc"
}
query
:=
DB
.
Where
(
"tag = ?"
,
tag
)
.
Order
(
order
)
order
:=
resolveChannelSortOptions
(
idSort
,
sortOptions
)
query
:=
order
.
Apply
(
DB
.
Where
(
"tag = ?"
,
tag
))
if
!
selectAll
{
query
=
query
.
Omit
(
"key"
)
}
...
...
@@ -289,7 +344,7 @@ func GetChannelsByTag(tag string, idSort bool, selectAll bool) ([]*Channel, erro
return
channels
,
err
}
func
SearchChannels
(
keyword
string
,
group
string
,
model
string
,
idSort
bool
)
([]
*
Channel
,
error
)
{
func
SearchChannels
(
keyword
string
,
group
string
,
model
string
,
idSort
bool
,
sortOptions
...
ChannelSortOptions
)
([]
*
Channel
,
error
)
{
var
channels
[]
*
Channel
modelsCol
:=
"`models`"
...
...
@@ -304,10 +359,7 @@ func SearchChannels(keyword string, group string, model string, idSort bool) ([]
baseURLCol
=
`"base_url"`
}
order
:=
"priority desc"
if
idSort
{
order
=
"id desc"
}
order
:=
resolveChannelSortOptions
(
idSort
,
sortOptions
)
// 构造基础查询
baseQuery
:=
DB
.
Model
(
&
Channel
{})
.
Omit
(
"key"
)
...
...
@@ -331,7 +383,7 @@ func SearchChannels(keyword string, group string, model string, idSort bool) ([]
}
// 执行查询
err
:=
baseQuery
.
Where
(
whereClause
,
args
...
)
.
Order
(
order
)
.
Find
(
&
channels
)
.
Error
err
:=
order
.
Apply
(
baseQuery
.
Where
(
whereClause
,
args
...
)
)
.
Find
(
&
channels
)
.
Error
if
err
!=
nil
{
return
nil
,
err
}
...
...
web/default/src/features/channels/components/channels-table.tsx
View file @
dc8deb0c
...
...
@@ -5,6 +5,7 @@ import {
getCoreRowModel
,
useReactTable
,
getExpandedRowModel
,
type
OnChangeFn
,
type
SortingState
,
type
VisibilityState
,
type
ExpandedState
,
...
...
@@ -33,13 +34,22 @@ import {
getChannelTypeIcon
,
getChannelTypeLabel
,
}
from
'../lib'
import
type
{
Channel
}
from
'../types'
import
type
{
Channel
,
ChannelSortBy
}
from
'../types'
import
{
useChannelsColumns
}
from
'./channels-columns'
import
{
useChannels
}
from
'./channels-provider'
import
{
DataTableBulkActions
}
from
'./data-table-bulk-actions'
const
route
=
getRouteApi
(
'/_authenticated/channels/'
)
const
CHANNEL_SORTABLE_COLUMNS
=
new
Set
<
ChannelSortBy
>
([
'id'
,
'name'
,
'priority'
,
'balance'
,
'response_time'
,
'test_time'
,
])
function
isDisabledChannelRow
(
channel
:
Channel
)
{
return
(
!
isTagAggregateRow
(
channel
)
&&
channel
.
status
!==
CHANNEL_STATUS
.
ENABLED
...
...
@@ -121,6 +131,31 @@ export function ChannelsTable() {
// Determine whether to use search or regular list API
const
shouldSearch
=
Boolean
(
globalFilter
?.
trim
()
||
modelFilter
.
trim
())
const
sortParams
=
useMemo
(()
=>
{
const
activeSort
=
sorting
[
0
]
if
(
!
activeSort
||
!
CHANNEL_SORTABLE_COLUMNS
.
has
(
activeSort
.
id
as
ChannelSortBy
)
)
{
return
{}
}
return
{
sort_by
:
activeSort
.
id
as
ChannelSortBy
,
sort_order
:
activeSort
.
desc
?
'desc'
:
'asc'
,
}
as
const
},
[
sorting
])
const
handleSortingChange
:
OnChangeFn
<
SortingState
>
=
(
updater
)
=>
{
setSorting
((
previous
)
=>
{
const
next
=
typeof
updater
===
'function'
?
updater
(
previous
)
:
updater
if
(
pagination
.
pageIndex
>
0
)
{
onPaginationChange
({
...
pagination
,
pageIndex
:
0
})
}
return
next
})
}
// Fetch groups for filter
const
{
data
:
groupsData
}
=
useQuery
({
queryKey
:
[
'groups'
],
...
...
@@ -156,6 +191,7 @@ export function ChannelsTable() {
:
undefined
,
tag_mode
:
enableTagMode
,
id_sort
:
idSort
,
...
sortParams
,
p
:
pagination
.
pageIndex
+
1
,
page_size
:
pagination
.
pageSize
,
}),
...
...
@@ -178,6 +214,7 @@ export function ChannelsTable() {
:
undefined
,
tag_mode
:
enableTagMode
,
id_sort
:
idSort
,
...
sortParams
,
p
:
pagination
.
pageIndex
+
1
,
page_size
:
pagination
.
pageSize
,
})
...
...
@@ -197,6 +234,7 @@ export function ChannelsTable() {
:
undefined
,
tag_mode
:
enableTagMode
,
id_sort
:
idSort
,
...
sortParams
,
p
:
pagination
.
pageIndex
+
1
,
page_size
:
pagination
.
pageSize
,
})
...
...
@@ -238,7 +276,7 @@ export function ChannelsTable() {
},
enableRowSelection
:
(
row
:
Row
<
Channel
>
)
=>
!
isTagAggregateRow
(
row
.
original
),
onRowSelectionChange
:
setRowSelection
,
onSortingChange
:
setSorting
,
onSortingChange
:
handleSortingChange
,
onColumnFiltersChange
,
onColumnVisibilityChange
:
setColumnVisibility
,
onPaginationChange
,
...
...
web/default/src/features/channels/types.ts
View file @
dc8deb0c
...
...
@@ -194,6 +194,16 @@ export interface MultiKeyStatusResponse {
// API Request Parameters
// ============================================================================
export
type
ChannelSortBy
=
|
'id'
|
'name'
|
'priority'
|
'balance'
|
'response_time'
|
'test_time'
export
type
ChannelSortOrder
=
'asc'
|
'desc'
export
interface
GetChannelsParams
{
p
?:
number
page_size
?:
number
...
...
@@ -202,6 +212,8 @@ export interface GetChannelsParams {
group
?:
string
id_sort
?:
boolean
tag_mode
?:
boolean
sort_by
?:
ChannelSortBy
sort_order
?:
ChannelSortOrder
}
export
interface
SearchChannelsParams
{
...
...
@@ -212,6 +224,8 @@ export interface SearchChannelsParams {
type
?:
number
id_sort
?:
boolean
tag_mode
?:
boolean
sort_by
?:
ChannelSortBy
sort_order
?:
ChannelSortOrder
p
?:
number
page_size
?:
number
}
...
...
web/default/src/i18n/locales/fr.json
View file @
dc8deb0c
...
...
@@ -1108,7 +1108,7 @@
"Deployment Region *"
:
"Région de déploiement *"
,
"Deployment requested"
:
"Déploiement demandé"
,
"Deployments"
:
"Déploiements"
,
"Desc"
:
"Desc
ription
"
,
"Desc"
:
"Desc
.
"
,
"Describe"
:
"Décrire"
,
"Describe this model..."
:
"Décrire ce modèle..."
,
"Describe this vendor..."
:
"Décrire ce fournisseur..."
,
...
...
web/default/src/i18n/locales/ja.json
View file @
dc8deb0c
...
...
@@ -1108,7 +1108,7 @@
"Deployment Region *"
:
"デプロイリージョン *"
,
"Deployment requested"
:
"デプロイメントが要求されました"
,
"Deployments"
:
"デプロイ"
,
"Desc"
:
"
説明
"
,
"Desc"
:
"
降順
"
,
"Describe"
:
"説明"
,
"Describe this model..."
:
"このモデルを説明..."
,
"Describe this vendor..."
:
"このベンダーを説明..."
,
...
...
web/default/src/i18n/locales/ru.json
View file @
dc8deb0c
...
...
@@ -1108,7 +1108,7 @@
"Deployment Region *"
:
"Регион развертывания *"
,
"Deployment requested"
:
"Развертывание запрошено"
,
"Deployments"
:
"Развертывания"
,
"Desc"
:
"
Описание
"
,
"Desc"
:
"
По убыванию
"
,
"Describe"
:
"Описание"
,
"Describe this model..."
:
"Опишите эту модель..."
,
"Describe this vendor..."
:
"Опишите этого поставщика..."
,
...
...
web/default/src/i18n/locales/vi.json
View file @
dc8deb0c
...
...
@@ -1108,7 +1108,7 @@
"Deployment Region *"
:
"Khu vực triển khai *"
,
"Deployment requested"
:
"Yêu cầu triển khai"
,
"Deployments"
:
"Triển khai"
,
"Desc"
:
"
Mô tả
"
,
"Desc"
:
"
Giảm dần
"
,
"Describe"
:
"Mô tả"
,
"Describe this model..."
:
"Mô tả mô hình này..."
,
"Describe this vendor..."
:
"Mô tả nhà cung cấp này..."
,
...
...
web/default/src/i18n/locales/zh.json
View file @
dc8deb0c
...
...
@@ -1108,7 +1108,7 @@
"Deployment Region *"
:
"部署区域 *"
,
"Deployment requested"
:
"部署已请求"
,
"Deployments"
:
"部署"
,
"Desc"
:
"
描述
"
,
"Desc"
:
"
降序
"
,
"Describe"
:
"图生文"
,
"Describe this model..."
:
"描述此模型..."
,
"Describe this vendor..."
:
"描述此供应商..."
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment