Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
5fa93ebd
Unverified
Commit
5fa93ebd
authored
Feb 08, 2026
by
Wesley Liddick
Committed by
GitHub
Feb 08, 2026
Browse files
Merge pull request #519 from bayma888/feature/group-sort-order
feat(admin): 新增-分组管理自由拖拽排序功能
parents
e4d74ae1
8aa0aed5
Changes
35
Hide whitespace changes
Inline
Side-by-side
backend/internal/service/admin_service_delete_test.go
View file @
5fa93ebd
...
...
@@ -172,6 +172,10 @@ func (s *groupRepoStub) GetAccountIDsByGroupIDs(ctx context.Context, groupIDs []
panic
(
"unexpected GetAccountIDsByGroupIDs call"
)
}
func
(
s
*
groupRepoStub
)
UpdateSortOrders
(
ctx
context
.
Context
,
updates
[]
GroupSortOrderUpdate
)
error
{
return
nil
}
type
proxyRepoStub
struct
{
deleteErr
error
countErr
error
...
...
backend/internal/service/admin_service_group_test.go
View file @
5fa93ebd
...
...
@@ -116,6 +116,10 @@ func (s *groupRepoStubForAdmin) GetAccountIDsByGroupIDs(_ context.Context, _ []i
panic
(
"unexpected GetAccountIDsByGroupIDs call"
)
}
func
(
s
*
groupRepoStubForAdmin
)
UpdateSortOrders
(
_
context
.
Context
,
_
[]
GroupSortOrderUpdate
)
error
{
return
nil
}
// TestAdminService_CreateGroup_WithImagePricing 测试创建分组时 ImagePrice 字段正确传递
func
TestAdminService_CreateGroup_WithImagePricing
(
t
*
testing
.
T
)
{
repo
:=
&
groupRepoStubForAdmin
{}
...
...
@@ -395,6 +399,10 @@ func (s *groupRepoStubForFallbackCycle) GetAccountIDsByGroupIDs(_ context.Contex
panic
(
"unexpected GetAccountIDsByGroupIDs call"
)
}
func
(
s
*
groupRepoStubForFallbackCycle
)
UpdateSortOrders
(
_
context
.
Context
,
_
[]
GroupSortOrderUpdate
)
error
{
return
nil
}
type
groupRepoStubForInvalidRequestFallback
struct
{
groups
map
[
int64
]
*
Group
created
*
Group
...
...
@@ -466,6 +474,10 @@ func (s *groupRepoStubForInvalidRequestFallback) BindAccountsToGroup(_ context.C
panic
(
"unexpected BindAccountsToGroup call"
)
}
func
(
s
*
groupRepoStubForInvalidRequestFallback
)
UpdateSortOrders
(
_
context
.
Context
,
_
[]
GroupSortOrderUpdate
)
error
{
return
nil
}
func
TestAdminService_CreateGroup_InvalidRequestFallbackRejectsUnsupportedPlatform
(
t
*
testing
.
T
)
{
fallbackID
:=
int64
(
10
)
repo
:=
&
groupRepoStubForInvalidRequestFallback
{
...
...
backend/internal/service/gateway_multiplatform_test.go
View file @
5fa93ebd
...
...
@@ -298,6 +298,10 @@ func (m *mockGroupRepoForGateway) GetAccountIDsByGroupIDs(ctx context.Context, g
return
nil
,
nil
}
func
(
m
*
mockGroupRepoForGateway
)
UpdateSortOrders
(
ctx
context
.
Context
,
updates
[]
GroupSortOrderUpdate
)
error
{
return
nil
}
func
ptr
[
T
any
](
v
T
)
*
T
{
return
&
v
}
...
...
backend/internal/service/gemini_multiplatform_test.go
View file @
5fa93ebd
...
...
@@ -226,6 +226,10 @@ func (m *mockGroupRepoForGemini) GetAccountIDsByGroupIDs(ctx context.Context, gr
return
nil
,
nil
}
func
(
m
*
mockGroupRepoForGemini
)
UpdateSortOrders
(
ctx
context
.
Context
,
updates
[]
GroupSortOrderUpdate
)
error
{
return
nil
}
var
_
GroupRepository
=
(
*
mockGroupRepoForGemini
)(
nil
)
// mockGatewayCacheForGemini Gemini 测试用的 cache mock
...
...
backend/internal/service/group.go
View file @
5fa93ebd
...
...
@@ -45,6 +45,9 @@ type Group struct {
// 可选值: claude, gemini_text, gemini_image
SupportedModelScopes
[]
string
// 分组排序
SortOrder
int
CreatedAt
time
.
Time
UpdatedAt
time
.
Time
...
...
backend/internal/service/group_service.go
View file @
5fa93ebd
...
...
@@ -33,6 +33,14 @@ type GroupRepository interface {
GetAccountIDsByGroupIDs
(
ctx
context
.
Context
,
groupIDs
[]
int64
)
([]
int64
,
error
)
// BindAccountsToGroup 将多个账号绑定到指定分组
BindAccountsToGroup
(
ctx
context
.
Context
,
groupID
int64
,
accountIDs
[]
int64
)
error
// UpdateSortOrders 批量更新分组排序
UpdateSortOrders
(
ctx
context
.
Context
,
updates
[]
GroupSortOrderUpdate
)
error
}
// GroupSortOrderUpdate 分组排序更新
type
GroupSortOrderUpdate
struct
{
ID
int64
`json:"id"`
SortOrder
int
`json:"sort_order"`
}
// CreateGroupRequest 创建分组请求
...
...
backend/migrations/052_add_group_sort_order.sql
0 → 100644
View file @
5fa93ebd
-- Add sort_order field to groups table for custom ordering
ALTER
TABLE
groups
ADD
COLUMN
IF
NOT
EXISTS
sort_order
INT
NOT
NULL
DEFAULT
0
;
-- Initialize existing groups with sort_order based on their ID
UPDATE
groups
SET
sort_order
=
id
WHERE
sort_order
=
0
;
-- Create index for efficient sorting
CREATE
INDEX
IF
NOT
EXISTS
idx_groups_sort_order
ON
groups
(
sort_order
);
frontend/package.json
View file @
5fa93ebd
...
...
@@ -27,6 +27,7 @@
"qrcode"
:
"^1.5.4"
,
"vue"
:
"^3.4.0"
,
"vue-chartjs"
:
"^5.3.0"
,
"vue-draggable-plus"
:
"^0.6.1"
,
"vue-i18n"
:
"^9.14.5"
,
"vue-router"
:
"^4.2.5"
,
"xlsx"
:
"^0.18.5"
...
...
frontend/pnpm-lock.yaml
View file @
5fa93ebd
...
...
@@ -44,6 +44,9 @@ importers:
vue-chartjs
:
specifier
:
^5.3.0
version
:
5.3.3(chart.js@4.5.1)(vue@3.5.26(typescript@5.6.3))
vue-draggable-plus
:
specifier
:
^0.6.1
version
:
0.6.1(@types/sortablejs@1.15.9)
vue-i18n
:
specifier
:
^9.14.5
version
:
9.14.5(vue@3.5.26(typescript@5.6.3))
...
...
@@ -1254,67 +1257,56 @@ packages:
resolution
:
{
integrity
:
sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==
}
cpu
:
[
arm
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-arm-musleabihf@4.54.0'
:
resolution
:
{
integrity
:
sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==
}
cpu
:
[
arm
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rollup/rollup-linux-arm64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==
}
cpu
:
[
arm64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-arm64-musl@4.54.0'
:
resolution
:
{
integrity
:
sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==
}
cpu
:
[
arm64
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rollup/rollup-linux-loong64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==
}
cpu
:
[
loong64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-ppc64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==
}
cpu
:
[
ppc64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-riscv64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==
}
cpu
:
[
riscv64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-riscv64-musl@4.54.0'
:
resolution
:
{
integrity
:
sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==
}
cpu
:
[
riscv64
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rollup/rollup-linux-s390x-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==
}
cpu
:
[
s390x
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-x64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==
}
cpu
:
[
x64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-x64-musl@4.54.0'
:
resolution
:
{
integrity
:
sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==
}
cpu
:
[
x64
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rollup/rollup-openharmony-arm64@4.54.0'
:
resolution
:
{
integrity
:
sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==
}
...
...
@@ -1515,6 +1507,9 @@ packages:
'
@types/react@19.2.7'
:
resolution
:
{
integrity
:
sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==
}
'
@types/sortablejs@1.15.9'
:
resolution
:
{
integrity
:
sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==
}
'
@types/trusted-types@2.0.7'
:
resolution
:
{
integrity
:
sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
}
...
...
@@ -4298,6 +4293,15 @@ packages:
'
@vue/composition-api'
:
optional
:
true
vue-draggable-plus@0.6.1
:
resolution
:
{
integrity
:
sha512-FbtQ/fuoixiOfTZzG3yoPl4JAo9HJXRHmBQZFB9x2NYCh6pq0TomHf7g5MUmpaDYv+LU2n6BPq2YN9sBO+FbIg==
}
peerDependencies
:
'
@types/sortablejs'
:
^1.15.0
'@vue/composition-api'
:
'
*'
peerDependenciesMeta
:
'
@vue/composition-api'
:
optional
:
true
vue-eslint-parser@9.4.3
:
resolution
:
{
integrity
:
sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==
}
engines
:
{
node
:
^14.17.0 || >=16.0.0
}
...
...
@@ -5958,6 +5962,8 @@ snapshots:
dependencies
:
csstype
:
3.2.3
'
@types/sortablejs@1.15.9'
:
{}
'
@types/trusted-types@2.0.7'
:
{}
'
@types/unist@2.0.11'
:
{}
...
...
@@ -9401,6 +9407,10 @@ snapshots:
dependencies
:
vue
:
3.5.26(typescript@5.6.3)
vue-draggable-plus@0.6.1(@types/sortablejs@1.15.9)
:
dependencies
:
'
@types/sortablejs'
:
1.15.9
vue-eslint-parser@9.4.3(eslint@8.57.1)
:
dependencies
:
debug
:
4.4.3
...
...
frontend/src/api/admin/groups.ts
View file @
5fa93ebd
...
...
@@ -153,6 +153,20 @@ export async function getGroupApiKeys(
return
data
}
/**
* Update group sort orders
* @param updates - Array of { id, sort_order } objects
* @returns Success confirmation
*/
export
async
function
updateSortOrder
(
updates
:
Array
<
{
id
:
number
;
sort_order
:
number
}
>
):
Promise
<
{
message
:
string
}
>
{
const
{
data
}
=
await
apiClient
.
put
<
{
message
:
string
}
>
(
'
/admin/groups/sort-order
'
,
{
updates
})
return
data
}
export
const
groupsAPI
=
{
list
,
getAll
,
...
...
@@ -163,7 +177,8 @@ export const groupsAPI = {
delete
:
deleteGroup
,
toggleStatus
,
getStats
,
getGroupApiKeys
getGroupApiKeys
,
updateSortOrder
}
export
default
groupsAPI
frontend/src/components/icons/Icon.vue
View file @
5fa93ebd
...
...
@@ -58,6 +58,7 @@ const icons = {
arrowLeft
:
'
M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18
'
,
arrowUp
:
'
M5 10l7-7m0 0l7 7m-7-7v18
'
,
arrowDown
:
'
M19 14l-7 7m0 0l-7-7m7 7V3
'
,
arrowsUpDown
:
'
M3 7.5L7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5
'
,
chevronUp
:
'
M5 15l7-7 7 7
'
,
externalLink
:
'
M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14
'
,
...
...
frontend/src/i18n/locales/en.ts
View file @
5fa93ebd
...
...
@@ -1042,6 +1042,10 @@ export default {
createGroup
:
'
Create Group
'
,
editGroup
:
'
Edit Group
'
,
deleteGroup
:
'
Delete Group
'
,
sortOrder
:
'
Sort
'
,
sortOrderHint
:
'
Drag groups to adjust display order, groups at the top will be displayed first
'
,
sortOrderUpdated
:
'
Sort order updated
'
,
failedToUpdateSortOrder
:
'
Failed to update sort order
'
,
allPlatforms
:
'
All Platforms
'
,
allStatus
:
'
All Status
'
,
allGroups
:
'
All Groups
'
,
...
...
frontend/src/i18n/locales/zh.ts
View file @
5fa93ebd
...
...
@@ -1099,6 +1099,10 @@ export default {
createGroup
:
'
创建分组
'
,
editGroup
:
'
编辑分组
'
,
deleteGroup
:
'
删除分组
'
,
sortOrder
:
'
排序
'
,
sortOrderHint
:
'
拖拽分组调整显示顺序,排在前面的分组会优先显示
'
,
sortOrderUpdated
:
'
排序已更新
'
,
failedToUpdateSortOrder
:
'
更新排序失败
'
,
deleteConfirm
:
"
确定要删除分组 '{name}' 吗?所有关联的 API 密钥将不再属于任何分组。
"
,
deleteConfirmSubscription
:
"
确定要删除订阅分组 '{name}' 吗?此操作会让所有绑定此订阅的用户的 API Key 失效,并删除所有相关的订阅记录。此操作无法撤销。
"
,
...
...
frontend/src/types/index.ts
View file @
5fa93ebd
...
...
@@ -379,6 +379,9 @@ export interface AdminGroup extends Group {
// 分组下账号数量(仅管理员可见)
account_count
?:
number
// 分组排序
sort_order
:
number
}
export
interface
ApiKey
{
...
...
frontend/src/views/admin/GroupsView.vue
View file @
5fa93ebd
...
...
@@ -52,6 +52,14 @@
>
<Icon
name=
"refresh"
size=
"md"
:class=
"loading ? 'animate-spin' : ''"
/>
</button>
<button
@
click=
"openSortModal"
class=
"btn btn-secondary"
:title=
"t('admin.groups.sortOrder')"
>
<Icon
name=
"arrowsUpDown"
size=
"md"
class=
"mr-2"
/>
{{
t
(
'
admin.groups.sortOrder
'
)
}}
</button>
<button
@
click=
"showCreateModal = true"
class=
"btn btn-primary"
...
...
@@ -1455,6 +1463,92 @@
@
confirm
=
"
confirmDelete
"
@
cancel
=
"
showDeleteDialog = false
"
/>
<!--
Sort
Order
Modal
-->
<
BaseDialog
:
show
=
"
showSortModal
"
:
title
=
"
t('admin.groups.sortOrder')
"
width
=
"
normal
"
@
close
=
"
closeSortModal
"
>
<
div
class
=
"
space-y-4
"
>
<
p
class
=
"
text-sm text-gray-500 dark:text-gray-400
"
>
{{
t
(
'
admin.groups.sortOrderHint
'
)
}}
<
/p
>
<
VueDraggable
v
-
model
=
"
sortableGroups
"
:
animation
=
"
200
"
class
=
"
space-y-2
"
>
<
div
v
-
for
=
"
group in sortableGroups
"
:
key
=
"
group.id
"
class
=
"
flex cursor-grab items-center gap-3 rounded-lg border border-gray-200 bg-white p-3 transition-shadow hover:shadow-md active:cursor-grabbing dark:border-dark-600 dark:bg-dark-700
"
>
<
div
class
=
"
text-gray-400
"
>
<
Icon
name
=
"
menu
"
size
=
"
md
"
/>
<
/div
>
<
div
class
=
"
flex-1
"
>
<
div
class
=
"
font-medium text-gray-900 dark:text-white
"
>
{{
group
.
name
}}
<
/div
>
<
div
class
=
"
text-xs text-gray-500 dark:text-gray-400
"
>
<
span
:
class
=
"
[
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',
group.platform === 'anthropic'
? 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
: group.platform === 'openai'
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400'
: group.platform === 'antigravity'
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
]
"
>
{{
t
(
'
admin.groups.platforms.
'
+
group
.
platform
)
}}
<
/span
>
<
/div
>
<
/div
>
<
div
class
=
"
text-sm text-gray-400
"
>
#
{{
group
.
id
}}
<
/div
>
<
/div
>
<
/VueDraggable
>
<
/div
>
<
template
#
footer
>
<
div
class
=
"
flex justify-end gap-3 pt-4
"
>
<
button
@
click
=
"
closeSortModal
"
type
=
"
button
"
class
=
"
btn btn-secondary
"
>
{{
t
(
'
common.cancel
'
)
}}
<
/button
>
<
button
@
click
=
"
saveSortOrder
"
:
disabled
=
"
sortSubmitting
"
class
=
"
btn btn-primary
"
>
<
svg
v
-
if
=
"
sortSubmitting
"
class
=
"
-ml-1 mr-2 h-4 w-4 animate-spin
"
fill
=
"
none
"
viewBox
=
"
0 0 24 24
"
>
<
circle
class
=
"
opacity-25
"
cx
=
"
12
"
cy
=
"
12
"
r
=
"
10
"
stroke
=
"
currentColor
"
stroke
-
width
=
"
4
"
><
/circle
>
<
path
class
=
"
opacity-75
"
fill
=
"
currentColor
"
d
=
"
M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z
"
><
/path
>
<
/svg
>
{{
sortSubmitting
?
t
(
'
common.saving
'
)
:
t
(
'
common.save
'
)
}}
<
/button
>
<
/div
>
<
/template
>
<
/BaseDialog
>
<
/AppLayout
>
<
/template
>
...
...
@@ -1476,6 +1570,7 @@ import EmptyState from '@/components/common/EmptyState.vue'
import
Select
from
'
@/components/common/Select.vue
'
import
PlatformIcon
from
'
@/components/common/PlatformIcon.vue
'
import
Icon
from
'
@/components/icons/Icon.vue
'
import
{
VueDraggable
}
from
'
vue-draggable-plus
'
const
{
t
}
=
useI18n
()
const
appStore
=
useAppStore
()
...
...
@@ -1640,9 +1735,12 @@ let abortController: AbortController | null = null
const
showCreateModal
=
ref
(
false
)
const
showEditModal
=
ref
(
false
)
const
showDeleteDialog
=
ref
(
false
)
const
showSortModal
=
ref
(
false
)
const
submitting
=
ref
(
false
)
const
sortSubmitting
=
ref
(
false
)
const
editingGroup
=
ref
<
AdminGroup
|
null
>
(
null
)
const
deletingGroup
=
ref
<
AdminGroup
|
null
>
(
null
)
const
sortableGroups
=
ref
<
AdminGroup
[]
>
([])
const
createForm
=
reactive
({
name
:
''
,
...
...
@@ -2101,6 +2199,46 @@ const handleClickOutside = (event: MouseEvent) => {
}
}
// 打开排序弹窗
const
openSortModal
=
async
()
=>
{
try
{
// 获取所有分组(不分页)
const
allGroups
=
await
adminAPI
.
groups
.
getAll
()
// 按 sort_order 排序
sortableGroups
.
value
=
[...
allGroups
].
sort
((
a
,
b
)
=>
a
.
sort_order
-
b
.
sort_order
)
showSortModal
.
value
=
true
}
catch
(
error
)
{
appStore
.
showError
(
t
(
'
admin.groups.failedToLoad
'
))
console
.
error
(
'
Error loading groups for sorting:
'
,
error
)
}
}
// 关闭排序弹窗
const
closeSortModal
=
()
=>
{
showSortModal
.
value
=
false
sortableGroups
.
value
=
[]
}
// 保存排序
const
saveSortOrder
=
async
()
=>
{
sortSubmitting
.
value
=
true
try
{
const
updates
=
sortableGroups
.
value
.
map
((
g
,
index
)
=>
({
id
:
g
.
id
,
sort_order
:
index
*
10
}
))
await
adminAPI
.
groups
.
updateSortOrder
(
updates
)
appStore
.
showSuccess
(
t
(
'
admin.groups.sortOrderUpdated
'
))
closeSortModal
()
loadGroups
()
}
catch
(
error
:
any
)
{
appStore
.
showError
(
error
.
response
?.
data
?.
detail
||
t
(
'
admin.groups.failedToUpdateSortOrder
'
))
console
.
error
(
'
Error updating sort order:
'
,
error
)
}
finally
{
sortSubmitting
.
value
=
false
}
}
onMounted
(()
=>
{
loadGroups
()
document
.
addEventListener
(
'
click
'
,
handleClickOutside
)
...
...
Prev
1
2
Next
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