Browse Source

完成日期选择器封装

chenritian 2 months ago
parent
commit
9c7f38eaf8

+ 5 - 0
commons/basic/Index.ets

@@ -63,6 +63,11 @@ export * from "./src/main/ets/utils/YTBreakPoint"
 
 export { BasicType } from './src/main/ets/models/index'
 
+export {YTAddressSelectorDialog,DateOption} from './src/main/ets//datepicker/YTDataPickerSelector'
+
+
+export {YTDateUtil,DateFormat} from './src/main/ets/utils/YTDateUtil'
+
 export * from '@mumu/crop'
 
 export * from './src/main/ets/models'

+ 20 - 0
commons/basic/src/main/ets/datepicker/DatePickerEnums.ets

@@ -0,0 +1,20 @@
+/*
+ * 日期选择器枚举定义
+ */
+
+
+// 单位类型枚举(仅在滚轮式下生效)
+export enum UnitType {
+  YEAR = 'YEAR',             // 年
+  MONTH = 'MONTH',            // 月
+  DAY = 'DAY',              // 日
+  YEAR_MONTH = 'YEAR_MONTH',       // 年月
+  MONTH_DAY = 'MONTH_DAY',        // 月日
+  YEAR_MONTH_DAY = 'YEAR_MONTH_DAY'    // 年月日
+}
+
+// 结果类型枚举
+export enum ResultType {
+  CANCEL = 'CANCEL',  // 取消
+  FINISH = 'FINISH'   // 完成
+}

+ 274 - 0
commons/basic/src/main/ets/datepicker/WheelPicker.ets

@@ -0,0 +1,274 @@
+/*
+ * 滚轮式日期选择器组件
+ */
+
+import { YTDateUtil } from '../utils/YTDateUtil';
+import { UnitType } from './DatePickerEnums';
+
+@Component
+export struct WheelPicker {
+  @Prop @Require date: Date;
+  @State dateLocal: Date = new Date();
+  upDate: (date: Date) => void = (date: Date) => {};
+  @Prop @Require minDate: Date;
+  @Prop @Require maxDate: Date;
+  @Prop @Require unitType: UnitType;
+  @State years: number[] = [];
+  @State yearsString: string[] = [];
+  @State months: number[] = [];
+  @State monthsString: string[] = [];
+  @State days: number[] = [];
+  @State daysString: string[] = [];
+  @State selectedYear: number = 0;
+  @State selectedYearString: string = '';
+  @State selectedMonth: number = 0;
+  @State selectedMonthString: string = '';
+  @State selectedDay: number = 0;
+  @State selectedDayString: string = '';
+  @State debounceYear: number = -1;
+  @State debounceMonth: number = -1;
+
+  @Prop highlightBackgroundColor:ResourceColor
+  @Prop highlightBorderColor:ResourceColor
+  @Prop selectedTextStyle:PickerTextStyle
+  @Prop textStyle:PickerTextStyle
+
+
+
+
+  aboutToAppear() {
+    // 设置初始选中值
+    this.selectedYear = this.date.getFullYear();
+    this.selectedMonth = this.date.getMonth() + 1; // 月份从1开始
+    this.selectedDay = this.date.getDate();
+
+    this.selectedYearString = this.selectedYear.toString() + '年';
+    this.selectedMonthString = YTDateUtil.padNumber(this.selectedMonth) + '月';
+    this.selectedDayString = this.selectedDay.toString() + '日';
+
+    // 初始化年份数据
+    this.initYears();
+
+    // 初始化月份数据
+    this.initMonths();
+
+    // 初始化日期数据
+    this.initDays();
+
+  }
+
+  // 初始化年份数据
+  initYears() {
+    const minYear = this.minDate.getFullYear();
+    const maxYear = this.maxDate.getFullYear();
+    this.years = [];
+    this.yearsString = [];
+
+    for (let year = minYear; year <= maxYear; year++) {
+      this.years.push(year);
+      this.yearsString.push(year.toString() + '年');
+    }
+  }
+
+  // 初始化月份数据
+  initMonths() {
+    this.months = [];
+    this.monthsString = [];
+    let minMonth = 1;
+    let maxMonth = 12;
+    if (!this.selectedYear) {
+      this.selectedYear = this.date.getFullYear()
+      this.selectedYearString = this.selectedYear.toString() + '年';
+      console.log(this.date.getFullYear() + ',.,' + this.selectedYear)
+    }
+
+    // 如果是最小年份,则从最小月份开始
+    if (this.selectedYear === this.minDate.getFullYear()) {
+      minMonth = this.minDate.getMonth() + 1;
+    }
+
+    // 如果是最大年份,则到最大月份结束
+    if (this.selectedYear === this.maxDate.getFullYear()) {
+      maxMonth = this.maxDate.getMonth() + 1;
+    }
+
+    for (let month = minMonth; month <= maxMonth; month++) {
+      //补零
+      this.monthsString.push(YTDateUtil.padNumber(month)+'月')
+      this.months.push(month);
+    }
+
+    // 确保选中的月份在可选范围内
+    console.log('选择的月份是',this.selectedMonth)
+    if (this.selectedMonth < minMonth) {
+      this.selectedMonth = minMonth;
+      this.selectedMonthString = YTDateUtil.padNumber(minMonth)+'月';
+    } else if (this.selectedMonth > maxMonth) {
+      this.selectedMonth = maxMonth;
+      this.selectedMonthString = YTDateUtil.padNumber(minMonth)+'月';
+    }
+  }
+
+  // 初始化日期数据
+  initDays() {
+    this.days = [];
+    this.daysString = [];
+    let minDay = 1;
+    let maxDay = new Date(this.selectedYear, this.selectedMonth, 0).getDate(); // 获取当月天数
+
+    // 如果是最小年份和最小月份,则从最小日期开始
+    if (this.selectedYear === this.minDate.getFullYear() && this.selectedMonth === this.minDate.getMonth() + 1) {
+      minDay = this.minDate.getDate();
+    }
+
+    // 如果是最大年份和最大月份,则到最大日期结束
+    if (this.selectedYear === this.maxDate.getFullYear() && this.selectedMonth === this.maxDate.getMonth() + 1) {
+      maxDay = this.maxDate.getDate();
+    }
+
+    for (let day = minDay; day <= maxDay; day++) {
+      this.days.push(day);
+      this.daysString.push(day.toString()+'日');
+    }
+
+    // 确保选中的日期在可选范围内
+    if (this.selectedDay < minDay) {
+      this.selectedDay = minDay;
+      this.selectedDayString = this.selectedDay+'日';
+    } else if (this.selectedDay > maxDay) {
+      this.selectedDay = maxDay;
+      this.selectedDayString = this.selectedDay+'日';
+    }
+  }
+
+  // 更新日期
+  updateDate() {
+    this.dateLocal = new Date(this.selectedYear, this.selectedMonth - 1, this.selectedDay);
+    this.upDate(this.dateLocal)
+  }
+
+  build() {
+    Stack() {
+      // 中间高亮横条
+      Row() {
+      }
+      // .width(343)
+      // .height(44)
+      .width('100%')
+      .height(44)
+      .backgroundColor(this.highlightBackgroundColor)
+      .borderRadius(44)
+      .border({width: 1, color: this.highlightBorderColor})
+      Row() {
+        // 根据单位类型显示不同的选择器
+        if (this.unitType === UnitType.YEAR || this.unitType === UnitType.YEAR_MONTH ||
+          this.unitType === UnitType.YEAR_MONTH_DAY) {
+          // 年份选择器
+          TextPicker({
+            range: this.yearsString,
+            selected: this.yearsString.indexOf(this.selectedYearString)
+          })
+            .divider(null)
+            .selectedTextStyle(this.selectedTextStyle)
+            .textStyle(this.textStyle)
+            .canLoop(true)
+            .width(70)
+            .onChange((val) => {
+              if (this.debounceYear !== -1) {
+                clearTimeout(this.debounceYear);
+              }
+
+              this.debounceYear = setTimeout(() => {
+
+                this.selectedYear = Number((val as string).replace('年',''));
+                this.selectedYearString = val as string;
+                this.initMonths();
+                this.initDays();
+                this.updateDate();
+                this.debounceYear = -1;
+              }, 300); // 300ms 的防抖延迟
+            })
+ /*         Text('年')
+            .fontSize(16)
+            .fontColor(Color.Black)
+            .margin({ left: 10, right: 16 })*/
+        }
+
+        if (this.unitType === UnitType.MONTH || this.unitType === UnitType.YEAR_MONTH ||
+          this.unitType === UnitType.MONTH_DAY || this.unitType === UnitType.YEAR_MONTH_DAY) {
+          // 月份选择器
+          TextPicker({
+            range: this.monthsString,
+            selected: this.monthsString.indexOf(this.selectedMonthString)
+          })
+            .divider(null)
+            .selectedTextStyle(this.selectedTextStyle)
+            .textStyle(this.textStyle)
+            .canLoop(true)
+            .width(70)
+            .onChange((val) => {
+              if (this.debounceMonth !== -1) {
+                clearTimeout(this.debounceMonth);
+              }
+              this.debounceMonth = setTimeout(() => {
+                //去0和月字
+                this.selectedMonth = Number((val as string).replace(/^0+/, '').replace('月', ''));
+                // this.selectedMonth = Number(val);
+                this.selectedMonthString = val as string;
+                this.initDays();
+                this.updateDate();
+                this.debounceMonth = -1;
+              }, 100); // 100ms 的防抖延迟
+
+            })
+
+   /*       Text('月')
+            .fontSize(16)
+            .margin({ left: 3, right: 16 })
+            .fontColor(Color.Black)*/
+        }
+
+        if (this.unitType === UnitType.DAY || this.unitType === UnitType.MONTH_DAY ||
+          this.unitType === UnitType.YEAR_MONTH_DAY) {
+          // 日期选择器
+
+          TextPicker({
+            range: this.daysString,
+            selected: this.daysString.indexOf(this.selectedDayString)
+          })
+            .divider(null)
+            .selectedTextStyle(this.selectedTextStyle)
+            .textStyle(this.textStyle)
+            .canLoop(true)
+            .width(70)
+            .onChange((val) => {
+              //去0和日字
+              this.selectedDay = Number((val as string).replace(/^0+/, '').replace('日', ''));
+              // this.selectedDay = Number(val);
+              this.selectedDayString = val as string;
+              this.updateDate();
+            })
+
+/*          Text('日')
+            .fontSize(16)
+            .fontColor(Color.Black)*/
+        }
+      }
+      .justifyContent((this.unitType==UnitType.YEAR||this.unitType==UnitType.MONTH||this.unitType==UnitType.DAY)?FlexAlign.Center:FlexAlign.SpaceBetween)
+      .width('100%')
+      .padding({ left: 25, right: 25 })
+
+
+      // .position({ x: 0, y: 90 }) // y = (224-40)/2,居中
+      // // 上边界白色线
+      // // Row()
+      // //   .width(320)
+      // //   .height(1)
+      // //   .backgroundColor('#fff')
+      // //   .align(Alignment.Center)
+      // //   .margin({ top: -24 }) // 48/2=24,正好到高亮条上边缘
+    }
+    .width('100%')
+    .backgroundColor('#FFF')
+  }
+} 

+ 371 - 0
commons/basic/src/main/ets/datepicker/YTDataPickerSelector.ets

@@ -0,0 +1,371 @@
+import { YTHeader } from "../components/generalComp/YTHeader";
+import { YTDateUtil } from "../utils/YTDateUtil";
+import { ResultType, UnitType } from "./DatePickerEnums";
+import { QNUIFloatingContainer } from "./YTUIFloatingContainer"
+import { WheelPicker } from "./WheelPicker";
+
+
+@Component
+export struct YTUIDatePickerComponent {
+  // 属性
+  @Prop @Require date: Date; // 当前选中的日期
+  @State dateLocal: Date = new Date();
+  @Prop @Require minDate: Date; // 最小可选日期,默认10年前
+  @Prop @Require maxDate: Date; // 最大可选日期,默认10年后
+  @Prop @Require unitType: UnitType; // 单位类型,默认为年月日
+  @State isVisible: string = '100%'; // 是否显示选择器
+  // 回调函数
+  confirm: (date: Date) => void = () => {
+  };
+  close: () => void = () => {
+  };
+  title:string = '选择日期'
+  @Prop headerBuilder:DatePickerHeaderGlobalBuilder
+  @Prop highlightBackgroundColor:ResourceColor
+  @Prop highlightBorderColor:ResourceColor
+  @Prop selectedTextStyle:PickerTextStyle
+  @Prop textStyle:PickerTextStyle
+
+  aboutToAppear(): void {
+    this.dateLocal = this.date;
+    console.log('aboutToAppear',YTDateUtil.formatDate(this.dateLocal))
+  }
+
+  // 确认选择
+  private onConfirm(): void {
+    this.confirm?.( this.dateLocal);
+    this.isVisible = '100%'
+    this.close?.()
+
+  }
+
+  // 取消选择
+  private onCancel(): void {
+    // this.confirm(ResultType.CANCEL, this.dateLocal);
+    this.isVisible = '100%'
+    this.close?.()
+  }
+
+  build() {
+    Column() {
+        Column() {
+          //标题
+          Column({space:12}){
+            YTHeader({
+              defaultStyle:{
+                backArrow:true,
+                title:this.title,
+              },
+              headerHeight: 44,
+              headerPadding:0,
+              rightComp:()=>{
+                this.headerRightBuilder()
+              },
+              leftComp:()=>{
+                this.headerLeftBuilder()
+              }
+            })
+            Divider()
+              .strokeWidth(1)
+              .color('#DFDFDF')
+          }
+          .width('100%')
+          .padding({top:20,bottom:20})
+
+          WheelPicker({
+            date: this.date,
+            minDate: this.minDate,
+            maxDate: this.maxDate,
+            unitType: this.unitType,
+            highlightBackgroundColor:this.highlightBackgroundColor,
+            highlightBorderColor:this.highlightBorderColor,
+            textStyle:this.textStyle,
+            selectedTextStyle:this.selectedTextStyle,
+            upDate: (date: Date) => {
+              this.dateLocal = date
+            }
+          })
+        }
+        .backgroundColor(Color.White)
+        .width('100%')
+        .borderRadius({ topLeft: 14, topRight: 14 })
+        .height(334)
+        .padding({left:20,right:20})
+    }
+    .width('100%')
+    .backgroundColor(Color.White)
+    // .onAppear(() => {
+    //   this.isVisible = '0'
+    // })
+    .borderRadius({topLeft:24,topRight:24})
+    // .translate({ y: this.isVisible })
+    // .animation({ duration: 200 })
+
+  }
+  @Builder
+  headerRightBuilder(){
+    Column(){
+      this.headerBuilder.headerRightBuilder.builder()
+    }
+    .onClick(()=>{
+      this.onConfirm()
+    })
+  }
+
+  @Builder
+  headerLeftBuilder(){
+    Column(){
+      this.headerBuilder.headerLeftBuilder.builder()
+    }
+    .onClick(()=>{
+      this.onCancel()
+    })
+  }
+}
+
+export class DateOption {
+  /**
+   * 当前选中的日期
+   * @default new Date()
+   */
+  date?: Date;
+  /**
+   * 最小可选日期
+   * @default 当前日期的前10年
+   */
+  minDate?: Date;
+  /**
+   * 最大可选日期
+   * @default 当前日期的后10年
+   */
+  maxDate?: Date;
+  /**
+   * 单位类型
+   * @default UnitType.YEAR_MONTH_DAY (年月日)
+   */
+  unitType?: UnitType;
+  /**
+   * 确认选择回调
+   */
+  confirm?: (date: Date) => void;
+
+  /**
+   * 头部左侧按钮
+   */
+  headerLeftBuilder?:WrappedBuilder<[]>
+  /**
+   * 头部右侧按钮
+   */
+  headerRightBuilder?:WrappedBuilder<[]>
+  /**
+   * 标题
+   */
+  title?:string
+  /**
+   * 高亮背景色
+   */
+  highlightBackgroundColor?:ResourceColor
+
+  /**
+   * 高亮边框颜色
+   */
+  highlightBorderColor?:ResourceColor
+
+  /**
+   * 选中的字体样式
+   */
+  selectedTextStyle?:PickerTextStyle
+
+
+  /**
+   * 非选中的字体样式
+   */
+  textStyle?:PickerTextStyle
+}
+
+@Builder
+function headerLeftBuilder(){
+  Image($r('app.media.ic_back'))
+    .width(24)
+    .margin({ left: 16 })
+}
+
+@Builder
+function headerRightBuilder(){
+  Image($r("app.media.slice_sure"))
+    .width(24)
+}
+
+
+@ObservedV2
+export class YTDatePickerOptions extends DateOption {
+  @Trace
+  isEnd?: boolean = false
+  cancel?: () => void = () => {
+  }
+  headerBuilder?:DatePickerHeaderGlobalBuilder
+}
+
+// 组件包装器
+@Builder
+export function YTDatePickerViewBuilder(param: YTDatePickerOptions) {
+  Column(){
+    YTUIDatePickerComponent({
+      date: param.date|| new Date(),
+      minDate: param.minDate|| YTDateUtil.getYearToday(-10),
+      maxDate: param.maxDate||YTDateUtil.getYearToday(10),
+      unitType: param.unitType||UnitType.YEAR_MONTH_DAY,
+      confirm: param.confirm,
+      close: param.cancel,
+      title:param.title,
+      headerBuilder:param.headerBuilder,
+      highlightBackgroundColor:param.highlightBackgroundColor,
+      highlightBorderColor:param.highlightBorderColor,
+      selectedTextStyle:param.selectedTextStyle,
+      textStyle:param.textStyle,
+    });
+  }
+  .width('100%')
+  .translate({
+    y: param.isEnd ? '100%' : 0
+  })
+  .transition(TransitionEffect.translate({ y: '100%' }).animation({ duration: 200 }))
+}
+
+export class DatePickerHeaderGlobalBuilder {
+  headerLeftBuilder: WrappedBuilder<[]> = wrapBuilder(headerLeftBuilder)
+  headerRightBuilder: WrappedBuilder<[]> = wrapBuilder(headerRightBuilder)
+}
+
+// 使用浮动容器重构 AddressSelector
+export class YTAddressSelectorDialog {
+  private uiContext: UIContext;
+  private dialog?: QNUIFloatingContainer<YTDatePickerOptions>;
+
+  constructor(uiContext: UIContext) {
+    this.uiContext = uiContext;
+
+  }
+  // 拷贝传入的配置到携带picker的配置中(Builder仅支持传递一个参数)
+  private optionToPickerOption(option: DateOption): YTDatePickerOptions {
+    const dateOptionAndPicker = new YTDatePickerOptions();
+
+    // 显式复制每个属性(仅复制用户传入的值,保留未传参数的默认值)
+    if (option.date !== undefined) {
+      dateOptionAndPicker.date = option.date
+    }
+    if (option.minDate !== undefined) {
+      dateOptionAndPicker.minDate = option.minDate;
+    }
+    if (option.maxDate !== undefined) {
+      dateOptionAndPicker.maxDate = option.maxDate;
+    }
+    if (option.unitType !== undefined) {
+      dateOptionAndPicker.unitType = option.unitType;
+    }
+    if (option.headerLeftBuilder !== undefined) {
+      dateOptionAndPicker.headerLeftBuilder = option.headerLeftBuilder
+    }
+    if (option.title !== undefined) {
+      dateOptionAndPicker.title = option.title
+    }else {
+      dateOptionAndPicker.title = '选择日期'
+    }
+
+    dateOptionAndPicker.headerBuilder = new DatePickerHeaderGlobalBuilder()
+    if (option.headerLeftBuilder !== undefined) {
+      dateOptionAndPicker.headerBuilder.headerLeftBuilder = option.headerLeftBuilder
+    }else {
+      dateOptionAndPicker.headerBuilder.headerLeftBuilder = wrapBuilder(headerLeftBuilder)
+    }
+    if (option.headerRightBuilder !== undefined) {
+      dateOptionAndPicker.headerBuilder.headerRightBuilder = option.headerRightBuilder
+    }else {
+      dateOptionAndPicker.headerBuilder.headerRightBuilder = wrapBuilder(headerRightBuilder)
+    }
+    if (option.highlightBackgroundColor!==undefined) {
+      dateOptionAndPicker.highlightBackgroundColor = option.highlightBackgroundColor
+    }else {
+      dateOptionAndPicker.highlightBackgroundColor ='#FFFFF6D1'
+    }
+    if (option.highlightBorderColor!==undefined) {
+      dateOptionAndPicker.highlightBorderColor = option.highlightBorderColor
+    }else {
+      dateOptionAndPicker.highlightBorderColor =Color.Transparent
+    }
+    if (option.selectedTextStyle!==undefined) {
+      dateOptionAndPicker.selectedTextStyle = option.selectedTextStyle
+    }else {
+      dateOptionAndPicker.selectedTextStyle = { font: { size: 16,weight:500 }, color: '#CC353C46' }
+    }
+    if (option.textStyle!==undefined) {
+      dateOptionAndPicker.textStyle = option.textStyle
+    }else {
+      dateOptionAndPicker.textStyle = { font: { size: 16,weight:500 }, color: '#CC353C46' }
+    }
+    return dateOptionAndPicker;
+  }
+
+  // 显示地址选择器对话框
+  public show(dateOption?:DateOption) {
+    let option:DateOption = {}
+    if (dateOption) {
+      option = dateOption
+    }else {
+      option = new DateOption()
+    }
+    const pickerOption = this.optionToPickerOption(option)
+    pickerOption.confirm = (date: Date) => {
+      option.confirm?.(date)
+      animateTo({
+        duration: 200,
+      }, () => {
+        pickerOption.isEnd = true
+        this.dialog?.updateParam(pickerOption)
+        this.dialog?.dismissDialog()
+      })
+    }
+    pickerOption.cancel = () => {
+      animateTo({
+        duration: 200,
+      }, () => {
+        pickerOption.isEnd = true
+        this.dialog?.updateParam(pickerOption)
+        this.dialog?.dismissDialog()
+      })
+    }
+    this.dialog = new QNUIFloatingContainer<YTDatePickerOptions>(this.uiContext)
+    this.dialog.setOptions({
+      alignment: DialogAlignment.Bottom,
+      autoCancel: true, //点击蒙层是否关闭弹窗
+      isModal: true, //是否为半模态
+      maskColor: '#0f000000',
+      transition: TransitionEffect.opacity(0).animation({ duration: 100 }),
+      offset: { dx: 0, dy: 0 } as Offset,
+      onWillDismiss: (action) => {
+        animateTo({
+          duration: 100,
+          onFinish: () => {
+            action.dismiss()
+          }
+        }, () => {
+          /*    this.calendarOptionAndPicker!.isEnd = true
+              this.contentNode?.update(this.calendarOptionAndPicker)*/
+          pickerOption.isEnd = true
+          // console.log('测试', this.dialog?.getDialogContent())
+          this.dialog?.updateParam(pickerOption)
+          // this.dialog?.getDialogContent()?.update(pickerOption)
+        })
+
+      }
+    })
+    this.dialog.showDialog(YTDatePickerViewBuilder, pickerOption);
+  }
+
+  // 取消对话框的方法
+  public hide() {
+    this.dialog?.dismissDialog();
+  }
+}
+
+

+ 140 - 0
commons/basic/src/main/ets/datepicker/YTUIFloatingContainer.ets

@@ -0,0 +1,140 @@
+import { ComponentContent, curves, promptAction } from '@kit.ArkUI'
+
+@ComponentV2
+struct QNUIFloatingContainerView {
+  @Require @Param param: QNUIFloatingContainerBuilderParams<object>
+  @BuilderParam contentBuilder: Function
+
+  @Builder
+  showTitleBar() {
+    if (this.param.title.length > 0 || this.param.showClose == true) {
+      Row() {
+        Text(this.param.title)
+          .fontColor('#000')
+          .fontSize(18)
+          .fontWeight(500)
+          .textAlign(TextAlign.Start)
+          .layoutWeight(1)
+      }
+      .padding({ left: 20, right: 12 })
+      .margin({ bottom: 28, top: 20 })
+    }
+  }
+
+  build() {
+    Column() {
+      this.showTitleBar()
+      this.contentBuilder(this.param.params)
+    }
+    .width('100%')
+    .alignItems(HorizontalAlign.Start)
+  }
+}
+
+@ObservedV2
+class QNUIFloatingContainerBuilderParams<T extends object> {
+  @Trace title: string = ''
+  showClose: boolean
+  contentBuilder: Function
+  cancel: Function
+  params: T
+
+  constructor(title: string, showClose: boolean, contentBuilder: Function, params: T, cancel: Function) {
+    this.title = title || ''
+    this.showClose = showClose
+    this.contentBuilder = contentBuilder
+    this.cancel = cancel
+    this.params = params
+  }
+}
+
+@Builder
+function QNUIFloatingContainerBuilder<T extends object>(params: QNUIFloatingContainerBuilderParams<T>) {
+  QNUIFloatingContainerView({ param: params, contentBuilder: params.contentBuilder })
+}
+
+
+export class QNUIFloatingContentParams {
+}
+
+
+export class QNUIFloatingContainer<T extends object> {
+  private uiContext: UIContext
+  private dialogContent?: ComponentContent<object>
+  private title: string = ''
+  private showCloseBtn: boolean = false
+  private onDismiss: Function = () => {
+  }
+  private params?: QNUIFloatingContainerBuilderParams<T>
+  private options: promptAction.BaseDialogOptions = {
+    alignment: DialogAlignment.Bottom,
+    maskColor: 'rgba(0, 0, 0, 0.2)',
+    maskRect: {
+      x: 0,
+      y: 0,
+      width: "100%",
+      height: "100%"
+    },
+    offset: { dx: 0, dy: 0 },
+    transition: TransitionEffect.OPACITY
+      .animation({ duration: 300, curve: curves.initCurve(Curve.EaseInOut) }),
+    onDidDisappear: () => {
+      this.onDismiss()
+    },
+    onWillDisappear: () => {
+    }
+  }
+
+  constructor(uiContext: UIContext) {
+    this.uiContext = uiContext
+  }
+
+  updateParam(params: T) {
+    this.params!.params = params
+    this.dialogContent?.update(this.params)
+  }
+
+  showDialog(builder: (params: T) => void, params: T) {
+    let promptAction = this.uiContext.getPromptAction()
+    if (promptAction === undefined) {
+      console.warn("promptAction is undefined")
+      return
+    }
+    //配置参数
+    this.params = new QNUIFloatingContainerBuilderParams(this.title, this.showCloseBtn, builder, params, () => {
+      promptAction.closeCustomDialog(this.dialogContent)
+    })
+    this.dialogContent = new ComponentContent(this.uiContext,
+      wrapBuilder(QNUIFloatingContainerBuilder),
+      this.params
+    )
+
+
+    promptAction.openCustomDialog(this.dialogContent, this.options)
+  }
+
+  dismissDialog() {
+    if (this.dialogContent != null && this.uiContext.getPromptAction() !== undefined) {
+      this.uiContext.getPromptAction().closeCustomDialog(this.dialogContent)
+    }
+  }
+
+  setOptions(options: promptAction.BaseDialogOptions) {
+    this.options = options;
+  }
+
+  setTitle(title: string) {
+    this.title = title
+    if (this.params) {
+      this.params.title = title
+    }
+  }
+
+  showClose(show: boolean) {
+    this.showCloseBtn = show
+  }
+
+  setDismissListener(listener: Function) {
+    this.onDismiss = listener
+  }
+}

+ 115 - 0
commons/basic/src/main/ets/utils/YTDateUtil.ets

@@ -0,0 +1,115 @@
+
+export enum DateFormat {
+  CHINA = '年月日',
+  UNDERLINE = '-',
+  FULL_STOP = '.',
+}
+
+export class YTDateUtil {
+  private static timestampCache = new Map<string, number>();
+
+  // 格式化日期  xx月xx日
+  static formatMonth(date: Date): string {
+    return `${YTDateUtil.padNumber(date.getMonth() + 1)}月${YTDateUtil.padNumber(date.getDate())}日`
+  }
+  // 格式化日期  yyyy年xx月xx日
+  static formatDate(date: Date, format: DateFormat = DateFormat.CHINA): string {
+    if (format == DateFormat.CHINA) {
+      return `${date.getFullYear()}年${YTDateUtil.padNumber(date.getMonth() + 1)}月${YTDateUtil.padNumber(date.getDate())}日`
+    }
+    return `${date.getFullYear()}${format}${YTDateUtil.padNumber(date.getMonth() + 1)}${format}${YTDateUtil.padNumber(date.getDate())}`
+  }
+
+  /*   static  formatDate(date: Date): string {
+      return `${date.getFullYear()}年${date.getMonth() + 1}月`
+    }*/
+
+  // 组合格式化(如:周二 00:00:00)
+  static formatFull(date: Date): string {
+    return `${YTDateUtil.getWeekCN(date)} ${date.toTimeString().slice(0, 8)}`
+  }
+
+  // 获取中文星期几
+  static getWeekCN(date: Date): string {
+    const week = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
+    return week[date.getDay()]
+  }
+
+  // 补零工具方法
+  static padNumber(num: number): string {
+    return num.toString().padStart(2, '0');
+  }
+
+
+  static SATURDAY = 6; // 日历表上周六对应的序列号,从周日开始算起,取值0~6
+
+  /*
+   * 根据指定年份和月份获取该月在日历表上的日期排布数据
+   * @param {number} specifiedMonth - 指定月份
+   * @param {number} specifiedYear - 指定年份
+   */
+  static getMonthDate(specifiedMonth: number, specifiedYear: number) {
+    let currentFirstWeekDay: number = 0; // 初始化指定月的第一天是周几
+    let currentLastWeekDay: number = 0; // 初始化指定月的最后一天是周几
+    let currentAllDay: number[] = []; // 初始化指定月的日期排列数组
+    let totalDays = new Date(specifiedYear, specifiedMonth, 0).getDate(); // 初始化指定月总天数
+    currentFirstWeekDay = new Date(specifiedYear, specifiedMonth - 1, 1).getDay(); // 获取指定月的第一天是周几
+    currentLastWeekDay = new Date(specifiedYear, specifiedMonth - 1, totalDays).getDay(); // 获取指定月的最后一天是周几
+    // 将月份中显示上个月日期的内容置0
+    for (let item = 0; item < currentFirstWeekDay; item++) {
+      currentAllDay[item] = 0;
+    }
+    // 将本月日期内容存入数组
+    for (let item = 1; item <= totalDays; item++) {
+      currentAllDay.push(item);
+    }
+    // 将月份中显示下个月日期的内容置0
+    for (let item = 0; item < YTDateUtil.SATURDAY - currentLastWeekDay; item++) {
+      currentAllDay.push(0);
+    }
+    return currentAllDay;
+  }
+
+  // 深拷贝日期对象 并且 将日期对象中的时、分、秒、毫秒归零
+  public static normalizeDate(date: Date): Date {
+    return new Date(date.getFullYear(), date.getMonth(), date.getDate());
+  }
+
+  // 在是否在日期范围内
+  public static isDateBetween(target: Date, start: Date, end: Date): boolean {
+    const time = target.getTime();
+    return time > start.getTime() && time < end.getTime();
+  }
+
+  public static isDateBetweenByTimestamp(target: Date, startTime: number, endTime: number): boolean {
+    const targetTime = target.getTime();
+    return targetTime > startTime && targetTime < endTime;
+  }
+
+
+  static getCachedTime(date: Date): number {
+    const key = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
+    if (!YTDateUtil.timestampCache.has(key)) {
+      YTDateUtil.timestampCache.set(key, date.getTime());
+    }
+    return YTDateUtil.timestampCache.get(key)!;
+  }
+
+
+  //计算两个日期的天数差
+  static getDaysBetweenDates(startDate: Date, endDate: Date): number {
+    const startTime = startDate.getTime();
+    const endTime = endDate.getTime();
+    const diffTime = Math.abs(endTime - startTime);
+    const diffDays =Math.floor(diffTime / (1000 * 3600 * 24)) + 1;
+    return diffDays;
+  }
+
+  //获取多少年的今天
+  static getYearToday(year: number): Date {
+    const today = new Date();
+    const yearToday = new Date(today.getFullYear() + year, today.getMonth(), today.getDate());
+    return yearToday;
+  }
+
+}

BIN
commons/basic/src/main/resources/base/media/slice_sure.png


+ 1 - 1
features/feature/oh-package.json5

@@ -8,6 +8,6 @@
   "packageType": "InterfaceHar",
   "dependencies": {
     "basic": "file:../../commons/basic",
-    "user": "file:../../features/user"
+    "user": "file:../../features/user",
   }
 }

+ 45 - 1
features/feature/src/main/ets/view/MainView.ets

@@ -1,6 +1,50 @@
+import { DateOption, YTDateUtil, YTAddressSelectorDialog } from "basic"
+import { UnitType } from "basic/src/main/ets/datepicker/DatePickerEnums"
+import { promptAction } from "@kit.ArkUI"
+
 @Component
 export struct MainView {
+
   build() {
-    Text('1')
+    Text('打开日历')
+      .onClick(() => {
+        //以下内容都可以不传 有默认值
+        const date: Date = new Date()
+        const yTDateDialog = new YTAddressSelectorDialog(this.getUIContext())
+        let dateOption = new DateOption()//选项配置类
+        dateOption.date = date//当前日期  默认今天
+        dateOption.minDate = new Date(2000,0,1)//最小选择的日期 默认10年前的今天
+        dateOption.maxDate = new Date(2048,12,30)//最大选择的日期 默认10年后的今天
+        dateOption.unitType = UnitType.YEAR_MONTH_DAY//日期单位 默认年月日 可选年月、月日等组合
+        dateOption.confirm = (date: Date) => {//确认选择回调函数
+          console.log('确认', YTDateUtil.formatDate( date))
+          promptAction.openToast({message:YTDateUtil.formatDate( date)})
+        }
+        dateOption.headerLeftBuilder = wrapBuilder(headerLeftBuilder)//头部左侧按钮 默认返回按钮
+        dateOption.headerRightBuilder = wrapBuilder(headerLeftBuilder)//头部右侧按钮 默认确认按钮
+        dateOption.highlightBackgroundColor = Color.Pink//选中高亮背景色
+        dateOption.highlightBorderColor = Color.Red//选中高亮边框色
+        dateOption.textStyle = {//日期文字样式
+          font:{size:20,weight:400},
+          color:Color.Gray
+        }
+        dateOption.selectedTextStyle = {//选中日期文字样式
+          font:{size:25,weight:400},
+          color:Color.Yellow
+        }
+        yTDateDialog.show(dateOption)//设置好配置之后打开日历的函数
+      })
   }
+
+}
+@Builder
+function headerLeftBuilder(){
+  Image($r('app.media.app_icon'))
+    .width(24)
+}
+
+@Builder
+function headerRightBuilder(){
+  Image($r("app.media.app_icon"))
+    .width(24)
 }