diff --git a/backend/internal/handler/user.go b/backend/internal/handler/user.go index cc96811..091ccef 100644 --- a/backend/internal/handler/user.go +++ b/backend/internal/handler/user.go @@ -95,3 +95,12 @@ func (h *UserHandler) DeletePlatformAdmin(c *gin.Context) { } response.OK(c, nil) } + +func (h *UserHandler) DeleteMerchantAccount(c *gin.Context) { + id, _ := strconv.ParseUint(c.Param("id"), 10, 64) + if err := h.svc.DeleteMerchantAccount(uint(id), middleware.GetUserID(c)); err != nil { + response.BadRequest(c, err.Error()) + return + } + response.OK(c, nil) +} diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index a816633..3bf1e89 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -195,6 +195,7 @@ func Setup(h *Handlers) *gin.Engine { { admin.GET("/platform/admins", h.User.ListPlatformAdmins) admin.GET("/platform/merchant-accounts", h.User.ListMerchantAccountGroups) + admin.DELETE("/platform/merchant-accounts/:id", h.User.DeleteMerchantAccount) admin.POST("/platform/admins", h.User.CreatePlatformAdmin) admin.PATCH("/platform/admins/:id", h.User.UpdatePlatformAdmin) admin.DELETE("/platform/admins/:id", h.User.DeletePlatformAdmin) diff --git a/backend/internal/service/merchant_test.go b/backend/internal/service/merchant_test.go index e23259a..ade2a7a 100644 --- a/backend/internal/service/merchant_test.go +++ b/backend/internal/service/merchant_test.go @@ -336,3 +336,40 @@ func TestListMerchantAccountGroupsExcludesPlatformAdmins(t *testing.T) { t.Fatalf("unexpected merchant account group: %+v", groups[0]) } } + +func TestDeleteMerchantAccountRemovesAllMemberships(t *testing.T) { + db := newServiceTestDB(t) + merchantA := model.Merchant{Code: "delete-account-a", Name: "删除账号商户 A", Status: model.MerchantStatusActive} + merchantB := model.Merchant{Code: "delete-account-b", Name: "删除账号商户 B", Status: model.MerchantStatusActive} + merchantUser := model.User{Username: "delete-merchant-account", PasswordHash: "hash", Role: model.RoleMerchant, Status: 1} + platformAdmin := model.User{Username: "delete-account-admin", PasswordHash: "hash", Role: model.RoleAdmin, Status: 1} + for _, entity := range []interface{}{&merchantA, &merchantB, &merchantUser, &platformAdmin} { + if err := db.Create(entity).Error; err != nil { + t.Fatalf("create fixture: %v", err) + } + } + for _, member := range []model.MerchantMember{ + {MerchantID: merchantA.ID, UserID: merchantUser.ID, Role: model.MemberRoleOperator, Status: 1}, + {MerchantID: merchantB.ID, UserID: merchantUser.ID, Role: "support", Status: 1}, + } { + if err := db.Create(&member).Error; err != nil { + t.Fatalf("create membership: %v", err) + } + } + + svc := NewUserService(db, nil) + if err := svc.DeleteMerchantAccount(merchantUser.ID, platformAdmin.ID); err != nil { + t.Fatalf("delete merchant account: %v", err) + } + var memberships int64 + if err := db.Model(&model.MerchantMember{}).Where("user_id = ?", merchantUser.ID).Count(&memberships).Error; err != nil || memberships != 0 { + t.Fatalf("merchant memberships should be removed, count=%d err=%v", memberships, err) + } + var deleted model.User + if err := db.Unscoped().First(&deleted, merchantUser.ID).Error; err != nil || !deleted.DeletedAt.Valid { + t.Fatalf("merchant account should be soft deleted, user=%+v err=%v", deleted, err) + } + if err := svc.DeleteMerchantAccount(platformAdmin.ID, merchantUser.ID); err == nil { + t.Fatal("platform administrator should not be deletable from merchant account endpoint") + } +} diff --git a/backend/internal/service/user.go b/backend/internal/service/user.go index f961e82..bc7cb23 100644 --- a/backend/internal/service/user.go +++ b/backend/internal/service/user.go @@ -316,3 +316,31 @@ func (s *UserService) DeletePlatformAdmin(id, actorUserID uint) error { return tx.Delete(&user).Error }) } + +// DeleteMerchantAccount permanently removes a merchant login account and all +// of its merchant memberships. Platform administrators must be managed from +// the dedicated platform administrator view instead. +func (s *UserService) DeleteMerchantAccount(id, actorUserID uint) error { + if id == 0 { + return errors.New("用户不存在") + } + if id == actorUserID { + return errors.New("不能删除当前登录账号") + } + return s.db.Transaction(func(tx *gorm.DB) error { + var user model.User + if err := tx.First(&user, id).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("用户不存在") + } + return err + } + if user.Role != model.RoleMerchant { + return errors.New("平台管理员请在平台管理员列表中管理") + } + if err := tx.Where("user_id = ?", user.ID).Delete(&model.MerchantMember{}).Error; err != nil { + return err + } + return tx.Delete(&user).Error + }) +} diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 2373dde..d073ce0 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -181,6 +181,8 @@ export const deliveryApi = { export const platformApi = { merchantAccounts: (params?: Record) => request.get('/platform/merchant-accounts', { params }).then((r) => r.data.data as PageResult), + deleteMerchantAccount: (userId: number) => + request.delete(`/platform/merchant-accounts/${userId}`).then((r) => r.data.data), merchants: (params?: Record) => request.get('/platform/merchants', { params }).then((r) => r.data.data as PageResult), createMerchant: (data: { diff --git a/frontend/src/pages/PlatformUsers.tsx b/frontend/src/pages/PlatformUsers.tsx index ed442ac..defd1d5 100644 --- a/frontend/src/pages/PlatformUsers.tsx +++ b/frontend/src/pages/PlatformUsers.tsx @@ -104,6 +104,16 @@ export default function PlatformUsers() { } } + const removeMerchantAccount = async (record: MerchantMember) => { + try { + await platformApi.deleteMerchantAccount(record.user_id) + message.success('商户账号已删除') + void loadMerchantAccounts() + } catch (e) { + message.error(e instanceof Error ? e.message : '删除失败') + } + } + const columns: ColumnsType = [ { title: '用户名', dataIndex: 'username', width: 260, render: (value) => {value} }, { title: '昵称', dataIndex: 'nickname', width: 220, render: (value) => value || '-' }, @@ -128,6 +138,18 @@ export default function PlatformUsers() { { title: '昵称', dataIndex: ['user', 'nickname'], width: 220, render: (_, record) => record.user?.nickname || '-' }, { title: '商户角色', dataIndex: 'role', width: 160, render: (role) => {roleText(role)} }, { title: '成员状态', dataIndex: 'status', width: 130, render: (status) => status === 1 ? 启用 : 停用 }, + { + title: '操作', + width: 120, + render: (_, record) => void removeMerchantAccount(record)} + okButtonProps={{ danger: true }} + > + + , + }, ] const merchantColumns: ColumnsType = [