tree_menu.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. package tree_menu
  2. import (
  3. "github.com/druidcaesa/gotool"
  4. "sort"
  5. "ulink-admin/modules/system/models/model"
  6. )
  7. // Tree 统一定义菜单树的数据结构,也可以自定义添加其他字段
  8. type Tree struct {
  9. Name string `json:"name,omitempty"` //节点名字
  10. Path string `json:"path,omitempty"`
  11. Hidden bool `json:"hidden"`
  12. Redirect string `json:"redirect,omitempty"`
  13. Component string `json:"component,omitempty"`
  14. AlwaysShow bool `json:"alwaysShow"`
  15. Data interface{} `json:"-"` //自定义对象
  16. Meta interface{} `json:"meta"`
  17. Leaf bool `json:"-"` //叶子节点
  18. Selected bool `json:"-"` //选中
  19. PartialSelected bool `json:"-"` //部分选中
  20. Children []Tree `json:"children"` //子节点
  21. Id int64 `json:"id"`
  22. Label string `json:"label"`
  23. }
  24. // ConvertToINodeArray 其他的结构体想要生成菜单树,直接实现这个接口
  25. type INode interface {
  26. // GetName 获取显示名字
  27. GetName() string
  28. // GetMenuId 获取id
  29. GetMenuId() int64
  30. // GetParentId 获取父id
  31. GetParentId() int64
  32. // GetData 获取附加数据
  33. GetData() interface{}
  34. // IsRoot 判断当前节点是否是顶层根节点
  35. IsRoot() bool
  36. // GetPath 路径
  37. GetPath() string
  38. GetId() int64
  39. GetLabel() string
  40. }
  41. type INodes []INode
  42. func (nodes INodes) Len() int {
  43. return len(nodes)
  44. }
  45. func (nodes INodes) Swap(i, j int) {
  46. nodes[i], nodes[j] = nodes[j], nodes[i]
  47. }
  48. func (nodes INodes) Less(i, j int) bool {
  49. return nodes[i].GetMenuId() < nodes[j].GetMenuId()
  50. }
  51. // GenerateTree 自定义的结构体实现 INode 接口后调用此方法生成树结构
  52. // nodes 需要生成树的节点
  53. // selectedNode 生成树后选中的节点
  54. // menuTrees 生成成功后的树结构对象
  55. func GenerateTree(nodes, selectedNodes []INode) (trees []Tree) {
  56. trees = []Tree{}
  57. // 定义顶层根和子节点
  58. var roots, childs []INode
  59. for _, v := range nodes {
  60. if v.IsRoot() {
  61. // 判断顶层根节点
  62. roots = append(roots, v)
  63. }
  64. childs = append(childs, v)
  65. }
  66. for _, v := range roots {
  67. var flag = false
  68. var component = "Layout"
  69. m := make(map[string]interface{})
  70. if v.GetData() != nil {
  71. menu := v.GetData().(model.SysMenu)
  72. m["title"] = menu.MenuName
  73. m["icon"] = menu.Icon
  74. m["noCache"] = menu.IsCache == 1
  75. if menu.IsFrame == 0 {
  76. m["link"] = menu.Path
  77. } else {
  78. m["link"] = nil
  79. }
  80. flag = menu.Visible
  81. if !gotool.StrUtils.HasEmpty(menu.Component) {
  82. component = menu.Component
  83. }
  84. }
  85. childTree := &Tree{
  86. Name: v.GetName(),
  87. Data: v.GetData(),
  88. Path: "/" + v.GetPath(),
  89. Hidden: flag,
  90. AlwaysShow: true,
  91. Redirect: "noRedirect",
  92. Meta: m,
  93. Component: component,
  94. Id: v.GetId(),
  95. Label: v.GetLabel(),
  96. }
  97. // 递归之前,根据父节点确认 childTree 的选中状态
  98. childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children)
  99. // 递归
  100. recursiveTree(childTree, childs, selectedNodes)
  101. // 递归之后,根据子节点确认 childTree 的选中状态
  102. if !childTree.Selected {
  103. childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children)
  104. }
  105. // 递归之后,根据子节点确认 childTree 的半选中状态
  106. childTree.PartialSelected = nodePartialSelected(childTree.Children)
  107. // 递归之后,根据子节确认是否是叶子节点
  108. childTree.Leaf = len(childTree.Children) == 0
  109. trees = append(trees, *childTree)
  110. }
  111. return
  112. }
  113. // recursiveTree 递归生成树结构
  114. // tree 递归的树对象
  115. // nodes 递归的节点
  116. // selectedNodes 选中的节点
  117. func recursiveTree(tree *Tree, nodes, selectedNodes []INode) {
  118. data := tree.Data.(INode)
  119. for _, v := range nodes {
  120. if v.IsRoot() {
  121. // 如果当前节点是顶层根节点就跳过
  122. continue
  123. }
  124. var flag = false
  125. var component = "Layout"
  126. m := make(map[string]interface{})
  127. if v.GetData() != nil {
  128. menu := v.GetData().(model.SysMenu)
  129. flag = menu.Visible
  130. m["title"] = menu.MenuName
  131. m["icon"] = menu.Icon
  132. m["noCache"] = menu.IsCache == 1
  133. if menu.IsFrame == 0 {
  134. m["link"] = menu.Path
  135. } else {
  136. m["link"] = nil
  137. }
  138. if !gotool.StrUtils.HasEmpty(menu.Component) {
  139. component = menu.Component
  140. }
  141. }
  142. if data.GetMenuId() == v.GetParentId() {
  143. childTree := &Tree{
  144. Name: v.GetName(),
  145. Data: v.GetData(),
  146. Path: v.GetPath(),
  147. Hidden: flag,
  148. Meta: m,
  149. Component: component,
  150. Id: v.GetId(),
  151. Label: v.GetLabel(),
  152. }
  153. // 递归之前,根据子节点和父节点确认 childTree 的选中状态
  154. childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children) || tree.Selected
  155. recursiveTree(childTree, nodes, selectedNodes)
  156. if !childTree.Selected {
  157. // 递归之后,根据子节点确认 childTree 的选中状态
  158. childTree.Selected = nodeSelected(v, selectedNodes, childTree.Children)
  159. }
  160. // 递归之后,根据子节点确认 childTree 的半选中状态
  161. childTree.PartialSelected = nodePartialSelected(childTree.Children)
  162. // 递归之后,根据子节确认是否是叶子节点
  163. childTree.Leaf = len(childTree.Children) == 0
  164. tree.Children = append(tree.Children, *childTree)
  165. }
  166. }
  167. }
  168. // FindRelationNode 在 allTree 中查询 nodes 中节点的所有父节点
  169. // nodes 要查询父节点的子节点数组
  170. // allTree 所有节点数组
  171. func FindRelationNode(nodes, allNodes []INode) (respNodes []INode) {
  172. nodeMap := make(map[int64]INode)
  173. for _, v := range nodes {
  174. recursiveFindRelationNode(nodeMap, allNodes, v, 0)
  175. }
  176. for _, v := range nodeMap {
  177. respNodes = append(respNodes, v)
  178. }
  179. sort.Sort(INodes(respNodes))
  180. return
  181. }
  182. // recursiveFindRelationNode 递归查询关联父子节点
  183. // nodeMap 查询结果搜集到map中
  184. // allNodes 所有节点
  185. // node 递归节点
  186. // t 递归查找类型:0 查找父、子节点;1 只查找父节点;2 只查找子节点
  187. func recursiveFindRelationNode(nodeMap map[int64]INode, allNodes []INode, node INode, t int) {
  188. nodeMap[node.GetMenuId()] = node
  189. for _, v := range allNodes {
  190. if _, ok := nodeMap[v.GetMenuId()]; ok {
  191. continue
  192. }
  193. // 查找父节点
  194. if t == 0 || t == 1 {
  195. if node.GetParentId() == v.GetMenuId() {
  196. nodeMap[v.GetMenuId()] = v
  197. if v.IsRoot() {
  198. // 是顶层根节点时,不再进行递归
  199. continue
  200. }
  201. recursiveFindRelationNode(nodeMap, allNodes, v, 1)
  202. }
  203. }
  204. // 查找子节点
  205. if t == 0 || t == 2 {
  206. if node.GetMenuId() == v.GetParentId() {
  207. nodeMap[v.GetMenuId()] = v
  208. recursiveFindRelationNode(nodeMap, allNodes, v, 2)
  209. }
  210. }
  211. }
  212. }
  213. // nodeSelected 判断节点的选中状态
  214. // node 进行判断节点
  215. func nodeSelected(node INode, selectedNodes []INode, children []Tree) bool {
  216. for _, v := range selectedNodes {
  217. if node.GetMenuId() == v.GetMenuId() {
  218. // 1. 如果选择节点数组中存在当前节点
  219. return true
  220. }
  221. }
  222. if len(children) == 0 {
  223. // 2. 不满足前置条件1,且没有子节点
  224. return false
  225. }
  226. selectedNum := 0
  227. for _, v := range children {
  228. if v.Selected {
  229. selectedNum++
  230. }
  231. }
  232. if selectedNum == len(children) {
  233. // 不满足前置条件1,2 ,且子节点全部是选中状态
  234. return true
  235. }
  236. return false
  237. }
  238. // nodePartialSelected 判断节点的半选中状态
  239. func nodePartialSelected(trees []Tree) bool {
  240. selectedNum := 0
  241. for _, v := range trees {
  242. if v.Selected {
  243. selectedNum++
  244. }
  245. }
  246. if selectedNum == len(trees) || selectedNum == 0 {
  247. // 子节点全选中,或一个也没有选中
  248. return false
  249. }
  250. return true
  251. }
  252. type SystemMenus []model.SysMenu
  253. // ConvertToINodeArray 将当前数组转换成父类 INode 接口 数组
  254. func (s SystemMenus) ConvertToINodeArray() (nodes []INode) {
  255. for _, v := range s {
  256. nodes = append(nodes, v)
  257. }
  258. return
  259. }
  260. // GetTree 获取树结构数据
  261. func (s SystemMenus) GetTree() []Tree {
  262. array := s.ConvertToINodeArray()
  263. return GenerateTree(array, nil)
  264. }