feat: 支付渠道、邮箱配置、存储管理页面添加批量操作功能

- 添加表格多选功能
- 添加批量启用/禁用/删除功能
- 添加统计卡片(总数、已启用、已禁用、启用率)
- 后端添加批量操作API接口
- 存储配置批量删除保护本地存储
This commit is contained in:
2026-05-02 18:07:19 +08:00
parent 9b82fbef4e
commit 00067b1bff
9 changed files with 525 additions and 15 deletions
@@ -20,6 +20,8 @@ func SetupEmailConfigRoutes(r *gin.RouterGroup) {
emailConfigs.DELETE("/:id", handleDeleteSystemEmailConfig)
emailConfigs.PUT("/:id/status", handleUpdateSystemEmailConfigStatus)
emailConfigs.POST("/:id/test", handleTestSystemEmailConfig)
emailConfigs.PUT("/batch/status", handleBatchUpdateEmailConfigStatus)
emailConfigs.DELETE("/batch", handleBatchDeleteEmailConfigs)
}
}
@@ -301,3 +303,84 @@ func handleTestSystemEmailConfig(c *gin.Context) {
"message": "测试邮件已发送",
})
}
type BatchUpdateEmailConfigStatusRequest struct {
IDs []uint `json:"ids"`
Status string `json:"status"`
}
func handleBatchUpdateEmailConfigStatus(c *gin.Context) {
var req BatchUpdateEmailConfigStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
})
return
}
if req.Status != "active" && req.Status != "inactive" {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的状态",
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请选择要更新的邮箱配置",
})
return
}
if err := database.DB.Model(&model.EmailConfig{}).Where("id IN ?", req.IDs).Update("status", req.Status).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "批量更新失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "批量更新成功",
})
}
type BatchDeleteEmailConfigsRequest struct {
IDs []uint `json:"ids"`
}
func handleBatchDeleteEmailConfigs(c *gin.Context) {
var req BatchDeleteEmailConfigsRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请选择要删除的邮箱配置",
})
return
}
if err := database.DB.Where("id IN ?", req.IDs).Delete(&model.EmailConfig{}).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "批量删除失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "批量删除成功",
})
}
@@ -20,6 +20,8 @@ func SetupStorageConfigRoutes(r *gin.RouterGroup) {
storageConfigs.PUT("/:id/status", handleUpdateStorageConfigStatus)
storageConfigs.PUT("/:id/default", handleSetDefaultStorageConfig)
storageConfigs.POST("/:id/test", handleTestStorageConfig)
storageConfigs.PUT("/batch/status", handleBatchUpdateStorageConfigStatus)
storageConfigs.DELETE("/batch", handleBatchDeleteStorageConfigs)
}
}
@@ -317,3 +319,94 @@ func handleTestStorageConfig(c *gin.Context) {
"message": "连接成功",
})
}
type BatchUpdateStorageConfigStatusRequest struct {
IDs []uint `json:"ids"`
Status string `json:"status"`
}
func handleBatchUpdateStorageConfigStatus(c *gin.Context) {
var req BatchUpdateStorageConfigStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
})
return
}
if req.Status != "active" && req.Status != "inactive" {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的状态",
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请选择要更新的存储配置",
})
return
}
if err := database.DB.Model(&model.StorageConfig{}).Where("id IN ?", req.IDs).Update("status", req.Status).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "批量更新失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "批量更新成功",
})
}
type BatchDeleteStorageConfigsRequest struct {
IDs []uint `json:"ids"`
}
func handleBatchDeleteStorageConfigs(c *gin.Context) {
var req BatchDeleteStorageConfigsRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请选择要删除的存储配置",
})
return
}
var localConfigs []model.StorageConfig
database.DB.Where("id IN ? AND type = ?", req.IDs, "local").Find(&localConfigs)
if len(localConfigs) > 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "本地存储不能删除",
})
return
}
if err := database.DB.Where("id IN ?", req.IDs).Delete(&model.StorageConfig{}).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "批量删除失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "批量删除成功",
})
}
@@ -35,6 +35,8 @@ func SetupSystemSettingsRoutes(r *gin.RouterGroup) {
paymentChannels.PUT("/:id", handleUpdatePaymentChannel)
paymentChannels.DELETE("/:id", handleDeletePaymentChannel)
paymentChannels.PUT("/:id/status", handleUpdatePaymentChannelStatus)
paymentChannels.PUT("/batch/status", handleBatchUpdatePaymentChannelStatus)
paymentChannels.DELETE("/batch", handleBatchDeletePaymentChannels)
}
}
@@ -682,3 +684,84 @@ func handleUpdatePaymentChannelStatus(c *gin.Context) {
"message": "更新成功",
})
}
type BatchUpdatePaymentChannelStatusRequest struct {
IDs []uint `json:"ids"`
Status string `json:"status"`
}
func handleBatchUpdatePaymentChannelStatus(c *gin.Context) {
var req BatchUpdatePaymentChannelStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
})
return
}
if req.Status != "active" && req.Status != "inactive" {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的状态",
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请选择要更新的支付渠道",
})
return
}
if err := database.DB.Model(&model.PaymentChannel{}).Where("id IN ?", req.IDs).Update("status", req.Status).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "批量更新失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "批量更新成功",
})
}
type BatchDeletePaymentChannelsRequest struct {
IDs []uint `json:"ids"`
}
func handleBatchDeletePaymentChannels(c *gin.Context) {
var req BatchDeletePaymentChannelsRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
})
return
}
if len(req.IDs) == 0 {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "请选择要删除的支付渠道",
})
return
}
if err := database.DB.Where("id IN ?", req.IDs).Delete(&model.PaymentChannel{}).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "批量删除失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "批量删除成功",
})
}
@@ -6,6 +6,7 @@ import { computed } from 'vue'
import type { DataTableProps } from '@/components/data-table/types'
import type { EmailConfig } from '@/pages/admin/email-settings/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'
@@ -21,6 +22,9 @@ const props = defineProps<Omit<DataTableProps<EmailConfig>, 'columns'> & {
const emit = defineEmits<{
'refresh': []
'batchEnable': []
'batchDisable': []
'batchDelete': []
}>()
const t = (key: string) => key
@@ -59,11 +63,24 @@ defineExpose({
<template>
<DataTable :columns="columns" :data :loading :table @refresh="emit('refresh')">
<template #toolbar>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="text-sm text-muted-foreground">
{{ data.length }} 个邮箱配置
<div class="space-y-3">
<BulkActions :table="table" entity-name="email-configs">
<UiButton variant="outline" size="sm" @click="emit('batchEnable')">
批量启用
</UiButton>
<UiButton variant="outline" size="sm" @click="emit('batchDisable')">
批量禁用
</UiButton>
<UiButton variant="destructive" size="sm" @click="emit('batchDelete')">
批量删除
</UiButton>
</BulkActions>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="text-sm text-muted-foreground">
{{ data.length }} 个邮箱配置
</div>
<DataTableViewOptions :table="table" :column-labels="columnLabels" />
</div>
<DataTableViewOptions :table="table" :column-labels="columnLabels" />
</div>
</template>
</DataTable>
@@ -19,6 +19,8 @@ const tableRef = ref()
const deleteDialogOpen = ref(false)
const deleteTarget = ref<EmailConfig | null>(null)
const batchDeleteDialogOpen = ref(false)
const batchDeleteIds = ref<number[]>([])
const testDialogOpen = ref(false)
const testTarget = ref<EmailConfig | null>(null)
@@ -87,6 +89,38 @@ async function handleDelete() {
}
}
function confirmBatchDelete(ids: number[]) {
batchDeleteIds.value = ids
batchDeleteDialogOpen.value = true
}
async function handleBatchDelete() {
try {
await api.delete('/dev/email-configs/batch', { ids: batchDeleteIds.value } as any)
toast.success('批量删除成功')
fetchEmailConfigs()
}
catch (error: any) {
console.error('批量删除失败:', error)
toast.error(error.message || '批量删除失败')
}
finally {
batchDeleteIds.value = []
}
}
async function batchToggleStatus(ids: number[], status: string) {
try {
await api.put('/dev/email-configs/batch/status', { ids, status })
toast.success('批量更新成功')
fetchEmailConfigs()
}
catch (error: any) {
console.error('批量更新状态失败:', error)
toast.error(error.message || '批量更新失败')
}
}
function openTestDialog(config: EmailConfig) {
testTarget.value = config
testEmail.value = ''
@@ -131,7 +165,7 @@ onMounted(() => {
</template>
<div class="space-y-6">
<div class="grid gap-4 sm:grid-cols-3">
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<UiCard>
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
<UiCardTitle class="text-sm font-medium">
@@ -173,6 +207,20 @@ onMounted(() => {
</div>
</UiCardContent>
</UiCard>
<UiCard>
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
<UiCardTitle class="text-sm font-medium">
启用率
</UiCardTitle>
<Icon icon="lucide:percent" class="size-4 text-muted-foreground" />
</UiCardHeader>
<UiCardContent>
<div class="text-2xl font-bold">
{{ emailConfigs.length ? Math.round(activeCount / emailConfigs.length * 100) : 0 }}%
</div>
</UiCardContent>
</UiCard>
</div>
<UiCard>
@@ -186,6 +234,9 @@ onMounted(() => {
:on-delete="confirmDelete"
:on-test="openTestDialog"
@refresh="fetchEmailConfigs"
@batch-enable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'inactive').map((r: any) => r.original.id) || [], 'active')"
@batch-disable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'active').map((r: any) => r.original.id) || [], 'inactive')"
@batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])"
/>
</UiCardContent>
</UiCard>
@@ -206,6 +257,21 @@ onMounted(() => {
</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>
<UiDialog v-model:open="testDialogOpen">
<UiDialogContent class="sm:max-w-md">
<UiDialogHeader>
@@ -6,6 +6,7 @@ import { computed } from 'vue'
import type { DataTableProps } from '@/components/data-table/types'
import type { PaymentChannel } from '@/pages/admin/payment-channels/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'
@@ -20,6 +21,9 @@ const props = defineProps<Omit<DataTableProps<PaymentChannel>, 'columns'> & {
const emit = defineEmits<{
'refresh': []
'batchEnable': []
'batchDisable': []
'batchDelete': []
}>()
const t = (key: string) => key
@@ -57,11 +61,24 @@ defineExpose({
<template>
<DataTable :columns="columns" :data :loading :table @refresh="emit('refresh')">
<template #toolbar>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="text-sm text-muted-foreground">
{{ data.length }} 个支付渠道
<div class="space-y-3">
<BulkActions :table="table" entity-name="payment-channels">
<UiButton variant="outline" size="sm" @click="emit('batchEnable')">
批量启用
</UiButton>
<UiButton variant="outline" size="sm" @click="emit('batchDisable')">
批量禁用
</UiButton>
<UiButton variant="destructive" size="sm" @click="emit('batchDelete')">
批量删除
</UiButton>
</BulkActions>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="text-sm text-muted-foreground">
{{ data.length }} 个支付渠道
</div>
<DataTableViewOptions :table="table" :column-labels="columnLabels" />
</div>
<DataTableViewOptions :table="table" :column-labels="columnLabels" />
</div>
</template>
</DataTable>
@@ -19,6 +19,8 @@ const tableRef = ref()
const deleteDialogOpen = ref(false)
const deleteTarget = ref<PaymentChannel | null>(null)
const batchDeleteDialogOpen = ref(false)
const batchDeleteIds = ref<number[]>([])
const activeCount = computed(() => paymentChannels.value.filter(c => c.status === 'active').length)
const inactiveCount = computed(() => paymentChannels.value.filter(c => c.status === 'inactive').length)
@@ -82,6 +84,38 @@ async function handleDelete() {
}
}
function confirmBatchDelete(ids: number[]) {
batchDeleteIds.value = ids
batchDeleteDialogOpen.value = true
}
async function handleBatchDelete() {
try {
await api.delete('/dev/payment-channels/batch', { ids: batchDeleteIds.value } as any)
toast.success('批量删除成功')
fetchPaymentChannels()
}
catch (error: any) {
console.error('批量删除失败:', error)
toast.error(error.message || '批量删除失败')
}
finally {
batchDeleteIds.value = []
}
}
async function batchToggleStatus(ids: number[], status: string) {
try {
await api.put('/dev/payment-channels/batch/status', { ids, status })
toast.success('批量更新成功')
fetchPaymentChannels()
}
catch (error: any) {
console.error('批量更新状态失败:', error)
toast.error(error.message || '批量更新失败')
}
}
onMounted(() => {
fetchPaymentChannels()
})
@@ -101,7 +135,7 @@ onMounted(() => {
</template>
<div class="space-y-6">
<div class="grid gap-4 sm:grid-cols-3">
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<UiCard>
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
<UiCardTitle class="text-sm font-medium">
@@ -143,6 +177,20 @@ onMounted(() => {
</div>
</UiCardContent>
</UiCard>
<UiCard>
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
<UiCardTitle class="text-sm font-medium">
启用率
</UiCardTitle>
<Icon icon="lucide:percent" class="size-4 text-muted-foreground" />
</UiCardHeader>
<UiCardContent>
<div class="text-2xl font-bold">
{{ paymentChannels.length ? Math.round(activeCount / paymentChannels.length * 100) : 0 }}%
</div>
</UiCardContent>
</UiCard>
</div>
<UiCard>
@@ -155,6 +203,9 @@ onMounted(() => {
:on-edit="goToEdit"
:on-delete="confirmDelete"
@refresh="fetchPaymentChannels"
@batch-enable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'inactive').map((r: any) => r.original.id) || [], 'active')"
@batch-disable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'active').map((r: any) => r.original.id) || [], 'inactive')"
@batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])"
/>
</UiCardContent>
</UiCard>
@@ -174,5 +225,20 @@ onMounted(() => {
确定要删除支付渠道"{{ deleteTarget?.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>
@@ -6,6 +6,7 @@ import { computed } from 'vue'
import type { DataTableProps } from '@/components/data-table/types'
import type { StorageConfig } from '@/pages/admin/storage-configs/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'
@@ -21,6 +22,9 @@ const props = defineProps<Omit<DataTableProps<StorageConfig>, 'columns'> & {
const emit = defineEmits<{
'refresh': []
'batchEnable': []
'batchDisable': []
'batchDelete': []
}>()
const t = (key: string) => key
@@ -58,11 +62,24 @@ defineExpose({
<template>
<DataTable :columns="columns" :data :loading :table @refresh="emit('refresh')">
<template #toolbar>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="text-sm text-muted-foreground">
{{ data.length }} 个存储配置
<div class="space-y-3">
<BulkActions :table="table" entity-name="storage-configs">
<UiButton variant="outline" size="sm" @click="emit('batchEnable')">
批量启用
</UiButton>
<UiButton variant="outline" size="sm" @click="emit('batchDisable')">
批量禁用
</UiButton>
<UiButton variant="destructive" size="sm" @click="emit('batchDelete')">
批量删除
</UiButton>
</BulkActions>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="text-sm text-muted-foreground">
{{ data.length }} 个存储配置
</div>
<DataTableViewOptions :table="table" :column-labels="columnLabels" />
</div>
<DataTableViewOptions :table="table" :column-labels="columnLabels" />
</div>
</template>
</DataTable>
@@ -15,9 +15,12 @@ const router = useRouter()
const loading = ref(true)
const storageConfigs = ref<StorageConfig[]>([])
const tableRef = ref()
const deleteDialogOpen = ref(false)
const deleteTarget = ref<StorageConfig | null>(null)
const batchDeleteDialogOpen = ref(false)
const batchDeleteIds = ref<number[]>([])
const activeCount = computed(() => storageConfigs.value.filter(c => c.status === 'active').length)
const inactiveCount = computed(() => storageConfigs.value.filter(c => c.status === 'inactive').length)
@@ -93,6 +96,38 @@ async function handleDelete() {
}
}
function confirmBatchDelete(ids: number[]) {
batchDeleteIds.value = ids
batchDeleteDialogOpen.value = true
}
async function handleBatchDelete() {
try {
await api.delete('/dev/storage-configs/batch', { ids: batchDeleteIds.value } as any)
toast.success('批量删除成功')
fetchStorageConfigs()
}
catch (error: any) {
console.error('批量删除失败:', error)
toast.error(error.message || '批量删除失败')
}
finally {
batchDeleteIds.value = []
}
}
async function batchToggleStatus(ids: number[], status: string) {
try {
await api.put('/dev/storage-configs/batch/status', { ids, status })
toast.success('批量更新成功')
fetchStorageConfigs()
}
catch (error: any) {
console.error('批量更新状态失败:', error)
toast.error(error.message || '批量更新失败')
}
}
onMounted(() => {
fetchStorageConfigs()
})
@@ -112,7 +147,7 @@ onMounted(() => {
</template>
<div class="space-y-6">
<div class="grid gap-4 sm:grid-cols-3">
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
<UiCard>
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
<UiCardTitle class="text-sm font-medium">
@@ -154,11 +189,26 @@ onMounted(() => {
</div>
</UiCardContent>
</UiCard>
<UiCard>
<UiCardHeader class="flex flex-row items-center justify-between pb-2 space-y-0">
<UiCardTitle class="text-sm font-medium">
启用率
</UiCardTitle>
<Icon icon="lucide:percent" class="size-4 text-muted-foreground" />
</UiCardHeader>
<UiCardContent>
<div class="text-2xl font-bold">
{{ storageConfigs.length ? Math.round(activeCount / storageConfigs.length * 100) : 0 }}%
</div>
</UiCardContent>
</UiCard>
</div>
<UiCard>
<UiCardContent class="p-6">
<DataTable
ref="tableRef"
:loading="loading"
:data="storageConfigs"
:on-toggle-status="toggleStatus"
@@ -166,6 +216,9 @@ onMounted(() => {
:on-edit="goToEdit"
:on-delete="confirmDelete"
@refresh="fetchStorageConfigs"
@batch-enable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'inactive').map((r: any) => r.original.id) || [], 'active')"
@batch-disable="batchToggleStatus(tableRef?.table?.getSelectedRowModel().rows.filter((r: any) => r.original.status === 'active').map((r: any) => r.original.id) || [], 'inactive')"
@batch-delete="confirmBatchDelete(tableRef?.table?.getSelectedRowModel().rows.map((r: any) => r.original.id) || [])"
/>
</UiCardContent>
</UiCard>
@@ -185,5 +238,20 @@ onMounted(() => {
确定要删除存储配置"{{ deleteTarget?.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>