zoukankan      html  css  js  c++  java
  • 基于EasyUI 快速搭建权限管理平台

    前言:

    一.用户角色权限设计思路:

    <1>不同职责的人员,对于系统操作的权限应该是不同;
    <2>可以对“组”进行权限分配;
    <3>权限管理系统应该是可扩展的;
    <4>满足业务系统中的功能权限

    2. 角色和操作的权限设计(
    不同的应用场合,你可能会想出不同的需求,提了一个新的需求以后,可能会发现原来的设计没方法实现了,于是还要添加一个新的表。这也是上面所提到的问题。 其实不必想得那么复杂,权限可以简单描述为:

    某某主体 在 某某领域 有 某某权限

    1,主体可以是用户,可以是角色,也可以是一个部门

    2, 领域可以是一个模块,可以是一个页面,也可以是页面上的按钮

    3, 权限可以是“可见”,可以是“只读”,也可以是“可用”(如按钮可以点击)

    其实就是Who、What、How的问题, 谁对什么功能,动作有怎样的操作权限

    下面来看看表结构是如何设计的,代码如下:

    from django.db import models
    
    # Create your models here.
    
    
    class Permission(models.Model):
        caption = models.CharField(max_length=32)
        parent_id = models.ForeignKey('Permission', related_name='k', to_field='id', null=True, blank=True)
        code = models.CharField(max_length=64, null=True,blank=True)
        method = models.CharField(max_length=16, null=True,blank=True)
        kwargs = models.CharField(max_length=128, null=True,blank=True)
        is_menu = models.BooleanField(default=False)
    
        def __str__(self):
            return self.caption
    
    class Role(models.Model):
        name = models.CharField(max_length=32)
        def __str__(self):
            return self.name
    
    class RoleToPermission(models.Model):
        menu_id = models.ForeignKey(Permission, to_field='id')
        role_id = models.ForeignKey(Role, to_field='id')
    
        def __str__(self):
            return "%s-%s" %(self.menu_id.caption, self.role_id.name)
    # 目标,根据角色列表获取权限 li
    # 获取当前用户的所有标题权限
    # RoleToPermission.objects.filter(role_id__in=li,menu_id__is_menu=True).
    #     values('menu_id__caption','menu_id__parent_id','menu_id__parent_id','menu_id__code')
    
    # 获取当前用户的所有权限
    # RoleToPermission.objects.filter(role_id__in=li).
    #     values('menu_id__caption','menu_id__parent_id','menu_id__parent_id','menu_id__code')
    
    class UserInfo(models.Model):
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=64)
    
        def __str__(self):
            return self.username
    
    
    class UserInfoToRole(models.Model):
        user_id = models.ForeignKey(UserInfo, to_field='id')
        role_id = models.ForeignKey(Role, to_field='id')
        def __str__(self):
            return '%s-%s' %(self.user_id.username, self.role_id.name)
    用户,角色, 权限表

     权限表直接将权限分为具体的标题级别,url或者具体的某种操作方法

    ##权限表与角色表关系

    先来看看权限表的表结构是如何构造的:

    class Permission(models.Model):
        ##菜单的的权限,主要是为后台界面菜单部分单独拆分出来的字段,跟下面定义的code,method,kwargs等字段进行区分
        caption = models.CharField(max_length=32)
        ##用于构造树形结构的权限表,可以自己关联自己
        parent_id = models.ForeignKey('Permission', related_name='k', to_field='id', null=True, blank=True)
        ##具体的某个访问的url
        code = models.CharField(max_length=64, null=True,blank=True)
        ##具体的请求方法
        method = models.CharField(max_length=16, null=True,blank=True)
        ##请求方法所带的参数
        kwargs = models.CharField(max_length=128, null=True,blank=True)
        ##是否是菜单栏
        is_menu = models.BooleanField(default=False)
    
        def __str__(self):
            return self.caption

    从上边的表可以看出,我们将权限分类处理,这样可以更清晰准确的针对不同类型的权限进行划分,并且构造出了有关联关系的树形结构

    接下来将权限表与角色表通过多对多关联,代码如下:

    class Role(models.Model):
        #定义角色名称
        name = models.CharField(max_length=32)
        def __str__(self):
            return self.name
    
    class RoleToPermission(models.Model):
        ##定义角色和权限的对应关系
        menu_id = models.ForeignKey(Permission, to_field='id')
        role_id = models.ForeignKey(Role, to_field='id')
    
        def __str__(self):
            return "%s-%s" %(self.menu_id.caption, self.role_id.name)
    # 目标,根据角色列表获取权限 li
    # 获取当前用户的所有标题权限
    # RoleToPermission.objects.filter(role_id__in=li,menu_id__is_menu=True).
    #     values('menu_id__caption','menu_id__parent_id','menu_id__parent_id','menu_id__code')

    ##用户与角色关系

    用户表UserInfo 通过中间表UserInfoToRole 多对多关联 角色表Role,具体代码如下

    class Role(models.Model):
        name = models.CharField(max_length=32)
        def __str__(self):
            return self.name
    
    class UserInfo(models.Model):
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=64)
    
        def __str__(self):
            return self.username
    
    
    class UserInfoToRole(models.Model):
        user_id = models.ForeignKey(UserInfo, to_field='id')
        role_id = models.ForeignKey(Role, to_field='id')
        def __str__(self):
            return '%s-%s' %(self.user_id.username, self.role_id.name)

    ##登录过程,根据用户信息,关联查询获取用户相关角色,根据用户所属的角色关联查询用户对应的权限,具体实现代码如下:

    def login(request):
        if request.method == 'POST':
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
            obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
            if obj:
                # 当前用户信息保存至Session
                request.session['user_info'] = {'id': obj.id, 'name': obj.username}
    
                # 当前用户角色列表保存至Session
                result_list = models.UserInfoToRole.objects.filter(user_id_id=obj.id).values('role_id_id')
                role_list = list(map(lambda x: x['role_id_id'], result_list))
                request.session['role_list'] = role_list
    
                # 当前用户所有权限加入Session
                from django.db.models import Count, Min, Max, Sum
                permission_list = models.RoleToPermission.objects.filter(role_id__in=role_list).values(
                    'menu_id_id').annotate(c=Count('menu_id_id')).values('menu_id__caption',
                                                                         'menu_id__parent_id',
                                                                         'menu_id__code',
                                                                         'menu_id__method',
                                                                         'menu_id__kwargs',
                                                                         'menu_id__id')
                # 根据permission_id去重
                permission_list = list(permission_list)
                request.session['permission_list'] = permission_list
    
                menu_list = models.RoleToPermission.objects.filter(role_id__in=role_list,menu_id__is_menu=True).values(
                    'menu_id_id').annotate(c=Count('menu_id_id')).values('menu_id__caption',
                                                                         'menu_id__parent_id',
                                                                         'menu_id__code',
                                                                         'menu_id__method',
                                                                         'menu_id__kwargs',
                                                                         'menu_id__id',)
                # 根据permission_id去重
                menu_list = list(menu_list)
                request.session['menu_list'] = menu_list
    
                return redirect('/index/')
        return render(request, 'login.html')
    Login

    #自定义render,从session中获取menu_list,代码如下:

    def my_render(request, template_name, context=None, *args, **kwargs):
        session_menu_list = request.session['menu_list']
        menu_list = build_tree(session_menu_list)
        print(request.path_info.split('/')[1])
        if context:
            context['menu_list'] = menu_list
        else:
            context = {'menu_list': menu_list}
        return render(request, template_name, context, *args, **kwargs)
    my_render

    #定义装饰器validator,用来验证用户所请求的url是否已经过授权,代码如下:

    def validate(func):
        def inner(request,*args,**kwargs):
            session_perm_l = list(map(lambda  x : x['menu_id__code']  , request.session['permission_list']))
            if request.path_info.split('/')[1] in session_perm_l:
                ret = func(request,*args,**kwargs)
                return ret
            else:
                return HttpResponse('unauhoted')
        return inner
    Validate

    #通过递归方法构建权限树形结构,代码如下:

    def build_node(menu_list, dic):
        #
        for menu in menu_list:
            if menu['id'] == dic['menu_id__parent_id']:
                temp = {'id': dic['menu_id__id'],'text': dic['menu_id__caption'], 'url': dic['menu_id__code'],'children': []}
                menu['children'].append(temp)
                break
            else:
                build_node(menu['children'], dic)
    
    def build_tree(session_menu_list):
        # [ {menu_id__parent_id: None, 'menu_id__caption': '权限管理', 'menu_id__code': 'permission'},{},{} ]
        menu_list = []
        # menu_list = [{...}]
        for dic in session_menu_list:
            if dic['menu_id__parent_id'] == None:
                temp = {'id': dic['menu_id__id'],'text': dic['menu_id__caption'], 'url': dic['menu_id__code'],'children': []}
                menu_list.append(temp)
            else:
                # 当前
                build_node(menu_list, dic)
        return menu_list
    build_tree

    下面看下前端是如何实现的,前端使用EasyUI, 界面比较老旧,不过前端已经高度封装,有利于提高开发效率,首先我们先构建自己的母版,将基本的标题和样式,js文件,并展示出左侧菜单按钮

    具体实现方法如下:

    首先EasyUI以west,east,north,south,center 将前端分文左右上下中间的布局效果,具体实现代码如下:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <title>Full Layout - jQuery EasyUI Demo</title>
        <link rel="stylesheet" type="text/css" href="/static/easyui/themes/default/easyui.css">
        <link rel="stylesheet" type="text/css" href="/static/easyui/themes/icon.css">
        <script type="text/javascript" src="/static/easyui/jquery.min.js"></script>
        <script type="text/javascript" src="/static/easyui/jquery.easyui.min.js"></script>
        <style>
            .crm-menu{
                display: block;
                padding: 8px;
                border-bottom: 1px dotted #dddddd;
            }
            .crm-menu:hover{
                background-color: #E0ECFF;
            }
        </style>
        {% block css %} {% endblock %}
    </head>
    <body class="easyui-layout">
        <div data-options="region:'north',border:false" style="height:60px;background:#B3DFDA;padding:10px">
            LOGO
        </div>
        <div data-options="region:'west',split:true,title:'West'" style="150px;">
            <div id="aa" class="easyui-accordion" style="100%;">
    
                {% for menu in menu_list %}
                    <div title="{{ menu.text }}" data-options="iconCls:'icon-ok'" style="overflow:auto;">
    
                        {% for child in menu.children %}
                            <a id="menu_{{ child.url }}" href='/{{ child.url }}/' class='crm-menu'>{{ child.text }}</a>
                        {% endfor %}
                    </div>
                {% endfor %}
    
    
            </div>
        
        </div>
        <div data-options="region:'south',border:false" style="height:50px;background:#A9FACD;padding:10px;">south region</div>
        <div data-options="region:'center',title:'Center'">
            {% block content %} {% endblock %}
        </div>
    
        {% block js %} {% endblock %}
    </body>
    </html>
    _layout.html

    母版定义好后,我们可以基于母版定义具有树形结构的权限展示页面.

    EasyUI可以通过两种方式实现此效果,通过“easyui_tree” 类实现树形菜单,也可以通过js生成前端展示页面,具体代码如下:

    {% extends 'layout/_layout.html' %}
    
    {% block  content %}
    
        <div style="float: left; 300px">
            <ul id="pers_tree" ></ul>
            <!-- <ul id="tt" class="easyui-tree" data-options="url:'/get_permission_tree/',method:'get',animate:true"></ul> -->
        </div>
    
        <div style="float: left; 600px">
             <table id="dg"></table>
        </div>
        <div id="dlg" class="easyui-dialog" style="400px;height:200px;padding:10px 20px" closed="true" buttons="#dlg-buttons"> <!-#easyui订制模态对话框,默认关闭状态->
            <form id="fm1">
                <div class="input-group clearfix">
                    <div class="group-label" style=" 80px;">
                        <span>省份:</span>
                    </div>
                    <div class="group-input" style=" 300px;">
                        <input id="dlg_nid" style=" 200px;display: none"  name="nid"/>
                        <input id="dlg_province" style=" 200px" class="easyui-textbox" type="text" name="caption" data-options="required:true,missingMessage:'省份不能为空'" />
                    </div>
                </div>
            </form>
        </div>
        <div id="dlg-buttons"> 
            <span id="dlg_summary" style="color: red"></span>
            <a href="#" class="easyui-linkbutton" iconCls="icon-ok" onclick="Save()">保存</a>
            <a href="#" class="easyui-linkbutton" iconCls="icon-cancel" onclick="javascript:$('#dlg').dialog('close')">取消</a>
        </div>
    {% endblock %}
    
    {% block js %}
        <script>
            $(function(){
                InitTree();
            });
    
            function InitTree(){
                $('#pers_tree').tree({
                    url: '/get_permission_tree/',
                    method: 'get',
                    animate: true,
                    onClick: function(node){
                        console.log(node.text,node.id);
                        InitTable(node.id);
                        InitPagination();
                    }
                })
            }
    
            function InitTable(node_parent_id){
                $('#dg').datagrid({
                    title: '听不下去了',
                    url: '/get_child_permission/',
                    method: 'get',
                    queryParams: {
                        node_parent_id: node_parent_id
                    },
                    columns: [[
                        {
                            field: 'ck',
                            checkbox: true
                        },
                        {
                            field: 'caption',
                            title: '标题',
                             180,
                            align: 'center'
                        },
                        {
                            field: 'code',
                            title: 'URL',
                             180,
                            align: 'center'
                        }
    
                    ]],
                    toolbar: [
                        {
                            text: '添加',
                            iconCls: 'icon-add',
                            handler: AddRow
                        }, {
                            text: '删除',
                            iconCls: 'icon-remove',
                            handler: RemoveRow
                        }, {
                            text: '修改',
                            iconCls: 'icon-edit',
                            handler: EditRow
                        }
                    ],
                    pagePosition: 'both',
                    pagination: true,
                    pageSize: 10,
                    pageNumber: 1,
                    pageList: [10, 20, 50]
                })
            }
    
            function AddRow(){
                // 显示对话框,由于希望添加则将方法设置为POST
                $('#fm1').form('clear');
                $('#dlg').dialog('open').dialog('setTitle','创建省份');
                $('#dlg_summary').empty();
                METHOD = 'post';  
            }
            function RemoveRow(){
                console.log('RemoveRow');
            }
            function EditRow(){
                console.log('EditRow');
            }
    
            function InitPagination() {
                var pager = $('#dg').datagrid('getPager');
                $(pager).pagination({
                    beforePageText: '',
                    afterPageText: '页 共{pages}页',
                    displayMsg: '当前显示{from}-{to}条记录 共{total}条数据'
                })
            }
        </script>
    {% endblock %}
    View Code

    EasyUI 已经高度封装了树形菜单的实现方法,我们只要了解具体的参数即可,详见http://www.jeasyui.com/demo/main/index.php

  • 相关阅读:
    CSS经典实用技巧10招
    分析与设计数据库模型的简单过程
    MS SQL数据类型及长度限制
    firefox与IE的nextSibling
    DIV+CSS布局总结
    Sql Server查找临时表,并删除
    powerDesigner15.1破解
    分别使用函数及游标实现SQL多行转一列
    生命的帐单
    北京有380万“北漂族” 没有归属感但不离不弃!
  • 原文地址:https://www.cnblogs.com/jasonwang-2016/p/6022243.html
Copyright © 2011-2022 走看看