Notification 通知
与统一布局属性的关系:
NotificationManager管理独立 overlay 卡片,不使用RenderBox.margin或 alignment;本页的margin是通知堆栈与 viewport 边缘距离。详见组件通用布局属性。
NotificationManager 用于展示非阻塞 toast 通知。通知显示在 viewport 右上角,按时间自动关闭,多条通知垂直堆叠,并带滑入和淡出动画。
Notification 基于 Popup 管理器注册为非交互 overlay:它不抢占焦点,不参与 popup 当前交互对象,也不会阻断底层鼠标和键盘。
API 总览
import { AppOverlayService, NotificationManager, type NotificationType,} from 'ds-ui'
| API | 类型 | 用途 |
|---|---|---|
NotificationManager |
class | 非阻塞 toast 管理器,负责通知堆叠、自动关闭、动画和 popup 注册。 |
NotificationType |
type | 通知类型:info、success、warning、error。 |
AppOverlayService |
class | 应用级通知推荐入口,统一复用 manager 并在服务释放时清理通知。 |
最小装配顺序是:应用层复用一个 NotificationManager 或 AppOverlayService,调用 show()/showNotification() 显示通知。不要为每条通知创建新的 manager。
何时使用
使用 Notification:
- 保存成功、提交成功、后台任务完成。
- 非阻塞警告或错误提示。
- 用户不需要立即决策的信息。
- 操作结果需要短暂反馈,但不应该打断当前流程。
不要使用:
最小示例
const notificationManager = new NotificationManager() notificationManager.show( 'success', '保存成功', '病历内容已保存。',)
show() 返回通知 id,可用于后续程序化关闭:
const notificationManager = new NotificationManager()const id = notificationManager.show('warning', '网络较慢', '正在重试...', 0) notificationManager.close(id)
duration 为 0 时不会自动关闭,需要调用 close(id) 或 close()。
AppOverlayService 示例
业务页面通常通过应用 overlay 服务显示通知:
const overlayService = new AppOverlayService() overlayService.showNotification( 'error', '保存失败', '请检查网络后重试。', 5000,)
关闭单条或全部通知:
const overlayService = new AppOverlayService()const id = overlayService.showNotification('info', '正在同步') overlayService.closeNotification(id)overlayService.closeNotification()
构造参数
new NotificationManager() 不接收 options。通知类型、标题、消息和自动关闭时间通过 show(type, title, message?, duration?) 传入。
AppOverlayService.showNotification(type, title, message?, duration?) 使用同一组 show 参数。应用层通常只创建一个 NotificationManager 或一个 AppOverlayService,不要为每条通知创建 manager。详细参数见下方 show 参数。
NotificationType
type NotificationType = 'info' | 'success' | 'warning' | 'error'
| 类型 | 语义 | 当前图标 | 强调色 |
|---|---|---|---|
info |
普通信息 | ℹ |
蓝色 |
success |
成功反馈 | ✓ |
绿色 |
warning |
警告提醒 | ⚠ |
黄色 |
error |
错误反馈 | ✕ |
红色 |
图标和强调色由组件内部常量定义,目前不是公开配置项。整体卡片背景、边框、阴影、字号和间距来自主题派生 token。
NotificationManager API
| API | 类型 | 说明 |
|---|---|---|
overlayLayer |
'tooltip' |
popup 绘制层。 |
cardWidth |
number |
当前主题下卡片宽度。默认 280。 |
cardHeight |
number |
当前主题下卡片高度。 |
cardSpacing |
number |
多条通知之间的垂直间距。 |
margin |
number |
与 viewport 顶部/右侧的距离。 |
hasItems |
boolean |
当前是否有通知。 |
show(type, title, message?, duration?) |
number |
显示通知并返回 id。默认 duration 为 3000ms。 |
close(id) |
void |
关闭指定通知,启动淡出动画。 |
close() |
void |
立即关闭所有通知并注销 popup。 |
hitTest(point, context?) |
boolean |
始终返回 false,通知不拦截鼠标。 |
paint(context, offset) |
void |
绘制所有通知卡片。 |
dispose() |
void |
关闭所有通知并清理计时器、动画资源。 |
AppOverlayService 对应入口:
| API | 说明 |
|---|---|
showNotification(type, title, message?, duration?) |
转发到内部 NotificationManager.show()。 |
closeNotification(id?) |
未传 id 时关闭全部;传 id 时关闭指定通知。 |
show 参数
show(
type: NotificationType,
title: string,
message = '',
duration = 3000,
): number
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
type |
NotificationType |
必填 | 通知类型。 |
title |
string |
必填 | 主标题。 |
message |
string |
'' |
副文本。为空时标题垂直居中。 |
duration |
number |
3000 |
自动关闭延迟,单位 ms。0 或负数表示不自动关闭。 |
通知关闭分两步:
- 到达
duration或调用close(id)后进入 closing 状态,启动 300ms 淡出动画。 - 约 320ms 后移除该项,dispose 对应动画和计时器。
重复关闭同一个 id 是安全的:已经 closing 或不存在的通知会被忽略。
堆叠和定位
通知从右上角向下排列:
x = viewport.width - margin - cardWidth + slideX
y = margin + index * (cardHeight + cardSpacing)
打开动画期间,slideX 会让通知从右侧滑入。关闭动画期间,透明度会从当前值淡出。
当前实现没有最大显示数量限制,也不会自动合并相同通知。高频业务事件应在业务层合并、节流或替换为状态栏/任务中心,避免通知堆满屏幕。
交互行为
| 行为 | 说明 |
|---|---|
| 鼠标点击 | 不拦截。hitTest() 始终为 false。 |
| 键盘焦点 | 不获取焦点。 |
| Popup 当前对象 | openPopup({ interactive: false }),因此 PopupManager.current 仍为 null 或其他交互 popup。 |
| 自动关闭 | duration > 0 时通过内部 disposable timer 自动关闭。 |
| 手动关闭 | 只能通过 close(id) 或 close() 程序化关闭。当前没有可点击关闭按钮。 |
| 与 Modal/Loading 共存 | 通知作为被动 overlay,不会主动关闭 Modal、Loading、Popover 等交互 popup。 |
因为通知不阻断交互,它适合作为结果反馈,不适合承载必须阅读或必须处理的信息。
绘制和主题
通知绘制顺序:
- 卡片阴影。
- 卡片背景。
- 左侧类型强调色条。
- 卡片边框。
- 类型图标。
- 标题。
- 可选 message。
主题 token 来自 deriveNotificationStyle(theme):
| Token | 说明 |
|---|---|
cardWidth、cardHeight、cardSpacing、margin |
尺寸和堆叠间距。 |
accentWidth |
左侧强调色条宽度。 |
bgColor、borderColor、borderWidth、borderRadius |
卡片背景和边框。 |
shadowColor、shadowBlur、shadowOffsetX/Y |
阴影。 |
titleText、messageText |
标题和消息文本颜色。 |
fontSize、messageFontSize、iconFontSize、fontFamily |
字体。 |
padding |
卡片内部间距。 |
cardHeight、cardSpacing、margin 会随主题字号、间距和窗口 padding 派生;切换主题后下一次读取和绘制会使用新值。
生命周期
创建方式通常有两种:
const notificationManager = new NotificationManager()notificationManager.show('info', '已连接') notificationManager.dispose()
或者由 AppOverlayService 统一持有:
const overlayService = new AppOverlayService()overlayService.showNotification('success', '完成') overlayService.dispose()
dispose() 会:
- 关闭所有通知。
- dispose 每条通知的 enter/exit 动画。
- 清理自动关闭 timer 和移除 timer。
- 注销 popup overlay。
不要为每次通知都创建新的 manager。应用层应复用同一个 NotificationManager 或使用 AppOverlayService。
性能边界
- 每条通知都有 enter 和 exit 动画,会在动画期间请求 overlay 重绘。
- 自动关闭依赖 timer;大量通知会产生大量 timer 和动画对象。
- 当前没有内置队列上限和去重策略。业务应避免把高频事件直接映射为通知。
- 长标题/长 message 当前不会自动换行,绘制会被卡片宽度视觉约束限制不足。通知文案应短而明确。
常见问题
为什么点不到通知?
当前通知是被动 toast,不是交互卡片。hitTest() 始终返回 false,鼠标会继续命中底层内容。
如何让通知不自动消失?
把 duration 传为 0 或负数,然后保存 show() 返回的 id,后续调用 close(id)。
为什么关闭后不是立即消失?
close(id) 会启动淡出动画,约 320ms 后从列表移除。close() 不传 id 时会立即清空全部通知。
是否有关闭按钮?
没有。当前 UI 不绘制关闭按钮,也不处理通知点击。需要用户必须处理的信息应使用 Modal 或业务自定义浮层。
多条通知会不会自动限制数量?
不会。通知会持续向下堆叠。复杂业务系统应在业务层做合并、限流或替换为任务中心。