diff --git a/frontend/src/pages/admin/agents/[id].vue b/frontend/src/pages/admin/agents/[id].vue index 90f101a..71adba1 100644 --- a/frontend/src/pages/admin/agents/[id].vue +++ b/frontend/src/pages/admin/agents/[id].vue @@ -2,355 +2,312 @@ import { Icon } from '@iconify/vue' import { computed, onMounted, ref } from 'vue' import { useI18n } from 'vue-i18n' -import { useRouter } from 'vue-router' +import { useRoute, useRouter } from 'vue-router' import { toast } from 'vue-sonner' -import type { Agent } from './data/schema' - -import ConfirmDialog from '@/components/confirm-dialog.vue' -import SingleFilter from '@/components/data-table/single-filter.vue' import { BasicPage } from '@/components/global-layout' -import DataTable from './components/data-table.vue' import api from '@/services/api' -const { t } = useI18n() const router = useRouter() +const route = useRoute() +const { t } = useI18n() +interface Agent { + id: number + username: string + email: string + status: string +} + +const agentId = computed(() => route.params.id as string) const loading = ref(true) +const saving = ref(false) const agents = ref([]) -const tableRef = ref | null>(null) -const searchFilter = ref('') -const statusFilter = ref('') -const viewMode = ref<'tree' | 'list'>('tree') -const deleteDialogOpen = ref(false) -const deleteTarget = ref(null) -const batchDeleteDialogOpen = ref(false) -const batchDeleteIds = ref([]) - -const filteredAgents = computed(() => { - let result = agents.value - - if (statusFilter.value) { - const filterByStatus = (items: Agent[]): Agent[] => { - return items.reduce((acc: Agent[], item) => { - const matches = item.status === statusFilter.value - const filteredChildren = item.children ? filterByStatus(item.children) : [] - - if (matches || filteredChildren.length > 0) { - acc.push({ - ...item, - children: filteredChildren.length > 0 ? filteredChildren : item.children, - }) - } - return acc - }, []) - } - result = filterByStatus(result) - } - - if (searchFilter.value) { - const search = searchFilter.value.toLowerCase() - const filterTree = (items: Agent[]): Agent[] => { - return items.reduce((acc: Agent[], item) => { - const matchesSearch = - item.username?.toLowerCase().includes(search) || - item.email?.toLowerCase().includes(search) || - item.parent_agent_name?.toLowerCase().includes(search) - - const filteredChildren = item.children ? filterTree(item.children) : [] - - if (matchesSearch || filteredChildren.length > 0) { - acc.push({ - ...item, - children: filteredChildren.length > 0 ? filteredChildren : item.children, - }) - } - - return acc - }, []) - } - result = filterTree(result) - } - - return result +const form = ref({ + username: '', + email: '', + password: '', + parent_agent_id: '', + can_create_agent: false, + balance: 0, }) -const totalAgents = computed(() => { - const countNodes = (items: Agent[]): number => { - return items.reduce((acc, item) => { - return acc + 1 + (item.children ? countNodes(item.children) : 0) - }, 0) - } - return countNodes(agents.value) -}) - -const activeCount = computed(() => { - const countByStatus = (items: Agent[], status: string): number => { - return items.reduce((acc, item) => { - const self = item.status === status ? 1 : 0 - const children = item.children ? countByStatus(item.children, status) : 0 - return acc + self + children - }, 0) - } - return countByStatus(agents.value, 'active') -}) - -const inactiveCount = computed(() => { - const countByStatus = (items: Agent[], status: string): number => { - return items.reduce((acc, item) => { - const self = item.status === status ? 1 : 0 - const children = item.children ? countByStatus(item.children, status) : 0 - return acc + self + children - }, 0) - } - return countByStatus(agents.value, 'inactive') -}) - -const bannedCount = computed(() => { - const countByStatus = (items: Agent[], status: string): number => { - return items.reduce((acc, item) => { - const self = item.status === status ? 1 : 0 - const children = item.children ? countByStatus(item.children, status) : 0 - return acc + self + children - }, 0) - } - return countByStatus(agents.value, 'banned') -}) - -const statusOptions = computed(() => [ - { label: t('admin.agents.status.active'), value: 'active' }, - { label: t('admin.agents.status.inactive'), value: 'inactive' }, - { label: t('admin.agents.status.banned'), value: 'banned' }, -]) - -async function fetchAgents() { +async function fetchAgent() { loading.value = true try { - const endpoint = viewMode.value === 'tree' ? '/dev/agents/tree' : '/dev/agents' - const data = await api.get<{ tree?: Agent[], agents?: Agent[], total: number }>(endpoint) - if (viewMode.value === 'tree') { - agents.value = data?.tree || [] - } else { - agents.value = data?.agents || [] + const data = await api.get(`/dev/agents/${agentId.value}`) + const agent = data?.agent || data + if (agent) { + form.value.username = agent.username || '' + form.value.email = agent.email || '' + form.value.parent_agent_id = agent.parent_agent_id ? String(agent.parent_agent_id) : 'none' + form.value.can_create_agent = agent.can_create_agent || false + form.value.balance = agent.balance || 0 } - } catch (error) { - console.error('获取代理列表失败:', error) - agents.value = [] - } finally { + } + catch (error) { + console.error('获取代理信息失败:', error) + toast.error(t('admin.agents.editFailed')) + } + finally { loading.value = false } } -function goToCreate() { - router.push('/admin/agents/create') -} - -function toggleViewMode() { - viewMode.value = viewMode.value === 'tree' ? 'list' : 'tree' - fetchAgents() -} - -function handleEdit(agent: Agent) { - router.push(`/admin/agents/${agent.id}/edit`) -} - -async function handleToggleStatus(agent: Agent) { +async function fetchAgents() { try { - const newStatus = agent.status === 'banned' ? 'active' : 'banned' - await api.put(`/dev/agents/${agent.id}/status`, { status: newStatus }) - toast.success(t('admin.agents.statusUpdateSuccess')) - fetchAgents() - } catch (error: any) { - console.error('切换代理状态失败:', error) - toast.error(error.message || t('admin.agents.statusUpdateFailed')) + const data = await api.get<{ agents?: Agent[], total: number }>('/dev/agents') + agents.value = data?.agents || [] + } + catch (error) { + console.error('获取代理列表失败:', error) } } -function confirmDelete(agent: Agent) { - deleteTarget.value = agent - deleteDialogOpen.value = true -} - -async function handleDelete() { - if (!deleteTarget.value) return - - try { - await api.delete(`/dev/agents/${deleteTarget.value.id}`) - toast.success(t('admin.agents.deleteSuccess')) - fetchAgents() - } catch (error: any) { - console.error('删除代理失败:', error) - toast.error(error.message || t('admin.agents.deleteFailed')) - } finally { - deleteTarget.value = null +const selectedParentAgent = computed(() => { + if (form.value.parent_agent_id && form.value.parent_agent_id !== 'none') { + return agents.value.find(a => String(a.id) === form.value.parent_agent_id) } -} + return null +}) -function confirmBatchDelete(ids: number[]) { - batchDeleteIds.value = ids - batchDeleteDialogOpen.value = true -} - -async function handleBatchDelete() { - try { - await api.delete('/dev/agents/batch', { agent_ids: batchDeleteIds.value } as any) - toast.success(t('admin.agents.batchDeleteSuccess')) - fetchAgents() - } catch (error: any) { - console.error('批量删除失败:', error) - toast.error(error.message || t('admin.agents.batchDeleteFailed')) - } finally { - batchDeleteIds.value = [] +async function handleSave() { + if (!form.value.username) { + toast.error(t('admin.agents.create.usernameRequired')) + return } -} -async function batchToggleStatus(ids: number[], status: string) { + saving.value = true try { - await api.post('/dev/agents/batch/status', { agent_ids: ids, status }) - toast.success(t('admin.agents.batchUpdateSuccess')) - fetchAgents() - } catch (error: any) { - console.error('批量更新状态失败:', error) - toast.error(error.message || t('admin.agents.batchUpdateFailed')) + const payload: any = { + username: form.value.username, + can_create_agent: form.value.can_create_agent, + balance: form.value.balance, + } + + if (form.value.email) { + payload.email = form.value.email + } + + if (form.value.password) { + payload.password = form.value.password + } + + if (form.value.parent_agent_id && form.value.parent_agent_id !== 'none') { + payload.parent_agent_id = Number.parseInt(form.value.parent_agent_id) + } + + await api.put(`/dev/agents/${agentId.value}`, payload) + toast.success(t('admin.agents.editSuccess')) + router.push('/admin/agents') + } + catch (error: any) { + console.error('更新代理失败:', error) + toast.error(error.message || t('admin.agents.editFailed')) + } + finally { + saving.value = false } } onMounted(() => { fetchAgents() + fetchAgent() }) diff --git a/frontend/src/pages/admin/announcements/[id].vue b/frontend/src/pages/admin/announcements/[id].vue index d9a2cc7..73d4700 100644 --- a/frontend/src/pages/admin/announcements/[id].vue +++ b/frontend/src/pages/admin/announcements/[id].vue @@ -1,19 +1,11 @@