feat(teams): fix checkbox multi-select and improve batch operations UI

- Fix checkbox binding using :model-value instead of :checked
- Change selectedIds from Set to reactive array for proper Vue reactivity
- Move batch refresh/delete buttons to top bar (matching CardKeysPage layout)
- Buttons show selection count like 'Refresh (2)' when items selected
- Swap position of 'Add Team' and 'Random Invite' buttons
- Remove unused isIndeterminate computed property
This commit is contained in:
sar
2026-01-16 11:53:04 +08:00
parent 59f5a87275
commit 474f592dcd
32 changed files with 405 additions and 67 deletions

View File

@@ -15,13 +15,15 @@ import (
// ChatGPTAccountHandler ChatGPT 账号处理器
type ChatGPTAccountHandler struct {
repo *repository.ChatGPTAccountRepository
invitationRepo *repository.InvitationRepository
chatgptService *service.ChatGPTService
}
// NewChatGPTAccountHandler 创建处理器
func NewChatGPTAccountHandler(repo *repository.ChatGPTAccountRepository, chatgptService *service.ChatGPTService) *ChatGPTAccountHandler {
func NewChatGPTAccountHandler(repo *repository.ChatGPTAccountRepository, invitationRepo *repository.InvitationRepository, chatgptService *service.ChatGPTService) *ChatGPTAccountHandler {
return &ChatGPTAccountHandler{
repo: repo,
invitationRepo: invitationRepo,
chatgptService: chatgptService,
}
}
@@ -307,10 +309,19 @@ func (h *ChatGPTAccountHandler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
// 先删除相关的邀请记录,避免外键约束失败
if err := h.invitationRepo.DeleteByAccountID(id); err != nil {
respondJSON(w, http.StatusInternalServerError, AccountResponse{
Success: false,
Message: "Failed to delete related invitations: " + err.Error(),
})
return
}
if err := h.repo.Delete(id); err != nil {
respondJSON(w, http.StatusInternalServerError, AccountResponse{
Success: false,
Message: "Failed to delete account",
Message: "Failed to delete account: " + err.Error(),
})
return
}
@@ -320,3 +331,150 @@ func (h *ChatGPTAccountHandler) Delete(w http.ResponseWriter, r *http.Request) {
Message: "Account deleted successfully",
})
}
// BatchDeleteRequest 批量删除请求
type BatchDeleteAccountRequest struct {
IDs []int `json:"ids"`
}
// BatchDeleteResponse 批量操作响应
type BatchOperationResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
SuccessCount int `json:"success_count"`
FailedCount int `json:"failed_count"`
}
// BatchDelete 批量删除账号
func (h *ChatGPTAccountHandler) BatchDelete(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
respondJSON(w, http.StatusMethodNotAllowed, BatchOperationResponse{
Success: false,
Message: "Method not allowed",
})
return
}
var req BatchDeleteAccountRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondJSON(w, http.StatusBadRequest, BatchOperationResponse{
Success: false,
Message: "Invalid request body",
})
return
}
if len(req.IDs) == 0 {
respondJSON(w, http.StatusBadRequest, BatchOperationResponse{
Success: false,
Message: "No accounts selected",
})
return
}
successCount := 0
failedCount := 0
for _, id := range req.IDs {
// 先删除相关的邀请记录
if err := h.invitationRepo.DeleteByAccountID(id); err != nil {
failedCount++
continue
}
// 再删除账号
if err := h.repo.Delete(id); err != nil {
failedCount++
continue
}
successCount++
}
respondJSON(w, http.StatusOK, BatchOperationResponse{
Success: failedCount == 0,
Message: "Batch delete completed",
SuccessCount: successCount,
FailedCount: failedCount,
})
}
// BatchRefreshRequest 批量刷新请求
type BatchRefreshRequest struct {
IDs []int `json:"ids"`
}
// BatchRefresh 批量刷新账号
func (h *ChatGPTAccountHandler) BatchRefresh(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
respondJSON(w, http.StatusMethodNotAllowed, BatchOperationResponse{
Success: false,
Message: "Method not allowed",
})
return
}
var req BatchRefreshRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondJSON(w, http.StatusBadRequest, BatchOperationResponse{
Success: false,
Message: "Invalid request body",
})
return
}
if len(req.IDs) == 0 {
respondJSON(w, http.StatusBadRequest, BatchOperationResponse{
Success: false,
Message: "No accounts selected",
})
return
}
successCount := 0
failedCount := 0
for _, id := range req.IDs {
account, err := h.repo.FindByID(id)
if err != nil || account == nil {
failedCount++
continue
}
// 调用 ChatGPT API 获取订阅信息
subInfo, err := h.chatgptService.GetSubscription(account.TeamAccountID, account.AuthToken)
if err != nil {
account.ConsecutiveFailures++
account.IsActive = false
account.LastCheck = sql.NullTime{Time: time.Now(), Valid: true}
h.repo.Update(account)
failedCount++
continue
}
// 更新账号信息
if subInfo.IsValid {
account.SeatsInUse = subInfo.SeatsInUse
account.SeatsEntitled = subInfo.SeatsEntitled
account.ActiveStart = sql.NullTime{Time: subInfo.ActiveStart, Valid: !subInfo.ActiveStart.IsZero()}
account.ActiveUntil = sql.NullTime{Time: subInfo.ActiveUntil, Valid: !subInfo.ActiveUntil.IsZero()}
account.IsActive = true
account.ConsecutiveFailures = 0
} else {
account.IsActive = false
account.ConsecutiveFailures++
}
account.LastCheck = sql.NullTime{Time: time.Now(), Valid: true}
if err := h.repo.Update(account); err != nil {
failedCount++
continue
}
successCount++
}
respondJSON(w, http.StatusOK, BatchOperationResponse{
Success: failedCount == 0,
Message: "Batch refresh completed",
SuccessCount: successCount,
FailedCount: failedCount,
})
}