JavaScript 参考

本文档介绍 Odoo JavaScript 框架。从代码行数上看,这个框架并不庞大,但它相当通用,因为它本质上是一台把声明式界面描述转换成可运行应用的机器,能够与数据库中的所有模型和记录交互。甚至可以使用 web 客户端来修改 web 客户端本身的界面。

概述

JavaScript 框架的设计主要面向三个使用场景:

  • web 客户端:这是私有的 Web 应用,用户可以在这里查看和编辑业务数据。这是一个单页应用(页面永远不会重新加载,只在需要时才从服务器获取新数据)

  • 网站:这是 Odoo 的公开部分。它允许未识别的用户浏览内容、购物或以客户身份执行许多操作。这是一个经典的网站:由各种带控制器的路由以及一些使其运作的 JavaScript 组成。

  • 销售点:这是销售点(POS)的界面。它是一个专门的单页应用。

这三个使用场景共用一部分 JavaScript 代码,这些代码被捆绑在一起(参见下文的资源部分)。本文档将主要聚焦于 web 客户端的架构。

Web 客户端

单页应用

Web 客户端是一个单页应用:用户每次执行操作时,它不会向服务器请求完整页面,而是只加载更新用户界面(UI)所需的内容。在此过程中,它还会更新 URL 中的信息,因此在大多数情况下,刷新页面或者关闭浏览器后重新打开,看到的仍是同样的内容。

Web 客户端 JS 代码概览

这里我们对 web 插件中的 web 客户端代码作一个非常简要的概述。路径将相对于 web/static/src 来描述。以下描述刻意不求详尽,目标只是让读者对架构有一个整体认识。

  • module_loader.js:这个文件定义了 Odoo 的 JavaScript 模块系统。它需要在任何其他 JS 模块之前加载。

  • core/:这个文件夹包含构成 JavaScript 框架最底层的代码,既可用于 web 客户端,也可用于网站、门户和销售点应用。

  • weblient/:这个文件夹包含 web 客户端专有、不能用于网站或销售点的文件,例如动作管理器和动作服务。

  • webclient/webclient.js:这是 webclient 组件本身。它主要是动作容器和导航栏的一个包装器,并在应用启动时执行一些必要的工作,例如加载 URL 的状态。

  • webclient/actions/:这个文件夹包含负责显示动作以及在动作之间切换的代码。

  • views/:这个文件夹包含视图基础设施的代码以及大部分视图(某些类型的视图由其他插件添加)。

  • views/fields/:包含各种字段组件的定义,以及多个字段共用的一些实用工具。

  • search/ 这些文件定义了搜索视图(从 web 客户端的角度看它不是一个视图,只是从服务器的角度看才是)

文件未加载/未更新时该怎么办

文件未能正确加载的原因有很多。以下是一些可以尝试的解决办法:

  • 确认您已保存文件;忘记保存这种事再老练的人也会遇上。

  • 查看控制台(在开发者工具中,通常按 F12 打开)并检查错误。

  • 尝试在文件开头添加一个 console.log(),这样就能看出文件是否已被加载。如果没有加载,可能是文件不在正确的资源包中,或者资源包尚未更新。

  • 根据您的设置,服务器在文件被修改后可能不会重新生成资源包;有以下几种解决办法:

    • 重启服务器会强制它在资源包下次被请求时检查其是否为最新版本

    • 在调试模式下,调试菜单(导航栏中的 按钮)中有一个选项,可以强制服务器在不重启的情况下即时重新生成资源包。

    • 使用 --dev=xml 选项启动服务器,会强制服务器在资源包每次被请求时检查其是否为最新版本。我们建议您在积极开发时使用该选项,但在生产环境中不要使用。

  • 修改代码后请确保刷新页面。Odoo 目前没有任何热模块重载机制。

加载 JavaScript 代码

大型应用通常会拆分成多个较小的文件,这些文件需要相互连接。某些文件可能需要使用另一个文件中定义的代码。在文件之间共享代码有两种方式:

  • 使用全局作用域(window 对象)来读写某些对象或函数的引用,

  • 使用模块系统,它为每个模块提供导出或导入值的方式,并确保各模块按正确的顺序加载。

虽然可以在全局作用域中工作,但这存在许多问题:

  • 难以确保实现细节不被暴露:全局作用域中的函数声明对所有其他代码都是可访问的。

  • 只有一个命名空间,极易产生命名冲突。

  • 依赖是隐式的:如果一段代码依赖另一段代码,二者的加载顺序很重要,却难以保证。

使用模块系统有助于解决这些问题:由于模块会声明各自的依赖,模块系统可以按正确的顺序加载它们,或在依赖缺失或存在循环依赖时抛出错误。模块还构成各自的命名空间,并且可以选择要导出的内容,从而避免暴露实现细节和发生命名冲突。

虽然我们可以直接使用 ECMAScript(ES)模块,但这种方式有不少缺点:每个 ES 模块都需要一次网络往返,当文件多达数百个时会非常慢;而且 Odoo 中有许多文件尽管没有被任何代码导入,却必须存在,因为它们只是添加框架将要使用的代码,而不是相反。

因此,Odoo 提供了一套资源包系统。在这些资源包中,JavaScript 文件是顶部带有特殊注解的 ES 模块。这些模块会被打包并转译,以供我们的模块加载器使用。虽然您可以编写不使用该模块系统的代码,但一般不建议这样做。

(参见 原生 Javascript 模块)

给类打补丁

尽管我们尽力提供无需打补丁的扩展点,但有时确实需要 原位 修改现有类的行为。目标是提供一种机制,能够修改类本身以及所有未来/现有的实例。这可以通过 patch 工具函数实现:

import { Hamster } from "@web/core/hamster"
import { patch } from "@web/core/utils/patch";

patch(Hamster.prototype, {
    sleep() {
        super.sleep(...arguments);
        console.log("zzzz");
    },
});

修补方法时需要修补类的原型;但如果想修补类的静态属性,则需要修补类本身。

打补丁是一种危险的操作,应当谨慎进行,因为它会修改该类的所有实例,即使它们已经被创建。为避免奇怪的问题,补丁应当尽早应用,即在模块的顶层应用。如果类已经被实例化,在运行时给类打补丁可能会导致极难调试的问题。

注册表

在 Odoo 生态中,一个常见的需求是从外部(通过安装一个应用,即另一个模块)扩展/改变基础系统的行为。例如,可能需要在某些视图中添加一个新的字段组件。在这种情况下以及许多其他情况下,通常的做法是先创建所需的组件,然后将其添加到某个注册表(注册步骤),让 web 客户端的其余部分知道它的存在。

系统中提供了若干注册表。框架使用的注册表是主注册表上的分类,可以从 @web/core/registry 导入

字段注册表

字段注册表包含 web 客户端所知的所有字段组件。每当视图(通常是表单或列表/看板)需要字段组件时,都会在这里查找。一个典型的用例如下:

import { registry } from "@web/core/registry";
class PadField extends Component { ... }

registry.category("fields").add("pad", {
  component: PadField,
  supportedTypes: ["char"],
  // ...
});
视图注册表

该注册表包含 web 客户端所知的所有 JS 视图。

动作注册表

我们在该注册表中记录所有客户端动作。动作管理器在需要创建客户端动作时就会在这里查找。客户端动作可以是一个函数——该函数会在动作被调用时执行,其返回值在需要时会作为后续动作执行——也可以是一个 Owl 组件,在执行该动作时显示。

服务

在 webclient 中,有些关注点无法由单个组件处理,因为它们是横切的、涉及许多组件,或者需要在应用的整个存续期间维护某些状态。

服务正是解决这些问题的方案:它们在应用启动时创建,通过 useService 钩子供组件使用,并在应用的整个生命周期内保持存活。

例如,我们有 orm 服务,它的职责是允许与服务器上的业务对象进行交互。

以下是 orm 服务实现方式的一个简化示例:

import { registry } from "@web/core/registry";
export const OrmService = {
    start() {
        return {
            read(...) { ... },
            write(...) { ... },
            unlink(...) { ... },
            ...
        }
    },
};
registry.category("services").add("orm", OrmService);

使用服务

服务在环境中可用,但通常应通过 useService 钩子来使用,它可以防止在组件销毁后调用服务的方法,并且当组件在方法调用期间被销毁时,可以阻止方法调用之后的代码继续执行。

class SomeComponent extends Component {
    setup() {
        this.orm = useService("orm");
    }
    // ...
    getActivityModelViewID(model) {
        return this.orm.call(model, "get_activity_view_id", this.params);
    }
}

与服务器通信

在 Odoo 中开发通常有两种用例:可能需要调用(Python)模型上的方法(这通过控制器 /web/dataset/call_kw 进行),或者可能需要直接调用某个控制器(在某个路由上可用)。

  • 调用 Python 模型上的方法通过 orm 服务完成:

    return this.orm.call("some.model", "some_method", [some, args]);
    
  • 直接调用控制器则通过 rpc 服务完成:

    return this.rpc("/some/route/", {
        some: param,
    });
    

Note

rpc 服务并不真正执行通常意义上的远程过程调用(RPC),但由于历史原因,在 Odoo 中我们一般把 JavaScript 中执行的任何网络请求都称为 RPC。正如上一段所强调的,如果要调用模型上的方法,应当使用 orm 服务。

通知

Odoo 框架提供了一种向用户传递各类信息的标准方式:通知,它显示在用户界面的右上角。通知的类型遵循 bootstrap 的 toast:

  • info:用于在某个不可能失败的操作之后显示一些信息性反馈。

  • success:用户执行了一个有时会失败的操作,但这次没有失败。

  • warning:用户执行的操作只能部分完成。当出了问题但并非用户直接造成、或者用户无从着手处理时,也很有用。

  • success:用户尝试执行一个操作,但未能完成。

通知还可以用于在不打断用户工作流程的情况下向用户提问:例如通过 VOIP 接到来电时,可以显示一条带有 接受 和 拒绝 两个按钮的粘性通知。

显示通知

在 Odoo 中显示通知有两种方式:

  • notification 服务允许组件通过调用 add 方法从 JS 代码中显示通知。

  • display_notification 客户端动作允许从 Python 端触发通知的显示(例如在用户点击 object 类型按钮时调用的方法中)。该客户端动作使用了 notification 服务。

通知有几个 选项:

  • title:字符串,可选。它将显示在顶部作为标题。

  • message:字符串,可选。通知的内容。可以是一个 markup 对象,用于显示带格式的文本。

  • sticky:布尔值,可选(默认为 false)。如果为 true,通知将一直保留,直到用户将其关闭。否则,通知会在短暂延迟后自动关闭。

  • type:字符串,可选(默认为 “warning”)。决定通知的样式。可能的值:”info”、”success”、”warning”、”danger”

  • className:字符串,可选。这是一个会自动添加到通知上的 CSS 类名。可用于样式定制,尽管不建议使用。

以下是在 JS 中显示通知的一些示例:

// note that we call _t on the text to make sure it is properly translated.
this.notification.add({
    title: _t("Success"),
    message: _t("Your signature request has been sent.")
});
this.notification.add({
    title: _t("Error"),
    message: _t("Filter name is required."),
    type: "danger",
});

而在 Python 中:

# note that we call _(string) on the text to make sure it is properly translated.
def show_notification(self):
    return {
        'type': 'ir.actions.client',
        'tag': 'display_notification',
        'params': {
            'title': _('Success'),
            'message': _('Your signature request has been sent.'),
            'sticky': False,
        }
    }

系统托盘(Systray)

系统托盘是界面中导航栏的右侧部分,web 客户端在其中显示一些组件,例如消息菜单。

导航栏创建系统托盘时,会查找所有已注册的系统托盘项并将其显示出来。

目前没有针对系统托盘项的专用 API。它们是 Owl 组件,可以像其他组件一样与其环境通信,例如通过使用服务。

添加新的系统托盘项

将项目添加到 “systray” 注册表,即可将其加入系统托盘:

import { registry } from "@web/core/registry"
class MySystrayComponent extends Component {
    ...
}
registry.category("systray").add("MySystrayComponent", MySystrayComponent, { sequence: 1 });

系统托盘中的项目按其在 systray 注册表中的顺序排列。

翻译管理

有些翻译在服务器端进行(基本上所有由服务器渲染或处理的文本字符串都是如此),但静态文件中也有一些字符串需要翻译。目前的工作方式如下:

  • 每个可翻译的字符串都用特殊函数 _t 标记

  • 服务器使用这些字符串来生成相应的 PO 文件

  • 每当 web 客户端加载时,它都会调用路由 /web/webclient/translations,该路由返回所有可翻译术语的列表

  • 在运行时,每当函数 _t 被调用时,它都会在该列表中查找翻译,并返回翻译结果;如果没有找到,则返回原始字符串。

请注意,从服务器的角度出发,翻译在文档 翻译模块 中有更详细的说明。

import { _t } from "@web/core/l10n/translation";

class SomeComponent extends Component {
    static exampleString = _t("this should be translated");
    ...
    someMethod() {
        const str = _t("some text");
    }
}

请注意,使用翻译函数需要小心:作为参数传入的字符串不能是动态的,因为它会被静态地从代码中提取出来以生成 PO 文件,并作为待翻译术语的标识符。如果需要在字符串中注入动态内容,_t 支持占位符:

import { _t } from "@web/core/l10n/translation";
const str = _t("Hello %s, you have %s unread messages.", user.name, unreadCount);

注意字符串本身是固定的。这使得翻译函数能够在 使用它进行 插值 之前 获取翻译后的字符串。

会话

webclient 需要一些来自 Python 的信息才能正常运行。为了避免在 JavaScript 中发起网络请求而多一次服务器往返,这些信息被直接序列化到页面中,并可以在 JS 中通过 @web/session 模块访问。

向会话添加信息

当 /web 路由被加载时,服务器会将这些信息注入到一个 script 标签中。这些信息通过调用模型 ir.http 的 session_info 方法获取。你可以重写该方法,向返回的字典中添加信息。

from odoo import models
from odoo.http import request

class IrHttp(models.AbstractModel):
    _inherit = 'ir.http'

    def session_info(self):
        result = super(IrHttp, self).session_info()
        result['some_key'] = get_some_value_from_db()
        return result

现在,可以在 JavaScript 中通过读取会话来获取该值:

import { session } from "@web/session"
const myValue = session.some_key;
...

请注意,这一机制旨在减少 web 客户端就绪所需的通信量。它只适用于计算成本低的数据(缓慢的 session_info 调用会拖延所有人的 web 客户端加载),以及初始化过程早期就需要的数据。

视图

“view” 一词有多重含义。本节讨论的是视图的 JavaScript 代码设计,而非 arch 的结构或其他内容。

虽然视图只是 owl 组件,但内置视图通常具有相同的结构:一个名为 “SomethingController” 的组件作为视图的根。该组件会创建某个 “模型”(负责管理数据的对象)的实例,并拥有一个称为 “渲染器”(renderer)的子组件来处理显示逻辑。

字段

web 客户端的很大一部分体验是关于编辑和创建数据。这些工作大部分借助字段组件完成,它们了解字段类型以及值应如何显示和编辑的具体细节。

装饰(Decorations)

与列表视图一样,字段组件对装饰提供了简单支持。装饰的目标是提供一种简单的方式,根据记录当前状态指定文本颜色。例如:

<field name="state" decoration-danger="amount &lt; 10000"/>

有效的装饰名称为:

  • decoration-bf

  • decoration-it

  • decoration-danger

  • decoration-info

  • decoration-muted

  • decoration-primary

  • decoration-success

  • decoration-warning

每个装饰 decoration-X 会被映射到 css 类 text-X,这是一个标准的 bootstrap css 类(text-it 和 text-bf 除外,它们由 odoo 处理,分别对应斜体和粗体)。请注意,装饰属性的值应该是一个有效的 python 表达式,它将以记录作为求值上下文进行求值。

非关系型字段

我们在这里记录所有默认可用的非关系型字段,顺序不分先后。

整型(integer)

这是类型为 integer 的字段的默认字段类型。

  • 支持的字段类型:integer

选项:

  • type:设置输入类型(默认为 "text",可设置为 "number")

    在编辑模式下,该字段渲染为一个 HTML 属性 type 设置为 "number" 的输入框(这样用户可以使用原生支持,尤其是在移动设备上)。在这种情况下,默认格式化会被禁用以避免不兼容。

    <field name="int_value" options="{'type': 'number'}" />
    
  • step:设置用户点击按钮时数值上下增减的步长(仅适用于 number 类型的输入,默认为 1)

    <field name="int_value" options="{'type': 'number', 'step': 100}" />
    
  • format:是否格式化数字(默认为 true)

    默认情况下,数字根据区域设置参数进行格式化。此选项将阻止字段值被格式化。

    <field name="int_value" options='{"format": false}' />
    
浮点型(float)

这是类型为 float 的字段的默认字段类型。

  • 支持的字段类型:float

属性:

  • digits:显示精度

    <field name="factor" digits="[42,5]" />
    

选项:

  • type:设置输入类型(默认为 "text",可设置为 "number")

    在编辑模式下,该字段渲染为一个 HTML 属性 type 设置为 "number" 的输入框(这样用户可以使用原生支持,尤其是在移动设备上)。在这种情况下,默认格式化会被禁用以避免不兼容。

    <field name="int_value" options="{'type': 'number'}" />
    
  • step:设置用户点击按钮时数值上下增减的步长(仅适用于 number 类型的输入,默认为 1)

    <field name="int_value" options="{'type': 'number', 'step': 0.1}" />
    
  • format:是否格式化数字(默认为 true)

    默认情况下,数字根据区域设置参数进行格式化。此选项将阻止字段值被格式化。

    <field name="int_value" options="{'format': false}" />
    
时间(float_time)

该组件的目标是正确显示表示时间间隔(以小时为单位)的浮点值。例如,0.5 应格式化为 0:30,4.75 对应 4:45。

  • 支持的字段类型:float

浮点系数(float_factor)

该组件旨在使用其选项中给定的系数,正确显示转换后的浮点值。例如,数据库中保存的值为 0.5,系数为 3,则组件值应格式化为 1.5。

  • 支持的字段类型:float

浮点切换(float_toggle)

该组件的目标是将输入字段替换为一个包含一系列可能值(在选项中给出)的按钮。每次点击允许用户在该范围内循环。其目的是将字段值限制在预定义的选择中。此外,该组件还像 float_factor 组件一样支持系数转换(范围值应为转换后的结果)。

  • 支持的字段类型:float

<field name="days_to_close" widget="float_toggle" options="{'factor': 2, 'range': [0, 4, 8]}" />
布尔型(boolean)

这是类型为 boolean 的字段的默认字段类型。

  • 支持的字段类型:boolean

字符(char)

这是类型为 char 的字段的默认字段类型。

  • 支持的字段类型:char

日期(date)

这是类型为 date 的字段的默认字段类型。它由一个文本框和一个日期选择器组成。

  • 支持的字段类型:date

选项:

  • min_date / max_date:设置可接受值的日期限制。默认情况下,最早可接受的日期为 1000-01-01,最晚为 9999-12-31。可接受的值为 SQL 格式的日期(yyyy-MM-dd HH:mm:ss)或 "today"。

    <field name="datefield" options="{'min_date': 'today', 'max_date': '2023-12-31'}" />
    
  • warn_future:如果值在将来(基于今天),则显示警告。

    <field name="datefield" options="{'warn_future': true}" />
    
日期和时间(datetime)

这是类型为 datetime 的字段的默认字段类型。值始终使用客户端的时区。

  • 支持的字段类型:datetime

选项:

  • 参见 Date Field 的选项

  • rounding:用于在时间选择器中生成可用分钟的增量。这不会影响实际值,只影响下拉选择中可用选项的数量(默认:5)。

    <field name="datetimefield" options="{'rounding': 10}" />
    
  • show_seconds:设为 false 时,隐藏日期时间字段中的秒。该字段仍接受日期时间值,但秒将在 UI 中隐藏(默认:true)。

    <field name="datetimefield" widget="datetime" options="{'show_seconds': false}" />
    
  • show_time:设为 false 时,隐藏日期时间字段中的时间部分。该字段仍接受日期时间值,但时间部分将在 UI 中隐藏(默认:true)。

    <field name="datetimefield" widget="datetime" options="{'show_time': false}" />
    
日期范围(daterange)

该组件允许用户从单个选择器中选择开始和结束日期。

  • 支持的字段类型:date、datetime

选项:

  • 参见 Date Field 或 Date & Time Field 的选项

  • start_date_field:用于获取/设置日期范围起始值的字段(不能与 end_date_field 一起使用)。

    <field name="end_date" widget="daterange" options="{'start_date_field': 'start_date'}" />
    
  • end_date_field:用于获取/设置日期范围结束值的字段(不能与 start_date_field 一起使用)。

    <field name="start_date" widget="daterange" options="{'end_date_field': 'end_date'}" />
    
剩余天数(remaining_days)

该组件可用于 date 和 datetime 字段。在只读模式下,它显示字段值与今天之间的差值(以天为单位)。在编辑模式下,该组件会变为常规的 date 或 datetime 字段。

  • 支持的字段类型:date、datetime

货币(monetary)

这是类型为 monetary 的字段的默认字段类型。它用于显示货币。如果在选项中给定了货币字段,它将使用该字段,否则回退到默认货币(会话中)。

  • 支持的字段类型:monetary、float

选项:

  • currency_field:另一个字段名,应为一个指向货币的 many2one 字段。

    <field name="value" widget="monetary" options="{'currency_field': 'currency_id'}" />
    
文本(text)

这是类型为 text 的字段的默认字段类型。

  • 支持的字段类型:text

拖拽柄(handle)

该字段的用途是显示为 handle (拖拽柄),并允许通过拖放对各种记录重新排序。

Warning

必须将其指定在记录排序所依据的字段上。

Warning

同一列表中不支持多个字段使用 handle 组件。

  • 支持的字段类型:integer

电子邮箱(email)

该字段显示电子邮箱地址。使用它的主要原因是,在只读模式下,它会被渲染为带有正确 href 的锚点标签。

  • 支持的字段类型:char

电话(phone)

该字段显示电话号码。使用它的主要原因是,在只读模式下,它会被渲染为带有正确 href 的锚点标签,但仅限于某些情况:我们只希望在设备能够拨打该号码时才使其可点击。

  • 支持的字段类型:char

URL(url)

该字段显示一个 URL(在只读模式下)。使用它的主要原因是,它会被渲染为带有正确 css 类和 href 的锚点标签。

此外,锚点标签的文本可以通过 text 属性进行自定义(它不会改变 href 的值)。

  • 支持的字段类型:char

<field name="foo" widget="url" text="Some URL" />

选项:

  • website_path:(默认:false)默认情况下,该组件会强制(如果尚未如此)将 href 值以 "http://" 开头,除非此选项设置为 true,从而允许重定向到数据库自己的网站。

域(domain)

domain 字段允许用户通过树状界面构建技术前缀域,并实时查看所选记录。在调试模式下,还提供一个输入框,可以直接输入前缀字符域(或构建树状界面不允许的高级域)。

请注意,这仅限于 静态 域(没有动态表达式,或对上下文变量的访问)。

  • 支持的字段类型:char

选项:

  • model:编码该域所应用的 res_model 的字符字段名称。

  • foldable (默认:false):如果为 true,域字段将紧凑地呈现,并在用户交互时展开。

  • in_dialog (默认:false):如果为 true,当用户想编辑域时组件会打开一个对话框,而默认情况下,域编辑器会直接呈现在值的下方。

链接按钮(link_button)

LinkButton 组件实际上只是显示一个带有图标、以文本值作为内容的 span。该链接可点击,并将在新的浏览器窗口中以其值作为 URL 打开。

  • 支持的字段类型:char

图像文件(image)

该组件用于将二进制值表示为图像。在某些情况下,服务器返回的是 bin_size 而非实际图像(bin_size 是表示文件大小的字符串,例如 "6.5kb")。在这种情况下,组件将创建一个图像,其 source 属性对应服务器上的图像。

  • 支持的字段类型:binary

选项:

  • preview_image:如果图像仅作为 bin_size 加载,则此选项可用于告知 web 客户端,默认字段名不是当前字段的名称,而是另一个字段的名称。

    <field name="image" widget="image" options="{'preview_image': 'image_128'}" />
    
  • accepted_file_extensions:用户可以从文件输入对话框中选择的文件扩展名(默认值为 "image/*")

    (参见 <input type="file" /> 上的 accept 属性)

二进制文件(binary)

用于保存/下载二进制文件的通用组件。

  • 支持的字段类型:binary

属性:

  • filename:保存二进制文件会丢失其文件名,因为它只保存二进制值。文件名可以保存在另一个字段中。为此,应将 filename 属性设置为视图中存在的某个字段。

    <field name="datas" filename="datas_fname" />
    

选项:

  • accepted_file_extensions:用户可以从文件输入对话框中选择的文件扩展名

    (参见 <input type="file" /> 上的 accept 属性)

优先级(priority)

该组件渲染为一组星号,允许用户点击它来选择或不选择一个值。例如,这对于将任务标记为高优先级非常有用。

请注意,该组件在 readonly 模式下也可用,这一点不同寻常。

  • 支持的字段类型:selection

附件图像(attachment_image)

用于 many2one 字段的图像组件。如果该字段已设置,该组件将渲染为带有正确 src url 的图像。该组件在编辑或只读模式下没有不同的行为,它只用于查看图像。

  • 支持的字段类型:many2one

<field name="displayed_image_id" widget="attachment_image" />
标签选择(label_selection)

该组件渲染一个简单的不可编辑标签。它只用于显示信息,而非编辑信息。

  • 支持的字段类型:selection

选项:

  • classes:从选择值到 CSS 类名的映射

    <field
        name="state"
        widget="label_selection"
        options="{
            'classes': {
                'draft': 'default',
                'cancel': 'default',
                'none': 'danger',
            },
        }"
    />
    
状态选择(state_selection)

这是一个专门的选择(selection)组件。它假定记录具有视图中存在的一些硬编码字段:stage_id、legend_normal、legend_blocked、legend_done。它主要用于显示和更改项目中任务的状态,并在下拉菜单中显示附加信息。

  • 支持的字段类型:selection

<field name="kanban_state" widget="state_selection" />
状态选择 - 列表视图(list.state_selection)

在列表视图中,state_selection 字段默认在图标旁显示标签。

  • 支持的字段类型:selection

选项:

  • hide_label:隐藏图标旁的标签

    <field name="kanban_state" widget="state_selection" options="{'hide_label': true}" />
    
收藏(boolean_favorite)

该组件根据布尔值显示为一颗空(或非空)星。请注意,在只读模式下它也可以编辑。

  • 支持的字段类型:boolean

开关(boolean_toggle)

显示一个开关来表示布尔值。它是 boolean 字段的子字段,主要用于呈现不同的外观。

  • 支持的字段类型:boolean

统计信息(statinfo)

该组件用于在 stat button 中呈现统计信息。它基本上只是一个带数字的标签。

  • 支持的字段类型:integer、float

选项:

  • label_field:如提供,组件将使用 label_field 的值作为文本。

    <button
        name="%(act_payslip_lines)d"
        icon="fa-money"
        type="action"
    >
        <field
            name="payslip_count"
            widget="statinfo"
            string="Payslip"
            options="{'label_field': 'label_tasks'}"
        />
    </button>
    
百分比饼图(percentpie)

该组件用于在 stat button 中呈现统计信息。它与 statinfo 组件类似,但信息以 饼图 (从空到满)呈现。 请注意,该值会被解释为百分比(0 到 100 之间的数字)。

  • 支持的字段类型:integer、float

<field name="replied_ratio" string="Replied" widget="percentpie" />
进度条(progressbar)

将值显示为进度条(从 0 到某个值)

  • 支持的字段类型:integer、float

选项:

  • editable:布尔值,决定 value 是否可编辑

  • current_value:从字段获取当前值,该字段必须存在于视图中

  • max_value:从字段获取最大值,该字段必须存在于视图中

  • edit_max_value:布尔值,决定 max_value 是否可编辑

  • title:进度条的标题,显示在进度条上方

    -> 不进行翻译,如需翻译该术语,请改用 title 属性(而非选项)

<field
    name="absence_of_today"
    widget="progressbar"
    options="{
        'current_value': 'absence_of_today',
        'max_value': 'total_employee',
        'editable': false,
    }"
/>
日记账仪表板图表(dashboard_graph)

这是一个更专门的组件,适用于显示表示一组数据的图表。例如,它用于会计仪表板的看板视图中。

它假定该字段是一组数据的 JSON 序列化结果。

  • 支持的字段类型:char

属性:

  • graph_type:字符串,可以为 "line" 或 "bar"

    <field name="dashboard_graph_data" widget="dashboard_graph" graph_type="line" />
    
Ace 编辑器(ace)

该组件用于文本(Text)字段。它提供 Ace 编辑器来编辑 XML 和 Python。

  • 支持的字段类型:char、text

徽章(badge)

在 Bootstrap 徽章胶囊中显示该值。

  • 支持的字段类型:char、selection、many2one

默认情况下,徽章的背景为浅灰色,但可以使用 Decoration 机制进行自定义。例如,要在给定条件下显示红色徽章:

<field name="foo" widget="badge" decoration-danger="state == 'cancel'" />

关联字段

选择(selection)

  • 支持的字段类型:selection

属性:

  • placeholder:一个字符串,用于在未选择值时显示一些提示信息

    <field name="tax_id" widget="selection" placeholder="Select a tax" />
    
单选按钮(radio)

它是 FielSelection 的子字段,专门用于将所有有效选项显示为单选按钮。

请注意,如果用在 many2one 记录上,则会执行更多 RPC 来获取相关记录的 name_get。

  • 支持的字段类型:selection、many2one

选项:

  • horizontal:若为 true,单选按钮将水平显示。

    <field name="recommended_activity_type_id" widget="radio" options="{'horizontal': true}"/>
    
徽章选择(selection_badge)

它是 selection 字段的子字段,专门用于将所有有效选项显示为矩形徽章。

  • 支持的字段类型:selection、many2one

<field name="recommended_activity_type_id" widget="selection_badge" />
多对一(many2one)

many2one 字段的默认组件。

  • 支持的字段类型:many2one

属性:

  • can_create:允许创建相关记录(优先于 no_create 选项)

  • can_write:允许编辑相关记录(默认:true)

选项:

  • quick_create:允许快速创建相关记录(默认:true)

  • no_create:禁止创建相关记录 - 同时隐藏 新建 “xxx” 和 新建并编辑 下拉菜单项(默认:false)

  • no_quick_create:禁止快速创建相关记录 - 隐藏 新建 “xxx” 下拉菜单项(默认:false)

  • no_create_edit:隐藏 新建并编辑 下拉菜单项(默认:false)

  • create_name_field:创建相关记录时,如设置了该选项,输入框中的值将填入 create_name_field 字段(默认:name)

  • always_reload:布尔值,默认为 false。若为 true,组件将始终额外执行一次 name_get 来获取名称值。这用于 name_get 方法被重写的情况(请不要这样做)

  • no_open:布尔值,默认为 false。若设为 true,在只读模式下点击 many2one 时不会跳转到该记录

<field name="currency_id" options="{'no_create': true, 'no_open': true}" />
多对一条码(many2one_barcode)

用于 many2one 字段的组件,允许从移动设备(Android/iOS)打开摄像头扫描条码。

many2one 字段的特殊化版本,允许用户使用原生摄像头扫描条码,然后使用 name_search 搜索该值。

如果设置了该组件而用户未使用移动应用,则会回退为普通 many2one (Many2OneField)

  • 支持的字段类型:many2one

多对一头像(many2one_avatar)

该组件仅支持指向继承自 image.mixin 的模型的 many2one 字段。在只读模式下,它会在相关记录的 display_name 旁显示其图像。请注意,此时 display_name 不是可点击的链接。在编辑模式下,其行为与普通 many2one 完全相同。

  • 支持的字段类型:many2one

多对一用户头像(many2one_avatar_user)

该组件是 Many2OneAvatar 的特殊化版本。点击头像时,会打开与相应用户的聊天窗口。该组件只能设置在指向 res.users 模型的 many2one 字段上。

  • 支持的字段类型:many2one (指向 res.users)

多对一员工头像(many2one_avatar_employee)

与 many2one_avatar_user 相同,但用于指向 hr.employee 的 many2one 字段。

  • 支持的字段类型:many2one (指向 hr.employee)

多对多(many2many)

many2many 字段的默认组件。

  • 支持的字段类型:many2many

属性:

  • mode:字符串,默认显示的视图

  • domain:将数据限制到特定域

选项:

  • create_text:允许自定义添加新记录时显示的文本

  • link:决定记录能否添加到关系中的域(默认:true)。

  • unlink:决定记录能否从关系中移除的域(默认:true)。

多对多二进制文件(many2many_binary)

该组件帮助用户同时上传或删除一个或多个文件。

请注意,该组件专用于 ir.attachment 模型。

  • 支持的字段类型:many2many

选项:

  • accepted_file_extensions:用户可以从文件输入对话框中选择的文件扩展名

    (参见 <input type="file" /> 上的 accept 属性)

多对多标签(many2many_tags)

将 many2many 字段显示为标签列表。

  • 支持的字段类型:many2many

选项:

  • create:决定能否创建新标签的域(默认:true)。

    <field name="category_id" widget="many2many_tags" options="{'create': [['some_other_field', '>', 24]]}" />
    
  • color_field:一个数值字段的名称,该字段应存在于视图中。将根据其值选择颜色。

    <field name="category_id" widget="many2many_tags" options="{'color_field': 'color'}" />
    
  • no_edit_color:设为 true 以移除更改标签颜色的可能(默认:false)。

    <field name="category_id" widget="many2many_tags" options="{'color_field': 'color', 'no_edit_color': true}" />
    
  • edit_tags:设为 true 以增加通过点击标签来更新标签相关记录的可能。(默认:false)。

    <field name="category_id" widget="many2many_tags" options="{'edit_tags': true}" />
    
多对多标签 - 表单视图(form.many2many_tags)

many2many_tags 组件针对表单视图的特殊化版本。它包含一些额外的代码,允许编辑标签的颜色。

  • 支持的字段类型:many2many

多对多标签 - 看板视图(kanban.many2many_tags)

many2many_tags 组件针对看板视图的特殊化版本。

  • 支持的字段类型:many2many

多对多复选框(many2many_checkboxes)

该字段显示一组复选框,允许用户选择选项的一个子集。请注意,显示值的数量上限为 100。该限制不可自定义,仅用于应对极端情况,即该组件被错误地设置在具有庞大伴随模型(comodel)的字段上。在这种情况下,列表视图更为合适,因为它支持分页和筛选。

  • 支持的字段类型:many2many

一对多(one2many)

one2many 字段的默认组件。它通常在子列表视图或子看板视图中显示数据。

  • 支持的字段类型:one2many

选项:

  • create:决定能否创建相关记录的域(默认:true)。

  • delete:决定能否删除相关记录的域(默认:true)。

    <field name="turtles" options="{'create': [['some_other_field', '>', 24]]}" />
    
  • create_text:用于自定义“添加”标签/文本的字符串。

    <field name="turtles" options="{'create_text': 'Add turtle'}" />
    
状态栏(statusbar)

这是表单视图特有的字段。它是许多表单顶部的条状元素,用于表示流程,并允许选择特定状态。

  • 支持的字段类型:selection、many2one

引用(reference)

reference 字段是一个选择框(用于模型)和一个 many2one 字段(用于其值)的组合。 它允许在任意模型上选择一条记录。

  • 支持的字段类型:char、reference

选项:

  • model_field:一个 ir.model 的名称,包含可选择的记录的模型。设置该选项后,reference 字段的选择框部分将不显示。

组件

丝带(web_ribbon)

该组件在看板卡片或表单视图页面的右上角显示一个丝带,例如用于指示已归档的记录。

<widget name="web_ribbon" title="Archived" bg_color="text-bg-danger"/>

属性:

  • title:显示在丝带中的文本。

  • tooltip:显示在丝带工具提示中的文本。

  • bg-class:设置在丝带上的类名,通常用于定义丝带的颜色。

每周各天(week_days)

该组件显示一周各天的复选框列表,每天一个复选框,并允许用户选择其中一部分。

<widget name="week_days" />

客户端动作

客户端动作是可以作为 webclient 主元素显示的组件,占据导航栏下方的全部空间,就像 act_window_action 一样。当您需要一个与现有视图或特定模型没有紧密关联的组件时,这很有用。 例如,Discuss 应用就是一个客户端动作。

客户端动作是一个在不同上下文中有不同含义的术语:

  • 从服务器的角度来看,它是模型 ir_action 的一条记录,带有一个 char 类型的 tag 字段

  • 从 web 客户端的角度来看,它是一个 Owl 组件,以其 tag 为键注册在动作注册表中

当菜单项关联到客户端动作时,打开它只需从服务器获取动作定义,然后在动作注册表中查找其 tag 以获取组件定义。该组件随后将由动作容器渲染。

添加客户端动作

客户端动作是一个控制导航栏下方屏幕区域的组件。定义客户端动作非常简单,只需创建一个 Owl 组件并将其添加到动作注册表中即可。

import { registry } from "@web/core/registry";
class MyClientAction extends Component { ... }
registry.category("actions").add("my-custom-action", ClientAction);

然后,要在 web 客户端中使用客户端动作,我们需要创建一个带有正确 tag 属性的客户端动作记录(ir.actions.client 模型的一条记录):

<record id="my_client_action" model="ir.actions.client">
    <field name="name">Some Name</field>
    <field name="tag">my-custom-action</field>
</record>