修复: 1.管理员编辑代理云端数据权限switch无效(后端缺少CanViewCloudData字段) 2.代理卡密status显示disabledbanned 3.代理用户管理只显示代理/子代理卡密充值过的用户

This commit is contained in:
2026-05-10 14:12:35 +08:00
parent f3b9feca6a
commit 409ec4f75b
7 changed files with 99 additions and 50 deletions
+1
View File
@@ -29,6 +29,7 @@ type User struct {
ParentAgentID *uint `gorm:"index" json:"parent_agent_id"` ParentAgentID *uint `gorm:"index" json:"parent_agent_id"`
CanCreateAgent bool `gorm:"default:false" json:"can_create_agent"` CanCreateAgent bool `gorm:"default:false" json:"can_create_agent"`
CanViewCloudData bool `gorm:"default:false" json:"can_view_cloud_data"`
Balance float64 `gorm:"default:0" json:"balance"` Balance float64 `gorm:"default:0" json:"balance"`
TotalConsumption float64 `gorm:"default:0" json:"total_consumption"` TotalConsumption float64 `gorm:"default:0" json:"total_consumption"`
+35 -24
View File
@@ -42,7 +42,8 @@ type AgentTreeNode struct {
CreatedAt string `json:"created_at"` CreatedAt string `json:"created_at"`
LastLoginAt string `json:"last_login_at"` LastLoginAt string `json:"last_login_at"`
Balance float64 `json:"balance"` Balance float64 `json:"balance"`
CanCreateAgent bool `json:"can_create_agent"` CanCreateAgent bool `json:"can_create_agent"`
CanViewCloudData bool `json:"can_view_cloud_data"`
CardsCount int `json:"cards_count"` CardsCount int `json:"cards_count"`
ChildAgentsCount int `json:"child_agents_count"` ChildAgentsCount int `json:"child_agents_count"`
Children []AgentTreeNode `json:"children,omitempty"` Children []AgentTreeNode `json:"children,omitempty"`
@@ -80,6 +81,7 @@ func handleGetAgents(c *gin.Context) {
Balance float64 `json:"balance"` Balance float64 `json:"balance"`
TotalConsumption float64 `json:"total_consumption"` TotalConsumption float64 `json:"total_consumption"`
CanCreateAgent bool `json:"can_create_agent"` CanCreateAgent bool `json:"can_create_agent"`
CanViewCloudData bool `json:"can_view_cloud_data"`
CardsCount int `json:"cards_count"` CardsCount int `json:"cards_count"`
ChildAgentsCount int `json:"child_agents_count"` ChildAgentsCount int `json:"child_agents_count"`
} }
@@ -152,8 +154,9 @@ func handleGetAgents(c *gin.Context) {
LastLoginAt: lastLoginAt, LastLoginAt: lastLoginAt,
Balance: user.Balance, Balance: user.Balance,
TotalConsumption: user.TotalConsumption, TotalConsumption: user.TotalConsumption,
CanCreateAgent: user.CanCreateAgent, CanCreateAgent: user.CanCreateAgent,
CardsCount: cardsCountMap[user.ID], CanViewCloudData: user.CanViewCloudData,
CardsCount: cardsCountMap[user.ID],
ChildAgentsCount: childCountMap[user.ID], ChildAgentsCount: childCountMap[user.ID],
}) })
} }
@@ -229,8 +232,9 @@ func handleGetAgentsTree(c *gin.Context) {
CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"),
LastLoginAt: lastLoginAt, LastLoginAt: lastLoginAt,
Balance: user.Balance, Balance: user.Balance,
CanCreateAgent: user.CanCreateAgent, CanCreateAgent: user.CanCreateAgent,
CardsCount: int(cardsCount), CanViewCloudData: user.CanViewCloudData,
CardsCount: int(cardsCount),
ChildAgentsCount: int(childAgentsCount), ChildAgentsCount: int(childAgentsCount),
} }
@@ -256,12 +260,13 @@ func handleGetAgentsTree(c *gin.Context) {
func handleCreateAgent(c *gin.Context) { func handleCreateAgent(c *gin.Context) {
var req struct { var req struct {
Username string `json:"username" binding:"required"` Username string `json:"username" binding:"required"`
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password" binding:"required"` Password string `json:"password" binding:"required"`
ParentAgentID *uint `json:"parent_agent_id"` ParentAgentID *uint `json:"parent_agent_id"`
CanCreateAgent bool `json:"can_create_agent"` CanCreateAgent bool `json:"can_create_agent"`
Balance float64 `json:"balance"` CanViewCloudData bool `json:"can_view_cloud_data"`
Balance float64 `json:"balance"`
} }
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误") response.Error(c, 400, "参数错误")
@@ -296,13 +301,14 @@ func handleCreateAgent(c *gin.Context) {
} }
user := model.User{ user := model.User{
Username: req.Username, Username: req.Username,
Password: string(hashedPassword), Password: string(hashedPassword),
Role: "agent", Role: "agent",
Status: "active", Status: "active",
ParentAgentID: req.ParentAgentID, ParentAgentID: req.ParentAgentID,
CanCreateAgent: req.CanCreateAgent, CanCreateAgent: req.CanCreateAgent,
Balance: req.Balance, CanViewCloudData: req.CanViewCloudData,
Balance: req.Balance,
} }
if req.Email != "" { if req.Email != "" {
@@ -406,7 +412,8 @@ func handleGetAgentDetail(c *gin.Context) {
"role": user.Role, "role": user.Role,
"parent_agent_id": user.ParentAgentID, "parent_agent_id": user.ParentAgentID,
"parent_agent_name": parentAgentName, "parent_agent_name": parentAgentName,
"can_create_agent": user.CanCreateAgent, "can_create_agent": user.CanCreateAgent,
"can_view_cloud_data": user.CanViewCloudData,
"balance": user.Balance, "balance": user.Balance,
"created_at": user.CreatedAt.Format("2006-01-02 15:04:05"), "created_at": user.CreatedAt.Format("2006-01-02 15:04:05"),
"last_login_at": lastLoginAt, "last_login_at": lastLoginAt,
@@ -426,11 +433,12 @@ func handleUpdateAgent(c *gin.Context) {
} }
var req struct { var req struct {
ParentAgentID *uint `json:"parent_agent_id"` ParentAgentID *uint `json:"parent_agent_id"`
Email *string `json:"email"` Email *string `json:"email"`
Password *string `json:"password"` Password *string `json:"password"`
CanCreateAgent *bool `json:"can_create_agent"` CanCreateAgent *bool `json:"can_create_agent"`
Balance *float64 `json:"balance"` CanViewCloudData *bool `json:"can_view_cloud_data"`
Balance *float64 `json:"balance"`
} }
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, 400, "参数错误") response.Error(c, 400, "参数错误")
@@ -464,6 +472,9 @@ func handleUpdateAgent(c *gin.Context) {
if req.CanCreateAgent != nil { if req.CanCreateAgent != nil {
user.CanCreateAgent = *req.CanCreateAgent user.CanCreateAgent = *req.CanCreateAgent
} }
if req.CanViewCloudData != nil {
user.CanViewCloudData = *req.CanViewCloudData
}
if req.Balance != nil { if req.Balance != nil {
user.Balance = *req.Balance user.Balance = *req.Balance
} }
+40 -17
View File
@@ -181,8 +181,8 @@ func handleGetCards(c *gin.Context) {
var expiredCount int64 var expiredCount int64
database.DB.Model(&model.Card{}).Where("("+baseCondition+") AND status = ?", userID, userID, "expired").Count(&expiredCount) database.DB.Model(&model.Card{}).Where("("+baseCondition+") AND status = ?", userID, userID, "expired").Count(&expiredCount)
var disabledCount int64 var bannedCount int64
database.DB.Model(&model.Card{}).Where("("+baseCondition+") AND status = ?", userID, userID, "disabled").Count(&disabledCount) database.DB.Model(&model.Card{}).Where("("+baseCondition+") AND status = ?", userID, userID, "banned").Count(&bannedCount)
query := database.DB.Model(&model.Card{}).Where(baseCondition, userID, userID) query := database.DB.Model(&model.Card{}).Where(baseCondition, userID, userID)
@@ -283,7 +283,7 @@ func handleGetCards(c *gin.Context) {
"unused_count": unusedCount, "unused_count": unusedCount,
"used_count": usedCount, "used_count": usedCount,
"expired_count": expiredCount, "expired_count": expiredCount,
"disabled_count": disabledCount, "banned_count": bannedCount,
}) })
} }
@@ -412,17 +412,40 @@ func generateCardCode() string {
func handleGetUsers(c *gin.Context) { func handleGetUsers(c *gin.Context) {
userID := c.GetUint("user_id") userID := c.GetUint("user_id")
var agentApps []model.AgentApplication agentIDs := []uint{userID}
database.DB.Where("agent_id = ?", userID).Find(&agentApps) var childAgents []model.User
appIDs := make([]uint, 0) database.DB.Where("parent_agent_id = ? AND role = ?", userID, "agent").Find(&childAgents)
for _, app := range agentApps { for _, child := range childAgents {
appIDs = append(appIDs, app.ApplicationID) agentIDs = append(agentIDs, child.ID)
} }
if len(appIDs) == 0 { var cardIDs []uint
database.DB.Model(&model.Card{}).Where("agent_id IN ?", agentIDs).Pluck("id", &cardIDs)
if len(cardIDs) == 0 {
response.Success(c, gin.H{ response.Success(c, gin.H{
"users": []interface{}{}, "users": []interface{}{},
"total": 0, "total": 0,
"active_count": 0,
"disabled_count": 0,
"banned_count": 0,
})
return
}
var appUserIDs []uint
database.DB.Model(&model.RechargeRecord{}).
Where("card_id IN ? AND status = ?", cardIDs, "success").
Distinct("user_id").
Pluck("user_id", &appUserIDs)
if len(appUserIDs) == 0 {
response.Success(c, gin.H{
"users": []interface{}{},
"total": 0,
"active_count": 0,
"disabled_count": 0,
"banned_count": 0,
}) })
return return
} }
@@ -431,12 +454,12 @@ func handleGetUsers(c *gin.Context) {
pageSize := c.DefaultQuery("page_size", "20") pageSize := c.DefaultQuery("page_size", "20")
var total int64 var total int64
database.DB.Model(&model.AppUser{}).Where("application_id IN ?", appIDs).Count(&total) database.DB.Model(&model.AppUser{}).Where("id IN ?", appUserIDs).Count(&total)
var activeCount, disabledCount, bannedCount int64 var activeCount, disabledCount, bannedCount int64
database.DB.Model(&model.AppUser{}).Where("application_id IN ? AND status = ?", appIDs, "active").Count(&activeCount) database.DB.Model(&model.AppUser{}).Where("id IN ? AND status = ?", appUserIDs, "active").Count(&activeCount)
database.DB.Model(&model.AppUser{}).Where("application_id IN ? AND status = ?", appIDs, "disabled").Count(&disabledCount) database.DB.Model(&model.AppUser{}).Where("id IN ? AND status = ?", appUserIDs, "disabled").Count(&disabledCount)
database.DB.Model(&model.AppUser{}).Where("application_id IN ? AND status = ?", appIDs, "banned").Count(&bannedCount) database.DB.Model(&model.AppUser{}).Where("id IN ? AND status = ?", appUserIDs, "banned").Count(&bannedCount)
var users []model.AppUser var users []model.AppUser
offset := 0 offset := 0
@@ -449,7 +472,7 @@ func handleGetUsers(c *gin.Context) {
limit = pageSizeInt limit = pageSizeInt
} }
database.DB.Where("application_id IN ?", appIDs).Order("created_at DESC").Limit(limit).Offset(offset).Find(&users) database.DB.Where("id IN ?", appUserIDs).Order("created_at DESC").Limit(limit).Offset(offset).Find(&users)
response.Success(c, gin.H{ response.Success(c, gin.H{
"users": users, "users": users,
@@ -710,7 +733,7 @@ func handleExportCards(c *gin.Context) {
for _, card := range cards { for _, card := range cards {
ct := cardTypeMap[card.CardTypeID] ct := cardTypeMap[card.CardTypeID]
app := appMap[card.ApplicationID] app := appMap[card.ApplicationID]
statusMap := map[string]string{"unused": "未使用", "used": "已使用", "expired": "已过期", "disabled": "已禁用"} statusMap := map[string]string{"unused": "未使用", "used": "已使用", "expired": "已过期", "banned": "已禁用"}
statusText := statusMap[card.Status] statusText := statusMap[card.Status]
if statusText == "" { if statusText == "" {
statusText = card.Status statusText = card.Status
@@ -47,7 +47,7 @@ export function getColumns(t: Composer['t']): ColumnDef<Card>[] {
unused: { label: t('agent.cards.status.unused'), variant: 'default' }, unused: { label: t('agent.cards.status.unused'), variant: 'default' },
used: { label: t('agent.cards.status.used'), variant: 'secondary' }, used: { label: t('agent.cards.status.used'), variant: 'secondary' },
expired: { label: t('agent.cards.status.expired'), variant: 'destructive' }, expired: { label: t('agent.cards.status.expired'), variant: 'destructive' },
disabled: { label: t('agent.cards.status.disabled'), variant: 'secondary' }, banned: { label: t('agent.cards.status.banned'), variant: 'destructive' },
} }
const { label, variant } = statusMap[status] || { label: status || '-', variant: 'secondary' } const { label, variant } = statusMap[status] || { label: status || '-', variant: 'secondary' }
return h(Badge, { variant }, () => label) return h(Badge, { variant }, () => label)
+18 -4
View File
@@ -32,7 +32,7 @@ const total = ref(0)
const unusedCount = ref(0) const unusedCount = ref(0)
const usedCount = ref(0) const usedCount = ref(0)
const expiredCount = ref(0) const expiredCount = ref(0)
const disabledCount = ref(0) const bannedCount = ref(0)
const currentPage = ref(1) const currentPage = ref(1)
const pageSize = ref(20) const pageSize = ref(20)
const tableRef = ref() const tableRef = ref()
@@ -74,7 +74,7 @@ const statusOptions = computed(() => [
{ label: t('agent.cards.status.unused'), value: 'unused' }, { label: t('agent.cards.status.unused'), value: 'unused' },
{ label: t('agent.cards.status.used'), value: 'used' }, { label: t('agent.cards.status.used'), value: 'used' },
{ label: t('agent.cards.status.expired'), value: 'expired' }, { label: t('agent.cards.status.expired'), value: 'expired' },
{ label: t('agent.cards.status.disabled'), value: 'disabled' }, { label: t('agent.cards.status.banned'), value: 'banned' },
]) ])
const serverPagination = computed(() => ({ const serverPagination = computed(() => ({
@@ -152,7 +152,7 @@ async function fetchCards() {
unusedCount.value = data.unused_count || 0 unusedCount.value = data.unused_count || 0
usedCount.value = data.used_count || 0 usedCount.value = data.used_count || 0
expiredCount.value = data.expired_count || 0 expiredCount.value = data.expired_count || 0
disabledCount.value = data.disabled_count || 0 bannedCount.value = data.banned_count || data.disabled_count || 0
} else if (Array.isArray(data)) { } else if (Array.isArray(data)) {
cards.value = data cards.value = data
total.value = data.length total.value = data.length
@@ -241,7 +241,7 @@ onMounted(async () => {
</template> </template>
<div class="space-y-6"> <div class="space-y-6">
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
<UiCard> <UiCard>
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0"> <UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
<UiCardTitle class="text-sm font-medium"> <UiCardTitle class="text-sm font-medium">
@@ -297,6 +297,20 @@ onMounted(async () => {
</div> </div>
</UiCardContent> </UiCardContent>
</UiCard> </UiCard>
<UiCard>
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
<UiCardTitle class="text-sm font-medium">
{{ t('agent.cards.banned') }}
</UiCardTitle>
<Key class="size-4 text-muted-foreground" />
</UiCardHeader>
<UiCardContent>
<div class="text-2xl font-bold">
{{ bannedCount }}
</div>
</UiCardContent>
</UiCard>
</div> </div>
<UiCard> <UiCard>
+2 -2
View File
@@ -2819,7 +2819,7 @@
"unused": "Unused", "unused": "Unused",
"used": "Used", "used": "Used",
"expired": "Expired", "expired": "Expired",
"disabled": "Disabled", "banned": "Banned",
"totalCards": "Total Cards", "totalCards": "Total Cards",
"searchPlaceholder": "Search card code, app or card type...", "searchPlaceholder": "Search card code, app or card type...",
"exportBtn": "Export", "exportBtn": "Export",
@@ -2840,7 +2840,7 @@
"unused": "Unused", "unused": "Unused",
"used": "Used", "used": "Used",
"expired": "Expired", "expired": "Expired",
"disabled": "Disabled" "banned": "Banned"
}, },
"create": { "create": {
"title": "Generate Cards", "title": "Generate Cards",
+2 -2
View File
@@ -2820,7 +2820,7 @@
"unused": "未使用", "unused": "未使用",
"used": "已使用", "used": "已使用",
"expired": "已过期", "expired": "已过期",
"disabled": "已禁用", "banned": "已禁用",
"totalCards": "卡密总数", "totalCards": "卡密总数",
"searchPlaceholder": "搜索卡号、应用或卡类...", "searchPlaceholder": "搜索卡号、应用或卡类...",
"exportBtn": "导出", "exportBtn": "导出",
@@ -2841,7 +2841,7 @@
"unused": "未使用", "unused": "未使用",
"used": "已使用", "used": "已使用",
"expired": "已过期", "expired": "已过期",
"disabled": "已禁用" "banned": "已禁用"
}, },
"create": { "create": {
"title": "生成卡密", "title": "生成卡密",