第五章 小部件(Widgets)

篇别:第一篇 基础知识

本章学习目标

  • 能根据 字段类型与业务场景 为视图字段选择合适的 widget
  • 理解 widget 与底层字段类型 的约束关系,避免「有 widget 无数据」类问题。
  • 了解 OWL 字段组件 的注册方式,并能指向第三十二章做深入实现。
  • 能排查 widget 不生效(资产未加载、注册名拼写、字段类型不匹配)的常见原因。

导读:小部件解决什么问题

在 XML 视图中,同一字段类型往往有多种呈现方式:例如金额既可 plain text 显示,也可用 monetary 做币种对齐与小数格式;状态字段可用下拉框,也可用 statusbar 强化流程感。widget 属性告诉 WebClient:在 fields 注册表中选用哪一个 字段组件 来渲染该字段。

Odoo 19 中,这些组件绝大多数已是 OWL 实现;自定义扩展应优先 注册 OWL 字段组件,而不是沿用已废弃的 legacy Widget 体系。


5.1 常用字段小部件

5.1.1 知识要点

  • widget 写在 <field>,仅影响 表现层;不改变数据库类型或 ORM 行为。
  • 可用 widget 集合由 web 及已安装模块 在前端注册表 registry.category("fields") 中决定;拼写错误时通常 静默回退 到默认组件,排查时易忽略。
  • 部分 widget 依赖同视图内其他字段(如 monetary 依赖币种字段),缺字段会导致显示异常或报错。

5.1.2 字段类型与 widget 对照(常用)

字段类型 常用 widget 说明
Selection statusbarradioprioritybadge statusbar 常配合 statusbar_visible
Many2many many2many_tagsmany2many_checkboxes 标签可配合 comodel 的 color 字段(若支持)
Monetary monetary currency_field 对应字段出现在视图中(或默认可解析)
Float percentagefloat_timeprogressbar percentage 多用于 0–1 或 0–100 约定(与模块一致)
Integer handle 列表拖拽排序,常配合 sequence
Binary imagepdf_viewer(视版本) 图片预览注意体积与权限
Char urlemailphone 可点击跳转或唤起客户端
Text / Html html(富文本) 注意 XSS 与消毒策略

上表为经验归纳,以当前版本注册表为准。

5.1.3 案例

案例 A:流程状态条

<header>
  <field name="state" widget="statusbar"
         statusbar_visible="draft,sent,sale,done"
         options="{'clickable': '1'}"/>
</header>

statusbar_visible 限制 可见的选项(其余状态仍可能存在,只是条上不显示);clickable 是否允许点击切换依版本与字段是否只读而定。

案例 B:列表拖拽排序

<list default_order="sequence,name">
  <field name="sequence" widget="handle"/>
  <field name="name"/>
</list>

案例 C:金额与币种同屏

<group>
  <field name="currency_id" invisible="1"/>
  <field name="amount" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>

currency_id 对当前用户不可见但仍需参与计算,可用 invisible="1" 保留字段在视图中(注意与权限、业务暴露的平衡)。

案例 D:Many2many 标签 + 颜色(若关联模型有 color)

<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>

案例 E:布尔或状态的高亮

<field name="active" widget="boolean_toggle"/>

5.1.4 截图占位

图 5-1 表单顶部 statusbar 与列表 handle 列同框

图 5-1b 开发者模式「编辑视图:表单」中某字段的 widget 属性

5.1.5 本节练习

  1. 实操:为某 Selection 字段配置 statusbar,并故意写错 widget="statusbarr",观察界面表现与控制台(若有)报错。
  2. 简答monetary 为何通常要求币种字段同视图可解析?
  3. 判断widget 可以替代字段上的 groups 做敏感数据保护。( )

参考答案提示:2. 格式化与币种符号依赖 res.currency。3. 错,须 ACL/字段权限等后端策略。


5.2 小部件与视图修饰的配合

5.2.1 知识要点

  • options:JSON 字符串,向字段组件传参(不同 widget 键名不同)。
  • readonly / required / invisible:可与 widget 同用;注意 attrs 旧写法 在老模块中仍存在。
  • 列表装饰decoration-danger="state=='cancel'" 等,与 widget 独立,用于行样式。

5.2.2 案例

<list decoration-muted="state=='draft'" decoration-success="state=='done'">
  <field name="name"/>
  <field name="amount" widget="monetary" sum="Total"/>
</list>

5.2.3 本节练习

  1. 实操:为列表金额列加 sum 属性,观察底部合计行。
  2. 简答sum 是 ORM 聚合还是纯前端累加?

参考答案提示:2. 一般为当前列表已加载行的前端合计(大数据量分页时不等于全表)。


5.3 OWL 组件化小部件(新增)

5.3.1 知识要点

新开发推荐三步:

  1. 编写 OWL 字段组件类(实现与 record.props / standardFieldProps 等契约,以 @web 字段组件为准)。
  2. registry.category("fields").add("my_widget", 组件类或描述对象)
  3. 视图中 <field widget="my_widget"/>

AbstractField / legacy widget 应计划迁移;迁移检查:props、更新值、子键 additionalClasses、移动端

5.3.2 案例:自定义 widget 的「骨架」(与第三十二章对照)

/** @odoo-module **/
import { registry } from "@web/core/registry";
import { Component } from "@odoo/owl";
import { standardFieldProps } from "@web/views/fields/standard_field_props";

export class CounterField extends Component {
    static template = "my_module.CounterField";
    static props = { ...standardFieldProps };
}

registry.category("fields").add("counter", CounterField);
<!-- QWeb 模板 my_module.CounterField:展示 props.record.data[name] 与增减按钮,更新用 props.record.update({...}) -->

完整 update、只读态、对比 CharField 源码等见 第三十二章

5.3.3 截图占位

图 5-2 浏览器 Sources 中定位某字段组件的 OWL 类

图 5-3 在注册表调试输出中确认 `fields` 下存在 `counter`

5.3.4 本节练习

  1. 简答:OWL 字段组件相对 legacy 的三点优势(测试、类型、打包)。
  2. 拓展:在源码中打开 @web/views/fields/char/char_field.js,记录其 static props 与模板名。
  3. 实操:在本模块 assets 中注册一段 JS 后升级模块,确认 Network 中出现对应 chunk。

5.4 故障排查清单

现象 可能原因
widget 无变化 拼写错误、未升级模块、资产缓存、该字段类型不支持
monetary 显示异常 currency_id、币种为空、权限看不到币种字段
handle 无法拖拽 default_order 含 sequence、列表不可编辑、无写权限
自定义 widget 不加载 manifest assets 路径错误、未 -u 模块、注册名与 XML 不一致

5.4.1 截图占位

图 5-4 Network 中 assets 请求 404 或 200 对比

5.4.2 本节练习

  1. 实操:用无痕窗口 + 硬刷新验证 widget 相关 JS 是否更新。
  2. 简答:生产环境修改静态资源后为何要关注 CDN/浏览器缓存?

本章综合练习

  1. library.book 设计表单:state 用 statusbar,coverimage,列表用 handle 排序。
  2. Many2many 标签在 comodel 增加 color(Integer),并配置 color_field
  3. 列出你项目中 1 处 legacy widget,并写出替换为 OWL 的检查项(不少于 5 条)。
  4. 情景题:财务要求「列表合计必须等于全表」时,为何不能只依赖 sum 属性?应配合什么(报表/read_group/SQL)?

本章对应白皮书目录:第五章 小部件。