Răsfoiți Sursa

add:菜单管理

zoie 2 zile în urmă
părinte
comite
e3bb03723c
9 a modificat fișierele cu 855 adăugiri și 25 ștergeri
  1. 157 1
      Nats/NatsServer/NatsERP.go
  2. 3 3
      conf/app.conf
  3. 422 2
      controllers/Menu.go
  4. 103 4
      controllers/Power.go
  5. 22 3
      controllers/User.go
  6. 1 1
      go.mod
  7. 2 0
      go.sum
  8. 132 1
      models/Account/Menu.go
  9. 13 10
      routers/User.go

+ 157 - 1
Nats/NatsServer/NatsERP.go

@@ -7,11 +7,12 @@ import (
 	"ERP_user/models/Account"
 	"errors"
 	"fmt"
+	"time"
+
 	"github.com/vmihailenco/msgpack/v5"
 	menulibs "gogs.baozhida.cn/zoie/ERP_libs/Menu"
 	powerlibs "gogs.baozhida.cn/zoie/ERP_libs/Power"
 	"gogs.baozhida.cn/zoie/ERP_libs/lib"
-	"time"
 )
 
 func ERP_Read_Menu_List(prefix string) ([]menulibs.Menu, error) {
@@ -203,3 +204,158 @@ func ERP_Delete_Power(prefix string, power powerlibs.Power) (id int64, err error
 
 	return t_R.Data, nil
 }
+
+// ========== 菜单相关NATS客户端方法 ==========
+
+// ERP_Add_Menu 添加菜单
+func ERP_Add_Menu(prefix string, menu menulibs.Menu, apis string) (id int, err error) {
+	sysName := Account.Get_Sys_Name(prefix)
+	subj := conf.NatsSubj_Prefix + fmt.Sprintf("%s_%s", prefix, "Add_Menu")
+
+	type T_S struct {
+		Menu menulibs.Menu
+		APIs string
+	}
+	b, _ := msgpack.Marshal(&T_S{Menu: menu, APIs: apis})
+
+	msg, err := Nats.Nats.Request(subj, b, 3*time.Second)
+	if err != nil {
+		logs.Error(lib.FuncName(), sysName, err)
+		return id, err
+	}
+
+	type T_R struct {
+		Code int16  `xml:"Code"`
+		Msg  string `xml:"Msg"`
+		Data int    `xml:"Data"`
+	}
+	var t_R T_R
+
+	err = msgpack.Unmarshal(msg.Data, &t_R)
+	if err != nil {
+		logs.Error(lib.FuncName(), sysName, err)
+		return id, err
+	}
+	fmt.Printf("%s: %+v\n", subj, t_R)
+
+	if t_R.Code != 200 {
+		err = errors.New(t_R.Msg)
+		logs.Error(lib.FuncName(), sysName, err)
+		return id, err
+	}
+
+	return t_R.Data, nil
+}
+
+// ERP_Update_Menu 更新菜单
+func ERP_Update_Menu(prefix string, menu menulibs.Menu, cols []string, apis string) (id int, err error) {
+	sysName := Account.Get_Sys_Name(prefix)
+	subj := conf.NatsSubj_Prefix + fmt.Sprintf("%s_%s", prefix, "Update_Menu")
+
+	type T_S struct {
+		Menu menulibs.Menu
+		Cols []string
+		APIs string
+	}
+	b, _ := msgpack.Marshal(&T_S{Menu: menu, Cols: cols, APIs: apis})
+
+	msg, err := Nats.Nats.Request(subj, b, 3*time.Second)
+	if err != nil {
+		logs.Error(lib.FuncName(), sysName, err)
+		return id, err
+	}
+
+	type T_R struct {
+		Code int16  `xml:"Code"`
+		Msg  string `xml:"Msg"`
+		Data int    `xml:"Data"`
+	}
+	var t_R T_R
+
+	err = msgpack.Unmarshal(msg.Data, &t_R)
+	if err != nil {
+		logs.Error(lib.FuncName(), sysName, err)
+		return id, err
+	}
+	fmt.Printf("%s: %+v\n", subj, t_R)
+
+	if t_R.Code != 200 {
+		err = errors.New(t_R.Msg)
+		logs.Error(lib.FuncName(), sysName, err)
+		return id, err
+	}
+
+	return t_R.Data, nil
+}
+
+// ERP_Delete_Menu 删除菜单
+func ERP_Delete_Menu(prefix string, menu menulibs.Menu) (id int, err error) {
+	sysName := Account.Get_Sys_Name(prefix)
+	subj := conf.NatsSubj_Prefix + fmt.Sprintf("%s_%s", prefix, "Delete_Menu")
+
+	b, _ := msgpack.Marshal(&menu)
+
+	msg, err := Nats.Nats.Request(subj, b, 3*time.Second)
+	if err != nil {
+		logs.Error(lib.FuncName(), sysName, err)
+		return id, err
+	}
+
+	type T_R struct {
+		Code int16  `xml:"Code"`
+		Msg  string `xml:"Msg"`
+		Data int    `xml:"Data"`
+	}
+	var t_R T_R
+
+	err = msgpack.Unmarshal(msg.Data, &t_R)
+	if err != nil {
+		logs.Error(lib.FuncName(), sysName, err)
+		return id, err
+	}
+	fmt.Printf("%s: %+v\n", subj, t_R)
+
+	if t_R.Code != 200 {
+		err = errors.New(t_R.Msg)
+		logs.Error(lib.FuncName(), sysName, err)
+		return id, err
+	}
+
+	return t_R.Data, nil
+}
+
+// ERP_Read_Menu_ById 根据ID获取菜单
+func ERP_Read_Menu_ById(prefix string, id int) (menu menulibs.Menu, err error) {
+	sysName := Account.Get_Sys_Name(prefix)
+	subj := conf.NatsSubj_Prefix + fmt.Sprintf("%s_%s", prefix, "Read_Menu_ById")
+
+	b, _ := msgpack.Marshal(&id)
+
+	msg, err := Nats.Nats.Request(subj, b, 3*time.Second)
+	if err != nil {
+		logs.Error(lib.FuncName(), sysName, err)
+		return menu, err
+	}
+
+	type T_R struct {
+		Code int16         `xml:"Code"`
+		Msg  string        `xml:"Msg"`
+		Data menulibs.Menu `xml:"Data"`
+	}
+	var t_R T_R
+
+	err = msgpack.Unmarshal(msg.Data, &t_R)
+	if err != nil {
+		logs.Error(lib.FuncName(), sysName, err)
+		return menu, err
+	}
+	fmt.Printf("%s: %+v\n", subj, t_R)
+
+	if t_R.Code != 200 {
+		err = errors.New(t_R.Msg)
+		logs.Error(lib.FuncName(), sysName, err)
+		return menu, err
+	}
+
+	return t_R.Data, nil
+}

+ 3 - 3
conf/app.conf

@@ -8,7 +8,7 @@ copyrequestbody = true
 NatsServer_Url = "127.0.0.1:4223"
 NatsSubj_Prefix = "Test_"
 # Mysql 线上
-MysqlServer_UrlPort = "127.0.0.1:3316"
+MysqlServer_UrlPort = "36.137.156.216:3306"
 MysqlServer_Database = "erp_user_test"
 MysqlServer_Username = "erp_user_test"
 MysqlServer_Password = "C8iaSLwhRpCermaR"
@@ -16,7 +16,7 @@ MysqlServer_MaxIdleConnections = 100
 MysqlServer_MaxOpenConnections = 200
 
 # Redis
-Redis_address = "127.0.0.1:6378"
+Redis_address = "36.137.156.216:6379"
 Redis_password = ""
 Redis_dbNum = "2"
 
@@ -30,7 +30,7 @@ FilterExcludeURL = /Login_verification
 FilterOnlyLoginCheckURL = /Menu/List,/User/Info,/User/Post,/Menu/User_List,/User/List,/User/Get,/UpFileToken,/Dept/List,/Post/List,/News/List,/News/See
 
 #Sys = ERP_ACCOUNT|账号管理,ERP_AMS|考勤管理,ERP_SALARY|薪资管理,ERP_STORAGE|仓库管理,ERP_PROJECT|项目管理
-Sys = ERP_ACCOUNT|账号管理,ERP_AMS|考勤管理,ERP_SALARY|薪资管理,ERP_STORAGE|仓库管理
+Sys = ERP_ACCOUNT|账号管理,ERP_AMS|考勤管理,ERP_SALARY|薪资管理,ERP_STORAGE|仓库管理,ERP_PROJECT|项目管理
 # Sys = ERP_ACCOUNT|账号管理,ERP_STORAGE|仓库管理
 
 # 冷链验证地址

+ 422 - 2
controllers/Menu.go

@@ -5,7 +5,10 @@ import (
 	"ERP_user/conf"
 	"ERP_user/logs"
 	"ERP_user/models/Account"
+	"ERP_user/models/System"
+	"encoding/json"
 	"fmt"
+
 	beego "github.com/beego/beego/v2/server/web"
 	menulibs "gogs.baozhida.cn/zoie/ERP_libs/Menu"
 	"gogs.baozhida.cn/zoie/ERP_libs/lib"
@@ -55,7 +58,6 @@ func (c *MenuController) List() {
 
 	c.Data["json"] = lib.JSONS{Code: 200, Msg: sysName + "ok!", Data: r_jsons}
 	c.ServeJSON()
-	return
 }
 
 // 列表 -
@@ -119,5 +121,423 @@ func (c *MenuController) User_List() {
 
 	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: r_jsons}
 	c.ServeJSON()
-	return
+}
+
+// 获取单个菜单详情
+func (c *MenuController) Get() {
+	id, _ := c.GetInt("id")
+	if id < 1 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "id Err!"}
+		c.ServeJSON()
+		return
+	}
+
+	T_code := c.GetString("T_code")
+	if len(T_code) == 0 {
+		T_code = conf.ERP_ACCOUNT_Sys // 默认本地系统
+	}
+	sysName := Account.Get_Sys_Name(T_code)
+	if sysName == T_code {
+		// 不存在这个code
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "T_code Err!"}
+		c.ServeJSON()
+		return
+	}
+
+	var menu menulibs.Menu
+	var apis []menulibs.API
+	var err error
+
+	// 根据T_code判断是本地系统还是其他系统
+	if T_code == conf.ERP_ACCOUNT_Sys {
+		menu, err = Account.Read_Menu_ById(id)
+		if err != nil {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: "查询失败!"}
+			c.ServeJSON()
+			return
+		}
+
+		// 获取关联的API列表
+		apis, err = Account.Read_API_List_ByMenuId(id)
+		if err != nil {
+			logs.Error(lib.FuncName(), "获取API列表失败:", err)
+		}
+	} else {
+		// 其他系统,通过NATS调用
+		menu, err = NatsServer.ERP_Read_Menu_ById(T_code, id)
+		if err != nil {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: sysName + " 查询失败!"}
+			c.ServeJSON()
+			return
+		}
+		// 注意:其他系统的API列表需要通过各自的系统获取,这里暂时不获取
+	}
+
+	type MenuWithAPIs struct {
+		menulibs.Menu
+		APIs []menulibs.API `json:"apis"`
+	}
+	menuWithAPIs := MenuWithAPIs{
+		Menu: menu,
+		APIs: apis,
+	}
+
+	var r_jsons lib.R_JSONS
+	r_jsons.Data = menuWithAPIs
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: r_jsons}
+	c.ServeJSON()
+}
+
+// 添加菜单
+func (c *MenuController) Add() {
+	T_name := c.GetString("T_name")
+	T_mid, _ := c.GetInt("T_mid")
+	T_permission := c.GetString("T_permission")
+	T_icon := c.GetString("T_icon")
+	T_uri := c.GetString("T_uri")
+	T_type := c.GetString("T_type")
+	T_code := c.GetString("T_code")
+	T_sort, _ := c.GetInt("T_sort")
+	T_apis := c.GetString("T_apis") // API JSON字符串
+
+	if len(T_name) < 1 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "菜单名称不能为空!"}
+		c.ServeJSON()
+		return
+	}
+
+	sysName := Account.Get_Sys_Name(T_code)
+	if sysName == T_code {
+		// 不存在这个code
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "T_code Err!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 如果未指定类型,默认为菜单类型
+	if len(T_type) == 0 {
+		T_type = menulibs.MenuType
+	}
+
+	var_ := menulibs.Menu{
+		T_mid:        T_mid,
+		T_name:       T_name,
+		T_permission: T_permission,
+		T_icon:       T_icon,
+		T_uri:        T_uri,
+		T_type:       T_type,
+		T_sort:       T_sort,
+		T_State:      1,
+	}
+
+	var id int
+	var err error
+
+	// 根据T_code判断是本地系统还是其他系统
+	if T_code == conf.ERP_ACCOUNT_Sys {
+		// 本地系统,直接调用
+		id, err = Account.Add_Menu(var_)
+		if err != nil {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加失败!"}
+			c.ServeJSON()
+			return
+		}
+
+		// 处理API JSON字符串
+		if len(T_apis) > 0 {
+			var apiList []struct {
+				T_name   string `json:"T_name"`
+				T_uri    string `json:"T_uri"`
+				T_method string `json:"T_method"`
+			}
+			err = json.Unmarshal([]byte(T_apis), &apiList)
+			if err != nil {
+				logs.Error(lib.FuncName(), "解析API JSON失败:", err)
+				c.Data["json"] = lib.JSONS{Code: 202, Msg: "API JSON格式错误!"}
+				c.ServeJSON()
+				return
+			}
+
+			// 批量添加API
+			if len(apiList) > 0 {
+				var apis []menulibs.API
+				for _, api := range apiList {
+					if len(api.T_method) == 0 {
+						api.T_method = "POST"
+					}
+					apis = append(apis, menulibs.API{
+						T_Menu_Id: id,
+						T_name:    api.T_name,
+						T_uri:     api.T_uri,
+						T_method:  api.T_method,
+						T_enable:  1,
+					})
+				}
+				_, err = Account.InsertMulti_API(apis)
+				if err != nil {
+					logs.Error(lib.FuncName(), "添加API失败:", err)
+					c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加API失败!"}
+					c.ServeJSON()
+					return
+				}
+
+			}
+		}
+
+	} else {
+		// 其他系统,通过NATS调用
+		id, err = NatsServer.ERP_Add_Menu(T_code, var_, T_apis)
+		if err != nil {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: sysName + " 添加失败!"}
+			c.ServeJSON()
+			return
+		}
+	}
+	// 清除所有权限的API缓存
+	Account.Redis_API_DelAll()
+	// 确保菜单缓存已清除(Add_Menu中已清除,这里再次确保)
+	Account.Redis_Menu_DelAll()
+	System.Add_UserLogs_T(c.User.T_uuid, "菜单", sysName+" 新增", var_)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: sysName + " ok!", Data: id}
+	c.ServeJSON()
+}
+
+// 修改菜单
+func (c *MenuController) Edit() {
+	id, _ := c.GetInt("id")
+	if id < 1 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "id Err!"}
+		c.ServeJSON()
+		return
+	}
+
+	T_code := c.GetString("T_code")
+	if len(T_code) == 0 {
+		T_code = conf.ERP_ACCOUNT_Sys // 默认本地系统
+	}
+	sysName := Account.Get_Sys_Name(T_code)
+	if sysName == T_code {
+		// 不存在这个code
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "T_code Err!"}
+		c.ServeJSON()
+		return
+	}
+
+	var menu menulibs.Menu
+	var err error
+
+	// 根据T_code判断是本地系统还是其他系统
+	if T_code == conf.ERP_ACCOUNT_Sys {
+		menu, err = Account.Read_Menu_ById(id)
+	} else {
+		menu, err = NatsServer.ERP_Read_Menu_ById(T_code, id)
+	}
+	if err != nil {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "查询失败!"}
+		c.ServeJSON()
+		return
+	}
+
+	var cols []string
+
+	// 获取需要更新的字段
+	// 检查参数是否存在(通过检查原始请求参数)
+	input, _ := c.Input()
+
+	if _, exists := input["T_mid"]; exists {
+		if T_mid, err := c.GetInt("T_mid"); err == nil {
+			menu.T_mid = T_mid
+			cols = append(cols, "T_mid")
+		}
+	}
+	if _, exists := input["T_name"]; exists {
+		if T_name := c.GetString("T_name"); T_name != "" {
+			menu.T_name = T_name
+			cols = append(cols, "T_name")
+		}
+	}
+	// T_permission, T_icon, T_uri 允许为空字符串,只要参数存在就更新
+	if _, exists := input["T_permission"]; exists {
+		menu.T_permission = c.GetString("T_permission")
+		cols = append(cols, "T_permission")
+	}
+	if _, exists := input["T_icon"]; exists {
+		menu.T_icon = c.GetString("T_icon")
+		cols = append(cols, "T_icon")
+	}
+	if _, exists := input["T_uri"]; exists {
+		menu.T_uri = c.GetString("T_uri")
+		cols = append(cols, "T_uri")
+	}
+	if _, exists := input["T_type"]; exists {
+		if T_type := c.GetString("T_type"); T_type != "" {
+			menu.T_type = T_type
+			cols = append(cols, "T_type")
+		}
+	}
+	if _, exists := input["T_sort"]; exists {
+		if T_sort, err := c.GetInt("T_sort"); err == nil {
+			menu.T_sort = T_sort
+			cols = append(cols, "T_sort")
+		}
+	}
+	if _, exists := input["T_State"]; exists {
+		if T_State, err := c.GetInt("T_State"); err == nil && T_State >= 0 {
+			menu.T_State = T_State
+			cols = append(cols, "T_State")
+		}
+	}
+
+	// 处理API JSON字符串
+	T_apis := c.GetString("T_apis")
+
+	if len(cols) == 0 && len(T_apis) == 0 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "没有需要更新的字段!"}
+		c.ServeJSON()
+		return
+	}
+
+	// 根据T_code判断是本地系统还是其他系统
+	if T_code == conf.ERP_ACCOUNT_Sys {
+		// 本地系统,直接调用
+		// 处理API JSON字符串
+		if _, exists := input["T_apis"]; exists {
+			// 先删除旧的API(软删除)
+			_, err = Account.Delete_API_ByMenuId(id)
+			if err != nil {
+				logs.Error(lib.FuncName(), "删除旧API失败:", err)
+			}
+
+			// 如果有新的API,则添加
+			if len(T_apis) > 0 {
+				var apiList []struct {
+					T_name   string `json:"T_name"`
+					T_uri    string `json:"T_uri"`
+					T_method string `json:"T_method"`
+				}
+				err = json.Unmarshal([]byte(T_apis), &apiList)
+				if err != nil {
+					logs.Error(lib.FuncName(), "解析API JSON失败:", err)
+					c.Data["json"] = lib.JSONS{Code: 202, Msg: "API JSON格式错误!"}
+					c.ServeJSON()
+					return
+				}
+
+				// 批量添加API
+				if len(apiList) > 0 {
+					var apis []menulibs.API
+					for _, api := range apiList {
+						if len(api.T_method) == 0 {
+							api.T_method = "POST"
+						}
+						apis = append(apis, menulibs.API{
+							T_Menu_Id: id,
+							T_name:    api.T_name,
+							T_uri:     api.T_uri,
+							T_method:  api.T_method,
+							T_enable:  1,
+						})
+					}
+					_, err = Account.InsertMulti_API(apis)
+					if err != nil {
+						logs.Error(lib.FuncName(), "添加API失败:", err)
+						c.Data["json"] = lib.JSONS{Code: 202, Msg: "添加API失败!"}
+						c.ServeJSON()
+						return
+					}
+				}
+			}
+		}
+
+		// 如果有菜单字段需要更新,则更新菜单
+		if len(cols) > 0 {
+			_, err = Account.Update_Menu(menu, cols...)
+			if err != nil {
+				c.Data["json"] = lib.JSONS{Code: 202, Msg: "修改失败!"}
+				c.ServeJSON()
+				return
+			}
+		}
+
+	} else {
+		// 其他系统,通过NATS调用
+		_, err = NatsServer.ERP_Update_Menu(T_code, menu, cols, T_apis)
+		if err != nil {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: sysName + " 修改失败!"}
+			c.ServeJSON()
+			return
+		}
+	}
+
+	Account.Redis_Menu_DelAll()
+	Account.Redis_API_DelAll()
+	System.Add_UserLogs_T(c.User.T_uuid, "菜单", sysName+" 修改", menu)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: sysName + " ok!", Data: id}
+	c.ServeJSON()
+}
+
+// 删除菜单(软删除)
+func (c *MenuController) Del() {
+	id, _ := c.GetInt("id")
+	if id < 1 {
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "id Err!"}
+		c.ServeJSON()
+		return
+	}
+
+	T_code := c.GetString("T_code")
+	if len(T_code) == 0 {
+		T_code = conf.ERP_ACCOUNT_Sys // 默认本地系统
+	}
+	sysName := Account.Get_Sys_Name(T_code)
+	if sysName == T_code {
+		// 不存在这个code
+		c.Data["json"] = lib.JSONS{Code: 202, Msg: "T_code Err!"}
+		c.ServeJSON()
+		return
+	}
+
+	var menu menulibs.Menu
+	var err error
+
+	// 根据T_code判断是本地系统还是其他系统
+	if T_code == conf.ERP_ACCOUNT_Sys {
+		menu, err = Account.Read_Menu_ById(id)
+		if err != nil {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: "查询失败!"}
+			c.ServeJSON()
+			return
+		}
+
+		_, err = Account.Delete_Menu(menu)
+		if err != nil {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: "删除失败!"}
+			c.ServeJSON()
+			return
+		}
+	} else {
+		// 其他系统,先获取菜单,然后删除
+		menu, err = NatsServer.ERP_Read_Menu_ById(T_code, id)
+		if err != nil {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: "查询失败!"}
+			c.ServeJSON()
+			return
+		}
+
+		_, err = NatsServer.ERP_Delete_Menu(T_code, menu)
+		if err != nil {
+			c.Data["json"] = lib.JSONS{Code: 202, Msg: sysName + " 删除失败!"}
+			c.ServeJSON()
+			return
+		}
+	}
+	Account.Redis_Menu_DelAll()
+	Account.Redis_API_DelAll()
+	System.Add_UserLogs_T(c.User.T_uuid, "菜单", sysName+" 删除", menu)
+
+	c.Data["json"] = lib.JSONS{Code: 200, Msg: sysName + " ok!", Data: id}
+	c.ServeJSON()
 }

+ 103 - 4
controllers/Power.go

@@ -6,14 +6,15 @@ import (
 	"ERP_user/models/Account"
 	"ERP_user/models/System"
 	"fmt"
+	"math"
+	"strings"
+
 	"github.com/astaxie/beego/logs"
 	"github.com/beego/beego/v2/adapter/orm"
 	beego "github.com/beego/beego/v2/server/web"
 	menulibs "gogs.baozhida.cn/zoie/ERP_libs/Menu"
 	powerlibs "gogs.baozhida.cn/zoie/ERP_libs/Power"
 	"gogs.baozhida.cn/zoie/ERP_libs/lib"
-	"math"
-	"strings"
 )
 
 type PowerController struct {
@@ -125,10 +126,16 @@ func (c *PowerController) Get() {
 
 	data.Power = powerlibs.PowerToPower_R(power)
 	data.Menu = menu
+
+	// 处理 Menu_checked
+	// 如果角色没有某个父菜单下的所有子菜单权限,则不返回该父菜单给前端
 	if power.T_menu != "*" {
-		data.Menu_checked = lib.SplitStringToIntIds(power.T_menu, "M")
-	}
+		// 先获取已勾选的菜单ID列表
+		checkedIds := lib.SplitStringToIntIds(power.T_menu, "M")
 
+		// 过滤父级:仅当父菜单的所有子菜单都被勾选时才保留父菜单
+		data.Menu_checked = filterParentMenusFromChecked(menu, checkedIds)
+	}
 	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: data}
 	c.ServeJSON()
 	return
@@ -443,3 +450,95 @@ func (c *PowerController) Del() {
 	return
 
 }
+
+// 规则:父菜单只有在其所有直接子菜单都被勾选时,才可被返回;
+// 否则移除该父菜单。该规则会向上递归传播,确保多层级结构正确处理。
+func filterParentMenusFromChecked(menu []menulibs.Menu, checkedIds []int) []int {
+	// 先将树形菜单结构扁平化,提取所有菜单节点
+	allMenus := flattenMenuTree(menu)
+
+	// 构建父子关系映射
+	parentToChildren := make(map[int][]int)
+	for _, m := range allMenus {
+		if m.T_mid > 0 {
+			parentToChildren[m.T_mid] = append(parentToChildren[m.T_mid], m.Id)
+		}
+	}
+
+	// 勾选集合
+	checked := make(map[int]bool)
+	for _, id := range checkedIds {
+		checked[id] = true
+	}
+
+	// 计算叶子集合(无子节点的菜单)
+	isLeaf := make(map[int]bool)
+	for _, m := range allMenus {
+		isLeaf[m.Id] = len(parentToChildren[m.Id]) == 0
+	}
+
+	// DFS 获取某父级下的所有叶子后代
+	var getDescendantLeaves func(int, map[int]bool)
+	getDescendantLeaves = func(id int, leaves map[int]bool) {
+		children := parentToChildren[id]
+		if len(children) == 0 {
+			// 自身为叶子
+			leaves[id] = true
+			return
+		}
+		for _, child := range children {
+			if len(parentToChildren[child]) == 0 {
+				leaves[child] = true
+			} else {
+				getDescendantLeaves(child, leaves)
+			}
+		}
+	}
+
+	// 结果集合:
+	// 1) 始终包含勾选的叶子节点
+	// 2) 对于勾选的父级,仅当其所有叶子后代都被勾选时,才包含该父级
+	include := make(map[int]bool)
+	for id := range checked {
+		if isLeaf[id] {
+			include[id] = true
+		}
+	}
+
+	for id := range checked {
+		if isLeaf[id] {
+			continue
+		}
+		leaves := make(map[int]bool)
+		getDescendantLeaves(id, leaves)
+		allLeavesChecked := true
+		for leaf := range leaves {
+			if !checked[leaf] {
+				allLeavesChecked = false
+				break
+			}
+		}
+		if allLeavesChecked {
+			include[id] = true
+		}
+	}
+
+	// 输出结果
+	result := make([]int, 0, len(include))
+	for id := range include {
+		result = append(result, id)
+	}
+	return result
+}
+
+// flattenMenuTree 将树形菜单结构扁平化为一维数组
+func flattenMenuTree(menu []menulibs.Menu) []menulibs.Menu {
+	result := make([]menulibs.Menu, 0)
+	for _, m := range menu {
+		result = append(result, m)
+		if len(m.Children) > 0 {
+			result = append(result, flattenMenuTree(m.Children)...)
+		}
+	}
+	return result
+}

+ 22 - 3
controllers/User.go

@@ -6,13 +6,15 @@ import (
 	"ERP_user/models/System"
 	"encoding/json"
 	"fmt"
-	beego "github.com/beego/beego/v2/server/web"
-	"gogs.baozhida.cn/zoie/ERP_libs/lib"
 	"io"
 	"math"
 	"net/http"
 	"net/url"
 	"time"
+
+	beego "github.com/beego/beego/v2/server/web"
+	powerlibs "gogs.baozhida.cn/zoie/ERP_libs/Power"
+	"gogs.baozhida.cn/zoie/ERP_libs/lib"
 )
 
 type UserController struct {
@@ -102,7 +104,24 @@ func (c *UserController) Get() {
 
 // 个人信息
 func (c *UserController) Info() {
-	c.Data["json"] = lib.JSONS{Code: 200, Msg: "ok!", Data: Account.UserToUser_R(c.User)}
+	type JSONS struct {
+		Code  int16
+		Msg   string
+		Data  Account.User_R
+		Power powerlibs.Power_R
+	}
+
+	var data JSONS
+	data.Data = Account.UserToUser_R(c.User)
+
+	// 获取权限信息
+	power, err := Account.Read_Power_ByT_id(c.User.T_power)
+	if err == nil {
+		powerR := powerlibs.PowerToPower_R(power)
+		data.Power = powerR
+	}
+
+	c.Data["json"] = data
 	c.ServeJSON()
 	return
 }

+ 1 - 1
go.mod

@@ -37,7 +37,7 @@ require (
 	github.com/shopspring/decimal v1.3.1 // indirect
 	github.com/signintech/gopdf v0.16.1 // indirect
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
-	gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250901061433-cb5c3676aa11 // indirect
+	gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20251106072747-8715c5686ee8 // indirect
 	golang.org/x/crypto v0.31.0 // indirect
 	golang.org/x/net v0.33.0 // indirect
 	golang.org/x/sync v0.10.0 // indirect

+ 2 - 0
go.sum

@@ -321,6 +321,8 @@ gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250418032958-0b7532afe8a3 h1:gaqybp5CMEZ
 gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250418032958-0b7532afe8a3/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
 gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250901061433-cb5c3676aa11 h1:aMuM32qXdOojinJW9w9rm0tSopDvD5eecI14YXnwMdA=
 gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20250901061433-cb5c3676aa11/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20251106072747-8715c5686ee8 h1:UisQz0DrnbqRFLRgeD7iiungJp6qZlyfRUsMQpXUOmE=
+gogs.baozhida.cn/zoie/ERP_libs v0.0.0-20251106072747-8715c5686ee8/go.mod h1:wS/rgL1FkdfMsCwKpwG04dZtG4SOFcBADi4lxHN7UeM=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=

+ 132 - 1
models/Account/Menu.go

@@ -4,13 +4,15 @@ import (
 	"ERP_user/conf"
 	"encoding/json"
 	"fmt"
+	"time"
+
 	"github.com/astaxie/beego/cache"
 	_ "github.com/astaxie/beego/cache/redis"
 	"github.com/astaxie/beego/logs"
 	"github.com/beego/beego/v2/adapter/orm"
 	menulibs "gogs.baozhida.cn/zoie/ERP_libs/Menu"
+	powerlibs "gogs.baozhida.cn/zoie/ERP_libs/Power"
 	"gogs.baozhida.cn/zoie/ERP_libs/lib"
-	"time"
 )
 
 var redisCache_API cache.Cache
@@ -83,6 +85,27 @@ func Redis_Menu_DelK(T_power_id string) (err error) {
 	return
 }
 
+// Redis_Menu_DelAll 清除所有菜单缓存
+func Redis_Menu_DelAll() (err error) {
+	o := orm.NewOrm()
+	powerDao := powerlibs.NewPower(o)
+
+	// 获取所有权限ID列表(不分页,获取全部)
+	// 使用较大的page_z值来获取所有权限
+	powerList, _ := powerDao.Read_Power_List("", 1, 9999) // 假设最多10000个权限
+
+	// 遍历所有权限ID,逐个删除缓存
+	for _, power := range powerList {
+		err = Redis_Menu_DelK(power.T_id)
+		if err != nil {
+			logs.Error(lib.FuncName(), "删除缓存失败:", power.T_id, err)
+			// 继续删除其他缓存,不中断
+		}
+	}
+
+	return nil
+}
+
 func Read_Menu_List() ([]menulibs.Menu, error) {
 	o := orm.NewOrm()
 	MenuDao := menulibs.NewMenu(o)
@@ -122,3 +145,111 @@ func Read_API_List_ByPower(T_power_id string, Menu_Bind string) (maps []menulibs
 	}
 	return maps
 }
+
+// 添加菜单
+func Add_Menu(r menulibs.Menu) (id int, err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(&r)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return 0, err
+	}
+	return r.Id, err
+}
+
+// 获取菜单 ById
+func Read_Menu_ById(id int) (r menulibs.Menu, err error) {
+	o := orm.NewOrm()
+	r.Id = id
+	err = o.Read(&r)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return r, err
+	}
+	return r, nil
+}
+
+// 修改菜单
+func Update_Menu(r menulibs.Menu, cols ...string) (id int, err error) {
+	o := orm.NewOrm()
+
+	_, err = o.Update(&r, cols...)
+
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return r.Id, err
+	}
+	return r.Id, err
+}
+
+// 删除菜单(软删除,设置 T_State = 0)
+func Delete_Menu(r menulibs.Menu) (id int, err error) {
+	o := orm.NewOrm()
+	r.T_State = 0 // 软删除
+	_, err = o.Update(&r, "T_State")
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return r.Id, err
+	}
+	return r.Id, err
+}
+
+// ========== API 相关方法 ==========
+
+// 根据菜单ID获取API列表
+func Read_API_List_ByMenuId(T_Menu_Id int) (maps []menulibs.API, err error) {
+	o := orm.NewOrm()
+	APIDao := menulibs.NewAPI(o, redisCache_API)
+	maps, err = APIDao.Read_API_List_ByMenuId(T_Menu_Id)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return maps, err
+	}
+	return maps, nil
+}
+
+// 批量添加API
+func InsertMulti_API(r []menulibs.API) (id int64, err error) {
+	o := orm.NewOrm()
+	APIDao := menulibs.NewAPI(o, redisCache_API)
+	id, err = APIDao.InsertMulti_API(r)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return 0, err
+	}
+	return id, err
+}
+
+// 根据菜单ID删除所有关联的API(软删除)
+func Delete_API_ByMenuId(T_Menu_Id int) (int64, error) {
+	o := orm.NewOrm()
+	APIDao := menulibs.NewAPI(o, redisCache_API)
+	cnt, err := APIDao.Delete_API_ByMenuId(T_Menu_Id)
+	if err != nil {
+		logs.Error(lib.FuncName(), err)
+		return 0, err
+	}
+	return cnt, err
+}
+
+// Redis_API_DelAll 清除所有API缓存
+func Redis_API_DelAll() (err error) {
+	o := orm.NewOrm()
+	powerDao := powerlibs.NewPower(o)
+
+	// 获取所有权限ID列表(不分页,获取全部)
+	// 使用较大的page_z值来获取所有权限
+	powerList, _ := powerDao.Read_Power_List("", 1, 9999) // 假设最多10000个权限
+
+	// 遍历所有权限ID,逐个删除API缓存
+	APIDao := menulibs.NewAPI(o, redisCache_API)
+	for _, power := range powerList {
+		err = APIDao.Redis_API_DelK(power.T_id)
+		if err != nil {
+			logs.Error(lib.FuncName(), "删除API缓存失败:", power.T_id, err)
+			// 继续删除其他缓存,不中断
+		}
+	}
+
+	return nil
+}

+ 13 - 10
routers/User.go

@@ -2,6 +2,7 @@ package routers
 
 import (
 	"ERP_user/controllers"
+
 	beego "github.com/beego/beego/v2/server/web"
 )
 
@@ -12,14 +13,14 @@ func init() {
 	beego.Router("/Login_verification", &controllers.UserController{}, "*:Login_verification") // 获取未读消息
 
 	//-----------用户管理
-	beego.Router("/User/List", &controllers.UserController{}, "*:List")   //
-	beego.Router("/User/Get", &controllers.UserController{}, "*:Get")     //
-	beego.Router("/User/Info", &controllers.UserController{}, "*:Info")   //
-	beego.Router("/User/Post", &controllers.UserController{}, "*:Post")   //
-	beego.Router("/User/Add", &controllers.UserController{}, "*:Add")     //
-	beego.Router("/User/Edit", &controllers.UserController{}, "*:Edit")   //
-	beego.Router("/User/Del", &controllers.UserController{}, "*:Del")     //
-	beego.Router("/User/Leave", &controllers.UserController{}, "*:Leave") //离职
+	beego.Router("/User/List", &controllers.UserController{}, "*:List")                            //
+	beego.Router("/User/Get", &controllers.UserController{}, "*:Get")                              //
+	beego.Router("/User/Info", &controllers.UserController{}, "*:Info")                            //
+	beego.Router("/User/Post", &controllers.UserController{}, "*:Post")                            //
+	beego.Router("/User/Add", &controllers.UserController{}, "*:Add")                              //
+	beego.Router("/User/Edit", &controllers.UserController{}, "*:Edit")                            //
+	beego.Router("/User/Del", &controllers.UserController{}, "*:Del")                              //
+	beego.Router("/User/Leave", &controllers.UserController{}, "*:Leave")                          //离职
 	beego.Router("/ColdVerify/User/list", &controllers.UserController{}, "*:ColdVerify_User_List") //冷链验证用户列表
 
 	//-----------权限管理
@@ -46,12 +47,14 @@ func init() {
 	//-----------菜单
 	beego.Router("/Menu/List", &controllers.MenuController{}, "*:List")           // 菜单列表
 	beego.Router("/Menu/User_List", &controllers.MenuController{}, "*:User_List") // 用户绑定菜单列表
+	beego.Router("/Menu/Get", &controllers.MenuController{}, "*:Get")             // 获取菜单详情
+	beego.Router("/Menu/Add", &controllers.MenuController{}, "*:Add")             // 添加菜单
+	beego.Router("/Menu/Edit", &controllers.MenuController{}, "*:Edit")           // 修改菜单
+	beego.Router("/Menu/Del", &controllers.MenuController{}, "*:Del")             // 删除菜单
 
 	//-----------部门
 	beego.Router("/Dept/List", &controllers.DeptController{}, "*:List") // 部门列表
 	//-----------岗位
 	beego.Router("/Post/List", &controllers.DeptController{}, "*:Post_List") // 岗位列表
 
-
-
 }