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 })
关键改进点
- 数据库层面处理分组:用
TruncMonth在数据库端完成年月分组,避免遍历全量数据,性能大幅提升 - 直接使用年月作为分页参数:前端传递
2024-05格式的参数,逻辑更直观,避免数字页码与年月键不匹配的问题 - 兼容DRF分页接口:生成符合DRF规范的Page对象,确保视图层能正常处理
- 返回可用年月列表:前端可直接拿到所有有数据的年月,方便做月份选择器
Vue端配合调整
- 请求接口时传递
month参数,例如:/api/schedules/?month=2024-05 - 利用返回的
available_months列表渲染月份选择下拉框,支持用户切换月份 - 通过
links.next和links.previous实现上一月/下一月的切换按钮
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

