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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 00:55:00