本文将详细介绍如何使用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
中:
INSTALLED_APPS = [
...
'myapp',
'rest_framework',
]
2. 创建菜单模型
在myapp/models.py
中定义菜单模型:
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
中定义:
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查询内容的请求:
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
中定义路由:
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}nn`;
});
$('#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}nn`;
});
$('#content').text(content.trim());
});
} else {
alert('请选择至少一个节点');
}
});
});
</script>
</body>
</html>
四、总结
通过本教程,我们实现了一个使用Django、RestFul API和Bootstrap的多级菜单功能,并且在菜单末端节点上添加了复选框,点击按钮时可以获取选中的节点ID,并查询其内容。
关键步骤总结:
- 后端实现:
- 创建Django项目和应用。
- 定义菜单模型,并创建序列化器。
- 创建视图和路由,处理菜单数据和根据ID查询内容的请求。
- 前端实现:
- 引入必要的CSS和JavaScript文件。
- 通过Ajax请求从后端获取菜单数据,并初始化树视图。
- 在叶子节点的文本中添加复选框。
- 实现按钮点击事件,获取选中的节点ID,并查询内容。
后续扩展:
在本教程的基础上,你可以进一步扩展和优化以下功能:
- 为菜单项添加更多自定义图标和样式。
- 实现更多复杂的查询条件和过滤功能。
- 优化前端界面的用户体验。
通过这些扩展,你可以根据具体需求来调整和优化你的项目,构建一个功能更强大、用户体验更佳的多级菜单系统。
我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!