diff --git a/backend/internal/router/admin/email_config.go b/backend/internal/router/admin/email_config.go index 764a02e..0ac62dc 100644 --- a/backend/internal/router/admin/email_config.go +++ b/backend/internal/router/admin/email_config.go @@ -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": "批量删除成功", + }) +} diff --git a/backend/internal/router/admin/storage_config.go b/backend/internal/router/admin/storage_config.go index e74b02b..f75c287 100644 --- a/backend/internal/router/admin/storage_config.go +++ b/backend/internal/router/admin/storage_config.go @@ -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": "批量删除成功", + }) +} diff --git a/backend/internal/router/admin/system_settings.go b/backend/internal/router/admin/system_settings.go index 21b396c..861b1d4 100644 --- a/backend/internal/router/admin/system_settings.go +++ b/backend/internal/router/admin/system_settings.go @@ -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": "批量删除成功", + }) +} diff --git a/frontend/src/pages/admin/email-settings/components/data-table.vue b/frontend/src/pages/admin/email-settings/components/data-table.vue index 4b46912..52646e0 100644 --- a/frontend/src/pages/admin/email-settings/components/data-table.vue +++ b/frontend/src/pages/admin/email-settings/components/data-table.vue @@ -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, 'columns'> & { const emit = defineEmits<{ 'refresh': [] + 'batchEnable': [] + 'batchDisable': [] + 'batchDelete': [] }>() const t = (key: string) => key @@ -59,11 +63,24 @@ defineExpose({
-
+
@@ -173,6 +207,20 @@ onMounted(() => {
+ + + + + 启用率 + + + + +
+ {{ emailConfigs.length ? Math.round(activeCount / emailConfigs.length * 100) : 0 }}% +
+
+
@@ -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) || [])" /> @@ -206,6 +257,21 @@ onMounted(() => { + + + + + diff --git a/frontend/src/pages/admin/payment-channels/components/data-table.vue b/frontend/src/pages/admin/payment-channels/components/data-table.vue index 46c11fb..b27990c 100644 --- a/frontend/src/pages/admin/payment-channels/components/data-table.vue +++ b/frontend/src/pages/admin/payment-channels/components/data-table.vue @@ -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, 'columns'> & { const emit = defineEmits<{ 'refresh': [] + 'batchEnable': [] + 'batchDisable': [] + 'batchDelete': [] }>() const t = (key: string) => key @@ -57,11 +61,24 @@ defineExpose({
-
+
@@ -143,6 +177,20 @@ onMounted(() => {
+ + + + + 启用率 + + + + +
+ {{ paymentChannels.length ? Math.round(activeCount / paymentChannels.length * 100) : 0 }}% +
+
+
@@ -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) || [])" /> @@ -174,5 +225,20 @@ onMounted(() => { 确定要删除支付渠道"{{ deleteTarget?.name }}"吗?此操作不可撤销。 + + + + + diff --git a/frontend/src/pages/admin/storage-configs/components/data-table.vue b/frontend/src/pages/admin/storage-configs/components/data-table.vue index 10c5397..3915359 100644 --- a/frontend/src/pages/admin/storage-configs/components/data-table.vue +++ b/frontend/src/pages/admin/storage-configs/components/data-table.vue @@ -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, 'columns'> & { const emit = defineEmits<{ 'refresh': [] + 'batchEnable': [] + 'batchDisable': [] + 'batchDelete': [] }>() const t = (key: string) => key @@ -58,11 +62,24 @@ defineExpose({
-
+
@@ -154,11 +189,26 @@ onMounted(() => {
+ + + + + 启用率 + + + + +
+ {{ storageConfigs.length ? Math.round(activeCount / storageConfigs.length * 100) : 0 }}% +
+
+
{ :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) || [])" /> @@ -185,5 +238,20 @@ onMounted(() => { 确定要删除存储配置"{{ deleteTarget?.name }}"吗?此操作不可撤销。 + + + + +