You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue.js+Django项目按月份分页实现及现有方案故障排查

按年月实现排班记录分页(Django + Vue.js)

我正在开发一个基于Django和Vue.js的项目,需要实现按年月对排班记录进行分页——即2024年5月的记录与6月的记录分属不同页面,无需考虑每月的记录数量。

已编写Vue.js排班数据展示组件:

<div class="row">
  <div class="col-xl-4 col-sm-6 mb-3" v-for="object in personWorkingScheduleData" :key="object.id">
   <div class="card mb-5 h-100" style="border:1px solid  #eff2f7;  padding: 12px; height: 300px;">
     <div class="card-body">
       <div class="row">
         <div class="col-lg-3">
           <div class="text-lg-center">
             <div class="mt-3">
               <label>Night time</label>
               <b-form-checkbox :checked="object.is_night_shift" @input="object.is_night_shift = $event"
              :disabled="!object.can_change" class="mb-3 custom-checkbox" plain></b-form-checkbox>
             </div>
          </div>
        </div>
      <div class="col-lg-9">
        <div>
          <div class="mt-3">
            <label>Days</label>
            <multiselect :disabled="!object.can_change" required :options="weekdayOptions"
              :value="extractWeekdays(object.weekdays)" track-by="id" label="weekday" multiple
              placeholder="Choose days" selectLabel="Choose day" selectedLabel="selected"
               :max-height="200" @input="handleMultiselectChange($event, object)">
            </multiselect>
          </div>
          <div>
            <ul class="list-inline mb-0">
              <li class="list-inline-item me-3">
                <h5
                      class="font-size-14"
                      data-toggle="tooltip"
                      data-placement="top"
                      title="From"
                    >
                    <label class="mt-3">From*</label><br>
                    <i class="bx bx-calendar me-1 text-muted">
                      <b-form-input class="no-border" :value="object.start_date" :disabled="!object.can_change" @input="object.start_date = $event"
                      id="example-input" type="date" placeholder="YYYY-MM-DD" autocomplete="off"></b-form-input>
                    </i>
                  </h5>
                  
              </li>
              <li class="list-inline-item me-3">
                <h5
                      class="font-size-14"
                      data-toggle="tooltip"
                      data-placement="top"
                      title="To"
                    >
                    <label class="mt-3">To*</label><br>
                    <i class="bx bx-calendar me-1 text-muted">
                      <b-form-input class="no-border" :value="object.end_date" :disabled="!object.can_change" @input="object.end_date = $event"
                      id="example-input" type="date" placeholder="YYYY-MM-DD" autocomplete="off"></b-form-input>
                    </i>
                  </h5>
              </li>
            </ul>
          </div>
          <ul class="list-inline mb-0" v-if="object.start_time && object.end_time">
            <li class="list-inline-item me-3">
              <h5
                    class="font-size-14"
                    data-toggle="tooltip"
                    data-placement="top"
                    title="From"
                  >
                  <label>Start time</label>
                  <br />
                  <i class="bx bx-time me-1 text-muted">
                    <b-form-input class="no-border" :value="object.start_time" :disabled="!object.can_change" @input="object.start_time = $event"
                    format="HH:mm" value-type="format" type="time" placeholder="hh:mm"></b-form-input>
                  </i></h5>
            </li>
            <li class="list-inline-item me-3">
              <h5
                    class="font-size-14"
                    data-toggle="tooltip"
                    data-placement="top"
                    title="From"
                  >
                  <label>End time</label>
                <br />
                <i class="bx bx-time me-1 text-muted">
                  <b-form-input class="no-border" :value="object.end_time" :disabled="!object.can_change" @input="object.end_time = $event"
                  format="HH:mm" value-type="format" type="time" placeholder="hh:mm"></b-form-input>
                </i></h5>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</div>
</div>
</div>

我自己写了一个Django月度分页类,但无法正常工作,请问如何正确实现按月份分页?

原分页类代码:

class MonthlyPagination(PageNumberPagination):
page_size = 10

def paginate_queryset(self, queryset, request, view=None):
    self.months = {}
    for item in queryset:
        month_year = item.start_date.strftime("%Y-%m")
        if month_year not in self.months:
            self.months[month_year] = [item]
        else:
            self.months[month_year].append(item)

    page_number = request.query_params.get(self.page_query_param, 1)
    self.page = page_number
    return self.months.get(page_number, [])

def get_paginated_response(self, data):
    next_page_number = int(self.page) + 1 if int(self.page) < len(self.months) else None
    prev_page_number = int(self.page) - 1 if int(self.page) > 1 else None

    return Response({
        'links': {
            'next': next_page_number,
            'previous': prev_page_number
        },
        'count': sum(len(month) for month in self.months.values()),
        'total_pages': len(self.months),
        'current_page': int(self.page),
        'results': data  
    })

问题分析与修正方案

原分页类的核心问题:

  • 遍历整个queryset分组,数据量大时性能极差
  • 用数字页码匹配%Y-%m格式的年月键,完全不匹配,无法正确获取对应月份数据
  • 分页逻辑不符合Django分页类规范,返回值类型不兼容

修正后的Django分页实现

from django.core.paginator import Paginator, Page
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from django.db.models.functions import TruncMonth

class MonthlyPagination(PageNumberPagination):
    # 自定义查询参数,直接接收年月格式(如2024-05)
    page_query_param = 'month'

    def paginate_queryset(self, queryset, request, view=None):
        # 从数据库层面获取所有有数据的年月,按时间倒序排列
        month_list = queryset.annotate(
            month=TruncMonth('start_date')
        ).values_list('month', flat=True).distinct().order_by('-month')
        
        # 转换为%Y-%m格式的字符串列表
        self.month_strings = [month.strftime("%Y-%m") for month in month_list] if month_list else []
        
        # 获取当前请求的年月,默认取最新的月份
        current_month = request.query_params.get(self.page_query_param, self.month_strings[0] if self.month_strings else None)
        
        # 过滤出当前年月的所有记录
        filtered_queryset = queryset.none()
        if current_month and '-' in current_month:
            year, month = map(int, current_month.split('-'))
            filtered_queryset = queryset.filter(
                start_date__year=year,
                start_date__month=month
            )
        
        # 生成符合DRF规范的Page对象,保证视图层兼容
        self.page = Page(filtered_queryset, 1, Paginator(filtered_queryset, len(filtered_queryset)))
        self.request = request
        return list(filtered_queryset)

    def get_paginated_response(self, data):
        current_month = self.request.query_params.get(self.page_query_param, self.month_strings[0] if self.month_strings else '')
        current_idx = self.month_strings.index(current_month) if current_month in self.month_strings else -1
        
        # 计算总记录数
        total_count = 0
        for month_str in self.month_strings:
            year, month = map(int, month_str.split('-'))
            total_count += self.page.paginator.object_list.model.objects.filter(
                start_date__year=year, start_date__month=month
            ).count()

        return Response({
            'links': {
                'next': self.month_strings[current_idx - 1] if current_idx > 0 else None,
                'previous': self.month_strings[current_idx + 1] if current_idx < len(self.month_strings)-1 else None
            },
            'count': total_count,
            'total_pages': len(self.month_strings),
            'current_month': current_month,
            'available_months': self.month_strings,
            'results': data
        })

关键改进点

  1. 数据库层面处理分组:用TruncMonth在数据库端完成年月分组,避免遍历全量数据,性能大幅提升
  2. 直接使用年月作为分页参数:前端传递2024-05格式的参数,逻辑更直观,避免数字页码与年月键不匹配的问题
  3. 兼容DRF分页接口:生成符合DRF规范的Page对象,确保视图层能正常处理
  4. 返回可用年月列表:前端可直接拿到所有有数据的年月,方便做月份选择器

Vue端配合调整

  1. 请求接口时传递month参数,例如:/api/schedules/?month=2024-05
  2. 利用返回的available_months列表渲染月份选择下拉框,支持用户切换月份
  3. 通过links.next和links.previous实现上一月/下一月的切换按钮

内容的提问来源于stack exchange,提问作者Mike

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 10:15:04