Angular中实现类Chrome按天分组排序的历史记录功能
Angular+NgRx实现Chrome风格按天分组历史记录
实现逻辑核心是将平级的历史记录数组按本地日期维度做聚合,先按时间倒序排列所有记录,再按所属日期拆分分组,最终按「日期分组头+组内记录」的结构渲染即可,具体实现步骤如下:
1 定义分组数据结构
首先补充分组对应的TS类型,约束分组数据格式:
interface HistoryGroup { dateLabel: string; // 分组显示文案,如今天、昨天、2024/5/20 dateTimestamp: number; // 分组日期0点时间戳,用于排序 items: History[]; // 该日期下的所有历史记录 }
2 实现分组排序工具方法
方法会自动处理时区偏差、日期文案生成、排序逻辑,直接复制到组件或公共工具类中即可:
/** * 历史记录按天分组排序 * @param histories 原始平级历史记录数组 */ groupHistoriesByDay(histories: History[]): HistoryGroup[] { if (!histories?.length) return []; const groupMap = new Map<string, HistoryGroup>(); // 所有记录先按访问时间倒序:最新访问的排在最前 const sortedHistories = [...histories].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); // 计算今天、昨天的0点时间戳,用于特殊日期文案判断 const now = new Date(); const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); const yesterdayStart = todayStart - 24 * 60 * 60 * 1000; sortedHistories.forEach(item => { const itemDate = new Date(item.date); // 取记录对应本地日期的0点时间戳作为分组键,避免UTC时区差导致的日期分组错误 const itemDayStart = new Date(itemDate.getFullYear(), itemDate.getMonth(), itemDate.getDate()).getTime(); // 生成分组显示文案 let dateLabel: string; if (itemDayStart === todayStart) { dateLabel = '今天'; } else if (itemDayStart === yesterdayStart) { dateLabel = '昨天'; } else { dateLabel = itemDate.toLocaleDateString(); } // 分组不存在则初始化 if (!groupMap.has(String(itemDayStart))) { groupMap.set(String(itemDayStart), { dateLabel, dateTimestamp: itemDayStart, items: [] }); } // 记录归入对应分组 groupMap.get(String(itemDayStart))!.items.push(item); }); // 分组按日期从新到旧排序后返回 return Array.from(groupMap.values()).sort((a, b) => b.dateTimestamp - a.dateTimestamp); }
3 组件内对接数据流
在原有组件逻辑基础上,新增经过分组处理的可观察流,利用RxJS的map操作符自动响应历史数据变更,无需手动订阅:
// 分组后的历史记录流 historyGroups$: Observable<HistoryGroup[]> = this.Histories$.pipe( map(histories => this.groupHistoriesByDay(histories)) );
注意:你原有
onAddHistory方法存在变量名笔误,传入store的histo变量未定义,需要替换为方法内声明的history实例,否则新增记录逻辑会报错。
如果项目中多个地方需要用到分组后的历史数据,可以把分组逻辑封装到NgRx的selector中,符合全局状态管理的复用规范。
4 修改模板渲染分组结构
替换原有直接循环平级记录的模板逻辑,改为两层循环:先遍历日期分组渲染日期头,再遍历组内记录渲染条目,结构和Chrome历史页完全一致:
<!-- 空状态提示 --> <div mat-subheader *ngIf="(historyGroups$ | async)?.length === 0">The documents you visit will show here</div> <!-- 遍历所有日期分组 --> <ng-container *ngFor="let group of historyGroups$ | async"> <!-- 日期分组标题 --> <div mat-subheader class="history-date-header">{{ group.dateLabel }}</div> <!-- 遍历当前分组下的历史记录 --> <mat-list-item *ngFor="let history of group.items" class="historyClicking" (mouseover)="history.showTrash=true" (mouseout)="history.showTrash=false" (click)="onclick()" > <div class="block"> <div matList>{{history.id}}</div> <!-- 可在此处补充favicon、页面标题、url、删除按钮等元素 --> </div> </mat-list-item> </ng-container>
可选优化
- 如果需要更丰富的相对日期文案(如「上周二」「3天前」),直接修改
dateLabel的生成逻辑即可 - 可以给日期头加对应样式,和Chrome历史页的标题字号、间距保持一致
- 组内记录如果需要二次排序(比如相同时间的按访问频次排序),直接在
sortedHistories的排序逻辑里加判断条件即可
内容的提问来源于stack exchange,提问作者Picolo
相关产品推荐
相关产品推荐

