取消

Vue+abp增加三级菜单

原生vue版的abp只支持2级菜单,项目需要增加成3级菜单,一番搜索。成果如下


增加3级菜单显示

修改components->shrinkable-menu->components->sidebarMenu文件,增加一级菜单,并增加两个方法hasChildren和getChildren,避免html因为没有children属性报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
    <Menu ref="sideMenu" :active-name="$route.name" :open-names="openNames" :theme="menuTheme" width="auto" @on-select="changeMenu">
        <template v-for="item in menuList">
            <MenuItem v-if="item.children.length<=0" :name="item.children[0].name" :key="item.name">
                <!-- <Icon :type="item.icon" :size="iconSize"></Icon> -->
                <span class="iconfont"></span>
                <span></span>
            </MenuItem>
            <Submenu v-if="item.children.length > 0&&!item.meta.hidden" :name="item.name" :key="item.name">
                <template slot="title">
                    <i class="iconfont" v-html="item.icon"></i>
                    <span ></span>
                </template>
                <template v-for="child in item.children">
                    <MenuItem v-if="!hasChildren(child)&&!child.meta.hidden" :name="child.name" :key="child.name"> 
                        <i class="iconfont" v-html="child.icon"></i>                       
                        <span> </span>
                    </MenuItem>
                    <Submenu v-if="hasChildren(child)&&!child.meta.hidden" :name="child.name" :key="child.name">
                        <template slot="title">
                            <i class="iconfont" v-html="child.icon"></i>
                            <span ></span>
                        </template>
                        <template v-for="ss in child.children">
                            <MenuItem v-if="!hasChildren(ss)&&!ss.meta.hidden" :name="ss.name" :key="ss.name"> 
                                <i class="iconfont" v-html="ss.icon"></i>                       
                                <span> </span>
                            </MenuItem>
                        </template>
                    </Submenu>
                </template>
            </Submenu>
        </template>
    </Menu>
</template>
<script lang="ts">
    hasChildren(item:any){
        return !!item.children&&item.children.length>0
    }
    getChildren(item:any){
        return item.children;
    }
</script>

修改显示路由方法

就是显示图上这个 显示路由 这个方法在lib->util.ts文件中,我是抄的Vue iview-admin模板二级菜单改为三级菜单,根据abp做了一些调整,修改setCurrentPath方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
  setCurrentPath(vm: Vue, name?: string) {
    let title = "";
    let isOtherRouter = false;
    vm.$store.state.app.routers.forEach(item => {
      if (item.children.length === 1) {
        if (item.children[0].name === name) {
          title = util.handleTitle(vm, item);
          if (item.name === "otherRouter") {
            isOtherRouter = true;
          }
        }
      } else {
        item.children.forEach(child => {
          if (child.name === name) {
            title = util.handleTitle(vm, child);
            if (item.name === "otherRouter") {
              isOtherRouter = true;
            }
          }
        });
      }
    });
    let currentPathArr = [];
    //去首页
    if (name === "home_index") {
      currentPathArr = [
        {
          meta: {title: util.handleTitle(
            vm,
            util.getRouterObjByName(vm.$store.state.app.routers, "home_index")
          )},
          path: "",
          name: "home_index"
        }
      ];
    }
    //去导航菜单一级页面或者OtherRouter路由中的页面
    else if (
      (name.indexOf("_index") >= 0 || isOtherRouter) &&
      name !== "home_index"
    ) {
      currentPathArr = [
        {
          meta: {title: util.handleTitle(
            vm,
            util.getRouterObjByName(vm.$store.state.app.routers, "home_index")
          )},
          path: "/home",
          name: "home_index"
        },
        {
          meta: {title: title},
          path: "",
          name: name
        }
      ];
    }
    //去导航菜单二级页面或三级页面
    else {
      let currentPathObj = vm.$store.state.app.routers.filter(item => {
        var hasMenu;

        if (item.children.length <= 1) {
          hasMenu = item.children[0].name === name;
          return hasMenu;
        } else {
          let i = 0;
          let childArr = item.children;
          let len = childArr.length;
          while (i < len) {
            //如果是三级页面按钮,则在二级按钮数组中找不到这个按钮名称
            //需要二级页面下可能出现三级子菜单的情况逻辑加入
            if (childArr[i].name === name) {
              hasMenu = true;
              return hasMenu;
            }
            i++;
          }
          //如果一级,二级菜单下都没有此按钮名称,则遍历三级菜单
          if (!hasMenu) {
            for (let m = 0; m < childArr.length; m++) {
              if (!childArr[m].children) continue;
              let sonArr = childArr[m].children;
              for (let n = 0; n < sonArr.length; n++) {
                if (sonArr[n].name === name) {
                  hasMenu = true;
                  return hasMenu;
                }
              }
            }
          }

          return false;
        }
      })[0];

      if (
        currentPathObj.children.length <= 1 &&
        currentPathObj.name === "home"
      ) {
        currentPathArr = [
          {
                meta: { title: "HomePage" },
                path: "main/home",
                name: "home"
          }
        ];
      } else if (
        currentPathObj.children.length <= 1 &&
        currentPathObj.name !== "home"
      ) {
        currentPathArr = [
          {
                meta: { title: "HomePage" },
                path: "main/home",
                name: "home"
          },
          {
            meta: {title: currentPathObj.meta.title},
            path: "",
            name: name
          }
        ];
      } else {
        //如果是三级页面按钮,则在二级按钮数组中找不到这个按钮名称
        //需要二级页面下可能出现三级子菜单的情况逻辑加入
        let childObj = currentPathObj.children.filter(child => {
          return child.name === name;
        })[0];
        //二级页面
        if (childObj) {
          currentPathArr = [
            {
                meta: { title: "HomePage" },
                path: "main/home",
                name: "home"
            },
            {
              meta: {title: currentPathObj.meta.title},
              path: "",
              name: ""
            },
            {
              meta: {title: childObj.meta.title},
              path: currentPathObj.path + "/" + childObj.path,
              name: name
            }
          ];
        }
        //childobj为undefined,再从三级页面中遍历
        else {
          let thirdObj;
          let childObj = currentPathObj.children.filter(child => {
            let hasChildren;
            hasChildren = child.name === name;
            if (hasChildren) return hasChildren;

            if (child.children) {
              let sonArr = child.children;
              for (let n = 0; n < sonArr.length; n++) {
                if (sonArr[n].name === name) {
                  thirdObj = sonArr[n];
                  hasChildren = true;
                  return hasChildren;
                }
              }
            }
            return hasChildren;
          })[0];

          if (thirdObj && childObj) {
            currentPathArr = [
              {
                meta: { title: "HomePage" },
                path: "main/home",
                name: "home"
              },
              {
                meta: {title: currentPathObj.meta.title},
                path: "",
                name: ""
              },
              {
                meta: {title: childObj.meta.title},
                path: "", //设为空是因为此二级菜单没有实际页面且用于面包屑组件显示,path为空的将不可单击
                name: ""
              },
              {
                meta: {title: thirdObj.meta.title},
                path:
                  currentPathObj.path +
                  "/" +
                  childObj.path +
                  "/" +
                  thirdObj.path,
                name: thirdObj.name
              }
            ];
          }
        }
      }
    }
    vm.$store.commit("app/setCurrentPath", currentPathArr);
    return currentPathArr;
  }

修改根据菜单权限加载菜单

如果不修改加载权限,则第三级菜单无法用权限控制,第二级菜单也必须定义权限才能显示。如果第二级菜单只是目录,第三级菜单都没有权限,那么第二级目录是不应该显示出来的。 在store->modules->app.ts中修改updateMenulist方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
updateMenulist(state: AppState) {
    let menuList: Array<Router> = [];
    [...appRouters,...organizeRouters,...labRouters,...labcheckRouters,
        ...devRouters,...labreportRouters,...appraiseRouters].forEach((item, index) => {
        if (item.permission !== undefined) {
            Util.addHasPermissionChileMenu(item);
            if(item.children&&item.children.length>0){
                menuList.push(item);
            }
            // let hasPermissionMenuArr: Array<Router> = [];
            // hasPermissionMenuArr = item.children.filter(child => {
            //     if (child.permission !== undefined) {
            //         if (Util.abp.auth.hasPermission(child.permission)) {
            //             return child;
            //         }
            //     } else {
            //         return child;
            //     }
            // });
            // if (hasPermissionMenuArr.length > 0) {
            //     item.children = hasPermissionMenuArr;
            //     menuList.push(item);
            // }
        } else {
            if (item.children.length === 1) {
                menuList.push(item);
            } else {
                let len = menuList.push(item);
                let childrenArr = [];
                childrenArr = item.children.filter(child => {
                    return child;
                });
                let handledItem = JSON.parse(JSON.stringify(menuList[len - 1]));
                handledItem.children = childrenArr;
                menuList.splice(len - 1, 1, handledItem);
            }
        }
    });
    state.menuList = menuList;
}

在lib->util文件中,增加方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
addHasPermissionChileMenu(item:any){
    let that=this;
    let hasPermissionMenuArr: Array<Router> = [];
    if(!item.children){
        return;
    }
    hasPermissionMenuArr = item.children.filter(child => {  
        let isFather=!!child.children;
        that.addHasPermissionChileMenu(child);
        let hasChildren=!!child.children
        if (isFather&&!hasChildren) {
            return false;
        }

        if (child.permission !== undefined) {
            if (that.abp.auth.hasPermission(child.permission)) {
                return child;
            }
        } else {
            return child;
        }
    });
    if (hasPermissionMenuArr.length > 0) {
        item.children = hasPermissionMenuArr;
    }else{
        item.children=null;
    }
}

增加三级菜单路由

在component目录中增加一个显示三级菜单内容的容器three-leve-container.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div>
  <router-view></router-view>
</div>

</template>

<script lang="ts">
import { Component, Vue, Inject } from "vue-property-decorator";
import ViewUI from "view-design";
import AbpBase from "../lib/abpbase";
import util from "../lib/util";
@Component({
  components: {}
})
export default class ThreeLeveContainer extends AbpBase {
      
}
</script>

<style>
</style>

修改打开后的tab

做完以上步骤添加路由就能打开页面了,但是,你会发现打开菜单能正常打开一个新的tab页面,但是在打开同一个二级菜单下的两个三级菜单时,并没有打开新的tab,而是替换了前一次打开的tab内容,就需要修改lib->util中的openNewPage方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
  openNewPage(vm: Vue, name: string | undefined, argu?: any, query?: any) {
    // debugger;
    let pageOpenedList = vm.$store.state.app.pageOpenedList;
    let openedPageLen = pageOpenedList.length;
    let i = 0;
    let tagHasOpened = false;
    while (i < openedPageLen) {
      if (name === pageOpenedList[i].name) {
        // 页面已经打开
        vm.$store.commit("app/pageOpenedList", {
          index: i,
          argu: argu,
          query: query
        });
        tagHasOpened = true;
        break;
      }
      i++;
    }
    if (!tagHasOpened) {
      let tag = vm.$store.state.app.tagsList.filter((item: any) => {
        if (!!item.children) {
          for (let index = 0; index < item.children.length; index++) {
            const element = item.children[index];
            if (name === element.name) {
              return true;
            }
          }
          return false;
        } else {
          return name === item.name;
        }
      });
      tag = tag[0];
      if (tag) {debugger
          if (tag.children) {
            for (let index = 0; index < tag.children.length; index++) {
                const element = tag.children[index];
                if (name === element.name) {
                    tag=element;
                    break;
                }
            }
          } else {
               tag = tag;
          }
        if (argu) {
          tag.argu = argu;
        }
        if (query) {
          tag.query = query;
        }
        vm.$store.commit("app/increateTag", tag);
      }
    }
    vm.$store.commit("app/setCurrentPageName", name);
  }

现在可以修改你的菜单定义,在二级菜单下像第一级菜单下增加二级菜单一样增加三级菜单了,二级菜单的权限可以是undefined,二级菜单的component要定义成刚才增加的路由组件,这样不需要在后端定义具体的权限,自动根据三级菜单的权限决定是否显示二级菜单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  {
    path: "/menu1",
    name: "menu1",
    permission: "",
    meta: { title: "menu1" },
    icon: "&#xe68a;",
    component: main,
    children: [
      {
        path: "menu2",
        permission: undefined,
        meta: { title: "menu2" },
        name: "menu2",
        component:() => import("../components/three-leve-container.vue"),
        children: [
        {
            path: "menu3",
            permission: "menu3",
            meta: { title: "menu3" },
            name: "menu3",
            component: () =>
            import("../views/xxxx.vue")
        }
        ]
      }]
  }

修改收缩菜单样式

以上修改后菜单已经可以正常使用,只是在菜单收缩为图标菜单时,仍然无法显示三级菜单,还要修改components->shrinkable-menu->components->sidebarMenuShrink.vue文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<template>
    <div>
        <template v-for="(item, index) in menuList">
            <div style="text-align: center;" :key="index">
                <Dropdown transfer v-if="item.children.length !== 1" placement="right-start" :key="index" @on-click="changeMenu">
                    <Button style="width: 80px;margin-right:5px;padding:10px 0;" type="text">
                        <i class="iconfont" v-html="item.icon" style="color:white"></i>
                    </Button>
                    <DropdownMenu slot="list">
                        <template v-for="(child, i) in item.children">                            
                            <DropdownItem transfer v-if="!hasChildren(child)" :name="child.name" :key="i"><Icon :type="child.icon"></Icon><span style="padding-left:10px;"></span></DropdownItem>
                            <Dropdown transfer v-else placement="right-start" :key="i" @on-click="changeMenu">
                                <DropdownItem ><span style="padding-left:10px;"></span><Icon type="ios-arrow-forward"></Icon></DropdownItem>
                                <DropdownMenu slot="list">
                                    <template v-for="(ss, ii) in child.children">                            
                                        <DropdownItem :name="ss.name" :key="ii"><Icon :type="ss.icon"></Icon><span style="padding-left:10px;"></span></DropdownItem>
                                    </template>
                                </DropdownMenu>
                            </Dropdown><p v-if="hasChildren(child)"></p>
                        </template>
                    </DropdownMenu>
                </Dropdown>
                <Dropdown transfer v-else placement="right-start" :key="index" @on-click="changeMenu" style="left:100px">
                    <Button @click="changeMenu(item.children[0].name)" style="width: 80px;margin-right:5px;padding:10px 0;" type="text">
                        <Icon :size="20" :color="iconColor" :type="item.icon"></Icon>
                    </Button>
                    <DropdownMenu style="width: 200px;" slot="list">
                        <DropdownItem :name="item.children[0].name" :key="'d' + index"><Icon :type="item.icon"></Icon><span style="padding-left:10px;"></span></DropdownItem>
                    </DropdownMenu>
                </Dropdown>
            </div>
        </template>
    </div>
</template>

<script lang="ts">
import { Component, Vue,Inject,Prop,Emit } from 'vue-property-decorator';
import AbpBase from '../../../lib/abpbase'
@Component
export default class extends AbpBase {
    name:string='sidebarMenuShrink';
    @Prop({type:Array}) menuList:Array<any>;
    @Prop({default:'white'}) iconColor:string;
    @Prop({default:'darck'}) menuTheme:string;
    @Emit('on-change') changeMenu(active:string){

    }
    itemTitle(item:any){
        return this.L(item.meta.title);
    } 
    hasChildren(item:any){
        return !!item.children&&item.children.length>0
    }
    getChildren(item:any){
        return item.children;
    }
}
</script>

参考资料

本文会经常更新,请阅读原文: https://dashenxian.github.io/post/Vue+abp%E5%A2%9E%E5%8A%A0%E4%B8%89%E7%BA%A7%E8%8F%9C%E5%8D%95 ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 小神仙 (包含链接: https://dashenxian.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (125880321@qq.com)

登录 GitHub 账号进行评论