feat: 添加查看授权和编辑授权功能, 代理充值使用 NumberField 组件
This commit is contained in:
@@ -661,8 +661,9 @@ func handleUpdateAgentCardTypes(c *gin.Context) {
|
|||||||
|
|
||||||
var reqBody struct {
|
var reqBody struct {
|
||||||
CardTypes []struct {
|
CardTypes []struct {
|
||||||
CardTypeID uint `json:"card_type_id"`
|
CardTypeID uint `json:"card_type_id"`
|
||||||
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 {
|
||||||
@@ -689,6 +690,7 @@ func handleUpdateAgentCardTypes(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()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<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 { computed, onMounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { toast } from 'vue-sonner'
|
import { toast } from 'vue-sonner'
|
||||||
@@ -17,6 +17,17 @@ const agentApps = ref<AgentApp[]>([])
|
|||||||
const showRemoveDialog = ref(false)
|
const showRemoveDialog = ref(false)
|
||||||
const removingItem = ref<AgentApp | null>(null)
|
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 activeCount = computed(() => agentApps.value.filter(app => app.status === 'active').length)
|
||||||
const inactiveCount = computed(() => agentApps.value.filter(app => app.status === 'inactive').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))
|
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')
|
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) {
|
function confirmRemove(item: AgentApp) {
|
||||||
removingItem.value = item
|
removingItem.value = item
|
||||||
showRemoveDialog.value = true
|
showRemoveDialog.value = true
|
||||||
@@ -147,7 +207,7 @@ onMounted(() => {
|
|||||||
<UiCardTitle class="text-sm font-medium">
|
<UiCardTitle class="text-sm font-medium">
|
||||||
代理余额总计
|
代理余额总计
|
||||||
</UiCardTitle>
|
</UiCardTitle>
|
||||||
<Wallet class="size-4 text-muted-foreground" />
|
<Key class="size-4 text-muted-foreground" />
|
||||||
</UiCardHeader>
|
</UiCardHeader>
|
||||||
<UiCardContent>
|
<UiCardContent>
|
||||||
<div class="text-2xl font-bold">
|
<div class="text-2xl font-bold">
|
||||||
@@ -256,6 +316,15 @@ onMounted(() => {
|
|||||||
</UiButton>
|
</UiButton>
|
||||||
</UiDropdownMenuTrigger>
|
</UiDropdownMenuTrigger>
|
||||||
<UiDropdownMenuContent align="end">
|
<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)">
|
<UiDropdownMenuItem class="text-destructive" @click="confirmRemove(item)">
|
||||||
<Trash2 class="mr-2 h-4 w-4" />
|
<Trash2 class="mr-2 h-4 w-4" />
|
||||||
移除授权
|
移除授权
|
||||||
@@ -277,5 +346,157 @@ onMounted(() => {
|
|||||||
confirm-text="确定移除"
|
confirm-text="确定移除"
|
||||||
@confirm="handleRemove"
|
@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>
|
</BasicPage>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -408,13 +408,13 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<UiLabel>充值金额</UiLabel>
|
<UiLabel>充值金额</UiLabel>
|
||||||
<UiInput
|
<UiNumberField v-model="rechargeAmount" :min="0" :step="0.01">
|
||||||
v-model.number="rechargeAmount"
|
<UiNumberFieldContent>
|
||||||
type="number"
|
<UiNumberFieldDecrement />
|
||||||
min="0"
|
<UiNumberFieldInput />
|
||||||
step="0.01"
|
<UiNumberFieldIncrement />
|
||||||
placeholder="请输入充值金额"
|
</UiNumberFieldContent>
|
||||||
/>
|
</UiNumberField>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<UiButton
|
<UiButton
|
||||||
|
|||||||
Reference in New Issue
Block a user