# Calendar 项目排期日历

本页给出一个 Calendar 排期面板示例，演示 `Calendar`、`DateBadge`、`Alert`、`Button` 的组合用法。适合设备维保、课程安排、项目里程碑、值班计划等按日期组织的业务界面。

示例仍采用 aardio 推荐模式：在 `/*DSG{{*/ ... /*}}*/` 中放置多个语义化 `custom` 宿主，再用 `AntdUI.Control(winform.host)` 自然嵌入控件。这样窗口缩放、DPI 缩放和边距固定由 aardio 设计器维护，不需要在 .NET 控件内部手写大量 `SetBounds`。

## 完整示例

```aardio
import win.ui;
import dotNet.AntdUI;
import System;
import System.Drawing;
/*DSG{{*/
var winform = win.form(text="项目排期日历";right=920;bottom=600;bgcolor=0xF7F8FA)
winform.add(
calendarHost={cls="custom";left=20;top=20;right=610;bottom=560;db=1;dl=1;dt=1;edge=1;z=1};
detailHost={cls="custom";left=630;top=20;right=900;bottom=190;dl=1;dr=1;dt=1;frame=1;z=2};
legendHost={cls="custom";left=630;top=410;right=900;bottom=560;db=1;dl=1;dr=1;dt=1;z=7};
nextHost={cls="custom";text="下一项";left=820;top=350;right=900;bottom=386;dr=1;dt=1;frame=1;z=6};
prevHost={cls="custom";text="上一项";left=630;top=350;right=710;bottom=386;dl=1;dt=1;frame=1;z=4};
summaryHost={cls="custom";left=630;top=205;right=900;bottom=330;dl=1;dr=1;dt=1;frame=1;z=3};
todayHost={cls="custom";text="今天";left=725;top=350;right=805;bottom=386;dl=1;dt=1;frame=1;z=5}
)
/*}}*/

// 业务数据使用普通 aardio 数组保存；Calendar 只负责展示与选择日期。
var plans = [
    {date="2026-07-06"; type="巡检"; title="核心机房巡检"; desc="检查 UPS、温湿度、告警面板和备份链路。"; color=0xFF1677FF},
    {date="2026-07-09"; type="保养"; title="冷却系统保养"; desc="更换滤网，复核冷却水压力与风机状态。"; color=0xFF52C41A},
    {date="2026-07-15"; type="评审"; title="月度容量评审"; desc="评估存储、带宽和计算资源的增长趋势。"; color=0xFFFAAD14},
    {date="2026-07-21"; type="发布"; title="网关策略发布"; desc="灰度发布新访问策略，观察 30 分钟后扩大范围。"; color=0xFF722ED1},
    {date="2026-07-28"; type="演练"; title="灾备切换演练"; desc="按预案切换到备站，并记录恢复时间。"; color=0xFFF5222D}
];

var parseDateText = function(dateText){
    var y,m,d = string.match(dateText,"^(\d+)-(\d+)-(\d+)$");

    if(!y) return System.DateTime.Now.Date;
    
    //tonumber 返回 2 个值，这样实际传了 4 个参数，会因参数类型不匹配找不到方法
    //System.DateTime(tonumber(y),tonumber(m),tonumber(d))
    
    y,m,d = tonumber(y),tonumber(m),tonumber(d)
    return System.DateTime(y,m,d);
}

var calendar = AntdUI.Calendar(winform.calendarHost);
calendar.Full = true;
calendar.ShowChinese = true;
calendar.ShowButtonToDay = true;
calendar.Radius = 8;
calendar.SetMinMax(System.DateTime(2026,7,1), System.DateTime(2026,7,31));

var detail = AntdUI.Alert(winform.detailHost);
detail.TextTitle = "选中日期";
detail.Text = "请选择日历中的日期查看安排。";
detail.Icon = AntdUI.TType.Info;
detail.CloseIcon = false;
detail.Radius = 8;
detail.BorderWidth = 1;

var summary = AntdUI.Alert(winform.summaryHost);
summary.TextTitle = "本月计划";
summary.Text = "共 " + #plans + " 项排期，徽标颜色表示不同类型。";
summary.Icon = AntdUI.TType.Success;
summary.CloseIcon = false;
summary.Radius = 8;

var btnPrev = AntdUI.Button(winform.prevHost);
btnPrev.Type = AntdUI.TTypeMini.Default;

var btnToday = AntdUI.Button(winform.todayHost);
btnToday.Type = AntdUI.TTypeMini.Primary;

var btnNext = AntdUI.Button(winform.nextHost);
btnNext.Type = AntdUI.TTypeMini.Default;

var legend = AntdUI.Alert(winform.legendHost);
legend.TextTitle = "图例";
legend.Text = '蓝色：巡检\r\n绿色：保养\r\n黄色：评审\r\n紫色：发布\r\n红色：演练';
legend.Icon = AntdUI.TType.Info;
legend.CloseIcon = false;
legend.Radius = 8;

var buildBadges = function(){
    var badges = dotNet.list("AntdUI.DateBadge");
    for i,item in plans {
        // DateBadge 的 date 参数使用 yyyy-MM-dd 字符串，content 是显示在日期上的徽标文本。
        var badge = AntdUI.DateBadge(item.date,item.type,item.color);
        badge.Radius = 4;
        badge.Size = 0.6; //相对 Calendar 字体的缩放比例，不是像素字号
        badges.Add(badge);
    }
    return badges;
}

calendar.SetBadge(buildBadges());
calendar.LoadBadge();

var findPlansByDate = function(dateText){
    var found = [];
    for i,item in plans {
        if(item.date == dateText) table.push(found,item);
    }
    return found;
}

var renderDate = function(dateText){
    var found = findPlansByDate(dateText);
    if(#found){
        var lines = [];
        for i,item in found {
            table.push(lines,item.type + " · " + item.title ++ '\r\n' ++ item.desc);
        }
        detail.Icon = AntdUI.TType.Success;
        detail.TextTitle = dateText + " · " + #found + " 项安排";
        detail.Text = string.join(lines,'\r\n\r\n');
    }
    else{
        detail.Icon = AntdUI.TType.Info;
        detail.TextTitle = dateText + " · 无安排";
        detail.Text = "这一天没有排期，可作为缓冲或临时维护窗口。";
    }
}

var currentPlanIndex = 1;
var jumpToPlan = function(index){
    if(index < 1) index = #plans;
    if(index > #plans) index = 1;
    currentPlanIndex = index;

    var item = plans[currentPlanIndex];
    calendar.SetDate(parseDateText(item.date));
    renderDate(item.date);
}

calendar.DateChanged = function(sender,value){
    renderDate(value.ToString("yyyy-MM-dd"));
}

btnPrev.Click = function(sender,e){
    jumpToPlan(currentPlanIndex - 1);
}

btnNext.Click = function(sender,e){
    jumpToPlan(currentPlanIndex + 1);
}

btnToday.Click = function(sender,e){
    var todayText = System.DateTime.Now.ToString("yyyy-MM-dd");
    calendar.SetDate(System.DateTime.Now.Date);
    renderDate(todayText);
}

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

## 关键模式

### 1. 业务数组是数据源，Calendar 只是视图

不要把业务数据塞进控件内部再从控件反推业务状态。示例中 `plans` 保存真实数据，`DateBadge` 只是把重要日期视觉化：

```aardio
var badge = AntdUI.DateBadge(item.date,item.type,item.color);
badges.Add(badge);
calendar.SetBadge(badges);
```

### 2. DateBadge 使用字符串日期

当前 DLL 验证的 `DateBadge` 常用构造函数使用 `yyyy-MM-dd` 字符串作为日期参数，例如：

```aardio
var badge = AntdUI.DateBadge("2026-07-06","巡检",0xFF1677FF);
badge.Radius = 4;
badge.Size = 0.6; //相对 Calendar 字体的缩放比例，不是像素字号
```

`DateBadge` 常用属性包括：

- `Date`：日期字符串。
- `Content`：徽标文本或数值内容。
- `Fill`：徽标颜色；aardio 中直接使用 `0xAARRGGBB` 数值。
- `Round` / `Radius`：圆形或圆角样式。
- `Align`：徽标相对日期单元格的对齐位置。
- `Size`：徽标字体相对于 `Calendar.Font` 的缩放比例，默认值为 `0.6`，不是像素字号；一般使用 `0.5`～`0.8`，也可以不设置而沿用默认值。误设为 `11` 会得到基础字体 11 倍的超大徽标，在高 DPI 屏幕上尤其明显。
- `OffsetX` / `OffsetY`：徽标相对日期单元格的横向与纵向偏移。

### 3. DateChanged 只负责联动显示

用户选择日期后，`DateChanged` 传入 `System.DateTime`，常用 `value.ToString("yyyy-MM-dd")` 转成业务 key：

```aardio
calendar.DateChanged = function(sender,value){
    renderDate(value.ToString("yyyy-MM-dd"));
}
```

如果在代码中调用 `calendar.SetDate(...)` 后需要立即更新右侧详情，建议像示例一样主动调用 `renderDate(dateText)`，不要依赖事件是否同步触发。

### 4. 日期范围与农历显示

```aardio
calendar.SetMinMax(System.DateTime(2026,7,1), System.DateTime(2026,7,31));
calendar.ShowChinese = true;
calendar.ShowButtonToDay = true;
```

`ShowChinese` 适合排班、节假日、课程表等场景；如果是全球化业务系统，应根据用户区域决定是否启用。
