HarmonyOS NEX 鸿蒙日期选择器弹窗

115 阅读3分钟

先看效果

微信图片_20250327101113.jpg

微信图片_20250327101117.jpg

1.ohpm install dayjs

2.在目录ets/components/TimeDateDialog下创建index.ets


//弹窗具体内容
@CustomDialog
struct CustomDialogDate {
  controller?: CustomDialogController
  @Prop defaultTime: string; //默认值
  @State allYears:string[]=[] // 前后六十年
  @State allMonth:string[]=['01月','02月','03月','04月','05月','06月','07月','08月','09月','10月','11月','12月'] //只存储小时
  @State allDay:string[]=[] //当月多少天
  @State isHaveDay: boolean = false;
  @State loadingData: boolean = false;
  @State YearSelectValue: string = "0";
  @State YearSelect: number=0;
  @State MonthSelectValue: string= "00";
  @State MonthSelect: number=0;
  @State DaySelectValue: string= "00";
  @State DaySelect: number=0;



  cancel: () => void = () => {
  }
  confirm: (data:string) => void = () => {
  }

  // 提交数据
  onSubmit(){
    let dateTime  = this.YearSelectValue + "-" + this.MonthSelectValue
    if (this.isHaveDay){
      dateTime = dateTime + "-" + this.DaySelectValue
    }
    this.confirm(this.DeleteChina(dateTime)) //回传日期到父组件
    this.controller?.close()
  }

  aboutToAppear(): void {
    this.initYearList(); //获取年
    this.initDefaultTime() // 获取选中时间
    if (this.isHaveDay) {
      this.initDayList(); //初始化日
      this.getDay()
    }
  }

  initYearList(){
    // 获取当前年份
    const currentYear:number = new Date().getFullYear();
    for (let i = -30; i <= 30; i++) {
      this.allYears.push((currentYear + i ).toString()+'年')
    }
  }
  
   initDayList(){
    this.loadingData = true
    this.allDay = []

    const numbersDay: number = this.getDaysInMonth(Number(this.DeleteChina(this.YearSelectValue)) ,Number(this.DeleteChina(this.MonthSelectValue)))
    for (let i = 1; i <= numbersDay; i++) {
      this.allDay.push(i.toString()+'日')
    }
    this.loadingData = false

  }

  DeleteChina(data:string) :string{
    return data.replace(/[年月日]/g, '')
  }

  getDay(){
    this.DaySelectValue = new Date().getDate().toString();
    this.DaySelect = this.allDay.indexOf((new Date().getDate()) > 9  ?  (new Date().getDate()).toString() +'日' : '0' + (new Date().getDate()).toString() +"日");
  }

   getDaysInMonth(year: number, month: number): number {
  // 注意:JavaScript 的月份是从 0 开始计数的(0 表示 1 月,11 表示 12 月)
  // 所以需要将传入的月份减 1
  return new Date(year, month, 0).getDate();
}

  // 初始化默认选中
  initDefaultTime(){
    this.YearSelect = this.allYears.indexOf(new Date().getFullYear().toString()+'年');
    this.YearSelectValue = new Date().getFullYear().toString()
    this.MonthSelect = this.allMonth.indexOf( (new Date().getMonth()+1) > 9  ?  (new Date().getMonth()+1).toString() +'月' : '0' + (new Date().getMonth()+1).toString() +'月' ) ;
    this.MonthSelectValue = (new Date().getMonth()+1) > 9  ?  (new Date().getMonth()+1).toString() : '0' + (new Date().getMonth()+1).toString()
  }

  build() {
    Column() {
      Row(){
        Button('取消', { type: ButtonType.Normal, stateEffect: true })
          .margin(0)
          .borderRadius(6)
          .backgroundColor('#fff')
          .fontColor('#aaa')
          .fontSize(14)
          .onClick(()=>{
            this.controller?.close()
          })
        Button('确定', { type: ButtonType.Normal, stateEffect: true })
          .margin(0)
          .backgroundColor('#fff')
          .fontColor('#3C7FFF')
          .fontSize(14)
          .onClick(()=>{
            this.onSubmit()
            this.controller?.close()
          })
      }.width('100%').justifyContent(FlexAlign.SpaceBetween)

      Divider().color('#EEE').strokeWidth(1)
      
        // 滚动选中区域
      Flex(){
        // 年
        TextPicker({ range: this.allYears, selected: this.YearSelect})
          .width(this.isHaveDay ? '50%' : '60%')
          .canLoop(false)//不循环
          .divider({
            strokeWidth: 1,
            startMargin: 0,
            endMargin: 0,
            color:'#eee'
          })
          .onChange((value: string | string[], index: number | number[]) => {
            this.YearSelect= index as number
            this.YearSelectValue = value as string

              this.initDayList()

          })
          .defaultPickerItemHeight(40)
          .textStyle({color:'#cccccc', font: {size: 15, weight: 400}})
          .selectedTextStyle({color: '#555555', font: {size: 15}})

        //月
        TextPicker({ range: this.allMonth, selected: this.MonthSelect  }).width(this.isHaveDay ? '25%' : '40%')
          .canLoop(false)//不循环
          .divider({
            strokeWidth: 1,
            startMargin: 0,
            endMargin: 0,
            color:'#eee'
          })
          .defaultPickerItemHeight(40)
          .textStyle({color:'#cccccc', font: {size: 15, weight: 400}})
          .selectedTextStyle({color: '#555555', font: {size: 15}})
          .onChange((value: string | string[], index: number | number[]) => {
            this.MonthSelect= index as number
            this.MonthSelectValue =  value as string
              this.initDayList()
          })
        //日
        if(!this.loadingData && this.isHaveDay){
          TextPicker({ range: this.allDay, selected: this.DaySelect  }).width('25%').canLoop(false)//不循环
            .divider({
              strokeWidth: 1,
              startMargin: 0,
              endMargin: 0,
              color:'#eee'
            })
            .defaultPickerItemHeight(40)
            .textStyle({color:'#cccccc', font: {size:15, weight: 400}})
            .selectedTextStyle({color: '#555555', font: {size: 15}})
            .onChange((value: string | string[], index: number | number[]) => {
              this.DaySelect= index as number
              this.DaySelectValue = value as string
            })
        }

      }
    }.width('100%')
    .padding({bottom:40,top:5})
    .backgroundColor('#fff')
  }
}

//定义controller对象 主要作用父子通信 父组件调用子组件方法 唤醒弹窗
export  class DialogDateController {
  ShowDialog = (value?: string) => {
  }
}

//弹窗控制逻辑
@Component
export  struct TimeDateDialog { //修改命名 注意前面加了 export 需要暴露组件
  private controller: DialogDateController = new DialogDateController();
  CustomDialogController: CustomDialogController | null =null ;
  @Prop defaultTime: string;
  @State isHaveDay: boolean = false;
  cancel?: () => void
  confirm?: (data:string) => void = () => {
  }
  // 打开显示弹窗
  private async  ShowDialog(value?: string){
    this.CustomDialogController?.open()
  }
  aboutToAppear(): void {
    if (this.controller) {
      //给controller对应的方法赋值
      this.controller.ShowDialog = this.ShowDialog.bind(this); //这里需要注意 用了  bind改变 this 指向
    }
    this.CustomDialogController=  new CustomDialogController({
      builder: CustomDialogDate({
        isHaveDay :this.isHaveDay,
        cancel: this.cancel,
        confirm: this.confirm,
      }),
      autoCancel: true,
      onWillDismiss:(dismissDialogAction: DismissDialogAction)=> {
        if (dismissDialogAction.reason == DismissReason.PRESS_BACK) {
          dismissDialogAction.dismiss()
        }
        if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) {
          dismissDialogAction.dismiss()
        }
      },
      alignment: DialogAlignment.Bottom,
      offset: { dx: 0, dy:  0},
      customStyle: true,//这里为true 样式才可以完全自定义
    })
  }
  aboutToDisappear() {
    this.CustomDialogController = null // 将dialogController置空
  }


  build() { //因为用的 自定义弹窗功能,所以这下面可以为空

  }
}

3.在页面调用

import dayjs from 'dayjs'
import { DialogDateController, TimeDateDialog } from '../../component/TimeDateDialog'


@Component
export struct Home {
  @State DateDialogRef: DialogDateController = new DialogDateController(); //用于调用子组件的方法 唤醒弹窗
  @State DateDialogRefTrue: DialogDateController = new DialogDateController(); //用于调用子组件的方法 唤醒弹窗
  @State times: string = dayjs().format('YYYY-MM')
  @State timesR: string = dayjs().format('YYYY-MM-DD')


  build() {
    Column() {
      Column() {

          Button(dayjs(this.times).format('YYYY年M月'), { type: ButtonType.Normal, stateEffect: true })
            .fontSize(12)
            .fontWeight(400)
            .fontColor('#000')
            .onClick(() => {
              this.DateDialogRef.ShowDialog()
            })

          Button(dayjs(this.timesR).format('YYYY年M月D日'), { type: ButtonType.Normal, stateEffect: true })
            .fontSize(12)
            .fontWeight(400)
            .fontColor('#000')

            .onClick(() => {
              this.DateDialogRefTrue.ShowDialog()
            })

        TimeDateDialog({
          isHaveDay: false,
          controller: this.DateDialogRef,
          confirm: (data) => {
            this.times = data
          }
        })

        TimeDateDialog({
          isHaveDay: true,
          controller: this.DateDialogRefTrue,
          confirm: (data) => {
            this.timesR = data
          }
        })
      }
    }
  }
}