Services服务¶
服务是提供特定功能的长期运行代码片段。它们可以被组件(通过 useService
)或其他服务导入。此外,它们可以声明一组依赖项。从这个意义上说,服务基本上是一个 DI(依赖注入)系统。例如,notification
服务提供了显示通知的方式,而 rpc
服务则是向 Odoo 服务器发送请求的正确方法。
以下示例注册了一个简单的服务,该服务每 5 秒显示一次通知:
import { registry } from "@web/core/registry";
const myService = {
dependencies: ["notification"],
start(env, { notification }) {
let counter = 1;
setInterval(() => {
notification.add(`Tick Tock ${counter++}`);
}, 5000);
}
};
registry.category("services").add("myService", myService);
在启动时,Web 客户端会启动 services
注册表中存在的所有服务。请注意,注册表中使用的名称即为服务的名称。
注解
大多数非组件代码应被打包到服务中,尤其是当它会产生某些副作用时。这对测试非常有用:测试可以选择哪些服务处于活动状态,从而减少不必要的副作用干扰被测试代码的可能性。
定义服务¶
服务需要实现以下接口:
- dependencies¶
可选的字符串列表。这是该服务所需的所有依赖项(其他服务)的列表。
- start(env, deps)¶
- 参数
env (
Environment()
) – 应用程序环境deps (
Object()
) – 所有请求的依赖项
- 返回
服务的值或 Promise<服务的值>
这是服务的主要定义。它可以返回一个值或一个 Promise。在这种情况下,服务加载器只需等待 Promise 解析为一个值,该值即为服务的值。
有些服务不导出任何值。它们可能只是完成自己的工作,而无需被其他代码直接调用。在这种情况下,它们的值将在
env.services
中设置为null
。
- async¶
可选值。如果提供,应为
true
或字符串列表。有些服务需要提供异步 API。例如,
rpc
服务是一个异步函数,或者orm
服务提供了一组调用 Odoo 服务器的函数。在这种情况下,使用服务的组件可能会在异步函数调用结束之前被销毁。大多数情况下,异步函数调用需要被忽略。否则可能会非常危险,因为底层组件已不再活跃。
async
标志正是为此目的而设计的:它向服务创建者发出信号,表明如果组件被销毁,则所有来自组件的异步调用都应保持挂起状态。
使用服务¶
依赖于其他服务并正确声明了其 dependencies
的服务,会在 start
方法的第二个参数中接收到对应服务的引用。
useService
钩子是组件中使用服务的正确方式。它简单地返回服务值的引用,组件随后可以使用该引用。例如:
import { useService } from "@web/core/utils/hooks";
class MyComponent extends Component {
setup() {
const rpc = useService("rpc");
onWillStart(async () => {
this.someValue = await rpc(...);
});
}
}
参考列表¶
技术名称 |
简短描述 |
---|---|
读取或修改 cookie |
|
显示图形效果 |
|
执行低级别的 HTTP 调用 |
|
显示通知 |
|
管理浏览器 URL |
|
向服务器发送请求 |
|
处理对锚点元素的点击 |
|
读取或修改窗口标题 |
|
提供与当前用户相关的一些信息 |
概述¶
技术名称:
cookie
依赖项:无
提供一种操作 cookie 的方法。例如:
cookieService.setCookie("hello", "odoo");
API¶
- current¶
表示每个 cookie 及其值的对象(如果没有值则为空字符串)
- setCookie(name[, value, ttl])¶
- 参数
name (
string()
) – 应设置的 cookie 名称value (
any()
) – 可选。如果提供,则将 cookie 设置为该值ttl (
number()
) – 可选。cookie 删除前的时间(以秒为单位,默认值为 1 年)
将 cookie
name
设置为值value
,并设置最大存活时间为ttl
- deleteCookie(name)¶
- 参数
name (
string()
) – cookie 名称
删除 cookie
name
。
效果服务¶
概述¶
技术名称:
effect
依赖项:无
效果是图形元素,可以临时显示在页面顶部,通常用于向用户反馈某些有趣的事情已发生。
一个很好的例子是彩虹人:

以下是如何显示它的方法:
const effectService = useService("effect");
effectService.add({
type: "rainbow_man", // can be omitted, default type is already "rainbow_man"
message: "Boom! Team record for the past 30 days.",
});
警告
钩子 useEffect
与效果服务无关。
API¶
- effectService.add(options)¶
- 参数
options (
object()
) – 效果的选项。它们将被传递给底层的效果组件。
显示效果。
选项由以下定义:
interface EffectOptions {
// The name of the desired effect
type?: string;
[paramName: string]: any;
}
可用效果¶
目前,唯一的效果是彩虹人。
彩虹人¶
effectService.add({ type: "rainbow_man" });
名称 |
类型 |
描述 |
---|---|---|
|
|
要在 RainbowMan 内实例化的组件类(将替换消息)。 |
|
|
如果提供了 params.Component,则可以通过此参数传递其 props。 |
|
|
消息是彩虹人持有的通知内容。 如果为用户禁用了效果,彩虹人将不会出现,而是会显示一个简单的通知作为回退方案。 如果启用了效果并提供了 params.Component,则不会使用 params.message。 消息是一个简单的字符串或表示 HTML 的字符串(如果需要 DOM 中的交互,请优先使用 params.Component)。 |
|
|
如果消息表示 HTML,请设置为 true,以便它能正确插入到 DOM 中。 |
|
|
要在彩虹中显示的图像的 URL。 |
|
|
彩虹人消失的延迟时间。
|
如何添加效果¶
效果存储在一个名为 effects
的注册表中。您可以通过提供名称和函数来添加新效果。
const effectRegistry = registry.category("effects");
effectRegistry.add("rainbow_man", rainbowManEffectFunction);
该函数必须遵循以下 API:
- <newEffectFunction>(env, params)¶
- 参数
env (
Env()
) – 服务接收到的环境params (
object()
) – 从服务的 add 函数接收到的参数。
- 返回
({Component, props} | void)
一个组件及其 props,或者为空。
该函数必须创建一个组件并返回它。此组件将挂载到效果组件容器中。
示例¶
假设我们要添加一个效果,为页面添加棕褐色滤镜。
import { registry } from "@web/core/registry";
import { Component, xml } from "@odoo/owl";
class SepiaEffect extends Component {
static template = xml`
<div style="
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: rgba(124,87,0, 0.4);
"></div>
`;
}
export function sepiaEffectProvider(env, params = {}) {
return {
Component: SepiaEffect,
};
}
const effectRegistry = registry.category("effects");
effectRegistry.add("sepia", sepiaEffectProvider);
然后,在您需要的地方调用它,您将看到结果。在此示例中,它被调用在 webclient.js 中,以便在所有地方可见。
const effectService = useService("effect");
effectService.add({ type: "sepia" });

HTTP 服务¶
概述¶
技术名称:
http
依赖项:无
虽然 Odoo 中客户端与服务器之间的大多数交互是通过 RPC`(`XMLHTTPRequest
)完成的,但有时可能需要对请求进行更底层的控制。
此服务提供了一种发送 get
和 post
HTTP 请求的方法 <https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods>。
API¶
- async get(route[, readMethod = "json"])¶
- 参数
route (
string()
) – 发送请求的目标 URLreadMethod (
string()
) – 响应内容类型。可以是 “text”、”json”、”formData”、”blob”、”arrayBuffer”。
- 返回
由 readMethod 参数定义格式的请求结果。
发送一个 get 请求。
- async post(route[, params = {}, readMethod = "json"])¶
- 参数
route (
string()
) – 发送请求的目标 URLparams (
object()
) – 要在请求的表单数据部分设置的键值数据readMethod (
string()
) – 响应内容类型。可以是 “text”、”json”、”formData”、”blob”、”arrayBuffer”。
- 返回
由 readMethod 参数定义格式的请求结果。
发送一个 post 请求。
示例¶
const httpService = useService("http");
const data = await httpService.get("https://something.com/posts/1");
// ...
await httpService.post("https://something.com/posts/1", { title: "new title", content: "new content" });
通知服务¶
概述¶
技术名称:
notification
依赖项:无
notification
服务允许在屏幕上显示通知。
const notificationService = useService("notification");
notificationService.add("I'm a very simple notification");
API¶
- add(message[, options])¶
- 参数
message (
string()
) – 要显示的通知消息options (
object()
) – 通知的选项
- 返回
关闭通知的函数
显示通知。
选项由以下定义:
名称
类型
描述
标题
字符串
为通知添加标题
类型
警告
|危险
|成功
|信息
根据类型更改背景颜色
粘性
布尔值
通知是否应保持显示直到被关闭
className
字符串
将添加到通知中的额外 CSS 类
onClose
函数
通知关闭时执行的回调函数
buttons
按钮数组(见下文)
要在通知中显示的按钮列表
autocloseDelay
数字
通知自动关闭前的持续时间(以毫秒为单位)
按钮由以下定义:
名称
类型
描述
name
字符串
按钮文本
onClick
函数
按钮点击时执行的回调函数
primary
布尔值
按钮是否应设置为主要按钮样式
示例¶
当达成销售交易时的通知,并附带一个跳转到佣金页面的按钮。
// in setup
this.notificationService = useService("notification");
this.actionService = useService("action");
// later
this.notificationService.add("You closed a deal!", {
title: "Congrats",
type: "success",
buttons: [
{
name: "See your Commission",
onClick: () => {
this.actionService.doAction("commission_action");
},
},
],
});

一秒钟后关闭的通知:
const notificationService = useService("notification");
const close = notificationService.add("I will be quickly closed");
setTimeout(close, 1000);
路由器服务¶
概述¶
技术名称:
router
依赖项:无
router
服务提供三个功能:
当前路由的信息
应用程序根据其状态更新 URL 的方法
监听每个哈希变化,并通知应用程序的其余部分
API¶
- current
可以通过
current
键访问当前路由。它是一个包含以下信息的对象:pathname (字符串)
:当前位置的路径(很可能是/web
)search (对象)
:一个字典,将 URL 中的每个搜索关键字(查询字符串)映射到其值。如果没有明确给出值,则值为空字符串hash (对象)
:与上述相同,但针对哈希中描述的值。
例如:
// url = /web?debug=assets#action=123&owl&menu_id=174
const { pathname, search, hash } = env.services.router.current;
console.log(pathname); // /web
console.log(search); // { debug="assets" }
console.log(hash); // { action:123, owl: "", menu_id: 174 }
更新 URL 是通过 pushState
方法完成的:
- pushState(hash: object[, replace?: boolean])¶
- 参数
hash (
Object()
) – 包含从某些键到某些值的映射的对象replace (
boolean()
) – 如果为真,则 URL 将被替换;否则只会更新来自hash
的键/值对。
使用
hash
对象中的每个键/值对更新 URL。如果值设置为空字符串,则仅将键添加到 URL 中,而不附加任何对应值。如果为真,
replace
参数会告诉路由器完全替换 URL 哈希(因此未出现在hash
对象中的值将被移除)。此方法调用不会重新加载页面,也不会触发
hashchange
事件或在 主总线 中触发ROUTE_CHANGE
。这是因为该方法仅用于更新 URL。调用此方法的代码有责任确保屏幕也被更新。
例如:
// url = /web#action_id=123
routerService.pushState({ menu_id: 321 });
// url is now /web#action_id=123&menu_id=321
routerService.pushState({ yipyip: "" }, replace: true);
// url is now /web#yipyip
最后,redirect
方法会将浏览器重定向到指定的 URL:
- redirect(url[, wait])¶
- 参数
url (
string()
) – 有效的 URLwait (
boolean()
) – 如果为真,则等待服务器准备就绪后重定向
将浏览器重定向到
url
。此方法会重新加载页面。wait
参数很少使用:它在某些情况下很有用,例如我们知道服务器在短时间内不可用,通常是在插件更新或安装操作之后。
注解
每当当前路由发生变化时,路由器服务会在 主总线 上发出一个 ROUTE_CHANGE
事件。
RPC 服务¶
概述¶
技术名称:
rpc
依赖项:无
rpc
服务提供了一个异步函数,用于向服务器发送请求。调用控制器非常简单:路由应作为第一个参数,可选地可以将一个 params
对象作为第二个参数传入。
// in setup
this.rpc = useService("rpc");
// somewhere else, in an async function:
const result = await this.rpc("/my/route", { some: "value" });
注解
请注意,rpc
服务被视为低级服务。它应该仅用于与 Odoo 控制器交互。若要处理模型(这是迄今为止最重要的用例),应改用 orm
服务。
API¶
- rpc(route, params, settings)¶
- 参数
route (
string()
) – 请求的目标路由params (
Object()
) – (可选)发送到服务器的参数settings (
Object()
) – (可选)请求设置(见下文)
settings
对象可以包含:xhr
,应为一个XMLHTTPRequest
对象。在这种情况下,rpc
方法将直接使用它,而不是创建一个新的对象。这在访问XMLHTTPRequest
API 的高级功能时非常有用。silent (布尔值)
如果设置为true
,Web 客户端不会提供有关挂起的 rpc 的反馈。
rpc
服务通过使用配置为与 application/json
内容类型配合使用的 XMLHTTPRequest
对象与服务器通信。因此,请求的内容显然应为 JSON 可序列化的。该服务执行的每个请求都使用 POST
HTTP 方法。
服务器错误实际上会返回带有 HTTP 状态码 200 的响应。但 rpc
服务会将其视为错误处理。
错误处理¶
rpc 可能失败的主要原因有两个:
要么 Odoo 服务器返回错误(因此我们称其为
server
错误)。在这种情况下,HTTP 请求将返回 HTTP 状态码 200,但响应对象包含一个error
键。或者存在某种其他类型的网络错误
当 rpc 失败时:
代表 rpc 的 Promise 被拒绝,因此调用代码会崩溃,除非它处理了这种情况
在主应用总线上触发一个
RPC_ERROR
事件。事件的有效载荷包含错误原因的描述:如果是服务器错误(服务器代码抛出异常)。在这种情况下,事件的有效载荷将是一个包含以下键的对象:
type = 'server'
message(字符串)
code(数字)
name(字符串)
(可选,由错误服务使用以查找处理错误时使用的适当对话框)subType(字符串)
(可选,通常用于确定对话框标题)data(对象)
(可选对象,可能包含各种键,其中debug
:主要调试信息,包括调用堆栈)
如果是网络错误,则错误描述只是一个对象
{type: 'network'}
。当发生网络错误时,会显示一个 通知,并定期联系服务器,直到服务器响应为止。服务器响应后,通知会立即关闭。
滚动服务¶
概述¶
技术名称:
scroller
依赖项:无
每当用户在 Web 客户端中点击锚点时,此服务会自动滚动到目标(如果合适)。
该服务添加了一个事件监听器以捕获文档上的 click
事件。服务会检查其 href 属性中包含的选择器是否有效,以区分锚点和 Odoo 操作(例如 <a href="#target_element"></a>
)。如果无效,则不会执行任何操作。
如果点击似乎针对某个元素,则会在主应用总线上触发一个 SCROLLER:ANCHOR_LINK_CLICKED
事件。该事件包含一个自定义事件,其中包含匹配的 element
及其 id
作为参考。它可能允许其他部分处理与锚点本身相关的行为。原始事件也会被提供,因为它可能需要被阻止。如果事件未被阻止,则用户界面将滚动到目标元素。
API¶
以下值包含在上述解释的 anchor-link-clicked
自定义事件中。
名称 |
类型 |
描述 |
---|---|---|
|
|
href 所指向的锚点元素 |
|
|
href 中包含的 id |
|
|
原始点击事件 |
注解
滚动服务会在 主总线 上发出一个 SCROLLER:ANCHOR_LINK_CLICKED
事件。为了避免滚动服务的默认滚动行为,您必须在传递给监听器的事件上调用 preventDefault()
,以便可以从监听器正确实现自己的行为。
标题服务¶
概述¶
技术名称:
title
依赖项:无
title
服务提供了一个简单的 API,用于读取或修改文档标题。例如,如果当前文档标题为 “Odoo”,我们可以通过以下命令将其更改为 “Odoo 15 - Apple”:
// in some component setup method
const titleService = useService("title");
titleService.setParts({ odoo: "Odoo 15", fruit: "Apple" });
API¶
title
服务操作以下接口:
interface Parts {
[key: string]: string | null;
}
每个键代表标题某一部分的标识,每个值是显示的字符串,或者如果已被移除则为 null
。
其 API 如下:
- current
这是一个表示当前标题的字符串。其结构如下:
value_1 - ... - value_n
,其中每个value_i
是Parts
对象中的(非空)值(由getParts
函数返回)。
- getParts()¶
- 返回
当前由标题服务维护的
Parts
对象的部分
- setParts(parts)¶
- 参数
parts (
Parts()
) – 表示所需更改的对象
setParts
方法允许添加、替换或删除标题的多个部分。删除某部分(值)是通过将关联的键值设置为null
来完成的。请注意,可以仅修改单个部分而不影响其他部分。例如,如果标题由以下部分组成:
{ odoo: "Odoo", action: "Import" }
当前值为
Odoo - Import
,那么setParts({ action: null, });
将把标题更改为
Odoo
。
用户服务¶
概述¶
技术名称:
user
依赖项:
rpc
user
服务提供了有关已连接用户的一系列数据和一些辅助函数。
API¶
名称 |
类型 |
描述 |
---|---|---|
|
|
|
|
|
数据库信息 |
|
|
用作用户主页的操作 ID |
|
|
用户是否为管理员(属于 |
|
|
用户是否属于系统组( |
|
|
使用的语言 |
|
|
用户名 |
|
|
用户对应的合作伙伴实例 ID |
|
|
用户的时区 |
|
|
用户 ID |
|
|
用户的替代昵称 |
- updateContext(update)¶
- 参数
update (
object()
) – 用于更新上下文的对象
使用给定对象更新 用户上下文。
userService.updateContext({ isFriend: true })
- removeFromContext(key)¶
- 参数
key (
string()
) – 目标属性的键
从 用户上下文 中移除具有指定键的值
userService.removeFromContext("isFriend")
- hasGroup(group)¶
- 参数
group (
string()
) – 要查找的组的 xml_id
- 返回
Promise<boolean>
表示用户是否在该组中
检查用户是否属于某个组
const isInSalesGroup = await userService.hasGroup("sale.group_sales")