团队管理:重命名与删除团队

- 新接口:PATCH /api/teams/:id 重命名(1-40 字符)、DELETE /api/teams/:id 删除(均仅创建者);删除随外键级联清理成员/共享空间/Mock 接口
- TeamDialog:卡片右上 ✎ 内联重命名、🗑 删除二次确认(红色警示条,明示连带删除范围)
- 删除后若当前停留在该团队空间自动切回本地空间
- curl E2E:改名/越权 403/删除/空间级联失效全通过;浏览器验证改名与删除全流程;53 前端测试 + Go 构建通过
This commit is contained in:
logan committed 2026-09-18 13:40:32 +08:00
1 parent c6868b0ddd
commit 36df1cec83
8 files changed
+223 -10

No files matched your search

Binary file not shown.
Binary file not shown.
+47
View File
@@ -35,6 +35,8 @@ func (a *API) Register(r *gin.Engine) {
g.POST("/auth/logout", a.auth(a.handleLogout))
g.GET("/me", a.auth(a.handleMe))
g.POST("/teams", a.auth(a.handleCreateTeam))
g.PATCH("/teams/:id", a.auth(a.handleRenameTeam))
g.DELETE("/teams/:id", a.auth(a.handleDeleteTeam))
g.POST("/teams/:id/members", a.auth(a.handleAddMember))
g.GET("/teams/:id/members", a.auth(a.handleListMembers))
g.PUT("/teams/:id/members/:uid", a.auth(a.handleUpdateSpecialty))
@@ -192,6 +194,51 @@ func (a *API) handleAddMember(c *gin.Context, user *store.User) {
c.JSON(http.StatusOK, gin.H{"ok": true, "member": member})
}
// handleRenameTeam 重命名团队(仅创建者)
func (a *API) handleRenameTeam(c *gin.Context, user *store.User) {
teamID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的团队"})
return
}
owner, err := a.store.IsTeamOwner(teamID, user.ID)
if err != nil || !owner {
c.JSON(http.StatusForbidden, gin.H{"error": "仅团队创建者可重命名团队"})
return
}
var req struct {
Name string `json:"name" binding:"required,min=1,max=40"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "团队名称需 1-40 个字符"})
return
}
if err := a.store.RenameTeam(teamID, strings.TrimSpace(req.Name)); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "重命名失败:" + err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// handleDeleteTeam 删除团队(仅创建者):成员、共享空间与 Mock 数据一并删除
func (a *API) handleDeleteTeam(c *gin.Context, user *store.User) {
teamID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的团队"})
return
}
owner, err := a.store.IsTeamOwner(teamID, user.ID)
if err != nil || !owner {
c.JSON(http.StatusForbidden, gin.H{"error": "仅团队创建者可删除团队"})
return
}
if err := a.store.DeleteTeam(teamID); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败:" + err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true})
}
// handleListMembers 团队成员列表(创建者与成员均可查看)
func (a *API) handleListMembers(c *gin.Context, user *store.User) {
teamID, err := strconv.ParseInt(c.Param("id"), 10, 64)
@@ -29,6 +29,7 @@
活动栏账号菜单或空间菜单 → **团队管理**:
- **新建团队**:创建后自动获得同名共享空间;
- **重命名 / 删除**:团队卡片右上角的 ✎ / 🗑(仅创建者可见)。重命名就地保存;删除需二次确认,将同时删除成员关系、共享空间与已发布 Mock 数据,不可恢复(若当前正停在该团队空间,会自动切回本地空间);
- **添加成员**:输入已注册成员邮箱(创建者可操作),邀请时可顺手点选**擅长**预设;成员登录后即可看到并切换到该团队空间;
- **成员与擅长**:每个团队卡片展示成员列表(头像 / 名称 / 角色 / 擅长标签)。擅长支持预设点选(前端、后端、全栈、测试、运维、移动端、数据库、AI、接口设计、安全)与自定义输入,空格或顿号分隔;创建者可修改任何成员的擅长,成员只能修改自己的(行尾「擅长」按钮)。
+26
View File
@@ -367,6 +367,32 @@ func (s *Store) IsTeamOwner(teamID, userID int64) (bool, error) {
return err == nil, err
}
// RenameTeam 重命名团队(权限由 API 层校验)
func (s *Store) RenameTeam(teamID int64, name string) error {
res, err := s.db.Exec(`UPDATE teams SET name = ? WHERE id = ?`, name, teamID)
if err != nil {
return err
}
n, _ := res.RowsAffected()
if n == 0 {
return ErrNotFound
}
return nil
}
// DeleteTeam 删除团队:成员 / 团队空间 / 其 Mock 接口随外键级联清理
func (s *Store) DeleteTeam(teamID int64) error {
res, err := s.db.Exec(`DELETE FROM teams WHERE id = ?`, teamID)
if err != nil {
return err
}
n, _ := res.RowsAffected()
if n == 0 {
return ErrNotFound
}
return nil
}
/* ---------------- 工作区(云端空间) ---------------- */
type Workspace struct {