package controllers import ( "encoding/json" "io" "server/models" "strconv" "strings" beego "github.com/beego/beego/v2/server/web" ) // AdminMenuController 后台菜单控制器 type AdminMenuController struct { beego.Controller } 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 { return nil } s := strings.TrimSpace(*raw) if s == "" { return nil } var arr []int if err := json.Unmarshal([]byte(s), &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 viewsJSON(views []int) string { // 默认:平台端显示 if len(views) == 0 { views = []int{1} } b, _ := json.Marshal(views) return string(b) } func filterMenusByView(menus []models.SystemMenu, v int) []models.SystemMenu { out := make([]models.SystemMenu, 0, len(menus)) for _, m := range menus { views := parseViews(m.Views) // 兼容旧数据:views 为空时,平台端菜单默认可见(保持旧 is_platform=1 的常见默认体验) // 租户端不做默认放行,避免把未迁移数据误暴露到租户端。 if v == 1 && len(views) == 0 { out = append(out, m) continue } if hasView(views, v) { out = append(out, m) } } return out } // GetMenu 获取指定用户可见的菜单列表(简化版:当前先忽略用户权限,返回全部启用且平台端菜单) // 路由示例:GET /platform/menu/1 func (c *AdminMenuController) GetMenu() { // 从路由参数中解析用户 ID,占位保留,方便后续按用户权限过滤 _ = c.Ctx.Input.Param(":id") // 查询所有启用菜单,再按 views 过滤平台端可见 var menus []models.SystemMenu qs := models.Orm. QueryTable(new(models.SystemMenu)). Filter("status", 1) _, err := qs.All(&menus) if err != nil { c.Data["json"] = map[string]interface{}{ "code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil, } _ = c.ServeJSON() return } menus = filterMenusByView(menus, 1) // 将平铺的菜单列表构建为树形结构 menuTree := buildMenuTree(menus, 0) c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": menuTree, } _ = c.ServeJSON() } // GetBackendMenu 获取租户端用户可见的菜单列表(简化版:当前先忽略用户权限,返回全部启用且租户端菜单) // 路由示例:GET /backend/menu/1 func (c *AdminMenuController) GetBackendMenu() { // 从路由参数中解析用户 ID,占位保留,方便后续按用户权限过滤 _ = c.Ctx.Input.Param(":id") var menus []models.SystemMenu qs := models.Orm. QueryTable(new(models.SystemMenu)). Filter("status", 1) _, err := qs.All(&menus) if err != nil { c.Data["json"] = map[string]interface{}{ "code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil, } _ = c.ServeJSON() return } menus = filterMenusByView(menus, 2) menuTree := buildMenuTree(menus, 0) c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": menuTree, } _ = c.ServeJSON() } // GetAllMenus 获取平台端全部菜单(用于菜单管理界面) // 路由:GET /platform/allmenu func (c *AdminMenuController) GetAllMenus() { var menus []models.SystemMenu cid, _ := c.GetInt("cid") qs := models.Orm.QueryTable(new(models.SystemMenu)) // 菜单管理默认返回全量菜单;仅在明确传 cid 时按分类筛选 // cid: 1平台角色 -> 平台菜单;2租户角色 -> 租户菜单 _, err := qs.All(&menus) if 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) } tree := buildMenuTree(menus, 0) c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": tree, } _ = c.ServeJSON() } // GetAllBackendMenus 获取租户端全部菜单(用于菜单管理界面) // 路由:GET /backend/allmenu func (c *AdminMenuController) GetAllBackendMenus() { var menus []models.SystemMenu _, err := models.Orm.QueryTable(new(models.SystemMenu)). All(&menus) if err != nil { c.Data["json"] = map[string]interface{}{ "code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil, } _ = c.ServeJSON() return } menus = filterMenusByView(menus, 2) tree := buildMenuTree(menus, 0) c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": tree, } _ = c.ServeJSON() } // menuNode 用于 JSON 返回的菜单结构 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"` } // buildMenuTree 将菜单列表构建成树结构 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 } // 递归查找子菜单 children := buildMenuTree(menus, int64(m.ID)) if len(children) > 0 { node.Children = children } tree = append(tree, node) } } return tree } // UpdateMenuStatus 更新菜单状态 // 路由:PATCH /platform/menu/status/:id func (c *AdminMenuController) 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"` } rawBody, _ := io.ReadAll(c.Ctx.Request.Body) if err := json.Unmarshal(rawBody, &body); err != nil || body.Status == nil { c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} _ = c.ServeJSON() return } _, err = models.Orm.QueryTable(new(models.SystemMenu)). Filter("id", id). Update(map[string]interface{}{"status": *body.Status}) 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": "success", "success": true} _ = c.ServeJSON() } // CreateMenu 创建菜单 // 路由:POST /platform/createmenu func (c *AdminMenuController) CreateMenu() { payload, ok := c.parseMenuPayload(true) if !ok { return } 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: ptrString(viewsJSON(payload.Views)), Type: valueInt8(payload.Type, 1), } menu.Path = ptrString(valueString(payload.Path, "")) menu.ComponentPath = ptrString(valueString(payload.ComponentPath, "")) menu.Icon = ptrString(valueString(payload.Icon, "")) menu.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() } // UpdateMenu 更新菜单 // 路由:PUT /platform/updatemenu/:id func (c *AdminMenuController) 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 } 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": viewsJSON(payload.Views), "type": valueInt8(payload.Type, 1), "permission": valueString(payload.Permission, ""), } _, err = models.Orm.QueryTable(new(models.SystemMenu)). Filter("id", id). Update(update) 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": "更新成功"} _ = c.ServeJSON() } // DeleteMenu 删除菜单 // 路由:DELETE /platform/deletemenu/:id func (c *AdminMenuController) 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 } _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Delete() 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": "删除成功", "success": true} _ = c.ServeJSON() } func (c *AdminMenuController) parseMenuPayload(needTitle bool) (*menuPayload, bool) { rawBody, _ := io.ReadAll(c.Ctx.Request.Body) var payload menuPayload if err := json.Unmarshal(rawBody, &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 }