fix: enable channel table server-side sorting (#4600)
This commit is contained in:
parent
f8cf9c57c4
commit
dc8deb0c24
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
14
web/default/src/features/channels/types.ts
vendored
14
web/default/src/features/channels/types.ts
vendored
@ -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
|
||||
}
|
||||
|
||||
2
web/default/src/i18n/locales/fr.json
vendored
2
web/default/src/i18n/locales/fr.json
vendored
@ -1108,7 +1108,7 @@
|
||||
"Deployment Region *": "Région de déploiement *",
|
||||
"Deployment requested": "Déploiement demandé",
|
||||
"Deployments": "Déploiements",
|
||||
"Desc": "Description",
|
||||
"Desc": "Desc.",
|
||||
"Describe": "Décrire",
|
||||
"Describe this model...": "Décrire ce modèle...",
|
||||
"Describe this vendor...": "Décrire ce fournisseur...",
|
||||
|
||||
2
web/default/src/i18n/locales/ja.json
vendored
2
web/default/src/i18n/locales/ja.json
vendored
@ -1108,7 +1108,7 @@
|
||||
"Deployment Region *": "デプロイリージョン *",
|
||||
"Deployment requested": "デプロイメントが要求されました",
|
||||
"Deployments": "デプロイ",
|
||||
"Desc": "説明",
|
||||
"Desc": "降順",
|
||||
"Describe": "説明",
|
||||
"Describe this model...": "このモデルを説明...",
|
||||
"Describe this vendor...": "このベンダーを説明...",
|
||||
|
||||
2
web/default/src/i18n/locales/ru.json
vendored
2
web/default/src/i18n/locales/ru.json
vendored
@ -1108,7 +1108,7 @@
|
||||
"Deployment Region *": "Регион развертывания *",
|
||||
"Deployment requested": "Развертывание запрошено",
|
||||
"Deployments": "Развертывания",
|
||||
"Desc": "Описание",
|
||||
"Desc": "По убыванию",
|
||||
"Describe": "Описание",
|
||||
"Describe this model...": "Опишите эту модель...",
|
||||
"Describe this vendor...": "Опишите этого поставщика...",
|
||||
|
||||
2
web/default/src/i18n/locales/vi.json
vendored
2
web/default/src/i18n/locales/vi.json
vendored
@ -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...",
|
||||
|
||||
2
web/default/src/i18n/locales/zh.json
vendored
2
web/default/src/i18n/locales/zh.json
vendored
@ -1108,7 +1108,7 @@
|
||||
"Deployment Region *": "部署区域 *",
|
||||
"Deployment requested": "部署已请求",
|
||||
"Deployments": "部署",
|
||||
"Desc": "描述",
|
||||
"Desc": "降序",
|
||||
"Describe": "图生文",
|
||||
"Describe this model...": "描述此模型...",
|
||||
"Describe this vendor...": "描述此供应商...",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user