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 相关文章推荐
cookie丢失问题(认证失效) Authentication (用户验证信息)也会丢失
Jun 04 Javascript
JS判断文本框内容改变事件的简单实例
Mar 07 Javascript
分享一个常用的javascript静态类
Dec 31 Javascript
JS简单计算器实例
Jan 20 Javascript
jQuery实现的类似淘宝网站搜索框样式代码分享
Aug 24 Javascript
jQuery stop()用法实例详解
Jul 28 Javascript
使用Angular 6创建各种动画效果的方法
Oct 10 Javascript
微信小程序使用setData修改数组中单个对象的方法分析
Dec 30 Javascript
Vue源码学习之关于对Array的数据侦听实现
Apr 23 Javascript
JavaScript怎样在删除前添加确认弹出框?
May 27 Javascript
Vue vm.$attrs使用场景详解
Mar 08 Javascript
vue+django实现下载文件的示例
Mar 24 Vue.js
如何用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 随机数的产生、页面跳转、件读写、文件重命名、switch语句
2009/08/07 PHP
PHP flock 文件锁详细介绍
2012/12/29 PHP
CI框架自动加载session出现报错的解决办法
2014/06/17 PHP
php一行代码获取文件后缀名实例分析
2014/11/12 PHP
php解决和避免form表单重复提交的几种方法
2016/08/31 PHP
yii2实现Ueditor百度编辑器的示例代码
2018/11/02 PHP
cookie.js 加载顺序问题怎么才有效
2013/07/31 Javascript
在页面上用action传递参数到后台出现乱码的解决方法
2013/12/31 Javascript
详解js中class的多种函数封装方法
2016/01/03 Javascript
Bootstrap标签页(Tab)插件使用方法
2017/03/21 Javascript
详解Node项目部署到云服务器上
2017/07/12 Javascript
详解angular脏检查原理及伪代码实现
2018/06/08 Javascript
Vue2 监听属性改变watch的实例代码
2018/08/27 Javascript
vue+axios+element ui 实现全局loading加载示例
2018/09/11 Javascript
将RGB值转换为灰度值的简单算法
2019/10/09 Javascript
H5+css3+js搭建带验证码的登录页面
2020/10/11 Javascript
Vue如何实现验证码输入交互
2020/12/07 Vue.js
[04:59]2018DOTA2亚洲邀请赛 4.7 Mineski夺冠时刻
2018/04/09 DOTA
[28:05]完美世界DOTA2联赛循环赛Inki vs DeMonsTer 第一场 10月30日
2020/10/31 DOTA
Python编写检测数据库SA用户的方法
2014/07/11 Python
Python将8位的图片转为24位的图片实现方法
2018/10/24 Python
详解重置Django migration的常见方式
2019/02/15 Python
Python实现的序列化和反序列化二叉树算法示例
2019/03/02 Python
python递归法实现简易连连看小游戏
2020/03/25 Python
Python Opencv图像处理基本操作代码详解
2020/08/31 Python
容易被忽略的Python内置类型
2020/09/03 Python
pycharm-professional-2020.1下载与激活的教程
2020/09/21 Python
CSS3中的display:grid,网格布局介绍
2019/10/30 HTML / CSS
物业管理毕业生的自我评价
2014/02/17 职场文书
酒店管理专业自荐信
2014/05/23 职场文书
2014高考励志标语
2014/06/05 职场文书
护林防火标语
2014/06/27 职场文书
警告通知
2015/04/25 职场文书
护士心得体会范文
2016/01/25 职场文书
小学语文的各类谚语(70首)
2019/08/15 职场文书
MySQL中的 inner join 和 left join的区别解析(小结果集驱动大结果集)
2023/05/08 MySQL