关于 frontend-js
面向 Odoo 14–19 前端 JavaScript 开发的参考技能,按版本提供网站主题、Owl 组件、翻译和 Bootstrap 迁移模式。
主要功能
- 01按 Odoo 版本提供指导
- 02使用 publicWidget 构建网站主题
- 03Owl 组件模式
- 04JavaScript 翻译实践
- 05Bootstrap 4 到 5 的迁移
适用场景
- 01实现 Odoo 网站主题交互
- 02按 Odoo 版本调整前端代码
- 03为 Odoo 16+ 迁移 Bootstrap 类名
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ahmed-lakosha/ odoo-plugins --skill frontend-js安装后在 Claude Code 中输入/frontend-js确认已加载。
详细介绍
Odoo 前端 JavaScript 模式
关键规则
- 网站主题:仅使用
publicWidget框架,不要使用 Owl 或原生 JS - JS 模块:每个文件都必须以
/** @odoo-module **/开头 - 禁止内联 JS/CSS:始终将文件分开放在
static/src/js/和static/src/scss/中 - Bootstrap:Odoo 16 及更高版本使用 v5.1.3(绝不要使用 Tailwind)
- 翻译:静态 JS 标签必须在定义时使用
_t()
版本检测
| Odoo | Bootstrap | Owl | JavaScript |
|---|---|---|---|
| 14 | 4.x | — | ES6+ |
| 15 | 4.x | v1 | ES6+ |
| 16 | 5.1.3 | v1 | ES2020+ |
| 17 | 5.1.3 | v2 | ES2020+ |
| 18-19 | 5.1.3 | v2 | ES2020+ |
可根据路径(odoo17/ → v17)、manifest 版本字段或配置文件进行检测。
publicWidget 模式(主题必需)
用于:网站交互、主题功能、动画、表单
/** @odoo-module **/
import publicWidget from "@web/legacy/js/public/public_widget";
publicWidget.registry.MyWidget = publicWidget.Widget.extend({
selector: '.my-selector',
disabledInEditableMode: false, // Allow in website builder
events: {
'click .button': '_onClick',
'change input': '_onChange',
'submit form': '_onSubmit',
},
/**
* CRITICAL: Check editableMode for website builder compatibility
*/
start: function () {
if (!this.editableMode) {
this._initializeAnimation();
this._bindExternalEvents();
}
return this._super.apply(this, arguments);
},
_initializeAnimation: function () {
this.$el.addClass('animated');
},
_bindExternalEvents: function () {
$(window).on('scroll.myWidget', this._onScroll.bind(this));
$(window).on('resize.myWidget', this._onResize.bind(this));
},
_onClick: function (ev) {
ev.preventDefault();
if (this.editableMode) return;
// Handler logic
},
/**
* CRITICAL: Clean up event listeners to prevent memory leaks
*/
destroy: function () {
$(window).off('.myWidget'); // Remove namespaced events
this._super.apply(this, arguments);
},
});
export default publicWidget.registry.MyWidget;
要点
- 在动画或交互前,务必检查
this.editableMode disabledInEditableMode: false可使小部件在网站构建器中正常工作- 务必在
destroy()中清理事件监听器 - 网站主题绝不要使用 Owl 或原生 JS
- 使用带命名空间的事件(
.myWidget),以便轻松清理
在清单中包含
'assets': {
'web.assets_frontend': [
'module_name/static/src/js/my_widget.js',
],
}
Owl 组件模式
Odoo 17(Owl v1)
/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
class MyComponent extends Component {
setup() {
this.state = useState({ items: [], loading: false });
}
async willStart() {
await this.loadData();
}
}
MyComponent.template = "module_name.MyComponentTemplate";
registry.category("public_components").add("MyComponent", MyComponent);
Odoo 18-19(Owl v2 — 重大变更)
/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
class MyComponent extends Component {
static template = "module_name.MyComponentTemplate"; // Static property
static props = {
title: { type: String, optional: true },
items: { type: Array },
};
setup() {
this.state = useState({ selectedId: null });
}
}
XML 模板
<template id="MyComponentTemplate" name="My Component">
<div class="my-component">
<h3 t-if="props.title"><t t-esc="props.title"/></h3>
<ul>
<li t-foreach="props.items" t-as="item" t-key="item.id">
<t t-esc="item.name"/>
</li>
</ul>
</div>
</template>
翻译(_t)最佳实践
正确 — 在定义时包装
/** @odoo-module **/
import { _t } from "@web/core/l10n/translation";
// Static labels wrapped where defined
const MONTHS = [
{value: 1, short: _t("Jan"), full: _t("January")},
{value: 2, short: _t("Feb"), full: _t("February")},
// ...
];
const STATUS_LABELS = {
draft: _t("Draft"),
pending: _t("Pending"),
approved: _t("Approved"),
};
错误 — 运行时包装不起作用
// WRONG: Strings without _t() at definition
const MONTHS = [{value: 1, label: "Jan"}]; // NOT found by PO extractor!
// WRONG: Variable passed to _t() at runtime
translateLabel(key) {
return _t(key); // PO extractor can't find string literals
}
何时使用 _t()
| 使用 _t() | 不使用 _t() |
|---|---|
| JS 数组/对象中的静态标签 | XML 模板中的静态文本(自动翻译) |
| JS 常量中的错误消息 | 运行时传入的动态变量 |
| JS 中定义的面向用户的字符串 | .xml 文件中的硬编码字符串 |
Bootstrap 4 → 5 迁移(Odoo 14/15 → 16+)
类替换
| Bootstrap 4 | Bootstrap 5 |
|---|---|
ml-* | ms-*(起始侧外边距) |
mr-* | me-*(末尾侧外边距) |
pl-* | ps-*(起始侧内边距) |
pr-* | pe-*(末尾侧内边距) |
text-left | text-start |
text-right | text-end |
float-left | float-start |
float-right | float-end |
form-group | mb-3 |
custom-select | form-select |
close | btn-close |
badge-* | bg-* |
font-weight-bold | fw-bold |
sr-only | visually-hidden |
no-gutters | g-0 |
数据属性
| Bootstrap 4 | Bootstrap 5 |
|---|---|
data-toggle | data-bs-toggle |
data-target | data-bs-target |
data-dismiss | data-bs-dismiss |
已移除的类(请查找替代项)
form-inline→ 使用网格/弹性布局工具类jumbotron→ 使用工具类重新创建media→ 使用d-flex和弹性布局工具类
SCSS Bootstrap 覆盖
文件:static/src/scss/bootstrap_overridden.scss
资源包:web._assets_frontend_helpers
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
$spacer: 1rem !default;
$border-radius: 0.25rem !default;
$border-radius-lg: 0.5rem !default;
$box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15) !default;
所有覆盖项都使用 !default 标志。
特定版本说明
Odoo 17
- Owl v1:将模板作为单独属性
- 片段注册:使用简单 XPath
- 导入:
@web/legacy/js/public/public_widget
Odoo 18-19
- Owl v2:静态模板、属性验证
- 必须配置片段组
- 网站构建器:插件架构(Odoo 19)
- 重大变更:控制器中的
type='json'→type='jsonrpc'
评论
支持 Markdown