二級動態菜單的功能


二級動態菜單的實現, 我們可能需要一個  下方展示的這樣的一種數據結構:

{
    1: {
        'title': '用戶管理', 
        'icon': 'fa fa-envira', 
        'children': [
            {'title': '客戶列表', 'url': '/customer/list/'}
        ]
    }, 
    2: {
        'title': '信息管理', 
        'icon': 'fa-black-tie', 
        'children': [
            {'title': '賬單列表', 'url': '/payment/list/'}
        ]
    }
}

 OK 如何實現:
1. 數據庫中 我們新添加一張表吧。 就叫 Menu 表。 一級菜單表:
  我們通過 一級菜單表, 來確定我們的 一級菜單應該有的樣子

class Menu(models.Model):
    mid = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='一級菜單標題', max_length=32)
    icon = models.CharField(verbose_name="圖標", max_length=32, null=True, blank=True)

    def __str__(self):
        return self.title
一級菜單, Menu表模型

然后他長這個樣子:

so 一級菜單中,有 title 字段,表示菜單的名稱。 icon字段,表示這個菜單的圖標。

然后就是  一級菜單下的子菜單,  我們就修改一下  Permission表, 給他添加一個 ment 的外鍵。用來表示這個 url 可以被作為子菜單使用。
這樣 就不需要,原來的表中 is_menu 表示是否可以成為菜單的選項了, 我們刪掉他:

class Permission(models.Model):
    """
    權限表  二級菜單使用表
    """
    title = models.CharField(verbose_name='標題', max_length=32)
    url = models.CharField(verbose_name='含正則的URL', max_length=128)
    menu = models.ForeignKey(verbose_name="所屬菜單", to="Menu", null=True, blank=True, on_delete=models.CASCADE)

    def __str__(self):
        return self.title
對Permission 權限表進行修改

這樣我們就只需要,為 可以成為子菜單的 url進行,關聯屬於哪一個。一級菜單。

大概的樣子就是這樣:

 如果喜歡也可以,給子菜單添加icon。 為他也添加一個圖標。

2. 數據庫方面, 這就妥了。然后是 用戶登錄的時候。 我們要怎樣 進行數據結構的編寫了。就是 init_permission 初始化權限的階段。 我們就要把 最初說的數據結構保存到session中。

def init_permission(current_user, request):
    '''  二級菜單,實現
    :param current_user: 當前請求 用戶對象
    :param request:  當前請求 數據
    :return:
    '''
    # 2. 權限 初始化
    # 根據當前用戶信息,獲取當前用戶所擁有的所有的權限(queryset對象 是不能直接放入,session中的)
    permission_queryset = current_user.roles.filter(permissions__isnull=False).values("permissions__url","permissions__title","permissions__menu_id""permissions__menu__title", "permissions__menu__icon", ).distinct()

    # 獲取權限 和 菜單信息。  權限放在權限列表,菜單放在菜單列表
    menu_dict = {}  # 菜單我們就需要一個字典了
    permission_list = []  # 所有的權限好說,還是放到列表中

    for item in permission_queryset:
        permission_list.append(item.get("permissions__url"))
        menu_id = item.get("permissions__menu_id")   #先取得當前這個url的父菜單的id
        if not menu_id:  # 如果為null的話,直接跳過。 說明這不是一個子菜單,也就不需要后續的操作了。
            continue

        node = {"title": item.get("permissions__title"), "url": item.get("permissions__url")}   # 獲取到當前這個url的信息
        if menu_id in menu_dict:
            menu_dict[menu_id]["children"].append(node)
        else:
            menu_dict[menu_id] = {
                "title": item.get("permissions__menu__title"),
                "icon": item.get("permissions__menu__icon"),
                "children": [node]
            }
# 下面的解釋,有一點復雜: 如果當前這個url 的menu_id 在 menu_dict字典中。那么就將當前這條url 添加到一級菜單的 "children" 列表中。也就是node
# 否則,就應該 以當前這條url 的menu_id為鍵。其中保存 一級菜單的title  和  icon字段信息。 並且"children"為鍵的列表中也要保存 node 這個參數代表的數據.
# 最終我們就能得到,最初我們想要的 數據結構
    request.session[settings.PERMISSIONS_SESSION_KEY] = permission_list
    request.session[settings.MENU_SESSION_KEY] = menu_dict
init_permission 中處理數據,並保存到session

3. ok 數據結構已經 搞定。 就剩下 改怎么渲染的問題了!  還是使用 inclusion_tag  進行渲染:

考慮到,字典是一個 無序的,存儲容器。 那么我們將他轉化成有序字典 就好了呀!

這里 需要使用到 re模塊。 和OrderedDict模塊

from django.template import Library
from django.conf import settings
import re
from collections import OrderedDict

@register.inclusion_tag("rbac/multi_menu.html")
def multi_menu(request):
    '''
    創建二級菜單
    :return:
    '''
    '''
    {
        1: {
            'title': '用戶管理', 
            'icon': 'fa fa-envira', 
            'children': [
                {'title': '客戶列表', 'url': '/customer/list/'}
            ]
        }, 
        2: {
            'title': '信息管理', 
            'icon': 'fa-black-tie', 
            'children': [
                {'title': '賬單列表', 'url': '/payment/list/'}
            ]
        }
    }
    我們可以對字典進行排序, 構成一個有序字典
    '''
    path_info = request.path_info
    menu_dict = request.session.get(settings.MENU_SESSION_KEY)  # 取出數據還是一樣的操作

    key_list = sorted(menu_dict)  # 對字典的key 進行排序
    ordered_dict = OrderedDict()  # 創建一個空的 有序字典
    for key in key_list:   # 循環根據key 排序之后的大字典
        val = menu_dict[key]  # 得到其中的每一個小字典
        val["class"] = "hide"  # 添加一個 class:hide 鍵值. 控制標簽的顯示隱藏
        for per in val["children"]:  # 循環 當前字典(菜單) 下的 children 子菜單
            regex = "^%s$" % per["url"]   # 每一個子菜單中的url 添加 起始和終止符。 進行嚴格的匹配。
            if re.match(regex, request.path_info):  # 與當前訪問的url:request.path_info  進行匹配
                per["class"] = "active"   # 匹配成功 為當前子菜單添加  class:active 類屬性(表示被選中的)
                val["class"] = ""  # 當前一級菜單的 class:""  跟改為空, 不hide(不隱藏)
        ordered_dict[key] = val  # 最終將數據組織好的每一個小字典。 添加到有序的大字典當中
    return {"ordered_dict": ordered_dict}
rbac_tags 自定義模板語法
a = OrderedDict(
    [
        ('1', {
            'title': '用戶管理', 
            'icon': 'fa-envira', 
            'children': [
                {
                    'title': '客戶列表', 
                    'url': '/customer/list/', 
                    'class': 'active'
                }
            ], 
            'class': ''
        }
         ), 
        ('2', {
            'title': '信息管理', 
            'icon': 'fa-black-tie', 
            'children': [
                {
                    'title': '賬單列表', 
                    'url': '/payment/list/'
                }
            ], 
            'class': 'hide'
        }
         )
    ]
)
自定義模板語法中對字典進行處理,變成有序字典的結構 應該是這個樣子

再看看 我們的這個 multi_menu.html 要怎么搞:

{#二級菜單#}
<div class="multi-menu">
    {% for item in ordered_dict.values %}
        <div class="item">
            <a class="title"><span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span>{{ item.title }}</a>
            <div class="body {{ item.class }}">
                {% for per in item.children %}
                    <a class="{{ per.class }}" href="{{ per.url }}">{{ per.title }}</a>
                {% endfor %}
            </div>
        </div>
    {% endfor %}
</div>

使用了 兩層 for 循環來做這件事情!

{{ item.class }} 這個變量就是 一級菜單中保存的 class鍵對應的 值。 用來控制 這個一級菜單下的 子菜單得,顯示和隱藏;
{{ per.class }}  這個變量是循環 item.children 列表中,每個子菜單字典中,保存的 'class': 'active' , 用來表示當前這個子標簽,是被選中的狀態。
  (怎么確定的呢? 通過request.path_info 這個是發送到服務端的 當前訪問的url。 我們和用戶所有的權限, 使用re模塊進行匹配時,確定的。 只有匹配成功的才會添加這個 鍵值對)

 
        
到這里基本功能已經實現了; 還卻一些。 比如我想要 的效果是: 當我點擊一個 一級菜單時,其余的一級菜單 全部收起.(也就是為除點擊菜單之外的 其余菜單下的 class="body" 的這個div添加上hide)

我們需要的就是 javascript 的東西了。 用 jQuery 來搞吧:

(function (jq) {
    jq('.multi-menu .title').click(function () {
        $(this).next().removeClass("hide");
        $(this).parent().siblings().children(".body").addClass('hide');
    });
})(jQuery);

 

 點擊時 當前這個一級菜單,刪除 hide。 使用js中的 排他思想,為除我之外其他兄弟,全部加上hide。

當然,我們應該,把這些所有的代碼,都添加到。我的 rbac 組件當中。 其余地方使用時,只需要引用就可以。

在我們的 BASE.html 中添加,就可以所有的 繼承 都使用到:
頭部引入 css 的文件
    <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
    <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
    <link rel="stylesheet" href="{% static 'rbac/css/rbac.css' %} "/>


在中間需要的地方, 使用我們的自定義,模板語法:
{% load rbac_tags %}   // 這一句放到,文件的首行或者首行下面就可以
    <div class="left-menu">
        <div class="menu-body">
            {% multi_menu request %}
        </div>
    </div>


在 文件的最下方引入 js 文件:
<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script>
<script src="{% static 'rbac/js/rbac.js' %} "></script>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM