Angular中如何实现类Windows风格的60天Kendo日期选择器(带滚动)
类Windows风格60天滚动日期选择器实现方案(Angular)
Kendo UI的classic日历视图确实无法直接满足展示60天(上月15天+当月30天+下月15天)和Windows风格滚动的需求,下面提供两种可行的实现思路:
方案一:基于Kendo UI扩展自定义日历视图
通过扩展Kendo的日历视图组件,自定义渲染逻辑来展示指定日期范围,并添加滚动功能:
1. 创建自定义60天视图组件
import { Component, OnInit, Input } from '@angular/core'; import { CalendarViewComponent, CalendarService } from '@progress/kendo-angular-dateinputs'; import { DatePipe } from '@angular/common'; @Component({ selector: 'app-60-day-calendar-view', template: ` <div class="scrollable-calendar" style="max-height: 320px; overflow-y: auto;"> <div class="day-grid"> <button *ngFor="let day of displayDays" class="day-cell" [class.selected]="isDateSelected(day)" (click)="selectDate(day)" > <span class="day-number">{{ day | date:'d' }}</span> <span class="month-tag" *ngIf="!isCurrentMonth(day)">{{ day | date:'MMM' }}</span> </button> </div> </div> `, styles: [` .scrollable-calendar { width: 290px; padding: 8px; } .day-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; } .day-cell { height: 42px; border: none; border-radius: 4px; background-color: #f8f8f8; cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 14px; } .day-cell:hover { background-color: #eaeaea; } .day-cell.selected { background-color: #0078d4; color: #fff; } .month-tag { font-size: 10px; margin-top: 2px; } `] }) export class SixtyDayCalendarViewComponent extends CalendarViewComponent implements OnInit { @Input() value: Date; displayDays: Date[] = []; constructor( private calendarService: CalendarService, private datePipe: DatePipe ) { super(calendarService); } ngOnInit(): void { this.generateDateRange(); } // 生成上月15天+当月30天+下月15天的日期列表 private generateDateRange(): void { const currentDate = new Date(); const currentMonthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); // 添加上月15天 for (let i = 15; i > 0; i--) { const prevDay = new Date(currentMonthStart); prevDay.setDate(currentMonthStart.getDate() - i); this.displayDays.push(prevDay); } // 添加当月天数(最多30天,不足则取实际天数) const currentMonthTotalDays = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0).getDate(); const displayCurrentDays = Math.min(currentMonthTotalDays, 30); for (let i = 1; i <= displayCurrentDays; i++) { this.displayDays.push(new Date(currentDate.getFullYear(), currentDate.getMonth(), i)); } // 添加下月15天 for (let i = 1; i <= 15; i++) { const nextDay = new Date(currentMonthStart); nextDay.setMonth(currentMonthStart.getMonth() + 1); nextDay.setDate(i); this.displayDays.push(nextDay); } } private isDateSelected(day: Date): boolean { if (!this.value) return false; return this.datePipe.transform(day, 'yyyy-MM-dd') === this.datePipe.transform(this.value, 'yyyy-MM-dd'); } private isCurrentMonth(day: Date): boolean { const currentDate = new Date(); return day.getMonth() === currentDate.getMonth() && day.getFullYear() === currentDate.getFullYear(); } private selectDate(day: Date): void { this.calendarService.change.emit(day); this.value = day; } }
2. 在Kendo日期选择器中使用自定义视图
<kendo-datepicker [(value)]="selectedDate"> <ng-template kendoCalendarView let-activeView> <app-60-day-calendar-view *ngIf="activeView === 'month'" [value]="selectedDate" ></app-60-day-calendar-view> </ng-template> </kendo-datepicker>
方案二:纯自定义日期选择器组件
如果不想依赖Kendo UI,可以完全自定义一个类Windows风格的日期选择器,实现滚动和指定日期范围:
import { Component, Input, Output, EventEmitter } from '@angular/core'; import { DatePipe } from '@angular/common'; @Component({ selector: 'app-windows-datepicker', template: ` <div class="datepicker-wrapper"> <input type="text" class="date-input" [value]="selectedDate | date:'MM/dd/yyyy'" (click)="toggleDropdown()" readonly placeholder="Select date" > <div class="calendar-dropdown" *ngIf="showDropdown"> <div class="scrollable-day-list" style="max-height: 300px; overflow-y: auto;"> <button *ngFor="let day of displayDays" class="day-item" [class.active]="isDaySelected(day)" (click)="onDaySelect(day)" > {{ day | date:'EEE, MMM d, yyyy' }} </button> </div> </div> </div> `, styles: [` .datepicker-wrapper { position: relative; display: inline-block; } .date-input { padding: 8px 12px; border: 1px solid #cccccc; border-radius: 4px; font-size: 14px; cursor: pointer; width: 180px; } .calendar-dropdown { position: absolute; top: 100%; left: 0; margin-top: 2px; border: 1px solid #cccccc; border-radius: 4px; background-color: #ffffff; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 260px; z-index: 1000; } .day-item { width: 100%; padding: 8px 16px; text-align: left; border: none; background-color: transparent; cursor: pointer; font-size: 14px; } .day-item:hover { background-color: #f0f0f0; } .day-item.active { background-color: #0078d4; color: #ffffff; } `] }) export class WindowsDatepickerComponent { @Input() selectedDate: Date = new Date(); @Output() selectedDateChange = new EventEmitter<Date>(); showDropdown = false; displayDays: Date[] = []; constructor(private datePipe: DatePipe) { this.buildDateList(); } private buildDateList(): void { const currentDate = new Date(); const currentMonthStart = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); // 上月15天 for (let i = 15; i > 0; i--) { const prevDay = new Date(currentMonthStart); prevDay.setDate(currentMonthStart.getDate() - i); this.displayDays.push(prevDay); } // 当月最多30天 const currentMonthTotalDays = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0).getDate(); const displayCurrentDays = Math.min(currentMonthTotalDays, 30); for (let i = 1; i <= displayCurrentDays; i++) { this.displayDays.push(new Date(currentDate.getFullYear(), currentDate.getMonth(), i)); } // 下月15天 for (let i = 1; i <= 15; i++) { const nextDay = new Date(currentMonthStart); nextDay.setMonth(currentMonthStart.getMonth() + 1); nextDay.setDate(i); this.displayDays.push(nextDay); } } private isDaySelected(day: Date): boolean { return this.datePipe.transform(day, 'yyyy-MM-dd') === this.datePipe.transform(this.selectedDate, 'yyyy-MM-dd'); } onDaySelect(day: Date): void { this.selectedDate = day; this.selectedDateChange.emit(day); this.showDropdown = false; } toggleDropdown(): void { this.showDropdown = !this.showDropdown; } }
使用自定义组件
<app-windows-datepicker [(selectedDate)]="selectedDate"></app-windows-datepicker>
注意事项
- 日期范围计算可根据需求调整,比如当月天数超过30时是否显示全部,或调整上月/下月的天数
- 滚动条样式可以通过CSS自定义,模拟Windows系统的滚动效果
- 若需要支持年份/月份切换,可在组件中添加切换按钮并扩展日期生成逻辑
内容的提问来源于stack exchange,提问作者Jaka rebel
相关产品推荐
相关产品推荐

