fix: 修复授权详情API返回格式,重构授权管理页面表格和批量操作
This commit is contained in:
@@ -568,9 +568,11 @@ func handleGetAgentAppDetail(c *gin.Context) {
|
||||
}
|
||||
|
||||
type CardTypeResponse struct {
|
||||
ID uint `json:"id"`
|
||||
CardTypeID uint `json:"card_type_id"`
|
||||
Name string `json:"name"`
|
||||
BillingType string `json:"billing_type"`
|
||||
Price float64 `json:"price"`
|
||||
OriginPrice float64 `json:"origin_price"`
|
||||
CanGenerate bool `json:"can_generate"`
|
||||
}
|
||||
|
||||
@@ -583,6 +585,7 @@ func handleGetAgentAppDetail(c *gin.Context) {
|
||||
AppName string `json:"app_name"`
|
||||
Discount float64 `json:"discount"`
|
||||
Status string `json:"status"`
|
||||
Balance float64 `json:"balance"`
|
||||
CardTypes []CardTypeResponse `json:"card_types"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
@@ -590,9 +593,11 @@ func handleGetAgentAppDetail(c *gin.Context) {
|
||||
var cardTypes []CardTypeResponse
|
||||
for _, ct := range agentApp.CardTypes {
|
||||
cardTypes = append(cardTypes, CardTypeResponse{
|
||||
ID: ct.CardTypeID,
|
||||
CardTypeID: ct.CardTypeID,
|
||||
Name: ct.CardType.Name,
|
||||
Price: ct.CardType.Price,
|
||||
BillingType: ct.CardType.RechargeType,
|
||||
Price: ct.Price,
|
||||
OriginPrice: ct.CardType.Price,
|
||||
CanGenerate: ct.CanGenerate,
|
||||
})
|
||||
}
|
||||
@@ -605,6 +610,7 @@ func handleGetAgentAppDetail(c *gin.Context) {
|
||||
AppName: application.Name,
|
||||
Discount: agentApp.Discount,
|
||||
Status: agentApp.Status,
|
||||
Balance: agent.Balance,
|
||||
CardTypes: cardTypes,
|
||||
CreatedAt: agentApp.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
@@ -613,7 +619,9 @@ func handleGetAgentAppDetail(c *gin.Context) {
|
||||
result.AgentEmail = *agent.Email
|
||||
}
|
||||
|
||||
response.Success(c, result)
|
||||
response.Success(c, gin.H{
|
||||
"agent_app": result,
|
||||
})
|
||||
}
|
||||
|
||||
func handleUpdateAgentApp(c *gin.Context) {
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import type { ColumnDef } from '@tanstack/vue-table'
|
||||
|
||||
import { Eye, MoreHorizontal, Pencil, Trash2 } from 'lucide-vue-next'
|
||||
import { h } from 'vue'
|
||||
|
||||
import type { AgentApp } from '@/pages/admin/agent-apps/data/schema'
|
||||
|
||||
import Badge from '@/components/ui/badge/Badge.vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
|
||||
export function getColumns(actions: {
|
||||
onView: (row: AgentApp) => void
|
||||
onEdit: (row: AgentApp) => void
|
||||
onDelete: (row: AgentApp) => void
|
||||
}): ColumnDef<AgentApp>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'agent_name',
|
||||
header: '代理',
|
||||
cell: ({ row }) => {
|
||||
const name = row.getValue('agent_name') as string
|
||||
const email = row.original.agent_email
|
||||
return h('div', { class: 'flex items-center space-x-3' }, [
|
||||
h('div', { class: 'h-8 w-8 rounded bg-primary/10 flex items-center justify-center flex-shrink-0' }, [
|
||||
h('span', { class: 'text-primary font-bold text-sm' }, name ? name.charAt(0).toUpperCase() : '?'),
|
||||
]),
|
||||
h('div', {}, [
|
||||
h('p', { class: 'font-medium' }, name || '-'),
|
||||
email ? h('p', { class: 'text-xs text-muted-foreground' }, email) : null,
|
||||
]),
|
||||
])
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'app_name',
|
||||
header: '应用',
|
||||
cell: ({ row }) => {
|
||||
const appName = row.getValue('app_name') as string
|
||||
return appName ? h(Badge, { variant: 'secondary' }, () => appName) : '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'card_types',
|
||||
header: '卡密权限',
|
||||
cell: ({ row }) => {
|
||||
const cardTypes = row.original.card_types || []
|
||||
if (cardTypes.length === 0)
|
||||
return h('span', { class: 'text-muted-foreground text-sm' }, '-')
|
||||
const authorized = cardTypes.filter(ct => ct.can_generate).length
|
||||
return h('span', { class: 'text-sm' }, `${authorized}/${cardTypes.length}`)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'balance',
|
||||
header: '余额',
|
||||
cell: ({ row }) => {
|
||||
const balance = row.getValue('balance') as number
|
||||
return h('span', { class: 'font-medium' }, `¥${(balance || 0).toFixed(2)}`)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: '状态',
|
||||
cell: ({ row }) => {
|
||||
const status = row.getValue('status') as string
|
||||
const isActive = status === 'active'
|
||||
return h(Badge, { variant: isActive ? 'default' : 'secondary' }, () => isActive ? '已启用' : '已禁用')
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'created_at',
|
||||
header: '授权时间',
|
||||
cell: ({ row }) => {
|
||||
const createdAt = row.getValue('created_at')
|
||||
if (!createdAt)
|
||||
return '-'
|
||||
try {
|
||||
const date = new Date(createdAt as string)
|
||||
if (Number.isNaN(date.getTime()))
|
||||
return '-'
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
catch {
|
||||
return '-'
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: () => h('span', { class: 'sr-only' }, '操作'),
|
||||
cell: ({ row }) => {
|
||||
const item = row.original
|
||||
return h(
|
||||
DropdownMenu,
|
||||
{},
|
||||
{
|
||||
default: () => [
|
||||
h(DropdownMenuTrigger, { asChild: true }, () =>
|
||||
h(Button, { variant: 'ghost', class: 'h-8 w-8 p-0' }, () => [
|
||||
h(MoreHorizontal, { class: 'h-4 w-4' }),
|
||||
h('span', { class: 'sr-only' }, '打开菜单'),
|
||||
]),
|
||||
),
|
||||
h(
|
||||
DropdownMenuContent,
|
||||
{ align: 'end' },
|
||||
() => [
|
||||
h(DropdownMenuItem, { onClick: () => actions.onView(item) }, () => [
|
||||
h(Eye, { class: 'mr-2 h-4 w-4' }),
|
||||
'查看授权',
|
||||
]),
|
||||
h(DropdownMenuItem, { onClick: () => actions.onEdit(item) }, () => [
|
||||
h(Pencil, { class: 'mr-2 h-4 w-4' }),
|
||||
'编辑授权',
|
||||
]),
|
||||
h(DropdownMenuSeparator),
|
||||
h(DropdownMenuItem, { class: 'text-destructive', onClick: () => actions.onDelete(item) }, () => [
|
||||
h(Trash2, { class: 'mr-2 h-4 w-4' }),
|
||||
'移除授权',
|
||||
]),
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
},
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import type { ColumnDef } from '@tanstack/vue-table'
|
||||
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { DataTableProps } from '@/components/data-table/types'
|
||||
import type { AgentApp } from '@/pages/admin/agent-apps/data/schema'
|
||||
|
||||
import BulkActions from '@/components/data-table/bulk-actions.vue'
|
||||
import DataTable from '@/components/data-table/data-table.vue'
|
||||
import { SelectColumn } from '@/components/data-table/table-columns'
|
||||
import { generateVueTable } from '@/components/data-table/use-generate-vue-table'
|
||||
import DataTableViewOptions from '@/components/data-table/view-options.vue'
|
||||
import { getColumns } from '@/pages/admin/agent-apps/components/columns'
|
||||
|
||||
const props = defineProps<Omit<DataTableProps<AgentApp>, 'columns'> & {
|
||||
onView: (row: AgentApp) => void
|
||||
onEdit: (row: AgentApp) => void
|
||||
onDelete: (row: AgentApp) => void
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
refresh: []
|
||||
batchDelete: [ids: (string | number)[]]
|
||||
}>()
|
||||
|
||||
const columns = computed(() => [
|
||||
SelectColumn as ColumnDef<AgentApp>,
|
||||
...getColumns({
|
||||
onView: props.onView,
|
||||
onEdit: props.onEdit,
|
||||
onDelete: props.onDelete,
|
||||
}),
|
||||
])
|
||||
|
||||
const table = generateVueTable<AgentApp>({
|
||||
get data() { return props.data },
|
||||
get loading() { return props.loading },
|
||||
columns: columns.value,
|
||||
}, columns.value)
|
||||
|
||||
const columnLabels: Record<string, string> = {
|
||||
'select': '选择',
|
||||
'agent_name': '代理',
|
||||
'app_name': '应用',
|
||||
'card_types': '卡密权限',
|
||||
'balance': '余额',
|
||||
'status': '状态',
|
||||
'created_at': '授权时间',
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
table,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DataTable :columns="columns" :data :loading :table @refresh="emit('refresh')">
|
||||
<template #toolbar>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-end">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
<BulkActions :table="table" entity-name="agent-apps">
|
||||
<UiButton variant="destructive" size="sm" @click="emit('batchDelete', table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])">
|
||||
批量移除
|
||||
</UiButton>
|
||||
</BulkActions>
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<slot name="filters" />
|
||||
</div>
|
||||
<DataTableViewOptions :table="table" :column-labels="columnLabels" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</DataTable>
|
||||
</template>
|
||||
@@ -1,45 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckCircle, Eye, Key, MoreHorizontal, Pencil, Plus, Share2, Trash2, XCircle } from 'lucide-vue-next'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { CheckCircle, Key, Plus, Share2, XCircle } from 'lucide-vue-next'
|
||||
import { computed, onActivated, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
import type { AgentApp } from '@/pages/admin/agent-apps/data/schema'
|
||||
|
||||
import ConfirmDialog from '@/components/confirm-dialog.vue'
|
||||
import SingleFilter from '@/components/data-table/single-filter.vue'
|
||||
import { BasicPage } from '@/components/global-layout'
|
||||
import DataTable from '@/pages/admin/agent-apps/components/data-table.vue'
|
||||
import api from '@/services/api'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(true)
|
||||
const agentApps = ref<AgentApp[]>([])
|
||||
const showRemoveDialog = ref(false)
|
||||
const removingItem = ref<AgentApp | null>(null)
|
||||
const tableRef = ref()
|
||||
|
||||
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))
|
||||
const statusFilter = ref<string>('')
|
||||
|
||||
const searchFilter = ref('')
|
||||
const statusFilter = ref('all')
|
||||
const deleteDialogOpen = ref(false)
|
||||
const deleteTarget = ref<AgentApp | null>(null)
|
||||
const batchDeleteDialogOpen = ref(false)
|
||||
const batchDeleteIds = ref<(string | number)[]>([])
|
||||
|
||||
const filteredApps = computed(() => {
|
||||
let result = agentApps.value
|
||||
if (statusFilter.value && statusFilter.value !== 'all') {
|
||||
|
||||
if (statusFilter.value) {
|
||||
result = result.filter(app => app.status === statusFilter.value)
|
||||
}
|
||||
if (searchFilter.value) {
|
||||
const search = searchFilter.value.toLowerCase()
|
||||
result = result.filter(app =>
|
||||
app.agent_name?.toLowerCase().includes(search)
|
||||
|| app.app_name?.toLowerCase().includes(search)
|
||||
|| app.agent_email?.toLowerCase().includes(search),
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
const activeCount = computed(() => filteredApps.value.filter(app => app.status === 'active').length)
|
||||
const inactiveCount = computed(() => filteredApps.value.filter(app => app.status === 'inactive').length)
|
||||
const totalBalance = computed(() => filteredApps.value.reduce((sum, app) => sum + (app.balance || 0), 0))
|
||||
|
||||
const statusOptions = computed(() => [
|
||||
{ label: '已启用', value: 'active' },
|
||||
{ label: '已禁用', value: 'inactive' },
|
||||
])
|
||||
|
||||
async function fetchData() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -67,30 +71,58 @@ function handleEdit(item: AgentApp) {
|
||||
router.push(`/admin/agent-apps/${item.id}/edit`)
|
||||
}
|
||||
|
||||
function confirmRemove(item: AgentApp) {
|
||||
removingItem.value = item
|
||||
showRemoveDialog.value = true
|
||||
function confirmDelete(item: AgentApp) {
|
||||
deleteTarget.value = item
|
||||
deleteDialogOpen.value = true
|
||||
}
|
||||
|
||||
async function handleRemove() {
|
||||
if (!removingItem.value)
|
||||
async function handleDelete() {
|
||||
if (!deleteTarget.value)
|
||||
return
|
||||
|
||||
try {
|
||||
await api.delete(`/dev/agent-apps/${removingItem.value.id}`)
|
||||
await api.delete(`/dev/agent-apps/${deleteTarget.value.id}`)
|
||||
toast.success('移除授权成功')
|
||||
showRemoveDialog.value = false
|
||||
fetchData()
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('移除失败:', error)
|
||||
toast.error(error.message || '移除失败')
|
||||
}
|
||||
finally {
|
||||
deleteTarget.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function confirmBatchDelete(ids: (string | number)[]) {
|
||||
batchDeleteIds.value = ids
|
||||
batchDeleteDialogOpen.value = true
|
||||
}
|
||||
|
||||
async function handleBatchDelete() {
|
||||
try {
|
||||
for (const id of batchDeleteIds.value) {
|
||||
await api.delete(`/dev/agent-apps/${id}`)
|
||||
}
|
||||
toast.success('批量移除成功')
|
||||
fetchData()
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('批量移除失败:', error)
|
||||
toast.error(error.message || '批量移除失败')
|
||||
}
|
||||
finally {
|
||||
batchDeleteIds.value = []
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
fetchData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -117,7 +149,7 @@ onMounted(() => {
|
||||
</UiCardHeader>
|
||||
<UiCardContent>
|
||||
<div class="text-2xl font-bold">
|
||||
{{ agentApps.length }}
|
||||
{{ filteredApps.length }}
|
||||
</div>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
@@ -167,132 +199,56 @@ onMounted(() => {
|
||||
|
||||
<UiCard>
|
||||
<UiCardContent class="p-6">
|
||||
<div class="flex flex-wrap items-center gap-2 mb-4">
|
||||
<UiInput
|
||||
v-model="searchFilter"
|
||||
placeholder="搜索代理名称、应用名称..."
|
||||
class="h-9 w-[200px]"
|
||||
/>
|
||||
<UiSelect v-model="statusFilter">
|
||||
<UiSelectTrigger class="h-9 w-[120px]">
|
||||
<UiSelectValue placeholder="全部状态" />
|
||||
</UiSelectTrigger>
|
||||
<UiSelectContent>
|
||||
<UiSelectItem value="all">
|
||||
全部状态
|
||||
</UiSelectItem>
|
||||
<UiSelectItem value="active">
|
||||
已启用
|
||||
</UiSelectItem>
|
||||
<UiSelectItem value="inactive">
|
||||
已禁用
|
||||
</UiSelectItem>
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<div class="i-lucide-loader-2 h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<div v-else-if="filteredApps.length === 0" class="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
||||
<Share2 class="h-12 w-12 mb-4" />
|
||||
<p>暂无授权记录</p>
|
||||
<p class="text-sm mt-1">
|
||||
点击"新建授权"为代理分配应用权限
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<UiTable v-else>
|
||||
<UiTableHeader>
|
||||
<UiTableRow>
|
||||
<UiTableHead>代理</UiTableHead>
|
||||
<UiTableHead>应用</UiTableHead>
|
||||
<UiTableHead>卡密权限</UiTableHead>
|
||||
<UiTableHead>余额</UiTableHead>
|
||||
<UiTableHead>状态</UiTableHead>
|
||||
<UiTableHead>授权时间</UiTableHead>
|
||||
<UiTableHead class="text-right">
|
||||
操作
|
||||
</UiTableHead>
|
||||
</UiTableRow>
|
||||
</UiTableHeader>
|
||||
<UiTableBody>
|
||||
<UiTableRow v-for="item in filteredApps" :key="item.id">
|
||||
<UiTableCell>
|
||||
<div>
|
||||
<div class="font-medium">
|
||||
{{ item.agent_name }}
|
||||
</div>
|
||||
<div v-if="item.agent_email" class="text-xs text-muted-foreground">
|
||||
{{ item.agent_email }}
|
||||
</div>
|
||||
</div>
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
<UiBadge variant="secondary">
|
||||
{{ item.app_name }}
|
||||
</UiBadge>
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
<div v-if="item.card_types && item.card_types.length > 0" class="flex items-center gap-1">
|
||||
<Key class="h-3 w-3 text-muted-foreground" />
|
||||
<span class="text-sm">
|
||||
{{ item.card_types.filter(ct => ct.can_generate).length }}/{{ item.card_types.length }}
|
||||
</span>
|
||||
</div>
|
||||
<span v-else class="text-muted-foreground text-sm">-</span>
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
<span class="font-medium">¥{{ (item.balance || 0).toFixed(2) }}</span>
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
<UiBadge
|
||||
:variant="item.status === 'active' ? 'default' : 'secondary'"
|
||||
>
|
||||
{{ item.status === 'active' ? '已启用' : '已禁用' }}
|
||||
</UiBadge>
|
||||
</UiTableCell>
|
||||
<UiTableCell class="text-muted-foreground text-sm">
|
||||
{{ item.created_at }}
|
||||
</UiTableCell>
|
||||
<UiTableCell class="text-right">
|
||||
<UiDropdownMenu>
|
||||
<UiDropdownMenuTrigger as-child>
|
||||
<UiButton variant="ghost" size="sm">
|
||||
<MoreHorizontal class="h-4 w-4" />
|
||||
</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" />
|
||||
移除授权
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuContent>
|
||||
</UiDropdownMenu>
|
||||
</UiTableCell>
|
||||
</UiTableRow>
|
||||
</UiTableBody>
|
||||
</UiTable>
|
||||
<DataTable
|
||||
ref="tableRef"
|
||||
:loading
|
||||
:data="filteredApps"
|
||||
:on-view="handleView"
|
||||
:on-edit="handleEdit"
|
||||
:on-delete="confirmDelete"
|
||||
@refresh="fetchData"
|
||||
@batch-delete="confirmBatchDelete"
|
||||
>
|
||||
<template #filters>
|
||||
<SingleFilter
|
||||
v-model="statusFilter"
|
||||
title="状态"
|
||||
:options="statusOptions"
|
||||
/>
|
||||
</template>
|
||||
</DataTable>
|
||||
</UiCardContent>
|
||||
</UiCard>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog
|
||||
v-model:open="showRemoveDialog"
|
||||
title="移除授权"
|
||||
:description="`确定要移除 ${removingItem?.agent_name} 对 ${removingItem?.app_name} 的授权吗?`"
|
||||
confirm-text="确定移除"
|
||||
@confirm="handleRemove"
|
||||
/>
|
||||
v-model:open="deleteDialogOpen"
|
||||
destructive
|
||||
confirm-button-text="确定移除"
|
||||
cancel-button-text="取消"
|
||||
@confirm="handleDelete"
|
||||
>
|
||||
<template #title>
|
||||
移除授权
|
||||
</template>
|
||||
<template #description>
|
||||
确定要移除 {{ deleteTarget?.agent_name }} 对 {{ deleteTarget?.app_name }} 的授权吗?
|
||||
</template>
|
||||
</ConfirmDialog>
|
||||
|
||||
<ConfirmDialog
|
||||
v-model:open="batchDeleteDialogOpen"
|
||||
destructive
|
||||
confirm-button-text="确定移除"
|
||||
cancel-button-text="取消"
|
||||
@confirm="handleBatchDelete"
|
||||
>
|
||||
<template #title>
|
||||
批量移除授权
|
||||
</template>
|
||||
<template #description>
|
||||
确定要移除选中的 {{ batchDeleteIds.length }} 项授权吗?
|
||||
</template>
|
||||
</ConfirmDialog>
|
||||
</BasicPage>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user