团队管理:成员擅长标签
- team_members 新增 specialty 列(老库自动 ALTER 迁移,幂等) - 新接口:GET /api/teams/:id/members 成员列表(仅成员可看)、PUT /api/teams/:id/members/:uid 更新擅长(创建者可改他人,成员仅改自己);邀请接口支持带擅长 - TeamDialog 重构:团队卡片内成员列表(头像/名称/邮箱/角色/彩色擅长 chip,超 3 个折叠 +N)、行内擅长编辑器(10 个预设点选 + 自定义输入)、邀请时顺手点选擅长预设 - 文档 20-account 同步更新(含活动栏新导航说明) - curl E2E:邀请带擅长/创建者改他人/自改/越权与非成员 403 全部通过;53 前端测试 + Go 测试全绿
This commit is contained in:
1 parent
6d72264793
commit
c6868b0ddd
9 files changed
+451
-48
No files matched your search
@@ -36,6 +36,8 @@ func (a *API) Register(r *gin.Engine) {
|
||||
g.GET("/me", a.auth(a.handleMe))
|
||||
g.POST("/teams", a.auth(a.handleCreateTeam))
|
||||
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))
|
||||
g.GET("/workspaces/:id", a.auth(a.handleGetWorkspace))
|
||||
g.PUT("/workspaces/:id", a.auth(a.handlePutWorkspace))
|
||||
g.PUT("/workspaces/:id/mock", a.auth(a.handlePublishMock))
|
||||
@@ -175,13 +177,14 @@ func (a *API) handleAddMember(c *gin.Context, user *store.User) {
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Specialty string `json:"specialty"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写成员邮箱"})
|
||||
return
|
||||
}
|
||||
member, err := a.store.AddTeamMember(teamID, strings.ToLower(req.Email))
|
||||
member, err := a.store.AddTeamMember(teamID, strings.ToLower(req.Email), trimSpecialty(req.Specialty))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "该邮箱尚未注册 OpsxAPI 账号"})
|
||||
return
|
||||
@@ -189,6 +192,71 @@ func (a *API) handleAddMember(c *gin.Context, user *store.User) {
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true, "member": member})
|
||||
}
|
||||
|
||||
// handleListMembers 团队成员列表(创建者与成员均可查看)
|
||||
func (a *API) handleListMembers(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
|
||||
}
|
||||
member, err := a.store.IsTeamMember(teamID, user.ID)
|
||||
if err != nil || !member {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "仅团队成员可查看成员列表"})
|
||||
return
|
||||
}
|
||||
members, err := a.store.TeamMembers(teamID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"members": members})
|
||||
}
|
||||
|
||||
// handleUpdateSpecialty 更新成员擅长:创建者可改任何人,成员只能改自己
|
||||
func (a *API) handleUpdateSpecialty(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
|
||||
}
|
||||
uid, err := strconv.ParseInt(c.Param("uid"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的成员"})
|
||||
return
|
||||
}
|
||||
owner, err := a.store.IsTeamOwner(teamID, user.ID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if !owner && uid != user.ID {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "仅团队创建者可修改他人擅长,成员可修改自己的"})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Specialty string `json:"specialty"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"})
|
||||
return
|
||||
}
|
||||
if err := a.store.UpdateMemberSpecialty(teamID, uid, trimSpecialty(req.Specialty)); err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "该成员不在团队中"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// trimSpecialty 擅长标签去空白、压长度
|
||||
func trimSpecialty(s string) string {
|
||||
s = strings.Join(strings.Fields(strings.TrimSpace(s)), " ")
|
||||
r := []rune(s)
|
||||
if len(r) > 60 {
|
||||
return string(r[:60])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
/* ---------------- Mock 发布 ---------------- */
|
||||
|
||||
func (a *API) handlePublishMock(c *gin.Context, user *store.User) {
|
||||
|
||||
Reference in new issue
Block a user