diff --git a/backend/internal/router/admin/agent_apps.go b/backend/internal/router/admin/agent_apps.go index f31d63f..1ce1c8c 100644 --- a/backend/internal/router/admin/agent_apps.go +++ b/backend/internal/router/admin/agent_apps.go @@ -1,4 +1,4 @@ -package admin +package admin import ( "fmt" @@ -17,6 +17,7 @@ func SetupAgentAppRoutes(r *gin.RouterGroup) { { agentApps.GET("", handleGetAgentApps) agentApps.GET("/requests", handleGetAgentRequests) + agentApps.POST("/authorize", handleDirectAuthorize) agentApps.POST("/invite", handleInviteAgent) agentApps.PUT("/requests/:id/approve", handleApproveRequest) agentApps.PUT("/requests/:id/reject", handleRejectRequest) @@ -66,61 +67,34 @@ func checkAgentPermission(userID uint) bool { func handleGetAgentApps(c *gin.Context) { userID := c.GetUint("user_id") - log.Printf("[DEBUG] handleGetAgentApps called for Admin %d\n", userID) - var myAuthorizations []model.AgentApplication - if err := database.DB.Where("admin_id = ?", userID). - Preload("CardTypes.CardType"). - Find(&myAuthorizations).Error; err != nil { - response.Error(c, 500, "获取授权列表失败") + var currentUser model.User + if err := database.DB.First(¤tUser, userID).Error; err != nil { + response.Error(c, 500, "获取用户信息失败") return } - var receivedAuthorizations []model.AgentApplication - if err := database.DB.Where("agent_id = ?", userID). - Preload("CardTypes.CardType"). - Find(&receivedAuthorizations).Error; err != nil { - response.Error(c, 500, "获取授权列表失败") - return - } - - log.Printf("[DEBUG] Found %d my authorizations and %d received authorizations for Admin %d\n", - len(myAuthorizations), len(receivedAuthorizations), userID) - - var allAgentApps []model.AgentApplication - allAgentApps = append(allAgentApps, myAuthorizations...) - allAgentApps = append(allAgentApps, receivedAuthorizations...) - - var agentIDs []uint - var AdminIDs []uint - var applicationIDs []uint - - for _, aa := range allAgentApps { - agentIDs = append(agentIDs, aa.AgentID) - AdminIDs = append(AdminIDs, aa.AdminID) - applicationIDs = append(applicationIDs, aa.ApplicationID) - } - - var users []model.User - if err := database.DB.Where("id IN ?", append(agentIDs, AdminIDs...)).Find(&users).Error; err != nil { - log.Printf("[ERROR] Failed to query users: %v\n", err) + var authorizations []model.AgentApplication + if currentUser.Role == "admin" { + if err := database.DB.Where("admin_id = ?", userID). + Preload("CardTypes.CardType"). + Preload("Agent"). + Preload("Application"). + Order("created_at DESC"). + Find(&authorizations).Error; err != nil { + response.Error(c, 500, "获取授权列表失败") + return + } } else { - log.Printf("[DEBUG] Found %d users\n", len(users)) - } - - var applications []model.Application - if err := database.DB.Where("id IN ?", applicationIDs).Find(&applications).Error; err != nil { - log.Printf("[ERROR] Failed to query applications: %v\n", err) - } - - userMap := make(map[uint]model.User) - for _, user := range users { - userMap[user.ID] = user - } - - applicationMap := make(map[uint]model.Application) - for _, app := range applications { - applicationMap[app.ID] = app + if err := database.DB.Where("admin_id = ?", userID). + Preload("CardTypes.CardType"). + Preload("Agent"). + Preload("Application"). + Order("created_at DESC"). + Find(&authorizations).Error; err != nil { + response.Error(c, 500, "获取授权列表失败") + return + } } type CardTypeResponse struct { @@ -139,25 +113,27 @@ func handleGetAgentApps(c *gin.Context) { AppName string `json:"app_name"` Discount float64 `json:"discount"` Status string `json:"status"` + Balance float64 `json:"balance"` CardTypes []CardTypeResponse `json:"card_types"` CreatedAt string `json:"created_at"` - IsReceived bool `json:"is_received"` } var result []AgentAppResponse - for _, aa := range allAgentApps { + for _, aa := range authorizations { agentName := "" agentEmail := "" - if user, exists := userMap[aa.AgentID]; exists { - agentName = user.Username - if user.Email != nil { - agentEmail = *user.Email + var agentBalance float64 + if aa.Agent.ID != 0 { + agentName = aa.Agent.Username + if aa.Agent.Email != nil { + agentEmail = *aa.Agent.Email } + agentBalance = aa.Agent.Balance } appName := "" - if app, exists := applicationMap[aa.ApplicationID]; exists { - appName = app.Name + if aa.Application.ID != 0 { + appName = aa.Application.Name } var cardTypes []CardTypeResponse @@ -170,9 +146,6 @@ func handleGetAgentApps(c *gin.Context) { }) } - log.Printf("[DEBUG] Processing AgentApp: ID=%d, AgentID=%d, ApplicationID=%d, CardTypes count=%d, IsReceived=%v\n", - aa.ID, aa.AgentID, aa.ApplicationID, len(cardTypes), aa.AgentID == userID) - result = append(result, AgentAppResponse{ ID: aa.ID, AgentID: aa.AgentID, @@ -182,13 +155,12 @@ func handleGetAgentApps(c *gin.Context) { AppName: appName, Discount: aa.Discount, Status: aa.Status, + Balance: agentBalance, CardTypes: cardTypes, CreatedAt: aa.CreatedAt.Format("2006-01-02 15:04:05"), - IsReceived: aa.AgentID == userID, }) } - log.Printf("[DEBUG] Returning %d agent apps for user %d\n", len(result), userID) response.Success(c, gin.H{ "agent_apps": result, "total": len(result), @@ -765,3 +737,125 @@ func handleCancelRequest(c *gin.Context) { "id": req.ID, }) } + +func handleDirectAuthorize(c *gin.Context) { + userID := c.GetUint("user_id") + + var reqBody struct { + AgentID uint `json:"agent_id" binding:"required"` + ApplicationID uint `json:"application_id" binding:"required"` + Discount float64 `json:"discount"` + CardTypes []struct { + CardTypeID uint `json:"card_type_id" binding:"required"` + CanGenerate bool `json:"can_generate"` + } `json:"card_types"` + } + if err := c.ShouldBindJSON(&reqBody); err != nil { + response.Error(c, 400, "参数错误") + return + } + + if reqBody.Discount <= 0 || reqBody.Discount > 1 { + reqBody.Discount = 1.0 + } + + var agent model.User + if err := database.DB.Where("id = ? AND role = ?", reqBody.AgentID, "agent").First(&agent).Error; err != nil { + response.Error(c, 404, "代理不存在") + return + } + + if agent.ParentAgentID != nil && *agent.ParentAgentID != userID { + var currentUser model.User + if err := database.DB.First(¤tUser, userID).Error; err != nil { + response.Error(c, 403, "无权授权该代理") + return + } + if currentUser.Role != "admin" { + response.Error(c, 403, "只能授权自己的下级代理") + return + } + } + + var app model.Application + if err := database.DB.Where("id = ?", reqBody.ApplicationID).First(&app).Error; err != nil { + response.Error(c, 404, "应用不存在") + return + } + + if app.UserID != userID { + var currentUser model.User + if err := database.DB.First(¤tUser, userID).Error; err != nil { + response.Error(c, 403, "无权授权该应用") + return + } + if currentUser.Role != "admin" { + response.Error(c, 403, "只能授权自己的应用") + return + } + } + + var existing model.AgentApplication + if err := database.DB.Where("agent_id = ? AND application_id = ?", reqBody.AgentID, reqBody.ApplicationID).First(&existing).Error; err == nil { + response.Error(c, 400, "该代理已获得此应用的授权") + return + } + + tx := database.DB.Begin() + + agentApp := model.AgentApplication{ + AgentID: reqBody.AgentID, + ApplicationID: reqBody.ApplicationID, + AdminID: userID, + Discount: reqBody.Discount, + Status: "active", + IsReceived: true, + } + if err := tx.Create(&agentApp).Error; err != nil { + tx.Rollback() + response.Error(c, 500, "创建授权失败") + return + } + + if len(reqBody.CardTypes) > 0 { + for _, ct := range reqBody.CardTypes { + agentCardType := model.AgentApplicationCardType{ + AgentApplicationID: agentApp.ID, + CardTypeID: ct.CardTypeID, + CanGenerate: ct.CanGenerate, + } + if err := tx.Create(&agentCardType).Error; err != nil { + tx.Rollback() + response.Error(c, 500, "创建卡密权限失败") + return + } + } + } else { + var cardTypes []model.CardType + database.DB.Where("application_id = ?", reqBody.ApplicationID).Find(&cardTypes) + for _, ct := range cardTypes { + agentCardType := model.AgentApplicationCardType{ + AgentApplicationID: agentApp.ID, + CardTypeID: ct.ID, + CanGenerate: false, + } + if err := tx.Create(&agentCardType).Error; err != nil { + tx.Rollback() + response.Error(c, 500, "创建卡密权限失败") + return + } + } + } + + tx.Commit() + + response.Success(c, gin.H{ + "id": agentApp.ID, + "agent_id": agentApp.AgentID, + "agent_name": agent.Username, + "app_id": agentApp.ApplicationID, + "app_name": app.Name, + "discount": agentApp.Discount, + "status": agentApp.Status, + }) +} diff --git a/frontend/src/components/admin-sidebar/index.vue b/frontend/src/components/admin-sidebar/index.vue index dae4a2c..4eee6c1 100644 --- a/frontend/src/components/admin-sidebar/index.vue +++ b/frontend/src/components/admin-sidebar/index.vue @@ -135,7 +135,7 @@ const navMain = [ icon: Network, }, { - title: '代理授权', + title: '授权管理', url: '/admin/agent-apps', icon: Share2, }, diff --git a/frontend/src/layouts/admin.vue b/frontend/src/layouts/admin.vue index 761c842..c61f7cb 100644 --- a/frontend/src/layouts/admin.vue +++ b/frontend/src/layouts/admin.vue @@ -25,7 +25,7 @@ const breadcrumbs = computed(() => { announcements: '公告管理', versions: '版本管理', 'card-types': '卡类管理', - 'agent-apps': '代理授权', + 'agent-apps': '授权管理', agents: '代理管理', finance: '财务管理', logs: '日志记录', diff --git a/frontend/src/pages/admin/agent-apps/components/columns.ts b/frontend/src/pages/admin/agent-apps/components/columns.ts deleted file mode 100644 index 27c540d..0000000 --- a/frontend/src/pages/admin/agent-apps/components/columns.ts +++ /dev/null @@ -1,228 +0,0 @@ -import type { ColumnDef } from '@tanstack/vue-table' - -import { h } from 'vue' -import { useRouter } from 'vue-router' - -import type { CombinedItem } from '@/pages/admin/agent-apps/data/schema' - -import Badge from '@/components/ui/badge/Badge.vue' -import Button from '@/components/ui/button/Button.vue' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' - -export function getStatusVariant(status: string, hasAuthorization: boolean) { - if (hasAuthorization) { - return status === 'active' ? 'default' : 'secondary' - } - if (status === 'pending') - return 'outline' - if (status === 'approved') - return 'default' - return 'destructive' -} - -export function getStatusText(status: string, hasAuthorization: boolean) { - if (hasAuthorization) { - return status === 'active' ? '已授权' : '已禁用' - } - const map: Record = { - pending: '待处理', - approved: '已通过', - rejected: '已拒绝', - } - return map[status] || status -} - -export function getItemTypeText(itemType: string) { - const map: Record = { - authorization: '授权记录', - request: '收到的申请', - my_request: '发出的申请', - } - return map[itemType] || itemType -} - -export function getColumns(actions: { - onApprove: (row: CombinedItem) => void - onReject: (row: CombinedItem) => void - onRemove: (row: CombinedItem) => void -}): ColumnDef[] { - return [ - { - accessorKey: 'itemType', - header: '类型', - cell: ({ row }) => { - const itemType = row.getValue('itemType') as string - const variants: Record = { - authorization: 'default', - request: 'outline', - my_request: 'secondary', - } - return h(Badge, { variant: variants[itemType] || 'default' }, () => getItemTypeText(itemType)) - }, - }, - { - accessorKey: 'agent_name', - header: '代理商/管理员', - cell: ({ row }) => { - const agentName = row.getValue('agent_name') as string - const itemType = row.original.itemType - const label = itemType === 'my_request' ? '管理员' : '代理商' - return h('div', {}, [ - h('div', { class: 'font-medium' }, agentName || '-'), - h('div', { class: 'text-xs text-muted-foreground' }, label), - ]) - }, - }, - { - accessorKey: 'app_name', - header: '应用', - cell: ({ row }) => { - const appName = row.getValue('app_name') as string - return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-' - }, - }, - { - accessorKey: 'status', - header: '状态', - cell: ({ row }) => { - const status = row.getValue('status') as string - const hasAuthorization = row.original.hasAuthorization - const variant = getStatusVariant(status, hasAuthorization) - const text = getStatusText(status, hasAuthorization) - return h(Badge, { variant }, () => text) - }, - }, - { - accessorKey: 'discount', - header: '折扣', - cell: ({ row }) => { - const hasAuthorization = row.original.hasAuthorization - if (!hasAuthorization) - return '-' - const discount = row.getValue('discount') as number - return `${(discount * 10).toFixed(1)}折` - }, - }, - { - accessorKey: 'balance', - header: '余额', - cell: ({ row }) => { - const hasAuthorization = row.original.hasAuthorization - if (!hasAuthorization) - return '-' - const balance = row.getValue('balance') as number - return h('span', { class: 'font-medium' }, `¥${balance.toFixed(2)}`) - }, - }, - { - accessorKey: 'created_at', - header: '创建时间', - cell: ({ row }) => { - const createdAt = row.getValue('created_at') - if (!createdAt) - return '-' - try { - const date = new Date(createdAt as string) - if (Number.isNaN(date.getTime())) - return '-' - return date.toLocaleString('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - }) - } - catch { - return '-' - } - }, - }, - { - id: 'actions', - header: () => h('span', { class: 'sr-only' }, '操作'), - cell: ({ row }) => { - const item = row.original - const router = useRouter() - - return h( - DropdownMenu, - {}, - { - default: () => [ - h(DropdownMenuTrigger, { asChild: true }, () => - h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => - h('span', { class: 'sr-only' }, '打开菜单'))), - h( - DropdownMenuContent, - { align: 'end' }, - () => { - const items = [] - - if (item.itemType === 'request' && item.status === 'pending') { - items.push( - h(DropdownMenuItem, { onClick: () => actions.onApprove(item) }, () => [ - h('span', { class: 'i-lucide-check mr-2 h-4 w-4' }), - '批准', - ]), - h(DropdownMenuItem, { onClick: () => actions.onReject(item) }, () => [ - h('span', { class: 'i-lucide-x mr-2 h-4 w-4' }), - '拒绝', - ]), - ) - } - - if (item.hasAuthorization) { - items.push( - h(DropdownMenuItem, { onClick: () => router.push(`/admin/agent-apps/${item.authorizationId}/edit`) }, () => [ - h('span', { class: 'i-lucide-edit mr-2 h-4 w-4' }), - '编辑', - ]), - h(DropdownMenuItem, { onClick: () => router.push(`/admin/agent-apps/${item.authorizationId}/card-types`) }, () => [ - h('span', { class: 'i-lucide-key mr-2 h-4 w-4' }), - '卡密权限', - ]), - h(DropdownMenuItem, { onClick: () => router.push(`/admin/agent-apps/${item.authorizationId}/recharge`) }, () => [ - h('span', { class: 'i-lucide-wallet mr-2 h-4 w-4' }), - '充值余额', - ]), - ) - } - - if (item.itemType === 'my_request' && item.status === 'pending') { - items.push( - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onRemove(item) }, () => [ - h('span', { class: 'i-lucide-trash-2 mr-2 h-4 w-4' }), - '撤回申请', - ]), - ) - } - else if (item.hasAuthorization) { - items.push( - h(DropdownMenuSeparator), - h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onRemove(item) }, () => [ - h('span', { class: 'i-lucide-trash-2 mr-2 h-4 w-4' }), - '移除授权', - ]), - ) - } - - return items - }, - ), - ], - }, - ) - }, - enableSorting: false, - enableHiding: false, - }, - ] -} diff --git a/frontend/src/pages/admin/agent-apps/components/data-table-toolbar.vue b/frontend/src/pages/admin/agent-apps/components/data-table-toolbar.vue deleted file mode 100644 index 5a6bcd0..0000000 --- a/frontend/src/pages/admin/agent-apps/components/data-table-toolbar.vue +++ /dev/null @@ -1,40 +0,0 @@ - - - diff --git a/frontend/src/pages/admin/agent-apps/components/data-table.vue b/frontend/src/pages/admin/agent-apps/components/data-table.vue deleted file mode 100644 index 8b6d924..0000000 --- a/frontend/src/pages/admin/agent-apps/components/data-table.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - diff --git a/frontend/src/pages/admin/agent-apps/data/data.ts b/frontend/src/pages/admin/agent-apps/data/data.ts deleted file mode 100644 index 6b1f9c9..0000000 --- a/frontend/src/pages/admin/agent-apps/data/data.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const agentAppStatuses = [ - { label: '已授权', value: 'active' }, - { label: '已禁用', value: 'inactive' }, - { label: '待处理', value: 'pending' }, - { label: '已拒绝', value: 'rejected' }, -] - -export const itemTypes = [ - { label: '授权记录', value: 'authorization' }, - { label: '收到的申请', value: 'request' }, - { label: '发出的申请', value: 'my_request' }, -] diff --git a/frontend/src/pages/admin/agent-apps/data/schema.ts b/frontend/src/pages/admin/agent-apps/data/schema.ts index 265708b..4b1407f 100644 --- a/frontend/src/pages/admin/agent-apps/data/schema.ts +++ b/frontend/src/pages/admin/agent-apps/data/schema.ts @@ -1,54 +1,24 @@ import { z } from 'zod' -export const agentAppStatusSchema = z.enum(['active', 'inactive', 'pending', 'approved', 'rejected']) +export const agentAppStatusSchema = z.enum(['active', 'inactive']) export const agentAppSchema = z.object({ id: z.number(), agent_id: z.number(), agent_name: z.string(), + agent_email: z.string().optional(), application_id: z.number(), app_name: z.string(), status: agentAppStatusSchema, discount: z.number(), balance: z.number(), card_types: z.array(z.object({ + id: z.number().optional(), card_type_id: z.number(), + name: z.string().optional(), can_generate: z.boolean(), })).optional(), created_at: z.string(), - updated_at: z.string().optional(), - is_received: z.boolean().optional(), -}) - -export const agentRequestSchema = z.object({ - id: z.number(), - admin_id: z.number(), - admin_name: z.string(), - agent_id: z.number(), - agent_name: z.string(), - application_id: z.number(), - app_name: z.string(), - status: z.enum(['pending', 'approved', 'rejected']), - message: z.string().optional(), - created_at: z.string(), }) export type AgentApp = z.infer -export type AgentRequest = z.infer - -export interface CombinedItem { - id: number - agent_id: number - agent_name: string - application_id: number - app_name: string - status: string - discount: number - balance: number - card_types?: Array<{ card_type_id: number, can_generate: boolean }> - created_at: string - itemType: 'authorization' | 'request' | 'my_request' - hasAuthorization: boolean - authorizationId: number | null - is_received?: boolean -} diff --git a/frontend/src/pages/admin/agent-apps/index.vue b/frontend/src/pages/admin/agent-apps/index.vue index 0aaac0c..e202b16 100644 --- a/frontend/src/pages/admin/agent-apps/index.vue +++ b/frontend/src/pages/admin/agent-apps/index.vue @@ -1,16 +1,14 @@ - - diff --git a/frontend/src/pages/admin/agent-apps/request.vue b/frontend/src/pages/admin/agent-apps/request.vue deleted file mode 100644 index 90b67a8..0000000 --- a/frontend/src/pages/admin/agent-apps/request.vue +++ /dev/null @@ -1,170 +0,0 @@ - - - diff --git a/frontend/src/router/routes.ts b/frontend/src/router/routes.ts index 72e0557..b619940 100644 --- a/frontend/src/router/routes.ts +++ b/frontend/src/router/routes.ts @@ -171,7 +171,7 @@ const routes: RouteRecordRaw[] = [ path: 'agent-apps', name: 'AdminAgentApps', component: () => import('@/pages/admin/agent-apps/index.vue'), - meta: { title: '代理授权 - 管理后台' }, + meta: { title: '授权管理 - 管理后台' }, }, { path: 'agents', @@ -191,18 +191,6 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/pages/admin/agents/[id].vue'), meta: { title: '编辑代理 - 管理后台' }, }, - { - path: 'agent-apps/request', - name: 'AdminAgentAppRequest', - component: () => import('@/pages/admin/agent-apps/request.vue'), - meta: { title: '申请授权 - 管理后台' }, - }, - { - path: 'agent-apps/invite', - name: 'AdminAgentAppInvite', - component: () => import('@/pages/admin/agent-apps/invite.vue'), - meta: { title: '邀请授权 - 管理后台' }, - }, { path: 'agent-apps/:id/edit', name: 'AdminAgentAppEdit',