未设置排序的记录 sort_order=999999 自然排在后面,无需 CASE WHEN。 - GORM 默认值 default:0 → default:999999 - 数据库迁移:将现有 sort_order=0 的记录更新为 999999 - 回退 CASE WHEN 排序逻辑,恢复简单的 sort_order ASC, id ASC - 前端编辑弹窗默认值同步改为 999999 - 表格列中 999999 显示为空(表示未设置) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>master
| @@ -298,9 +298,12 @@ func migrateDB() error { | |||||
| if err := DB.AutoMigrate(&SubscriptionPlan{}); err != nil { | if err := DB.AutoMigrate(&SubscriptionPlan{}); err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| } | |||||
| // 将现有 sort_order=0 的模型和供应商更新为默认大数 | |||||
| DB.Model(&Model{}).Where("sort_order = 0").Update("sort_order", 999999) | |||||
| DB.Model(&Vendor{}).Where("sort_order = 0").Update("sort_order", 999999) | |||||
| return nil | |||||
| } | } | ||||
| return nil | |||||
| } | |||||
| func migrateDBFast() error { | func migrateDBFast() error { | ||||
| // Drop bound_channel_id column from tokens table (deprecated field) | // Drop bound_channel_id column from tokens table (deprecated field) | ||||
| @@ -42,7 +42,7 @@ type Model struct { | |||||
| Endpoints string `json:"endpoints,omitempty" gorm:"type:text"` | Endpoints string `json:"endpoints,omitempty" gorm:"type:text"` | ||||
| Status int `json:"status" gorm:"default:1"` | Status int `json:"status" gorm:"default:1"` | ||||
| SyncOfficial int `json:"sync_official" gorm:"default:1"` | SyncOfficial int `json:"sync_official" gorm:"default:1"` | ||||
| SortOrder int `json:"sort_order" gorm:"default:0"` | |||||
| SortOrder int `json:"sort_order" gorm:"default:999999"` | |||||
| CreatedTime int64 `json:"created_time" gorm:"bigint"` | CreatedTime int64 `json:"created_time" gorm:"bigint"` | ||||
| UpdatedTime int64 `json:"updated_time" gorm:"bigint"` | UpdatedTime int64 `json:"updated_time" gorm:"bigint"` | ||||
| DeletedAt gorm.DeletedAt `json:"-" gorm:"index;uniqueIndex:uk_model_name_delete_at,priority:2"` | DeletedAt gorm.DeletedAt `json:"-" gorm:"index;uniqueIndex:uk_model_name_delete_at,priority:2"` | ||||
| @@ -118,7 +118,7 @@ func GetVendorModelCounts() (map[int64]int64, error) { | |||||
| func GetAllModels(offset int, limit int) ([]*Model, error) { | func GetAllModels(offset int, limit int) ([]*Model, error) { | ||||
| var models []*Model | var models []*Model | ||||
| err := DB.Order("CASE WHEN sort_order = 0 THEN 1 ELSE 0 END ASC, sort_order ASC, id ASC").Offset(offset).Limit(limit).Find(&models).Error | |||||
| err := DB.Order("sort_order ASC, id ASC").Offset(offset).Limit(limit).Find(&models).Error | |||||
| return models, err | return models, err | ||||
| } | } | ||||
| @@ -166,7 +166,7 @@ func SearchModels(keyword string, vendor string, offset int, limit int) ([]*Mode | |||||
| if err := db.Count(&total).Error; err != nil { | if err := db.Count(&total).Error; err != nil { | ||||
| return nil, 0, err | return nil, 0, err | ||||
| } | } | ||||
| if err := db.Order("CASE WHEN sort_order = 0 THEN 1 ELSE 0 END ASC, sort_order ASC, models.id ASC").Offset(offset).Limit(limit).Find(&models).Error; err != nil { | |||||
| if err := db.Order("sort_order ASC, models.id ASC").Offset(offset).Limit(limit).Find(&models).Error; err != nil { | |||||
| return nil, 0, err | return nil, 0, err | ||||
| } | } | ||||
| return models, total, nil | return models, total, nil | ||||
| @@ -183,16 +183,6 @@ func updatePricing() { | |||||
| sort.Slice(vendorsList, func(i, j int) bool { | sort.Slice(vendorsList, func(i, j int) bool { | ||||
| oi := vendorOrderMap[vendorsList[i].ID] | oi := vendorOrderMap[vendorsList[i].ID] | ||||
| oj := vendorOrderMap[vendorsList[j].ID] | oj := vendorOrderMap[vendorsList[j].ID] | ||||
| // 0 排到最后 | |||||
| if oi == 0 && oj == 0 { | |||||
| return vendorsList[i].ID < vendorsList[j].ID | |||||
| } | |||||
| if oi == 0 { | |||||
| return false | |||||
| } | |||||
| if oj == 0 { | |||||
| return true | |||||
| } | |||||
| if oi != oj { | if oi != oj { | ||||
| return oi < oj | return oi < oj | ||||
| } | } | ||||
| @@ -346,23 +336,13 @@ func updatePricing() { | |||||
| pricingMap = append(pricingMap, pricing) | pricingMap = append(pricingMap, pricing) | ||||
| } | } | ||||
| // 按 sort_order 排序 pricingMap,0 排到最后 | |||||
| // 按 sort_order 排序 pricingMap | |||||
| sort.Slice(pricingMap, func(i, j int) bool { | sort.Slice(pricingMap, func(i, j int) bool { | ||||
| mi, okI := metaMap[pricingMap[i].ModelName] | mi, okI := metaMap[pricingMap[i].ModelName] | ||||
| mj, okJ := metaMap[pricingMap[j].ModelName] | mj, okJ := metaMap[pricingMap[j].ModelName] | ||||
| if okI && okJ { | if okI && okJ { | ||||
| si, sj := mi.SortOrder, mj.SortOrder | |||||
| if si == 0 && sj == 0 { | |||||
| return pricingMap[i].ModelName < pricingMap[j].ModelName | |||||
| } | |||||
| if si == 0 { | |||||
| return false | |||||
| } | |||||
| if sj == 0 { | |||||
| return true | |||||
| } | |||||
| if si != sj { | |||||
| return si < sj | |||||
| if mi.SortOrder != mj.SortOrder { | |||||
| return mi.SortOrder < mj.SortOrder | |||||
| } | } | ||||
| } else if okI { | } else if okI { | ||||
| return true | return true | ||||
| @@ -18,7 +18,7 @@ type Vendor struct { | |||||
| Description string `json:"description,omitempty" gorm:"type:text"` | Description string `json:"description,omitempty" gorm:"type:text"` | ||||
| Icon string `json:"icon,omitempty" gorm:"type:varchar(128)"` | Icon string `json:"icon,omitempty" gorm:"type:varchar(128)"` | ||||
| Status int `json:"status" gorm:"default:1"` | Status int `json:"status" gorm:"default:1"` | ||||
| SortOrder int `json:"sort_order" gorm:"default:0"` | |||||
| SortOrder int `json:"sort_order" gorm:"default:999999"` | |||||
| CreatedTime int64 `json:"created_time" gorm:"bigint"` | CreatedTime int64 `json:"created_time" gorm:"bigint"` | ||||
| UpdatedTime int64 `json:"updated_time" gorm:"bigint"` | UpdatedTime int64 `json:"updated_time" gorm:"bigint"` | ||||
| DeletedAt gorm.DeletedAt `json:"-" gorm:"index;uniqueIndex:uk_vendor_name_delete_at,priority:2"` | DeletedAt gorm.DeletedAt `json:"-" gorm:"index;uniqueIndex:uk_vendor_name_delete_at,priority:2"` | ||||
| @@ -66,7 +66,7 @@ func GetVendorByID(id int) (*Vendor, error) { | |||||
| // GetAllVendors 获取全部供应商(分页) | // GetAllVendors 获取全部供应商(分页) | ||||
| func GetAllVendors(offset int, limit int) ([]*Vendor, error) { | func GetAllVendors(offset int, limit int) ([]*Vendor, error) { | ||||
| var vendors []*Vendor | var vendors []*Vendor | ||||
| err := DB.Order("CASE WHEN sort_order = 0 THEN 1 ELSE 0 END ASC, sort_order ASC, id ASC").Offset(offset).Limit(limit).Find(&vendors).Error | |||||
| err := DB.Order("sort_order ASC, id ASC").Offset(offset).Limit(limit).Find(&vendors).Error | |||||
| return vendors, err | return vendors, err | ||||
| } | } | ||||
| @@ -82,7 +82,7 @@ func SearchVendors(keyword string, offset int, limit int) ([]*Vendor, int64, err | |||||
| return nil, 0, err | return nil, 0, err | ||||
| } | } | ||||
| var vendors []*Vendor | var vendors []*Vendor | ||||
| if err := db.Order("CASE WHEN sort_order = 0 THEN 1 ELSE 0 END ASC, sort_order ASC, id ASC").Offset(offset).Limit(limit).Find(&vendors).Error; err != nil { | |||||
| if err := db.Order("sort_order ASC, id ASC").Offset(offset).Limit(limit).Find(&vendors).Error; err != nil { | |||||
| return nil, 0, err | return nil, 0, err | ||||
| } | } | ||||
| return vendors, total, nil | return vendors, total, nil | ||||
| @@ -286,6 +286,7 @@ export const getModelsColumns = ({ | |||||
| title: t('排序'), | title: t('排序'), | ||||
| dataIndex: 'sort_order', | dataIndex: 'sort_order', | ||||
| width: 60, | width: 60, | ||||
| render: (val) => (val != null && val < 999999 ? val : ''), | |||||
| }, | }, | ||||
| { | { | ||||
| title: t('图标'), | title: t('图标'), | ||||
| @@ -126,7 +126,7 @@ const EditModelModal = (props) => { | |||||
| name_rule: props.editingModel?.model_name ? 0 : undefined, // 通过未配置模型过来的固定为精确匹配 | name_rule: props.editingModel?.model_name ? 0 : undefined, // 通过未配置模型过来的固定为精确匹配 | ||||
| status: true, | status: true, | ||||
| sync_official: true, | sync_official: true, | ||||
| sort_order: 0, | |||||
| sort_order: 999999, | |||||
| }); | }); | ||||
| const handleCancel = () => { | const handleCancel = () => { | ||||
| @@ -38,7 +38,7 @@ const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => { | |||||
| description: '', | description: '', | ||||
| icon: '', | icon: '', | ||||
| status: true, | status: true, | ||||
| sort_order: 0, | |||||
| sort_order: 999999, | |||||
| }); | }); | ||||
| const handleCancel = () => { | const handleCancel = () => { | ||||