如何按年、月、日嵌套分组MongoDB集合数据?含Angular方案
按年-月-日嵌套分组实现时间线视图方案
方案一:MongoDB聚合管道实现
通过多层$group聚合操作,从最细的日期粒度逐步向上嵌套,同时将数字月份转换为英文缩写,最终得到符合要求的层级结构。
完整聚合代码:
db.collection.aggregate([ // 1. 按年、月、日分组,收集当日所有文档 { $group: { _id: { year: "$dShow.year", month: "$dShow.month", day: "$dShow.day" }, dayList: { $push: { dShow: "$dShow", title: "$title", // 按需添加其他需要的字段:_id, forever, text等 } } } }, // 2. 按年、月分组,嵌套日期分组数据 { $group: { _id: { year: "$_id.year", month: "$_id.month" }, days: { $push: { day: "$_id.day", list: "$dayList" } } } }, // 3. 按年分组,嵌套月份分组数据,同时转换数字月份为英文缩写 { $group: { _id: "$_id.year", months: { $push: { month: { $dateToString: { format: "%b", date: { $dateFromParts: { year: "$_id.year", month: "$_id.month", day: 1 } } } }, list: "$days" } } } }, // 4. 调整输出结构,匹配期望格式 { $project: { _id: 0, year: "$_id", list: "$months" } }, // 可选:按年份升序排序 { $sort: { year: 1 } } ])
步骤说明
- 第一层
$group:将同一天的文档归为一组,收集所有需要展示的字段。 - 第二层
$group:将同一月份下的日期组嵌套整合。 - 第三层
$group:将同一年份下的月份组嵌套整合,通过$dateFromParts将年月转为日期对象,再用$dateToString生成三位英文月份缩写。 - 最后通过
$project调整输出字段名,去掉多余的_id。
方案二:Angular自定义管道实现
如果不需要后端处理,可在前端通过自定义管道对原始数据进行分组,适合数据量较小或需要灵活调整分组逻辑的场景。
1. 创建自定义管道
import { Pipe, PipeTransform } from '@angular/core'; // 定义数据结构,按需扩展字段 interface TimelineItem { dShow: { year: number; month: number; day: number }; title: string; } interface DayGroup { day: number; list: TimelineItem[]; } interface MonthGroup { month: string; list: DayGroup[]; } interface YearGroup { year: number; list: MonthGroup[]; } @Pipe({ name: 'nestedDateGroup' }) export class NestedDateGroupPipe implements PipeTransform { // 月份英文缩写映射 private monthAbbrs = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec']; transform(items: TimelineItem[]): YearGroup[] { if (!items?.length) return []; // 按年份分组 const yearMap = items.reduce((map, item) => { const year = item.dShow.year; if (!map[year]) map[year] = []; map[year].push(item); return map; }, {} as Record<number, TimelineItem[]>); // 转换为年份分组数组并排序 return Object.entries(yearMap) .map(([yearStr, yearItems]) => { const year = parseInt(yearStr); // 按月份分组 const monthMap = yearItems.reduce((map, item) => { const month = item.dShow.month; if (!map[month]) map[month] = []; map[month].push(item); return map; }, {} as Record<number, TimelineItem[]>); // 转换为月份分组数组并排序 const monthList = Object.entries(monthMap) .map(([monthStr, monthItems]) => { const monthIndex = parseInt(monthStr) - 1; // 按日期分组 const dayMap = monthItems.reduce((map, item) => { const day = item.dShow.day; if (!map[day]) map[day] = []; map[day].push(item); return map; }, {} as Record<number, TimelineItem[]>); // 转换为日期分组数组并排序 const dayList = Object.entries(dayMap) .map(([dayStr, dayItems]) => ({ day: parseInt(dayStr), list: dayItems })) .sort((a, b) => a.day - b.day); return { month: this.monthAbbrs[monthIndex], list: dayList }; }) .sort((a, b) => this.monthAbbrs.indexOf(a.month) - this.monthAbbrs.indexOf(b.month)); return { year, list: monthList }; }) .sort((a, b) => a.year - b.year); } }
2. 使用管道
首先在你的Angular模块中声明该管道:
import { NgModule } from '@angular/core'; import { NestedDateGroupPipe } from './nested-date-group.pipe'; @NgModule({ declarations: [NestedDateGroupPipe], exports: [NestedDateGroupPipe] }) export class PipesModule {}
然后在模板中直接使用:
<div *ngFor="let yearGroup of timelineData | nestedDateGroup"> <h3>{{ yearGroup.year }}</h3> <div *ngFor="let monthGroup of yearGroup.list"> <h4>{{ monthGroup.month }}</h4> <div *ngFor="let dayGroup of monthGroup.list"> <h5>Day {{ dayGroup.day }}</h5> <div *ngFor="let item of dayGroup.list"> <p>{{ item.title }}</p> </div> </div> </div> </div>
方案对比
- MongoDB聚合:适合数据量大的场景,减少前端数据传输量和处理压力,性能更优。
- Angular管道:无需修改后端逻辑,前端灵活调整分组规则,适合数据量较小或需要动态调整视图的场景。
内容的提问来源于stack exchange,提问作者DValdir Martins
相关产品推荐
相关产品推荐

