当前位置: 首页 > news >正文

构建 Odoo 18 移动端导航:深入解析 OWL 框架、操作与服务

第一节:架构基础:Odoo 的操作驱动导航模型

在 Odoo 18 中使用 Odoo Web 库 (OWL) 构建移动端或任何复杂的前端应用时,理解其核心导航范式至关重要。与许多传统单页应用 (SPA) 框架不同,Odoo 的导航并非主要由客户端的 URL 路由驱动,而是由一个在服务器端定义的、名为“操作 (Action)”的系统来精心编排。对于习惯了纯前端路由的开发者而言,这是一个根本性的思维转变。本节将深入剖析 Odoo 的操作体系,并将 ir.actions.client 模型定位为所有定制化 OWL 应用的基石与入口。

1.1 解构 Odoo 操作体系

Odoo 的用户界面本质上是通过“操作”来协调的。这些操作是存储在数据库中的记录,它们定义了系统应如何响应用户的交互(例如点击菜单项)。这是一种以服务器为中心的模式,由后端决定前端可用的导航路径和行为。为了更好地理解其上下文,有必要对比几种核心操作类型:

  • 窗口操作 (ir.actions.act_window): 这是最经典的操作类型,用于显示特定模型的视图(如列表、表单、看板视图)。
  • 服务操作 (ir.actions.server): 用于在服务器端执行预定义的 Python 代码。
  • URL 操作 (ir.actions.act_url): 用于在新标签页或当前窗口打开内部或外部的 URL。
  • 客户端操作 (ir.actions.client): 这是本文的焦点。此操作类型将控制权完全委托给一个客户端(即 JavaScript)组件。它是启动定制 OWL 应用的官方指定机制。

1.2 客户端操作 (ir.actions.client):通往 OWL 应用的门户

客户端操作是在 ir.actions.client 模型中定义的一条记录,它扮演着连接 Odoo 后端(如菜单项点击事件)与特定 JavaScript 组件的桥梁角色。

关键字段:

  • name: 操作的人类可读名称,将显示在 UI 上。
  • tag: 这是最关键的字段。它是一个唯一的字符串标识符,用于将 XML 中定义的操作记录与在客户端 actions 注册表中注册的 JavaScript 组件关联起来。
  • params: 一个可选的字典,用于向客户端组件传递静态或动态数据。这是初始化页面时传递数据的主要方式之一。
  • target: 定义操作的显示方式。常见的值包括 current(在主内容区打开,替换当前视图)、new(在对话框或弹窗中打开)和 fullscreen(全屏模式)。对于移动应用,currentfullscreen 是最常用的选项。

1.3 实现演练:创建您的第一个可导航“页面”

本小节将通过一个完整的步骤指南,演示如何创建一个可启动的基础 OWL 组件,我们将其视为移动应用中的一个“页面”。

步骤 1:在 XML 中定义客户端操作

首先,需要创建一个 ir.actions.client 记录,并通常会创建一个 menuitem 来触发它,完成后端配置。

代码示例:

<record id="action_my_mobile_app_main" model="ir.actions.client"><field name="name">My Mobile App</field><field name="tag">my_mobile_app.MainScreen</field>
</record><menuitem id="menu_my_mobile_app_root"name="My Mobile App"action="action_my_mobile_app_main"sequence="10"/>

步骤 2:创建 OWL 组件

接下来,为应用的主屏幕定义 JavaScript 类。

代码示例:

/** @odoo-module **/import { Component } from "@odoo/owl";export class MainScreen extends Component {static template = "my_mobile_app.MainScreenTemplate";// 组件逻辑将在此处实现
}

步骤 3:定义组件的模板

使用 QWeb 模板为组件提供 HTML 结构。

代码示例:

<templates xml:space="preserve"><t t-name="my_mobile_app.MainScreenTemplate" owl="1"><div class="o_my_mobile_app"><h1>Welcome to My Mobile App</h1></div></t>
</templates>

步骤 4:将组件注册为操作

这是将 XML 中的 tag 与 JavaScript 组件连接起来的关键一步。

代码示例:

/** @odoo-module **/import { registry } from "@web/core/registry";
import { Component } from "@odoo/owl";export class MainScreen extends Component {static template = "my_mobile_app.MainScreenTemplate";
}registry.category("actions").add("my_mobile_app.MainScreen", MainScreen);

步骤 5:定义资源文件

最后,确保 Odoo 的资源管理系统能够加载这些新建的 JS 和 XML 文件。

代码示例:

# 位于 __manifest__.py
'assets': {'web.assets_backend': ['my_module/static/src/components/**/*.js','my_module/static/src/xml/**/*.xml',],
},

此流程的底层逻辑可以被理解为一种“命令模式”的实现。当用户点击菜单项时,该交互与一个操作 ID 相关联。服务器随后将此操作的定义(一个字典)返回给客户端。客户端的 ActionManager(操作管理器)接收此字典,并检查其 type 字段。如果类型是 ir.actions.client,它会使用 tag 字段作为关键索引,在 actions 注册表 (registry.category("actions")) 中查找对应的 OWL 组件类。找到后,ActionManager 会实例化该组件并将其挂载到 UI 中。这个过程清晰地将请求(菜单点击)与执行(渲染 OWL 组件)解耦,为 Odoo 提供了巨大的灵活性和可扩展性。因此,为了让移动应用与Odoo 的其余部分无缝集成,开发者应在顶层导航(如从菜单打开应用)中拥抱这种操作驱动的模型,而不是试图从一开始就强行引入纯客户端的路由解决方案。

第二节:编程方式的正向导航:掌握 action 服务

本节将从静态的入口点转向动态的应用内导航。我们将引入 action 服务,它是 OWL 组件中用于触发导航到另一个页面、视图或操作的主要工具。我们将探讨如何使用它,以及在这些转换过程中如何传递数据。

2.1 Odoo JavaScript 服务简介

服务 (Services) 是在整个客户端应用中可用的单例对象,它们提供了核心功能,如显示通知、发起 RPC 调用,或者在本例中,执行操作。使用服务的标准、惯用方式是在 OWL 组件的 setup 方法中使用 useService 钩子。

代码示例:

import { useService } from "@web/core/utils/hooks";//... 在一个 OWL 组件类内部
setup() {this.actionService = useService("action");this.notificationService = useService("notification");
}

2.2 doAction 方法:导航的主力

actionService.doAction() 是从客户端以编程方式触发任何 Odoo 操作的方法。它可以执行窗口操作、服务器操作或其他客户端操作。

doAction 方法可以接受一个操作的 xml_id(字符串)或一个操作的字典定义(对象),后者在构建动态导航时更为灵活。

用例 1:导航到标准的 Odoo 视图

此示例演示了如何打开特定记录的表单视图。

代码示例:

// 在一个 OWL 组件的方法中
async openPartnerForm(partnerId) {await this.actionService.doAction({type: 'ir.actions.act_window',res_model: 'res.partner',res_id: partnerId,views: [[false, 'form']],target: 'current', // 'current' 会替换主视图内容context: { /* 如果需要,可以添加额外的上下文 */ },});
}

用例 2:导航到另一个 OWL 组件(页面)

这是移动应用导航的核心。通过触发另一个 ir.actions.client 来实现页面跳转。

代码示例:

// 在一个 OWL 组件的方法中,导航到 "DetailPage" 组件
async openDetailPage(itemId) {await this.actionService.doAction({type: 'ir.actions.client',tag: 'my_mobile_app.DetailPage',params: {record_id: itemId,some_other_info: 'hello'}});
}

2.3 在页面间传递数据:contextparams

当一个操作被执行时,其 contextparams 字典会被传递给新创建的组件,并通过 this.props 访问。这是数据传递的主要渠道。

  • params vs. context:
    • params: 通常用于在客户端操作之间传递客户端特定的信息。
    • context: 是一个标准的 Odoo 概念。它会在 RPC 调用中被传递到服务器,并能影响服务器端的逻辑(例如,设置默认值、过滤域等)。

在目标组件中接收数据:

来自操作的 params 和 context 会被合并到组件的 props 对象中。

代码示例 (在 DetailPage.js 中):

import { onWillStart, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";//...
export class DetailPage extends Component {static template = "my_mobile_app.DetailPageTemplate";setup() {this.state = useState({ recordData: null });this.orm = useService("orm");onWillStart(async () => {// 通过 this.props 访问传递的参数const recordId = this.props.params?.record_id;if (recordId) {// 使用该 ID 从服务器获取数据const data = await this.orm.read("my.model", [recordId], ["name", "description"]);this.state.recordData = data;}});}
}

这个例子展示了一个完整的“导航并获取数据”的周期。MainScreen 组件使用带 paramsdoAction 来启动 DetailPageDetailPageonWillStart 生命周期钩子中从其 props 中访问传递的 record_id,并在初次渲染前使用 orm 服务获取相关数据。

在 Odoo 的发展过程中,action 服务是 ActionManager 的一个现代抽象层。它为 OWL 开发提供了一个简洁的、基于钩子的 API (useService),隐藏了旧版本 Odoo 中基于事件的、更复杂的通信机制。这种设计遵循了现代框架(如 React 的钩子和 Angular 的依赖注入)的模式,使得 Odoo 前端开发对新开发者来说更易于维护和理解。因此,开发者在 OWL 组件中应始终优先使用

useService("action") 进行导航,这是现代、受支持且符合惯例的方法。

第三节:实现后退导航:操作堆栈与 restore

本节将解决用户查询中的“返回”部分。Odoo 维护着自己的导航堆栈,并提供了一个特定的机制——action.restore()——来实现后退功能。与依赖浏览器原生历史记录相比,这是一种更为健壮的方案。

3.1 ActionManager 的内部堆栈

Odoo 的 ActionManager 不仅仅是执行操作,它还维护着一个操作堆栈。当使用 doAction 时,一个新的操作通常会被推入这个堆栈,代表了用户在应用内的导航历史。Odoo UI 顶部的面包屑导航就是这个操作堆栈的一个可视化表现。操作的 target 属性可以影响堆栈行为,例如,target: 'main' 可能会重置堆栈,从而清空面包屑。

3.2 用于编程“后退”的 action.restore() 方法

action.restore() 方法是以编程方式从堆栈中弹出当前操作并“恢复”前一个操作的方式,其功能等同于一个“后退”按钮。

3.3 内置的 history_back 客户端操作

Odoo 框架提供了一个预定义的、标签为 history_back 的客户端操作。这个操作专门用于处理后退导航。触发此操作是实现后退按钮最可靠且面向未来的方式。它封装了对 action.restore() 的调用,确保了即使 restore 的底层实现在未来的 Odoo 版本中发生变化,history_back 操作的行为仍将保持稳定。

实现方式:

可以在移动 UI 中创建一个“返回”按钮,并将其 t-on-click 事件绑定到一个调用该操作的方法上。

模板代码:

<button class="btn btn-secondary" t-on-click="goBack">返回</button>

JavaScript 代码:

// 在 setup() 中: this.actionService = useService("action");async goBack() {// 只需执行 'history_back' 客户端操作即可await this.actionService.doAction({ type: 'ir.actions.client', tag: 'history_back' });
}

3.4 陷阱:浏览器返回按钮 vs. action.restore()

依赖浏览器的原生返回按钮 (window.history.back()) 是不可靠的,并且可能导致错误。一个 GitHub 问题描述了一个场景:在一个未完成的表单上使用浏览器的返回按钮会导致整个 UI 卡死。这是因为浏览器的历史记录与 Odoo 的内部状态(例如,必填字段的验证状态)变得不同步。

Odoo 的操作堆栈是导航状态的“唯一真实来源”。action.restore()(通过 history_back 操作)正确地与这个真实来源交互,允许 Odoo 妥善管理组件的生命周期和状态转换。将浏览器历史记录与应用的内部操作堆栈分离是 Odoo 的一个刻意架构选择,旨在创建比典型网站更健壮、状态感知更强的用户体验。当 action.restore() 被调用时,它不仅仅是“返回”,而是一个受控的、对当前操作的销毁过程,以及一个对前一个操作的受控的重新挂载过程。因此,对于基于 Odoo 构建的移动应用,开发者必须提供自己的 UI 控件(例如,页眉中的 < 返回 按钮)并将其连接到 history_back 操作。不应假设用户会或能够使用设备的本机返回功能,因为它可能未与 Odoo 的操作堆栈集成。

第四节:使用 router 服务实现高级 SPA 路由

本节将探讨一种更高级的导航模式,用于在单个客户端操作内部创建流畅的单页应用 (SPA) 体验。这对于包含许多内部屏幕的复杂移动应用是理想选择,因为在这种场景下,为每次屏幕切换都触发一个完整的 Odoo 操作会显得缓慢和笨重。

4.1 router 服务简介

router 服务提供了一个较低级别的接口,用于与浏览器的 URL 哈希 (#) 和历史 API (pushState) 进行交互。

关键方法:

  • router.current: 一个包含当前哈希信息(路径、参数)的对象。
  • router.navigate(hash): 向浏览器历史记录中推送一个新状态并更新 URL 哈希。
  • router.redirect(hash): 替换浏览器历史记录中的当前状态。

访问服务:

// 在 setup() 中
this.router = useService("router");

4.2 案例研究:pos_self_order 应用

Odoo 自身的 pos_self_order 模块是自定义路由器实现的绝佳范例。其架构可以解构如下:

  1. 整个自助点餐应用通过单个 ir.actions.client 启动。
  2. 在这个主组件内部,一个 Router 组件监听 router 服务的变化。
  3. 它使用 URL 哈希(例如 #/products, #/cart, #/payment)来条件性地渲染不同的子组件(ProductScreen, CartScreen 等)。
  4. 这创造了一种多页面的体验,却从未离开初始的客户端操作,从而实现了非常快速和流畅的导航。

4.3 构建一个简单的自定义路由器

本小节提供了一个实用指南,用于实现 pos_self_order 模式的简化版本。

步骤 1:主应用组件(外壳)

此组件将包含路由逻辑。

代码示例:

/** @odoo-module **/import { Component, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { registry } from "@web/core/registry";
import { HomeScreen } from "./home_screen";
import { SettingsScreen } from "./settings_screen";class MobileAppContainer extends Component {static template = "my_mobile_app.AppContainer";static components = { HomeScreen, SettingsScreen };setup() {this.router = useService("router");// 要显示的组件派生自 URL 哈希this.state = useState({get currentScreen() {// 如果哈希为空或未知,则默认为主页switch (this.router.current.hash.path) {case "settings": return "SettingsScreen";default: return "HomeScreen";}}});}
}registry.category("actions").add("my_mobile_app.Container", MobileAppContainer);

步骤 2:带有条件渲染的模板

使用 t-if 或 t-component 在不同屏幕之间切换。

代码示例:

<t t-name="my_mobile_app.AppContainer" owl="1"><div class="mobile-app-container"><t t-if="state.currentScreen === 'HomeScreen'"><HomeScreen /></t><t t-if="state.currentScreen === 'SettingsScreen'"><SettingsScreen /></t></div>
</t>

步骤 3:触发导航

子组件使用 router.navigate 来改变屏幕。

代码示例 (在 HomeScreen.js 中):

// 在 setup() 中: this.router = useService("router");goToSettings() {this.router.navigate({ path: "settings" });
}

action 服务和 router 服务并非互斥;它们在不同的抽象层次上运作,可以结合使用以创建复杂的应用程序。action 服务用于粗粒度的、应用级别的导航(例如,打开销售应用、打开库存应用、打开我的移动应用),它管理 Odoo 的主操作堆栈和面包屑。而 router 服务用于细粒度的、应用内部的导航(例如,在我的移动应用内,从主列表转到设置屏幕),它管理浏览器的 URL 哈希和本地组件状态。

对于复杂的移动应用,最佳架构是一种混合模式:使用单个 ir.actions.client 作为入口点(“外壳”)。在该外壳内部,使用 router 服务和条件渲染来管理应用的内部屏幕。如果应用需要导航到 Odoo 的一个完全不同的部分(例如,打开一个标准的产品表单),它仍然可以使用 action 服务的 doAction 方法。因此,开发者应根据导航的范围选择合适的工具。

第五节:综合与架构建议

本节将前述概念综合成一套清晰的建议和最佳实践,提供一个决策框架,帮助开发者根据具体需求选择正确的导航策略。

5.1 决策框架:选择您的导航策略

为指导开发者做出选择,可以考虑以下问题:

  • 您的功能是一个孤立的单屏幕,还是一个多屏幕的应用?
  • 您的导航是否需要反映在 URL 中以便于收藏或分享?
  • 您的应用需要与 Odoo 标准视图集成到何种程度?
  • 应用内屏幕切换的性能有多重要?

根据答案,可以推荐以下三种模式之一:

  1. 简单操作模式:使用多个 ir.actions.client 记录,并通过 doAction 在它们之间导航。最适合简单的 UI 或需要与 Odoo 面包屑系统深度集成的情况。
  2. 混合 SPA 模式(移动端推荐):使用单个 ir.actions.client 作为外壳,通过 router 服务管理内部导航。仅在需要导航到应用外部时才使用 doAction。这是实现高性能和流畅移动体验的最佳选择。
  3. 状态驱动模式(无路由器):对于一个组件内非常简单的多步骤向导,完全可以不使用 router 服务,仅通过 useState 来控制模板的哪个部分可见。

5.2 Odoo 移动导航最佳实践

  • 始终使用 useService:避免直接访问全局管理器。
  • 实现自定义“返回”按钮:将其功能绑定到 history_back 操作。不要依赖设备的本机返回按钮。
  • 集中化数据获取:在 onWillStart 钩子中根据导航时传递的 props 来加载数据。
  • 谨慎管理状态:对于混合 SPA 模式下跨多个屏幕的共享状态,可以考虑创建一个自定义服务或从主容器组件通过 props 向下传递状态。
  • 提供用户反馈:在数据获取或操作执行期间,使用 notification 服务或加载指示器(ui 服务)来改善用户体验。

5.3 导航机制对比分析

下表总结了本文中讨论的各种导航机制的权衡,为架构决策提供了一个高密度的、可扫描的参考。

表 1: Odoo 18 导航机制对比

特性

action.doAction (正向导航)

history_back / action.restore (后退导航)

router.navigate (SPA 导航)

主要用例

在 Odoo 主要应用/视图之间导航

返回到堆栈中的前一个视图

在单个客户端操作内进行应用内导航

机制

执行服务器定义的操作记录

从内部 ActionManager 堆栈中弹出一个操作

操作浏览器 URL 哈希并触发客户端重渲染

URL 影响

通常改变主 URL 路径 (如 /web#action=...)

将 URL 恢复到上一个操作的状态

仅改变哈希片段 (如 /web#...&hash=settings)

历史管理

推入 Odoo ActionManager 堆栈

从 Odoo ActionManager 堆栈中弹出

推入浏览器的 History API (在哈希内管理)

数据传递

通过操作定义中的 contextparams,在 props 中可用

不适用(恢复先前的状态)

通过哈希参数或共享的组件状态/服务

性能

较慢(涉及服务器通信和完整的组件销毁/重挂载)

中等(客户端操作,但仍是完整的重挂载)

最快(纯客户端操作,通常仅重渲染子组件)

使用时机

导航到不同的顶层功能或 Odoo 标准视图时

用于所有面向用户的“返回”按钮

在单个操作内构建流畅的多屏幕移动应用时

http://www.dtcms.com/a/296915.html

相关文章:

  • P1013 [NOIP 1998 提高组] 进制位
  • 【算法】递归、搜索与回溯算法入门
  • 星痕共鸣数据分析2
  • 【Guava】1.1.我的报告
  • 移动前端开发与 Web 前端开发的区别
  • 电商接口常见误区与踩坑提醒
  • 3.SOAP
  • 跨境支付入门~国际支付结算(风控篇)
  • 酷狗最新版KG-DEVID 算法分析
  • Unity 时间抗锯齿(Temporal Antialiasing, TAA)技术解析
  • T-RO顶刊|单视角“找相似”,大阪大学提出新型点云描述符(C-FPFH),杂乱场景一抓一个准!
  • 2025国自然青基、面上会评结束,资助率或创新低,跌破11.19%!
  • 期货交易系统用户操作与应用逻辑全析
  • springboot实战demo2
  • 图像识别任务的边界正在改变
  • Linux系统编译安装PostgreSQL 12.8(含报错处理与配置热加载)
  • C++标准库算法实战指南
  • Linux 进程间通信:共享内存详解
  • 2025年人形机器人动捕技术研讨会于7月31日在京召开
  • 如何使用 pdfMake 中文字体
  • Next.js 中配置不同页面布局方案
  • 无锡市亨达电机盛装亮相 2025上海生物发酵展引关注
  • 深入理解大语言模型生成参数:temperature、top\_k、top\_p 等全解析
  • 首发即开源!DAWorkBench数据可视化分析软件正式发布!(附源码下载网址)
  • ubuntu安装teams解决方法
  • JavaScript中this的5大核心规则详解
  • vue 项目中 components 和 views 包下的组件功能区别对比,示例演示
  • Eureka-服务注册,服务发现
  • CSDN技术专栏开篇:高效开发环境搭建指南
  • Android Activity与Fragment生命周期变化