メインコンテンツにスキップ

Odoo テーマ開発ガイド: Odoo 17/18 のカスタム テーマの構築

QWeb テンプレートを使用した Odoo テーマの開発、SCSS カスタマイズ、レスポンシブ デザイン、アセット管理、Odoo 17/18 の Web サイト ビルダー統合について学びます。

E
ECOSIRE Research and Development Team
|2026年3月23日11 分で読める2.0k 語数|

Odoo テーマのエコシステムは、Odoo App Store で年間 1,200 万ドル以上を生み出していますが、5 つ星の評価を獲得しているテーマは 8% 未満です。忘れられないテーマとベストセラーの違いは、Odoo のレンダリング パイプライン、QWeb テンプレートの継承、および Web サイト ビルダーのドラッグ アンド ドロップ アーキテクチャを理解することによって決まります。

Odoo テーマのエコシステムは、Odoo App Store で年間 1,200 万ドル以上を生み出していますが、5 つ星の評価を獲得しているテーマは 8% 未満です。忘れられないテーマとベストセラーの違いは、Odoo のレンダリング パイプライン、QWeb テンプレートの継承、および Web サイト ビルダーのドラッグ アンド ドロップ アーキテクチャを理解することによって決まります。

このガイドでは、プロジェクトのスキャフォールディングから高度な SCSS アーキテクチャ、カスタム スニペットの構築から Odoo の Web サイト ビルダーでテーマをシームレスに動作させるまで、Odoo テーマ開発のあらゆる層をカバーしています。

重要なポイント

  • Odoo テーマは、特定のディレクトリ構造を持つ標準モジュールです。website モジュールを拡張し、アセット バンドル システムを介してテンプレート、SCSS、および JavaScript を登録します。
  • QWeb は Odoo のサーバー側テンプレート エンジンです - HTML にコンパイルする XML ディレクティブ (t-if、t-foreach、t-call、t-inherit) を使用します。 XPath によるテンプレートの継承を理解することは、Web サイトのあらゆる部分をカスタマイズするために不可欠です。
  • SCSS パイプライン は Odoo のアセット マネージャーを通じてコン​​パイルされます。カスタム変数は Bootstrap 5 のデフォルトをオーバーライドし、フレームワークの前に _variables.scss をロードする必要があります。
  • Web サイト ビルダーの統合 では、スニペット オプション、カスタム ビルディング ブロック、および動的コンテンツ ブロックを定義する必要があり、技術者以外のユーザーはドラッグ アンド ドロップ エディターを使用してこれらを構成できます。
  • Odoo のレスポンシブ デザイン は、Odoo 固有のユーティリティを備えた Bootstrap 5 のグリッド システムを活用しています。すべてのブレークポイントにわたってテストし、国際展開向けに RTL 言語を検討します。

1. テーマモジュールの構造

Odoo テーマは、Web サイト固有の追加を加えた標準モジュール構造に従います。

theme_ecosire/
├── __init__.py
├── __manifest__.py
├── data/
│   ├── theme_data.xml           # Default pages, menus
│   └── images.xml               # Pre-loaded image library
├── static/
│   ├── description/
│   │   ├── icon.png             # Module icon (128x128)
│   │   └── icon.svg             # SVG version
│   ├── src/
│   │   ├── img/                 # Theme images
│   │   ├── fonts/               # Custom fonts
│   │   ├── scss/
│   │   │   ├── _variables.scss  # Bootstrap variable overrides
│   │   │   ├── _mixins.scss     # Custom SCSS mixins
│   │   │   ├── _typography.scss # Font stacks, sizes
│   │   │   ├── _header.scss     # Header styles
│   │   │   ├── _footer.scss     # Footer styles
│   │   │   ├── _snippets.scss   # Snippet-specific styles
│   │   │   └── primary.scss     # Main entry point
│   │   ├── js/
│   │   │   ├── snippets/        # Snippet JS (animations, interactions)
│   │   │   └── theme.js         # Theme-wide JS
│   │   └── xml/
│   │       └── snippets.xml     # OWL snippet templates
│   └── img/                     # Public images (backgrounds, icons)
├── views/
│   ├── assets.xml               # Asset bundle registration
│   ├── layout.xml               # Header/footer templates
│   ├── pages.xml                # Pre-built page templates
│   └── snippets/
│       ├── options.xml          # Snippet options (color pickers, etc.)
│       └── s_custom_block.xml   # Custom snippet definitions
└── tests/
    └── test_theme.py            # Theme installation tests

テーママニフェスト

# __manifest__.py
{
    'name': 'Theme Ecosire',
    'description': 'Modern, performance-optimized theme for Odoo Website',
    'category': 'Theme',
    'version': '18.0.1.0.0',
    'author': 'ECOSIRE Private Limited',
    'website': 'https://ecosire.com',
    'license': 'LGPL-3',
    'depends': ['website'],
    'data': [
        'views/assets.xml',
        'views/layout.xml',
        'views/pages.xml',
        'views/snippets/options.xml',
        'views/snippets/s_custom_block.xml',
    ],
    'assets': {
        'web.assets_frontend': [
            # Variables MUST come before Bootstrap
            ('prepend', 'theme_ecosire/static/src/scss/_variables.scss'),
            'theme_ecosire/static/src/scss/primary.scss',
            'theme_ecosire/static/src/js/theme.js',
        ],
        'website.assets_wysiwyg': [
            'theme_ecosire/static/src/js/snippets/*.js',
        ],
    },
    'images': [
        'static/description/banner.png',
        'static/description/theme_screenshot.jpg',
    ],
    'snippet_lists': {
        'website.snippets': [
            ('replace', 'theme_ecosire.s_custom_hero'),
        ],
    },
    'installable': True,
    'application': False,
    'auto_install': False,
}

assets キーに関する重要な注意事項: Odoo 17 以降では、__manifest__.py の assets 辞書が静的ファイルを登録するための推奨される方法です。 <template inherit_id="web.assets_frontend"> を使用した古い views/assets.xml アプローチはまだ機能しますが、段階的に廃止されています。互換性を最大限に高めるには両方を使用してください。

2. QWeb テンプレートの詳細

QWeb は、Odoo の XML ベースのテンプレート エンジンです。 t- というプレフィックスが付いたディレクティブを処理し、HTML を出力します。テーマ開発においては、QWeb を理解することは譲れません。

コアディレクティブ

<!-- Conditional rendering -->
<div t-if="website.is_publisher()">
    <span>Edit mode controls here</span>
</div>
<div t-elif="request.env.user._is_internal()">
    <span>Internal user view</span>
</div>
<div t-else="">
    <span>Public visitor view</span>
</div>

<!-- Loops -->
<ul>
    <t t-foreach="website.menu_id.child_id" t-as="menu_item">
        <li t-attf-class="nav-item #{('active' if menu_item.is_active else '')}">
            <a t-att-href="menu_item.url" t-out="menu_item.name"/>
        </li>
    </t>
</ul>

<!-- Variable setting -->
<t t-set="company" t-value="res_company"/>
<t t-set="primary_color" t-value="'#FF6B00'"/>

<!-- Output (auto-escaped) -->
<span t-out="partner.name"/>

<!-- Raw HTML output (use with caution) -->
<div t-raw="sanitized_html_content"/>

<!-- Dynamic attributes -->
<div t-att-id="'section-' + str(section.id)"
     t-attf-class="container #{extra_class}"
     t-att-data-section="section.sequence"/>

<!-- Template calling -->
<t t-call="theme_ecosire.hero_section">
    <t t-set="title">Welcome to Our Store</t>
    <t t-set="subtitle">Premium Odoo Themes</t>
</t>

XPath によるテンプレートの継承

ここでテーマの開発が強力になります。既存のテンプレートを置き換えずに変更できます。

<!-- views/layout.xml -->
<template id="custom_header" inherit_id="website.layout" name="Custom Header">
    <!-- Replace the entire header -->
    <xpath expr="//header" position="replace">
        <header id="top" class="ecosire-header">
            <nav class="navbar navbar-expand-lg">
                <div class="container">
                    <a class="navbar-brand" t-att-href="'/'">
                        <img t-if="website.logo"
                             t-att-src="website.image_url(website, 'logo')"
                             alt="Logo" class="img-fluid" style="max-height: 50px;"/>
                    </a>
                    <!-- Custom mega menu -->
                    <t t-call="theme_ecosire.mega_menu"/>
                    <!-- CTA button -->
                    <a href="/contactus" class="btn btn-primary ms-3">
                        Get Started
                    </a>
                </div>
            </nav>
        </header>
    </xpath>

    <!-- Add a top bar before the header -->
    <xpath expr="//header[@id='top']" position="before">
        <div class="ecosire-topbar bg-dark text-white py-1">
            <div class="container d-flex justify-content-between">
                <span t-out="res_company.phone"/>
                <span t-out="res_company.email"/>
            </div>
        </div>
    </xpath>

    <!-- Modify footer: add a newsletter section -->
    <xpath expr="//footer" position="inside">
        <div class="ecosire-newsletter bg-primary text-white py-5">
            <div class="container text-center">
                <h3>Stay Updated</h3>
                <t t-call="website.s_newsletter_block"/>
            </div>
        </div>
    </xpath>
</template>

XPath 位置参照

ポジション効果
replace一致した要素を完全に置き換える
inside一致した要素内に追加
before一致した要素の前に挿入
after一致した要素の後に挿入
attributes一致した要素の属性を変更する
<!-- Modify attributes example -->
<xpath expr="//div[@id='wrapwrap']" position="attributes">
    <attribute name="class" add="ecosire-theme" separator=" "/>
</xpath>

<!-- Remove an element -->
<xpath expr="//div[hasclass('o_not_editable')]" position="replace"/>

3. SCSS アーキテクチャ

Odoo の SCSS パイプラインはアセット マネージャーを通じてコン​​パイルされます。つまり、ブートストラップのデフォルトの前に変数オーバーライドをロードする必要があります。マニフェストのアセット ディクショナリで「prepend」ディレクティブを使用して、_variables.scss ファイルが優先されるようにします。この 1 つのファイルは、テーマ内の色、フォント、間隔、境界線の半径、その他すべてのブートストラップ デザイン トークンを制御します。

変数のオーバーライド (_variables.scss)

// _variables.scss — loaded BEFORE Bootstrap
// Override Bootstrap 5 defaults

// Brand Colors
$o-brand-primary: #FF6B00;
$o-brand-secondary: #1A1F36;

// Bootstrap color overrides
$primary: $o-brand-primary;
$secondary: $o-brand-secondary;
$success: #00C853;
$info: #2196F3;
$warning: #FFB300;
$danger: #FF1744;

// Typography
$font-family-sans-serif: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
$font-family-monospace: 'JetBrains Mono', 'Fira Code', monospace;
$font-size-base: 1rem;
$headings-font-weight: 700;
$headings-line-height: 1.2;

// Spacing
$spacer: 1rem;

// Border radius
$border-radius: 0.5rem;
$border-radius-lg: 1rem;
$border-radius-pill: 50rem;

// Shadows
$box-shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
$box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
$box-shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);

// Navbar
$navbar-padding-y: 1rem;
$navbar-brand-font-size: 1.5rem;

// Cards
$card-border-radius: $border-radius-lg;
$card-border-color: transparent;
$card-box-shadow: $box-shadow;

メイン スタイルシート (primary.scss)

// primary.scss — Main entry point

@import 'variables';   // Already prepended, but import for IDE support
@import 'mixins';
@import 'typography';
@import 'header';
@import 'footer';
@import 'snippets';

// ============================================
// Global Overrides
// ============================================

#wrapwrap.ecosire-theme {
    // Smooth scrolling
    scroll-behavior: smooth;

    // Better text rendering
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

// ============================================
// Section Spacing
// ============================================

.s_section {
    padding: 5rem 0;

    @media (max-width: 768px) {
        padding: 3rem 0;
    }
}

// ============================================
// Button Styles
// ============================================

.btn-primary {
    background: linear-gradient(135deg, $primary, darken($primary, 10%));
    border: none;
    padding: 0.75rem 2rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    transition: all 0.3s ease;

    &:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 15px rgba($primary, 0.4);
    }
}

// ============================================
// Card Component
// ============================================

.ecosire-card {
    background: white;
    border-radius: $border-radius-lg;
    overflow: hidden;
    transition: all 0.3s ease;

    &:hover {
        transform: translateY(-4px);
        box-shadow: $box-shadow-lg;
    }

    &__image {
        aspect-ratio: 16 / 9;
        object-fit: cover;
        width: 100%;
    }

    &__body {
        padding: 1.5rem;
    }
}

カスタム フォントの読み込み

// _typography.scss

@font-face {
    font-family: 'Inter';
    src: url('/theme_ecosire/static/src/fonts/Inter-Variable.woff2') format('woff2');
    font-weight: 100 900;
    font-display: swap;
    font-style: normal;
}

@font-face {
    font-family: 'Inter';
    src: url('/theme_ecosire/static/src/fonts/Inter-Variable-Italic.woff2') format('woff2');
    font-weight: 100 900;
    font-display: swap;
    font-style: italic;
}

// Heading hierarchy
h1, .h1 { font-size: 2.5rem; font-weight: 800; }
h2, .h2 { font-size: 2rem; font-weight: 700; }
h3, .h3 { font-size: 1.5rem; font-weight: 600; }
h4, .h4 { font-size: 1.25rem; font-weight: 600; }

4. カスタム スニペット (構成要素)

スニペットは、Odoo の Web サイト ビルダーのドラッグ アンド ドロップの構成要素です。カスタム スニペットを作成することで、テーマが真に価値のあるものになります。

スニペット テンプレートの定義

<!-- views/snippets/s_custom_block.xml -->
<odoo>
    <!-- The snippet template -->
    <template id="s_ecosire_hero" name="Ecosire Hero Section">
        <section class="s_ecosire_hero pt-5 pb-5 o_cc o_cc1">
            <div class="container">
                <div class="row align-items-center">
                    <div class="col-lg-6">
                        <h1 class="display-4 fw-bold">
                            Transform Your Business
                        </h1>
                        <p class="lead text-muted mt-3">
                            Enterprise solutions that scale with your ambitions.
                        </p>
                        <div class="mt-4">
                            <a href="/contactus" class="btn btn-primary btn-lg me-3">
                                Get Started
                            </a>
                            <a href="/about" class="btn btn-outline-secondary btn-lg">
                                Learn More
                            </a>
                        </div>
                    </div>
                    <div class="col-lg-6">
                        <img src="/theme_ecosire/static/src/img/hero-illustration.svg"
                             alt="Hero" class="img-fluid" loading="lazy"/>
                    </div>
                </div>
            </div>
        </section>
    </template>

    <!-- Register snippet in the builder panel -->
    <template id="snippets" inherit_id="website.snippets">
        <xpath expr="//div[@id='snippet_structure']//t[@t-snippet]/.."
               position="inside">
            <t t-snippet="theme_ecosire.s_ecosire_hero"
               t-thumbnail="/theme_ecosire/static/src/img/snippets/hero_thumb.png"/>
        </xpath>
    </template>
</odoo>

スニペット オプション (ユーザーのカスタマイズ)

<!-- views/snippets/options.xml -->
<odoo>
    <template id="s_ecosire_hero_options" inherit_id="website.snippet_options">
        <xpath expr="." position="inside">
            <div data-js="EcosireHeroOptions"
                 data-selector=".s_ecosire_hero">

                <!-- Layout selector -->
                <we-select string="Layout">
                    <we-button data-select-class="s_hero_left">Text Left</we-button>
                    <we-button data-select-class="s_hero_center">Centered</we-button>
                    <we-button data-select-class="s_hero_right">Text Right</we-button>
                </we-select>

                <!-- Background style -->
                <we-select string="Background">
                    <we-button data-select-class="bg-white">White</we-button>
                    <we-button data-select-class="bg-dark text-white">Dark</we-button>
                    <we-button data-select-class="bg-gradient">Gradient</we-button>
                </we-select>

                <!-- Animation toggle -->
                <we-checkbox string="Enable Animation"
                             data-toggle-class="s_hero_animated"/>
            </div>
        </xpath>
    </template>
</odoo>

スニペット JavaScript (アニメーション)

// static/src/js/snippets/s_ecosire_hero.js
/** @odoo-module */

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

const EcosireHero = publicWidget.Widget.extend({
    selector: '.s_ecosire_hero.s_hero_animated',
    disabledInEditableMode: true,

    start() {
        this._super(...arguments);
        this._initAnimation();
        return Promise.resolve();
    },

    _initAnimation() {
        const observer = new IntersectionObserver(
            (entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        entry.target.classList.add('animate-in');
                        observer.unobserve(entry.target);
                    }
                });
            },
            { threshold: 0.2 }
        );

        this.el.querySelectorAll('.animate-target').forEach(el => {
            observer.observe(el);
        });
    },

    destroy() {
        this._super(...arguments);
    },
});

publicWidget.registry.EcosireHero = EcosireHero;

export default EcosireHero;

5. レスポンシブデザインのベストプラクティス

モバイルファースト SCSS

// _responsive.scss

// Mobile-first breakpoints (Bootstrap 5)
// xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px

.ecosire-header {
    // Mobile default
    padding: 0.5rem 0;

    .navbar-brand img {
        max-height: 35px;
    }

    .nav-link {
        padding: 0.75rem 0;
        border-bottom: 1px solid rgba(0, 0, 0, 0.05);
    }

    // Tablet and up
    @include media-breakpoint-up(md) {
        padding: 0.75rem 0;

        .nav-link {
            padding: 0.5rem 1rem;
            border-bottom: none;
        }
    }

    // Desktop
    @include media-breakpoint-up(lg) {
        padding: 1rem 0;

        .navbar-brand img {
            max-height: 50px;
        }
    }
}

// Touch-friendly elements
@media (hover: none) {
    .btn {
        min-height: 44px;
        min-width: 44px;
    }

    .nav-link {
        min-height: 44px;
        display: flex;
        align-items: center;
    }
}

RTL サポート

// _rtl.scss — Critical for Arabic, Hebrew, Urdu markets

html[dir="rtl"] {
    .ecosire-header {
        .navbar-brand {
            margin-left: 1rem;
            margin-right: 0;
        }

        .btn + .btn {
            margin-right: 0.75rem;
            margin-left: 0;
        }
    }

    .ecosire-card__body {
        text-align: right;
    }

    // Use logical properties where possible
    .ms-3 { margin-inline-start: 1rem; }
    .me-3 { margin-inline-end: 1rem; }
}

6. パフォーマンスの最適化

画像の最適化

<!-- Use responsive images with srcset -->
<img t-att-src="'/web/image/ir.attachment/%s/datas/800x400' % image.id"
     t-att-srcset="'/web/image/ir.attachment/%s/datas/400x200 400w, /web/image/ir.attachment/%s/datas/800x400 800w, /web/image/ir.attachment/%s/datas/1200x600 1200w' % (image.id, image.id, image.id)"
     sizes="(max-width: 576px) 400px, (max-width: 992px) 800px, 1200px"
     loading="lazy"
     decoding="async"
     alt="Descriptive alt text"/>

クリティカル CSS インライン化

<!-- views/layout.xml -->
<template id="critical_css" inherit_id="website.layout">
    <xpath expr="//head" position="inside">
        <style type="text/css">
            /* Inline critical above-the-fold CSS */
            .ecosire-header { min-height: 72px; }
            .s_ecosire_hero { min-height: 60vh; }
            .navbar-brand img { max-height: 50px; }
        </style>
    </xpath>
</template>

アセットロード戦略

# __manifest__.py
'assets': {
    # Critical CSS — loaded in head
    'web.assets_frontend': [
        ('prepend', 'theme_ecosire/static/src/scss/_variables.scss'),
        'theme_ecosire/static/src/scss/primary.scss',
    ],
    # Non-critical JS — deferred
    'web.assets_frontend_lazy': [
        'theme_ecosire/static/src/js/animations.js',
        'theme_ecosire/static/src/js/parallax.js',
    ],
    # Editor-only assets
    'website.assets_wysiwyg': [
        'theme_ecosire/static/src/js/snippets/*.js',
        'theme_ecosire/static/src/scss/_editor.scss',
    ],
},

7. ウェブサイトビルダーの統合

動的コンテンツブロック

<!-- A snippet that fetches products dynamically -->
<template id="s_featured_products" name="Featured Products Grid">
    <section class="s_featured_products pt-5 pb-5">
        <div class="container">
            <h2 class="text-center mb-5">Featured Products</h2>
            <div class="row" t-if="products">
                <t t-foreach="products[:8]" t-as="product">
                    <div class="col-md-3 mb-4">
                        <div class="ecosire-card h-100">
                            <img t-att-src="product.image_url"
                                 t-att-alt="product.name"
                                 class="ecosire-card__image" loading="lazy"/>
                            <div class="ecosire-card__body">
                                <h5 t-out="product.name"/>
                                <p class="text-primary fw-bold"
                                   t-out="product.list_price"
                                   t-options='{"widget": "monetary"}'/>
                                <a t-att-href="product.website_url"
                                   class="btn btn-outline-primary btn-sm">
                                    View Details
                                </a>
                            </div>
                        </div>
                    </div>
                </t>
            </div>
        </div>
    </section>
</template>

ページ テンプレート

<!-- Pre-built page templates that users can select -->
<template id="landing_page_template" name="Landing Page">
    <t t-call="website.layout">
        <div id="wrap" class="oe_structure">
            <t t-call="theme_ecosire.s_ecosire_hero"/>
            <t t-call="theme_ecosire.s_features_grid"/>
            <t t-call="theme_ecosire.s_testimonials"/>
            <t t-call="theme_ecosire.s_cta_banner"/>
        </div>
    </t>
</template>

8. テーマのテスト

# tests/test_theme.py
from odoo.tests.common import HttpCase, tagged

@tagged('post_install', '-at_install')
class TestThemeEcosire(HttpCase):

    def test_01_theme_installation(self):
        """Theme installs without errors."""
        module = self.env['ir.module.module'].search([
            ('name', '=', 'theme_ecosire')
        ])
        self.assertEqual(module.state, 'installed')

    def test_02_homepage_loads(self):
        """Homepage renders with theme applied."""
        response = self.url_open('/')
        self.assertEqual(response.status_code, 200)
        self.assertIn('ecosire-theme', response.text)

    def test_03_snippets_available(self):
        """Custom snippets appear in the builder."""
        self.start_tour('/web#action=website.action_website',
                       'theme_ecosire_snippet_tour', login='admin')

    def test_04_responsive_meta(self):
        """Viewport meta tag is present."""
        response = self.url_open('/')
        self.assertIn('viewport', response.text)

9. Odoo App Store での公開

要件詳細
スクリーンショット3 ~ 5 枚の高品質スクリーンショット (1920x1080)
アイコンstatic/description/ の 128x128 PNG
説明static/description/index.html の HTML 記述
ライセンスLGPL-3 テーマ (標準的な実践)
バージョンOdoo シリーズと一致 (18.0.x.y.z)
テストOdoo の自動リンティング チェックに合格する
デモデータ購入前に試すためのデモ データを含める

よくある質問

Odoo テーマで Tailwind CSS を使用できますか?

技術的には可能ですが、Odoo の組み込み Bootstrap 5 と一緒に Tailwind CSS を使用すると競合が発生し、バンドル サイズが大幅に増加します。推奨されるアプローチは、Odoo のアセット パイプラインおよび Web サイト ビルダーとネイティブに統合される Bootstrap 5 ユーティリティと SCSS カスタム プロパティを使用することです。 Bootstrap が提供しないユーティリティ クラスが必要な場合は、代わりにカスタム SCSS ユーティリティを作成してください。

テーマを Odoo 17 と 18 の両方と互換性を持たせるにはどうすればよいですか?

Odoo バージョンごとに個別のブランチを作成します。 Odoo 17 は Bootstrap 5.1 を使用しますが、Odoo 18 は CSS 変数をサポートする Bootstrap 5.3 を使用します。アセット登録も変更されました。Odoo 18 ではマニフェスト アセット キーが優先されますが、Odoo 17 では XML テンプレートの継承が使用されます。共有 SCSS パーシャルとバージョン固有の統合ファイルを維持します。

SCSS 変数のオーバーライドが機能しないのはなぜですか?

最も一般的な原因はロード順序です。 _variables.scss は、ブートストラップをコンパイルする前にロードする必要があります。マニフェストで 'prepend' ディレクティブを使用します: ('prepend', 'theme_name/static/src/scss/_variables.scss')。また、正しい変数名をオーバーライドしていることを確認してください。Odoo は、$o-brand-primary など、一部のブートストラップ変数を o- プレフィックス付きバージョンでラップします。

カスタム スニペットはウェブサイト ビルダーとどのように連携しますか?

カスタム スニペットは、website.snippets のテンプレート継承を通じてスニペット パネルに登録された QWeb テンプレートです。各スニペットには、HTML 構造用のテンプレート、カスタマイズ パネル用の website.snippet_options で定義されたオプション オプション、および対話機能用に publicWidget.Widget を拡張するオプションの JavaScript が必要です。ウェブサイト ビルダーは、登録されたスニペットのドラッグ アンド ドロップ、インライン編集、元に戻す/やり直しを自動的に処理します。

Odoo テーマにカスタム フォントを追加する最良の方法は何ですか?

WOFF2 フォント ファイルを static/src/fonts/ に配置し、SCSS で @font-face ルールを定義します。パフォーマンスのために font-display: swap を使用します。次に、_variables.scss 内の $font-family-sans-serif ブートストラップ変数をオーバーライドします。外部 CDN からのフォントの読み込みは避けてください。DNS ルックアップが追加され、制限されたネットワークでは失敗する可能性があります。必要な文字範囲のみを含むフォントのサブセット。

Odoo テーマでダーク モードをサポートするにはどうすればよいですか?

Odoo 18 では、CSS カスタム プロパティを通じてネイティブ ダーク モード サポートが導入されました。 [data-bs-theme="dark"] セレクターを使用して、テーマのダーク モード変数をオーバーライドします。カスタムの色、背景、影の明るいバージョンと暗いバージョンの両方を定義します。ウェブサイト ビルダーのトグルが切り替えを処理します。テーマで必要なのは、両方のモードに適切な CSS 変数値を提供することだけです。

次のステップ

テーマ開発では、デザインとエンジニアリングが融合します。適切に構築された Odoo テーマは、何百もの顧客にサービスを提供し、Odoo App Store で多額の経常収益を生み出すことができます。

関連ガイドについては:

カスタムの Odoo テーマや Web サイトの再設計が必要ですか? ECOSIRE の Odoo カスタマイズ チーム は、Web サイト ビルダーの完全な統合、すべてのブレークポイントにわたる応答性の高いデザイン、および国際展開のための RTL サポートを備えたエンタープライズ グレードのテーマを設計および構築します。無料のデザイン相談についてはお問い合わせください。

E

執筆者

ECOSIRE Research and Development Team

テクニカルライティング

ECOSIRE のテクニカルライティングチームは、Odoo ERP、Shopify による EC、AI エージェント、Power BI による分析、GoHighLevel による自動化、そしてエンタープライズソフトウェアのベストプラクティスを扱っています。私たちのガイドは、企業が十分な情報に基づいてテクノロジーを判断できるよう支援します。

ECOSIRE

Odoo ERP でビジネスを変革

Odoo の専門家による実装、カスタマイズ、サポートにより、業務を合理化します。