@@ -240,9 +240,73 @@
v-model = "createForm.platform"
:options = "platformOptions"
data -tour = " group -form -platform "
@change ="createForm.copy_accounts_from_group_ids = []"
/ >
< p class = "input-hint" > { { t ( 'admin.groups.platformHint' ) } } < / p >
< / div >
<!-- 从分组复制账号 -- >
< div v-if = "copyAccountsGroupOptions.length > 0" >
< div class = "mb-1.5 flex items-center gap-1" >
< label class = "text-sm font-medium text-gray-700 dark:text-gray-300" >
{ { t ( 'admin.groups.copyAccounts.title' ) } }
< / label >
< div class = "group relative inline-flex" >
< Icon
name = "questionCircle"
size = "sm"
:stroke-width = "2"
class = "cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
/ >
< div class = "pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-72 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100" >
< div class = "rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800" >
< p class = "text-xs leading-relaxed text-gray-300" >
{ { t ( 'admin.groups.copyAccounts.tooltip' ) } }
< / p >
< div class = "absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800" > < / div >
< / div >
< / div >
< / div >
< / div >
<!-- 已选分组标签 -- >
< div v-if = "createForm.copy_accounts_from_group_ids.length > 0" class="flex flex-wrap gap-1.5 mb-2" >
< span
v-for = "groupId in createForm.copy_accounts_from_group_ids"
:key = "groupId"
class = "inline-flex items-center gap-1 rounded-full bg-primary-100 px-2.5 py-1 text-xs font-medium text-primary-700 dark:bg-primary-900/30 dark:text-primary-300"
>
{ { copyAccountsGroupOptions . find ( o => o . value === groupId ) ? . label || ` # ${ groupId } ` } }
< button
type = "button"
@click ="createForm.copy_accounts_from_group_ids = createForm.copy_accounts_from_group_ids.filter(id => id !== groupId)"
class = "ml-0.5 text-primary-500 hover:text-primary-700 dark:hover:text-primary-200"
>
< Icon name = "x" size = "xs" / >
< / button >
< / span >
< / div >
<!-- 分组选择下拉 -- >
< select
class = "input"
@change ="(e) => {
const val = Number((e.target as HTMLSelectElement).value)
if (val && !createForm.copy_accounts_from_group_ids.includes(val)) {
createForm.copy_accounts_from_group_ids.push(val)
}
(e.target as HTMLSelectElement).value = ''
}"
>
< option value = "" > { { t ( 'admin.groups.copyAccounts.selectPlaceholder' ) } } < / option >
< option
v-for = "opt in copyAccountsGroupOptions"
:key = "opt.value"
:value = "opt.value"
:disabled = "createForm.copy_accounts_from_group_ids.includes(opt.value)"
>
{ { opt . label } }
< / option >
< / select >
< p class = "input-hint" > { { t ( 'admin.groups.copyAccounts.hint' ) } } < / p >
< / div >
< div >
< label class = "input-label" > { { t ( 'admin.groups.form.rateMultiplier' ) } } < / label >
< input
@@ -680,6 +744,69 @@
/ >
< p class = "input-hint" > { { t ( 'admin.groups.platformNotEditable' ) } } < / p >
< / div >
<!-- 从分组复制账号 ( 编辑时 ) -- >
< div v-if = "copyAccountsGroupOptionsForEdit.length > 0" >
< div class = "mb-1.5 flex items-center gap-1" >
< label class = "text-sm font-medium text-gray-700 dark:text-gray-300" >
{ { t ( 'admin.groups.copyAccounts.title' ) } }
< / label >
< div class = "group relative inline-flex" >
< Icon
name = "questionCircle"
size = "sm"
:stroke-width = "2"
class = "cursor-help text-gray-400 transition-colors hover:text-primary-500 dark:text-gray-500 dark:hover:text-primary-400"
/ >
< div class = "pointer-events-none absolute bottom-full left-0 z-50 mb-2 w-72 opacity-0 transition-all duration-200 group-hover:pointer-events-auto group-hover:opacity-100" >
< div class = "rounded-lg bg-gray-900 p-3 text-white shadow-lg dark:bg-gray-800" >
< p class = "text-xs leading-relaxed text-gray-300" >
{ { t ( 'admin.groups.copyAccounts.tooltipEdit' ) } }
< / p >
< div class = "absolute -bottom-1.5 left-3 h-3 w-3 rotate-45 bg-gray-900 dark:bg-gray-800" > < / div >
< / div >
< / div >
< / div >
< / div >
<!-- 已选分组标签 -- >
< div v-if = "editForm.copy_accounts_from_group_ids.length > 0" class="flex flex-wrap gap-1.5 mb-2" >
< span
v-for = "groupId in editForm.copy_accounts_from_group_ids"
:key = "groupId"
class = "inline-flex items-center gap-1 rounded-full bg-primary-100 px-2.5 py-1 text-xs font-medium text-primary-700 dark:bg-primary-900/30 dark:text-primary-300"
>
{ { copyAccountsGroupOptionsForEdit . find ( o => o . value === groupId ) ? . label || ` # ${ groupId } ` } }
< button
type = "button"
@click ="editForm.copy_accounts_from_group_ids = editForm.copy_accounts_from_group_ids.filter(id => id !== groupId)"
class = "ml-0.5 text-primary-500 hover:text-primary-700 dark:hover:text-primary-200"
>
< Icon name = "x" size = "xs" / >
< / button >
< / span >
< / div >
<!-- 分组选择下拉 -- >
< select
class = "input"
@change ="(e) => {
const val = Number((e.target as HTMLSelectElement).value)
if (val && !editForm.copy_accounts_from_group_ids.includes(val)) {
editForm.copy_accounts_from_group_ids.push(val)
}
(e.target as HTMLSelectElement).value = ''
}"
>
< option value = "" > { { t ( 'admin.groups.copyAccounts.selectPlaceholder' ) } } < / option >
< option
v-for = "opt in copyAccountsGroupOptionsForEdit"
:key = "opt.value"
:value = "opt.value"
:disabled = "editForm.copy_accounts_from_group_ids.includes(opt.value)"
>
{ { opt . label } }
< / option >
< / select >
< p class = "input-hint" > { { t ( 'admin.groups.copyAccounts.hintEdit' ) } } < / p >
< / div >
< div >
< label class = "input-label" > { { t ( 'admin.groups.form.rateMultiplier' ) } } < / label >
< input
@@ -1202,6 +1329,29 @@ const fallbackGroupOptionsForEdit = computed(() => {
return options
} )
// 复制账号的源分组选项(创建时)- 仅包含相同平台且有账号的分组
const copyAccountsGroupOptions = computed ( ( ) => {
const eligibleGroups = groups . value . filter (
( g ) => g . platform === createForm . platform && ( g . account _count || 0 ) > 0
)
return eligibleGroups . map ( ( g ) => ( {
value : g . id ,
label : ` ${ g . name } ( ${ g . account _count || 0 } 个账号) `
} ) )
} )
// 复制账号的源分组选项(编辑时)- 仅包含相同平台且有账号的分组,排除自身
const copyAccountsGroupOptionsForEdit = computed ( ( ) => {
const currentId = editingGroup . value ? . id
const eligibleGroups = groups . value . filter (
( g ) => g . platform === editForm . platform && ( g . account _count || 0 ) > 0 && g . id !== currentId
)
return eligibleGroups . map ( ( g ) => ( {
value : g . id ,
label : ` ${ g . name } ( ${ g . account _count || 0 } 个账号) `
} ) )
} )
const groups = ref < AdminGroup [ ] > ( [ ] )
const loading = ref ( false )
const searchQuery = ref ( '' )
@@ -1244,7 +1394,9 @@ const createForm = reactive({
claude _code _only : false ,
fallback _group _id : null as number | null ,
// 模型路由开关
model _routing _enabled : false
model _routing _enabled : false ,
// 从分组复制账号
copy _accounts _from _group _ids : [ ] as number [ ]
} )
// 简单账号类型(用于模型路由选择)
@@ -1415,7 +1567,9 @@ const editForm = reactive({
claude _code _only : false ,
fallback _group _id : null as number | null ,
// 模型路由开关
model _routing _enabled : false
model _routing _enabled : false ,
// 从分组复制账号
copy _accounts _from _group _ids : [ ] as number [ ]
} )
// 根据分组类型返回不同的删除确认消息
@@ -1497,6 +1651,7 @@ const closeCreateModal = () => {
createForm . image _price _4k = null
createForm . claude _code _only = false
createForm . fallback _group _id = null
createForm . copy _accounts _from _group _ids = [ ]
createModelRoutingRules . value = [ ]
}
@@ -1547,6 +1702,7 @@ const handleEdit = async (group: AdminGroup) => {
editForm . claude _code _only = group . claude _code _only || false
editForm . fallback _group _id = group . fallback _group _id
editForm . model _routing _enabled = group . model _routing _enabled || false
editForm . copy _accounts _from _group _ids = [ ] // 复制账号字段每次编辑时重置为空
// 加载模型路由规则(异步加载账号名称)
editModelRoutingRules . value = await convertApiFormatToRoutingRules ( group . model _routing )
showEditModal . value = true
@@ -1556,6 +1712,7 @@ const closeEditModal = () => {
showEditModal . value = false
editingGroup . value = null
editModelRoutingRules . value = [ ]
editForm . copy _accounts _from _group _ids = [ ]
}
const handleUpdateGroup = async ( ) => {