第三十五章 组件开发

篇别:第八篇 前端开发

本章学习目标

  • 从 0 实现可复用的 OWL 字段组件展示组件,并挂入 fields registry
  • 理解 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:常见 recordRelationalRecord)、name(字段名)、readonlytype禁止 依赖未文档化的 全局单例(除 env / service 等官方路径)。
  • 更新:可编辑场景用 record.update({ [name]: value });只读组件 勿写库onWillUpdateProps 中对比 新旧 record,避免 陈旧缓存
  • 模板static template = xml\...``独立 XMLDOM 宜浅,便于 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 表单中自定义 widget 渲染 ISBN 提示

35.1.4 本节练习

  1. 实操:实现 只读 「ISBN 校验位」颜色提示(可用 简化校验规则 演示)。
  2. 简答editable list 中自定义字段组件 不写 record.update 会怎样?
  3. 判断:字段组件可直接 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 相关文件,记录:titlecontentfooter slotssize等价枚举关闭回调 的 prop 名。

35.2.3 截图占位

图 35-2 官方文档或源码中的组件列表

35.2.4 本节练习

  1. 拓展:阅读 Dialog 组件源码,记录 不少于 3 个 props 名
  2. 简答直接 patch Dialog封装 MyDialog 再使用 的取舍?
  3. 实操:用 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 Powerbox 命令列表

35.3.4 本节练习

  1. 简答:允许用户嵌入 任意 iframe(如外部地图)的 两条风险
  2. 判断:前端 DOMPurify 消毒可 替代 后端存储前过滤。( )
  3. 简答站内帮助页客户可见网站页 的 HTML 策略应如何区分?

参考答案提示:2. 错;后端最终把关。3. 外站更严、内站可适度放宽但仍要消毒。


35.4 与第三十二章、第三十三章的衔接

35.4.1 知识要点

  • 组件通信子块拆分32.8Service 获取 orm32.5 / 33.3
  • 样式:组件旁 SCSS第三十四章全局主题32.7
  • patch改核心字段基类评估升级成本;能 Registry 替换优先 Registry

35.4.2 本节练习

  1. 简答:字段组件与 客户端动作组件props 上的 主要差异
  2. 综合:说明 字段组件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.dataupdate,断言 模板输出update 调用;参考 web 模块字段测试 目录结构。
  • x2many:在 内联列表 中重测 新建行、删除行、失去焦点保存手机宽度 下再测一遍。

35.5.2 案例

// 断言思路(伪代码):mount(IsbnHintField, { props: { record: mockRecord, name: "isbn", ... } })
// expect(mockRecord.update).not.toHaveBeenCalled()  // 只读

35.5.3 截图占位

图 35-5 前端测试运行器中的字段组件用例

35.5.4 本节练习

  1. 实操:为 35.1 的组件写 一条 mount 测试 或写出 mock 字段清单
  2. 简答x2many 内联主表单 测试差异 两点

参考答案提示:2. 行级 record、焦点切换、子表 dirty 状态 更易出边界 bug。


本章综合练习

  1. 组件库:团队私有 OWL 组件 命名前缀 约定(模块名 + 功能 示例 3 个)。
  2. 无障碍按钮 aria-label 检查清单5 条以内)。
  3. 综合x2many 列表内嵌 自定义字段时,需 额外测试两种交互(从 新增行、删除行、内联保存、Tab 切格 中任选其二说明)。
  4. 实操:为 35.1 的组件补 一条 前端单测(mock record),或写出 完整断言思路
  5. 安全:自定义 Html 字段 widget 时,三条 禁止必须消毒 的规则。
  6. 阅读:对照 Owl components — Odoo 19.0 核对 slotsDialog 中的用法。

本章对应白皮书目录:第三十五章 组件开发。