前端

Odoo 为什么把提示、庆祝和彩蛋分成两条线:notification、effect 与 display_notification 动作讲透

很多系统把“提示用户”做成一个组件,最后成功提示、错误消息、庆祝动画和操作按钮全搅在一起。Odoo 则把 notification、effect 和客户端动作拆开,让业务逻辑、视觉反馈和降级策略各自承担不同责任。

前端
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

“给用户一个提示”听起来像一件小事,但做久了就会发现,这里面至少混着三类完全不同的需求:

  1. 告诉用户发生了什么 —— 成功、失败、警告、信息;
  2. 强化某个成就感瞬间 —— 比如保存成功后的庆祝感;
  3. 让后端动作能在前端立即给出反馈 —— 不要求业务代码自己懂 UI 细节。

很多系统一开始偷懒,把这三件事全塞进一个 toast 组件。结果通常是:

  • 成功提示和错误提示长得都一样;
  • 想做庆祝动画时只能硬塞自定义 HTML;
  • 关闭逻辑、自动消失时机、可交互按钮越来越混乱;
  • 功能降级时更是一团糟。

Odoo 在这里反而做得很清楚。顺着 notification_service.jseffect_service.jswebclient/actions/client_actions.js 去看,可以总结成一句话:

通知、特效、动作入口三者分工明确,但又能互相接力。

一、notification 解决的是“可管理的信息呈现”

notificationService.start() 创建了一个响应式 notifications 容器,并把 NotificationContainer 注册到 main_components

这一步非常关键,它意味着 notification 不是某个页面临时 render 出来的孤立组件,而是应用主界面的一部分基础设施。

随后 add(message, options) 会:

  • 生成唯一 id;
  • 组装 props;
  • 把通知写入响应式对象;
  • 返回一个 closeFn

这个接口设计很干净,说明 notification 的职责就是:

  • 在主界面上稳定出现;
  • 允许业务层传标题、类型、按钮、sticky 等选项;
  • 同时把关闭控制权显式交回调用方。

为什么返回 closeFn 很重要

因为这代表 notification 不只是“一次性广播”,而是一个可被业务逻辑继续管理的 UI 实体。

调用方可以:

  • 在异步成功后手动关闭;
  • 任务取消时撤回提示;
  • onClose 做额外清理。

这比只给个 toast API 然后完全失控,要成熟得多。

二、effect 处理的是“视觉强化”,不是普通消息

effectService 默认依赖 overlay,而 rainbow_man 效果通过 effectRegistry 注册。

这里最有味道的地方在于:

  • effect 不是往 notification 容器里再塞一条特别样式;
  • 它本质上是往 overlay 层注入一个组件。

这说明 Odoo 对“通知”和“效果”的理解完全不同:

  • notification:属于主界面信息流;
  • effect:属于临时视觉层,强调情绪与反馈强度。

这也是为什么 RainbowMan 可以拥有:

  • 自己的图片;
  • fadeout 策略;
  • 自定义组件与 props;
  • 基于 overlay 的独立关闭时机。

如果把它硬做成 notification 皮肤,很多语义都会变得别扭。

三、最精彩的细节:特效关闭时,不是静默失败,而是优雅降级到 notification

rainbowMan(env, params) 里有个非常值得点赞的判断:

  • 如果 user.showEffect 为真,就走视觉特效;
  • 否则直接 env.services.notification.add(message)

这说明 Odoo 不是把“特效关闭”理解成“那就什么都不提示了”,而是理解成:

视觉增强可以不要,但语义反馈必须保留。

这是很成熟的产品与前端共同设计思路。

因为用户真正需要的是“我完成了一件事,并且系统确认了它”。彩虹人只是强化手段,不是语义本身。

四、display_notification 客户端动作,把业务反馈入口统一了

client_actions.js 里的 displayNotificationAction 让服务器侧动作可以直接下发一段前端提示描述,包括:

  • title
  • type
  • sticky
  • className
  • message
  • links
  • next

这个设计特别适合 Odoo 这种强服务端动作系统。

因为它避免了很多不必要的耦合:

  • Python 端不用知道前端 notification 组件长什么样;
  • 前端也不用为每个后端动作专门写一份提示代码;
  • 动作执行链还能通过 params.next 继续串下去。

换句话说,display_notification 充当的是业务结果到 UI 反馈的标准桥梁

五、为什么 notification 和 effect 都要走 registry/服务化,而不是直接 import 组件调用

因为提示系统最怕“谁都能随手 new 一个组件”。

一旦这样做,问题会快速出现:

  • 提示显示层级不一致;
  • 样式容器分散;
  • 关闭时机和生命周期无法统一;
  • 业务代码越来越耦合具体 UI。

Odoo 让它们都挂到 service 层,有两个明显收益:

1)反馈入口统一

调用方只关心 env.services.notification.add(...)env.services.effect.add(...)

2)呈现容器统一

notification 在主组件树,effect 在 overlay 层,各自有稳定宿主。

这种分工能让系统长大后依然清晰。

六、notification 的响应式对象设计,也透露了它的性能与状态观

notificationsreactive({}),而不是 append 一个数组后到处同步。

这表示 Odoo 希望:

  • 新通知进来时局部更新;
  • 关闭通知时按 id 删除;
  • 容器自然跟随响应式刷新。

这是一种很顺手的状态模型:通知集合本质上是“按 id 管理的活跃实例表”。

对于按钮、sticky、手动关闭、回调清理这些需求来说,比纯数组更稳。

七、二开时的好习惯:把“语义反馈”和“视觉花样”分开设计

很多自定义模块喜欢把成功提示写成:

  • 一个 notification 里塞 HTML;
  • 或者直接在 action 完成后插入一段自定义 DOM 动画。

短期很爽,长期会越来越难维护。

更好的思路是先问:

  1. 这条反馈的核心语义是什么?
  2. 它应该属于主界面消息,还是临时强化效果?
  3. 如果禁用动画、简化模式、移动端受限,信息还能不能保留?

一旦你用 Odoo 的 notification/effect 分工去设计,很多决定会自然清晰:

  • 可读信息进 notification;
  • 情绪强化进 effect;
  • 后端动作触发前端提示时,优先走标准 client action。

八、总结:Odoo 不是把“提示”做复杂了,而是把责任拆清了

看完这三块源码,最值得学的并不是 RainbowMan 本身,而是责任划分:

  • notification 管理信息提示;
  • effect 管理短时视觉强化;
  • display_notification 提供服务端到前端的标准反馈入口;
  • 禁用特效时自动降级到 notification,保证语义不丢。

这套设计的结果是:

  • 提示层不会无限臃肿;
  • 视觉彩蛋不会污染信息系统;
  • 后端动作也能自然接入前端反馈。

说到底,好的用户反馈体系,不是“有很多提示组件”,而是让信息、情绪、动作入口各自有明确位置

Odoo 在这一点上,做得相当克制,也相当成熟。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。