feat: 添加查看授权和编辑授权功能, 代理充值使用 NumberField 组件

This commit is contained in:
2026-05-07 01:34:05 +08:00
parent a2eccb9818
commit 7a3353ee96
3 changed files with 234 additions and 11 deletions
+4 -2
View File
@@ -661,8 +661,9 @@ func handleUpdateAgentCardTypes(c *gin.Context) {
var reqBody struct {
CardTypes []struct {
CardTypeID uint `json:"card_type_id"`
CanGenerate bool `json:"can_generate"`
CardTypeID uint `json:"card_type_id"`
CanGenerate bool `json:"can_generate"`
Price float64 `json:"price"`
} `json:"card_types"`
}
if err := c.ShouldBindJSON(&reqBody); err != nil {
@@ -689,6 +690,7 @@ func handleUpdateAgentCardTypes(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()
+223 -2
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { CheckCircle, Key, MoreHorizontal, Plus, Share2, Trash2, XCircle } from 'lucide-vue-next'
import { CheckCircle, Eye, Key, MoreHorizontal, Pencil, Plus, Share2, Trash2, XCircle } from 'lucide-vue-next'
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { toast } from 'vue-sonner'
@@ -17,6 +17,17 @@ const agentApps = ref<AgentApp[]>([])
const showRemoveDialog = ref(false)
const removingItem = ref<AgentApp | null>(null)
const viewDialogOpen = ref(false)
const viewItem = ref<AgentApp | null>(null)
const editDialogOpen = ref(false)
const editItem = ref<AgentApp | null>(null)
const editSaving = ref(false)
const editForm = ref({
status: 'active',
card_types: [] as Array<{ card_type_id: number, can_generate: boolean, price: number, name: string }>,
})
const activeCount = computed(() => agentApps.value.filter(app => app.status === 'active').length)
const inactiveCount = computed(() => agentApps.value.filter(app => app.status === 'inactive').length)
const totalBalance = computed(() => agentApps.value.reduce((sum, app) => sum + (app.balance || 0), 0))
@@ -59,6 +70,55 @@ function goToCreate() {
router.push('/admin/agent-apps/create')
}
function handleView(item: AgentApp) {
viewItem.value = item
viewDialogOpen.value = true
}
function handleEdit(item: AgentApp) {
editItem.value = item
editForm.value = {
status: item.status || 'active',
card_types: (item.card_types || []).map(ct => ({
card_type_id: ct.card_type_id || ct.id,
can_generate: ct.can_generate,
price: ct.price || 0,
name: ct.name || '',
})),
}
editDialogOpen.value = true
}
async function handleEditSubmit() {
if (!editItem.value) return
editSaving.value = true
try {
await api.put(`/dev/agent-apps/${editItem.value.id}`, {
status: editForm.value.status,
})
await api.put(`/dev/agent-apps/${editItem.value.id}/card-types`, {
card_types: editForm.value.card_types.map(ct => ({
card_type_id: ct.card_type_id,
can_generate: ct.can_generate,
price: ct.price,
})),
})
toast.success('保存成功')
editDialogOpen.value = false
fetchData()
}
catch (error: any) {
console.error('保存失败:', error)
toast.error(error.message || '保存失败')
}
finally {
editSaving.value = false
}
}
function confirmRemove(item: AgentApp) {
removingItem.value = item
showRemoveDialog.value = true
@@ -147,7 +207,7 @@ onMounted(() => {
<UiCardTitle class="text-sm font-medium">
代理余额总计
</UiCardTitle>
<Wallet class="size-4 text-muted-foreground" />
<Key class="size-4 text-muted-foreground" />
</UiCardHeader>
<UiCardContent>
<div class="text-2xl font-bold">
@@ -256,6 +316,15 @@ onMounted(() => {
</UiButton>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent align="end">
<UiDropdownMenuItem @click="handleView(item)">
<Eye class="mr-2 h-4 w-4" />
查看授权
</UiDropdownMenuItem>
<UiDropdownMenuItem @click="handleEdit(item)">
<Pencil class="mr-2 h-4 w-4" />
编辑授权
</UiDropdownMenuItem>
<UiDropdownMenuSeparator />
<UiDropdownMenuItem class="text-destructive" @click="confirmRemove(item)">
<Trash2 class="mr-2 h-4 w-4" />
移除授权
@@ -277,5 +346,157 @@ onMounted(() => {
confirm-text="确定移除"
@confirm="handleRemove"
/>
<UiDialog v-model:open="viewDialogOpen">
<UiDialogContent class="sm:max-w-lg">
<UiDialogHeader>
<UiDialogTitle>查看授权</UiDialogTitle>
<UiDialogDescription>
{{ viewItem?.agent_name }} - {{ viewItem?.app_name }}
</UiDialogDescription>
</UiDialogHeader>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-1">
<span class="text-sm text-muted-foreground">代理</span>
<p class="font-medium">{{ viewItem?.agent_name }}</p>
</div>
<div class="space-y-1">
<span class="text-sm text-muted-foreground">应用</span>
<p class="font-medium">{{ viewItem?.app_name }}</p>
</div>
<div class="space-y-1">
<span class="text-sm text-muted-foreground">状态</span>
<UiBadge :variant="viewItem?.status === 'active' ? 'default' : 'secondary'">
{{ viewItem?.status === 'active' ? '已启用' : '已禁用' }}
</UiBadge>
</div>
<div class="space-y-1">
<span class="text-sm text-muted-foreground">余额</span>
<p class="font-medium">¥{{ (viewItem?.balance || 0).toFixed(2) }}</p>
</div>
<div class="space-y-1">
<span class="text-sm text-muted-foreground">授权时间</span>
<p class="font-medium">{{ viewItem?.created_at }}</p>
</div>
</div>
<div class="space-y-2">
<span class="text-sm text-muted-foreground">卡密权限</span>
<div v-if="viewItem?.card_types && viewItem.card_types.length > 0" class="border rounded-lg">
<UiTable>
<UiTableHeader>
<UiTableRow>
<UiTableHead>卡类名称</UiTableHead>
<UiTableHead>授权价格</UiTableHead>
<UiTableHead>状态</UiTableHead>
</UiTableRow>
</UiTableHeader>
<UiTableBody>
<UiTableRow v-for="ct in viewItem.card_types" :key="ct.card_type_id || ct.id">
<UiTableCell>{{ ct.name }}</UiTableCell>
<UiTableCell>¥{{ ct.price || 0 }}</UiTableCell>
<UiTableCell>
<UiBadge :variant="ct.can_generate ? 'default' : 'secondary'">
{{ ct.can_generate ? '已授权' : '未授权' }}
</UiBadge>
</UiTableCell>
</UiTableRow>
</UiTableBody>
</UiTable>
</div>
<p v-else class="text-muted-foreground text-sm">暂无卡密权限</p>
</div>
</div>
<UiDialogFooter>
<UiButton variant="outline" @click="viewDialogOpen = false">
关闭
</UiButton>
</UiDialogFooter>
</UiDialogContent>
</UiDialog>
<UiDialog v-model:open="editDialogOpen">
<UiDialogContent class="sm:max-w-2xl">
<UiDialogHeader>
<UiDialogTitle>编辑授权</UiDialogTitle>
<UiDialogDescription>
{{ editItem?.agent_name }} - {{ editItem?.app_name }}
</UiDialogDescription>
</UiDialogHeader>
<div class="space-y-4">
<div class="space-y-2">
<UiLabel>状态</UiLabel>
<UiSelect v-model="editForm.status">
<UiSelectTrigger>
<UiSelectValue placeholder="请选择状态" />
</UiSelectTrigger>
<UiSelectContent>
<UiSelectItem value="active">
已启用
</UiSelectItem>
<UiSelectItem value="inactive">
已禁用
</UiSelectItem>
</UiSelectContent>
</UiSelect>
</div>
<div class="space-y-2">
<UiLabel>卡密权限</UiLabel>
<div v-if="editForm.card_types.length > 0" class="border rounded-lg">
<UiTable>
<UiTableHeader>
<UiTableRow>
<UiTableHead class="w-12" />
<UiTableHead>卡类名称</UiTableHead>
<UiTableHead>授权价格</UiTableHead>
</UiTableRow>
</UiTableHeader>
<UiTableBody>
<UiTableRow
v-for="ct in editForm.card_types"
:key="ct.card_type_id"
:class="ct.can_generate ? 'bg-primary/10' : ''"
class="cursor-pointer"
@click="ct.can_generate = !ct.can_generate"
>
<UiTableCell>
<UiCheckbox
:checked="ct.can_generate"
@update:checked="ct.can_generate = $event"
@click.stop
/>
</UiTableCell>
<UiTableCell class="font-medium">
{{ ct.name }}
</UiTableCell>
<UiTableCell @click.stop>
<UiNumberField v-model="ct.price" :min="0" class="w-28">
<UiNumberFieldContent>
<UiNumberFieldDecrement />
<UiNumberFieldInput />
<UiNumberFieldIncrement />
</UiNumberFieldContent>
</UiNumberField>
</UiTableCell>
</UiTableRow>
</UiTableBody>
</UiTable>
</div>
<p v-else class="text-muted-foreground text-sm">暂无卡密权限</p>
</div>
</div>
<UiDialogFooter>
<UiButton variant="outline" @click="editDialogOpen = false">
取消
</UiButton>
<UiButton :disabled="editSaving" @click="handleEditSubmit">
<span v-if="editSaving" class="i-lucide-loader-2 mr-2 h-4 w-4 animate-spin" />
保存
</UiButton>
</UiDialogFooter>
</UiDialogContent>
</UiDialog>
</BasicPage>
</template>
+7 -7
View File
@@ -408,13 +408,13 @@ onMounted(() => {
</div>
<div class="space-y-2">
<UiLabel>充值金额</UiLabel>
<UiInput
v-model.number="rechargeAmount"
type="number"
min="0"
step="0.01"
placeholder="请输入充值金额"
/>
<UiNumberField v-model="rechargeAmount" :min="0" :step="0.01">
<UiNumberFieldContent>
<UiNumberFieldDecrement />
<UiNumberFieldInput />
<UiNumberFieldIncrement />
</UiNumberFieldContent>
</UiNumberField>
</div>
<div class="flex flex-wrap gap-2">
<UiButton