前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Django、RestFul API和Bootstrap实现可折叠的多级菜单功能

使用Django、RestFul API和Bootstrap实现可折叠的多级菜单功能

原创
作者头像
IT蜗壳-Tango
发布2024-07-06 22:58:14
200
代码可运行
发布2024-07-06 22:58:14
举报
运行总次数:0
代码可运行

本文将详细介绍如何使用Django、RestFul API和Bootstrap实现一个可折叠的多级菜单功能,并在菜单末端节点上添加复选框,点击按钮时获取这些节点的ID并查询其内容。这篇教程将涵盖后端的API设计、前端的实现以及如何整合两者,以实现所需的功能。

一、环境准备

在开始之前,请确保已经安装并配置好以下环境:

  • Python 3.x
  • Django
  • Django Rest Framework
  • Bootstrap 4.x

二、后端实现

首先,我们需要在Django中创建一个简单的菜单模型,用于存储菜单的层级结构和内容信息。然后,我们将创建一个API端点来返回菜单数据,并处理根据多个ID查询内容的请求。

1. 创建Django项目和应用

如果还没有创建Django项目,可以使用以下命令创建:

代码语言:bash
复制
django-admin startproject myproject
cd myproject
python manage.py startapp myapp

settings.py中,添加新创建的应用到INSTALLED_APPS中:

代码语言:python
代码运行次数:0
复制
INSTALLED_APPS = [
    ...
    'myapp',
    'rest_framework',
]

2. 创建菜单模型

myapp/models.py中定义菜单模型:

代码语言:python
代码运行次数:0
复制
from django.db import models

class Menu(models.Model):
    title = models.CharField(max_length=100)
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
    content = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.title

这个模型包含了title(菜单项的标题)、parent(父菜单项)和content(菜单项的内容)。

3. 创建序列化器

为了将菜单模型序列化为JSON格式,我们需要创建一个序列化器。在myapp/serializers.py中定义:

代码语言:python
代码运行次数:0
复制
from rest_framework import serializers
from .models import Menu

class MenuSerializer(serializers.ModelSerializer):
    class Meta:
        model = Menu
        fields = ('id', 'title', 'parent', 'content')

4. 创建视图和路由

myapp/views.py中创建视图,处理菜单列表和根据ID查询内容的请求:

代码语言:python
代码运行次数:0
复制
from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.decorators import api_view
from .models import Menu
from .serializers import MenuSerializer

class MenuListCreate(generics.ListCreateAPIView):
    queryset = Menu.objects.all()
    serializer_class = MenuSerializer

@api_view(['GET'])
def get_contents_by_ids(request):
    ids = request.query_params.get('ids')
    if ids:
        ids_list = ids.split(',')
        menus = Menu.objects.filter(id__in=ids_list)
        serializer = MenuSerializer(menus, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    return Response({"error": "No IDs provided"}, status=status.HTTP_400_BAD_REQUEST)

myapp/urls.py中定义路由:

代码语言:python
代码运行次数:0
复制
from django.urls import path
from .views import MenuListCreate, get_contents_by_ids

urlpatterns = [
    path('menus/', MenuListCreate.as_view(), name='menu-list-create'),
    path('menus/contents/', get_contents_by_ids, name='menu-contents-by-ids'),
]

这样,我们就完成了后端的API设计。接下来,我们将实现前端部分。

三、前端实现

前端部分将使用Bootstrap和jQuery来创建可折叠的多级菜单,并在末端节点添加复选框,点击按钮时获取这些节点的ID并查询其内容。

1. 引入必要的CSS和JavaScript文件

在HTML文件中,引入Bootstrap和jQuery:

代码语言:html
复制
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>bstreeview</title>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"
          integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
    <link href="css/bstreeview.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <div class="content">
            <div class="row">
                <div class="col-md-6 pt-5">
                    <div id="tree"></div>
                    <button id="getSelected" class="btn btn-primary mt-3">获取选中的节点ID</button>
                </div>
                <div class="col-md-6 pt-5">
                    <div id="content"></div>
                </div>
            </div>
        </div>
    </div>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"
            integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
            integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
            crossorigin="anonymous"></script>
    <script src="js/bstreeview.js"></script>
</body>
</html>

2. 加载菜单数据并初始化树视图

在页面加载完成后,通过Ajax请求从后端获取菜单数据,并初始化树视图:

代码语言:javascript
复制
$(function () {
    function transformMenuData(menuData) {
        let tree = [];
        // 创建所有父节点
        menuData.forEach(item => {
            if (!item.parent) {
                tree.push({
                    id: item.id,
                    text: item.title,
                    icon: "",
                    nodes: []
                });
            }
        });

        // 将子节点添加到父节点中
        menuData.forEach(item => {
            if (item.parent) {
                addNodeToParent(tree, item);
            }
        });

        // 移除叶子节点的空nodes数组
        removeEmptyNodes(tree);

        return tree;
    }

    function addNodeToParent(tree, item) {
        for (let i = 0; i < tree.length; i++) {
            if (tree[i].id === item.parent) {
                if (!tree[i].nodes) {
                    tree[i].nodes = [];
                }
                tree[i].nodes.push({
                    id: item.id,
                    text: item.title,
                    icon: "",
                    nodes: []
                });
                return;
            }
            if (tree[i].nodes && tree[i].nodes.length > 0) {
                addNodeToParent(tree[i].nodes, item);
            }
        }
    }

    function removeEmptyNodes(tree) {
        tree.forEach(node => {
            if (node.nodes && node.nodes.length === 0) {
                node.icon = "bx bx-book"; // 叶子节点添加图标
                node.text = `<input type="checkbox" class="menu-checkbox" data-id="${node.id}"> ${node.text}`;
                delete node.nodes;
            } else if (node.nodes) {
                removeEmptyNodes(node.nodes);
            }
        });
    }

    $.get('/api/menus/', function(data) {
        let transformedData = transformMenuData(data);
        console.info(transformedData);
        $('#tree').bstreeview({
            data: transformedData,
            expandIcon: 'bx bx-minus',
            collapseIcon: 'bx bx-plus',
            indent: 1.25,
            parentsMarginLeft: '1.25rem',
            openNodeLinkOnNewTab: true
        });
    });

    $('#getSelected').on('click', function() {
        let selectedIds = [];
        $('.menu-checkbox:checked').each(function() {
            selectedIds.push($(this).data('id'));
        });

        if (selectedIds.length > 0) {
            $.get(`/api/menus/contents/?ids=${selectedIds.join(',')}`, function(data) {
                let content = "";
                data.forEach((item, index) => {
                    content += `問題${index + 1}\n${item.content}\n\n`;
                });
                $('#content').text(content.trim());
            });
        } else {
            alert('请选择至少一个节点');
        }
    });
});

3. 添加复选框和按钮功能

在叶子节点的文本中添加复选框,并在按钮点击时获取选中的节点ID,发送请求到后端获取内容数据,并在页面上显示。

前端代码

在前面的代码基础上,我们已经在叶子节点上添加了复选框,同时实现了按钮点击时获取选中的节点ID。下面是完整的前端代码:

代码语言:html
复制
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>bstreeview</title>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"
          integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
    <link href="css/bstreeview.css" rel="stylesheet">
</head>
<body>
    <div class="container">
        <div class="content">
            <div class="row">
                <div class="col-md-6 pt-5">
                    <div id="tree"></div>
                    <button id="getSelected" class="btn btn-primary mt-3">获取选中的节点ID</button>
                </div>
                <div class="col-md-6 pt-5">
                    <div id="content"></div>
                </div>
            </div>
        </div>
    </div>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"
            integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
            integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
            crossorigin="anonymous"></script>
    <script src="js/bstreeview.js"></script>
    <script>
        $(function () {
            function transformMenuData(menuData) {
                let tree = [];
                // 创建所有父节点
                menuData.forEach(item => {
                    if (!item.parent) {
                        tree.push({
                            id: item.id,
                            text: item.title,
                            icon: "",
                            nodes: []
                        });
                    }
                });

                // 将子节点添加到父节点中
                menuData.forEach(item => {
                    if (item.parent) {
                        addNodeToParent(tree, item);
                    }
                });

                // 移除叶子节点的空nodes数组
                removeEmptyNodes(tree);

                return tree;
            }

            function addNodeToParent(tree, item) {
                for (let i = 0; i < tree.length; i++) {
                    if (tree[i].id === item.parent) {
                        if (!tree[i].nodes) {
                            tree[i].nodes = [];
                        }
                        tree[i].nodes.push({
                            id: item.id,
                            text: item.title,
                            icon: "",
                            nodes: []
                        });
                        return;
                    }
                    if (tree[i].nodes && tree[i].nodes.length > 0) {
                        addNodeToParent(tree[i].nodes, item);
                    }
                }
            }

            function removeEmptyNodes(tree) {
                tree.forEach(node => {
                    if (node.nodes && node.nodes.length === 0) {
                        node.icon = "bx bx-book"; // 叶子节点添加图标
                        node.text = `<input type="checkbox" class="menu-checkbox" data-id="${node.id}"> ${node.text}`;
                        delete node.nodes;
                    } else if (node.nodes) {
                        removeEmptyNodes(node.nodes);
                    }
                });
            }

            $.get('/api/menus/', function(data) {
                let transformedData = transformMenuData(data);
                console.info(transformedData);
                $('#tree').bstreeview({
                    data: transformedData,
                    expandIcon: 'bx bx-minus',
                    collapseIcon: 'bx bx-plus',
                    indent: 1.25,
                    parentsMarginLeft: '1.25rem',
                    openNodeLinkOnNewTab: true
                });
            });

            $('#getSelected').on('click', function() {
                let selectedIds = [];
                $('.menu-checkbox:checked').each(function() {
                    selectedIds.push($(this).data('id'));
                });

                if (selectedIds.length > 0) {
                    $.get(`/api/menus/contents/?ids=${selectedIds.join(',')}`, function(data) {
                        let content = "";
                        data.forEach((item, index) => {
                            content += `問題${index + 1}\n${item.content}\n\n`;
                        });
                        $('#content').text(content.trim());
                    });
                } else {
                    alert('请选择至少一个节点');
                }
            });
        });
    </script>
</body>
</html>

四、总结

通过本教程,我们实现了一个使用Django、RestFul API和Bootstrap的多级菜单功能,并且在菜单末端节点上添加了复选框,点击按钮时可以获取选中的节点ID,并查询其内容。

关键步骤总结:

  1. 后端实现
    • 创建Django项目和应用。
    • 定义菜单模型,并创建序列化器。
    • 创建视图和路由,处理菜单数据和根据ID查询内容的请求。
  2. 前端实现
    • 引入必要的CSS和JavaScript文件。
    • 通过Ajax请求从后端获取菜单数据,并初始化树视图。
    • 在叶子节点的文本中添加复选框。
    • 实现按钮点击事件,获取选中的节点ID,并查询内容。

后续扩展:

在本教程的基础上,你可以进一步扩展和优化以下功能:

  • 为菜单项添加更多自定义图标和样式。
  • 实现更多复杂的查询条件和过滤功能。
  • 优化前端界面的用户体验。

通过这些扩展,你可以根据具体需求来调整和优化你的项目,构建一个功能更强大、用户体验更佳的多级菜单系统。


我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、环境准备
  • 二、后端实现
    • 1. 创建Django项目和应用
      • 2. 创建菜单模型
        • 3. 创建序列化器
          • 4. 创建视图和路由
          • 三、前端实现
            • 1. 引入必要的CSS和JavaScript文件
              • 2. 加载菜单数据并初始化树视图
                • 3. 添加复选框和按钮功能
                  • 前端代码
              • 四、总结
                • 关键步骤总结:
                  • 后续扩展:
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档