ArkUI TimePicker:跨午夜值班段零点换日与草稿提交校验【鸿蒙心迹】
说明:本文以
NightWindow演示项目、NightShiftPage页面和任务NW-3008解释一套可落地的状态约束。界面、HiLog、时间与保存结果均为人为构造的演示数据,并非真实 DevEco Studio、真机测试或上线审核结论。本文只使用已在公开接口文档核对的TimePicker、onChange、useMilitaryTime等基础能力,跨日规则是应用业务实现。
一、表面只是两个时间,真正难的是“第二天”
值班排班页面最容易出现一种不显眼的错误:用户把开始时间设为22:30、结束时间设为06:30,产品却提示“结束时间不能早于开始时间”。从界面上看两侧都是正常的时间选择器,错误来自业务把两串时分放在同一个自然日比较。夜班天生跨过零点,结束时刻小于开始时刻,反而可能是一个合法结果。
我在 NightWindow 中没有直接引入日期选择器,也不依赖设备当前是星期几。这个演示只编辑一段日内循环的值班窗口,并约定结束时间不晚于开始时间时表示次日结束。若真实业务涉及多个日期、夏令时或调班,请另外持久化绝对日期和时区,不能把这个简化模型当成完整排班引擎。
这里还有一个经常在评审时被忽略的显示细节:跨日图标必须来自当前草稿的计算结果,不能根据结束时间是否小于某个固定值猜测。比如从03:00排到04:30属于当日,结束时间虽然在凌晨,却不应该出现“次日”;从23:50排到00:50则刚好60分钟,属于合法的跨日下限。把这两组作为单元测试,比肉眼观察滑轮顺畅程度更能检验业务正确性。
版本变化的处理也应当写进接口契约。如果服务端后来增加“夜班最长10小时”的规定,本地页面即使还显示480分钟通过,保存请求也必须携带规则版本并由服务端复核。客户端的绿色提示只表示当前内存数据通过了本地规则,不能作为跨设备并发排班已经成功的凭证。
页面先保留旧的确认值 22:00→06:00,用户正在编辑的新草稿是 22:30→06:30。两者都持续480分钟,但并不相等。若用户尚未点击保存,退出页面不应该把草稿偷换成已确认窗口。

二、先约定一条可回放的数据契约
本篇采用分钟整数作为唯一业务事实。22:30 对应1350,06:30 对应390;界面的字符串只用于显示,绝不用于比较。结束分钟不大于开始分钟时,将结束分钟加上1440构造下一日的相对位置,再计算持续时长。这样的好处是它不依赖当下系统日历,回放日志也可以得到相同结果。
规则限制单次值班至少60分钟、最多720分钟。两个时间相同会得到1440分钟,必须拒绝,而不是悄悄变成零分钟。输入里还要检查是否为整数、是否位于0到1439之间,避免调用处的错误值直接进入持久化层。例子中跨日标记为 day+1,验证结果为 READY_TO_SAVE,这里的“准备提交”只是业务检查结果,不代表任何系统服务已经保存。
这一段代码解决的是“午夜换日和非法时长”两个一起发生的问题。校验结果只由传入的两个整数决定,不读系统时钟,不操作页面状态,也没有需要释放的资源。
interface WindowCheck {
ok: boolean;
minutes: number;
crossesDay: boolean;
reason: string;
}
function checkNightWindow(start: number, end: number): WindowCheck {
const integerInput: boolean = Number.isInteger(start) && Number.isInteger(end);
if (!integerInput || start < 0 || start >= 1440 || end < 0 || end >= 1440) {
return { ok: false, minutes: 0, crossesDay: false, reason: 'BAD_TIME' };
}
const crossesDay: boolean = end <= start;
const absoluteEnd: number = crossesDay ? end + 1440 : end;
const duration: number = absoluteEnd - start;
if (duration < 60 || duration > 720) {
return { ok: false, minutes: duration, crossesDay, reason: 'BAD_DURATION' };
}
return { ok: true, minutes: duration, crossesDay, reason: 'READY_TO_SAVE' };
}
返回对象之所以包含 reason,是为了让保存按钮、页内提示和诊断记录读取同一份结论。生产项目还应给每次提交分配唯一修订号,防止多个窗口同时修改同一排班;本文的示例只有单页本地草稿,不编造后端事务成功的结果。
三、TimePicker只负责选值,别让它替业务确认
TimePicker提供小时和分钟的滑动选择,onChange 返回的时间结果包含 hour、minute。这里选择24小时展示,既方便夜班人员核对,也减少上下午文本造成的歧义。重要的是不要把“轮盘滚动一次”写成“确认提交一次”:轮盘更新只能改草稿,旧确认值必须独立存放。
公开接口资料还明确提醒,不建议在选择器动效过程中频繁修改其配置。为避免因为状态回写导致轮盘抖动,我给组件的初始选中值提供稳定的 Date 对象;回调只更新分钟整数,并由旁边的预览文本渲染跨日结果。真实产品如果要响应外部重置,应该在安全的交互边界重建选中值并进行设备测试,不能假定任意时刻强制改值都不会影响滚动。
下面这段是页面核心。它解决“草稿变化误覆盖确认值”和“退出页面仍把旧预览当成保存成功”的问题,保存按钮只有在校验通过时才写入确认状态。
@Entry
@Component
struct NightShiftPage {
@State private draftStart: number = 1350;
@State private draftEnd: number = 390;
@State private savedStart: number = 1320;
@State private savedEnd: number = 360;
@State private status: string = 'READY_TO_SAVE';
@State private startSeed: Date = new Date(2026, 9, 13, 22, 30);
@State private endSeed: Date = new Date(2026, 9, 14, 6, 30);
private asDate(minutes: number): Date {
return new Date(2026, 9, 13, Math.floor(minutes / 60), minutes % 60);
}
private format(minutes: number): string {
const h: string = `${Math.floor(minutes / 60)}`.padStart(2, '0');
const m: string = `${minutes % 60}`.padStart(2, '0');
return `${h}:${m}`;
}
private preview(): void {
this.status = checkNightWindow(this.draftStart, this.draftEnd).reason;
}
private commit(): void {
const result: WindowCheck = checkNightWindow(this.draftStart, this.draftEnd);
if (!result.ok) {
this.status = result.reason;
return;
}
this.savedStart = this.draftStart;
this.savedEnd = this.draftEnd;
this.status = 'SAVED_LOCAL';
}
private discard(): void {
this.draftStart = this.savedStart;
this.draftEnd = this.savedEnd;
// 在用户明确撤回的交互边界刷新选中时刻;不在滚轮拖动中回写。
this.startSeed = this.asDate(this.savedStart);
this.endSeed = this.asDate(this.savedEnd);
this.status = 'DRAFT_RESTORED';
}
build() {
Column({ space: 12 }) {
Text('NW-3008 | NightWindow').fontSize(18)
Text(`已确认:${this.format(this.savedStart)} → ${this.format(this.savedEnd)}`)
TimePicker({ selected: this.startSeed }).useMilitaryTime(true)
.onChange((value: TimePickerResult) => {
this.draftStart = value.hour * 60 + value.minute;
this.preview();
})
TimePicker({ selected: this.endSeed }).useMilitaryTime(true)
.onChange((value: TimePickerResult) => {
this.draftEnd = value.hour * 60 + value.minute;
this.preview();
})
Text(`草稿校验:${this.status}`)
Button('保存值班段').onClick(() => this.commit())
Button('撤回草稿').onClick(() => this.discard())
}.width('100%').padding(16)
}
}
示例中“已确认”文字由 savedStart 和 savedEnd 动态格式化,保存后同步改变。只有明确点击撤回才替换 startSeed 和 endSeed 的选中时刻;这一行为应在用户停止滚动的交互边界进行,不能在选择器动画进行中强制回写。页面没有创建定时器或文件句柄,所以退出时无额外系统资源需要释放;但它也没有实现后端提交,SAVED_LOCAL 只表示内存状态变化。若应用需持久化,要接入受控的数据访问层,并在写入失败时保持原确认值。

四、把合法性判断放在提交点,而不是只在手势回调中
做这类编辑页时,我更在意一次很普通的操作:用户滑到一个无效短时段,界面立刻标红;随后恢复为合法时段,点击保存之前又发生外部规则更新。仅依赖上次 onChange 的缓存布尔值就容易错,因为缓存可能早于最后一次编辑,也可能来自旧规则版本。最稳妥的做法是在 commit() 再计算一遍,并在实际接入服务端时携带当次规则版本。
这里可以用三个确定性的输入检查不变量。第二个用23:30到00:15制造45分钟非法区间;第三个用同一时间制造24小时窗口并拒绝。测试函数不依赖UI,调用它不会产生后台线程,检查结束不需要释放;真实设备日志不能直接引用以下手写的期望输出声称跑过测试。
const valid: WindowCheck = checkNightWindow(1350, 390); // 22:30→06:30
const shortShift: WindowCheck = checkNightWindow(1410, 15); // 45分钟
const sameClock: WindowCheck = checkNightWindow(1350, 1350); // 1440分钟
console.info(`NW-3008 PREVIEW 22:30->06:30 day+1 ${valid.minutes}m`);
console.info(`NW-3008 CHECK ${valid.reason}`);
console.info(`NW-3008 REJECT ${shortShift.reason} ${shortShift.minutes}m`);
console.info(`NW-3008 REJECT ${sameClock.reason} ${sameClock.minutes}m`);
这四条只作为本文统一的“演示日志”,不是从DevEco Studio抓取的运行记录。断言应该分别检查 valid.ok===true、另外两项 ok===false,并测试0分钟、720分钟边界和非整数输入。未来如需处理跨两个自然日的值班,就要把规则升级成包含起止日期的结构,而不是继续给相对结束分钟叠加1440。
五、页面上的证据应该展示状态差异
我把演示屏幕拆成“已确认”和“正在编辑”两个区块,展示初始确认 22:00→06:00 与草稿 22:30→06:30。草稿旁明确显示 480分钟 / day+1 / READY_TO_SAVE。截图顶部包含状态栏,但页面中的时间、网络、电池以及日志全部是假定的演示界面元素,不代表任何真实手机采集。

可以直接拿这张图作为验收清单:滑动只改变草稿;跨午夜仍能通过;45分钟被拒绝;撤回后回到已确认时间;保存时重新验证;退出前若没有持久化写入就不应把数据声称“已同步”。这些都是可以独立检查的业务条件,不需要借助未验证的系统权限。
六、收口:组件解决选值,工程代码负责约束
TimePicker把小时和分钟交给我们,不能替我们理解夜班跨日、连续工作上限与业务审核。把分钟数、跨日标识和提交门禁拆开之后,出错时更容易定位:是组件返回值异常、草稿回写错误,还是持久化写入失败。这个 Demo 的边界也很清楚:固定演示日期、不讨论夏令时、不做后台排班任务、不产生真实写盘结果。下一步如果接入生产排班数据,应先补提交修订号、持久化失败恢复与真实多设备时区回归,而不是先在画面上添加更多成功提示。
参考资料
- 华为 HarmonyOS 开发者文档:ArkTS 简介
- OpenHarmony 官方项目源码文档:TimePicker API 参考
更多推荐



所有评论(0)