[django项目] 后台菜单管理功能

后台菜单管理功能

菜单的管理功能其实就是, 对菜单的增删改查

I. 业务功能分析

1>业务需求分析

后台首页菜单根据用户权限动态生成,不同菜单对应不同的功能视图。

菜单的增删改查。

2>功能分析

  • 菜单列表
  • 添加菜单
  • 修改菜单
  • 删除菜单

3>模型设计

3.1>字段分析
  • name, 菜单名
  • url, 菜单的路由
  • parent, 父菜单的id
  • order, 排序
  • permission, 访问该菜单的权限名
  • icon, 菜单显示的icon
  • codename, 菜单的权限码
  • is_visible, 是否可见
3.2>模型定义
# 在myadmin/models.py中定义如下模型
from django.db import models
from django.contrib.auth.models import Permission

from utils.models import BaseModel
# Create your models here.


class Menu(BaseModel):
    name = models.CharField('菜单名', max_length=48, help_text='菜单名')
    url = models.CharField('url', max_length=256, null=True, blank=True, help_text='url')
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
    order = models.SmallIntegerField('排序', default=0)
    permission = models.OneToOneField(Permission, on_delete=models.SET_NULL, null=True)
    icon = models.CharField('图标', max_length=48, default='fa-link')
    codename = models.CharField('权限码', max_length=48, help_text='权限码', unique=True)
    is_visible = models.BooleanField('是否可见', default=False)

    class Meta:
        ordering = ['-order']
        db_table = 'tb_menu'
        verbose_name = '菜单'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

模型创建后记得迁移, 项目到目前的阶段, 数据库的内容开始变得比较复杂了, 所以该迁移的时候一定不要落下

II. 菜单列表

1>业务流程分析

  1. 获取未删除,的一级菜单
  2. 根据一级菜单获取未删除的二级菜单
  3. 渲染页面

2>接口设计

  1. 接口说明
类目说明
请求方法GET
url定义/admin/menus/
参数格式无参数
  1. 返回结果

    html

3>后端代码

3.1>视图
#  myadmin/views.py下定义如下视图:
class MenuListView(View):
    """
    菜单列表视图
    url:/admin/menu_list/
    """
    def get(self, request):
        # 为了便于后续的修改, 需要展示被逻辑删除的菜单,
        # 因此filter的is_delete属性就不许要加了
        menus = models.Menu.objects.only(
            'name', 'url', 'icon', 'is_visible', 'order', 'codename'
        ).filter(parent=None)
        # parent=None表示没有父菜单, 即一级菜单
        return render(request, 'myadmin/menu/menu_list.html', context={'menus': menus})
#  myadmin/views.py中修改菜单管理对应的路由:
class IndexView(View):
    """
    后台首页视图
    """
    def get(self, request):
        menus = [
            {...},
            {...},
            {...},
            {...},
            {...},
            {
                "name": "系统设置",
                "icon": "fa-cogs",
                "children": [
                    {...},
                    {...},
                    {
                        "name": "菜单管理",
                        "url": "myadmin:menu_list"
                    },
                    {...}
                ]
            }

        ]
        return render(request, 'myadmin/index.html', context={'menus': menus})
3.2>路由
# admin/urls.py中添加如下路由
path('menu_list/', views.MenuListView.as_view(), name='menu_list'),

4>前端代码

4.1>html

咱们先简单的写一下前端, 然后去看一下页面的情况

<!-- 创建templates/myadmin/menu/menu_list.html-->
{% extends 'myadmin/base/content_base.html' %}
{% load static %}
{% block page_header %}系统设置{% endblock %}
{% block page_option %}菜单管理{% endblock %}

创建好后记得重启一下django的服务哦

页面效果:

[外链图片转存失败(img-U73ceLqz-1567554944282)(django%E9%A1%B9%E7%9B%AE-%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86.assets/1567311498712.png)]

可以打开这个界面, 就说明路由和视图配置好了

接下来将列表填充到这个页面, 我们可以使用AdminLTE为用户提供的表格模板:

[外链图片转存失败(img-ZlwaLcOP-1567554944283)(django%E9%A1%B9%E7%9B%AE-%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86.assets/1567311671862.png)]

这个页面上有很多类型的表格, 修改前端代码

{% extends 'myadmin/base/content_base.html' %}
{% load static %}
{% block page_header %}系统设置{% endblock %}
{% block page_option %}菜单管理{% endblock %}

{% block content %}
    <div class="box">
        <div class="box-header">
            <h3 class="box-title">菜单列表</h3>
            <div class="box-tools">
                <button type="button" class="btn btn-primary btn-sm">添加菜单
                </button>
            </div>
        </div>
        <!-- /.box-header -->
        <div class="box-body">
            <table class="table table-bordered ">
                <tbody>
                <tr role="row">
                    <!-- 列表字段 -->
                    <th>菜单</th>
                    <th>子菜单</th>
                    <th>路由地址</th>
                    <th>图标</th>
                    <th>权限码</th>
                    <th>顺序</th>
                    <th>是否可见</th>
                    <th>逻辑删除</th>
                    <th>操作</th>
                </tr>
                {% for menu in menus %}
                    <!-- 循环遍历列表内容 -->
                    <tr>
                    <!-- 遍历父菜单属性 -->
                        <td>{{ menu.name }}</td>
                        <td><!-- 子菜单为空 --></td>
                        <td>{{ menu.url|default:'' }}</td>
                        <td>{{ menu.icon }}</td>
                        <td>{{ menu.codename }}</td>
                        <td>{{ menu.order }}</td>
                        <td>
                            {% if menu.is_visible %}
                                是
                            {% else %}
                                否
                            {% endif %}
                        </td>
                        <td style="width: 100px" data-id="{{ menu.id }}" data-name="{{ menu.name }}">
                            {% if menu.children.all %}
                                <button type="button" class="btn btn-info btn-xs edit">编辑</button>
                            {% else %}
                                <button type="button" class="btn btn-info btn-xs edit">编辑</button>
                                <button type="button" class="btn btn-danger btn-xs delete">删除</button>
                            {% endif %}
                        </td>
                    </tr>
                    {% if menu.children.all %}
                        {% for child in menu.children.all %}
                            <!-- 遍历子菜单属性 -->
                            <tr>
                                <td><!-- 父菜单为空 --></td>
                                <td>{{ child.name }}</td>
                                <td>{{ child.url }}</td>
                                <td>{{ child.icon }}</td>
                                <td>{{ child.codename }}</td>
                                <td>{{ child.order }}</td>
                                <td style="width: 80px">
                                    {% if child.is_visible %}
                                        是
                                    {% else %}
                                        否
                                    {% endif %}
                                </td>
                                <td style="width: 100px" data-id="{{ child.id }}" data-name="{{ child.name }}">
                                    <button type="button" class="btn btn-info btn-xs edit">编辑</button>
                                    <button type="button" class="btn btn-danger btn-xs delete">删除</button>
                                </td>
                            </tr>
                        {% endfor %}
                    {% endif %}
                {% endfor %}
                </tbody>
            </table>
        </div>
        <!-- /.box-body -->
    </div>
{% endblock %}

注意父菜单和子菜单循环体中的那两个<td></td>标签, 这样做得目的是为了更好的分辨父菜单和子菜单

III. 添加菜单页面

功能概述: 点击添加菜单按钮, 弹出新增菜单窗口, 输入信息提交, 即可添加到菜单列表中

1>接口设计

  1. 接口说明:
类目说明
请求方法GET
url定义/admin/menu/
参数格式无参数
  1. 返回数据

    html

2>后端代码

2.1>视图
# 在myadmin/views.py中添加如下视图
class MenuAddView(View):
    """
    添加菜单视图
    url:/admin/menu/
    """

    def get(self, request):

        form = MenuModelForm()
        return render(request, 'myadmin/menu/add_menu.html', context={'form': form})
2.2>路由
# 在myadmin/urls.py中添加如下路由
path('menu/', views.MenuAddView.as_view(), name='add_menu')
2.3>表单
# 在myadmin/forms.py中定义如下表单
from django import forms

from .models import Menu


class MenuModelForm(forms.ModelForm):
    parent = forms.ModelChoiceField(queryset=None, required=False, help_text='父菜单')

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['parent'].queryset = Menu.objects.filter(is_delete=False, is_visible=True, parent=None)
        # https://docs.djangoproject.com/en/2.2/ref/forms/fields/#fields-which-handle-relationships

    class Meta:
        model = Menu
        fields = ['name', 'url', 'order', 'parent', 'icon', 'codename', 'is_visible']

2.4>自定义标签

为了在渲染表单是能加入自定义css样式,在应用admin中定义自定义标签,在admin下创建templatetags包,在其中创建admin_customer_tags.py模块

# myadmin/tamplatetags/admin_customer_tags.py
from django.template import Library

register = Library()


@register.simple_tag()
def add_class(field, class_str):
    return field.as_widget(attrs={'class': class_str})

3>前端代码

3.1>html
<!-- 修改 templates/myadmin/menu/menu_list.html -->
{% extends 'myadmin/base/content_base.html' %}
{% load static %}
{% block page_header %}系统设置{% endblock %}
{% block page_option %}菜单管理{% endblock %}
{% block content %}
    <div class="box">
        <div class="box-header with-border">...</div>
        <!-- /.box-header -->
        <div class="box-body">...</div>
    </div>

    <!-- add modle -->
    <div class="modal fade" id="modal-add" role="dialog" >
        <div class="modal-dialog">
            <div class="modal-content">

            </div>
            <!-- /.modal-content -->
        </div>
        <!-- /.modal-dialog -->
    </div>
    <!-- /.modal -->

{% endblock %}

弹出窗口我们选用表单的形式来完成, 因此这里单独创建一个模型, 方便渲染

这里的模型来自Bootstarp

   <!-- 新建 templates/myadmin/menu/add_menu.html -->
   {% load admin_customer_tags %}
   {% load static %}
   <div class="modal-header">
       <button type="button" class="close" data-dismiss="modal" aria-label="Close">
           <span aria-hidden="true">&times;</span></button>
       <h4 class="modal-title">添加菜单</h4>
   </div>
   <div class="modal-body">
       <form class="form-horizontal" id="add-menu">
           {% csrf_token %}
           <div class="box-body">
               {% for field in form %}
                   {% if field.name == 'is_visible' %}
                       <div class="form-group">
   
                           <div class="col-sm-offset-2 col-sm-10">
   
                               <div class="checkbox">
                                   <label for="{{ field.id_for_label }}">{{ field }}{{ field.label }}</label>
                               </div>
                           </div>
   
                       </div>
                   {% else %}
                       <div class="form-group {% if field.errors %}has-error{% endif %}">
   
                           <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
   
                           <div class="col-sm-10">
                               {% for error in field.errors %}
                                   <label class="control-label" for="{{ field.id_for_label }}">{{ error }}</label>
                               {% endfor %}
                               {% add_class field 'form-control' %}
                           </div>
                       </div>
                   {% endif %}
               {% endfor %}
   
           </div>
       </form>
   </div>
   <div class="modal-footer">
       <button type="button" class="btn btn-default pull-left" data-dismiss="modal">取消</button>
       <button type="button" class="btn btn-primary add">添加</button>
   </div>


根据官方文档我们还需要加一个href=#foo(路由地址)属性到添加菜单按钮标签中

<div class="box-tools">
	<button type="button" class="btn btn-primary btn-sm" 
            data-toggle="modal" data-target="#modal-add" 
            href="/admin/add_menu/">添加菜单</button>
</div>


页面效果:

[外链图片转存失败(img-QtGhDXrh-1567554944294)(django%E9%A1%B9%E7%9B%AE-%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86.assets/)]

IIII. 添加菜单

1>业务流程分析

  • 接收表单参数
  • 校验表单参数
  • 校验成功保存菜单数据,创建菜单一对一关联权限对象,返回创建成功的json数据
  • 校验失败,返回渲染了错误信息的表单

2>接口设计

2.1>接口说明:
类目说明
请求方法POST
url定义/admin/menu/
参数格式表单参数
2.2>参数说明:
参数名类型是否必须描述
name字符串菜单名
url字符串当前文章页数
order整数排序
parent整数父菜单id
icon字符串渲染图标类名
codename字符串权限码
is_visible整数是否可见
2.3>返回数据
# 添加正常返回json数据
{
"errno": "0",
"errmsg": "菜单添加成功!"
}

如果有错误,返回html表单

3>后端代码

3.1>视图
# 在myadmin/views.py中的MenuAddView视图中添加post方法
class MenuAddView(View):
    """
    添加菜单视图
    url:/admin/menu/
    """

    def get(self, request):
        form = MenuModelForm()
        return render(request, 'myadmin/menu/add_menu.html', context={'form': form})

    def post(self, request):
        form = MenuModelForm(request.POST)
        
        if form.is_valid():
            new_menu = form.save()
            content_type = ContentType.objects.filter(app_label='myadmin', model='menu').first()
            permission = Permission.objects.create(name=new_menu.name, content_type=content_type, codename=new_menu.codename)
            new_menu.permission = permission
            new_menu.save(update_fields=['permission'])
            return json_response(errmsg='菜单添加成功!')
        else:
            return render(request, 'myadmin/menu/add_menu.html', context={'form': form})

4>前端代码

4.1>js
// 创建static/js/myadmin/menu/add_menu.js
$(() => {
    let $addBtn = $('button.add');          //  模态框中的添加按钮
    let $form = $('#add-menu');             //  模态矿中的表单
    let data = {};
    $addBtn.click(function () {

        $
            .ajax({
                url: '/admin/menu/',
                type: 'POST',
                data: $form.serialize(),
                // dataType: "json"
            })
            .done((res) => {
                if (res.errno === '0') {
                    // 添加成功,关闭模态框,并刷新菜单列表
                    $('#modal-add').modal('hide').on('hidden.bs.modal', function (e) {
                        $('#content').load(
                            $('.sidebar-menu li.active a').data('url'),
                            (response, status, xhr) => {
                                if (status !== 'success') {
                                    message.showError('服务器超时,请重试!')
                                }
                            }
                        );
                    });
                    message.showSuccess(res.errmsg);


                } else {
                    message.showError('添加菜单失败!');
                    // 更新模特框中的表单信息
                    $('#modal-add .modal-content').html(res)
                }
            })
            .fail(() => {
                message.showError('服务器超时,请重试');
            });
    });

});

4.2>html
<!-- 在 templates/myadmin/menu/add_menu.html 中引入js -->
...
<script src="{% static 'js/myadmin/menu/add_menu.js' %}"></script>

V. 删除菜单

1>接口设计

1.1>接口说明:
类目说明
请求方法DELETE
url定义/admin/menu/<int:menu_id>/
参数格式路径参数
1.2>参数说明
参数名类型是否必须描述
menu_id整数菜单id
1.3>返回值
{
"errno": "0",
"errmsg": "删除菜单成功!"
}


后台展示的菜单不重要,所以我们不用逻辑删除菜单,而是采用真删除

2>后端代码

2.1>视图
# 在admin/views.py中创建一个MenuUpdateView视图
class MenuUpdateView(View):
    """
    菜单管理视图,delete删除菜单
    url:/admin/menu/<int:menu_id>/
    """
    def delete(self, request, menu_id):
        # 获取到需要删除的菜单
        menu = models.Menu.objects.only('name').filter(id=menu_id)

        if menu:
            menu = menu[0]
            # 判断是是否为父菜单
            if menu.children.filter(is_delete=False).exists():
                return json_response(errno=Code.DATAERR, errmsg='父菜单不能删除!')
            # 将menu模型中的permission设置为CASCADE级联删除, 就可以使其在被删除的时候同时删除当条菜单
            menu.permission.delete()
            # menu.delete()
            return json_response(errmsg='删除菜单:%s成功' % menu.name)
        else:
            return json_response(errno=Code.NODATA, errmsg='菜单不存在!')



2.2>路由
# 在admin/urls.py中添加如下路由
path('menu/<int:menu_id>/', views.MenuUpdateView.as_view(), name='menu_manage'),


3>前端代码

3.1>html
<!-- 
修改 templates/admin/menu/menu_list.html 
在content中,添加删除模态框
然后引入menu.js
-->
{% block content %}
...
...
    <div class="modal modal-danger fade" id="modal-delete">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span></button>
                    <h4 class="modal-title">警告</h4>
                </div>
                <div class="modal-body">
                    <p>One fine body&hellip;</p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-outline pull-left" data-dismiss="modal">取消</button>
                    <button type="button" class="btn btn-outline delete-confirm">删除</button>
                </div>
            </div>
            <!-- /.modal-content -->
        </div>
        <!-- /.modal-dialog -->
    </div>
    <!-- /.modal -->
{% endblock %}
{% block script %}
    <script src="{% static 'js/admin/menu/menu_list.js' %}"></script>
{% endblock %}

记得再去content_base里挖一个script的block

3.2>js
// 创建 static/js/admin/menu/menu_list.js
$(() => {
    let $deleteBtns = $('button.delete');	// 删除按钮
    menuId = 0;		// 被点击菜单id
    let $currentMenu = null;	// 当前被点击菜单对象, 也就是对应的tr标签(表格中的一行)

    $deleteBtns.click(function () {
        let $this = $(this);
        $currentMenu = $this.parent().parent();
        menuId = $this.parent().data('id');	// 菜单id
        let menuName = $this.parent().data('name');	// 菜单名
        
        // 改变模态框的显示内容
        $('#modal-delete .modal-body p').html('确定删除菜单: " + menuName + " ?');
        
        // 显示 模态框
        $('#modal-delete').modal('show');

    });

    $('#modal-delete button.delete-confirm').click(() => {
        deleteMenu()
    });

    // 删除菜单的函数
    function deleteMenu() {
        $
            .ajax({
                url: '/admin/menu/' + menuId + '/',
                type: 'DELETE',
                dataType: 'json'
            })
            .done((res) => {
                if (res.errno === '0') {	// errno为0代表成功
                    // 关闭模态框
                    $('#modal-delete').modal('hide');
                    // 删除菜单元素
                    $currentMenu.remove();
                    message.showSuccess('删除成功!');
                } else {	// 否则就表示出现了问题
                    message.showError(res.errmsg)
                }
            })
            .fail(() => {
                message.showError('服务器超时请重试!')
            })
    }
});

因为delete方法会改变数据库,所以需要csrftoken,在js/myadmin/menu.js中添加如下代码

   function getCookie(name) {
           var cookieValue = null;
           if (document.cookie && document.cookie !== '') {
               var cookies = document.cookie.split(';');
               for (var i = 0; i < cookies.length; i++) {
                   var cookie = cookies[i].trim();
                   // Does this cookie string begin with the name we want?
                   if (cookie.substring(0, name.length + 1) === (name + '=')) {
                       cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                       break;
                   }
               }
           }
           return cookieValue;
       }
   
       function csrfSafeMethod(method) {
           // these HTTP methods do not require CSRF protection
           return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
       }
   
       $.ajaxSetup({
           beforeSend: function (xhr, settings) {
               if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                   xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
               }
           }
       });

记得前端要加上{% csrf_token %}, 关于cookie还有一点就是

如果你先执行添加菜单再执行删除的话, 即使不加{% csrf_token %}也会执行删除

这是因为执行添加的话就会生成cookie, 已经有了当前页面的cookie, 其他需要cookie的请求(post,put,delete)都可以执行

VI. 编辑菜单页面

1>接口设计

  1. 接口说明:
类目说明
请求方法GET
url定义/admin/menu/<int:menu_id>/
参数格式路径参数
  1. 参数说明
参数名类型是否必须描述
menu_id整数菜单id
  1. 返回数据

    html

2>后端代码

# 在admin/views.py中的MenuUpdateView视图中添加一个get方法
class MenuUpdateView(View):
    """
    菜单管理视图
    url:/admin/menu/<int:menu_id>/
    """
    
    def get(self, request, menu_id):
        # 找到需要编辑的菜单
        menu = models.Menu.objects.filter(id=menu_id).first()
        # 使用之前定义的表单
        form = MenuModelForm(instance=menu)
        return render(request, 'myadmin/menu/update_menu.html', context={'form': form})

3>前端代码

3.1>html
<!-- 
修改 templates/admin/menu/menu_list.html 
在content中,添加修改模态框
-->
{% block content %}
	...
    <!-- update modle -->
    <div class="modal fade" id="modal-update" role="dialog" aria-labelledby="myLargeModalLabel">
        <div class="modal-dialog">
            <div class="modal-content">

            </div>
            <!-- /.modal-content -->
        </div>
        <!-- /.modal-dialog -->
    </div>
    <!-- /.modal -->
{% endblock %}

编辑的模态框其实和添加模态框很相似, 是可以进行代码复用的, 这个我们后面再说, 先完成功能

   <!-- 新建 templates/admin/menu/update_menu.html -->
   {% load admin_customer_tags %}
   {% load static %}
   <div class="modal-header">
       <button type="button" class="close" data-dismiss="modal" aria-label="Close">
           <span aria-hidden="true">&times;</span></button>
       <h4 class="modal-title">修改菜单</h4>
   </div>
   <div class="modal-body">
       <form class="form-horizontal" id="update-menu">
           {% csrf_token %}
           <div class="box-body">
               {% for field in form %}
                   {% if field.name == 'is_visible' %}
                       <div class="form-group">
   
                           <div class="col-sm-offset-2 col-sm-10">
   
                               <div class="checkbox">
                                   <label for="{{ field.id_for_label }}">{{ field }}{{ field.label }}</label>
                               </div>
                           </div>
   
                       </div>
                   {% else %}
                       <div class="form-group {% if field.errors %}has-error{% endif %}">
   
                           <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
   
                           <div class="col-sm-10">
                               {% for error in field.errors %}
                                   <label class="control-label" for="{{ field.id_for_label }}">{{ error }}</label>
                               {% endfor %}
                               {% add_class field 'form-control' %}
                           </div>
                       </div>
                   {% endif %}
               {% endfor %}
   
           </div>
       </form>
   </div>
   <div class="modal-footer">
       <button type="button" class="btn btn-default pull-left" data-dismiss="modal">取消</button>
       <button type="button" class="btn btn-primary update">修改</button>
   </div>
3.2>js
// 修改 static/js/admin/menu/menu_list.js 
$(() => {
    let $editBtns = $('button.edit');           // 编辑按钮
    let $deleteBtns = $('button.delete');       // 删除按钮
    menuId = 0;      // 被点击菜单id,要设置为全局变量, 不加let就行
    let $currentMenu = null;                    // 当前被点击菜单对象

    $deleteBtns.click(function () {...});

    $('#modal-delete button.delete-confirm').click(() => {...});

    // 删除菜单的函数
    function deleteMenu() {...}

    // 编辑菜单
    $editBtns.click(function () {
        let $this = $(this);
        $currentMenu = $this.parent().parent();
        menuId = $this.parent().data('id');

        $
            .ajax({
                url: '/admin/menu/' + menuId + '/',
                type: 'GET'
            })
            .done((res)=>{	// res是ajax请求回来的数据
                // 改变模态框的html
                $('#modal-update .modal-content').html(res);
                // 显示模态框
                $('#modal-update').modal('show')
            })
            .fail(()=>{
                message.showError('服务器超时,请重试!')
            })
    })
});

VII. 编辑菜单

完成了页面, 接下来就是点击修改, 再发送一个ajax回去, 我们需要再写一个后台接收编辑数据

1>业务流程分析

  • 接收表单参数
  • 校验表单参数
  • 校验成功, 保存菜单,判断改动字段是否影响了权限,如果有影响,修改权限,返回json信息
  • 校验失败,返回包含错误信息的html

2>接口设计

  1. 接口说明:
类目说明
请求方法PUT
url定义/admin/menu/<int:menu_id>
参数格式路径参数+表单参数
  1. 参数说明:
参数名类型是否必须描述
menu_id整数菜单id
name字符串菜单名
url字符串当前文章页数
order整数排序
parent整数父菜单id
icon字符串渲染图标类名
codename字符串权限码
is_visible整数是否可见
  1. 返回数据

    # 添加正常返回json数据
    {
    "errno": "0",
    "errmsg": "菜单修改成功!"
    }
    
    

    如果有错误,返回html表单

3>后端代码

3.1>视图
# 在admin/views.py中的MenuUpdateView视图中添加一个put方法
class MenuUpdateView(View):
    """
    菜单管理视图
    url:/admin/menu/<int:menu_id>/
    """
...

    def put(self, request, menu_id):
        # 获取到需要修改的菜单
        menu = models.Menu.objects.filter(id=menu_id).first()
        if not menu:
            return json_response(errno=Code.NODATA, errmsg='菜单不存在!')
        # 获取put请求的数据, request.body就是前端表单中所有的输入
        put_data = QueryDict(request.body)
        # QueryDict这个方法会将body转换成一个类字典对象
        form = MenuModelForm(put_data, instance=menu)
        if form.is_valid():
            obj = form.save()	# 拿到一个新的menu对象,命名为obj
            # 检查修改了的字段是否和权限有关
            flag = False
            if 'name' in form.changed_data:
                # changed_data会返回一个列表,包含当前修改了的字段
                # 将已有的name更新为最新的
                obj.permission.name = obj.name
                flag = True
            if 'codename' in form.changed_data:
                obj.permission.codename = obj.name
                flag = True
            if flag:
                obj.permission.save()
            return json_response(errmsg='菜单修改成功!')
        else:
            return render(request, 'admin/menu/update_menu.html', context={'form': form})
...

4>前端代码

4.1>html
<!-- 在 templates/myadmin/menu/update_menu.html 
中引入update_menu.js
-->
...
<script src="{% static 'js/myadmin/menu/update_menu.js' %}"></script>
4.2>js
// 这里的代码跟添加的js写法很相似,又是一个代码复用点
$(()=>{
    let $updateBtn = $('#modal-update button.update');
    let $form = $('#update-menu');
    
    $updateBtn.click(function () {
        $
            .ajax({
                url: '/admin/menu/' + menuId + '/',
                type: 'PUT',
                data: $form.serialize(),
                // dataType: "json"
            })
            .done((res) => {
                if (res.errno === '0') {
                    // 关闭模态框
                    $('#modal-update').modal('hide').on('hidden.bs.modal', function (e) {
                        $('#content').load(
                            $('.sidebar-menu li.active a').data('url'),
                            (response, status, xhr) => {
                                if (status !== 'success') {
                                    message.showError('服务器超时,请重试!')
                                }
                            }
                        );
                    });
                    message.showSuccess(res.errmsg);
                } else {
                    message.showError('修改菜单失败!');
                    // 修改模态框的内容为, 后端返回的带有错误信息的表单
                    $('#modal-update .modal-content').html(res)
                }
            })
            .fail(() => {
                message.showError('服务器超时,请重试');
            });
    });
});

VIII. 整合后台首页面菜单加载

我们来填之前菜单列表的坑, 之前我们使用list写的硬编码, 我们来到数据库中拿可用的菜单

1>后端代码

1.1>视图
class IndexView(LoginRequiredMixin, View):
    """
    后台首页视图
    """

    def get(self, request):
		
        objs = models.Menu.objects.only('name', 'url', 'icon', 'permission__codename',
                                        'permission__content_type__app_label').select_related(
            'permission__content_type').filter(is_delete=False, is_visible=True, parent=None)
        has_permissions = request.user.get_all_permissions()
        menus = []
        for menu in objs:
            if '%s.%s' % (menu.permission.content_type.app_label, menu.permission.codename) in has_permissions:
                temp = {
                    'name': menu.name,
                    'icon': menu.icon
                }

                children = menu.children.filter(is_delete=False, is_visible=True)
                if children:
                    temp['children'] = []
                    for child in children:
                        if '%s.%s' % (child.permission.content_type.app_label, child.permission.codename) in has_permissions:
                            temp['children'].append({
                                'name': child.name,
                                'url': child.url
                            })
                else:
                    if not menu.url:
                        continue
                    temp['url'] = menu.url
                menus.append(temp)
        print(menus)
     return render(request, 'admin/index.html', context={'menus': menus})

再运行之前, 一定要先添加一个菜单管理, 这样才可以看到我们的菜单列表效果

但是有一个bug, 当我们添加了一个新菜单之后, 虽然可以展示出来, 但是点击访问时就会提示我们没有定义路由,

这个可以设计一个自定义标签, 使用异常处理的方式, 若使用的使错误的路由, 就展示wait界面, 代码如下:

# 在myadmin/templatetags/admin_customer_tags.py中添加如下过滤器
@register.simple_tag()
def my_url(pattern, *args):
    try:
        url = reverse(pattern, *args)
    except Exception as e:
        url = reverse('myadmin:wait')
    return url

然后在前端代码中引用:

<!-- 在templates/myadmin/index.html中修改如下代码 -->
            <!-- Sidebar Menu -->
            <ul class="sidebar-menu" data-widget="tree">
                {% for menu in menus %}
                    {% if 'children' in menu %}
                        <li class="treeview">
                            <a href="#"><i class="fa {{ menu.icon }}"></i> <span>{{ menu.name }}</span>
                                <span class="pull-right-container">
                                    <i class="fa fa-angle-left pull-right"></i>
                                </span>
                            </a>
                            <ul class="treeview-menu">
                                {% for child in menu.children %}
                                    <li>
                                        <a href="#" data-url="{% my_url child.url  %}"><!-- 使用my_url过滤器 -->
                                            {{ child.name }}
                                        </a>
                                    </li>
                                {% endfor %}
                            </ul>
                        </li>
                    {% else %}
                        <li><a href="#" data-url="{% my_url menu.url %}"><!-- 使用my_url过滤器 -->
                            <i class="fa {{ menu.icon }}"></i>
                            <span>{{ menu.name }}</span></a></li>
                    {% endif %}
                {% endfor %}
            </ul>
  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 将 Vue-element-admin 与 Django 后台对接需要进行以下步骤: 1. 编写 Django 后台 API 接口,提供数据交互服务; 2. 在 Vue-element-admin 安装 Axios,用于向后台发送请求; 3. 在 Vue-element-admin 编写与后台 API 对接的代码; 4. 在 Vue-element-admin 使用前端路由(vue-router)跳转到对应页面。 具体操作步骤如下: 1. 编写 Django 后台 API 接口 在 Django 编写后台 API 接口,提供数据交互服务。可以使用 Django REST framework 来快速构建 RESTful API。 2. 安装 Axios 在 Vue-element-admin 安装 Axios,可以使用以下命令: ``` npm install axios ``` 3. 编写与后台 API 对接的代码 在 Vue-element-admin 编写与后台 API 对接的代码,可以使用 Axios 发送请求,例如: ``` import axios from 'axios' export default { // 获取用户列 getUsers() { return axios.get('/api/user/') }, // 创建用户 createUser(user) { return axios.post('/api/user/', user) }, // 删除用户 deleteUser(id) { return axios.delete(`/api/user/${id}/`) } } ``` 4. 使用前端路由跳转到对应页面 在 Vue-element-admin 使用前端路由(vue-router)跳转到对应页面,例如: ``` import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Dashboard', component: () => import('@/views/dashboard/index') }, { path: '/user', name: 'User', component: () => import('@/views/user/index') }, { path: '/user/create', name: 'CreateUser', component: () => import('@/views/user/create') }, { path: '/user/:id/edit', name: 'EditUser', component: () => import('@/views/user/edit') } ] }) ``` 以上就是将 Vue-element-admin 与 Django 后台对接的基本步骤,具体实现还需要根据具体需求进行调整。 ### 回答2: Vue-element-admin是一个基于Vue.js和Element UI开发的后台管理系统框架,而Django是一个基于Python的Web开发框架。对接Vue-element-admin和Django后台,需要以下几个步骤: 1. 了解Vue-element-admin的架构和功能:首先需要熟悉Vue-element-admin的组件和页面结构,了解其提供的基本功能和接口。 2. 安装Django并配置数据库:在Django创建一个新的项目,并配置好数据库信息,确保Django与数据库的连接正常。 3. 创建Django的URL和路由:根据Vue-element-admin的路由规则,在Django创建对应的URL和路由,用于响应前端请求。 4. 编写Django的视图函数:编写Django的视图函数,处理前端请求并返回相应的数据。可以根据需要,操作数据库、调用其他后台服务等。 5. 前后端数据交互:根据Vue-element-admin的接口规范,与Django进行数据交互。可以使用axios库发送请求,同时使用Django的序列化器将数据转化为JSON格式返回给前端。 6. 数据权限控制:在Vue-element-admin,可以根据用户权限动态显示菜单和路由。可以在Django实现相应的权限控制逻辑,根据用户角色或权限限制相应的数据访问。 7. 进行前后端联调和测试:在前后端对接完成后,通过联调和测试确保系统的正常运行。可以使用开发者工具进行调试,在两端之间传递数据并检查返回结果。 总结起来,对接Vue-element-admin和Django后台,需要根据Vue-element-admin的组件和路由规则,在Django进行相应的配置和编码,实现前后端的数据交互和权限控制。通过以上步骤,可以构建一个完整的Vue-element-admin后台管理系统,使用Django作为后台支持。 ### 回答3: vue-element-admin 是一个基于 Vue.js 和 Element UI 的后台管理系统模板。而 Django 是一个使用 Python 编写的高级 Web 开发框架。 对接 vue-element-admin 和 Django 后台的过程,需要进行以下几个步骤: 1. 安装和配置 vue-element-admin 和 Django 工程。 - 首先,在本地搭建好 Django 工程,确保能够正常运行。 - 然后,在 vue-element-admin 的基础上建立 Vue.js 工程,配置好相关依赖。 - 将 Django 与 vue-element-admin 的工程整合在一起,确保两者可以同时运行。 2. 定义接口和数据交互方式。 - 在 Django 的工程,根据需求定义后台接口,用于前后台数据的交互。 - 在 vue-element-admin 的工程,使用 axios 等ajax库,通过 HTTP 请求与 Django 后台进行数据交互。 3. 配置路由和权限控制。 - 在 Django 的工程,配置好路由,将前端请求映射到对应的后端视图函数。 - 在 vue-element-admin 的工程,配置好前端路由,确保前端页面可以正确访问,并实现权限控制策略。 4. 前后台数据的处理和展示。 - 在 vue-element-admin 的工程,通过获取 Django 后台的数据,对数据进行处理和展示。 - 可以使用 vue.js 的组件化开发思想,将数据展示和页面交互的逻辑进行拆分和重复利用。 通过以上步骤的对接,vue-element-admin 和 Django 后台可以实现前后台的数据交互、权限控制和页面展示。以此来构建一个完整的后台管理系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值