From 914e3d5127df42f746109646174fa56f24f4a0e5 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 9 May 2026 21:00:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=E5=92=8C=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E5=90=8E=E5=8F=B0=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加云端数据权限列到代理管理页面 - 重构代理后台用户管理、卡密管理、财务管理页面使用DataTable组件 - 添加统计卡片到代理后台各页面 - 修复agent-apps相关类型错误 - 添加代理后台页面i18n国际化支持 Co-Authored-By: Claude Opus 4.7 --- .../src/pages/admin/agent-apps/create.vue | 6 +- .../src/pages/admin/agent-apps/data/schema.ts | 2 +- frontend/src/pages/admin/agent-apps/index.vue | 2 +- .../pages/admin/agents/components/columns.ts | 14 +- .../src/pages/admin/agents/data/schema.ts | 1 + .../pages/agent/cards/components/columns.ts | 84 ++++++ .../cards/components/data-table-toolbar.vue | 35 +++ .../agent/cards/components/data-table.vue | 88 +++++++ frontend/src/pages/agent/cards/data/schema.ts | 9 + frontend/src/pages/agent/cards/index.vue | 240 ++++++++++-------- .../pages/agent/finance/components/columns.ts | 86 +++++++ .../finance/components/data-table-toolbar.vue | 46 ++++ .../agent/finance/components/data-table.vue | 71 ++++++ .../src/pages/agent/finance/data/schema.ts | 8 + frontend/src/pages/agent/finance/index.vue | 194 ++++++-------- .../pages/agent/users/components/columns.ts | 94 +++++++ .../users/components/data-table-toolbar.vue | 35 +++ .../agent/users/components/data-table.vue | 71 ++++++ frontend/src/pages/agent/users/data/schema.ts | 8 + frontend/src/pages/agent/users/index.vue | 180 ++++++------- frontend/src/plugins/i18n/en.json | 61 ++++- frontend/src/plugins/i18n/zh.json | 61 ++++- 22 files changed, 1089 insertions(+), 307 deletions(-) create mode 100644 frontend/src/pages/agent/cards/components/columns.ts create mode 100644 frontend/src/pages/agent/cards/components/data-table-toolbar.vue create mode 100644 frontend/src/pages/agent/cards/components/data-table.vue create mode 100644 frontend/src/pages/agent/cards/data/schema.ts create mode 100644 frontend/src/pages/agent/finance/components/columns.ts create mode 100644 frontend/src/pages/agent/finance/components/data-table-toolbar.vue create mode 100644 frontend/src/pages/agent/finance/components/data-table.vue create mode 100644 frontend/src/pages/agent/finance/data/schema.ts create mode 100644 frontend/src/pages/agent/users/components/columns.ts create mode 100644 frontend/src/pages/agent/users/components/data-table-toolbar.vue create mode 100644 frontend/src/pages/agent/users/components/data-table.vue create mode 100644 frontend/src/pages/agent/users/data/schema.ts diff --git a/frontend/src/pages/admin/agent-apps/create.vue b/frontend/src/pages/admin/agent-apps/create.vue index 625bb6f..3cba71d 100644 --- a/frontend/src/pages/admin/agent-apps/create.vue +++ b/frontend/src/pages/admin/agent-apps/create.vue @@ -108,6 +108,10 @@ async function fetchCardTypes(appId: string) { } } +function handleAppChange(value: string | number | bigint | Record | null | undefined) { + fetchCardTypes(String(value || '')) +} + watch(selectedCardType, (ct) => { if (ct) { formData.value.price = ct.price @@ -201,7 +205,7 @@ onMounted(() => {
{{ t('admin.agentApps.createForm.selectApp') }} * - + diff --git a/frontend/src/pages/admin/agent-apps/data/schema.ts b/frontend/src/pages/admin/agent-apps/data/schema.ts index f4f142a..0f281b3 100644 --- a/frontend/src/pages/admin/agent-apps/data/schema.ts +++ b/frontend/src/pages/admin/agent-apps/data/schema.ts @@ -7,7 +7,7 @@ export interface AgentApp { status: string discount: number balance: number - card_types: { card_type_id: number, can_generate: boolean, name?: string }[] + card_types: { card_type_id: number, can_generate: boolean, name?: string, price?: number }[] created_at?: string updated_at?: string remark?: string diff --git a/frontend/src/pages/admin/agent-apps/index.vue b/frontend/src/pages/admin/agent-apps/index.vue index d623cfb..d94e97f 100644 --- a/frontend/src/pages/admin/agent-apps/index.vue +++ b/frontend/src/pages/admin/agent-apps/index.vue @@ -34,7 +34,7 @@ async function fetchAgentApps() { loading.value = true try { const data = await api.get<{ agent_apps?: AgentApp[] }>('/dev/agent-apps') - agentApps.value = data?.agent_apps || data || [] + agentApps.value = Array.isArray(data) ? data : (data?.agent_apps || []) } catch { agentApps.value = [] diff --git a/frontend/src/pages/admin/agents/components/columns.ts b/frontend/src/pages/admin/agents/components/columns.ts index 436c260..032ecbd 100644 --- a/frontend/src/pages/admin/agents/components/columns.ts +++ b/frontend/src/pages/admin/agents/components/columns.ts @@ -68,7 +68,19 @@ export function getColumns(actions: { cell: ({ row }) => { const canCreate = row.getValue('can_create_agent') as boolean return h('div', { class: 'flex items-center justify-center' }, [ - canCreate + canCreate + ? h(Check, { class: 'h-4 w-4 text-green-500' }) + : h(X, { class: 'h-4 w-4 text-red-500' }), + ]) + }, + }, + { + accessorKey: 'can_view_cloud_data', + header: () => t('admin.agents.columns.canViewCloudData'), + cell: ({ row }) => { + const canView = row.getValue('can_view_cloud_data') as boolean + return h('div', { class: 'flex items-center justify-center' }, [ + canView ? h(Check, { class: 'h-4 w-4 text-green-500' }) : h(X, { class: 'h-4 w-4 text-red-500' }), ]) diff --git a/frontend/src/pages/admin/agents/data/schema.ts b/frontend/src/pages/admin/agents/data/schema.ts index 82c40ca..801c7b3 100644 --- a/frontend/src/pages/admin/agents/data/schema.ts +++ b/frontend/src/pages/admin/agents/data/schema.ts @@ -12,6 +12,7 @@ export interface Agent { balance: number total_consumption: number can_create_agent: boolean + can_view_cloud_data: boolean cards_count: number child_agents_count: number children?: Agent[] diff --git a/frontend/src/pages/agent/cards/components/columns.ts b/frontend/src/pages/agent/cards/components/columns.ts new file mode 100644 index 0000000..e2e2ad7 --- /dev/null +++ b/frontend/src/pages/agent/cards/components/columns.ts @@ -0,0 +1,84 @@ +import type { ColumnDef } from '@tanstack/vue-table' +import type { Composer } from 'vue-i18n' + +import { h } from 'vue' + +import type { Card } from '../data/schema' + +import Badge from '@/components/ui/badge/Badge.vue' + +export function getColumns(t: Composer['t']): ColumnDef[] { + return [ + { + accessorKey: 'code', + header: () => t('agent.cards.columns.code'), + cell: ({ row }) => { + const code = row.getValue('code') as string + return h('span', { class: 'font-mono text-sm' }, code) + }, + }, + { + accessorKey: 'app_name', + header: () => t('agent.cards.columns.appName'), + cell: ({ row }) => { + const appName = row.getValue('app_name') as string + return h(Badge, { variant: 'secondary' }, () => appName) + }, + }, + { + accessorKey: 'card_type_name', + header: () => t('agent.cards.columns.cardTypeName'), + cell: ({ row }) => { + const cardTypeName = row.getValue('card_type_name') as string + return h('span', { class: 'font-medium' }, cardTypeName) + }, + }, + { + accessorKey: 'status', + header: () => t('agent.cards.columns.status'), + cell: ({ row }) => { + const status = row.getValue('status') as string + const statusMap: Record = { + unused: { label: t('agent.cards.status.unused'), variant: 'default' }, + used: { label: t('agent.cards.status.used'), variant: 'secondary' }, + expired: { label: t('agent.cards.status.expired'), variant: 'destructive' }, + disabled: { label: t('agent.cards.status.disabled'), variant: 'secondary' }, + } + const { label, variant } = statusMap[status] || { label: status || '-', variant: 'secondary' } + return h(Badge, { variant }, () => label) + }, + }, + { + accessorKey: 'created_at', + header: () => t('agent.cards.columns.createdAt'), + cell: ({ row }) => { + const createdAt = row.getValue('created_at') as string + if (!createdAt) return '-' + try { + const date = new Date(createdAt) + if (Number.isNaN(date.getTime())) return '-' + return date.toLocaleDateString('zh-CN') + } + catch { + return '-' + } + }, + }, + { + accessorKey: 'used_at', + header: () => t('agent.cards.columns.usedAt'), + cell: ({ row }) => { + const usedAt = row.getValue('used_at') as string | null + if (!usedAt) return '-' + try { + const date = new Date(usedAt) + if (Number.isNaN(date.getTime())) return '-' + return date.toLocaleDateString('zh-CN') + } + catch { + return '-' + } + }, + }, + ] +} diff --git a/frontend/src/pages/agent/cards/components/data-table-toolbar.vue b/frontend/src/pages/agent/cards/components/data-table-toolbar.vue new file mode 100644 index 0000000..80478ec --- /dev/null +++ b/frontend/src/pages/agent/cards/components/data-table-toolbar.vue @@ -0,0 +1,35 @@ + + + diff --git a/frontend/src/pages/agent/cards/components/data-table.vue b/frontend/src/pages/agent/cards/components/data-table.vue new file mode 100644 index 0000000..bc9d4a4 --- /dev/null +++ b/frontend/src/pages/agent/cards/components/data-table.vue @@ -0,0 +1,88 @@ + + + diff --git a/frontend/src/pages/agent/cards/data/schema.ts b/frontend/src/pages/agent/cards/data/schema.ts new file mode 100644 index 0000000..a2f9dba --- /dev/null +++ b/frontend/src/pages/agent/cards/data/schema.ts @@ -0,0 +1,9 @@ +export interface Card { + id: number + code: string + app_name: string + card_type_name: string + status: string + created_at: string + used_at: string | null +} diff --git a/frontend/src/pages/agent/cards/index.vue b/frontend/src/pages/agent/cards/index.vue index 7b4e43e..4a21dbb 100644 --- a/frontend/src/pages/agent/cards/index.vue +++ b/frontend/src/pages/agent/cards/index.vue @@ -1,27 +1,43 @@ diff --git a/frontend/src/pages/agent/finance/components/columns.ts b/frontend/src/pages/agent/finance/components/columns.ts new file mode 100644 index 0000000..33728de --- /dev/null +++ b/frontend/src/pages/agent/finance/components/columns.ts @@ -0,0 +1,86 @@ +import type { ColumnDef } from '@tanstack/vue-table' +import type { Composer } from 'vue-i18n' + +import { ArrowDownLeft, ArrowUpRight, RotateCcw } from 'lucide-vue-next' +import { h } from 'vue' + +import type { Transaction } from '../data/schema' + +import Badge from '@/components/ui/badge/Badge.vue' + +export function getColumns(t: Composer['t']): ColumnDef[] { + return [ + { + accessorKey: 'type', + header: () => t('agent.finance.columns.type'), + cell: ({ row }) => { + const type = row.getValue('type') as string + const typeMap: Record = { + recharge: { label: t('agent.finance.types.recharge'), variant: 'default', icon: ArrowDownLeft }, + consume: { label: t('agent.finance.types.consume'), variant: 'secondary', icon: ArrowUpRight }, + refund: { label: t('agent.finance.types.refund'), variant: 'default', icon: RotateCcw }, + } + const config = typeMap[type] || { label: type, variant: 'secondary', icon: ArrowUpRight } + const iconClass = type === 'recharge' ? 'text-green-500' : type === 'refund' ? 'text-blue-500' : 'text-red-500' + const bgClass = type === 'recharge' ? 'bg-green-500/10' : type === 'refund' ? 'bg-blue-500/10' : 'bg-red-500/10' + + return h('div', { class: 'flex items-center gap-2' }, [ + h('div', { class: `size-8 rounded-lg flex items-center justify-center ${bgClass}` }, [ + h(config.icon, { class: `size-4 ${iconClass}` }), + ]), + h('span', config.label), + ]) + }, + }, + { + accessorKey: 'description', + header: () => t('agent.finance.columns.description'), + cell: ({ row }) => { + const description = row.getValue('description') as string + const type = row.getValue('type') as string + return h('span', { class: 'text-muted-foreground' }, description || t(`agent.finance.types.${type}`) || '-') + }, + }, + { + accessorKey: 'amount', + header: () => t('agent.finance.columns.amount'), + cell: ({ row }) => { + const type = row.getValue('type') as string + const amount = row.getValue('amount') as number + const prefix = type === 'recharge' || type === 'refund' ? '+' : '-' + const textClass = type === 'recharge' || type === 'refund' ? 'text-green-500' : 'text-red-500' + return h('span', { class: `font-semibold ${textClass}` }, `${prefix}¥${(amount || 0).toFixed(2)}`) + }, + }, + { + accessorKey: 'balance', + header: () => t('agent.finance.columns.balance'), + cell: ({ row }) => { + const balance = row.getValue('balance') as number + return h('span', { class: 'text-muted-foreground' }, `¥${(balance || 0).toFixed(2)}`) + }, + }, + { + accessorKey: 'created_at', + header: () => t('agent.finance.columns.createdAt'), + cell: ({ row }) => { + const createdAt = row.getValue('created_at') as string + if (!createdAt) return '-' + try { + const date = new Date(createdAt) + if (Number.isNaN(date.getTime())) return '-' + return h('span', { class: 'text-muted-foreground whitespace-nowrap' }, date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + })) + } + catch { + return '-' + } + }, + }, + ] +} diff --git a/frontend/src/pages/agent/finance/components/data-table-toolbar.vue b/frontend/src/pages/agent/finance/components/data-table-toolbar.vue new file mode 100644 index 0000000..09b2985 --- /dev/null +++ b/frontend/src/pages/agent/finance/components/data-table-toolbar.vue @@ -0,0 +1,46 @@ + + + diff --git a/frontend/src/pages/agent/finance/components/data-table.vue b/frontend/src/pages/agent/finance/components/data-table.vue new file mode 100644 index 0000000..1f8dcc7 --- /dev/null +++ b/frontend/src/pages/agent/finance/components/data-table.vue @@ -0,0 +1,71 @@ + + + diff --git a/frontend/src/pages/agent/finance/data/schema.ts b/frontend/src/pages/agent/finance/data/schema.ts new file mode 100644 index 0000000..79d722c --- /dev/null +++ b/frontend/src/pages/agent/finance/data/schema.ts @@ -0,0 +1,8 @@ +export interface Transaction { + id: number + type: string + amount: number + description: string + balance: number + created_at: string +} diff --git a/frontend/src/pages/agent/finance/index.vue b/frontend/src/pages/agent/finance/index.vue index cac1004..54fdb20 100644 --- a/frontend/src/pages/agent/finance/index.vue +++ b/frontend/src/pages/agent/finance/index.vue @@ -1,25 +1,29 @@