diff --git a/controller/token.go b/controller/token.go index c57552c0..173fc22e 100644 --- a/controller/token.go +++ b/controller/token.go @@ -258,3 +258,32 @@ func UpdateToken(c *gin.Context) { }) return } + +type TokenBatch struct { + Ids []int `json:"ids"` +} + +func DeleteTokenBatch(c *gin.Context) { + tokenBatch := TokenBatch{} + if err := c.ShouldBindJSON(&tokenBatch); err != nil || len(tokenBatch.Ids) == 0 { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": "参数错误", + }) + return + } + userId := c.GetInt("id") + count, err := model.BatchDeleteTokens(tokenBatch.Ids, userId) + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "", + "data": count, + }) +} diff --git a/model/token.go b/model/token.go index 2ed2c09a..7e68f185 100644 --- a/model/token.go +++ b/model/token.go @@ -327,3 +327,37 @@ func CountUserTokens(userId int) (int64, error) { err := DB.Model(&Token{}).Where("user_id = ?", userId).Count(&total).Error return total, err } + +// BatchDeleteTokens 删除指定用户的一组令牌,返回成功删除数量 +func BatchDeleteTokens(ids []int, userId int) (int, error) { + if len(ids) == 0 { + return 0, errors.New("ids 不能为空!") + } + + tx := DB.Begin() + + var tokens []Token + if err := tx.Where("user_id = ? AND id IN (?)", userId, ids).Find(&tokens).Error; err != nil { + tx.Rollback() + return 0, err + } + + if err := tx.Where("user_id = ? AND id IN (?)", userId, ids).Delete(&Token{}).Error; err != nil { + tx.Rollback() + return 0, err + } + + if err := tx.Commit().Error; err != nil { + return 0, err + } + + if common.RedisEnabled { + gopool.Go(func() { + for _, t := range tokens { + _ = cacheDeleteToken(t.Key) + } + }) + } + + return len(tokens), nil +} diff --git a/router/api-router.go b/router/api-router.go index badfa7bf..db4c3898 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -125,6 +125,7 @@ func SetApiRouter(router *gin.Engine) { tokenRoute.POST("/", controller.AddToken) tokenRoute.PUT("/", controller.UpdateToken) tokenRoute.DELETE("/:id", controller.DeleteToken) + tokenRoute.POST("/batch", controller.DeleteTokenBatch) } redemptionRoute := apiRouter.Group("/redemption") redemptionRoute.Use(middleware.AdminAuth()) diff --git a/web/src/components/table/TokensTable.js b/web/src/components/table/TokensTable.js index bc6c7607..0a049c39 100644 --- a/web/src/components/table/TokensTable.js +++ b/web/src/components/table/TokensTable.js @@ -435,6 +435,7 @@ const TokensTable = () => { const refresh = async () => { await loadTokens(1); + setSelectedKeys([]); }; const copyText = async (text) => { @@ -583,6 +584,29 @@ const TokensTable = () => { } }; + const batchDeleteTokens = async () => { + if (selectedKeys.length === 0) { + showError(t('请先选择要删除的令牌!')); + return; + } + setLoading(true); + try { + const ids = selectedKeys.map((token) => token.id); + const res = await API.post('/api/token/batch', { ids }); + if (res?.data?.success) { + const count = res.data.data || 0; + showSuccess(t('已删除 {{count}} 个令牌!', { count })); + await refresh(); + } else { + showError(res?.data?.message || t('删除失败')); + } + } catch (error) { + showError(error.message); + } finally { + setLoading(false); + } + }; + const renderHeader = () => (
@@ -595,12 +619,12 @@ const TokensTable = () => {
-
+
+ + + ), + }); }} > - {t('复制所选令牌到剪贴板')} + {t('复制所选令牌')} + +
+
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 6b982b51..a5151cbc 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -813,7 +813,16 @@ "复制所选令牌": "Copy selected token", "请至少选择一个令牌!": "Please select at least one token!", "管理员未设置查询页链接": "The administrator has not set the query page link", - "复制所选令牌到剪贴板": "Copy selected token to clipboard", + "批量删除令牌": "Batch delete token", + "确定要删除所选的 {{count}} 个令牌吗?": "Are you sure you want to delete the selected {{count}} tokens?", + "删除所选令牌": "Delete selected token", + "请先选择要删除的令牌!": "Please select the token to be deleted!", + "已删除 {{count}} 个令牌!": "Deleted {{count}} tokens!", + "删除失败": "Delete failed", + "复制令牌": "Copy token", + "请选择你的复制方式": "Please select your copy method", + "名称+密钥": "Name + key", + "仅密钥": "Only key", "查看API地址": "View API address", "打开查询页": "Open query page", "时间(仅显示近3天)": "Time (only displays the last 3 days)",