feat: 授权管理改为设置卡类价格,代理管理增加消费列
This commit is contained in:
@@ -27,9 +27,10 @@ type User struct {
|
||||
ApiCallsResetAt *time.Time `json:"api_calls_reset_at"`
|
||||
ApiToken string `gorm:"size:64" json:"api_token"`
|
||||
|
||||
ParentAgentID *uint `gorm:"index" json:"parent_agent_id"`
|
||||
CanCreateAgent bool `gorm:"default:false" json:"can_create_agent"`
|
||||
Balance float64 `gorm:"default:0" json:"balance"`
|
||||
ParentAgentID *uint `gorm:"index" json:"parent_agent_id"`
|
||||
CanCreateAgent bool `gorm:"default:false" json:"can_create_agent"`
|
||||
Balance float64 `gorm:"default:0" json:"balance"`
|
||||
TotalConsumption float64 `gorm:"default:0" json:"total_consumption"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
@@ -626,6 +627,7 @@ type AgentApplicationCardType struct {
|
||||
AgentApplicationID uint `json:"agent_application_id"`
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
CanGenerate bool `gorm:"default:false" json:"can_generate"`
|
||||
Price float64 `gorm:"type:decimal(10,2);default:0" json:"price"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
|
||||
@@ -98,10 +98,13 @@ func handleGetAgentApps(c *gin.Context) {
|
||||
}
|
||||
|
||||
type CardTypeResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
Name string `json:"name"`
|
||||
CanGenerate bool `json:"can_generate"`
|
||||
ID uint `json:"id"`
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
Name string `json:"name"`
|
||||
BillingType string `json:"billing_type"`
|
||||
CanGenerate bool `json:"can_generate"`
|
||||
Price float64 `json:"price"`
|
||||
OriginPrice float64 `json:"origin_price"`
|
||||
}
|
||||
|
||||
type AgentAppResponse struct {
|
||||
@@ -142,7 +145,10 @@ func handleGetAgentApps(c *gin.Context) {
|
||||
ID: ct.ID,
|
||||
CardTypeID: ct.CardTypeID,
|
||||
Name: ct.CardType.Name,
|
||||
BillingType: ct.CardType.RechargeType,
|
||||
CanGenerate: ct.CanGenerate,
|
||||
Price: ct.Price,
|
||||
OriginPrice: ct.CardType.Price,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -746,8 +752,9 @@ func handleDirectAuthorize(c *gin.Context) {
|
||||
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"`
|
||||
CardTypeID uint `json:"card_type_id" binding:"required"`
|
||||
CanGenerate bool `json:"can_generate"`
|
||||
Price float64 `json:"price"`
|
||||
} `json:"card_types"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&reqBody); err != nil {
|
||||
@@ -823,6 +830,7 @@ func handleDirectAuthorize(c *gin.Context) {
|
||||
AgentApplicationID: agentApp.ID,
|
||||
CardTypeID: ct.CardTypeID,
|
||||
CanGenerate: ct.CanGenerate,
|
||||
Price: ct.Price,
|
||||
}
|
||||
if err := tx.Create(&agentCardType).Error; err != nil {
|
||||
tx.Rollback()
|
||||
@@ -838,6 +846,7 @@ func handleDirectAuthorize(c *gin.Context) {
|
||||
AgentApplicationID: agentApp.ID,
|
||||
CardTypeID: ct.ID,
|
||||
CanGenerate: false,
|
||||
Price: ct.Price,
|
||||
}
|
||||
if err := tx.Create(&agentCardType).Error; err != nil {
|
||||
tx.Rollback()
|
||||
|
||||
@@ -36,8 +36,7 @@ const cardTypes = ref<CardType[]>([])
|
||||
const form = ref({
|
||||
agent_id: '',
|
||||
application_id: '',
|
||||
discount: 1.0,
|
||||
card_types: [] as Array<{ card_type_id: number, can_generate: boolean }>,
|
||||
card_types: [] as Array<{ card_type_id: number, can_generate: boolean, price: number }>,
|
||||
})
|
||||
|
||||
async function fetchAgents() {
|
||||
@@ -72,6 +71,7 @@ async function fetchCardTypes(applicationId: string) {
|
||||
form.value.card_types = cardTypes.value.map(ct => ({
|
||||
card_type_id: ct.id,
|
||||
can_generate: false,
|
||||
price: ct.price,
|
||||
}))
|
||||
}
|
||||
catch (error) {
|
||||
@@ -139,7 +139,6 @@ async function handleSave() {
|
||||
await api.post('/dev/agent-apps/authorize', {
|
||||
agent_id: Number(form.value.agent_id),
|
||||
application_id: Number(form.value.application_id),
|
||||
discount: form.value.discount,
|
||||
card_types: form.value.card_types,
|
||||
})
|
||||
toast.success('授权成功')
|
||||
@@ -218,26 +217,6 @@ onMounted(() => {
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<UiLabel>折扣</UiLabel>
|
||||
<div class="flex items-center gap-4">
|
||||
<UiInput
|
||||
v-model.number="form.discount"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
max="1"
|
||||
class="flex-1"
|
||||
/>
|
||||
<span class="text-sm text-muted-foreground w-20">
|
||||
{{ (form.discount * 10).toFixed(1) }}折
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
代理购买卡密的折扣,范围0.01-1
|
||||
</p>
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
|
||||
@@ -247,7 +226,7 @@ onMounted(() => {
|
||||
<Key class="size-5" />
|
||||
卡密权限
|
||||
</UiCardTitle>
|
||||
<UiCardDescription>配置代理可生成的卡密类型</UiCardDescription>
|
||||
<UiCardDescription>配置代理可生成的卡密类型及价格</UiCardDescription>
|
||||
</UiCardHeader>
|
||||
<UiCardContent class="space-y-4">
|
||||
<div class="flex gap-2">
|
||||
@@ -268,7 +247,8 @@ onMounted(() => {
|
||||
<UiTableHead class="w-12" />
|
||||
<UiTableHead>卡类名称</UiTableHead>
|
||||
<UiTableHead>计费类型</UiTableHead>
|
||||
<UiTableHead>价格</UiTableHead>
|
||||
<UiTableHead>原价</UiTableHead>
|
||||
<UiTableHead>授权价格</UiTableHead>
|
||||
</UiTableRow>
|
||||
</UiTableHeader>
|
||||
<UiTableBody>
|
||||
@@ -297,6 +277,16 @@ onMounted(() => {
|
||||
<UiTableCell>
|
||||
¥{{ cardTypes[index]?.price || 0 }}
|
||||
</UiTableCell>
|
||||
<UiTableCell @click.stop>
|
||||
<UiInput
|
||||
v-model.number="ct.price"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0"
|
||||
class="w-24 h-8"
|
||||
@click.stop
|
||||
/>
|
||||
</UiTableCell>
|
||||
</UiTableRow>
|
||||
</UiTableBody>
|
||||
</UiTable>
|
||||
@@ -323,13 +313,9 @@ onMounted(() => {
|
||||
<span class="text-muted-foreground">应用</span>
|
||||
<span>{{ selectedApplication?.name || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-muted-foreground">折扣</span>
|
||||
<span>{{ (form.discount * 10).toFixed(1) }}折</span>
|
||||
</div>
|
||||
<div v-if="cardTypes.length > 0" class="flex justify-between text-sm">
|
||||
<span class="text-muted-foreground">卡密权限</span>
|
||||
<span class="text-primary">{{ enabledCardTypesCount }}/{{ cardTypes.length }}</span>
|
||||
<span>{{ enabledCardTypesCount }}/{{ cardTypes.length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</UiCardContent>
|
||||
|
||||
@@ -2,6 +2,16 @@ import { z } from 'zod'
|
||||
|
||||
export const agentAppStatusSchema = z.enum(['active', 'inactive'])
|
||||
|
||||
export const cardTypeSchema = z.object({
|
||||
id: z.number().optional(),
|
||||
card_type_id: z.number(),
|
||||
name: z.string().optional(),
|
||||
billing_type: z.string().optional(),
|
||||
can_generate: z.boolean(),
|
||||
price: z.number().optional(),
|
||||
origin_price: z.number().optional(),
|
||||
})
|
||||
|
||||
export const agentAppSchema = z.object({
|
||||
id: z.number(),
|
||||
agent_id: z.number(),
|
||||
@@ -12,13 +22,9 @@ export const agentAppSchema = z.object({
|
||||
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(),
|
||||
card_types: z.array(cardTypeSchema).optional(),
|
||||
created_at: z.string(),
|
||||
})
|
||||
|
||||
export type CardTypeAuth = z.infer<typeof cardTypeSchema>
|
||||
export type AgentApp = z.infer<typeof agentAppSchema>
|
||||
|
||||
@@ -80,6 +80,15 @@ async function handleRemove() {
|
||||
}
|
||||
}
|
||||
|
||||
function getBillingTypeText(type: string) {
|
||||
const map: Record<string, string> = {
|
||||
subscription: '订阅',
|
||||
time: '计时',
|
||||
point: '点卡',
|
||||
}
|
||||
return map[type] || type
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
@@ -119,10 +128,10 @@ onMounted(() => {
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
已启用
|
||||
</UiCardTitle>
|
||||
<CheckCircle class="size-4 text-green-500" />
|
||||
<CheckCircle class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold text-green-600">
|
||||
<div class="text-2xl font-bold">
|
||||
{{ activeCount }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
@@ -133,10 +142,10 @@ onMounted(() => {
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
已禁用
|
||||
</UiCardTitle>
|
||||
<XCircle class="size-4 text-orange-500" />
|
||||
<XCircle class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold text-orange-600">
|
||||
<div class="text-2xl font-bold">
|
||||
{{ inactiveCount }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
@@ -147,10 +156,10 @@ onMounted(() => {
|
||||
<UiCardTitle class="text-sm font-medium">
|
||||
代理余额总计
|
||||
</UiCardTitle>
|
||||
<Wallet class="size-4 text-blue-500" />
|
||||
<Wallet class="size-4 text-muted-foreground" />
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold text-blue-600">
|
||||
<div class="text-2xl font-bold">
|
||||
¥{{ totalBalance.toFixed(2) }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
@@ -201,7 +210,6 @@ onMounted(() => {
|
||||
<UiTableHead>代理</UiTableHead>
|
||||
<UiTableHead>应用</UiTableHead>
|
||||
<UiTableHead>卡密权限</UiTableHead>
|
||||
<UiTableHead>折扣</UiTableHead>
|
||||
<UiTableHead>余额</UiTableHead>
|
||||
<UiTableHead>状态</UiTableHead>
|
||||
<UiTableHead>授权时间</UiTableHead>
|
||||
@@ -236,16 +244,12 @@ onMounted(() => {
|
||||
</div>
|
||||
<span v-else class="text-muted-foreground text-sm">-</span>
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
{{ (item.discount * 10).toFixed(1) }}折
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
<span class="font-medium">¥{{ (item.balance || 0).toFixed(2) }}</span>
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
<UiBadge
|
||||
:variant="item.status === 'active' ? 'default' : 'secondary'"
|
||||
:class="item.status === 'active' ? 'bg-green-500/10 text-green-600 hover:bg-green-500/20' : 'bg-orange-500/10 text-orange-600 hover:bg-orange-500/20'"
|
||||
>
|
||||
{{ item.status === 'active' ? '已启用' : '已禁用' }}
|
||||
</UiBadge>
|
||||
|
||||
@@ -97,6 +97,14 @@ export function getColumns(actions: {
|
||||
return `¥${balance.toFixed(2)}`
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'total_consumption',
|
||||
header: () => t('admin.agents.columns.totalConsumption'),
|
||||
cell: ({ row }) => {
|
||||
const consumption = row.getValue('total_consumption') as number
|
||||
return `¥${consumption.toFixed(2)}`
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'created_at',
|
||||
header: () => t('admin.agents.columns.createdAt'),
|
||||
|
||||
@@ -57,6 +57,7 @@ const columnLabels: Record<string, string> = {
|
||||
cards_count: 'admin.agents.columns.cardsCount',
|
||||
child_agents_count: 'admin.agents.columns.childAgentsCount',
|
||||
balance: 'admin.agents.columns.balance',
|
||||
total_consumption: 'admin.agents.columns.totalConsumption',
|
||||
created_at: 'admin.agents.columns.createdAt',
|
||||
last_login_at: 'admin.agents.columns.lastLoginAt',
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface Agent {
|
||||
created_at: string
|
||||
last_login_at: string
|
||||
balance: number
|
||||
total_consumption: number
|
||||
can_create_agent: boolean
|
||||
cards_count: number
|
||||
child_agents_count: number
|
||||
|
||||
@@ -1201,6 +1201,7 @@
|
||||
"childAgentsCount": "下级代理",
|
||||
"canCreateAgent": "创建代理权限",
|
||||
"balance": "余额",
|
||||
"totalConsumption": "消费",
|
||||
"createdAt": "注册时间",
|
||||
"lastLoginAt": "最后登录"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user