LayUI+Shiro实现动态菜单并记住菜单收展的示例


Posted in Javascript onMay 06, 2021

LayUI + Shiro + Thyemleaf 实现动态菜单并记住菜单收展

LayUI+Shiro实现动态菜单并记住菜单收展的示例

LayUI+Shiro实现动态菜单并记住菜单收展的示例

一、Maven 依赖

<dependencies>

        <!--阿里 FastJson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.39</version>
        </dependency>
        <!--权限控制 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.4.0-RC2</version>
        </dependency>

        <!-- 兼容于thymeleaf的shiro -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

二、菜单相关的类

1、主菜单

LayUI+Shiro实现动态菜单并记住菜单收展的示例

/**
 * @author wxhntmy
 */
@Getter
@Setter
public class Menu {
    private String name;
    private String icon;
    private String url;
    private Boolean hidden;
    private List<MenuList> list;
}

2、子菜单

LayUI+Shiro实现动态菜单并记住菜单收展的示例

/**
 * @author wxhntmy
 */
@Getter
@Setter
public class MenuList {
    private String name;
    private String url;

    public MenuList(String name, String url) {
        this.name = name;
        this.url = url;
    }
}

三、Shiro 配置

1、ShiroConfig

/**
 * @author wxhntmy
 */
@Configuration
public class ShiroConfig {
    /**
     * 配置拦截器
     * <p>
     * 定义拦截URL权限,优先级从上到下 1). anon : 匿名访问,无需登录 2). authc : 登录后才能访问 3). logout: 登出 4).
     * roles : 角色过滤器
     * <p>
     * URL 匹配风格 1). ?:匹配一个字符,如 /admin? 将匹配 /admin1,但不匹配 /admin 或 /admin/; 2).
     * *:匹配零个或多个字符串,如 /admin* 将匹配 /admin 或/admin123,但不匹配 /admin/1; 2).
     * **:匹配路径中的零个或多个路径,如 /admin/** 将匹配 /admin/a 或 /admin/a/b
     * <p>
     * 配置身份验证成功,失败的跳转路径
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

        // 静态资源匿名访问
        filterChainDefinitionMap.put("/layui/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/admin/**", "anon");

        filterChainDefinitionMap.put("/**/*.eot", "anon");
        filterChainDefinitionMap.put("/**/*.svg", "anon");
        filterChainDefinitionMap.put("/**/*.svgz", "anon");
        filterChainDefinitionMap.put("/**/*.ttf", "anon");
        filterChainDefinitionMap.put("/**/*.woff", "anon");
        filterChainDefinitionMap.put("/**/*.woff2", "anon");
        filterChainDefinitionMap.put("/**/*.gif", "anon");

        filterChainDefinitionMap.put("/favicon.ico", "anon");

        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/menu", "anon");
        filterChainDefinitionMap.put("/user/login", "anon");

        // 用户退出
        filterChainDefinitionMap.put("/logout", "logout");


        // 其他路径均需要身份认证,一般位于最下面,优先级最低
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        //登录路径
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 主页
        //shiroFilterFactoryBean.setSuccessUrl("/index");
        //验证失败跳转的路径
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        return shiroFilterFactoryBean;
    }

    /**
     * SecurityManager安全管理器;shiro的核心
     * 
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(myRealm());
        return defaultWebSecurityManager;
    }

    /**
     * 自定义Realm
     * 
     * @return
     */
    @Bean
    public MyRealm myRealm() {
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(myCredentialsMatcher());
        return myRealm;
    }

    /**
     * 配置加密方式
     * @return
     */
    @Bean
    public MyCredentialsMatcher myCredentialsMatcher() {
        return new MyCredentialsMatcher();
    }

    /**
     * 配置Shiro生命周期处理器
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 自动创建代理类,若不添加,Shiro的注解可能不会生效。
     */
    @Bean
    @DependsOn({ "lifecycleBeanPostProcessor" })
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启Shiro的注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

2、自定义shiro密码校验

/**
 * 自定义shiro密码校验
 * @author wxhntmy
 */
public class MyCredentialsMatcher implements CredentialsMatcher {

    @Resource
    private UserMapper userMapper;

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

        UsernamePasswordToken utoken = (UsernamePasswordToken) token;

        String password = new String(utoken.getPassword());
        String username = utoken.getUsername();

        User user = userMapper.getUserById(username);

        return user.getPwd().equals(password);
    }
}

3、MyRealm

/**
 * @author wxhntmy
 */
public class MyRealm extends AuthorizingRealm {

    @Resource
    private RoleMapper roleMapper;
    @Resource
    private UserRoleListMapper userRoleListMapper;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

        User user = (User) principalCollection.getPrimaryPrincipal();
        if (user == null) {
            return null;
        }


        List<UserRoleList> roleLists = userRoleListMapper.getUserRoleByUserId(user.getId());

        List<Role> roles = roleMapper.getAllRoles();

        if (roleLists != null && !roleLists.isEmpty()) {
            for (UserRoleList roleList : roleLists) {
                for (Role role : roles) {
                    if (Objects.equals(roleList.getRole_id(), role.getId())) {
                        authorizationInfo.addRole(role.getRole());
                    }
                }
            }
        }
        return authorizationInfo;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取登录用户账号
        UsernamePasswordToken utoken = (UsernamePasswordToken) authenticationToken;

        //获得用户输入的密码
        String password = new String(utoken.getPassword());
        String username = utoken.getUsername();

        User user = new User();

        user.setId(username);
        user.setPwd(password);

        //当前realm对象的唯一名字,调用父类的getName()方法
        String realmName = getName();

        // 获取盐值,即用户名
        ByteSource salt = ByteSource.Util.bytes(password);

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, salt, realmName);

        return info;

    }

}

四、控制类

1、LoginController

@RestController
public class LoginController {

    @Resource
    private RoleMapper roleMapper;
    @Resource
    private UserRoleListMapper userRoleListMapper;
    @Resource
    private UserMapper userMapper;

    @RequestMapping(value = "/user/login", method = RequestMethod.GET)
    public Msg<String> getUserByName(@RequestParam String user,
                                     @RequestParam String pwd,
                                     @RequestParam String usertype,
                                     @RequestParam String box) {


        Role role = roleMapper.getRoleByRoleName(usertype);
        User uUser = userMapper.getUserById(user);
        if (uUser == null){
            return Msg.fail("UserUnexit");
        }
        //登录验证
        UsernamePasswordToken token = new UsernamePasswordToken(user, pwd);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            return Msg.fail("PasswordError");
        }
        //设置登陆过期时间,单位毫秒,这里设置30分钟
        SecurityUtils.getSubject().getSession().setTimeout(1800000);
        return Msg.ok("Success");
    }
}

2、PageController

@Controller
public class PageController {

    @Resource
    private UserMapper userMapper;

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String Login(){
        return "login";
    }

    @RequestMapping(value = "/user/index", method = RequestMethod.GET)
    public String Index(Model model){

        User user = (User) SecurityUtils.getSubject().getPrincipal();

        User uuser = userMapper.getUserById(user.getId());

        if (StringUtils.isEmpty(user)) {
            return "redirect:/login";
        }

        model.addAttribute("user", uuser);

        return "index";
    }
}

3、MenuController

/**
 * @author wxhntmy
 */
@RestController
public class MenuController {

    @Resource
    private RoleMapper roleMapper;
    @Resource
    private UserRoleListMapper userRoleListMapper;

    //记住用户菜单收展
    private Map<String, Map> menu_map = new HashMap<>();

    @RequestMapping(value = "/menu", method = RequestMethod.GET)
    public Msg<List<Menu>> getMenu() {
        User user = (User) SecurityUtils.getSubject().getPrincipal();

        List<Menu> list = new ArrayList<>();

        if (StringUtils.isEmpty(user)) {
            return Msg.fail(list, "登录信息已过期!请重新登录!");
        }

        //记住收展
        Map<String, Boolean> map_store = new HashMap<>();

        if (menu_map.containsKey(user.getId())){
            map_store = menu_map.get(user.getId());
        }

        Map<String, String> map = new HashMap();
        List<UserRoleList> roleLists = userRoleListMapper.getUserRoleByUserId(user.getId());
        for (UserRoleList roleList : roleLists) {
            Role role = roleMapper.getRoleByRoleId(roleList.getRole_id());
            map.put(role.getRole(), roleList.getUser_id());
        }
        Menu menu1 = new Menu();
        menu1.setName("首页");
        menu1.setIcon("&#xe68e;");
        menu1.setUrl("/user/index");
        menu1.setHidden(false);

        List<MenuList> menuLists2 = new ArrayList<>();
        menu1.setList(menuLists2);
        list.add(menu1);


        if (map.containsKey("student")){
            Menu menu2 = new Menu();
            Menu menu3 = new Menu();

            menu2.setName("课程管理");
            menu2.setIcon("&#xe609;");
            menu2.setUrl("");
            menu2.setHidden(map_store.getOrDefault("课程管理", false));
            List<MenuList> menuLists = new ArrayList<>();
            MenuList menuList1 = new MenuList("已选课程", "");
            MenuList menuList2 = new MenuList("可选课程", "");
            MenuList menuList22 = new MenuList("课程论坛", "");
            menuLists.add(menuList1);
            menuLists.add(menuList2);
            menuLists.add(menuList22);
            menu2.setList(menuLists);

            menu3.setName("成果管理");
            menu3.setIcon("&#xe609;");
            menu3.setUrl("");
            menu3.setHidden(map_store.getOrDefault("成果管理", false));
            List<MenuList> menuLists3 = new ArrayList<>();
            MenuList menuList3 = new MenuList("提交课程成果", "");
            MenuList menuList33 = new MenuList("提交课程日志", "");
            MenuList menuList4 = new MenuList("实训课程成绩", "");
            MenuList menuList5 = new MenuList("课程调查问卷", "");
            menuLists3.add(menuList3);
            menuLists3.add(menuList33);
            menuLists3.add(menuList4);
            menuLists3.add(menuList5);
            menu3.setList(menuLists3);

            list.add(menu2);
            list.add(menu3);
        }

        if (map.containsKey("teacher")){
            Menu menu2 = new Menu();
            Menu menu3 = new Menu();

            menu2.setName("授课课程管理");
            menu2.setIcon("&#xe609;");
            menu2.setUrl("");
            menu2.setHidden(map_store.getOrDefault("授课课程管理", false));
            List<MenuList> menuLists = new ArrayList<>();
            MenuList menuList1 = new MenuList("教授的实训课程", "");
            MenuList menuList2 = new MenuList("课程论坛", "");
            menuLists.add(menuList1);
            menuLists.add(menuList2);
            menu2.setList(menuLists);

            menu3.setName("成果管理");
            menu3.setIcon("&#xe609;");
            menu3.setUrl("");
            menu3.setHidden(map_store.getOrDefault("成果管理", false));
            List<MenuList> menuLists3 = new ArrayList<>();
            MenuList menuList3  = new MenuList("课程成果检查", "");
            MenuList menuList33 = new MenuList("课程日志批复", "");
            MenuList menuList4  = new MenuList("实训课程成绩", "");
            menuLists3.add(menuList3);
            menuLists3.add(menuList33);
            menuLists3.add(menuList4);
            menu3.setList(menuLists3);

            list.add(menu2);
            list.add(menu3);
        }
        if (map.containsKey("professionor")){
            Menu menu2 = new Menu();
            Menu menu3 = new Menu();

            menu2.setName("实训课程管理");
            menu2.setIcon("&#xe609;");
            menu2.setUrl("");
            menu2.setHidden(map_store.getOrDefault("实训课程管理", false));
            List<MenuList> menuLists = new ArrayList<>();
            MenuList menuList1 = new MenuList("待批准实训课程", "");
            MenuList menuList2 = new MenuList("添加实训课程", "");
            MenuList menuList3 = new MenuList("实训课程管理", "");
            menuLists.add(menuList1);
            menuLists.add(menuList2);
            menuLists.add(menuList3);
            menu2.setList(menuLists);

            menu3.setName("发布调查问卷");
            menu3.setIcon("&#xe609;");
            menu3.setUrl("");
            menu3.setHidden(map_store.getOrDefault("发布调查问卷", false));
            List<MenuList> menuLists1 = new ArrayList<>();
            MenuList menuList11 = new MenuList("发布调查问卷", "");
            MenuList menuList21 = new MenuList("回收调查问卷", "");
            menuLists1.add(menuList11);
            menuLists1.add(menuList21);
            menu3.setList(menuLists1);

            list.add(menu2);
            list.add(menu3);
        }
        if (map.containsKey("admin")){
            Menu menu2 = new Menu();
            Menu menu3 = new Menu();

            menu2.setName("用户管理");
            menu2.setIcon("&#xe612;");
            menu2.setUrl("");
            menu2.setHidden(map_store.getOrDefault("用户管理", false));
            List<MenuList> menuLists = new ArrayList<>();
            MenuList menuList0 = new MenuList("添加用户", "");
            MenuList menuList1 = new MenuList("学生账号", "");
            MenuList menuList2 = new MenuList("教师账号", "");
            MenuList menuList3 = new MenuList("实训负责人账号", "");
            menuLists.add(menuList0);
            menuLists.add(menuList1);
            menuLists.add(menuList2);
            menuLists.add(menuList3);
            menu2.setList(menuLists);

            menu3.setName("数据库管理");
            menu3.setIcon("&#xe857;");
            menu3.setUrl("");
            menu3.setHidden(map_store.getOrDefault("数据库管理", false));
            List<MenuList> menuLists3 = new ArrayList<>();
            MenuList menuList4  = new MenuList("备份数据库", "");
            MenuList menuList5 = new MenuList("还原数据库", "");
            menuLists3.add(menuList4);
            menuLists3.add(menuList5);
            menu3.setList(menuLists3);

            list.add(menu2);
            list.add(menu3);
        }

        Menu menu4 = new Menu();
        menu4.setName("系统设置");
        menu4.setIcon("&#xe620;");
        menu4.setUrl("");
        menu4.setHidden(map_store.getOrDefault("系统设置", false));
        List<MenuList> menuLists4 = new ArrayList<>();
        MenuList menuList5 = new MenuList("修改个人信息", "");
        MenuList menuList6 = new MenuList("修改密码", "");
        MenuList menuList7 = new MenuList("清除缓存", "");
        menuLists4.add(menuList5);
        menuLists4.add(menuList6);
        menuLists4.add(menuList7);
        menu4.setList(menuLists4);

        Menu menu5 = new Menu();
        menu5.setName("退出登录");
        menu5.setIcon("&#xe65c;");
        menu5.setUrl("/logout");
        menu5.setHidden(false);
        List<MenuList> menuLists5 = new ArrayList<>();
        menu5.setList(menuLists5);

        list.add(menu4);
        list.add(menu5);

        if (map.containsKey("student")){
            return Msg.ok(list, "STU");
        }
        String message = null;
        if (map.containsKey("teacher")){
            message = "TEA";
        }
        if (map.containsKey("professionor")){
            message = "PRI";
        }
        if (map.containsKey("admin")){
            message = "ADM";

        }
        return Msg.ok(list, message);
    }

    @RequestMapping(value = "/menu_storage", method = RequestMethod.GET)
    public Msg<String> menu_storage(@RequestParam String data) {

        JSONArray jsonArray = JSONArray.parseArray(data);
        User user = (User) SecurityUtils.getSubject().getPrincipal();

        if (StringUtils.isEmpty(user)) {
            return Msg.fail("登录信息已过期!请重新登录!");
        }
        //记住收展
        Map<String, Boolean> map_store = new HashMap<>();

        for (Object o : jsonArray) {
            JSONObject jsonObject = JSONObject.parseObject(o.toString());
            map_store.put(jsonObject.getString("name"), Boolean.valueOf(jsonObject.getString("hidden")));
        }

        menu_map.put(user.getId(), map_store);

        return Msg.ok();
    }
}

五、数据库

1、user 表

LayUI+Shiro实现动态菜单并记住菜单收展的示例

2、role 表

LayUI+Shiro实现动态菜单并记住菜单收展的示例

3、user_role_list 表

LayUI+Shiro实现动态菜单并记住菜单收展的示例

六、前端页面

1、Ajax 请求菜单数据

let config = {};
        function set_menu() {
            //ajax提交信息
            $.ajax({
                type: "get",
                async: false,
                url: "/menu",// 请求发送到LoginServlet处
                dataType: 'json',
                success: function (msg) {
                    if (msg.ok === true && msg.data) {
                        config["name"] = msg.message;
                        config["menu"] = msg.data;
                    }
                    if (msg.ok === false) {
                        window.location.href = "/logout";
                    }
                    if (!msg.data) {
                        window.location.href = "/logout";
                    }
                },
                error: function (msg) {
                    // 请求失败时执行该函数
                    layer.alert('请求菜单数据失败!!!', function (index) {
                        //do something
                        layer.close(index);
                    });
                }
            });
        }

        set_menu();


        $(document).ready(function () {
            //删除
            $(".del").click(function () {
                var url = $(this).attr("href");
                var id = $(this).attr("data-id");

                layer.confirm('你确定要删除么?', {
                    btn: ['确定', '取消']
                }, function () {
                    $.get(url, function (data) {
                        if (data.code === 1) {
                            $(id).fadeOut();
                            layer.msg(data.msg, {icon: 1});
                        } else {
                            layer.msg(data.msg, {icon: 2});
                        }
                    });
                }, function () {
                    layer.msg("您取消了删除!");
                });
                return false;
            });
        })

        layui.use('form', function () {
            var form = layui.form,
                layer = layui.layer;
        });

        var vue = new Vue({
            el: '#app',
            data: {
                webname: config.name,
                menu: [],
                address: []
            },
            created: function () {
                this.menu = config.menu;
                this.thisActive();
                this.thisAttr();
            },
            methods: {
                //记住收展
                onActive: function (pid, id = false) {
                    let data;
                    if (id === false) {
                        data = this.menu[pid];
                        if (data.url.length > 0) {
                            this.menu.forEach((v, k) => {
                                v.active = false;
                                v.list.forEach((v2, k2) => {
                                    v2.active = false;
                                })
                            })
                            data.active = true;
                        }
                        data.hidden = !data.hidden;
                    } else {
                        this.menu.forEach((v, k) => {
                            v.active = false;
                            v.list.forEach((v2, k2) => {
                                v2.active = false;
                            })
                        })
                        data = this.menu[pid].list[id];
                    }

                    this.updateStorage();
                    if (data.url.length > 0) {
                        if (data.target) {
                            if (data.target === '_blank') {
                                window.open(data.url);
                            } else {
                                window.location.href = data.url;
                            }
                        } else {
                            window.location.href = data.url;
                        }
                    }
                },

                //更新菜单缓存
                updateStorage() {
                    //sessionStorage.menu = JSON.stringify(this.menu);
                    $.ajax({
                        type: "get",
                        async: false,
                        url: "/menu_storage",// 请求发送到LoginServlet处
                        data: {
                            "data": JSON.stringify(this.menu)
                        },
                        dataType: 'json',
                        success: function (msg) {

                        },
                        error: function (msg) {
                            // 请求失败时执行该函数
                            var index = layer.load();
                            layer.close(index);
                            layer.alert('请求菜单数据失败!!!', function (index) {
                                //do something
                                layer.close(index);
                            });
                        }
                    });
                },
                //菜单高亮
                thisActive: function () {
                    let pathname = window.location.pathname;
                    let host = window.location.host;
                    let pid = false;
                    let id = false;
                    this.menu.forEach((v, k) => {
                        let url = v.url;
                        if (url.length > 0) {
                            if (url[0] !== '/' && url.substr(0, 4) !== 'http') {
                                url = '/' + url;
                            }
                        }
                        if (pathname === url) {
                            pid = k;
                        }
                        v.list.forEach((v2, k2) => {
                            let url = v2.url;

                            if (url.length > 0) {
                                if (url[0] !== '/' && url.substr(0, 4) !== 'http') {
                                    url = '/' + url;
                                }
                            }
                            if (pathname === url) {
                                pid = k;
                                id = k2;
                            }
                        })
                    })


                    if (id !== false) {
                        this.menu[pid].list[id].active = true;
                    } else {
                        if (pid !== false) {
                            this.menu[pid].active = true;
                        }
                    }

                    this.updateStorage();

                },
                //当前位置
                thisAttr: function () {
                    //当前位置
                    let address = [{
                        name: '首页',
                        url: '/user/index'
                    }];
                    this.menu.forEach((v, k) => {
                        v.list.forEach((v2, k2) => {
                            if (v2.active) {
                                address.push({
                                    name: v.name,
                                    url: 'javascript:;'
                                })
                                address.push({
                                    name: v2.name,
                                    url: v2.url,
                                })
                                this.address = address;
                            }
                        })
                    })
                }
            }
        })

2、显示菜单栏

<ul class="cl">
        <!--顶级分类-->
        <li v-for="vo,index in menu" :class="{hidden:vo.hidden}">
            <a href="javascript:;" rel="external nofollow"  rel="external nofollow"  :class="{active:vo.active}" @click="onActive(index)">
                <i class="layui-icon" v-html="vo.icon"></i>
                <span v-text="vo.name"></span>
                <i class="layui-icon arrow" v-show="vo.url.length==0">&#xe61a;</i> <i v-show="vo.active"
                                                                                      class="layui-icon active">&#xe623;</i>
            </a>
            <!--子级分类-->
            <div v-for="vo2,index2 in vo.list">
                <a href="javascript:;" rel="external nofollow"  rel="external nofollow"  :class="{active:vo2.active}" @click="onActive(index,index2)"
                   v-text="vo2.name"></a>
                <i v-show="vo2.active" class="layui-icon active">&#xe623;</i>
            </div>
        </li>
    </ul>

七、完整代码

完整代码转 Gitee:wxhntmy / SpringBootLayuiMenu

到此这篇关于LayUI+Shiro实现动态菜单并记住菜单收展的示例的文章就介绍到这了,更多相关LayUI Shiro动态菜单内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
经典的带阴影的可拖动的浮动层
Jun 26 Javascript
javascript html 静态页面传参数
Apr 10 Javascript
JavaScript 基于原型的对象(创建、调用)
Oct 16 Javascript
深入理解JavaScript系列(1) 编写高质量JavaScript代码的基本要点
Jan 15 Javascript
jqTransform美化表单
Oct 10 Javascript
jquery实现模拟百分比进度条渐变效果代码
Oct 29 Javascript
理解JavaScript中worker事件api
Dec 25 Javascript
解读Bootstrap v4 sass设计
May 29 Javascript
JavaScript中利用构造器函数模拟类的方法
Feb 16 Javascript
webpack 单独打包指定JS文件的方法
Feb 22 Javascript
微信小程序分享功能onShareAppMessage(options)用法分析
Apr 24 Javascript
关于angular浏览器兼容性问题的解决方案
Jul 26 Javascript
如何用JavaScript实现一个数组惰性求值库
原生JS中应该禁止出现的写法
May 05 #Javascript
详解Javascript实践中的命令模式
如何制作自己的原生JavaScript路由
May 05 #Javascript
Vue项目中如何封装axios(统一管理http请求)
May 02 #Vue.js
如何用JavaScript学习算法复杂度
JS不要再到处使用绝对等于运算符了
Apr 30 #Javascript
You might like
iis下php mail函数的sendmail配置方法(官方推荐)
2012/04/25 PHP
基于php 随机数的深入理解
2013/06/05 PHP
php中fgetcsv()函数用法实例
2014/11/28 PHP
Laravel框架路由和控制器的绑定操作方法
2018/06/12 PHP
JavaScript 学习 - 提高篇
2007/02/02 Javascript
js中split函数的使用方法说明
2013/12/26 Javascript
javascript实现锁定网页、密码解锁效果(类似系统屏幕保护效果)
2014/08/15 Javascript
JavaScript通过Date-Mask将日期转换成字符串的方法
2015/06/04 Javascript
js随机生成26个大小写字母
2016/02/12 Javascript
老生常谈Javascript中的原型和this指针
2016/10/09 Javascript
Bootstrap CSS布局之列表
2016/12/15 Javascript
基于canvas的二维码邀请函生成插件
2017/02/14 Javascript
Linux CentOS系统下安装node.js与express的方法
2017/04/01 Javascript
Node.js利用debug模块打印出调试日志的方法
2017/04/25 Javascript
深入浅析javascript继承体系
2017/10/23 Javascript
vue-router 源码之实现一个简单的 vue-router
2018/07/02 Javascript
jQuery动态操作表单示例【基于table表格】
2018/12/06 jQuery
Node.js实现简单管理系统
2019/09/23 Javascript
Vue 一键清空表单的实现方法
2020/02/07 Javascript
JavaScript文档加载模式以及元素获取
2020/07/28 Javascript
JavaScript中while循环的基础使用教程
2020/08/11 Javascript
[03:00]DOTA2-DPC中国联赛1月18日Recap集锦
2021/03/11 DOTA
Python编程之列表操作实例详解【创建、使用、更新、删除】
2017/07/22 Python
使用Django搭建web服务器的例子(最最正确的方式)
2019/08/29 Python
python绘制封闭多边形教程
2020/02/18 Python
理解Django 中Call Stack机制的小Demo
2020/09/01 Python
俄罗斯马克西多姆家居用品网上商店:Максидом
2020/02/06 全球购物
垃圾回收的优点和原理
2014/05/16 面试题
火灾现场处置方案
2014/05/28 职场文书
幼儿园大班开学寄语
2014/08/02 职场文书
巾帼志愿者活动方案
2014/08/17 职场文书
银行竞聘上岗演讲稿
2014/09/12 职场文书
应届毕业生求职简历自我评价
2015/03/02 职场文书
团员自我评价范文
2015/03/10 职场文书
OpenCV-Python模板匹配人眼的实例
2021/06/08 Python
2022新作动画《福星小子》释出宣传影片 加入内田真礼&宫野真守配音演出
2022/04/08 日漫