mcpField
O
odoo-frontend

odoo-frontend

github.com/ahmed-lakosha开发7648 次安装31 周前更新
代码仓库

关于 odoo-frontend

用于 Odoo 前端开发的技能,涵盖主题生成、版本检测、主题配置及前端开发实践,支持 Odoo 14–19 的版本差异。适合构建和维护 Odoo 网站及主题的开发者。

主要功能

  1. 01使用 /create-theme 生成主题模块
  2. 02检测 Odoo 版本并对应 Bootstrap
  3. 03提供主题变量与动态页面参考
  4. 04涵盖 JavaScript、PWA、测试和性能实践
  5. 05支持 Figma 转换与 Chrome DevTools 样式提取

适用场景

  1. 01为 Odoo 网站搭建主题模块
  2. 02按 Odoo 版本选择前端实现方式
  3. 03将 Figma 设计转换为 Odoo 模板
  4. 04为网站添加离线支持并检查性能

MCP 相关

README 提及 MCP 集成,用于 Figma 设计转换和 Chrome DevTools 样式提取。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ahmed-lakosha/odoo-plugins --skill odoo-frontend

安装后在 Claude Code 中输入/odoo-frontend确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

scripts/
bootstrap_mapper.py
create_theme.py
devtools_extractor.py
figma_converter.py
pwa_generator.py
theme_mirror_generator.py
version_detector.py
templates/theme_module/
__manifest__.py.template

详细介绍

Odoo 前端开发技能 v6.0

概述

此技能提供高级 Odoo 前端开发能力,包括:

  • 🎨 /create-theme 命令:生成包含所有文件、可用于生产环境的完整主题模块
  • 🔧 主题功能激活:通过 theme.utils 模型中的 _theme_xxx_post_copy() 配置模板
  • 📋 完整的动态页面参考:包含 XML ID 的所有 11 种页眉、9 种页脚、商店、产品和博客模板
  • 🎯 设计工作流:Figma → Odoo 模板匹配 → 配置 → 增强方法
  • 主题开发:完整的 $o-website-values-palettes 参考、颜色语义和主题镜像模型
  • 自动检测:自动检测 Odoo 版本并映射到正确的 Bootstrap 版本
  • publicWidget 框架:全面的模式,包含针对网站构建器的 editableMode 处理
  • 渐进式 Web 应用:Service Worker、离线支持、推送通知
  • 现代 JavaScript:TypeScript、ES2020+、Web Components、Owl v1/v2 模式
  • 测试框架:Jest、Cypress、视觉回归测试
  • 性能优化:Core Web Vitals、资源提示、关键 CSS
  • 无障碍:符合 WCAG 2.1 AA 标准、ARIA 模式、键盘导航
  • 实时功能:WebSocket、Server-Sent Events、实时协作
  • MCP 集成:Figma 设计转换和 Chrome DevTools 样式提取
  • 主题脚手架:按照规范结构生成完整主题模块
  • 版本适配:处理 Odoo 14-19 之间的差异(Bootstrap 4/5、Owl v1/v2、Snippet 结构)
  • DevOps 就绪:CI/CD 流水线、Vite 构建、自动化测试

🎨 /create-theme 命令

快速开始:生成可用于生产环境的完整 Odoo 主题模块。

用法
# Interactive mode (recommended)
/create-theme

# Quick mode with arguments
/create-theme <theme_name> <project_path>

# Full arguments
/create-theme <theme_name> <project_path> --version=17 --colors="#207AB7,#FB9F54,#F6F4F0,#FFFFFF,#191A19" --font="IBM Plex Sans"
创建内容
theme_<name>/
├── __init__.py
├── __manifest__.py
├── security/
│   └── ir.model.access.csv
├── data/
│   ├── assets.xml
│   ├── menu.xml
│   └── pages/                        # Individual page files (BEST PRACTICE!)
│       ├── home_page.xml            # Homepage (inherits website.homepage)
│       ├── aboutus_page.xml         # About Us page
│       ├── contactus_page.xml       # Contact (inherits website.contactus)
│       └── services_page.xml        # Services page
├── views/
│   ├── layout/
│   │   ├── header.xml               # Header customization (OPTIONAL)
│   │   ├── footer.xml               # Footer customization (OPTIONAL)
│   │   └── templates.xml            # Base layout templates
│   └── snippets/
│       └── custom_snippets.xml      # Custom snippet definitions
└── static/src/
    ├── scss/
    │   ├── primary_variables.scss   # Theme variables + fonts
    │   ├── bootstrap_overridden.scss # Bootstrap overrides (OPTIONAL)
    │   └── theme.scss               # Additional custom styles
    ├── js/
    │   ├── theme.js                 # publicWidget implementations
    │   └── snippets_options.js      # Snippet options (if needed)
    └── img/
💡 简化方案(推荐)

大多数情况下,无需自定义 XML,通过 $o-website-values-palettes 即可完成配置:

  • 'header-template':'default' | 'hamburger' | 'vertical' | 'sidebar'
  • 'footer-template':'default' | 'centered' | 'minimalist' | 'links' | 'descriptive'

适用自定义 header.xml/footer.xml 的情况:

  • 设计需要模板中没有的完全自定义布局
  • 需要添加模板未提供的 HTML 元素
颜色系统(o-color-1 至 o-color-5)
变量语义默认值
o-color-1品牌主色#207AB7
o-color-2辅助色/强调色#FB9F54
o-color-3浅色背景#F6F4F0
o-color-4白色/主体基色#FFFFFF
o-color-5深色文本/标题#191A19
版本支持
  • Odoo 14-15:Bootstrap 4.5.0,简单 Snippet
  • Odoo 16-17:Bootstrap 5.1.3,现代资源包
  • Odoo 18-19:Bootstrap 5.1.3,需要 Snippet 组
直接运行脚本
python scripts/create_theme.py <theme_name> <output_path> --version=17 --colors="#207AB7,#FB9F54,#F6F4F0,#FFFFFF,#191A19"

关键规则

  1. 绝不修改 Odoo 核心代码,即 odoo/ 或 odoo/addons/ 目录中的内容
  2. 禁止内联 JS/CSS——创建独立的 .js 和 .scss 文件
  3. JS 模块:使用 /** @odoo-module **/ 注解
  4. 网站主题:仅使用 publicWidget 框架(不要使用 Owl 或原生 JS)
  5. Bootstrap:Odoo 16+ 使用 v5.1.3 类(绝不使用 Tailwind)
  6. 模块命名:使用 snake_case 规范
  7. 翻译:在定义时(而非通过运行时包装器)使用 _t() 将 JS 数组/常量中的静态标签标记为可翻译。静态 XML 字符串会自动翻译。

自动检测工作流

步骤 1:检测 Odoo 版本

开始任何前端任务时,首先检测 Odoo 版本:

# Use the version detector script
python scripts/version_detector.py <module_path>

版本检测器将:

  1. 查找 __manifest__.py 或 __openerp__.py
  2. 解析版本字段
  3. 如果未找到,则从父目录检测(例如 odoo17/)
  4. 返回:
    • Odoo 版本(例如 “17.0”)
    • Bootstrap 版本(例如 “5.1.3”)
    • Owl 版本(例如 “2.x” 或 None)
    • 模块类型(主题、网站、自定义)
步骤 2:引导版本映射

自动选择引导版本:

  • Odoo 14-15:Bootstrap 4.5.0
  • Odoo 16-19:Bootstrap 5.1.3
步骤 3:检测 Owl 版本

JavaScript 框架选择:

  • Odoo 14-15:Owl 实验版或仅使用 jQuery
  • Odoo 16-17:Owl v1
  • Odoo 18-19:Owl v2(存在破坏性变更)
步骤 4:检测片段结构

片段注册方式:

  • Odoo 14-17:直接插入片段,无需分组
  • Odoo 18-19:必须使用片段组(带有分组的 snippet_structure)

主题变量完整参考

本节完整介绍 Odoo 主题中使用的三种核心 SCSS 变量系统。


📚 1. $o-theme-font-configs - Google Fonts 配置

$o-theme-font-configs 用于定义 Odoo 网站主题中可用的字体,并自动导入 Google Fonts。

结构
$o-theme-font-configs: (
    '<Font Name>': (
        'family': (<CSS font-family list>),     // Required: Font family with fallbacks
        'url': '<Google Fonts parameter>',       // Required*: Google Fonts query param ONLY
        'properties': (                          // Optional: Per-context CSS overrides
            '<font-alias>': (
                '<website-value-key>': <value>,
            ),
        ),
    ),
);

⚠️ 重要提示:'url' 键中仅包含查询参数,而非完整 URL!

// ✅ CORRECT - Only the font parameter
'url': 'Poppins:300,300i,400,400i,600,600i,700,700i'

// The system generates: https://fonts.googleapis.com/css?family=Poppins:...&display=swap

// ❌ WRONG - Do not include full URL
'url': 'https://fonts.googleapis.com/css?family=Poppins:300,400,700'
字重指定
// Format: FontName:weight1,weight1i,weight2,weight2i,...
//   - Number alone = normal style
//   - Number + 'i' = italic style

'url': 'Poppins:300,300i,400,400i,600,600i,700,700i'
//      Light  Light-i  Regular Regular-i SemiBold ...
多单词字体名称
// Replace spaces with +
'url': 'Open+Sans:300,300i,400,400i,700,700i'
'url': 'Source+Sans+Pro:300,300i,400,400i,700,700i'
'url': 'DM+Serif+Display:400,400i'
字体别名(用于 'properties' 键)
别名映射到用途
'base''font'正文文本
'headings''headings-font'所有标题(H1-H6)
'h2' - 'h6''h2-font' - 'h6-font'单个标题
'navbar''navbar-font'导航菜单
'buttons''buttons-font'按钮文本
'display-1' - 'display-4''display-1-font' - 'display-4-font'展示文本
完整示例
// ⚠️ STANDALONE definition - NO map-merge with core variables!
$o-theme-font-configs: (
    'Poppins': (
        'family': ('Poppins', sans-serif),
        'url': 'Poppins:300,300i,400,400i,500,500i,600,600i,700,700i',
        'properties': (
            'base': (
                'font-size-base': (15 / 16) * 1rem,
                'header-font-size': (15 / 16) * 1rem,
            ),
        ),
    ),
    'Playfair Display': (
        'family': ('Playfair Display', serif),
        'url': 'Playfair+Display:400,400i,700,700i',
    ),
    'Inter': (
        'family': ('Inter', sans-serif),
        'url': 'Inter:300,400,500,600,700',
    ),
);
阿拉伯语/RTL 字体支持
$o-theme-font-configs: (
    'IBM Plex Sans Arabic': (
        'family': ('IBM Plex Sans Arabic', sans-serif),
        'url': 'IBM+Plex+Sans+Arabic:100,200,300,400,500,600,700',
    ),
    'Cairo': (
        'family': ('Cairo', sans-serif),
        'url': 'Cairo:200,300,400,500,600,700,800,900',
    ),
    'Almarai': (
        'family': ('Almarai', sans-serif),
        'url': 'Almarai:300,400,700,800',
    ),
);

📚 2. $o-color-palettes - 颜色系统

$o-color-palettes 用于定义所有颜色调色板,其中包含 5 种核心颜色以及组件颜色分配。

五种核心颜色
颜色语义典型用途
o-color-1主色/强调色品牌色、按钮、链接、突出显示
o-color-2辅助色互补强调色、辅助按钮
o-color-3浅色背景区块背景、卡片、浅色区域
o-color-4白色/最浅色主要内容背景(通常为 #FFFFFF)
o-color-5深色/文本色深色背景、文本颜色、页脚
视觉示意
┌─────────────────────────────────────────────────────────┐
│  o-color-4 (White Background)                           │
│  ┌─────────────────────────────────────────────────┐   │
│  │  o-color-3 (Light Section)                      │   │
│  │    Text: o-color-5 (Dark)                       │   │
│  │    Links: o-color-1 (Primary)                   │   │
│  │    Buttons: o-color-1 (Primary), o-color-2 (Sec)│   │
│  └─────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────┐   │
│  │  o-color-5 (Dark Section - Footer)              │   │
│  │    Text: o-color-4 (White)                      │   │
│  │    Links: o-color-3 (Light)                     │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
颜色组合(o_cc1 - o_cc5)

颜色组合是预设配色方案,可自动设置背景、文本、标题、链接和按钮的颜色。

类背景典型用途
o_cc1o-color-4(白色)主要内容区域
o_cc2o-color-3(浅色)交替区块
o_cc3o-color-2(辅助色)强调区块
o_cc4o-color-1(主色)行动号召区块
o_cc5o-color-5(深色)页脚、深色区块
颜色调色板结构
$o-color-palettes: map-merge($o-color-palettes, (
    'my-palette': (
        // Required: The 5 core colors
        'o-color-1': #09294F,      // Primary (Navy blue)
        'o-color-2': #FFA807,      // Secondary (Orange)
        'o-color-3': #F6F4F0,      // Light background
        'o-color-4': #FFFFFF,      // White
        'o-color-5': #1B212C,      // Dark

        // Component color assignments (1-5 → o_cc1-o_cc5)
        'menu': 1,                 // Menu uses o_cc1
        'footer': 5,               // Footer uses o_cc5
        'copyright': 5,            // Copyright uses o_cc5

        // Color combination overrides
        'o-cc1-text': 'o-color-5',
        'o-cc1-headings': 'o-color-5',
        'o-cc5-link': 'o-color-4',
        'o-cc5-btn-primary': 'o-color-2',
    ),
));
颜色组合的覆盖语法
// Override specific colors within a combination
'o-cc{n}-{property}': value

// Available properties:
'o-cc1-text': #333333,           // Text color
'o-cc1-headings': 'o-color-5',   // Headings color
'o-cc1-link': 'o-color-1',       // Link color
'o-cc1-btn-primary': 'o-color-1', // Primary button
'o-cc1-btn-secondary': 'o-color-2', // Secondary button

// Example for dark background (o_cc5)
'o-cc5-text': rgba(#fff, .8),    // Semi-transparent white
'o-cc5-headings': 'o-color-4',   // White headings
'o-cc5-link': 'o-color-4',       // White links
'o-cc5-btn-primary': 'o-color-2', // Orange buttons on dark
HTML 用法

<section class="o_cc o_cc1 pt32 pb32">
    <div class="container"><h2>Content</h2></div>
</section>


<section class="o_cc o_cc2 pt32 pb32">...</section>


<section class="o_cc o_cc4 pt32 pb32">...</section>


<section class="o_cc o_cc5 pt32 pb32">...</section>

📚 3. $o-website-values-palettes - 完整配置(115+ 个键)

$o-website-values-palettes 是主配置变量,用于控制 Bootstrap 组件、排版、按钮、输入框、页眉/页脚模板等。

按类别快速查阅
类别键数量说明
排版与字体13字体系列配置
字号13基础字号和标题字号
行高11文本间距
外边距22标题和段落外边距
按钮17按钮样式
输入框12表单字段样式
页眉13页眉/导航配置
页脚3页脚配置
链接1链接下划线行为
布局3页面布局
颜色与渐变5调色板和渐变
Google Fonts2加载其他字体
总计115+
3.1 排版与字体
键类型默认值描述
'font'字符串配置中的首个值整个网站的基础字体
'headings-font'字符串继承 fontH1-H6 标题的字体
'navbar-font'字符串继承 font导航菜单的字体
'buttons-font'字符串继承 font按钮文本的字体
'h2-font' - 'h6-font'字符串继承标题字体各级标题的字体
'display-1-font' - 'display-4-font'字符串继承标题字体展示文本的字体
3.2 字体大小
键类型默认值描述
'font-size-base'尺寸1rem基础字体大小(16px)
'small-font-size'尺寸0.875rem小号文本(14px)
'h1-font-size' - 'h6-font-size'尺寸计算得出标题字号
'display-1-font-size' - 'display-6-font-size'尺寸5rem-2.5rem展示字号
3.3 行高与边距
键类型默认值描述
'body-line-height'数字1.5正文行高
'headings-line-height'数字1.2所有标题的行高
'h2-line-height' - 'h6-line-height'数字继承各级标题的行高
'paragraph-margin-top'尺寸0段落上边距
'paragraph-margin-bottom'尺寸16px段落下边距
'headings-margin-top'尺寸0标题上边距
'headings-margin-bottom'尺寸0.5rem标题下边距
'h2-margin-top' - 'h6-margin-top'尺寸继承各级标题的上边距
'h2-margin-bottom' - 'h6-margin-bottom'尺寸继承各级标题的下边距
3.4 按钮(17 个键)
键类型默认值描述
'btn-padding-y'尺寸Bootstrap垂直内边距
'btn-padding-x'尺寸Bootstrap水平内边距
'btn-font-size'尺寸Bootstrap字体大小
'btn-padding-y-sm' / 'btn-padding-x-sm'尺寸Bootstrap小号按钮内边距
'btn-padding-y-lg' / 'btn-padding-x-lg'尺寸Bootstrap大号按钮内边距
'btn-font-size-sm' / 'btn-font-size-lg'尺寸Bootstrap不同尺寸的字号
'btn-border-width'尺寸Bootstrap边框粗细
'btn-border-radius'尺寸Bootstrap圆角半径
'btn-border-radius-sm' / 'btn-border-radius-lg'尺寸Bootstrap不同尺寸的圆角半径
'btn-primary-outline'布尔值false将主要按钮设为描边样式
'btn-secondary-outline'布尔值false将次要按钮设为描边样式
'btn-primary-flat'布尔值false主要按钮的扁平样式
'btn-secondary-flat'布尔值false次要按钮的扁平样式
'btn-ripple'布尔值falseMaterial Design 涟漪效果
3.5 输入框与表单(12 个键)
键类型默认值描述
'input-padding-y' / 'input-padding-x'尺寸Bootstrap输入框内边距
'input-font-size'尺寸Bootstrap输入框字号
'input-padding-y-sm' / 'input-padding-x-sm'尺寸Bootstrap小号输入框内边距
'input-padding-y-lg' / 'input-padding-x-lg'尺寸Bootstrap大号输入框内边距
'input-border-width'尺寸Bootstrap边框粗细
'input-border-radius'尺寸Bootstrap圆角半径
'input-border-radius-sm' / 'input-border-radius-lg'尺寸Bootstrap不同尺寸的圆角半径
3.6 页眉与导航(13 个键)
键类型值默认值描述
'header-template'字符串见下文'default'页眉布局
'header-links-style'字符串见下文'default'导航链接样式
'header-font-size'尺寸CSS 长度Bootstrap页眉文本大小
'logo-height'尺寸CSS 长度导航栏高度Logo 高度
'fixed-logo-height'尺寸CSS 长度更小固定时的 Logo 高度
'hamburger-position'字符串'left'/'center'/'right''left'桌面端位置
'hamburger-position-mobile'字符串'left'/'center'/'right''left'移动端位置
'menu-border-width'尺寸CSS 长度null菜单边框
'menu-border-radius'尺寸CSS 长度null菜单圆角
'menu-box-shadow'CSS阴影/无null菜单阴影
'sidebar-width'尺寸CSS 长度18.75rem侧边栏宽度

页眉模板选项:

  • 'default' - 标准水平导航栏
  • 'hamburger' - 汉堡菜单(折叠)
  • 'vertical' - 垂直侧边栏导航
  • 'sidebar' - 完整侧边栏布局

页眉链接样式选项:

  • 'default' - 标准链接
  • 'fill' - 悬停时填充背景
  • 'outline' - 悬停时显示轮廓边框
  • 'pills' - 圆角药丸形链接
  • 'block' - 块状链接
  • 'border-bottom' - 悬停时显示下划线边框
3.7 页脚(3 个键)
键类型值默认值描述
'footer-template'字符串模板名称'default'页脚布局
'footer-effect'字符串见下文null动画效果
'footer-scrolltop'布尔值true/falsefalse回到顶部按钮

页脚模板选项:

  • 'default' - 标准页脚
  • 'centered' - 居中对齐
  • 'minimalist' - 简洁极简
  • 'links' - 链接较多
  • 'descriptive' - 详细描述

页脚效果:

  • null - 无效果(静态)
  • 'slideout_slide_hover' - 悬停时滑出
  • 'slideout_shadow' - 滚动时显示阴影
3.8 链接(1 个键)
键类型值默认值描述
'link-underline'字符串'never'/'hover'/'always''hover'下划线行为
3.9 布局(3 个键)
键类型值默认值描述
'layout'字符串'full'/'boxed''full'页面布局
'body-image'URL图片路径null背景图片
'body-image-type'字符串'image'/'pattern''image'背景类型
3.10 颜色与渐变(5 个键)
键类型默认值描述
'color-palettes-name'字符串null当前颜色调色板名称
'menu-gradient'CSS 渐变null菜单背景渐变
'menu-secondary-gradient'CSS 渐变null次级菜单渐变
'footer-gradient'CSS 渐变null页脚背景渐变
'copyright-gradient'CSS 渐变null版权区域渐变
3.11 Google Fonts(2 个键)
键类型默认值描述
'google-fonts'列表null要加载的其他 Google 字体
'google-local-fonts'映射null本地托管的字体

完整示例 - 现代企业主题
$o-website-values-palettes: (
    (
        // === REQUIRED ===
        'color-palettes-name': 'my-corporate-palette',

        // === TYPOGRAPHY ===
        'font': 'Inter',
        'headings-font': 'Inter',
        'navbar-font': 'Inter',
        'buttons-font': 'Inter',
        'font-size-base': 1rem,
        'headings-line-height': 1.3,
        'body-line-height': 1.6,

        // === HEADER (NO custom header.xml needed!) ===
        'header-template': 'default',      // or 'hamburger', 'vertical', 'sidebar'
        'header-links-style': 'default',   // or 'pills', 'fill', 'border-bottom'
        'logo-height': 48px,
        'fixed-logo-height': 36px,

        // === BUTTONS ===
        'btn-padding-y': 0.75rem,
        'btn-padding-x': 1.5rem,
        'btn-padding-y-lg': 1rem,
        'btn-padding-x-lg': 2rem,
        'btn-border-radius': 8px,
        'btn-border-radius-lg': 12px,
        'btn-ripple': true,

        // === INPUTS ===
        'input-padding-y': 0.75rem,
        'input-padding-x': 1rem,
        'input-border-radius': 8px,

        // === FOOTER (NO custom footer.xml needed!) ===
        'footer-template': 'default',      // or 'centered', 'minimalist', 'links'
        'footer-scrolltop': true,

        // === LINKS ===
        'link-underline': 'hover',         // or 'never', 'always'

        // === LAYOUT ===
        'layout': 'full',                  // or 'boxed'
    ),
);
⚠️ 严重:Odoo 主题中的 SCSS 加载顺序

主题 SCSS 文件会在核心 Odoo 变量定义之前加载!

在清单的资产包中使用 prepend 时,你的 SCSS 会在 Odoo 核心 primary_variables.scss 之前执行:

LOAD ORDER:
1. YOUR theme's primary_variables.scss (via prepend)  ← FIRST
2. Odoo core primary_variables.scss                    ← SECOND
3. Other SCSS files

⛔ 严重限制:

  • 不能对核心变量使用 map-merge()(这些变量此时尚不存在!)
  • 加载主题时,$o-color-palettes、$o-theme-color-palettes、$o-theme-font-configs 均未定义

❌ 错误(会导致“Undefined variable”错误):

$o-color-palettes: map-merge($o-color-palettes, (...));         // ERROR!
$o-theme-font-configs: map-merge($o-theme-font-configs, (...)); // ERROR!

✅ 正确(作为独立项定义):

// Standalone font config (no map-merge!)
$o-theme-font-configs: (
    'Poppins': (
        'family': ('Poppins', sans-serif),
        'url': 'Poppins:300,300i,400,400i,500,500i,600,600i,700,700i',
    ),
);

// Reference existing palette by name
$o-website-values-palettes: (
    (
        'color-palettes-name': 'default-1',  // Use existing palette name!
        'font': 'Poppins',
        // ...other values
    ),
);

❌ 错误:主题中不要使用 ir.asset 记录来加载 Google Fonts,否则会导致 URL 格式错误!


主题页面创建规范

单个页面文件模式(推荐)

创建单独的页面文件,而不是单个 pages.xml:

theme_name/
├── data/
│   ├── home.xml          # Homepage template + page
│   ├── about.xml         # About page template + page
│   ├── contact.xml       # Contact page (inherits website.contactus)
│   ├── services.xml      # Services page template + page
│   └── menu.xml          # Menu configuration
└── views/
    └── templates.xml     # Shared templates and layout
主页(继承 website.homepage)

<template id="view_home" inherit_id="website.homepage" name="Home">
    <xpath expr="//div[@id='wrap']" position="replace">
        <div id="wrap" class="oe_structure">
            
        </div>
    </xpath>
</template>
联系页面(继承 website.contactus)

<template id="view_contact" inherit_id="website.contactus" name="Contact">
    <xpath expr="//h1" position="replace">
        <h1>Get in Touch</h1>
    </xpath>
</template>
自定义页面(theme.website.page)

<template id="view_about" name="About">
    <t t-call="website.layout">
        <div id="wrap" class="oe_structure">
            <section class="s_title pt96 pb48">
                <div class="container">
                    <h1>About Us</h1>
                </div>
            </section>
        </div>
    </t>
</template>

<record id="page_about" model="theme.website.page">
    <field name="view_id" ref="view_about"/>
    <field name="is_published" eval="True"/>
    <field name="url">/about</field>
    <field name="name">About</field>
</record>

主题镜像模型架构

主题如何安装到页面
Theme Module XML
    ↓
theme.ir.ui.view (Template View)
    ↓
theme.website.page (Template Page)
    ↓ (Theme Installation)
    ↓
ir.ui.view (Actual View with website_id)
    ↓
website.page (Actual Page with website_id)
要点
  • 主题模块包含 theme.* 模型(模板)
  • 安装时,这些模型会转换为带有 website_id 的实际模型
  • 每个网站都会获得独立副本,从而可以重复使用主题
  • website_id 会在视图创建层级分配

主题脚手架命令

命令:搭建完整主题模块

触发条件:用户要求“创建主题”“搭建主题”或“生成主题模块”

工作流:

  1. 检测上下文

    # Determine current Odoo version
    cd <project_path>
    python -c "import sys; sys.path.insert(0, 'helpers'); from version_detector import detect_version; print(detect_version('.'))"
    
  2. 创建模块结构

    theme_<name>/
    ├── __init__.py
    ├── __manifest__.py
    ├── security/
    │   └── ir.model.access.csv
    ├── data/
    │   ├── assets.xml
    │   ├── menu.xml
    │   └── pages/
    │       ├── home_page.xml
    │       └── aboutus_page.xml
    ├── views/
    │   ├── layout/
    │   │   ├── header.xml          # Header customization (OPTIONAL)
    │   │   ├── footer.xml          # Footer customization (OPTIONAL)
    │   │   └── templates.xml       # Base layout templates
    │   └── snippets/
    │       └── custom_snippets.xml # Custom snippet definitions
    ├── static/
    │   └── src/
    │       ├── scss/
    │       │   ├── primary_variables.scss
    │       │   ├── bootstrap_overridden.scss
    │       │   └── theme.scss
    │       ├── js/
    │       │   ├── theme.js
    │       │   └── snippets_options.js
    │       └── img/
    └── README.md
    
  3. 生成 __manifest__.py

    {
        'name': 'Theme <Name>',
        'version': '<odoo_version>.1.0.0',
        'category': 'Website/Theme',
        'author': 'TaqaTechno',
        'website': 'https://www.taqatechno.com/',
        'support': '[email protected]',
        'license': 'LGPL-3',
        'depends': ['website'],
        'data': [
            'security/ir.model.access.csv',
            'views/layout/templates.xml',
            'views/layout/header.xml',
            'views/layout/footer.xml',
            'views/snippets/custom_snippets.xml',
            'data/menu.xml',
            'data/pages/home_page.xml',
            'data/pages/aboutus_page.xml',
            'data/pages/contactus_page.xml',
        ],
        'assets': {
            'web._assets_primary_variables': [
                ('prepend', 'theme_<name>/static/src/scss/primary_variables.scss'),
            ],
            'web._assets_frontend_helpers': [
                'theme_<name>/static/src/scss/bootstrap_overridden.scss',
            ],
            'web.assets_frontend': [
                'theme_<name>/static/src/scss/theme.scss',
                'theme_<name>/static/src/js/theme.js',
            ],
            'website.assets_wysiwyg': [
                'theme_<name>/static/src/js/snippets_options.js',
            ],
        },
        'installable': True,
        'auto_install': False,
        'application': False,
    }
    
  4. 生成 primary_variables.scss(完整 v5.0)

    // ===================================================================
    // Theme: <Name>
    // Generated by TAQAT Techno /create-theme command v5.0
    // ===================================================================
    //
    // ⚠️ IMPORTANT: This file is PREPENDED before Odoo core variables!
    // DO NOT use map-merge() with core variables - they don't exist yet!
    // ===================================================================
    
    // === Font Configuration (STANDALONE - no map-merge!) ===
    $o-theme-font-configs: (
        'Inter': (
            'family': ('Inter', sans-serif),
            'url': 'Inter:300,300i,400,400i,500,500i,600,600i,700,700i',
        ),
    );
    
    // === Website Values Palette (MASTER CONFIGURATION) ===
    // NO bootstrap_overridden.scss needed - configure everything here!
    $o-website-values-palettes: (
        (
            // Reference existing palette (avoids map-merge issues)
            'color-palettes-name': 'default-1',
    
            // === Typography ===
            'font': 'Inter',
            'headings-font': 'Inter',
            'navbar-font': 'Inter',
            'buttons-font': 'Inter',
    
            // === Header (NO custom header.xml needed!) ===
            // Options: 'default' | 'hamburger' | 'vertical' | 'sidebar'
            'header-template': 'default',
            'header-links-style': 'default',
            'logo-height': 3rem,
            'fixed-logo-height': 2rem,
    
            // === Buttons ===
            'btn-padding-y': 0.45rem,
            'btn-padding-x': 1.35rem,
            'btn-border-radius': 0.25rem,
    
            // === Inputs ===
            'input-padding-y': 0.45rem,
            'input-border-radius': 0.25rem,
    
            // === Footer (NO custom footer.xml needed!) ===
            // Options: 'default' | 'centered' | 'minimalist' | 'links' | 'descriptive'
            'footer-template': 'default',
            'footer-scrolltop': true,
    
            // === Links & Layout ===
            'link-underline': 'hover',
            'layout': 'full',
        )
    );
    
  5. 生成初始模板

    <?xml version="1.0" encoding="utf-8"?>
    <odoo>
        
        <template id="layout" inherit_id="website.layout">
            <xpath expr="//head" position="inside">
                <meta name="theme-name" content="<Name>"/>
            </xpath>
        </template>
    </odoo>
    
命令:创建片段(适配版本)

触发条件:用户要求“创建片段”或“添加自定义片段”

工作流:

  1. 检测 Odoo 版本(确定注册方式)

  2. 适用于 Odoo 17 及更早版本:

    
    <template id="s_<snippet_name>" name="<Snippet Name>">
        <section class="s_<snippet_name>" data-name="<Snippet Name>">
            <div class="container">
                
            </div>
        </section>
    </template>
    
    
    <template id="s_<snippet_name>_insert" inherit_id="website.snippets">
        <xpath expr="//div[@id='snippet_effect']//t[@t-snippet][last()]" position="after">
            <t t-snippet="theme_<name>.s_<snippet_name>"
               string="<Snippet Name>"
               t-thumbnail="/theme_<name>/static/img/snippets/<snippet_name>.svg"/>
        </xpath>
    </template>
    
  3. 适用于 Odoo 18/19:

    
    <template id="s_<snippet_name>" name="<Snippet Name>">
        <section class="s_<snippet_name>" data-name="<Snippet Name>">
            <div class="container">
                
            </div>
        </section>
    </template>
    
    
    <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_<snippet_name>_insert" inherit_id="website.snippets">
        <xpath expr="//div[@id='snippet_structure']/*[1]" position="before">
            <t t-snippet="theme_<name>.s_<snippet_name>"
               string="<Snippet Name>"
               group="custom"
               t-thumbnail="/theme_<name>/static/img/snippets/<snippet_name>.svg"/>
        </xpath>
    </template>
    
  4. 生成片段选项(如有需要)

    <template id="s_<snippet_name>_options" inherit_id="website.snippet_options">
        <xpath expr="." position="inside">
            <div data-selector=".s_<snippet_name>">
                
                <we-select string="Layout">
                    <we-button data-select-class="">Default</we-button>
                    <we-button data-select-class="s_<snippet_name>_alt">Alternate</we-button>
                </we-select>
    
                
                <we-row string="Items">
                    <we-button data-add-item="true"
                               data-no-preview="true"
                               class="o_we_bg_brand_primary">Add Item</we-button>
                </we-row>
    
                
                <we-colorpicker string="Background Color"
                                data-css-property="background-color"/>
            </div>
        </xpath>
    </template>
    
  5. 生成选项 JavaScript(如需动态行为)

    /** @odoo-module **/
    
    import options from "@web_editor/js/editor/snippets.options";
    
    options.registry.<SnippetName> = options.Class.extend({
        /**
         * Add item to snippet
         */
        addItem: function(previewMode, widgetValue, params) {
            let $lastItem = this.$target.find('.snippet-item').last();
            if ($lastItem.length) {
                $lastItem.clone().appendTo(this.$target.find('.snippet-container'));
            }
        },
    });
    

    在清单资产中包含:

    'assets': {
        'website.assets_wysiwyg': [
            'theme_<name>/static/src/js/snippets_options.js',
        ],
    }
    

Figma 集成工作流

命令:将 Figma 设计转换为 Odoo 主题

触发条件:用户提供 Figma URL,或要求“将 Figma 转换为 Odoo”“导入 Figma 设计”

先决条件:必须能够使用 Figma MCP

工作流:

  1. 使用 Figma MCP 和特定版本的提示词

    Convert this Figma design to HTML for Odoo <version> website theme with these requirements:
    - Use Bootstrap v<bootstrap_version> classes (not Tailwind)
    - Apply proper Odoo theme structure with sections and containers
    - Include responsive classes (col-sm-*, col-md-*, col-lg-*)
    - Use semantic HTML5 elements (header, nav, main, section, article)
    - Add data attributes for Odoo website builder compatibility
    - Map colors to CSS custom properties for theme variables
    - Include accessibility attributes (aria-labels, alt text)
    
  2. 提取调色板

    Extract the color palette from this Figma design and map to CSS custom properties using the o-color-1 through o-color-5 naming convention for Odoo themes.
    
  3. 将 HTML 转换为 QWeb 模板

    • 用 <template> 标签包裹
    • 添加 t-name 属性
    • 添加供 Odoo 编辑器使用的数据属性
    • 转换为基于区块的结构
  4. 根据 Figma 颜色生成 SCSS

    python scripts/figma_converter.py <figma_url> <output_dir> <odoo_version>
    
  5. 根据 Figma 组件创建片段

    • 使用转换后的 HTML 作为片段内容
    • 添加正确的 Odoo 结构
    • 根据版本(17 与 18/19)注册片段
    • 如果需要交互功能,则生成选项
Figma MCP 提示词参考

基础 HTML + Bootstrap:

Generate this Figma selection as HTML with Bootstrap v<version> classes instead of React and Tailwind. Use Odoo-compatible structure with proper semantic HTML5 elements.

颜色提取:

Extract colors as CSS custom properties for Odoo theme

排版提取:

Convert this Figma text styling to Bootstrap v<version> typography classes and CSS custom properties. Map font families to Google Fonts configuration for Odoo theme integration.

布局提取:

Convert this Figma layout to Bootstrap v<version> grid system and utility classes. Use proper container, row, col-* structure with responsive breakpoints.

Chrome DevTools 集成工作流

命令:从在线网站提取样式

触发条件:用户提供网站 URL,或要求“从网站提取”“复制网站样式”

前提条件:必须可用 Chrome DevTools MCP

工作流:

  1. 连接到 DevTools MCP

    • 在 Chrome DevTools MCP 中打开 URL
    • 选择要提取的元素
  2. 提取计算样式

    python scripts/devtools_extractor.py <url> <output_dir>
    
  3. 转换为 Odoo SCSS

    • 将 CSS 属性映射为 SCSS 变量
    • 将颜色转换为 Odoo 主题变量(o-color-*)
    • 在适用时使用 Bootstrap 变量
  4. 映射到 Bootstrap 工具类

    • 找出等效的 Bootstrap 类
    • 尽可能用工具类替换自定义 CSS
    • 生成基于类的实现方案建议
  5. 将 DOM 转换为 QWeb

    • 提取 HTML 结构
    • 添加 Odoo 专属属性
    • 使用正确的模板结构进行封装
    • 添加 Bootstrap 网格结构
  6. 生成完整的主题代码

    • 包含提取样式的 SCSS 文件
    • 包含结构的 QWeb 模板
    • Bootstrap 类建议
    • 片段注册 XML

JavaScript 开发

Public Widget 模式(publicWidget——主题必需)

重要:对于网站主题,始终使用 publicWidget 框架,不要使用 Owl 或原生 JavaScript。

适用场景:网站交互、主题功能、表单处理、动画

包含 editableMode 的完整结构:

/** @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',
    },

    /**
     * Lifecycle: Called when widget starts
     * CRITICAL: Check editableMode for website builder compatibility
     */
    start: function () {
        // IMPORTANT: Only run animations/interactions in read mode
        if (!this.editableMode) {
            this._initializeAnimation();
            this._bindExternalEvents();
        }
        return this._super.apply(this, arguments);
    },

    /**
     * Initialize animations (only in read mode)
     */
    _initializeAnimation: function () {
        // Animation code that should NOT run in edit mode
        this.$el.addClass('animated');
    },

    /**
     * Bind events outside the widget element
     */
    _bindExternalEvents: function () {
        $(window).on('scroll.myWidget', this._onScroll.bind(this));
        $(window).on('resize.myWidget', this._onResize.bind(this));
    },

    /**
     * Click handler
     */
    _onClick: function (ev) {
        ev.preventDefault();
        // Don't run in edit mode
        if (this.editableMode) return;
        // Handler logic
    },

    /**
     * Scroll handler
     */
    _onScroll: function () {
        // Throttle scroll events for performance
    },

    /**
     * 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;

publicWidget 要点:

  1. 始终检查 this.editableMode,再运行动画或交互
  2. 使用 disabledInEditableMode: false,让 Widget 在网站构建器中正常工作
  3. 始终在 destroy() 方法中清理事件监听器
  4. 网站主题绝不要使用 Owl 或原生 JavaScript——只能使用 publicWidget
  5. 使用带命名空间的事件(.myWidget),以便轻松清理

在清单中引入:

'assets': {
    'web.assets_frontend': [
        'module_name/static/src/js/my_widget.js',
    ],
}
Owl 组件模式(现代、响应式)

适用场景:复杂的交互式 UI、响应式状态管理、组件组合

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();
    }

    async loadData() {
        this.state.loading = true;
        // Fetch data via RPC
        const data = await this.rpc('/my/route');
        this.state.items = data.items;
        this.state.loading = false;
    }

    onItemClick(item) {
        console.log('Clicked:', item);
    }
}

MyComponent.template = "module_name.MyComponentTemplate";

registry.category("public_components").add("MyComponent", MyComponent);

export default MyComponent;

Odoo 18/19(Owl v2——注意差异):

/** @odoo-module **/

import { Component, useState } from "@odoo/owl";

class MyComponent extends Component {
    static template = "module_name.MyComponentTemplate";
    static props = {
        // Define props with validation
        title: { type: String, optional: true },
        items: { type: Array },
    };

    setup() {
        this.state = useState({
            selectedId: null,
        });
    }

    // Owl v2 uses static template property
}

export default MyComponent;

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)最佳实践

关键规则:对于 JavaScript 数组/常量中的静态标签,应在定义时使用 _t(),而不要通过运行时包装方法处理。Odoo 会自动翻译 XML 模板中的静态字符串。

原因?

  • Odoo 的翻译系统会自动提取 XML 模板(.xml 文件)中的静态字符串
  • 在模板中对静态字符串使用 _t() 会重复创建翻译条目,并带来不必要的开销
  • 必须在 JavaScript 中定义时用 _t() 包装字符串,这样 Odoo 的 PO 提取器才能找到它们
  • 运行时包装方法(如 translateLabel(key))不起作用——提取器永远找不到这些字符串

正确——在定义时用 _t() 包装静态标签:

/** @odoo-module **/

import { _t } from "@web/core/l10n/translation";

// ✅ CORRECT: Wrap each string with _t() at definition time
const MONTHS = [
    {value: 1, short: _t("Jan"), full: _t("January")},
    {value: 2, short: _t("Feb"), full: _t("February")},
    {value: 3, short: _t("Mar"), full: _t("March")},
    {value: 4, short: _t("Apr"), full: _t("April")},
    {value: 5, short: _t("May"), full: _t("May")},
    {value: 6, short: _t("Jun"), full: _t("June")},
    {value: 7, short: _t("Jul"), full: _t("July")},
    {value: 8, short: _t("Aug"), full: _t("August")},
    {value: 9, short: _t("Sep"), full: _t("September")},
    {value: 10, short: _t("Oct"), full: _t("October")},
    {value: 11, short: _t("Nov"), full: _t("November")},
    {value: 12, short: _t("Dec"), full: _t("December")},
];

// ✅ CORRECT: Status labels wrapped at definition
const STATUS_LABELS = {
    draft: _t("Draft"),
    pending: _t("Pending"),
    approved: _t("Approved"),
    rejected: _t("Rejected"),
};

export class DatePicker extends Component {
    setup() {
        this.months = MONTHS;  // Already translated at definition
    }

    get displayLabel() {
        const month = this.months.find(m => m.value === this.selectedMonth);
        // No translateLabel() needed - values are already translated
        return `${month?.short || ""} ${this.selectedYear}`;
    }
}

错误——运行时包装方法不起作用:

// ❌ WRONG: Strings without _t() at definition will NOT be translated
const MONTHS = [
    {value: 1, label: "Jan", full: "January"},  // NOT FOUND by PO extractor!
    {value: 2, label: "Feb", full: "February"},
];

// ❌ WRONG: This pattern does NOT work for translation
translateLabel(key) {
    return _t(key);  // key is a variable, not a literal - PO extractor can't find it!
}

正确——模板使用预先翻译的值:


<t t-foreach="getAvailableMonths()" t-as="month" t-key="month.value">
    <a href="#" t-on-click.prevent="() => this.selectMonth(month.value)">
        
        <t t-esc="month.full"/>
    </a>
</t>

正确——静态 XML 字符串无需包装:


<span>No data available</span>
<a href="#">Live</a>
<button type="submit">Submit</button>

何时在定义时使用 _t():

  1. ✅ JavaScript 数组中的静态标签(月名称、星期名称、状态标签)
  2. ✅ JavaScript 对象/常量中的静态标签
  3. ✅ 定义为常量的错误消息
  4. ✅ 将从 JavaScript 显示给用户的任何字符串字面量

何时不使用 _t():

  1. ❌ XML 模板中的静态文本(会自动翻译)
  2. ❌ 运行时包装方法,如 translateLabel(key)(不起作用!)
  3. ❌ 在运行时传递给 _t() 的动态变量
  4. ❌ .xml 文件中的任何硬编码字符串

请记住:如果你在 JavaScript 中定义的字符串将显示给用户,应在定义处使用 _t() 包装,而不是在使用处。

SCSS 和 Bootstrap 自定义

主要变量覆盖

文件:static/src/scss/primary_variables.scss

用途:覆盖 Odoo 内置的样式变量(颜色、字体、间距)

引入位置:web._assets_primary_variables 资源包

结构:

// ============================================================
// Theme Primary Variables
// Override Odoo defaults with !default to allow further overriding
// ============================================================

// Color Palette
$o-color-1: #3498db !default;  // Primary
$o-color-2: #2ecc71 !default;  // Success
$o-color-3: #e74c3c !default;  // Danger
$o-color-4: #f39c12 !default;  // Warning
$o-color-5: #9b59b6 !default;  // Info

// Website Values Palette Configuration
$o-website-values-palettes: (
    (
        // Color Configuration
        'color-palettes-name': 'my-theme',

        // Typography
        'font': 'Inter',
        'headings-font': 'Inter',
        'navbar-font': 'Inter',
        'buttons-font': 'Inter',
        'headings-line-height': 1.2,

        // Header & Navigation
        'header-template': 'default',  // or 'centered', 'boxed'
        'header-font-size': 1rem,
        'logo-height': 3rem,
        'fixed-logo-height': 2rem,
        'hamburger-position': 'right',  // Mobile menu

        // Buttons
        'btn-padding-y': 0.45rem,
        'btn-padding-x': 1.35rem,
        'btn-padding-y-sm': 0.3rem,
        'btn-padding-x-sm': 0.9rem,
        'btn-padding-y-lg': 0.6rem,
        'btn-padding-x-lg': 1.8rem,
        'btn-border-radius': 0.25rem,
        'btn-border-width': 1px,
        'btn-font-size': 1rem,

        // Input Fields
        'input-padding-y': 0.45rem,
        'input-border-radius': 0.25rem,

        // Colors (map to palette)
        'color-1': $o-color-1,
        'color-2': $o-color-2,
        'color-3': $o-color-3,
        'color-4': $o-color-4,
        'color-5': $o-color-5,
    )
);

// Google Fonts Configuration (STANDALONE - no map-merge!)
$o-theme-font-configs: (
    'Inter': (
        'family': ('Inter', sans-serif),
        'url': 'Inter:wght@300;400;500;600;700',
    ),
);

⚠️ 主题中绝不要使用以下模式:

// ❌ WRONG - Will cause "Undefined variable" errors
$o-color-palettes: map-merge($o-color-palettes, (...));
$o-theme-color-palettes: map-merge($o-theme-color-palettes, (...));
$o-theme-font-configs: map-merge($o-theme-font-configs, (...));
Bootstrap 覆盖项

文件:static/src/scss/bootstrap_overridden.scss

用途:覆盖 Odoo 未开放的 Bootstrap 变量

包含于:web._assets_frontend_helpers 资源包

结构:

// ============================================================
// Bootstrap Variable Overrides
// Only override Bootstrap vars not available in Odoo variables
// ============================================================

@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";

// Spacing
$spacer: 1rem !default;

// Grid breakpoints
$grid-breakpoints: (
    xs: 0,
    sm: 576px,
    md: 768px,
    lg: 992px,
    xl: 1200px,
    xxl: 1400px
) !default;

// Container max widths
$container-max-widths: (
    sm: 540px,
    md: 720px,
    lg: 960px,
    xl: 1140px,
    xxl: 1320px
) !default;

// Typography
$font-size-base: 1rem !default;
$line-height-base: 1.5 !default;

// Border radius
$border-radius: 0.25rem !default;
$border-radius-sm: 0.125rem !default;
$border-radius-lg: 0.5rem !default;

// Shadows
$box-shadow-sm: 0 .125rem .25rem rgba(0, 0, 0, .075) !default;
$box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15) !default;
$box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, .175) !default;

特定版本注意事项

Odoo 17 特性
  1. Owl v1

    • 从 @odoo/owl 使用 useState
    • 使用旧版导入路径
    • 将组件模板作为单独的属性
  2. 片段结构

    • 无需分组,直接插入
    • 使用 XPath 定位 //div[@id='snippet_effect']
  3. 公共 Widget

    • 从 @web/legacy/js/public/public_widget 导入
    • 可完全使用 jQuery
Odoo 18/19 特性
  1. Owl v2

    • 使用静态模板属性
    • 必须进行 Props 验证
    • 使用新的响应式钩子
    • 相比 v1 有破坏性变更
  2. 片段分组

    • 必须包含 snippet-group 属性
    • 按类别组织
    • XPath 定位 //div[@id='snippet_structure']
  3. Bootstrap 5.1.3

    • 均使用相同的 Bootstrap 版本
    • 工具类保持一致
  4. 网站构建器增强功能

    • 可在编辑器中浏览主题
    • 可通过 UI 上传自定义字体
    • 增强的片段选项
Bootstrap 4 到 5 的迁移(Odoo 14/15 → 16+)

迁移主题时:

  1. 类名替换:

    python scripts/bootstrap_mapper.py <old_classes>
    

    常见替换:

    • ml-* → ms-*(margin-left → margin-start)
    • mr-* → me-*(margin-right → margin-end)
    • pl-* → ps-*(padding-left → padding-start)
    • pr-* → pe-*(padding-right → padding-end)
    • 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-*(例如,badge-primary → bg-primary)
    • font-weight-bold → fw-bold
    • sr-only → visually-hidden
    • no-gutters → g-0
  2. 已移除的类(请寻找替代方案):

    • form-inline - 使用网格/弹性布局工具类
    • jumbotron - 使用工具类重新创建
    • media - 使用 d-flex 和弹性布局工具类
  3. 移除 jQuery:

    • Bootstrap 5 使用原生 JavaScript
    • 可能需要调整 Widget 初始化方式

前端性能最佳实践

  1. 资源打包

    • 始终使用 Odoo 的资源包
    • 不要单独链接外部文件
    • 利用压缩功能(在生产环境中已启用)
  2. 延迟加载

    • 在图片上使用 loading="lazy"
    • 利用 Odoo 的 /website/image 路由自动调整图片大小
    • 延迟加载视频(Odoo 18+ 内置支持)
  3. 优化片段

    • 批量处理服务端调用(所有数据使用一次 RPC)
    • 使用 QWeb 高效渲染
    • 尽量减少 DOM 操作
  4. CSS 优化

    • 使用 Bootstrap 工具类,而非自定义 CSS
    • 保持 HTML 结构精简
    • 从资源包中移除未使用的 CSS/JS
  5. 缓存

    • 使用带有 store=True 的计算字段
    • 缓存开销较大的计算结果
    • 在服务端执行繁重的工作
  6. SEO 注意事项

    • 在服务端渲染关键内容
    • 使用 Owl 增强功能,而非呈现主要内容
    • 确保页面加载速度快

安全与访问控制

模型级访问控制

始终创建 security/ir.model.access.csv:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_model_user,model.user,model_model_name,base.group_user,1,1,1,0
access_model_public,model.public,model_model_name,,1,0,0,0
记录级规则

创建 security/rules.xml 以实现精细控制:

<record id="model_rule" model="ir.rule">
    <field name="name">Model: User own records</field>
    <field name="model_id" ref="model_model_name"/>
    <field name="domain_force">[('user_id', '=', user.id)]</field>
    <field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
控制器安全
from odoo import http

class MyController(http.Controller):
    @http.route('/public/route', auth='public')  # No login required
    def public_route(self):
        pass

    @http.route('/user/route', auth='user')  # Login required
    def user_route(self):
        pass
安全地公开数据
# Use sudo() carefully
records = request.env['model'].sudo().search([('published', '=', True)])

# Check access rights
if not request.env['model'].check_access_rights('read', raise_exception=False):
    return request.redirect('/access-denied')

常见工作流

根据 Figma 创建完整主题
  1. 用户提供 Figma URL
  2. 检测目标 Odoo 版本
  3. 使用 Figma MCP 提取设计
  4. 生成主题模块结构
  5. 将颜色转换为 SCSS 变量
  6. 将组件转换为片段
  7. 创建片段选项
  8. 在 Odoo 网站构建器中测试
扩展现有主题
  1. 检测当前主题和版本
  2. 分析现有结构
  3. 创建继承模块
  4. 覆盖指定模板
  5. 添加自定义片段
  6. 扩展 SCSS 变量
跨版本迁移主题
  1. 检测源版本和目标版本
  2. 转换 Bootstrap 类(如果是 4→5)
  3. 更新片段结构(如果是 17→18/19)
  4. 更新 JavaScript(如果是 Owl v1→v2)
  5. 测试所有功能
  6. 生成迁移报告

测试与验证

主题安装测试
# Install theme
python -m odoo -c conf/<config>.conf -d <db> -i theme_<name> --stop-after-init

# Check for errors in log
grep ERROR odoo.log
片段注册测试
  1. 安装主题
  2. 前往 Website → Edit
  3. 检查片段面板
  4. 验证片段是否出现在正确的类别中
  5. 测试拖放
  6. 测试片段选项
浏览器测试
  1. 在多个浏览器中测试(Chrome、Firefox、Safari、Edge)
  2. 测试响应式断点(sm、md、lg、xl、xxl)
  3. 验证可访问性(ARIA 标签、键盘导航)
  4. 检查性能(Lighthouse、Page Speed)

故障排除

⚠️ SCSS 编译错误(严重)

问题:“Undefined variable: $o-color-palettes”或“$o-theme-font-configs”

**原因:**在主题 SCSS 中使用 map-merge() 合并 Odoo 核心变量。主题文件会在核心变量定义之前加载。

解决方案:

  1. 移除所有与核心变量一起使用的 map-merge() 调用
  2. 将 $o-theme-font-configs 定义为独立变量(不进行合并)
  3. 使用 'color-palettes-name': 'default-1' 引用现有调色板
  4. 请参阅上文“严重:SCSS 加载顺序”一节

问题:CSS 显示 0 条规则/样式未加载

**原因:**由于变量未定义,SCSS 编译静默失败。

诊断:

// In browser console
document.styleSheets[0].cssRules.length  // Should be > 0
// If 0, SCSS compilation failed silently

解决方案:

  1. 检查浏览器控制台是否出现“Style compilation failed”错误
  2. 修复未定义变量错误(见上文)
  3. 清除资源缓存:
# Via Odoo shell
>>> self.env['ir.attachment'].search([('url', 'like', '/web/assets/')]).unlink()
>>> self.env.cr.commit()

问题:Google Fonts URL 格式错误

**原因:**使用 ir.asset 记录添加外部字体 URL 会导致 URL 重复。

**解决方案:**在 SCSS 中使用 $o-theme-font-configs,而不是 ir.asset XML 记录。

片段未显示

原因:

  • XPath 不正确(版本不匹配)
  • 缺少 group 属性(Odoo 18/19)
  • 未找到模板
  • XML 语法错误

解决方案:

  1. 检查 Odoo 版本并使用正确的插入方法
  2. 验证模板 ID 是否存在
  3. 检查 XML 语法
  4. 查看日志中的错误
样式未生效

原因:

  • SCSS 未包含在正确的资源包中
  • 变量覆盖时未使用 !default
  • CSS 优先级问题
  • 资源未编译
  • SCSS 编译静默失败(检查控制台!)

解决方案:

  1. 在清单中验证资源包
  2. 清除浏览器缓存
  3. 检查资源重新生成情况:odoo-bin --update theme_<name>
  4. 使用浏览器 DevTools 检查计算后的样式
  5. 先检查控制台中的 SCSS 错误!
JavaScript 无法运行

原因:

  • 对应 Odoo 版本的导入路径不正确
  • 未注册组件
  • jQuery 冲突(Bootstrap 5)
  • Owl 版本不匹配

解决方案:

  1. 检查对应 Odoo 版本的导入路径
  2. 验证组件/部件注册情况
  3. 检查控制台中的错误
  4. 确保使用了正确的 Owl 版本
页眉/页脚模板中的 XPath 错误

原因://header//nav 等复杂 XPath 表达式可能与 Odoo 的实际 HTML 结构不匹配。

**解决方案:**使用简单的 XPath 表达式:


<xpath expr="//header" position="attributes">


<xpath expr="//header//nav" position="attributes">

专家提示

  1. 始终先检测版本

    • 不要假定 Odoo 版本
    • 生成任何代码前都要先检查
  2. 优先使用 Bootstrap 实用工具类

    • 自定义 CSS 越少,维护越容易
    • 性能更好
    • 与编辑器兼容
  3. 使用服务器端渲染优化 SEO

    • 不要依赖客户端 JavaScript 提供内容
    • 仅使用 Owl 增强功能
  4. 在编辑模式下测试

    • 确保网站构建器能够处理你的代码
    • 彻底测试片段选项
  5. 遵循 Odoo 模式

    • 研究核心主题(odoo/addons/theme_*)
    • 遵循现有惯例
    • 使用相同的类前缀(s_、o_)
  6. 文档

    • 为复杂的 SCSS 添加注释
    • 记录片段选项
    • 为主题提供 README

渐进式 Web 应用(PWA)实现

命令:为 Odoo 网站设置 PWA

触发条件:用户要求“制作 PWA”“添加离线支持”或“启用推送通知”

工作流:

  1. 生成 Service Worker

    // static/src/service-worker.js
    const CACHE_NAME = 'odoo-pwa-v1';
    const urlsToCache = ['/', '/web/static/lib/owl/owl.js'];
    
    self.addEventListener('install', event => {
      event.waitUntil(
        caches.open(CACHE_NAME)
          .then(cache => cache.addAll(urlsToCache))
      );
    });
    
    self.addEventListener('fetch', event => {
      event.respondWith(
        caches.match(event.request)
          .then(response => response || fetch(event.request))
      );
    });
    
  2. 创建 Web App Manifest

    {
      "name": "Odoo PWA",
      "short_name": "OdooPWA",
      "start_url": "/",
      "display": "standalone",
      "background_color": "#875A7B",
      "theme_color": "#875A7B",
      "icons": [...]
    }
    
  3. 注册 Service Worker

    /** @odoo-module **/
    import publicWidget from "@web/legacy/js/public/public_widget";
    
    publicWidget.registry.ServiceWorkerRegistration = publicWidget.Widget.extend({
      selector: 'body',
      start: function() {
        if ('serviceWorker' in navigator) {
          navigator.serviceWorker.register('/service-worker.js');
        }
        return this._super(...arguments);
      }
    });
    

现代 JavaScript 与 TypeScript

命令:设置 TypeScript

触发条件:用户要求“使用 TypeScript”“添加类型安全”或“配置 TypeScript”

工作流:

  1. 生成 tsconfig.json

    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "ES2020",
        "lib": ["ES2020", "DOM"],
        "strict": true,
        "paths": {
          "@web/*": ["./addons/web/static/src/*"],
          "@owl/*": ["./addons/web/static/lib/owl/*"]
        }
      }
    }
    
  2. 创建类型化 Owl 组件

    /** @odoo-module **/
    import { Component, useState } from "@odoo/owl";
    
    interface ProductProps {
      id: number;
      name: string;
      price: number;
    }
    
    export class ProductComponent extends Component<ProductProps> {
      static template = xml`...`;
    
      setup() {
        this.state = useState({ loading: false });
      }
    }
    
ES2020+ 模式

使用现代 JavaScript 功能:

// Optional chaining
const userName = this.props.user?.profile?.name;

// Nullish coalescing
const displayName = userName ?? 'Guest User';

// Dynamic imports
const module = await import('./HeavyFeatureComponent');

// Private class fields
class SecureWidget {
  #apiKey = null;
  #generateKey() {
    return crypto.randomUUID();
  }
}

测试基础设施

命令:设置测试

触发条件:用户要求“添加测试”“设置 Jest”或“配置 Cypress”

工作流:

  1. Jest 配置

    // jest.config.js
    module.exports = {
      testEnvironment: 'jsdom',
      moduleNameMapper: {
        '^@web/(.*)$': '<rootDir>/addons/web/static/src/$1',
      },
      collectCoverageFrom: ['static/src/**/*.{js,jsx}']
    };
    
  2. Cypress E2E 测试

    // cypress/e2e/ecommerce.cy.js
    describe('E-commerce Flow', () => {
      it('should complete purchase', () => {
        cy.visit('/shop');
        cy.get('[data-product-id="1"]').click();
        cy.get('#add_to_cart').click();
        cy.get('.btn-primary').contains('Checkout').click();
      });
    });
    
  3. 视觉回归测试

    // backstop.config.js
    module.exports = {
      scenarios: [{
        label: 'Homepage',
        url: 'http://localhost:8069',
        misMatchThreshold: 0.1
      }]
    };
    

性能优化

命令:优化 Core Web Vitals

触发条件:用户要求“提升性能”“优化 LCP/FID/CLS”或“加快网站速度”

工作流:

  1. 优化最大内容绘制(LCP)

    // Preload critical resources
    const link = document.createElement('link');
    link.rel = 'preload';
    link.as = 'image';
    link.href = '/web/image/website/1/logo';
    document.head.appendChild(link);
    
  2. 降低首次输入延迟(FID)

    // Break up long tasks
    function optimizeLongTask(items) {
      const chunks = [];
      for (let i = 0; i < items.length; i += 100) {
        chunks.push(items.slice(i, i + 100));
      }
      chunks.forEach(chunk => {
        requestIdleCallback(() => processChunk(chunk));
      });
    }
    
  3. 尽量减少累积布局偏移(CLS)

    /* Reserve space for dynamic content */
    .product-image-container {
      aspect-ratio: 1/1;
      contain: layout;
    }
    

无障碍访问(符合 WCAG)

命令:实现无障碍访问

触发条件:用户要求“添加 ARIA”“提升无障碍访问能力”或“符合 WCAG”

工作流:

  1. 实现 ARIA

    <form role="form" aria-label="Contact Form">
      <input type="email"
             aria-labelledby="email-label"
             aria-describedby="email-error"
             aria-required="true"
             aria-invalid="false"/>
      <span id="email-error" role="alert" aria-live="polite"></span>
    </form>
    
  2. 键盘导航

    class KeyboardNavigationManager {
      handleTab(event) {
        if (event.shiftKey) {
          // Backward navigation
        } else {
          // Forward navigation
        }
      }
      trapFocus() {
        // Trap focus within modal
      }
    }
    
  3. 屏幕阅读器支持

    class ScreenReaderAnnouncer {
      announce(message, priority = 'polite') {
        this.liveRegion.setAttribute('aria-live', priority);
        this.liveRegion.textContent = message;
      }
    }
    

实时功能

命令:添加 WebSocket 支持

触发条件:用户要求“添加实时功能”“实现 WebSocket”或“实时更新”

工作流:

  1. WebSocket 管理器

    /** @odoo-module **/
    export class WebSocketManager {
      constructor(url) {
        this.socket = new WebSocket(url);
        this.setupEventHandlers();
      }
    
      setupEventHandlers() {
        this.socket.onmessage = (event) => {
          const data = JSON.parse(event.data);
          this.handleMessage(data);
        };
      }
    
      send(data) {
        if (this.socket.readyState === WebSocket.OPEN) {
          this.socket.send(JSON.stringify(data));
        }
      }
    }
    
  2. 服务器发送事件(SSE)

    class SSEManager {
      constructor(url) {
        this.eventSource = new EventSource(url);
        this.eventSource.onmessage = (event) => {
          this.handleMessage(event.data);
        };
      }
    }
    

Web 组件

命令:创建 Web 组件

触发条件:用户要求“创建 Web 组件”或“自定义元素”

工作流:

/** @odoo-module **/

export class OdooSearchInput extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  static get observedAttributes() {
    return ['placeholder', 'value'];
  }

  connectedCallback() {
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = `
      <style>
        :host { display: block; }
        input { width: 100%; padding: 0.5rem; }
      </style>
      <input type="text" placeholder="${this.getAttribute('placeholder')}"/>
    `;
  }
}

customElements.define('odoo-search-input', OdooSearchInput);

Odoo 高级功能

导览管理器

命令:创建引导式导览

/** @odoo-module **/
import tour from 'web_tour.tour';

tour.register('product_tour', {
  url: '/shop',
}, [{
  content: 'Click on any product',
  trigger: '.oe_product:first',
  position: 'bottom',
}]);
自定义字段 Widget
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { Component } from "@odoo/owl";

export class ColorPickerField extends Component {
  static template = xml`
    <input type="color" t-att-value="props.value" t-on-change="onChange"/>
  `;

  onChange(event) {
    this.props.update(event.target.value);
  }
}

registry.category("fields").add("color_picker", ColorPickerField);
系统托盘组件
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { Component, useState } from "@odoo/owl";

export class NotificationBell extends Component {
  static template = xml`...`;

  setup() {
    this.state = useState({ notifications: [] });
  }
}

registry.category("systray").add("NotificationBell", {
  Component: NotificationBell,
});

现代构建工具

Vite 配置

命令:设置 Vite 构建

// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  base: '/web/static/',
  build: {
    rollupOptions: {
      input: {
        main: 'static/src/main.js',
        website: 'static/src/website.js'
      }
    }
  },
  server: {
    proxy: {
      '/web': 'http://localhost:8069'
    }
  }
});
CI/CD 流水线
# .github/workflows/odoo-frontend.yml
name: Odoo Frontend CI/CD

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Run tests
      run: |
        npm run test:unit
        npm run test:e2e
    - name: Build assets
      run: npm run build

资源

主题功能激活系统(重要)

概述:theme.utils 模型

创建主题时,必须包含一个实现 theme.utils 模式的 models/theme_xxx.py 文件。这样,主题便可以:

  • 启用/禁用页眉模板
  • 启用/禁用页脚模板
  • 在主题安装时激活可选功能
  • 配置主题的默认外观
必需的文件结构
theme_yourtheme/
├── __init__.py                    # Must import models
├── __manifest__.py
├── models/
│   ├── __init__.py               # Must import theme_yourtheme
│   └── theme_yourtheme.py        # theme.utils implementation (REQUIRED!)
├── data/
├── views/
└── static/
models/init.py
from . import theme_yourtheme
models/theme_yourtheme.py(必需模式)
from odoo import models

class ThemeYourTheme(models.AbstractModel):
    _inherit = 'theme.utils'

    def _theme_yourtheme_post_copy(self, mod):
        """
        Called automatically when theme is installed on a website.
        Use enable_view() and disable_view() to configure templates.

        IMPORTANT: Method name MUST be _theme_{technical_name}_post_copy
        where technical_name matches folder name (underscores, not hyphens).
        """
        # Enable desired header template (mutual exclusivity enforced)
        self.enable_view('website.template_header_sales_two')

        # Enable desired footer template (mutual exclusivity enforced)
        self.enable_view('website.template_footer_contact')

        # Enable optional features
        self.enable_view('website.option_header_brand_logo')

        # Disable unwanted defaults
        self.disable_view('website.header_visibility_standard')
        self.enable_view('website.header_visibility_fixed')
可用的关键方法
方法用途示例
enable_view(xml_id)激活模板self.enable_view('website.template_header_hamburger')
disable_view(xml_id)停用模板self.disable_view('website.template_header_default')
enable_asset(xml_id)激活资源包self.enable_asset('website.theme_custom_assets')
disable_asset(xml_id)停用资源包self.disable_asset('website.theme_old_assets')
互斥规则

页眉:同一时间只能启用一个主页眉模板。启用某个页眉时,enable_view() 会由 Odoo 自动处理其他页眉的停用。

页脚:同一时间只能启用一个主页脚模板。其他页脚也会以同样的方式自动停用。

页眉选项:对齐方式、可见性效果和组件可以与当前启用的页眉组合使用。

完整示例:现代商务主题
from odoo import models

class ThemeModernBusiness(models.AbstractModel):
    _inherit = 'theme.utils'

    def _theme_modern_business_post_copy(self, mod):
        """Configure Modern Business theme defaults."""

        # === HEADER CONFIGURATION ===
        # Use hamburger header with centered mobile
        self.enable_view('website.template_header_hamburger')
        self.enable_view('website.template_header_hamburger_mobile_align_center')

        # Fixed header that disappears on scroll
        self.disable_view('website.header_visibility_standard')
        self.enable_view('website.header_visibility_disappears')

        # Show logo, not brand name
        self.enable_view('website.option_header_brand_logo')
        self.disable_view('website.option_header_brand_name')

        # === FOOTER CONFIGURATION ===
        # Use contact footer with call-to-action style
        self.enable_view('website.template_footer_contact')

        # Enable scroll-to-top button
        # (This is configured in $o-website-values-palettes, not via views)

        # === OPTIONAL FEATURES ===
        # Enable search in header
        self.enable_view('website.header_search_box')

        # Enable social links
        self.enable_view('website.header_social_links')

动态页面完整参考

本节介绍 Odoo 中用于动态页面的所有可用模板。可在以下情况下参考:

  1. 对照 Figma 设计,找到最接近的 Odoo 模板
  2. 通过 _theme_xxx_post_copy() 配置主题默认值
  3. 了解 Odoo 开箱即用的功能

📋 页眉模板(11 个主模板 + 变体)

所有页眉都继承自 website.layout。每个网站只能启用一个主页眉。

主页眉模板
XML ID名称描述适用场景
website.template_header_default默认标准水平导航栏大多数网站
website.template_header_hamburger汉堡菜单折叠式汉堡菜单极简设计
website.template_header_stretch延展式全宽延展式导航栏宽屏布局
website.template_header_vertical垂直式垂直侧边栏导航类似应用的站点
website.template_header_search搜索式带醒目搜索框的页眉电子商务、目录站点
website.template_header_sales_one销售 1面向电子商务的页眉在线商店
website.template_header_sales_two销售 2带分类的电子商务页眉大型商品目录
website.template_header_sales_three销售 3电子商务替代样式以产品为中心的站点
website.template_header_sales_four销售 4电子商务变体时尚、零售
website.template_header_sidebar侧边栏完整的侧边栏布局仪表板站点
website.template_header_boxed方框式圆角方框容器现代品牌
页眉对齐方式变体(适用于每种页眉)

每种页眉都支持对齐方式变体。以默认页眉为例:

  • website.template_header_default - 左对齐(默认)
  • website.template_header_default_align_center - 居中对齐
  • website.template_header_default_align_right - 右对齐

移动端变体(添加 _mobile_align_center 或 _mobile_align_right 后缀):

  • website.template_header_hamburger_mobile_align_center
  • website.template_header_hamburger_mobile_align_right
页眉可见性效果(互斥)
XML ID效果描述
website.header_visibility_standard标准始终可见,随页面滚动
website.header_visibility_fixed固定滚动时固定在顶部
website.header_visibility_disappears消失向下滚动时隐藏,向上滚动时显示
website.header_visibility_fade_out淡出滚动时淡出
页眉组件(可组合使用)
XML ID组件默认状态
website.option_header_brand_logo显示徽标图片启用
website.option_header_brand_name显示文字“My Website”未启用
website.header_call_to_actionCTA 按钮(联系我们)启用
website.header_search_box搜索栏启用
website.header_social_links社交媒体图标未启用
website.header_text_element自定义文本元素启用
website.header_language_selector语言下拉菜单启用
页眉视觉参考
┌─────────────────────────────────────────────────────────────────┐
│ DEFAULT HEADER                                                   │
│ ┌────────┬────────────────────────────────┬───────────────────┐ │
│ │ LOGO   │  Home  About  Services  Blog   │  Search   CTA BTN │ │
│ └────────┴────────────────────────────────┴───────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ HAMBURGER HEADER                                                 │
│ ┌────────┬───────────────────────────────┬────────────────────┐ │
│ │ ☰ MENU │         LOGO                  │   Search   CTA     │ │
│ └────────┴───────────────────────────────┴────────────────────┘ │
│                    ↓ (expanded menu)                             │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  Home  │  About  │  Services  │  Blog  │  Contact           │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ SALES TWO HEADER (E-commerce)                                    │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Top bar: Phone | Email | Currency | Language                 │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ LOGO        [    Search Bar    ]        Account  Cart 🛒    │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ Category 1  │  Category 2  │  Category 3  │  All Products   │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────────┐
│ VERTICAL/SIDEBAR HEADER                                         │
│ ┌──────────────┬──────────────────────────────────────────────┐│
│ │              │                                               ││
│ │    LOGO      │                                               ││
│ │              │                                               ││
│ │  ─────────── │              PAGE CONTENT                     ││
│ │              │                                               ││
│ │   Home       │                                               ││
│ │   About      │                                               ││
│ │   Services   │                                               ││
│ │   Blog       │                                               ││
│ │   Contact    │                                               ││
│ │              │                                               ││
│ │  ─────────── │                                               ││
│ │  [Social]    │                                               ││
│ └──────────────┴──────────────────────────────────────────────┘│
└────────────────────────────────────────────────────────────────┘

📋 页脚模板(9 个选项)

所有页脚均继承自 website.layout。每个网站只能启用一个主页脚。

XML ID名称描述适用场景
website.footer_custom默认可自定义的默认页脚通用
website.template_footer_descriptive详细介绍详细的公司介绍企业网站
website.template_footer_centered居中居中对齐的简约设计着陆页
website.template_footer_links链接多列链接大型网站
website.template_footer_minimalist极简极简页脚简洁设计
website.template_footer_contact联系方式侧重联系信息服务型企业
website.template_footer_call_to_action行动号召侧重新闻简报和行动号召营销网站
website.template_footer_headline标题大号标题文本品牌宣言
website.template_footer_slideout滑出式滚动时滑出现代/潮流风格
页脚选项
XML ID选项描述
website.footer_no_copyright移除版权信息隐藏版权行
website.footer_language_selector_inline内联语言选择器语言选择器样式
页脚视觉参考
┌─────────────────────────────────────────────────────────────────┐
│ DEFAULT FOOTER                                                   │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  LOGO                                                        │ │
│ │  Company description text here...                            │ │
│ │                                                              │ │
│ │  Links        Services      Contact                          │ │
│ │  · About      · Web Dev     123 Street                       │ │
│ │  · Blog       · Design      City, Country                    │ │
│ │  · Careers    · Support     +1 234 567 890                   │ │
│ │                                                              │ │
│ │  [Social Icons]                                              │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │  © 2024 Company Name. All rights reserved.                   │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ MINIMALIST FOOTER                                                │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │          © 2024 Company  ·  Privacy  ·  Terms               │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ CONTACT FOOTER                                                   │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │  ┌──────────────┬───────────────┬────────────────────────┐  │ │
│ │  │  📍 Address  │  📞 Phone     │  ✉️ Email              │  │ │
│ │  │  123 Street  │  +1 234 567   │  [email protected]     │  │ │
│ │  │  City        │               │                        │  │ │
│ │  └──────────────┴───────────────┴────────────────────────┘  │ │
│ │                        [Social Icons]                        │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ CALL-TO-ACTION FOOTER                                            │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │            Subscribe to our newsletter                       │ │
│ │  ┌─────────────────────────────────┬─────────────────────┐  │ │
│ │  │  Enter your email...            │  [Subscribe Now]    │  │ │
│ │  └─────────────────────────────────┴─────────────────────┘  │ │
│ │                                                              │ │
│ │  © 2024 Company  ·  [Social Icons]                          │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

📋 商店页面模板(website_sale 模块)
商店布局选项
XML ID功能默认值描述
website_sale.products_design_card卡片设计未启用卡片式商品项
website_sale.products_design_thumbs缩略图未启用缩略图布局
website_sale.products_design_grid网格设计未启用网格布局
website_sale.products_thumb_4_34:3 比例未启用4:3 图片宽高比
website_sale.products_thumb_4_54:5 比例未启用4:5 图片宽高比
website_sale.products_thumb_2_32:3 比例未启用2:3 图片宽高比
website_sale.products_thumb_cover封面填充已启用图片填满容器
商店分类与筛选器
XML ID功能默认值描述
website_sale.products_categories左侧边栏分类未启用在左侧边栏显示分类
website_sale.products_categories_top顶部分类已启用在顶部导航中显示分类
website_sale.products_attributes属性筛选器已启用商品属性筛选器
website_sale.filter_products_price价格筛选器未启用价格范围滑块
website_sale.filter_products_tags标签筛选器已启用按商品标签筛选
website_sale.search搜索框已启用搜索商品
website_sale.sort排序下拉菜单已启用对商品排序
website_sale.add_grid_or_list_option网格/列表切换已启用视图切换按钮
商品项选项
XML ID功能默认值描述
website_sale.products_description描述未启用在商品列表中显示描述
website_sale.products_add_to_cart加入购物车未启用在商品列表中显示加入购物车按钮
商店视觉参考
┌─────────────────────────────────────────────────────────────────┐
│ SHOP PAGE (Categories Top - Default)                             │
├─────────────────────────────────────────────────────────────────┤
│  All  │  Category 1  │  Category 2  │  Category 3               │
├─────────────────────────────────────────────────────────────────┤
│  [Search...]          [Sort: Featured ▼]   [☷ Grid] [☰ List]   │
├─────────────────────────────────────────────────────────────────┤
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │  [IMG]   │  │  [IMG]   │  │  [IMG]   │  │  [IMG]   │        │
│  │          │  │          │  │          │  │          │        │
│  │ Product 1│  │ Product 2│  │ Product 3│  │ Product 4│        │
│  │ $99.00   │  │ $149.00  │  │ $79.00   │  │ $199.00  │        │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘        │
│                                                                  │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐        │
│  │  [IMG]   │  │  [IMG]   │  │  [IMG]   │  │  [IMG]   │        │
│  │ ...      │  │ ...      │  │ ...      │  │ ...      │        │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘        │
├─────────────────────────────────────────────────────────────────┤
│                    [1] [2] [3] ... [Next →]                      │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ SHOP PAGE (Categories Left Sidebar)                              │
├────────────────┬────────────────────────────────────────────────┤
│ CATEGORIES     │  [Search...]     [Sort ▼]    [☷] [☰]          │
│ ─────────────  ├────────────────────────────────────────────────┤
│ □ All          │  ┌────────┐  ┌────────┐  ┌────────┐           │
│ ▸ Category 1   │  │ [IMG]  │  │ [IMG]  │  │ [IMG]  │           │
│   · Sub 1.1    │  │ Prod 1 │  │ Prod 2 │  │ Prod 3 │           │
│   · Sub 1.2    │  │ $99    │  │ $149   │  │ $79    │           │
│ ▸ Category 2   │  └────────┘  └────────┘  └────────┘           │
│ ▸ Category 3   │                                                │
│                │  ┌────────┐  ┌────────┐  ┌────────┐           │
│ PRICE          │  │ ...    │  │ ...    │  │ ...    │           │
│ ─────────────  │  └────────┘  └────────┘  └────────┘           │
│ $0 ─●────── $500│                                               │
│                │                                                │
│ TAGS           │                                                │
│ ─────────────  │                                                │
│ [New] [Sale]   │                                                │
└────────────────┴────────────────────────────────────────────────┘

📋 产品详情页模板
XML ID功能默认状态描述
website_sale.product_tags产品标签启用显示产品标签
website_sale.product_comment评价未启用讨论/评分版块
website_sale.product_custom_text条款区块启用条款与条件
website_sale.product_share_buttons分享按钮启用社交媒体分享按钮
website_sale.product_quantity数量选择器启用数量输入框
website_sale.product_buy_now立即购买未启用快速购买按钮
website_sale.product_variants变体列表未启用变体列表视图
website_sale.alternative_products替代产品启用替代产品轮播图
website_sale.carousel_product_indicators_bottom轮播图底部指示器启用底部的图片指示器
website_sale.carousel_product_indicators_left轮播图左侧指示器未启用左侧的图片指示器
产品页面视觉参考
┌─────────────────────────────────────────────────────────────────┐
│ PRODUCT DETAIL PAGE                                              │
├─────────────────────────────────────────────────────────────────┤
│  Home > Category > Product Name                                  │
├───────────────────────────────┬─────────────────────────────────┤
│                               │                                  │
│  ┌─────────────────────────┐  │  Product Name                    │
│  │                         │  │  ★★★★☆ (24 reviews)             │
│  │      [MAIN IMAGE]       │  │                                  │
│  │                         │  │  $199.00                         │
│  │                         │  │  ̶$̶2̶4̶9̶.̶0̶0̶ -20%                     │
│  └─────────────────────────┘  │                                  │
│  [○] [○] [●] [○]              │  Color: [Blue ▼]                 │
│                               │  Size:  [M ▼]                    │
│  ┌──────┐ ┌──────┐ ┌──────┐  │                                  │
│  │thumb1│ │thumb2│ │thumb3│  │  Qty: [- 1 +]                    │
│  └──────┘ └──────┘ └──────┘  │                                  │
│                               │  [Add to Cart]  [Buy Now]        │
│                               │                                  │
│                               │  [♡ Wishlist] [🔗 Share]         │
│                               │                                  │
│                               │  Tags: [New] [Bestseller]        │
├───────────────────────────────┴─────────────────────────────────┤
│  DESCRIPTION  │  SPECIFICATIONS  │  REVIEWS (24)                 │
├─────────────────────────────────────────────────────────────────┤
│  Full product description text goes here...                      │
│                                                                  │
├─────────────────────────────────────────────────────────────────┤
│  ALTERNATIVE PRODUCTS                                            │
│  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐                │
│  │ Alt 1  │  │ Alt 2  │  │ Alt 3  │  │ Alt 4  │                │
│  └────────┘  └────────┘  └────────┘  └────────┘                │
└─────────────────────────────────────────────────────────────────┘

📋 博客页面模板(website_blog 模块)
博客列表选项
XML ID功能默认状态描述
website_blog.opt_blog_cover_post最新文章横幅启用将最新文章显示为横幅
website_blog.opt_blog_cover_post_fullwidth_design全宽横幅启用横幅横跨整个宽度
website_blog.opt_blog_list_view列表视图未启用以列表而非网格显示文章
website_blog.opt_blog_cards_design卡片设计未启用Bootstrap 卡片组件
website_blog.opt_blog_readable易读模式启用使用更大、更易读的文本
website_blog.opt_blog_sidebar_show侧边栏未启用显示博客侧边栏
博客侧边栏组件(启用时)
XML ID功能默认状态描述
website_blog.opt_sidebar_blog_index_archives归档启用按日期归档
website_blog.opt_sidebar_blog_index_follow_us关注我们启用社交媒体链接
website_blog.opt_sidebar_blog_index_tags标签启用标签云
博客文章循环显示
XML ID功能默认状态描述
website_blog.opt_posts_loop_show_cover封面图片启用特色图片
website_blog.opt_posts_loop_show_author作者启用作者姓名/头像
website_blog.opt_posts_loop_show_stats统计信息未启用评论数/浏览量
website_blog.opt_posts_loop_show_teaser摘要启用预览文本和标签
博客文章详情选项
XML ID功能默认状态描述
website_blog.opt_blog_post_readable易读模式启用使用更大、更易读的文本
website_blog.opt_blog_post_sidebar侧边栏未启用显示文章侧边栏
website_blog.opt_blog_post_regular_cover常规封面未启用标题显示在封面上方
website_blog.opt_blog_post_breadcrumb面包屑导航启用导航面包屑
website_blog.opt_blog_post_read_next阅读下一篇启用下一篇文章横幅
website_blog.opt_blog_post_comment评论未启用启用评论
website_blog.opt_blog_post_select_to_tweet选中文字发推未启用高亮文字后发推
博客视觉参考
┌─────────────────────────────────────────────────────────────────┐
│ BLOG LISTING (Grid View - Default)                               │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────────┐│
│  │             [FEATURED POST BANNER IMAGE]                     ││
│  │                                                              ││
│  │  Latest Article Title                                        ││
│  │  Short teaser text for the featured post...                  ││
│  │                        [Read More →]                         ││
│  └─────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────┤
│  All Posts  │  Category 1  │  Category 2  │  [Search...]        │
├─────────────────────────────────────────────────────────────────┤
│  ┌────────────────┐  ┌────────────────┐  ┌────────────────┐    │
│  │   [IMAGE]      │  │   [IMAGE]      │  │   [IMAGE]      │    │
│  │                │  │                │  │                │    │
│  │ Post Title 1   │  │ Post Title 2   │  │ Post Title 3   │    │
│  │ By Author      │  │ By Author      │  │ By Author      │    │
│  │ Jan 15, 2024   │  │ Jan 10, 2024   │  │ Jan 5, 2024    │    │
│  │                │  │                │  │                │    │
│  │ Teaser text... │  │ Teaser text... │  │ Teaser text... │    │
│  │ [Tag1] [Tag2]  │  │ [Tag1]         │  │ [Tag2] [Tag3]  │    │
│  └────────────────┘  └────────────────┘  └────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ BLOG POST DETAIL (Fullwidth Cover - Default)                     │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────────┐│
│  │                   [FULL WIDTH COVER IMAGE]                   ││
│  │                                                              ││
│  │           Article Title Goes Here                            ││
│  │           By Author Name  ·  January 15, 2024               ││
│  └─────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│     Full article content with readable typography.               │
│                                                                  │
│     Multiple paragraphs of content...                            │
│                                                                  │
│     [Share: Facebook | Twitter | LinkedIn]                       │
│                                                                  │
│     Tags: [Technology] [Tutorial] [Odoo]                         │
│                                                                  │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────────────┐│
│  │  READ NEXT: Next Article Title                               ││
│  │  [Banner Image]                                              ││
│  └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘

📋 购物车与结账模板
XML ID功能默认状态描述
website_sale.suggested_products_list推荐产品启用购物车中的配件产品
website_sale.reduction_code促销码启用优惠券代码输入框
website_sale.address_b2bB2B 字段未启用商业地址字段
website_sale.accept_terms_and_conditions条款与条件复选框未启用要求接受条款与条件

设计工作流:从 Figma 到 Odoo

方法

将 Figma 设计转换为 Odoo 主题时,请遵循以下工作流:

┌─────────────────────────────────────────────────────────────────┐
│                    DESIGN WORKFLOW                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. ANALYZE DESIGN                                               │
│     ↓                                                            │
│     Extract: Colors, Fonts, Layout, Components                   │
│                                                                  │
│  2. COMPARE TO ODOO TEMPLATES                                    │
│     ↓                                                            │
│     Match: Header style, Footer style, Page layouts              │
│                                                                  │
│  3. CHOOSE CLOSEST TEMPLATE                                      │
│     ↓                                                            │
│     Select: Best matching header + footer + features             │
│                                                                  │
│  4. CONFIGURE VIA VARIABLES                                      │
│     ↓                                                            │
│     Set: $o-website-values-palettes configuration                │
│                                                                  │
│  5. ENHANCE WITH CUSTOM CSS                                      │
│     ↓                                                            │
│     Add: Only what templates can't provide                       │
│                                                                  │
│  6. CREATE CUSTOM SNIPPETS (if needed)                           │
│     ↓                                                            │
│     Build: Components not available in Odoo                      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
第 1 步:分析 Figma 设计

提取以下关键元素:

元素查找内容对应项
主色品牌主色(按钮、链接、CTA)o-color-1
次要颜色点缀色、次要按钮o-color-2
浅色背景区块背景、卡片o-color-3
白色/基础色主要内容背景o-color-4
深色/文本标题、页脚、深色区块o-color-5
字体系列正文、标题$o-theme-font-configs
页眉样式导航布局'header-template'
页脚样式页脚布局'footer-template'
第 2 步:匹配页眉样式
如果设计包含……使用模板
标准水平导航template_header_default
隐藏菜单(汉堡图标)template_header_hamburger
全宽展开式导航template_header_stretch
垂直侧边栏导航template_header_vertical
突出的搜索栏template_header_search
带有分类的电商布局template_header_sales_two
顶栏 + 主导航template_header_sales_one
带内容的侧边栏template_header_sidebar
圆角/带边框容器template_header_boxed
第 3 步:匹配页脚样式
如果设计包含……使用模板
带 Logo 的多栏布局footer_custom(默认)
详细的公司信息template_footer_descriptive
居中简约布局template_footer_centered
仅包含多列链接template_footer_links
极简版权信息template_footer_minimalist
突出联系信息template_footer_contact
突出新闻简报/CTAtemplate_footer_call_to_action
大号标题文字template_footer_headline
现代滑出效果template_footer_slideout
第 4 步:生成主题配置

根据分析结果创建配置:

// primary_variables.scss - Generated from Figma Analysis

$o-theme-font-configs: (
    'Poppins': (
        'family': ('Poppins', sans-serif),
        'url': 'Poppins:300,400,500,600,700',
    ),
);

$o-website-values-palettes: (
    (
        'color-palettes-name': 'default-1',

        // Typography (from Figma)
        'font': 'Poppins',
        'headings-font': 'Poppins',

        // Header (matched to closest template)
        'header-template': 'hamburger',  // Figma shows hamburger menu
        'header-links-style': 'pills',   // Rounded pill-style links
        'logo-height': 48px,

        // Footer (matched to closest template)
        'footer-template': 'contact',    // Figma shows contact-focused footer
        'footer-scrolltop': true,

        // Buttons (from Figma measurements)
        'btn-padding-y': 0.75rem,
        'btn-padding-x': 1.5rem,
        'btn-border-radius': 8px,

        // Layout
        'link-underline': 'never',
    )
);
第 5 步:创建 theme.utils 实现
# models/theme_yourtheme.py

from odoo import models

class ThemeYourTheme(models.AbstractModel):
    _inherit = 'theme.utils'

    def _theme_yourtheme_post_copy(self, mod):
        # Enable matched header template
        self.enable_view('website.template_header_hamburger')
        self.enable_view('website.template_header_hamburger_mobile_align_center')

        # Enable matched footer template
        self.enable_view('website.template_footer_contact')

        # Enable desired header components
        self.enable_view('website.option_header_brand_logo')
        self.enable_view('website.header_search_box')

        # Configure visibility effect
        self.disable_view('website.header_visibility_standard')
        self.enable_view('website.header_visibility_fixed')
决策流程图:选择页眉
START: Analyze header design
    │
    ├─ Is navigation hidden by default?
    │   │
    │   ├─ YES → template_header_hamburger
    │   │
    │   └─ NO → Is there a vertical sidebar?
    │           │
    │           ├─ YES → Is it full-page sidebar?
    │           │   │
    │           │   ├─ YES → template_header_sidebar
    │           │   └─ NO → template_header_vertical
    │           │
    │           └─ NO → Is it e-commerce focused?
    │                   │
    │                   ├─ YES → Does it have category mega-menu?
    │                   │   │
    │                   │   ├─ YES → template_header_sales_two
    │                   │   └─ NO → template_header_sales_one
    │                   │
    │                   └─ NO → Is search prominent?
    │                           │
    │                           ├─ YES → template_header_search
    │                           │
    │                           └─ NO → Is it boxed/contained?
    │                                   │
    │                                   ├─ YES → template_header_boxed
    │                                   │
    │                                   └─ NO → Is it full-width?
    │                                           │
    │                                           ├─ YES → template_header_stretch
    │                                           └─ NO → template_header_default
决策流程图:选择页脚
START: Analyze footer design
    │
    ├─ Is it minimal (just copyright)?
    │   │
    │   ├─ YES → template_footer_minimalist
    │   │
    │   └─ NO → Is it center-aligned?
    │           │
    │           ├─ YES → template_footer_centered
    │           │
    │           └─ NO → Is there a newsletter/CTA?
    │                   │
    │                   ├─ YES → template_footer_call_to_action
    │                   │
    │                   └─ NO → Is it contact info focused?
    │                           │
    │                           ├─ YES → template_footer_contact
    │                           │
    │                           └─ NO → Is it primarily links?
    │                                   │
    │                                   ├─ YES → template_footer_links
    │                                   │
    │                                   └─ NO → Has detailed description?
    │                                           │
    │                                           ├─ YES → template_footer_descriptive
    │                                           │
    │                                           └─ NO → footer_custom (default)

网站片段完整参考

本节介绍 Odoo 中所有可用的片段、它们的工作方式以及如何创建自定义片段。构建网站页面、创建自定义片段或了解网站构建器时,请参考本节。


📚 片段架构概述

片段是 Odoo 网站页面的可复用构建模块,可用于拖放式页面构建。

┌─────────────────────────────────────────────────────────────────┐
│                     ODOO SNIPPET SYSTEM                          │
├─────────────────────────────────────────────────────────────────┤
│  ┌──────────────────┐    ┌──────────────────┐                   │
│  │  STATIC SNIPPETS │    │ DYNAMIC SNIPPETS │                   │
│  ├──────────────────┤    ├──────────────────┤                   │
│  │ • Pre-built HTML │    │ • Data-driven    │                   │
│  │ • 81+ templates  │    │ • ir.filters     │                   │
│  │ • Drag & drop    │    │ • Display temps  │                   │
│  └──────────────────┘    └──────────────────┘                   │
│                              │                                   │
│  ┌───────────────────────────┴────────────────────────────────┐ │
│  │                   SNIPPET OPTIONS SYSTEM                    │ │
│  ├────────────────────────────────────────────────────────────┤ │
│  │  we-select | we-button | we-colorpicker | we-input | etc.  │ │
│  │  data-selector | data-select-class | data-js | data-css    │ │
│  └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

📋 静态片段清单(81+ 个模板)

Odoo 提供 81+ 个静态片段模板,分为 6 个主要类别:

类别数量用途
结构14页面区块、布局
图库/媒体3图片库、轮播图
功能11功能展示
动态内容15地图、表单、嵌入内容
内部内容16卡片、引用、文本块
超级菜单9导航菜单
结构片段(14)
片段 ID名称描述最适用于
s_banner横幅带背景的主视觉区域落地页
s_cover封面全宽封面图片页眉
s_text_image文字 - 图片两栏布局:左侧文字,右侧图片功能介绍
s_image_text图片 - 文字两栏布局:左侧图片,右侧文字功能介绍
s_title标题带副标题的章节标题章节标题
s_text_block文字块多栏文字内容内容
s_numbers数字统计数据/计数器展示成就
s_picture图片单张大图视觉焦点
s_three_columns栏三栏布局内容网格
s_big_boxes大型方框大型功能方框服务
s_features功能带图标的功能网格优势
s_masonry_block瀑布流区块瀑布流样式网格图库
s_image_gallery图片库多图图库作品集
s_images_wall图片墙墙面样式展示媒体
功能片段(11)
片段 ID名称描述最适用于
s_comparisons对比并排对比产品对比
s_company_team公司团队团队成员卡片关于页面
s_call_to_action行动号召带按钮的 CTA 区块转化
s_references客户案例客户徽标/参考资料社会认同
s_accordion折叠面板可折叠内容常见问题
s_features_grid功能网格功能图标网格优势
s_table_of_content目录滚动定位导航长页面
s_pricelist价格表定价表产品
s_product_list产品列表产品展示电子商务
s_faq_collapse常见问题常见问题解答支持
s_tabs标签页标签式内容内容组织
动态内容片段(15)
片段 ID名称描述最适用于
s_google_mapGoogle 地图嵌入 Google 地图联系页面
s_map地图Leaflet/OpenStreetMap地点
s_embed_code嵌入代码自定义 HTML/嵌入内容第三方内容
s_website_form网站表单联系表单/自定义表单潜在客户收集
s_searchbar搜索栏搜索功能导航
s_social_media社交媒体社交链接互动
s_share分享分享按钮内容分享
s_dynamic_snippet动态片段基础动态内容数据展示
s_dynamic_snippet_carousel动态轮播动态轮播特色内容
s_chart图表数据可视化报告
s_countdown倒计时计时器/倒计时活动
s_popup弹出窗口模态弹窗促销
s_newsletter_block新闻简报邮件订阅营销
s_newsletter_popup新闻简报弹窗弹窗订阅潜在客户收集
s_newsletter_subscribe_form新闻简报表单行内订阅页脚
内部内容片段(16)
片段 ID名称描述最适用于
s_hr水平线分隔线分隔章节
s_alert警示框警示/通知框通知
s_card卡片内容卡片项目
s_three_cards三卡片三卡片布局服务
s_four_cards四卡片四卡片布局功能
s_timeline时间线时间线展示历史
s_process_steps流程步骤分步指南教程
s_quotes_carousel引言轮播用户评价轮播社会认同
s_quotes_grid引言网格用户评价网格评论
s_rating评分星级评分展示评论
s_progress_bar进度条进度指示器统计数据
s_blockquote引用块带样式的引言强调
s_badge徽章徽章/标签标签
s_button按钮带样式的按钮行动号召
s_separator分隔符章节分隔符视觉分隔
s_text_highlight文本高亮高亮文本框强调
超级菜单片段(9)
片段 ID名称描述
s_mega_menu_multi_menus多菜单多个菜单栏
s_mega_menu_menu_image_menu图片菜单带图片的三个区域
s_mega_menu_little_icons小图标基于图标的菜单
s_mega_menu_images_subtitles图片与副标题带副标题的图片
s_mega_menu_cards卡片卡片式菜单
s_mega_menu_big_icons大图标大图标菜单
s_mega_menu_thumbnails缩略图缩略图库菜单
s_mega_menu_odoo_menuOdoo 菜单Odoo 风格菜单
s_mega_menu_no_extra_info无额外信息简洁菜单

📋 动态片段系统

动态片段从数据库获取数据,并使用模板进行渲染。

架构
┌─────────────────────────────────────────────────────────────────┐
│                  DYNAMIC SNIPPET FLOW                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌──────────────────┐                                           │
│  │   DATA SOURCE    │ ← ir.filters / ir.actions.server          │
│  └────────┬─────────┘                                           │
│           │                                                      │
│           ▼                                                      │
│  ┌──────────────────┐                                           │
│  │ DYNAMIC FILTER   │ ← website.snippet.filter model            │
│  │ (Data Fetcher)   │                                           │
│  └────────┬─────────┘                                           │
│           │                                                      │
│           ▼                                                      │
│  ┌──────────────────┐                                           │
│  │ DISPLAY TEMPLATE │ ← dynamic_filter_template_*               │
│  │   (Renderer)     │                                           │
│  └──────────────────┘                                           │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
商品动态片段(website_sale)

主片段:s_dynamic_snippet_products

展示模板(15 个变体):

模板键名称样式
dynamic_filter_template_product_product_add_to_cart加入购物车带购买按钮
dynamic_filter_template_product_product_banner横幅大型横幅格式
dynamic_filter_template_product_product_borderless_1/2/3无边框简洁无边框
dynamic_filter_template_product_product_card_style卡片样式基于卡片的样式
dynamic_filter_template_product_product_centered居中居中对齐
dynamic_filter_template_product_product_horizontal_card横向卡片横向布局
dynamic_filter_template_product_product_mini迷你紧凑展示
dynamic_filter_template_product_product_minimalist_1/2/3/4极简极简设计
dynamic_filter_template_product_product_picture图片以图片为主

动态筛选器(6 个数据源):

筛选器 ID名称逻辑
dynamic_filter_newest_products最新商品按 create_date 降序排序
dynamic_filter_recently_sold_products最近售出的商品最近有销量的商品
dynamic_filter_recently_viewed_products最近浏览的商品用户的浏览历史
dynamic_filter_accessories配件相关配件
dynamic_filter_products_sold_with搭配购买经常一起购买的商品
dynamic_filter_alternative_products替代商品类似商品
博客动态片段(website_blog)

主片段:s_blog_posts

展示模板(4 种布局):

模板键名称
dynamic_filter_template_blog_post_big_picture大图
dynamic_filter_template_blog_post_card卡片
dynamic_filter_template_blog_post_horizontal横向
dynamic_filter_template_blog_post_vertical纵向

筛选器:dynamic_filter_latest_posts、dynamic_filter_most_read_posts

Event 动态片段(website_event)

主要片段:s_events

显示模板:dynamic_filter_template_event_event_card、dynamic_filter_template_event_event_list

筛选器:dynamic_filter_upcoming_events


📋 片段选项系统

选项系统允许用户在网站编辑器中自定义片段。

We-* 元素参考
元素用途关键属性
we-select下拉选择data-name、data-dependencies
we-button切换按钮data-select-class、data-toggle-class
we-colorpicker颜色选择器data-css-property、data-color-prefix
we-input文本/数字输入data-attribute-name、data-unit
we-range滑块data-min、data-max、data-step
we-checkbox复选框切换data-select-class
we-button-group按钮组用于对齐等
we-row选项行对相关输入进行分组
we-select-pager分页选择用于大型选项集
数据属性参考
属性用途示例
data-selector目标 CSS 选择器data-selector=".s_banner"
data-select-class切换 CSS 类data-select-class="shadow-lg"
data-select-style启用样式编辑data-select-style="true"
data-css-property要修改的 CSS 属性data-css-property="padding"
data-jsJavaScript 处理程序类data-js="BackgroundImage"
data-dependencies根据其他选项显示data-dependencies="image_opt"
data-exclude对特定元素隐藏data-exclude=".no-shadow"
data-no-preview禁用实时预览data-no-preview="true"
data-attribute-name要设置的 HTML 属性data-attribute-name="data-speed"
内置 JavaScript 处理程序
处理程序用途
BackgroundImage背景图片管理
BackgroundPosition图片定位
BackgroundToggler切换背景类型
ColoredLevelBackground调色板背景
BackgroundShape装饰形状
ImageTools图片编辑
ReplaceMedia替换媒体
FontawesomeTools图标管理
Carousel轮播控件
DynamicSnippet动态内容选项
完整选项模板示例
<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"
             data-drop-near="section">

            
            <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-name="bg_color_opt"
                            data-select-style="true"
                            data-css-property="background-color"/>

            
            <we-range string="Spacing"
                      data-name="spacing_opt"
                      data-select-style="true"
                      data-css-property="gap"
                      data-min="0"
                      data-max="100"
                      data-step="5"
                      data-unit="px"/>

            
            <we-select string="Shadow Size"
                       data-name="shadow_size_opt"
                       data-dependencies="shadow_opt">
                <we-button data-select-class="shadow-sm">Small</we-button>
                <we-button data-select-class="shadow-lg">Large</we-button>
            </we-select>

        </div>
    </xpath>
</template>

📋 创建自定义片段(版本适配)
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_mega_menu超级菜单//div[@id='snippet_mega_menu']
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']

📋 片段 JavaScript 集成
片段的 publicWidget 模式
/** @odoo-module **/

import publicWidget from "@web/legacy/js/public/public_widget";

publicWidget.registry.MySnippet = publicWidget.Widget.extend({
    selector: '.s_my_snippet',
    disabledInEditableMode: false,  // Allow in website builder

    events: {
        'click .load-more': '_onLoadMore',
    },

    start: function () {
        // CRITICAL: Check editableMode for builder compatibility
        if (!this.editableMode) {
            this._initializeAnimations();
        }
        return this._super(...arguments);
    },

    _initializeAnimations: function () {
        // Animation code that should NOT run in edit mode
        this.$el.addClass('animated');
    },

    _onLoadMore: function (ev) {
        ev.preventDefault();
        if (this.editableMode) return;  // Don't run in edit mode
        // Handler logic
    },

    destroy: function () {
        // CRITICAL: Clean up event listeners
        $(window).off('.mySnippet');
        this._super(...arguments);
    },
});

export default publicWidget.registry.MySnippet;
片段选项 JavaScript 处理程序
/** @odoo-module **/

import options from "@web_editor/js/editor/snippets.options";

options.registry.MySnippetOption = options.Class.extend({
    /**
     * Handle data attribute changes
     */
    selectDataAttribute: function(previewMode, widgetValue, params) {
        this._super(...arguments);
        if (params.attributeName === 'layout') {
            this._applyLayout(widgetValue);
        }
    },

    _applyLayout: function(layout) {
        this.$target.removeClass('layout-grid layout-list');
        this.$target.addClass(`layout-${layout}`);
    },

    /**
     * Compute option visibility
     */
    _computeWidgetVisibility: function(methodName, params) {
        if (methodName === 'showAdvanced') {
            return this.$target.hasClass('advanced-mode');
        }
        return this._super(...arguments);
    },
});

📋 片段的版本差异
功能Odoo 14-15Odoo 16-17Odoo 18-19
片段数量~4081+155+
注册方式简单 XPath简单 XPath必须使用分组
资源系统模板继承ir.asset 模型ir.asset 模型
JavaScriptodoo.define()ES6 模块基于插件
Bootstrap4.x5.1.35.1.3
动态片段基础完整完整 + 分类
编辑器旧版旧版插件架构
Odoo 19 插件模式(新增)
import { Plugin } from "@html_editor/plugin";
import { registry } from "@web/core/registry";

export class MySnippetPlugin extends Plugin {
    static id = "mySnippet";
    static dependencies = ["builderOptions", "builderActions"];

    resources = {
        builder_options: [{
            template: "my_module.MySnippetOption",
            selector: "section",
            applyTo: ".s_my_snippet",
        }],
        builder_actions: {
            MyAction: { /* action definition */ },
        },
    };
}

registry.category("website-plugins").add("mySnippet", MySnippetPlugin);

📋 创建动态片段
步骤 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>

<record id="ir_filter_my_items" model="ir.filters">
    <field name="name">Latest Items</field>
    <field name="model_id">my.model</field>
    <field name="domain">[('is_published', '=', True)]</field>
    <field name="sort">create_date desc</field>
</record>
步骤 2:创建显示模板
<template id="dynamic_filter_template_my_model_card" name="My Model 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"/>
                    <p class="card-text" t-esc="record.description"/>
                </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-name="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"
             data-number-of-elements-small-devices="2">
        <div class="container">
            <div class="row">
                <h2 class="col-12 text-center mb-4">Featured Items</h2>
            </div>
            <div class="dynamic_snippet_template row"/>
        </div>
    </section>
</template>

更新后的 /create-theme 命令(v6.0)

/create-theme 命令现在会自动生成 models/theme_xxx.py 文件,并激活模板。

创建的内容(v6.0)
theme_<name>/
├── __init__.py                        # Imports models
├── __manifest__.py                    # Updated with models/ import
├── models/
│   ├── __init__.py                   # Imports theme_<name>
│   └── theme_<name>.py               # theme.utils implementation (NEW!)
├── security/
│   └── ir.model.access.csv
├── data/
│   ├── assets.xml
│   ├── menu.xml
│   └── pages/
│       ├── home_page.xml
│       ├── aboutus_page.xml
│       └── contactus_page.xml
├── views/
│   ├── layout/
│   │   └── templates.xml
│   └── snippets/
│       └── custom_snippets.xml
└── static/src/
    ├── scss/
    │   ├── primary_variables.scss
    │   └── theme.scss
    ├── js/
    │   └── theme.js
    └── img/
生成的 theme_.py
from odoo import models


class Theme<Name>(models.AbstractModel):
    _inherit = 'theme.utils'

    def _theme_<name>_post_copy(self, mod):
        """
        Configure theme defaults when installed on a website.

        This method is called automatically when the theme is applied.
        Use enable_view() and disable_view() to configure templates.
        """
        # === HEADER CONFIGURATION ===
        # Enable desired header template (only one can be active)
        self.enable_view('website.template_header_<detected>')

        # Configure header visibility
        self.disable_view('website.header_visibility_standard')
        self.enable_view('website.header_visibility_fixed')

        # Enable header components
        self.enable_view('website.option_header_brand_logo')

        # === FOOTER CONFIGURATION ===
        # Enable desired footer template (only one can be active)
        self.enable_view('website.template_footer_<detected>')

更新日志

  • v7.0.0:网站片段完整参考(重大更新)

    • 新增:完整的静态片段清单(81+ 个模板)
      • 结构片段(s_banner、s_cover、s_parallax 等)
      • 功能片段(s_features、s_comparisons、s_image_text 等)
      • 动态内容片段(s_dynamic_snippet_*、products、blog、events)
      • 内部内容片段(s_hr、s_badge、s_card、s_blockquote 等)
      • 超级菜单片段(s_mega_menu_*、multi_menus、odoo_menu)
    • 新增:动态片段系统文档
      • website.snippet.filter 模型架构
      • 15 个产品展示模板(dynamic_filter_template_*)
      • 6 个内置产品片段过滤器
      • 博客和活动的动态片段模式
    • 新增:片段选项系统参考
      • 完整的 we-* 元素(we-select、we-button、we-colorpicker、we-input、we-range、we-checkbox)
      • 数据属性参考(data-selector、data-select-class、data-js、data-css-property、data-dependencies)
      • 内置 JavaScript 处理器(BackgroundImage、Carousel、ImageTools 等)
      • 完整的片段选项 XML 示例
    • 新增:版本感知型自定义片段创建
      • Odoo 14-17 简单的 t-snippet 注册
      • Odoo 18-19 片段组模式
      • XPath 插入技术
    • 新增:Odoo 19 插件模式文档
      • 基于 @html_editor/plugin 的架构
      • 130+ 个插件文件参考
      • SnippetOption 类模式
    • 新增:创建动态片段指南
      • 定义过滤器(website.snippet.filter)
      • 创建展示模板(dynamic_filter_template_*)
      • 使用 data-filter-id 创建动态片段
  • v6.0.0:主题功能激活系统与动态页面参考(重大更新)

    • 新增:主题功能激活系统
      • 完整的 theme.utils 模型文档
      • _theme_{module}_post_copy() 模式要求
      • enable_view() 和 disable_view() 方法参考
      • 页眉/页脚互斥规则
      • 完整的可运行示例
    • 新增:完整的动态页面参考
      • 11 个页眉模板,附带 XML ID 和可视化示意图
      • 9 个页脚模板,附带 XML ID 和可视化示意图
      • 所有商店页面模板(类别、筛选器、布局)
      • 所有产品详情页模板
      • 所有博客模板(列表、详情、侧边栏)
      • 购物车和结账模板
    • 新增:设计工作流方法
      • Figma 分析提取指南
      • 模板匹配决策流程图
      • 根据设计生成配置
      • 分步工作流文档
    • 更新:/create-theme 命令 v6.0
      • 现在会自动生成 models/theme_xxx.py
      • 根据配置激活模板
      • 包含所有必需文件的完整模块结构
  • v5.1.0:全面增强变量参考(重大更新)

    • 完整的 $o-theme-font-configs 参考:
      • 'family':CSS font-family 列表(元组格式)
      • 'url':仅限 Google Fonts 参数(不是完整 URL)
      • 'properties':按上下文设置 CSS 覆盖项(base、headings、navbar、buttons)
      • 字体别名文档:'base'、'headings'、'h2'-'h6'、'navbar'、'buttons'
      • 阿拉伯语/RTL 字体支持示例
    • 完整的 $o-color-palettes 参考:
      • 5 种核心颜色的语义含义(o-color-1 至 o-color-5)
      • 颜色组合(o_cc1 - o_cc5)预设和用法
      • 颜色组合覆盖语法('o-cc{n}-{property}')
      • 组件分配(菜单、页脚、版权信息)
      • 使用颜色类的 HTML 示例
    • 完整的 $o-website-values-palettes 参考(115+ 个键):
      • 字体排版和字体(13 个键):字体系列配置
      • 字体大小(13 个键):基础和标题字号
      • 行高和边距(33 个键):间距配置
      • 按钮(17 个键):内边距、圆角、样式选项
      • 输入框(12 个键):表单字段样式
      • 页眉(13 个键):模板、链接样式、徽标、汉堡菜单
      • 页脚(3 个键):模板、效果、回到顶部
      • 链接(1 个键):下划线行为
      • 布局(3 个键):全宽/固定宽度、背景
      • 颜色和渐变(5 个键):调色板选择、渐变
      • Google Fonts(2 个键):附加字体
    • 恢复完整主题结构:header.xml、footer.xml、片段(可选,但包含在层级结构中)
    • 删除冗余文档章节:清理重复内容
  • v5.0.0:基于全面的变量系统分析进行重大增强

    • 无需自定义 XML,即可通过变量控制页眉/页脚模板
    • 通过 'header-template' 和 'footer-template' 变量进行配置
    • 通过 $o-website-values-palettes 控制 Bootstrap
  • v4.0.0:根据实际主题开发问题进行关键修复

    • ⚠️ SCSS 加载顺序文档:说明主题 SCSS 在核心变量之前加载
    • 移除 map-merge() 模式:修复使用 map-merge() 与核心变量的示例(这会导致“Undefined variable”错误)
    • 字体加载修复:将 $o-theme-font-configs 用作独立配置,而不是通过 ir.asset 记录加载
    • XPath 简化:使用简单表达式(//header),而不是复杂表达式(//header//nav)
    • 增强故障排查:添加 SCSS 调试、资产缓存清理和静默编译失败相关内容
    • 修正所有模板:现在 /create-theme 命令生成的代码可正常运行,不会出现 map-merge 问题
  • v3.1.0:添加 /create-theme 命令,根据 40+ 个实际实现生成完整主题

    • 完整的 $o-website-values-palettes 配置
    • 语义颜色系统(o-color-1 至 o-color-5)
    • 单独页面文件模式(最佳实践)
    • 处理 editableMode 的 publicWidget 模式
    • 特定版本的片段注册(14-19)
    • 修复脚本输出的 Windows 控制台编码问题
  • v3.0.0:增强主题系统,加入 $o-website-values-palettes 参考和镜像模型架构

  • v2.0.0:新增 PWA 支持、TypeScript、测试框架、性能优化、无障碍功能、实时功能和现代构建工具

  • v1.0.0:首次发布,完整支持 Odoo 14-19,并集成 Figma/DevTools MCP

评论

支持 Markdown