package controllers import ( "encoding/json" "server/models" "strconv" "strings" beego "github.com/beego/beego/v2/server/web" ) type BackendMenuController struct { beego.Controller } type AdminMenuController = BackendMenuController type menuPayload struct { Pid *int64 `json:"pid"` Title *string `json:"title"` Path *string `json:"path"` ComponentPath *string `json:"component_path"` Icon *string `json:"icon"` Sort *int64 `json:"sort"` Status *int8 `json:"status"` IsVisible *int8 `json:"is_visible"` Views []int `json:"views"` Type *int8 `json:"type"` Permission *string `json:"permission"` } func parseViews(raw *string) []int { if raw == nil || strings.TrimSpace(*raw) == "" { return nil } var arr []int if err := json.Unmarshal([]byte(*raw), &arr); err != nil { return nil } return arr } func hasView(arr []int, v int) bool { for _, n := range arr { if n == v { return true } } return false } func filterMenusByView(menus []models.SystemMenu, v int) []models.SystemMenu { out := make([]models.SystemMenu, 0, len(menus)) for _, m := range menus { views := parseViews(m.Views) if v == 1 && len(views) == 0 { out = append(out, m) continue } if hasView(views, v) { out = append(out, m) } } return out } func (c *BackendMenuController) GetMenu() { var menus []models.SystemMenu _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus) if err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} _ = c.ServeJSON() return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 1), 0)} _ = c.ServeJSON() } func (c *BackendMenuController) GetBackendMenu() { // 确保租户用户 group_id 列存在(老库兼容) models.EnsureTenantUserGroupColumn() var menus []models.SystemMenu _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus) if err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} _ = c.ServeJSON() return } backendMenus := filterMenusByView(menus, 2) // 按当前登录用户角色过滤可见功能菜单: // 角色 rights 为空(或未分配角色)→ 全权限,返回全部后端菜单; // 否则仅返回角色允许访问的菜单(含其祖先节点,保证树结构完整)。 idStr := c.Ctx.Input.Param(":id") uid, _ := strconv.ParseUint(idStr, 10, 64) if uid > 0 { var tu models.SystemTenantUser if e := models.Orm.QueryTable(new(models.SystemTenantUser)). Filter("id", uid). Filter("delete_time__isnull", true). One(&tu); e == nil && tu.GroupID > 0 { var role models.AdminRole if re := models.Orm.QueryTable(new(models.AdminRole)). Filter("id", tu.GroupID). Filter("cid", 2). One(&role); re == nil { backendMenus = filterMenusByRights(backendMenus, role.Rights) } } } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(backendMenus, 0)} _ = c.ServeJSON() } // parseRightsToSet 将角色 rights 解析为菜单 ID 集合(兼容 JSON 数组 / 逗号分隔) func parseRightsToSet(raw string) map[uint64]bool { set := map[uint64]bool{} var arr []string if err := json.Unmarshal([]byte(raw), &arr); err == nil { for _, s := range arr { s = strings.TrimSpace(s) if s == "" { continue } if n, e := strconv.ParseUint(s, 10, 64); e == nil { set[n] = true } } return set } for _, s := range strings.Split(raw, ",") { s = strings.TrimSpace(s) if s == "" { continue } if n, e := strconv.ParseUint(s, 10, 64); e == nil { set[n] = true } } return set } // filterMenusByRights 按角色 rights(菜单 ID 集合)过滤菜单,并补全祖先节点,保证树结构完整。 // rights 为空(nil / 空串)→ 原样返回(全权限)。 func filterMenusByRights(menus []models.SystemMenu, rights *string) []models.SystemMenu { if rights == nil || strings.TrimSpace(*rights) == "" { return menus } allowed := parseRightsToSet(*rights) if len(allowed) == 0 { return menus } pidOf := make(map[uint64]int64, len(menus)) byID := make(map[uint64]models.SystemMenu, len(menus)) for _, m := range menus { pidOf[m.ID] = m.Pid byID[m.ID] = m } final := make(map[uint64]bool, len(allowed)) for id := range allowed { cur := id for { if _, ok := byID[cur]; !ok { break } if final[cur] { break } final[cur] = true p := pidOf[cur] if p == 0 { break } cur = uint64(p) } } out := make([]models.SystemMenu, 0, len(final)) for id := range final { out = append(out, byID[id]) } return out } func (c *BackendMenuController) GetTenantList() { var tid uint64 if jwtTid := c.Ctx.Input.GetData("tid"); jwtTid != nil { tid = jwtTid.(uint64) } if tid == 0 { c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录或非法请求"} _ = c.ServeJSON() return } var menus []models.SystemMenu if _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus); err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取失败:" + err.Error()} _ = c.ServeJSON() return } tree := buildMenuTree(filterMenusByView(menus, 2), 0) c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "获取成功", "data": map[string]interface{}{"list": tree, "total": len(tree)}, } _ = c.ServeJSON() } func (c *BackendMenuController) GetAllMenus() { var menus []models.SystemMenu cid, _ := c.GetInt("cid") if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} _ = c.ServeJSON() return } if cid == 1 { menus = filterMenusByView(menus, 1) } else if cid == 2 { menus = filterMenusByView(menus, 2) } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(menus, 0)} _ = c.ServeJSON() } // filterAssignableMenus 仅保留「已启用且已显示」的菜单。 // 停用或隐藏的菜单不参与角色权限分配,直接在服务端屏蔽,不依赖前端过滤。 func filterAssignableMenus(menus []models.SystemMenu) []models.SystemMenu { out := make([]models.SystemMenu, 0, len(menus)) for _, m := range menus { if m.Status != 1 { continue } // is_visible 为空视为显示 if m.IsVisible != nil && *m.IsVisible == 0 { continue } out = append(out, m) } return out } // GetAssignableMenus 获取可分配给角色的菜单树 // 过滤规则(均在服务端完成): // 1. cid:按菜单所属端过滤(1=平台端 2=租户端,不传则不过滤) // 2. status=1:仅已启用的菜单 // 3. is_visible != 0:仅已显示的菜单(为空视为显示) // // 父级菜单被过滤时,其子菜单一并不可分配(菜单树按 pid 构建,父级缺失则整条分支不会返回)。 // GET /backend/assignableMenus?cid=2 func (c *BackendMenuController) GetAssignableMenus() { var menus []models.SystemMenu cid, _ := c.GetInt("cid") if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} _ = c.ServeJSON() return } if cid == 1 || cid == 2 { menus = filterMenusByView(menus, cid) } menus = filterAssignableMenus(menus) c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(menus, 0)} _ = c.ServeJSON() } func (c *BackendMenuController) GetAllBackendMenus() { var menus []models.SystemMenu if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} _ = c.ServeJSON() return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 2), 0)} _ = c.ServeJSON() } type menuNode struct { ID uint64 `json:"id"` Pid int64 `json:"pid"` Title string `json:"title"` Path string `json:"path,omitempty"` ComponentPath string `json:"component_path,omitempty"` Icon string `json:"icon,omitempty"` Sort int64 `json:"sort"` Status int8 `json:"status"` IsVisible *int8 `json:"is_visible,omitempty"` Views []int `json:"views,omitempty"` Type int8 `json:"type"` Permission string `json:"permission,omitempty"` Children []*menuNode `json:"children,omitempty"` } func buildMenuTree(menus []models.SystemMenu, pid int64) []*menuNode { var tree []*menuNode for _, m := range menus { if m.Pid == pid { node := &menuNode{ ID: m.ID, Pid: m.Pid, Title: m.Title, Sort: m.Sort, Status: m.Status, IsVisible: m.IsVisible, Views: parseViews(m.Views), Type: m.Type, } if m.Path != nil { node.Path = *m.Path } if m.ComponentPath != nil { node.ComponentPath = *m.ComponentPath } if m.Icon != nil { node.Icon = *m.Icon } if m.Permission != nil { node.Permission = *m.Permission } if children := buildMenuTree(menus, int64(m.ID)); len(children) > 0 { node.Children = children } tree = append(tree, node) } } return tree } func (c *BackendMenuController) UpdateMenuStatus() { id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} _ = c.ServeJSON() return } var body struct { Status *int8 `json:"status"` } if err := json.Unmarshal(c.Ctx.Input.RequestBody, &body); err != nil || body.Status == nil { c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} _ = c.ServeJSON() return } if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Update(map[string]interface{}{"status": *body.Status}); err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} _ = c.ServeJSON() return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "success": true} _ = c.ServeJSON() } func (c *BackendMenuController) CreateMenu() { payload, ok := c.parseMenuPayload(true) if !ok { return } var viewsStr string views := payload.Views if len(views) == 0 { views = []int{1} } if b, err := json.Marshal(views); err == nil { viewsStr = string(b) } menu := models.SystemMenu{ Pid: valueInt64(payload.Pid, 0), Title: strings.TrimSpace(valueString(payload.Title, "")), Sort: valueInt64(payload.Sort, 0), Status: valueInt8(payload.Status, 1), IsVisible: ptrInt8(valueInt8(payload.IsVisible, 1)), Views: &viewsStr, Type: valueInt8(payload.Type, 1), Path: ptrString(valueString(payload.Path, "")), ComponentPath: ptrString(valueString(payload.ComponentPath, "")), Icon: ptrString(valueString(payload.Icon, "")), Permission: ptrString(valueString(payload.Permission, "")), } id, err := models.Orm.Insert(&menu) if err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} _ = c.ServeJSON() return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} _ = c.ServeJSON() } func (c *BackendMenuController) UpdateMenu() { id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} _ = c.ServeJSON() return } payload, ok := c.parseMenuPayload(false) if !ok { return } views := payload.Views if len(views) == 0 { views = []int{1} } viewsBytes, _ := json.Marshal(views) update := map[string]interface{}{ "pid": valueInt64(payload.Pid, 0), "title": strings.TrimSpace(valueString(payload.Title, "")), "path": valueString(payload.Path, ""), "component_path": valueString(payload.ComponentPath, ""), "icon": valueString(payload.Icon, ""), "sort": valueInt64(payload.Sort, 0), "status": valueInt8(payload.Status, 1), "is_visible": valueInt8(payload.IsVisible, 1), "views": string(viewsBytes), "type": valueInt8(payload.Type, 1), "permission": valueString(payload.Permission, ""), } if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Update(update); err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} _ = c.ServeJSON() return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} _ = c.ServeJSON() } func (c *BackendMenuController) DeleteMenu() { id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) if err != nil || id == 0 { c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} _ = c.ServeJSON() return } if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Delete(); err != nil { c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} _ = c.ServeJSON() return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功", "success": true} _ = c.ServeJSON() } func (c *BackendMenuController) parseMenuPayload(needTitle bool) (*menuPayload, bool) { var payload menuPayload if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil { c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} _ = c.ServeJSON() return nil, false } if needTitle && strings.TrimSpace(valueString(payload.Title, "")) == "" { c.Data["json"] = map[string]interface{}{"code": 400, "msg": "菜单名称不能为空"} _ = c.ServeJSON() return nil, false } return &payload, true } func valueString(v *string, def string) string { if v == nil { return def } return *v } func valueInt8(v *int8, def int8) int8 { if v == nil { return def } return *v } func valueInt64(v *int64, def int64) int64 { if v == nil { return def } return *v } func ptrString(v string) *string { return &v } func ptrInt8(v int8) *int8 { return &v }