Files
xinghuoapi/frontend/src/views/admin/GroupsView.vue
IanShaw027 7a0de1765f refactor(frontend): 优化管理后台视图
- 改进账户管理视图
- 优化分组管理界面
- 完善代理管理功能
- 增强兑换码管理
- 改进订阅管理视图
- 优化使用统计展示
- 完善用户管理界面
2025-12-27 16:05:16 +08:00

786 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<AppLayout>
<TablePageLayout>
<template #actions>
<div class="flex justify-end gap-3">
<button
@click="loadGroups"
:disabled="loading"
class="btn btn-secondary"
:title="t('common.refresh')"
>
<svg
:class="['h-5 w-5', loading ? 'animate-spin' : '']"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="1.5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</button>
<button @click="showCreateModal = true" class="btn btn-primary">
<svg
class="mr-2 h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="1.5"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
{{ t('admin.groups.createGroup') }}
</button>
</div>
</template>
<template #filters>
<div class="flex flex-wrap gap-3">
<Select
v-model="filters.platform"
:options="platformFilterOptions"
:placeholder="t('admin.groups.allPlatforms')"
class="w-44"
@change="loadGroups"
/>
<Select
v-model="filters.status"
:options="statusOptions"
:placeholder="t('admin.groups.allStatus')"
class="w-40"
@change="loadGroups"
/>
<Select
v-model="filters.is_exclusive"
:options="exclusiveOptions"
:placeholder="t('admin.groups.allGroups')"
class="w-44"
@change="loadGroups"
/>
</div>
</template>
<template #table>
<DataTable :columns="columns" :data="groups" :loading="loading">
<template #cell-name="{ value }">
<span class="font-medium text-gray-900 dark:text-white">{{ value }}</span>
</template>
<template #cell-platform="{ value }">
<span
:class="[
'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',
value === 'anthropic'
? 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
: value === 'openai'
? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400'
: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
]"
>
<PlatformIcon :platform="value" size="xs" />
{{ value === 'anthropic' ? 'Anthropic' : value === 'openai' ? 'OpenAI' : 'Gemini' }}
</span>
</template>
<template #cell-billing_type="{ row }">
<div class="space-y-1">
<!-- Type Badge -->
<span
:class="[
'inline-block rounded-full px-2 py-0.5 text-xs font-medium',
row.subscription_type === 'subscription'
? 'bg-violet-100 text-violet-700 dark:bg-violet-900/30 dark:text-violet-400'
: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300'
]"
>
{{
row.subscription_type === 'subscription'
? t('admin.groups.subscription.subscription')
: t('admin.groups.subscription.standard')
}}
</span>
<!-- Subscription Limits - compact single line -->
<div
v-if="row.subscription_type === 'subscription'"
class="text-xs text-gray-500 dark:text-gray-400"
>
<template
v-if="row.daily_limit_usd || row.weekly_limit_usd || row.monthly_limit_usd"
>
<span v-if="row.daily_limit_usd"
>${{ row.daily_limit_usd }}/{{ t('admin.groups.limitDay') }}</span
>
<span
v-if="row.daily_limit_usd && (row.weekly_limit_usd || row.monthly_limit_usd)"
class="mx-1 text-gray-300 dark:text-gray-600"
>·</span
>
<span v-if="row.weekly_limit_usd"
>${{ row.weekly_limit_usd }}/{{ t('admin.groups.limitWeek') }}</span
>
<span
v-if="row.weekly_limit_usd && row.monthly_limit_usd"
class="mx-1 text-gray-300 dark:text-gray-600"
>·</span
>
<span v-if="row.monthly_limit_usd"
>${{ row.monthly_limit_usd }}/{{ t('admin.groups.limitMonth') }}</span
>
</template>
<span v-else class="text-gray-400 dark:text-gray-500">{{
t('admin.groups.subscription.noLimit')
}}</span>
</div>
</div>
</template>
<template #cell-rate_multiplier="{ value }">
<span class="text-sm text-gray-700 dark:text-gray-300">{{ value }}x</span>
</template>
<template #cell-is_exclusive="{ value }">
<span :class="['badge', value ? 'badge-primary' : 'badge-gray']">
{{ value ? t('admin.groups.exclusive') : t('admin.groups.public') }}
</span>
</template>
<template #cell-account_count="{ value }">
<span
class="inline-flex items-center rounded bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-800 dark:bg-dark-600 dark:text-gray-300"
>
{{ t('admin.groups.accountsCount', { count: value || 0 }) }}
</span>
</template>
<template #cell-status="{ value }">
<span :class="['badge', value === 'active' ? 'badge-success' : 'badge-danger']">
{{ value }}
</span>
</template>
<template #cell-actions="{ row }">
<div class="flex items-center gap-1">
<button
@click="handleEdit(row)"
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-primary-600 dark:hover:bg-dark-700 dark:hover:text-primary-400"
>
<svg
class="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="1.5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
/>
</svg>
<span class="text-xs">{{ t('common.edit') }}</span>
</button>
<button
@click="handleDelete(row)"
class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400"
>
<svg
class="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="1.5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
/>
</svg>
<span class="text-xs">{{ t('common.delete') }}</span>
</button>
</div>
</template>
<template #empty>
<EmptyState
:title="t('admin.groups.noGroupsYet')"
:description="t('admin.groups.createFirstGroup')"
:action-text="t('admin.groups.createGroup')"
@action="showCreateModal = true"
/>
</template>
</DataTable>
</template>
<template #pagination>
<Pagination
v-if="pagination.total > 0"
:page="pagination.page"
:total="pagination.total"
:page-size="pagination.page_size"
@update:page="handlePageChange"
/>
</template>
</TablePageLayout>
<!-- Create Group Modal -->
<Modal
:show="showCreateModal"
:title="t('admin.groups.createGroup')"
size="lg"
@close="closeCreateModal"
>
<form @submit.prevent="handleCreateGroup" class="space-y-5">
<div>
<label class="input-label">{{ t('admin.groups.form.name') }}</label>
<input
v-model="createForm.name"
type="text"
required
class="input"
:placeholder="t('admin.groups.enterGroupName')"
/>
</div>
<div>
<label class="input-label">{{ t('admin.groups.form.description') }}</label>
<textarea
v-model="createForm.description"
rows="3"
class="input"
:placeholder="t('admin.groups.optionalDescription')"
></textarea>
</div>
<div>
<label class="input-label">{{ t('admin.groups.form.platform') }}</label>
<Select v-model="createForm.platform" :options="platformOptions" />
<p class="input-hint">{{ t('admin.groups.platformHint') }}</p>
</div>
<div v-if="createForm.subscription_type !== 'subscription'">
<label class="input-label">{{ t('admin.groups.form.rateMultiplier') }}</label>
<input
v-model.number="createForm.rate_multiplier"
type="number"
step="0.001"
min="0.001"
required
class="input"
/>
<p class="input-hint">{{ t('admin.groups.rateMultiplierHint') }}</p>
</div>
<div v-if="createForm.subscription_type !== 'subscription'" class="flex items-center gap-3">
<button
type="button"
@click="createForm.is_exclusive = !createForm.is_exclusive"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
createForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
createForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
]"
/>
</button>
<label class="text-sm text-gray-700 dark:text-gray-300">
{{ t('admin.groups.exclusiveHint') }}
</label>
</div>
<!-- Subscription Configuration -->
<div class="mt-4 border-t pt-4">
<h4 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">
{{ t('admin.groups.subscription.title') }}
</h4>
<div class="mb-4">
<label class="input-label">{{ t('admin.groups.subscription.type') }}</label>
<Select v-model="createForm.subscription_type" :options="subscriptionTypeOptions" />
<p class="input-hint">{{ t('admin.groups.subscription.typeHint') }}</p>
</div>
<!-- Subscription limits (only show when subscription type is selected) -->
<div
v-if="createForm.subscription_type === 'subscription'"
class="space-y-4 border-l-2 border-primary-200 pl-4 dark:border-primary-800"
>
<div>
<label class="input-label">{{ t('admin.groups.subscription.dailyLimit') }}</label>
<input
v-model.number="createForm.daily_limit_usd"
type="number"
step="0.01"
min="0"
class="input"
:placeholder="t('admin.groups.subscription.noLimit')"
/>
</div>
<div>
<label class="input-label">{{ t('admin.groups.subscription.weeklyLimit') }}</label>
<input
v-model.number="createForm.weekly_limit_usd"
type="number"
step="0.01"
min="0"
class="input"
:placeholder="t('admin.groups.subscription.noLimit')"
/>
</div>
<div>
<label class="input-label">{{ t('admin.groups.subscription.monthlyLimit') }}</label>
<input
v-model.number="createForm.monthly_limit_usd"
type="number"
step="0.01"
min="0"
class="input"
:placeholder="t('admin.groups.subscription.noLimit')"
/>
</div>
</div>
</div>
<div class="flex justify-end gap-3 pt-4">
<button @click="closeCreateModal" type="button" class="btn btn-secondary">
{{ t('common.cancel') }}
</button>
<button type="submit" :disabled="submitting" class="btn btn-primary">
<svg
v-if="submitting"
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>
{{ submitting ? t('admin.groups.creating') : t('common.create') }}
</button>
</div>
</form>
</Modal>
<!-- Edit Group Modal -->
<Modal
:show="showEditModal"
:title="t('admin.groups.editGroup')"
size="lg"
@close="closeEditModal"
>
<form v-if="editingGroup" @submit.prevent="handleUpdateGroup" class="space-y-5">
<div>
<label class="input-label">{{ t('admin.groups.form.name') }}</label>
<input v-model="editForm.name" type="text" required class="input" />
</div>
<div>
<label class="input-label">{{ t('admin.groups.form.description') }}</label>
<textarea v-model="editForm.description" rows="3" class="input"></textarea>
</div>
<div>
<label class="input-label">{{ t('admin.groups.form.platform') }}</label>
<Select v-model="editForm.platform" :options="platformOptions" :disabled="true" />
<p class="input-hint">{{ t('admin.groups.platformNotEditable') }}</p>
</div>
<div v-if="editForm.subscription_type !== 'subscription'">
<label class="input-label">{{ t('admin.groups.form.rateMultiplier') }}</label>
<input
v-model.number="editForm.rate_multiplier"
type="number"
step="0.001"
min="0.001"
required
class="input"
/>
</div>
<div v-if="editForm.subscription_type !== 'subscription'" class="flex items-center gap-3">
<button
type="button"
@click="editForm.is_exclusive = !editForm.is_exclusive"
:class="[
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
editForm.is_exclusive ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
]"
>
<span
:class="[
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
editForm.is_exclusive ? 'translate-x-6' : 'translate-x-1'
]"
/>
</button>
<label class="text-sm text-gray-700 dark:text-gray-300">
{{ t('admin.groups.exclusiveHint') }}
</label>
</div>
<div>
<label class="input-label">{{ t('admin.groups.form.status') }}</label>
<Select v-model="editForm.status" :options="editStatusOptions" />
</div>
<!-- Subscription Configuration -->
<div class="mt-4 border-t pt-4">
<h4 class="mb-4 text-sm font-medium text-gray-900 dark:text-white">
{{ t('admin.groups.subscription.title') }}
</h4>
<div class="mb-4">
<label class="input-label">{{ t('admin.groups.subscription.type') }}</label>
<Select
v-model="editForm.subscription_type"
:options="subscriptionTypeOptions"
:disabled="true"
/>
<p class="input-hint">{{ t('admin.groups.subscription.typeNotEditable') }}</p>
</div>
<!-- Subscription limits (only show when subscription type is selected) -->
<div
v-if="editForm.subscription_type === 'subscription'"
class="space-y-4 border-l-2 border-primary-200 pl-4 dark:border-primary-800"
>
<div>
<label class="input-label">{{ t('admin.groups.subscription.dailyLimit') }}</label>
<input
v-model.number="editForm.daily_limit_usd"
type="number"
step="0.01"
min="0"
class="input"
:placeholder="t('admin.groups.subscription.noLimit')"
/>
</div>
<div>
<label class="input-label">{{ t('admin.groups.subscription.weeklyLimit') }}</label>
<input
v-model.number="editForm.weekly_limit_usd"
type="number"
step="0.01"
min="0"
class="input"
:placeholder="t('admin.groups.subscription.noLimit')"
/>
</div>
<div>
<label class="input-label">{{ t('admin.groups.subscription.monthlyLimit') }}</label>
<input
v-model.number="editForm.monthly_limit_usd"
type="number"
step="0.01"
min="0"
class="input"
:placeholder="t('admin.groups.subscription.noLimit')"
/>
</div>
</div>
</div>
<div class="flex justify-end gap-3 pt-4">
<button @click="closeEditModal" type="button" class="btn btn-secondary">
{{ t('common.cancel') }}
</button>
<button type="submit" :disabled="submitting" class="btn btn-primary">
<svg
v-if="submitting"
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>
{{ submitting ? t('admin.groups.updating') : t('common.update') }}
</button>
</div>
</form>
</Modal>
<!-- Delete Confirmation Dialog -->
<ConfirmDialog
:show="showDeleteDialog"
:title="t('admin.groups.deleteGroup')"
:message="deleteConfirmMessage"
:confirm-text="t('common.delete')"
:cancel-text="t('common.cancel')"
:danger="true"
@confirm="confirmDelete"
@cancel="showDeleteDialog = false"
/>
</AppLayout>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { adminAPI } from '@/api/admin'
import type { Group, GroupPlatform, SubscriptionType } from '@/types'
import type { Column } from '@/components/common/types'
import AppLayout from '@/components/layout/AppLayout.vue'
import TablePageLayout from '@/components/layout/TablePageLayout.vue'
import DataTable from '@/components/common/DataTable.vue'
import Pagination from '@/components/common/Pagination.vue'
import Modal from '@/components/common/Modal.vue'
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
import EmptyState from '@/components/common/EmptyState.vue'
import Select from '@/components/common/Select.vue'
import PlatformIcon from '@/components/common/PlatformIcon.vue'
const { t } = useI18n()
const appStore = useAppStore()
const columns = computed<Column[]>(() => [
{ key: 'name', label: t('admin.groups.columns.name'), sortable: true },
{ key: 'platform', label: t('admin.groups.columns.platform'), sortable: true },
{ key: 'billing_type', label: t('admin.groups.columns.billingType'), sortable: true },
{ key: 'rate_multiplier', label: t('admin.groups.columns.rateMultiplier'), sortable: true },
{ key: 'is_exclusive', label: t('admin.groups.columns.type'), sortable: true },
{ key: 'account_count', label: t('admin.groups.columns.accounts'), sortable: true },
{ key: 'status', label: t('admin.groups.columns.status'), sortable: true },
{ key: 'actions', label: t('admin.groups.columns.actions'), sortable: false }
])
// Filter options
const statusOptions = computed(() => [
{ value: '', label: t('admin.groups.allStatus') },
{ value: 'active', label: t('common.active') },
{ value: 'inactive', label: t('common.inactive') }
])
const exclusiveOptions = computed(() => [
{ value: '', label: t('admin.groups.allGroups') },
{ value: 'true', label: t('admin.groups.exclusive') },
{ value: 'false', label: t('admin.groups.nonExclusive') }
])
const platformOptions = computed(() => [
{ value: 'anthropic', label: 'Anthropic' },
{ value: 'openai', label: 'OpenAI' },
{ value: 'gemini', label: 'Gemini' }
])
const platformFilterOptions = computed(() => [
{ value: '', label: t('admin.groups.allPlatforms') },
{ value: 'anthropic', label: 'Anthropic' },
{ value: 'openai', label: 'OpenAI' },
{ value: 'gemini', label: 'Gemini' }
])
const editStatusOptions = computed(() => [
{ value: 'active', label: t('common.active') },
{ value: 'inactive', label: t('common.inactive') }
])
const subscriptionTypeOptions = computed(() => [
{ value: 'standard', label: t('admin.groups.subscription.standard') },
{ value: 'subscription', label: t('admin.groups.subscription.subscription') }
])
const groups = ref<Group[]>([])
const loading = ref(false)
const filters = reactive({
platform: '',
status: '',
is_exclusive: ''
})
const pagination = reactive({
page: 1,
page_size: 20,
total: 0,
pages: 0
})
const showCreateModal = ref(false)
const showEditModal = ref(false)
const showDeleteDialog = ref(false)
const submitting = ref(false)
const editingGroup = ref<Group | null>(null)
const deletingGroup = ref<Group | null>(null)
const createForm = reactive({
name: '',
description: '',
platform: 'anthropic' as GroupPlatform,
rate_multiplier: 1.0,
is_exclusive: false,
subscription_type: 'standard' as SubscriptionType,
daily_limit_usd: null as number | null,
weekly_limit_usd: null as number | null,
monthly_limit_usd: null as number | null
})
const editForm = reactive({
name: '',
description: '',
platform: 'anthropic' as GroupPlatform,
rate_multiplier: 1.0,
is_exclusive: false,
status: 'active' as 'active' | 'inactive',
subscription_type: 'standard' as SubscriptionType,
daily_limit_usd: null as number | null,
weekly_limit_usd: null as number | null,
monthly_limit_usd: null as number | null
})
// 根据分组类型返回不同的删除确认消息
const deleteConfirmMessage = computed(() => {
if (!deletingGroup.value) {
return ''
}
if (deletingGroup.value.subscription_type === 'subscription') {
return t('admin.groups.deleteConfirmSubscription', { name: deletingGroup.value.name })
}
return t('admin.groups.deleteConfirm', { name: deletingGroup.value.name })
})
const loadGroups = async () => {
loading.value = true
try {
const response = await adminAPI.groups.list(pagination.page, pagination.page_size, {
platform: (filters.platform as GroupPlatform) || undefined,
status: filters.status as any,
is_exclusive: filters.is_exclusive ? filters.is_exclusive === 'true' : undefined
})
groups.value = response.items
pagination.total = response.total
pagination.pages = response.pages
} catch (error) {
appStore.showError(t('admin.groups.failedToLoad'))
console.error('Error loading groups:', error)
} finally {
loading.value = false
}
}
const handlePageChange = (page: number) => {
pagination.page = page
loadGroups()
}
const closeCreateModal = () => {
showCreateModal.value = false
createForm.name = ''
createForm.description = ''
createForm.platform = 'anthropic'
createForm.rate_multiplier = 1.0
createForm.is_exclusive = false
createForm.subscription_type = 'standard'
createForm.daily_limit_usd = null
createForm.weekly_limit_usd = null
createForm.monthly_limit_usd = null
}
const handleCreateGroup = async () => {
submitting.value = true
try {
await adminAPI.groups.create(createForm)
appStore.showSuccess(t('admin.groups.groupCreated'))
closeCreateModal()
loadGroups()
} catch (error: any) {
appStore.showError(error.response?.data?.detail || t('admin.groups.failedToCreate'))
console.error('Error creating group:', error)
} finally {
submitting.value = false
}
}
const handleEdit = (group: Group) => {
editingGroup.value = group
editForm.name = group.name
editForm.description = group.description || ''
editForm.platform = group.platform
editForm.rate_multiplier = group.rate_multiplier
editForm.is_exclusive = group.is_exclusive
editForm.status = group.status
editForm.subscription_type = group.subscription_type || 'standard'
editForm.daily_limit_usd = group.daily_limit_usd
editForm.weekly_limit_usd = group.weekly_limit_usd
editForm.monthly_limit_usd = group.monthly_limit_usd
showEditModal.value = true
}
const closeEditModal = () => {
showEditModal.value = false
editingGroup.value = null
}
const handleUpdateGroup = async () => {
if (!editingGroup.value) return
submitting.value = true
try {
await adminAPI.groups.update(editingGroup.value.id, editForm)
appStore.showSuccess(t('admin.groups.groupUpdated'))
closeEditModal()
loadGroups()
} catch (error: any) {
appStore.showError(error.response?.data?.detail || t('admin.groups.failedToUpdate'))
console.error('Error updating group:', error)
} finally {
submitting.value = false
}
}
const handleDelete = (group: Group) => {
deletingGroup.value = group
showDeleteDialog.value = true
}
const confirmDelete = async () => {
if (!deletingGroup.value) return
try {
await adminAPI.groups.delete(deletingGroup.value.id)
appStore.showSuccess(t('admin.groups.groupDeleted'))
showDeleteDialog.value = false
deletingGroup.value = null
loadGroups()
} catch (error: any) {
appStore.showError(error.response?.data?.detail || t('admin.groups.failedToDelete'))
console.error('Error deleting group:', error)
}
}
// 监听 subscription_type 变化,订阅模式时重置 rate_multiplier 为 1is_exclusive 为 true
watch(
() => createForm.subscription_type,
(newVal) => {
if (newVal === 'subscription') {
createForm.rate_multiplier = 1.0
createForm.is_exclusive = true
}
}
)
onMounted(() => {
loadGroups()
})
</script>