| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- import { DateInfo } from "../../models";
- @Component
- @CustomDialog
- export struct YTCalendarPicker {
- /**
- * 外部传入
- */
- // 主题色
- linearInfo: LinearGradientOptions = { colors: [['#B9FD2A', 0.01], ['#F5FD6D', 1]], angle: 110 }
- // 边距
- cPadding: Padding | Length | LocalizedPadding = { left: 0, right: 0, top: 20, bottom: 4 }
- // yyyy-mm-dd 格式的数组, 符合数组内格式的日期下会有标记
- dateList: string[] = []
- // 取消
- onCancel: () => void = () => {}
- // 确定
- onConfirm: (date: Date) => void = () => {}
- // 点击某一天的回调
- onClickItem: (date: Date) => void = () => {}
- // 是否显示头部标题
- showTitle: boolean = true
- /**
- * 内部方法
- */
- // 年月
- @State year: number = new Date().getFullYear();
- @State month: number = new Date().getMonth() + 1;
- // 开始是星期几
- @State private weekStart: number = 1;
- // 本月的天数
- @State private daysInMonth: number = 31;
- // 渲染用的月份数组
- @State private monthArray: DateInfo[] = [];
- // 当前选中的日期
- @State private selectDay: Date = new Date()
- // 显示选择日期的菜单
- @State private showDatePickerMenu: boolean = false
- // 弹窗控制器 -
- diaLogControl?: CustomDialogController
- // 星期中文映射表
- private readonly WEEK_MAP = ['日', '一', '二', '三', '四', '五', '六'];
- // 日期选择器的范围
- private declare range: TextCascadePickerRangeContent[]
- // 年份 - 日期选择器使用
- private years = Array.from<number, number>({ length: new Date().getFullYear() - 1970 + 1 }, (_: number, i: number) => 1970 + i)
- //当前年月选择的下标
- private selectedIndex: number[] = [new Date().getFullYear() - 1970, new Date().getMonth()]
- /**
- * 回调
- */
- // 更改月份
- changeMonth(year: number, month: number) {
- // 1. 获取当前月份的天数
- this.daysInMonth = this.getDaysInMonth(year, month);
- // 2. 获取当月的第一天是星期几
- this.weekStart = new Date(year, month - 1, 1).getDay() + 1;
- // 3. 初始化月份数组
- this.monthArray = [...new Array(this.weekStart - 1).fill({} as DateInfo),
- ...this.generateBackwardDateArray(new Date(year, month - 1, 1), this.daysInMonth, false)];
- return this.monthArray
- }
- // 点击单个日期
- clickItem(item: DateInfo){
- this.selectDay = item.id
- this.onClickItem(this.selectDay)
- }
- // 更改日期选择器的显示和隐藏
- changeDatePickerMenu(show: boolean) {
- animateToImmediately({ duration: 200 }, () => {
- this.showDatePickerMenu = show
- })
- }
- /**
- * 获取指定月份的天数
- * @param year 年
- * @param month 月份
- * @returns 当月的天数
- */
- getDaysInMonth(year: number, month: number): number {
- return new Date(year, month, 0).getDate();
- }
- /**
- * 判断两个 Date 对象是否为同一天
- * @param date1
- * @param date2
- * @returns
- */
- isSameDay(date1: Date, date2: Date): boolean {
- return (
- date1.getFullYear() === date2.getFullYear() &&
- date1.getMonth() === date2.getMonth() &&
- date1.getDate() === date2.getDate()
- );
- }
- /**
- * 格式化日期对象为自定义字符串
- * @param date 日期对象
- * @param needTime 是否需要时间 ( 时、分、秒 )
- * @param needSecond 是否需要秒
- * @returns
- */
- formatDateToCustomString(date: Date, needTime: boolean = true, needSecond: boolean = true): string {
- // 转换为 YY-MM-DD HH:mm:ss 格式
- const year = date.getFullYear().toString();
- const month = (date.getMonth() + 1).toString().padStart(2, '0');
- const day = date.getDate().toString().padStart(2, '0');
- const hours = date.getHours().toString().padStart(2, '0');
- const minutes = date.getMinutes().toString().padStart(2, '0');
- const seconds = date.getSeconds().toString().padStart(2, '0');
- const result =
- `${year}-${month}-${day}` + (needTime ? ` ${hours}:${minutes}` + (needSecond ? `:${seconds}` : '') : '');
- return result;
- }
- /**
- * 生成从指定日期开始向后的连续日期数组,可以包含今天但不能超过今天
- * @param startDate 起始日期(默认当前日期)
- * @param count 生成的日期数量(默认7天)
- * @returns 日期对象数组,包含今天但不包含超过今天的日期
- */
- generateBackwardDateArray(
- startDate: Date = new Date(),
- count: number = 7,
- isCheck: boolean = true
- ): DateInfo[] {
- const dateArray: DateInfo[] = [];
- // 复制起始日期,避免修改原对象
- const currentDate = new Date(startDate);
- // 获取今天的日期
- // 获取今天的日期并设置时间为23:59:59:999,便于比较
- const today = new Date();
- today.setHours(0, 0, 0, 0);
- today.setDate(today.getDate() + 1);
- today.setTime(today.getTime() - 1); // 设置为今天的最后一毫秒
- for (let i = 0; i < count; i++) {
- // 检查当前日期是否超过今天
- if (isCheck && currentDate > today) {
- break;
- }
- // 添加日期信息到数组
- dateArray.push(this.createDateInfo(currentDate));
- // 日期加1天(向后)
- currentDate.setDate(currentDate.getDate() + 1);
- }
- return dateArray;
- }
- /**
- * 创建一个日期信息对象
- * @param date 日期对象
- * @returns DateInfo 对象
- */
- createDateInfo(date: Date): DateInfo {
- const year = date.getFullYear();
- const month = date.getMonth() + 1;
- const day = date.getDate();
- const week = this.WEEK_MAP[date.getDay()];
- return {
- year: year,
- month: month,
- day: day,
- week: week,
- id: new Date(date)
- };
- }
- aboutToAppear(): void {
- this.changeMonth(this.year, this.month);
- const monthsRange =
- Array.from<number, number>({ length: 12 }, (_: number, i: number) => 1 + i).map(month => {
- return { text: month.toString() } as TextCascadePickerRangeContent
- })
- this.range = this.years.map(year => {
- return { text: year.toString(), children: monthsRange } as TextCascadePickerRangeContent
- })
- }
- build() {
- Column({ space: 20 }) {
- // 月份切换和头部组件
- if(this.showTitle) {
- Row() {
- // 月份切换
- Row({ space: 8 }) {
- Text(`${this.year}-${this.month.toString().padStart(2, '0')}`)
- .fontSize(16)
- .fontWeight(600)
- .fontColor(Color.Black)
- Image($r('app.media.ic_back'))
- .width(14)
- .height(8)
- .rotate({angle: this.showDatePickerMenu ? 90 : 270})
- }
- .onClick(() => {
- this.changeDatePickerMenu(true)
- })
- .bindMenu(this.showDatePickerMenu, this.dateSelectMenu, {
- onDisappear: () => {
- this.changeDatePickerMenu(false)
- this.year = this.range[this.selectedIndex[0]].text.valueOf() as number
- this.month = this.range[this.selectedIndex[0]].children![this.selectedIndex[1]].text.valueOf() as number
- this.changeMonth(this.year, this.month)
- },
- placement: Placement.Bottom
- })
- // 右上角的结构
- this.buttonRow()
- }
- .width("100%")
- .alignItems(VerticalAlign.Center)
- .justifyContent(FlexAlign.SpaceBetween)
- }
- // 周 title 的显示
- Row() {
- ForEach(this.WEEK_MAP, (item: string, index: number) => {
- Row(){
- Text(item)
- .fontColor(Color.Black)
- }
- .layoutWeight(1)
- .justifyContent(FlexAlign.Center)
- })
- }
- .width("100%")
- .justifyContent(FlexAlign.SpaceBetween)
- // 日历主体
- Grid() {
- ForEach(this.monthArray, (item: DateInfo, index: number) => {
- GridItem() {
- Column({ space: 5 }) {
- if (item.id) {
- // 日历-单个日期的组件
- Row() {
- Text(item.day + '')
- .fontSize(12)
- .fontColor(this.isSameDay(item.id, this.selectDay) ? Color.Black : '#979797')
- }
- .width("100%")
- .aspectRatio(1)
- .borderRadius(8)
- .alignItems(VerticalAlign.Center)
- .justifyContent(FlexAlign.Center)
- .linearGradient(this.isSameDay(item.id, this.selectDay) ? this.linearInfo : {
- colors: [['#F6F6F6', 1]],
- })
- .onClick(() => {
- this.clickItem(item)
- })
- // 日期标记 - 如无需求,可删除
- Text()
- .width(4)
- .aspectRatio(1)
- .borderRadius(2)
- .linearGradient(this.dateList.indexOf(this.formatDateToCustomString(item.id, false)) !== -1 ? this.linearInfo : null)
- } else {
- // 空白占位符
- Text('')
- }
- }
- .width(32)
- .alignItems(HorizontalAlign.Center)
- .justifyContent(FlexAlign.Center)
- }
- })
- }
- .rowsGap(10)
- .maxCount(6)
- .width("100%")
- .columnsTemplate('repeat(7, 1fr)')
- }
- .width("100%")
- .padding(this.cPadding)
- .backgroundColor(Color.White)
- }
- // 日期选择菜单
- @Builder
- private dateSelectMenu() {
- Stack({ alignContent: Alignment.Center }) {
- TextPicker({ range: this.range, selected: this.selectedIndex })
- .divider(null)
- .defaultPickerItemHeight(36)
- .backgroundColor(Color.White)
- .selectedTextStyle({ color: '#FF353C46', font: { weight: 500, size: 16 } })
- .onScrollStop((value, index) => {
- console.log(`testLog ${value} ${index}`)
- this.selectedIndex = index as number[]
- })
- //自定义选择遮罩
- Column() {
- }
- .width('100%')
- .height(36)
- .backgroundColor('#52D9D9D9')
- .borderRadius(8)
- }
- .height(140)
- .width(160)
- .padding(12)
- .borderRadius(8)
- .backgroundColor(Color.White)
- }
- // 右上角结构 - 确定和取消按钮
- @Builder
- private buttonRow() {
- Row({ space: 14 }) {
- Text("取消")
- .borderRadius(36)
- .backgroundColor('#F6F6F6')
- .fontColor(Color.Black)
- .padding({
- left: 20,
- top: 5,
- right: 20,
- bottom: 5
- })
- .onClick(this.onCancel)
- Text("确认")
- .borderRadius(36)
- .linearGradient(this.linearInfo)
- .fontColor(Color.Black)
- .padding({
- left: 20,
- top: 5,
- right: 20,
- bottom: 5
- })
- .onClick(() => {
- this.onConfirm(this.selectDay)
- })
- }
- }
- }
|