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 相关文章推荐
表单项的name命名为submit、reset引起的问题
Dec 22 Javascript
javascript对数组的常用操作代码 数组方法总汇
Jan 27 Javascript
js简易namespace管理器 实例代码
Jun 21 Javascript
解决Jquery鼠标经过不停滑动的问题
Mar 03 Javascript
解决html按钮切换绑定不同函数后点击时执行多次函数问题
May 14 Javascript
js实现仿百度汽车频道选择汽车图片展示实例
May 06 Javascript
bootstrap实现动态进度条效果
Mar 08 Javascript
Vue中的数据监听和数据交互案例解析
Jul 12 Javascript
解决vue中修改export default中脚本报一大堆错的问题
Aug 27 Javascript
Vue filter格式化时间戳时间成标准日期格式的方法
Sep 16 Javascript
JS数组及对象遍历方法代码汇总
Jun 16 Javascript
three.js 制作动态二维码的示例代码
Jul 31 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
destoon调用企业会员公司形象图片的实现方法
2014/08/21 PHP
PHP多线程之内部多线程实例分析
2015/03/09 PHP
php命令行(cli)下执行PHP脚本文件的相对路径的问题解决方法
2015/05/25 PHP
thinkphp3.x中变量的获取和过滤方法详解
2016/05/20 PHP
详解json在php中的应用
2018/09/30 PHP
网页中实现浏览器的最大,最小化和关闭按钮
2007/03/12 Javascript
把textarea中字符串里含有的回车换行替换成&amp;lt;br&amp;gt;的javascript代码
2007/04/20 Javascript
JavaScript利用构造函数和原型的方式模拟C#类的功能
2014/03/06 Javascript
微信小程序 loading 详解及实例代码
2016/11/09 Javascript
BootStrap表单验证实例代码
2017/01/13 Javascript
使用jquery+iframe做一个ajax上传效果(实例)
2017/08/24 jQuery
seajs模块压缩问题与解决方法实例分析
2017/10/10 Javascript
JavaScript中this用法学习笔记
2019/03/17 Javascript
JS字符串常用操作方法实例小结
2019/06/24 Javascript
微信小程序 scroll-view 水平滚动实现过程解析
2019/10/12 Javascript
Vue中axios拦截器如何单独配置token
2019/12/27 Javascript
python实现异步回调机制代码分享
2014/01/10 Python
tensorflow 获取模型所有参数总和数量的方法
2018/06/14 Python
python opencv人脸检测提取及保存方法
2018/08/03 Python
Python SQL查询并生成json文件操作示例
2018/08/17 Python
对Python中画图时候的线类型详解
2019/07/07 Python
Django Form 实时从数据库中获取数据的操作方法
2019/07/25 Python
Python zip函数打包元素实例解析
2019/12/11 Python
Python绘制动态水球图过程详解
2020/06/03 Python
python学习之使用Matplotlib画实时的动态折线图的示例代码
2021/02/25 Python
德国宠物用品、宠物食品及水族馆网上商店:ZooRoyal
2017/07/09 全球购物
会计实习生工作总结的自我评价
2013/10/07 职场文书
给同事的道歉信
2014/01/11 职场文书
人事专员的职责
2014/02/26 职场文书
《颐和园》教学反思
2014/02/26 职场文书
农业局党的群众路线教育实践活动整改方案
2014/09/20 职场文书
委托代理人授权委托书范本
2014/09/24 职场文书
护士2014年终工作总结
2014/11/11 职场文书
《狮子和鹿》教学反思
2016/02/16 职场文书
2019运动会广播加油稿汇总
2019/08/21 职场文书
Go本地测试解耦任务拆解及沟通详解Go本地测试的思路沟通的重要性总结
2022/06/21 Golang