第三十五章 组件开发
篇别:第八篇 前端开发
本章学习目标
- 从 0 实现可复用的 OWL 字段组件 或 展示组件,并挂入
fieldsregistry。 - 理解
props(record、readonly、name 等) 与 标准字段组件 的 契约,能在 可编辑列表 / x2many 场景下自测。 - 浏览 内置 OWL 组件(Dialog、CheckBox 等)的 文档与源码入口,建立 「优先组合、慎 patch」 的习惯。
- 了解 Odoo Editor / Powerbox 的 扩展思路 与 HTML 消毒 风险。
- 掌握 字段组件调试与单测 的 最小闭环(mock record、DevTools)。
导读:组件是「契约」而不仅是 UI
Odoo 19 中 列表 / 表单中的单元格 已逐步 OWL 化。自定义组件必须遵守 RelationalRecord 更新协议、props 声明、无障碍与焦点 等约定;否则会出现 editable list 保存丢失、移动端布局错位、x2many 子表内联异常 等 间歇性问题。本章给出 最小字段组件、内置组件阅读法、编辑器扩展注意 与 测试调试 四条路径;第三十二章(OWL)、第三十三章(JS 模块)、第三十四章(样式) 为前置交叉阅读。
35.1 自定义 OWL 组件
35.1.1 知识要点
- 注册:
registry.category("fields").add("technical_name", { component: X, supportedTypes: ["char"] });supportedTypes/extractProps等键名以当前web源码为准。 - Props:常见
record(RelationalRecord)、name(字段名)、readonly、type;禁止 依赖未文档化的 全局单例(除 env / service 等官方路径)。 - 更新:可编辑场景用
record.update({ [name]: value });只读组件 勿写库。onWillUpdateProps中对比 新旧 record,避免 陈旧缓存。 - 模板:
static template = xml\...`` 或 独立 XML;DOM 宜浅,便于 QUnit/Hoot 断言。 - 性能:避免 在
onPatched中高频searchRead;批量数据用 父组件拉取 + props 下发。
35.1.2 案例:ISBN 校验位颜色提示(只读展示)
isbn_hint_field.js
/** @odoo-module **/
import { Component } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
export class IsbnHintField extends Component {
static template = "my_library.IsbnHintField";
static props = {
...standardFieldProps,
};
get hintClass() {
const v = this.props.record.data[this.props.name] || "";
return v.length >= 10 ? "text-success" : "text-muted";
}
}
registry.category("fields").add("isbn_hint", {
component: IsbnHintField,
supportedTypes: ["char"],
});
XML 视图
<field name="isbn" widget="isbn_hint" readonly="1"/>
isbn_hint_field.xml(示意)
<templates xml:space="preserve">
<t t-name="my_library.IsbnHintField">
<span t-att-class="hintClass" class="o_field_isbn_hint">
<t t-esc="props.record.data[props.name]"/>
</span>
</t>
</templates>
注意:
standardFieldProps与 精确 props 列表 请对照当前web模块 内char字段 实现微调。
35.1.3 截图占位

35.1.4 本节练习
- 实操:实现 只读 「ISBN 校验位」颜色提示(可用 简化校验规则 演示)。
- 简答:editable list 中自定义字段组件 不写
record.update会怎样? - 判断:字段组件可直接
fetch任意模型的全部记录 而不考虑权限。( )
参考答案提示:2. 用户编辑无法落库 或 被标准层拒绝。3. 错;须走 orm / record 上下文。
35.2 内置 OWL 组件参考(新增)
35.2.1 知识要点
- 位置:
@web/core/...下 Dialog、Dropdown、CheckBox、Pager、Notebook 等;barrel 导出 可能变化,以grep/ IDE 跳转 为准。 - 阅读顺序:
static props/ JSDoc → 模板 slots → 子组件 → 测试文件;关注t-portal、焦点陷阱、aria。 - 组合优于 patch:业务弹窗优先
Dialog+ 自定义 content,而非 patch Dialog 全局行为。 - 版本差异:小版本可能 重命名 props;升级后跑 前端测试 + 关键路径手工点验。
35.2.2 案例
阅读提纲:在源码中打开 Dialog 相关文件,记录:title、content、footer slots、size 或 等价枚举、关闭回调 的 prop 名。
35.2.3 截图占位

35.2.4 本节练习
- 拓展:阅读
Dialog组件源码,记录 不少于 3 个 props 名。 - 简答:直接 patch Dialog 与 封装 MyDialog 再使用 的取舍?
- 实操:用
CheckBox(或等价) 做一个 仅前端状态 的「记住筛选」开关(不写库,说明适用场景)。
参考答案提示:2. patch 影响全局;封装 影响面可控。
35.3 Odoo Editor(新增)
35.3.1 知识要点
- Powerbox:
/命令 扩展;注册 自定义块 须遵循web_editor插件 API(以官方文档为准)。 - 消毒(sanitize):允许 任意 HTML / iframe 易导致 XSS、钓鱼、恶意重定向;默认策略宜保守。
- 邮件 vs 网站:邮件 HTML 与 站内编辑器 的 允许标签集 常 不同;跨上下文复制 可能 丢样式或丢安全属性。
- 权限:Powerbox 命令可
groups限制;仅内部用户 可用的命令 勿对 portal 暴露。
35.3.2 案例(概念)
团队检查清单(新增命令前):数据来源是否用户可控? 输出是否经 sanitize? 是否需 groups? 是否记录审计日志?
35.3.3 截图占位

35.3.4 本节练习
- 简答:允许用户嵌入 任意 iframe(如外部地图)的 两条风险。
- 判断:前端 DOMPurify 消毒可 替代 后端存储前过滤。( )
- 简答:站内帮助页 与 客户可见网站页 的 HTML 策略应如何区分?
参考答案提示:2. 错;后端最终把关。3. 外站更严、内站可适度放宽但仍要消毒。
35.4 与第三十二章、第三十三章的衔接
35.4.1 知识要点
- 组件通信:子块拆分 见 32.8;Service 获取 orm 见 32.5 / 33.3。
- 样式:组件旁 SCSS 见 第三十四章;全局主题 回 32.7。
- patch:改核心字段基类 前 评估升级成本;能 Registry 替换 则 优先 Registry。
35.4.2 本节练习
- 简答:字段组件与 客户端动作组件 在 props 上的 主要差异?
- 综合:说明 字段组件 内
useService("orm")与record.update的 分工。
参考答案提示:1. 字段组件 绑定 record + name;客户端动作常 接收 action 参数。2. 批量读 / 侧路数据 用 orm;当前格写回 用 record.update。
35.5 调试、无障碍与测试要点(补充)
35.5.1 知识要点
- 调试:OWL DevTools(若启用)或 Sources 断点;Network 核对 是否多余
call_kw。 - 无障碍:图标按钮
aria-label;只读自定义渲染 勿丢title/ 字段 string。 - 单测:mock
record.data与update,断言 模板输出 与 update 调用;参考web模块字段测试 目录结构。 - x2many:在 内联列表 中重测 新建行、删除行、失去焦点保存;手机宽度 下再测一遍。
35.5.2 案例
// 断言思路(伪代码):mount(IsbnHintField, { props: { record: mockRecord, name: "isbn", ... } })
// expect(mockRecord.update).not.toHaveBeenCalled() // 只读
35.5.3 截图占位

35.5.4 本节练习
- 实操:为 35.1 的组件写 一条 mount 测试 或写出 mock 字段清单。
- 简答:x2many 内联 与 主表单 测试差异 两点。
参考答案提示:2. 行级 record、焦点切换、子表 dirty 状态 更易出边界 bug。
本章综合练习
- 组件库:团队私有 OWL 组件 命名前缀 约定(模块名 + 功能 示例 3 个)。
- 无障碍:按钮
aria-label检查清单(5 条以内)。 - 综合:x2many 列表内嵌 自定义字段时,需 额外测试 的 两种交互(从 新增行、删除行、内联保存、Tab 切格 中任选其二说明)。
- 实操:为 35.1 的组件补 一条 前端单测(mock
record),或写出 完整断言思路。 - 安全:自定义 Html 字段 widget 时,三条 禁止 或 必须消毒 的规则。
- 阅读:对照 Owl components — Odoo 19.0 核对
slots在 Dialog 中的用法。
本章对应白皮书目录:第三十五章 组件开发。