Odoo 16 OWL Field 组件开发指南
在 Odoo 16 中,Field 组件基于 OWL 2 (Odoo Web Library) 框架构建。与旧版本不同,现在的字段开发更贴近现代前端框架的组件化模式。
一、 Field 组件的五个核心组成部分
开发一个完整的字段组件,通常需要以下五个部分的协同工作:
1. 逻辑控制 (JavaScript)
这是组件的大脑。你需要定义一个继承自 OWL Component 的类。
- 核心任务:管理生命周期、处理用户交互、调用更新逻辑。
- 静态属性:
supportedTypes: 数组,声明支持的后端字段类型(如char,integer,float)。template: 字符串,关联 XML 模板名称。extractProps: 静态函数,负责将 XML 视图中的options属性解析并传递给组件的props。extractProps接收参数的Object中只有field、attrs两个key,其中xml视图中的options属性则属于attrs(处理这部分的源码位于addons/web/static/src/views/fields/field.js->get fieldComponentProps())。
2. 视图模板 (XML)
定义组件的 HTML 结构。
- 模式区分:通常使用
t-if="props.readonly"来区分只读模式(展示文本)和编辑模式(展示输入控件)。 - 引用绑定:使用
t-ref配合 JavaScript 中的useReference或 Hook 来操作具体的 DOM。
3. 样式定义 (SCSS)
定义组件的视觉外观。
- 命名规范:通常建议以
.o_field_[widget_name]作为根命名空间,以防样式冲突。 - 自适应:利用 Odoo 后端的 CSS 变量处理主题色和间距。
4. 注册 (Registry)
将你的 JavaScript 类告诉 Odoo 系统。
- 位置:注册到
fields类别中。import { registry } from "@web/core/registry";
registry.category("fields").add("my_widget_name", MyFieldComponent);
5. 资源声明 (Manifest)
在模块的 __manifest__.py 中通过 assets 字段引入相关文件,确保浏览器能加载到这些资源。
二、 Odoo 16 原生标准化支持
Odoo 16 提供了丰富的内置工具,开发时应优先使用这些“标准化”方案,以保持系统一致性:
1. 标准属性 (standardFieldProps)
所有被视图加载的字段组件都会自动接收到一套标准 Props:
id: Stringname: String,字段名称readonly: Boolean,标识当前只读状态record: Object,当前整条记录的代理对象(DataPoint),可用于获取其他字段的值type: String,类型update: Function,更新props.value的函数value: 后端存储的当前值decorations: Object,控制动态显色的对象(例如:decoration-success="state == 'done'")setDirty: Function,用于通知系统用户修改了数据
2. 核心 Hook:useInputField
对于所有“输入框”类型的字段,这是必用的 Hook。它自动处理了:
- 临时值管理:用户输入时的中间状态,失焦或回车时触发更新(
commitChanges->props.update())。 - 变更管理:通过事件管理监听
change事件,在数据发生变更时先通过params.parse()尝试进行数据转换,如果数据合法,则触发更新(component.props.update())。 - 解析与格式化:通过配置
parse和getValue参数自动处理数据转换。parse: 更新前处理数据的函数getValue: 格式化输出的函数
- 脏数据处理:在
onChange和commitChanges函数中尝试对数据进行params.parse()处理时,如果抛出异常,则视为数据不合法,调用component.props.record.setInvalidField(component.props.name)来通知系统该字段数据不合法。
3. 格式化与解析工具
@web/views/fields/formatters: 提供formatInteger,formatFloat,formatMonetary等,将后端数据转为本地化字符串(如加千分位)。@web/views/fields/parsers: 提供parseInteger,parseFloat等,将用户输入的字符串还原为后端数值。
三、 扩展与开发最佳实践
1. 扩展方式的选择
- 创建新 Widget:当逻辑完全不同时,定义新类并注册新名称。
- 打补丁 (Patching):如果你想修改 Odoo 原生字段(如
char字段)在全系统的表现,使用patch工具函数。import { patch } from "@web/core/utils/patch";
import { CharField } from "@web/views/fields/char/char_field";
patch(CharField.prototype, "my_custom_patch", { ... });
2. 数据流原则
- 单向数据流:永远不要直接修改
this.props.value。 - 唯一更新入口:始终通过
this.props.record.update({ [this.props.name]: newValue })或更简单的this.props.update(newValue)来修改数据。
3. 性能注意点
- Getter 缓存:由于 OWL 模板会频繁重绘,如果在
get属性中执行了复杂的循环或逻辑,建议配合onWillUpdateProps进行缓存处理。 - 异步处理:如果字段需要调用 RPC 获取数据,务必在
onWillStart或onWillUpdateProps中处理异步逻辑。
4. 错误验证
- 在
useInputField的parse方法中,如果输入非法,可以直接抛出异常。Odoo 的视图控制器会自动捕获异常并给字段标记红色边框,同时阻止记录保存。
四、 最小组件实现代码模板
/** @odoo-module **/
import { Component } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
import { useInputField } from "@web/views/fields/input_field_hook";
export class MyCustomField extends Component {
setup() {
// 使用标准化 Hook 处理输入逻辑
useInputField({
getValue: () => this.props.value || "",
parse: (v) => v.trim(), // 示例逻辑:自动去空格
});
}
}
// 关联模板
MyCustomField.template = "my_module.MyCustomFieldTemplate";
// 继承标准属性
MyCustomField.props = { ...standardFieldProps };
// 声明支持类型
MyCustomField.supportedTypes = ["char", "text"];
// 注册到系统
registry.category("fields").add("my_custom_widget", MyCustomField);
最后更新日期:2026年1月26日