关于 theme-snippets
这是一份 Odoo 网站片段开发参考,涵盖静态与数据驱动模板、可配置选项和版本差异,适合跨 Odoo 版本创建或定制片段的开发者。
主要功能
- 01静态片段模板目录
- 02动态片段的数据流
- 03片段选项元素与属性
- 04JavaScript 处理器和组件
- 05Odoo 14–19 版本差异
适用场景
- 01为落地页或常见问题选择片段
- 02在 Odoo 模块中注册自定义片段
- 03配置片段的可编辑控件
- 04构建数据驱动的产品展示
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ahmed-lakosha/ odoo-plugins --skill theme-snippets安装后在 Claude Code 中输入/theme-snippets确认已加载。
详细介绍
Odoo 网站片段参考
片段架构
STATIC SNIPPETS (81+ templates) DYNAMIC SNIPPETS (data-driven)
├── Pre-built HTML ├── ir.filters (data source)
├── Drag & drop ├── website.snippet.filter (fetcher)
└── 6 categories └── Display templates (renderer)
SNIPPET OPTIONS SYSTEM
we-select | we-button | we-colorpicker | we-input
data-selector | data-select-class | data-js
静态片段清单(81+ 个模板)
结构(14)
| ID | 名称 | 最适用场景 |
|---|---|---|
s_banner | 横幅 | 落地页 |
s_cover | 封面 | 页眉 |
s_text_image | 文本 - 图片 | 功能 |
s_image_text | 图片 - 文本 | 功能 |
s_title | 标题 | 区块标题 |
s_text_block | 文本块 | 内容 |
s_numbers | 数字 | 成就 |
s_three_columns | 栏 | 内容网格 |
s_features | 功能 | 优势 |
s_masonry_block | 瀑布流 | 图库 |
s_image_gallery | 图片库 | 作品集 |
功能(11)
| ID | 名称 | 最适用场景 |
|---|---|---|
s_comparisons | 对比 | 产品对比 |
s_company_team | 团队 | 关于页面 |
s_call_to_action | 行动号召 | 转化 |
s_references | 引用 | 社会认同 |
s_accordion | 手风琴 | 常见问题 |
s_features_grid | 功能网格 | 优势 |
s_pricelist | 价格表 | 产品 |
s_faq_collapse | 常见问题 | 支持 |
s_tabs | 选项卡 | 内容组织 |
动态内容(15)
| ID | 名称 | 最适用场景 |
|---|---|---|
s_google_map | Google 地图 | 联系页面 |
s_website_form | 表单 | 潜在客户收集 |
s_social_media | 社交媒体 | 互动 |
s_dynamic_snippet | 动态内容 | 数据展示 |
s_countdown | 倒计时 | 活动 |
s_popup | 弹出窗口 | 促销 |
s_newsletter_block | 新闻简报 | 营销 |
内部内容(16)
| ID | 名称 | 最适用场景 |
|---|---|---|
s_card | 卡片 | 项目 |
s_three_cards / s_four_cards | 卡片 | 服务/功能 |
s_timeline | 时间线 | 历史 |
s_process_steps | 步骤 | 教程 |
s_quotes_carousel | 用户评价 | 社会认同 |
s_blockquote | 引用块 | 强调 |
大型菜单(9)
| ID | 名称 |
|---|---|
s_mega_menu_multi_menus | 多列菜单 |
s_mega_menu_cards | 卡片式菜单 |
s_mega_menu_big_icons | 大图标菜单 |
s_mega_menu_thumbnails | 缩略图图库 |
创建自定义片段
Odoo 14-17:简单注册
<template id="s_my_snippet" name="My Snippet">
<section class="s_my_snippet pt48 pb48 o_cc o_cc1">
<div class="container">
<div class="row">
<div class="col-lg-12 text-center">
<h2>My Custom Snippet</h2>
<p>Content goes here...</p>
</div>
</div>
</div>
</section>
</template>
<template id="s_my_snippet_insert" inherit_id="website.snippets">
<xpath expr="//div[@id='snippet_structure']//t[@t-snippet][last()]" position="after">
<t t-snippet="my_module.s_my_snippet"
t-thumbnail="/my_module/static/src/img/snippets/s_my_snippet.svg">
<keywords>my, custom, snippet</keywords>
</t>
</xpath>
</template>
Odoo 18-19:使用片段组(必需)
<template id="snippet_group_custom" inherit_id="website.snippets">
<xpath expr="//div[@id='snippet_groups']" position="inside">
<t snippet-group="custom"
t-snippet="website.s_snippet_group"
string="Custom Snippets"/>
</xpath>
</template>
<template id="s_my_snippet_insert" inherit_id="website.snippets">
<xpath expr="//div[@id='snippet_structure']/*[1]" position="before">
<t t-snippet="my_module.s_my_snippet"
string="My Snippet"
group="custom"
t-thumbnail="/my_module/static/src/img/snippets/s_my_snippet.svg"/>
</xpath>
</template>
面板类别
| 面板 ID | 类别 | XPath |
|---|---|---|
snippet_structure | 结构 | //div[@id='snippet_structure'] |
snippet_media | 图库 | //div[@id='snippet_media'] |
snippet_feature | 功能 | //div[@id='snippet_feature'] |
snippet_dynamic | 动态内容 | //div[@id='snippet_dynamic'] |
snippet_inner_content | 内部内容 | //div[@id='snippet_inner_content'] |
片段选项系统
We-* 元素
| 元素 | 用途 | 关键属性 |
|---|---|---|
we-select | 下拉菜单 | data-name、data-dependencies |
we-button | 切换 | data-select-class、data-toggle-class |
we-colorpicker | 颜色选择器 | data-css-property |
we-input | 文本/数字 | data-attribute-name、data-unit |
we-range | 滑块 | data-min、data-max、data-step |
we-checkbox | 切换 | data-select-class |
数据属性
| 属性 | 用途 |
|---|---|
data-selector | 目标 CSS 选择器 |
data-select-class | 切换 CSS 类 |
data-css-property | 要修改的 CSS 属性 |
data-js | JavaScript 处理程序类 |
data-dependencies | 条件可见性 |
data-no-preview | 禁用实时预览 |
完整选项示例
<template id="s_my_snippet_options" inherit_id="website.snippet_options">
<xpath expr="." position="inside">
<div data-js="MySnippetHandler"
data-selector=".s_my_snippet"
data-drop-in=".oe_structure">
<we-select string="Layout" data-name="layout_opt">
<we-button data-select-class="layout-grid">Grid</we-button>
<we-button data-select-class="layout-list">List</we-button>
</we-select>
<we-colorpicker string="Background"
data-select-style="true"
data-css-property="background-color"/>
<we-range string="Spacing"
data-select-style="true"
data-css-property="gap"
data-min="0" data-max="100" data-step="5" data-unit="px"/>
</div>
</xpath>
</template>
内置 JS 处理程序
BackgroundImage、BackgroundPosition、BackgroundToggler、ColoredLevelBackground、BackgroundShape、ImageTools、Carousel、DynamicSnippet
动态片段
架构
Data Source (ir.filters) → Filter (website.snippet.filter) → Display Template
创建动态片段
步骤 1:定义筛选器
<record id="dynamic_filter_my_items" model="website.snippet.filter">
<field name="name">My Items</field>
<field name="model_name">my.model</field>
<field name="limit">12</field>
<field name="filter_id" ref="ir_filter_my_items"/>
</record>
步骤 2:显示模板
<template id="dynamic_filter_template_my_model_card" name="Card">
<t t-foreach="records" t-as="record">
<div class="col-lg-4 mb-4">
<div class="card h-100 shadow-sm">
<img t-att-src="record.image_url" class="card-img-top"/>
<div class="card-body">
<h5 class="card-title" t-esc="record.name"/>
</div>
</div>
</div>
</t>
</template>
步骤 3:动态片段
<template id="s_dynamic_my_items" name="Dynamic My Items">
<section class="s_dynamic_snippet s_dynamic_my_items pt48 pb48"
data-snippet="s_dynamic_my_items"
data-filter-id="my_module.dynamic_filter_my_items"
data-template-key="my_module.dynamic_filter_template_my_model_card"
data-number-of-elements="6">
<div class="container">
<div class="dynamic_snippet_template row"/>
</div>
</section>
</template>
Product Dynamic Snippets (website_sale)
Filters: dynamic_filter_newest_products, dynamic_filter_recently_sold_products, dynamic_filter_recently_viewed_products
Display templates: card_style, centered, horizontal_card, mini, minimalist_*, banner
Snippet JavaScript
publicWidget for Snippets
/** @odoo-module **/
import publicWidget from "@web/legacy/js/public/public_widget";
publicWidget.registry.MySnippet = publicWidget.Widget.extend({
selector: '.s_my_snippet',
disabledInEditableMode: false,
start: function () {
if (!this.editableMode) {
this._initializeAnimations();
}
return this._super(...arguments);
},
destroy: function () {
$(window).off('.mySnippet');
this._super(...arguments);
},
});
Snippet Options Handler
/** @odoo-module **/
import options from "@web_editor/js/editor/snippets.options";
options.registry.MySnippetOption = options.Class.extend({
selectDataAttribute: function(previewMode, widgetValue, params) {
this._super(...arguments);
if (params.attributeName === 'layout') {
this._applyLayout(widgetValue);
}
},
});
Version Differences
| Feature | Odoo 14-17 | Odoo 18-19 |
|---|---|---|
| Registration | Simple XPath | Groups required |
| JavaScript | @odoo-module | Plugin-based (19) |
| Bootstrap | 4.x (14-15) / 5.1.3 (16-17) | 5.1.3 |
| Dynamic Snippets | Basic | Full + Categories |
Odoo 19 Plugin Pattern
import { Plugin } from "@html_editor/plugin";
import { registry } from "@web/core/registry";
export class MySnippetPlugin extends Plugin {
static id = "mySnippet";
resources = {
builder_options: [{
template: "my_module.MySnippetOption",
selector: "section",
applyTo: ".s_my_snippet",
}],
};
}
registry.category("website-plugins").add("mySnippet", MySnippetPlugin);
评论
支持 Markdown