• rbac组件之动态二级菜单栏实现


    对于功能比较少的应用程序 “一级菜单” 基本可以满足需求,但是功能多的程序就需要 “二级菜单” 了,并且访问时候需要默认选中指定菜单。

    示例效果:

    基于一级菜单的表结构上进行表结构的更改,Permission权限表中把is_menu和icon两个字段删除,新增一张Menu表,这张表记录一级菜单,Menu表字段为title和icon两个字段,因为其不需要URL,只是展示二级菜单而已,二级菜单需要URL;而这两张表需要建立一对多的关系,一级菜单会有多个二级菜单,所以外键建在Permission表中。

    models.py

    from django.db import models
    
    # Create your models here.
    
    
    class Menu(models.Model):
        """一级菜单表"""
        title = models.CharField(
            verbose_name="一级菜单名称",
            max_length=32,
        )
        icon = models.CharField(
            verbose_name="图标",
            max_length=64,
            null=True,  # 表示数据库中可以为空
            blank=True,  # admin后台管理中可以输入为空
        )
    
        def __str__(self):
            return self.title
    
    
    class Permission(models.Model):
        """权限表"""
        title = models.CharField(
            verbose_name="权限名称",
            max_length=32,
        )
        url = models.CharField(
            verbose_name="含正则URL",
            max_length=128,
        )
        menu = models.ForeignKey(
            to="Menu",
            on_delete=models.CASCADE,
            verbose_name="所属二级菜单",
            null=True,  # 并不是所有的url都可以做二级菜单,所以这里需要设置null=True
            blank=True,
            help_text="null表示不是二级菜单,非null表示是二级菜单",
        )
    
        def __str__(self):
            return self.title
    
    
    class Role(models.Model):
        """角色表"""
        title = models.CharField(
            verbose_name="角色名称",
            max_length=32,
        )
        permissions = models.ManyToManyField(
            verbose_name="角色所拥有的权限",
            to="Permission",
            blank=True,
        )
    
        def __str__(self):
            return self.title
    
    
    class Userinfo(models.Model):
        """用户表"""
        name = models.CharField(
            verbose_name="用户名",
            max_length=32,
        )
        password = models.CharField(
            verbose_name="密码",
            max_length=64,
        )
        email = models.CharField(
            verbose_name="邮箱",
            max_length=32,
        )
        roles = models.ManyToManyField(
            verbose_name="用户所拥有的角色",
            to="Role",
            blank=True,
        )
    
        def __str__(self):
            return self.name

    因为表结构发生了变化,所以在权限初始化的步骤上也需要进行更改,即init_permission函数

    from django.conf import settings
    
    
    def init_permission(current_user, request):
        permission_queryset = current_user.roles.filter(
            permissions__isnull=False,
        ).values(
            "permissions__id",  # 二级菜单id
            "permissions__url",  # 二级菜单url
            "permissions__title",  # 二级菜单名称
            "permissions__menu__id",  # 一级菜单ID
            "permissions__menu__title", # 一级菜单名称
            "permissions__menu__icon",  # 一级菜单图标
        ).distinct()
        permission_list = []  # 存放用户的权限数据信息
        menu_dict = {}  # 存放菜单信息
        for item in permission_queryset:
            permission_list.append(item["permissions__url"])  # 将所有url存放到permission_list中,方便中间件的权限判断
            menu_id = item["permissions__menu__id"]  # 取到二级菜单所对应的一级菜单的id,如果不能做二级菜单的,那么这个字段值是null
            if not menu_id:
                continue
            node = {
                "title": item["permissions__title"],
                "url": item["permissions__url"],
            }  # 二级菜单数据信息,方便后续添加
            if menu_id in menu_dict:
                menu_dict[menu_id]["children"].append(node)  # 如果一级菜单已经存在,那么直接添加二级菜单数据信息即可
            else:
                # 如果一级菜单不存在,那么还需要添加一级菜单的数据信息和二级菜单的数据信息
                menu_dict[menu_id] = {
                    "title": item["permissions__menu__title"],
                    "icon": item["permissions__menu__icon"],
                    "children": [node,],
                }
        request.session[settings.PERMISSION_SESSION_KEY] = permission_list
        request.session[settings.MENU_SESSION_KEY] = menu_dict
    
    
    """
    menu_dict = {
        1: {
            'title': '信息管理',  # 一级菜单名称
            'icon': 'fa-camera-retro',  # 一级菜单图标
            'children': [
                {
                    'title': '客户列表',
                    'url': '/customer/list/'
                }
            ]  # 一级菜单下的所有二级菜单都在这个列表,每个二级菜单都是一个字典结构,存储了二级菜单的名称和url
        },
        2: {
            'title': '用户管理',
            'icon': 'fa-fire',
            'children': [
                {
                    'title': '账单列表',
                    'url': '/payment/list/'
                }
            ]
        }
    }
    """

    菜单栏数据信息结构发生了变化, 所以在前端渲染时也需要对其进行更改,之前一级菜单栏渲染时,我们做了一个inclusion_tag,所以直接对inclusion_tag更改即可

    from django.template import Library
    from django.conf import settings
    import re
    from collections import OrderedDict
    
    
    register = Library()
    
    
    @register.inclusion_tag("rbac/menu.html")
    def menu(request):
        menu_dict = request.session.get(settings.MENU_SESSION_KEY)  # 从session中取出信息
        key_list = sorted(menu_dict)  # 对字典的key进行排序
        ordered_dict = OrderedDict()  # 建立一个有序空字典(按存入顺序排序,先存入的在前面)
    
        for key in key_list:
            val = menu_dict[key]  # 取到一级菜单的所有数据信息
            val["class"] = "hide"  # 添加一个class键,值为hide;这个class属性是二级菜单引用的,并不是一级菜单使用;在前端的效果为所有二级菜单class都有hide属性值,即隐藏所有二级菜单
    
            for per in val["children"]:  # 循环当前一级菜单下的每个二级菜单
                regex = "^%s$" % per["url"]
                if re.match(regex, request.path_info):  # 如果当前访问的url与二级菜单匹配成功
                    per["class"] = "active"  # 为匹配成功的二级菜单的a标签添加一个class键,值为active;在前端的效果为此二级菜单为激活的状态,即被选中的效果
                    val["class"] = ""  # 把匹配成功的二级菜单的直属一级菜单,class键的值改为空,即这个二级菜单的class没有了hide属性值,也就会展开显示所有二级菜单
            ordered_dict[key] = val  # 将更改一级菜单所有数据信息,根据key和val存放到有序空字典中
        return {"menu_dict": ordered_dict}
    
    
    """
    menu_dict = {
        1: {
            'title': '信息管理',  # 一级菜单名称
            'icon': 'fa-camera-retro',  # 一级菜单图标
            'children': [
                {
                    'title': '客户列表',
                    'url': '/customer/list/'
                }
            ]  # 一级菜单下的所有二级菜单都在这个列表,每个二级菜单都是一个字典结构,存储了二级菜单的名称和url
        },
        2: {
            'title': '用户管理',
            'icon': 'fa-fire',
            'children': [
                {
                    'title': '账单列表',
                    'url': '/payment/list/'
                }
            ]
        }
    }
    """

    inclusion_tag返回的html页面也需要进行更改(menu.html)

    <div class="multi-menu">
        {% for item in menu_dict.values %}
            <div class="item">
                <div class="title">  {# 这个div是一级菜单 #}
                    <span class="icon-wrap">
                        <i class="fa {{ item.icon }}"></i>
                    </span>
                    {{ item.title }}
                </div>
                <div class="body {{ item.class }}">  {# 这个div是二级菜单,二级菜单引用了后端为一级菜单设置的class属性值,即hide属性 #}
                    {% for children in item.children %}
                        <a href="{{ children.url }}" class="{{ children.class }}">
                            {{ children.title }}
                        </a>
                    {% endfor %}
                </div>
            </div>
        {% endfor %}
    </div>

    此时,rbac组件已经更改完毕,已经支持二级菜单的显示了;因为前端页面的菜单栏展示发生了变化,所以菜单栏的css样式也还需要更改(多了个二级菜单);根据后端书写的逻辑,现在二级菜单的点击效果是:当点击一个二级菜单后,除了当前的一级菜单是展开显示外,其余的一级菜单都是隐藏的;如果想更改其效果,后端的这一段逻辑可以删除,自行编写js即可;为了解耦,把二级菜单的css样式和js代码都放入到rbac组件static文件夹下。

     二级菜单栏的css,部分与一级菜单栏的css重叠了,所以在一级菜单栏的layout.html的样式设计中需要删除一些样式设计,从.luffy-container以下的所有样式删除,且样式和js解耦后,再需要使用那么需要引入两个文件。

    <link rel="stylesheet" href="{% static 'rbac/css/rbac.css' %}">
    
    <script src="{% static 'rbac/js/rbac.js' %}"></script>

     

     

    前端的inclusion_tag调用和一级菜单一样,不需要做任何更改

    rbac组件文件结构

  • 相关阅读:
    OAF 开发,给组件添加javascript事件
    SQL SERVER占用CPU过高优化
    Winform嵌入CEF(非正常用法)
    多线程——i++的坑
    20150819(i++与++i的思考)
    listView中,checkBox的显示和隐藏
    装箱和拆箱
    虚方法
    [转]关于struct的一些解释与class对比
    提取行政区边界经纬度坐标(高德+百度)
  • 原文地址:https://www.cnblogs.com/xuewei95/p/15849327.html
Copyright © 2020-2023  润新知