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