# 日期时间筛选与预约面板

本页给出一个日期时间组合示例，演示 `DatePicker`、`TimePicker`、`DatePickerRange`、`Button`、`Alert` 的协作方式。适合报表查询区间、巡检预约、会议预定、计划生效期等业务界面。

示例采用 aardio 推荐模式：在 `/*DSG{{*/ ... /*}}*/` 中放置多个语义化 `custom` 宿主，再用 `AntdUI.Control(winform.host)` 自然嵌入控件。每个控件由 aardio 窗体设计器负责位置、DPI 缩放与锚定，不需要手写大量 .NET `SetBounds`。

## 完整示例

```aardio
import win.ui;
import dotNet.AntdUI;
import System;
/*DSG{{*/
var winform = win.form(text="巡检预约与报表区间";right=900;bottom=520;bgcolor=0xF7F8FA)
winform.add(
btnApplyHost={cls="custom";text="应用筛选";left=24;top=264;right=140;bottom=302;dl=1;dt=1;frame=1;z=8};
btnClearHost={cls="custom";text="清空";left=794;top=192;right=876;bottom=226;dr=1;dt=1;frame=1;z=7};
btnMonthHost={cls="custom";text="本月";left=692;top=192;right=782;bottom=226;dr=1;dt=1;frame=1;z=6};
btnWeekHost={cls="custom";text="近 7 天";left=590;top=192;right=680;bottom=226;dr=1;dt=1;frame=1;z=5};
dateHost={cls="custom";left=24;top=118;right=280;bottom=152;dl=1;dt=1;edge=1;z=2};
rangeHost={cls="custom";left=24;top=192;right=556;bottom=226;dl=1;dt=1;edge=1;z=4};
resultHost={cls="custom";left=24;top=330;right=876;bottom=490;db=1;dl=1;dr=1;dt=1;frame=1;z=9};
timeHost={cls="custom";left=300;top=118;right=556;bottom=152;dl=1;dt=1;edge=1;z=3};
tipsHost={cls="custom";left=24;top=20;right=876;bottom=86;dl=1;dr=1;dt=1;frame=1;z=1}
)
/*}}*/

var tips = AntdUI.Alert(winform.tipsHost);
tips.TextTitle = "日期时间筛选";
tips.Text = "左侧设置巡检预约时间，下方设置报表查询区间。按钮会快速填充最近 7 天或本月范围。";
tips.Icon = AntdUI.TType.Info;
tips.CloseIcon = false;
tips.Radius = 8;

var datePicker = AntdUI.DatePicker(winform.dateHost);
datePicker.Format = "yyyy-MM-dd";
datePicker.PrefixText = "日期";
datePicker.PlaceholderText = "选择巡检日期";
datePicker.AllowClear = true;
datePicker.ShowButtonToDay = true;
datePicker.DropDownArrow = true;
datePicker.Radius = 6;
datePicker.Value = System.DateTime(2026,7,8);

var timePicker = AntdUI.TimePicker(winform.timeHost);
timePicker.Format = "HH:mm";
timePicker.PrefixText = "时间";
timePicker.PlaceholderText = "选择开始时间";
timePicker.AllowClear = true;
timePicker.ShowButtonNow = true;
timePicker.DropDownArrow = true;
timePicker.Radius = 6;
timePicker.Value = System.TimeSpan(9,30,0);

var rangePicker = AntdUI.DatePickerRange(winform.rangeHost);
rangePicker.Format = "yyyy-MM-dd";
rangePicker.PrefixText = "区间";
rangePicker.PlaceholderStart = "开始日期";
rangePicker.PlaceholderEnd = "结束日期";
rangePicker.AllowClear = true;
rangePicker.DropDownArrow = true;
rangePicker.Radius = 6;

var btnWeek = AntdUI.Button(winform.btnWeekHost);
btnWeek.Type = AntdUI.TTypeMini.Primary;

var btnMonth = AntdUI.Button(winform.btnMonthHost);
btnMonth.Type = AntdUI.TTypeMini.Default;

var btnClear = AntdUI.Button(winform.btnClearHost);
btnClear.Type = AntdUI.TTypeMini.Warn;

var btnApply = AntdUI.Button(winform.btnApplyHost);
btnApply.Type = AntdUI.TTypeMini.Primary;
btnApply.Shape = AntdUI.TShape.Round;

var result = AntdUI.Alert(winform.resultHost);
result.TextTitle = "当前条件";
result.Text = "等待选择。";
result.Icon = AntdUI.TType.Success;
result.CloseIcon = false;
result.Radius = 8;

var makeDateArray = function(startDate,endDate){
    // DatePickerRange.Value 是 .NET System.DateTime[]，数组索引是 0 基。
    var arr = System.Array.CreateInstance(System.DateTime,2);
    arr.SetValue(startDate,0);
    arr.SetValue(endDate,1);
    return arr;
}

var setRange = function(startDate,endDate){
    rangePicker.Value = makeDateArray(startDate,endDate);
}

var formatDateValue = function(value){
    if(!value) return "未选择";
    return value.ToString("yyyy-MM-dd");
}

var formatTimeValue = function(value){
    if(!value) return "未选择";
    return value.ToString();
}

var formatRangeValue = function(value){
    if(!value || value.Length < 2) return "未选择";
    return value.GetValue(0).ToString("yyyy-MM-dd") + " 至 " + value.GetValue(1).ToString("yyyy-MM-dd");
}

var refreshResult = function(reason="已更新"){
    result.TextTitle = reason;
    result.Text = "巡检日期：" + formatDateValue(datePicker.Value)
        ++ '\r\n开始时间：' ++ formatTimeValue(timePicker.Value)
        ++ '\r\n报表区间：' ++ formatRangeValue(rangePicker.Value);
}

btnWeek.Click = function(sender,e){
    var endDate = System.DateTime.Now.Date;
    setRange(endDate.AddDays(-6),endDate);
    refreshResult("已选择最近 7 天");
}

btnMonth.Click = function(sender,e){
    var today = System.DateTime.Now.Date;
    var startDate = System.DateTime(today.Year,today.Month,1);
    setRange(startDate,startDate.AddMonths(1).AddDays(-1));
    refreshResult("已选择本月");
}

btnClear.Click = function(sender,e){
    datePicker.Clear();
    timePicker.Clear();
    rangePicker.Clear();
    refreshResult("已清空");
}

btnApply.Click = function(sender,e){
    refreshResult("筛选条件已应用");
}

datePicker.ValueChanged = function(sender,e){
    refreshResult("巡检日期已改变");
}

timePicker.ValueChanged = function(sender,e){
    refreshResult("开始时间已改变");
}

rangePicker.ValueChanged = function(sender,e){
    refreshResult("报表区间已改变");
}

setRange(System.DateTime(2026,7,1),System.DateTime(2026,7,31));
refreshResult("默认条件");

winform.show();
win.loopMessage();
```

## 关键模式

### 1. 使用 .NET 类型主动赋值

`DatePicker.Value` 使用 `System.DateTime`，`TimePicker.Value` 使用 `System.TimeSpan`：

```aardio
datePicker.Value = System.DateTime(2026,7,8);
timePicker.Value = System.TimeSpan(9,30,0);
```

如果只需要日期部分，建议使用 `System.DateTime(...).Date` 或直接构造年月日。

### 2. DatePickerRange.Value 是 System.DateTime[]

`DatePickerRange.Value` 是 .NET 数组，通常包含开始与结束两个 `System.DateTime`，索引从 0 开始：

```aardio
var arr = System.Array.CreateInstance(System.DateTime,2);
arr.SetValue(System.DateTime(2026,7,1),0);
arr.SetValue(System.DateTime(2026,7,31),1);
rangePicker.Value = arr;
```

读取时也使用 0 基索引：

```aardio
var startDate = rangePicker.Value.GetValue(0);
var endDate = rangePicker.Value.GetValue(1);
```

### 3. DatePickerRange 与 DatePicker 的“今天”按钮差异

`DatePicker` 与 `Calendar` 都支持 `ShowButtonToDay`，`TimePicker` 支持 `ShowButtonNow`。但 `DatePickerRange` 是独立的日期范围选择控件，不是 `DatePicker` 的简单组合；在 AntdUI 2.4.1 的 `AntdUI.DatePickerRange` 公开属性列表中没有 `ShowButtonToDay`。范围快捷选择建议像示例一样用外部按钮或 `Presets` 实现。

### 4. Clear 与事件

三个控件都可以调用 `Clear()` 清空。`ClearClick`、`ExpandDropChanged` 等事件可按常规 .NET 事件方式赋值：

```aardio
rangePicker.ClearClick = function(sender,e){
    // 点击清除按钮
}
```

当事件中要重新设置多个控件值并刷新界面时，可按需增加 `updating` 标志避免重复触发；本示例逻辑较简单，直接刷新即可。
