效果图预览
20250124-113957
使用说明
1.选择日期左右箭头,实现每月日历切换,示例中超出当前月份,禁止进入下一月,可在代码更改
2.日历中显示当前选择的日期,选中的日期颜色可自定义
3.日历中可展示历史记录作为数据存储标志
4.当前页面选择的日期可在本页面保存状态
可根据自己需求,对日历组件进行更改,将代码拷贝到DevEco Studio 可直接运行使用
日历组件使用代码
TypeScript
@Entry
@Component
struct TEST {
@State currentSelectDate: string = calendarUtils.getCurrentDay(); // 当前读取数据日期,用户数据获取
private historyDateArray: string[] =
[calendarUtils.getPreviousDay(calendarUtils.getCurrentDay()), calendarUtils.getCurrentDay()];
private calendarController: CustomDialogController | null = new CustomDialogController({
builder: CustomCalendar({
currentSelectDate: this.currentSelectDate,
defaultSelectDates: this.historyDateArray,
colors: ["#fed2cf", "#ed553c", "#f74d33", "#fcfaff"],
cancel: () => {
this.calendarController?.close();
},
confirm: (date: Date) => {
this.selectCalendarConfirm(date)
}
}),
autoCancel: true,
alignment: DialogAlignment.BottomEnd,
offset: { dx: -8, dy: -20 },
gridCount: 4,
showInSubWindow: true,
width: 359,
isModal: true,
customStyle: false,
cornerRadius: 16,
})
// 选择日历确认框
selectCalendarConfirm(date: Date) {
let dateTime = new Date(date);
let year = dateTime.getFullYear();
let month = dateTime.getMonth() + 1;
let day = dateTime.getDate();
let strDate = `${year}-${month}-${day}`;
this.currentSelectDate = strDate;
let selectIndex = calendarUtils.getDaysFromDate(strDate);
this.calendarController?.close();
}
build() {
Column() {
Button("日历组件").onClick(() => {
this.calendarController?.open();
})
}.width('100%').height('100%')
}
}
日历组件代码
TypeScript
import { calendarUtils } from "../../utlis/calendarUtils";
interface monthType {
defaultData: boolean,
value: number
}
// 日历
@CustomDialog
export struct CustomCalendar {
controller?: CustomDialogController
@State selectedDate: Date = new Date();
@State isDateSelected: boolean = false;
@State currentMonth: number = new Date().getMonth(); // 当前选择的月数
@State defaultMonth: number = new Date().getMonth(); // 默认月数
@State currentYear: number = new Date().getFullYear();
@Prop defaultSelectDates: string[] = []; // 默认历史数据
@State defaultYear: number = new Date().getFullYear();
@Prop currentSelectDate: string = calendarUtils.getCurrentDay();
@Prop colors: string[] = ["#fed2cf", "#ed553c", "#f74d33", "#fcfaff"]; // 0 默认背景 1 背景默认字体颜色 2 选中背景 3 选中字体颜色
@State monthDays: monthType[] = []; // 月份天数
cancel: () => void = () => {
}
confirm: (date: Date) => void = () => {
}
aboutToAppear() {
console.log("currentSelectDate===>", this.currentSelectDate)
this.onDefaultDataSelect(this.currentSelectDate);
let days = this.getDaysInMonth();
console.log("days===>", JSON.stringify(days))
this.monthDays = [...days]
}
build() {
Column() {
Row({ space: 30 }) {
Image($r('app.media.rightArrow'))
.width('24vp')
.height('24vp')
.padding({
left: 8,
right: 8,
top: 4,
bottom: 4
})
.rotate({
x: 0,
y: 0,
z: 90,
centerX: '50%',
centerY: '50%',
angle: 180
})
.onClick(() => this.onMonthChange(-1))
Text(`${this.currentYear}年${this.currentMonth + 1 >= 10 ? this.currentMonth + 1 :
'0' + (this.currentMonth + 1)}月`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ left: 15, right: 15 })
if (this.defaultYear != this.currentYear || this.currentMonth != this.defaultMonth) {
Image($r('app.media.rightArrow'))
.width('24vp')
.height('24vp')
.padding({
left: 8,
right: 8,
top: 4,
bottom: 4
})
.onClick(() => this.onMonthChange(1))
} else {
Image($r('app.media.rightArrowGray'))
.width('24vp')
.height('24vp')
.padding({
left: 8,
right: 8,
top: 4,
bottom: 4
})
}
}
.width('100%')
.justifyContent(FlexAlign.Center)
.margin({ top: 20, bottom: 30 })
// 星期标题
Row() {
ForEach(['日', '一', '二', '三', '四', '五', '六'], (day: string) => {
Text(day)
.textAlign(TextAlign.Center)
.fontSize(18)
.fontColor('#999999')
}, (day: string) => day)
}
.width('93%')
.margin({ bottom: 10 })
.justifyContent(FlexAlign.SpaceBetween)
Grid() {
ForEach(this.monthDays, (item: monthType, index: number) => {
GridItem() {
Column() {
Text(item.value.toString())
.fontSize(18)
.fontWeight(this.isSelectedDate(item.value) ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.isSelectedDate(item.value) ? this.colors[3] :
item.defaultData ? this.colors[1] : this.getDateColor(item.value))
}
.width('100%')
.height('100%')
.borderRadius(25)
.backgroundColor(this.isSelectedDate(item.value) ? this.colors[2] :
(item.defaultData ? this.colors[0] : Color.Transparent))
.justifyContent(FlexAlign.Center)
}
.aspectRatio(1)
.onClick(() => this.onDateSelected(item.value))
})
}
.width('100%')
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
.rowsGap(8)
.columnsGap(8)
.height('260vp')
// 按钮
Row() {
Button('取消', { type: ButtonType.Normal })
.width('140vp')
.height('50vp')
.backgroundColor("#f6f6f6")
.fontColor("#171d29")
.borderRadius(12)
.onClick(() => {
this.cancel();
})
Button('确定', { type: ButtonType.Normal })
.width('140vp')
.height('50vp')
.backgroundColor("#171d29")
.borderRadius(12)
.onClick(() => {
console.log("this.selectedDate==>", this.selectedDate);
this.confirm(this.selectedDate)
console.log("this.monthDays=>", JSON.stringify(this.monthDays))
})
}
.width('100%')
.height('50vp')
.justifyContent(FlexAlign.SpaceBetween)
.padding({ left: 18, right: 18 })
.margin({ bottom: 10 });
}
.width('100%')
.padding({
left: 16,
right: 16,
top: 16,
bottom: 16
})
.backgroundColor('#ffffff')
}
private onMonthChange(increment: number) {
let newMonth = this.currentMonth + increment
let newYear = this.currentYear
if (newMonth > 11) {
newMonth = 0
newYear++
} else if (newMonth < 0) {
newMonth = 11
newYear--
}
this.currentMonth = newMonth;
this.currentYear = newYear;
let result = this.getDaysInMonth();
this.monthDays = [...result];
}
private onDateSelected(day: number) {
const newSelectedDate = new Date(this.currentYear, this.currentMonth, day);
const currentDate = new Date(this.currentYear, this.currentMonth, day)
const today = new Date()
// 如果点击的值大于今天值,不选中
if (currentDate > today) {
return
}
if (this.isDateSelected &&
this.selectedDate.getDate() === day &&
this.selectedDate.getMonth() === this.currentMonth &&
this.selectedDate.getFullYear() === this.currentYear) {
// 如果点击的是已选中的日期,取消选中
// this.isDateSelected = false
this.isDateSelected = true
} else {
// 否则,选中新的日期
this.selectedDate = newSelectedDate
this.isDateSelected = true
}
}
// 默认选中
private onDefaultDataSelect(value: string) {
let date = value.split("-");
let year = Number(date[0]); // 获取当前年
let month = Number(date[1]) - 1; // 获取当前月,如果需要渲染到页面,需要+1,不渲染默认进行计算
let day = Number(date[2]); // 获取当前天数
this.selectedDate = new Date(year, month, day);
this.isDateSelected = true;
}
private isSelectedDate(day: number): boolean {
return this.isDateSelected &&
this.selectedDate.getDate() === day &&
this.selectedDate.getMonth() === this.currentMonth &&
this.selectedDate.getFullYear() === this.currentYear
}
private getDaysInMonth(): monthType[] {
const daysInMonth = new Date(this.currentYear, this.currentMonth + 1, 0).getDate();
const selectMonth = new Date(this.currentYear, this.currentMonth + 1, 0).getMonth() + 1;
const selectYear = new Date(this.currentYear, this.currentMonth + 1, 0).getFullYear();
let array = Array.from<number, number>({ length: daysInMonth }, (_, i) => i + 1);
let result: monthType[] = [];
for (let i = 0; i < array.length; i++) {
let obj: monthType = {
value: array[i],
defaultData: false,
}
let selectData = this.defaultSelectDates.find((item: string) => {
let date = item.split("-");
let year = date[0];
let month = date[1];
let day = date[2];
if (selectYear == Number(year) && selectMonth == Number(month) && Number(day) == array[i]) {
return item
} else {
return undefined
}
});
if (selectData) {
obj.defaultData = true;
} else {
obj.defaultData = false;
}
result.push(obj);
}
return result;
}
private getDateColor(day: number): string {
const currentDate = new Date(this.currentYear, this.currentMonth, day)
const today = new Date()
today.setHours(0, 0, 0, 0)
if (currentDate > today) {
return '#CCCCCC' // 灰色显示过去的日期
} else if (this.isSelectedDate(day)) {
return '#ffffff' // 选中日期的文字颜色
} else {
return '#000000' // 未选中日期的文字颜色
}
}
private getLunarDate(day: number): string {
return LunarDate.solarToLunar(this.currentYear, this.currentMonth + 1, day);
}
}
class LunarDate {
private static lunarInfo: number[] = [
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052d0, 0x0a9a8, 0x0e950, 0x06aa0,
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
0x05aa0, 0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0
];
private static Gan = ["甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"];
private static Zhi = ["子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"];
private static Animals = ["鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊", "猴", "鸡", "狗", "猪"];
private static lunarMonths = ["正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"];
private static lunarDays = ["初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十",
"十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十",
"廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"];
static solarToLunar(year: number, month: number, day: number): string {
if (year < 1900 || year > 2100) {
return "无效年份";
}
const baseDate = new Date(1900, 0, 31);
const objDate = new Date(year, month - 1, day);
let offset = Math.floor((objDate.getTime() - baseDate.getTime()) / 86400000);
let i: number, leap = 0, temp = 0;
for (i = 1900; i < 2101 && offset > 0; i++) {
temp = LunarDate.getLunarYearDays(i);
offset -= temp;
}
if (offset < 0) {
offset += temp;
i--;
}
const lunarYear = i;
leap = LunarDate.getLeapMonth(i);
let isLeap = false;
for (i = 1; i < 13 && offset > 0; i++) {
if (leap > 0 && i === (leap + 1) && isLeap === false) {
--i;
isLeap = true;
temp = LunarDate.getLeapDays(lunarYear);
} else {
temp = LunarDate.getLunarMonthDays(lunarYear, i);
}
if (isLeap === true && i === (leap + 1)) {
isLeap = false;
}
offset -= temp;
}
if (offset === 0 && leap > 0 && i === leap + 1) {
if (isLeap) {
isLeap = false;
} else {
isLeap = true;
--i;
}
}
if (offset < 0) {
offset += temp;
--i;
}
const lunarMonth = i;
const lunarDay = offset + 1;
const monthStr = (isLeap ? "闰" : "") + LunarDate.lunarMonths[lunarMonth - 1];
const dayStr = LunarDate.lunarDays[lunarDay - 1];
return dayStr === '初一' ? monthStr + "月" : dayStr;
}
private static getLunarYearDays(year: number): number {
let i = 0, sum = 348;
for (i = 0x8000; i > 0x8; i >>= 1) {
sum += (LunarDate.lunarInfo[year - 1900] & i) ? 1 : 0;
}
return sum + LunarDate.getLeapDays(year);
}
private static getLeapMonth(year: number): number {
return LunarDate.lunarInfo[year - 1900] & 0xf;
}
private static getLeapDays(year: number): number {
if (LunarDate.getLeapMonth(year)) {
return (LunarDate.lunarInfo[year - 1900] & 0x10000) ? 30 : 29;
}
return 0;
}
private static getLunarMonthDays(year: number, month: number): number {
return (LunarDate.lunarInfo[year - 1900] & (0x10000 >> month)) ? 30 : 29;
}
}
日期处理utils代码
TypeScript
class CalendarUtils {
private totalDays: number = 0;
// 获取日历全部时间列表值
public getCalendarListCount() {
let list: number[] = []
for (let i = 1; i <= this.getDaysInLastTenYears(); i++) {
list.push(i);
}
return list;
}
// 获取前10年的总共天数
public getDaysInLastTenYears(): number {
const now = new Date();
const tenYearsAgo = new Date(now.getFullYear() - 10, now.getMonth(), now.getDate());
const millisecondsInDay = 1000 * 60 * 60 * 24;
const diffInMilliseconds = now.getTime() - tenYearsAgo.getTime();
this.totalDays = Math.ceil(diffInMilliseconds / millisecondsInDay);
return Math.ceil(diffInMilliseconds / millisecondsInDay);
}
// 根据当前天数获取年月日
public getDateFromDaysAgo(days: number): string {
const now = new Date();
let dayCount = this.totalDays - days;
const millisecondsPerDay = 1000 * 60 * 60 * 24;
const targetDate = new Date(now.getTime() - dayCount * millisecondsPerDay);
const year = targetDate.getFullYear();
const month = String(targetDate.getMonth() + 1).padStart(2, '0'); // Months are zero-based
const day = String(targetDate.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 根据年月日获取天数下标 反值
public getDaysFromDate(dateString: string): number {
const dateParts = dateString.split('-');
const targetDate = new Date(Number(dateParts[0]), Number(dateParts[1]) - 1, Number(dateParts[2]));
const now = new Date();
const diffInMilliseconds = now.getTime() - targetDate.getTime();
const millisecondsPerDay = 1000 * 60 * 60 * 24;
let days = Math.floor(Math.abs(diffInMilliseconds) / millisecondsPerDay);
return (this.totalDays - 1) - days;
}
// 获取上一天年月日
public getPreviousDay(value: string | Date): string {
const now = new Date(value);
now.setDate(now.getDate() - 1); // 获取前一天
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以需要+1,并补0
const day = String(now.getDate()).padStart(2, '0'); // 补0
return `${year}-${month}-${day}`;
}
// 获取下一天年月日
public getNextDay(value: string | Date): string {
const now = new Date(value);
now.setDate(now.getDate() + 1); // 获取后一天
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以需要+1,并补0
const day = String(now.getDate()).padStart(2, '0'); // 补0
return `${year}-${month}-${day}`;
}
// 获取当前年月日
public getCurrentDay() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // 月份从0开始,加一,补0
const day = String(now.getDate()).padStart(2, '0'); // 补0
return `${year}-${month}-${day}`;
}
// 获取前10年的年月茹
public getTheFirstYearsDate(value: number) {
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
// 获取前10年的日期
const previousTenYearsDate = new Date(currentDate.setFullYear(currentYear - value));
const Year = previousTenYearsDate.getFullYear();
const Month = String(previousTenYearsDate.getMonth() + 1).padStart(2, '0');
const Day = String(previousTenYearsDate.getDate()).padStart(2, '0');
return `${Year}-${Month}-${Day}`;
}
}
// 获取数据预加载
export class MyDataSource implements IDataSource {
private list: number[] = []
constructor(list: number[]) {
this.list = list
}
totalCount(): number {
return this.list.length
}
getData(index: number): number {
return this.list[index]
}
registerDataChangeListener(listener: DataChangeListener): void {
}
unregisterDataChangeListener() {
}
}
export const calendarUtils = new CalendarUtils();