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 相关文章推荐
jQuery实现原理的模拟代码 -6 代码下载
Aug 16 Javascript
javascript实现div的拖动并调整大小类似qq空间个性编辑模块
Dec 12 Javascript
javascript操作符&quot;!~&quot;详解
Feb 10 Javascript
以jQuery中$.Deferred对象为例讲解promise对象是如何处理异步问题
Nov 13 Javascript
基于jQuery实现放大镜特效
Oct 19 Javascript
jquery表单验证插件formValidator使用方法
Apr 01 Javascript
原生js获取元素样式的简单方法
Aug 06 Javascript
jQuery获取单选按钮radio选中值与去除所有radio选中状态的方法
May 20 jQuery
JS 设置Cookie 有效期 检测cookie
Jun 15 Javascript
详解vue项目首页加载速度优化
Oct 18 Javascript
js中DOM事件绑定分析
Mar 18 Javascript
vue-router 中 meta的用法详解
Nov 01 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
PHP分页详细讲解(有实例)
2013/10/30 PHP
jQuery获取json后使用zy_tmpl生成下拉菜单
2015/03/27 PHP
脚本吧 - 幻宇工作室用到js,超强推荐base.js
2006/12/23 Javascript
Display SQL Server Version Information
2007/06/21 Javascript
JavaScript delete 属性的使用
2009/10/08 Javascript
jQuery第三课 修改元素属性及内容的代码
2010/03/14 Javascript
JS注册/移除事件处理程序(ExtJS应用程序设计实战)
2013/05/07 Javascript
JavaScript等比例缩放图片控制超出范围的图片
2013/08/06 Javascript
基于jquery实现的文字向上跑动类似跑马灯的效果
2014/06/17 Javascript
Javascript字符串对象的常用方法简明版
2014/06/26 Javascript
jquery实现鼠标拖拽滑动效果来选择数字的方法
2015/05/04 Javascript
简单实现js选项卡切换效果
2016/02/03 Javascript
Angular和Vue双向数据绑定的实现原理(重点是vue的双向绑定)
2016/11/22 Javascript
JS前向后瞻正则表达式定义与用法示例
2016/12/27 Javascript
react-native fetch的具体使用方法
2017/11/01 Javascript
jQuery实现的鼠标响应缓冲动画效果示例
2018/02/13 jQuery
vue 多入口文件搭建 vue多页面搭建的实例讲解
2018/03/12 Javascript
详解angular如何调用HTML字符串的方法
2018/06/30 Javascript
vue-drawer-layout实现手势滑出菜单栏
2020/11/19 Vue.js
[40:10]2015国际邀请赛全明星表演赛
2015/08/07 DOTA
Python中用Decorator来简化元编程的教程
2015/04/13 Python
Python使用ftplib实现简易FTP客户端的方法
2015/06/03 Python
python中私有函数调用方法解密
2016/04/29 Python
pycharm使用matplotlib.pyplot不显示图形的解决方法
2018/10/28 Python
python判断计算机是否有网络连接的实例
2018/12/15 Python
python生成器与迭代器详解
2019/01/01 Python
Linux下通过python获取本机ip方法示例
2019/09/06 Python
基于Django快速集成Echarts代码示例
2020/12/01 Python
荷兰本土平价百货:HEMA
2017/10/23 全球购物
商务主管岗位职责
2013/12/08 职场文书
助理政工师申报材料
2014/06/03 职场文书
2014离婚协议书范文
2014/09/10 职场文书
宾馆安全管理制度
2015/08/06 职场文书
汽车销售合同文本
2019/08/08 职场文书
ORACLE数据库对long类型字段进行模糊匹配的解决思路
2021/04/07 Oracle
HTML怎么设置下划线?html文字加下划线方法
2021/12/06 HTML / CSS