通过Pythonのrest_framework&element-plusのCascader实现多级级联记录

需求层面:后端使用Django&rest_framework,前端使用vue3&element-plus,实现分类多级选择。有点儿类似省市区。

models

class Category(models.Model):
    name = models.CharField(verbose_name='XX分类名称', max_length=100, help_text='请输入XX分类名称')
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children',verbose_name='父级分类',)
    create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
    update_time = models.DateTimeField(verbose_name='更新时间', blank=True, null=True)
    order_id = models.IntegerField(verbose_name='排序', default=0)
    is_active = models.BooleanField(default=True, verbose_name='是否可用')

    class Meta:
        db_table = 'category'
        verbose_name = 'XX分类'
        verbose_name_plural = verbose_name
        ordering = ['-order_id']
        unique_together = ('parent', 'name')
    def __str__(self):
        if self.parent:
            return f"{self.parent.name} - {self.name}"
        return self.name
    def save(self, *args, **kwargs):
        # 对象已存在,执行更新操作
        if self.pk is not None:
            self.update_time = datetime.now()
        super().save(*args, **kwargs)

serializers

from .models import Category
from rest_framework import serializers
class CategoryCascaderSerializer(serializers.ModelSerializer):
    # Vue3 Cascader需要的value字段
    value=serializers.CharField(source='id')
    # Vue3 Cascader需要的label字段
    label=serializers.CharField(source='name')
    # 递归处理子分类
    children=serializers.SerializerMethodField()
    class Meta:
         model = TransactionCategory
         fields=['value','label','children']

    def get_children(self, obj):
        # 获取活跃的子分类
        children = obj.children.filter(is_active=True)

        if children.exists():
            # 递归序列化子节点
            serializer = self.__class__(
                children,
                many=True,
                context=self.context
            )
            # 获取序列化后的数据
            data = serializer.data

            # 重要:只移除完全没有children字段的项
            # 保留有children字段但值为None的项(表示该节点没有子节点)
            data = [item for item in data if item]

            # 如果子节点列表不为空,返回数据
            if data:
                return data

        # 无子节点时返回None
        return None

    def to_representation(self, instance):
        # 重写序列化方法,移除空的children字段
        data = super().to_representation(instance)

        # 如果children字段存在但值为None,则移除该字段
        if 'children' in data and data['children'] is None:
            data.pop('children')

        return data

views

from rest_framework.response import Response
from .models import  Category
from .serializers import CategoryCascaderSerializer
from rest_framework import views

class CategoryCascaderView(views.APIView):
    def get(self,request,*args,**kwargs):
        # 获取所有顶级分类并按order_id排序
        top_level_category = Category.objects.filter(parent=None,is_active=True).order_by('-order_id')
        # 序列化顶级分类及其子类        serializer=CategoryCascaderSerializer(top_level_category,many=True)
        return Response(data=serializer.data)

urls


from django.urls import path
from .views import CategoryCascaderView

urlpatterns = [
    path('api/categories/cascader/', CategoryCascaderView.as_view(), name='category-cascader'),
]

使用方法

前端只需发送 GET 请求到/api/categories/cascader/即可获取类似以下格式的数据:

[{
    "value": "1",
    "label": "一级分类001",
    "children": [{
        "value": "7",
        "label": "二级分类01",
        "children": [{
            "value": "11",
            "label": "三级分类01"
          }, {
            "value": "12",
            "label": "三级分类02"
          }, {
            "value": "13",
            "label": "三级分类05"
          }
        ]
      }, {
        "value": "8",
        "label": "二级分类02",
        "children": [{
            "value": "14",
            "label": "三级分类01"
          }, {
            "value": "15",
            "label": "三级分类02"
          }
        ]
      }, {
        "value": "9",
        "label": "二级分类03"
      }, {
        "value": "10",
        "label": "二级分类04"
      }
    ]
  }, {
    "value": "2",
    "label": "一级分类002"
  }, {
    "value": "3",
    "label": "一级分类003"
  }
]

这个 API 可以直接集成到你的 Vue3 应用的 Cascader 组件中:

<el-cascader
  :options="categoryOptions"
  v-model="selectedCategory"
  @change="handleChange"
/>

<script setup>
import { ref, onMounted } from 'vue'

const categoryOptions = ref([])
const selectedCategory = ref([])

const fetchCategories = async () => {
  const res = await fetch('/api/categories/cascader/')
  categoryOptions.value = await res.json()
}

onMounted(fetchCategories)
</script>

知识点补充

SerializerMethodField

SerializerMethodField 是 Django REST Framework (DRF) 提供的一种特殊字段类型,用于在序列化过程中添加非模型字段的数据。它允许你通过定义一个方法来计算并返回自定义值,这个方法的返回值会被包含在序列化结果中。

  • 自定义计算逻辑:可以编写任意逻辑来生成字段值
  • 不直接映射模型字段:用于添加模型中不存在的动态数据
  • 只读字段:仅用于序列化输出,不参与反序列化过程
  • 方法命名约定:默认关联 get_<field_name> 方法
  • 上下文访问:可以通过 self.context 获取请求信息(如当前用户)

在上面的序列化器中,children 字段被定义为 SerializerMethodField,用于递归获取并序列化子分类:

class CategorySerializer(serializers.ModelSerializer):
    # ... 其他字段 ...
    children = serializers.SerializerMethodField()  # 声明一个自定义方法字段

    def get_children(self, obj):
        ...

方法详解:

  • 字段声明children = serializers.SerializerMethodField()
    • 声明一个名为 children 的自定义字段
  • 方法实现def get_children(self, obj)
    • 方法名必须是 get_<字段名> 的格式
    • 参数 obj 是当前正在序列化的模型实例
    • 返回值会被序列化为 children 字段的值

深度限制

如果层级可能非常深,可以添加深度参数控制:

def get_children(self, obj):
    # 获取当前深度(从上下文中传递)
    current_depth = self.context.get('depth', 0)
    max_depth = self.context.get('max_depth', 3)
    
    children = obj.children.filter(is_active=True)
    if children and current_depth < max_depth:
        # 传递递增的深度到子序列化器
        self.context['depth'] = current_depth + 1
        return CategorySerializer(children, many=True, context=self.context).data
    return None

 

THE END
分享
二维码
打赏
海报
通过Pythonのrest_framework&element-plusのCascader实现多级级联记录
需求层面:后端使用Django&rest_framework,前端使用vue3&element-plus,实现分类多级选择。有点儿类似省市区。 models class Category(models.Model)……
<<上一篇
下一篇>>
文章目录
关闭
目 录