Блог

Как использовать сервис "Нижняя часть тела" на веб-сайте Odoo 19

post-title

Что такое подстилка под дно?

Нижний экран — это модальное диалоговое окно, которое появляется снизу окна просмотра. В Odoo 19 компонент «Нижний экран» обеспечивает плавную анимацию, взаимодействие при прокрутке и автоматическое закрытие.

Обзор архитектуры

Наша реализация включает в себя:

  1. Компонент OWL : Содержимое, отображаемое в нижнем листе.
  2. Класс Interaction : обрабатывает взаимодействие с пользователем (заменяет publicWidget в Odoo 19)
  3. Сервис по обслуживанию нижних простыней : управление жизненным циклом.
  4. Контроллер : Обслуживает веб-страницу
  5. Шаблон : Отображает страницу с кнопками-триггерами.

Шаг 1: Создайте компонент OWL.

Создайте компонент карточки товара, который будет отображаться в нижней части листа.

Файл: static/src/components/product_card/product_card.js

/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
export class ProductCard extends Component {
    static template = "bottom_sheet_test.ProductCard";
    static props = {
        product: Object,
    };
}

Файл: static/src/components/product_card/product_card.xml

<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
    <t t-name="bottom_sheet_test.ProductCard">
        <div class="product-card-sheet">
            <!-- Product Image -->
            <div class="product-image">
                <img t-att-src="props.product?.image or '/web/static/img/placeholder.png'"
                     class="img-fluid"
                     alt="Product"/>
            </div>
            <!-- Product Info -->
            <div class="product-info">
                <h3 class="product-title" t-esc="props.product?.name or 'Product Name'"/>
                <div class="product-price">
                    <span class="currency">$</span>
                    <span class="amount" t-esc="props.product?.price or '0.00'"/>
                </div>
                <p class="product-description" t-esc="props.product?.description or 'Product description goes here.'"/>
            </div>
        </div>
    </t>
</templates>

Файл: static/src/components/product_card/product_card.scss

.product-card-sheet {
    display: flex;
    flex-direction: column;
    min-height: 400px;
    background: white;
    .product-image {
        padding: 0 2rem;
        text-align: center;
        img {
            max-height: 200px;
            object-fit: contain;
            border-radius: 12px;
        }
    }
    .product-info {
        padding: 2rem;
        flex: 1;
        .product-title {
            font-size: 1.75rem;
            font-weight: 700;
            margin-bottom: 0.5rem;
            color: #1a1a1a;
        }
        .product-price {
            font-size: 2rem;
            font-weight: 700;
            color: #875a7b;
            margin-bottom: 1rem;
            .currency {
                font-size: 1.5rem;
            }
        }
        .product-description {
            color: #666;
            line-height: 1.6;
            margin-bottom: 1.5rem;
        }
      
    }
}

Шаг 2: Создайте класс взаимодействия.

Класс Interaction — это новый способ обработки взаимодействий с DOM в Odoo 19, заменяющий publicWidget .

Файл: static/src/interactions/bottom_sheet_interaction.js

/** @odoo-module **/
import { registry } from '@web/core/registry';
import { Interaction } from "@web/public/interaction";
import { ProductCard } from '../components/product_card/product_card';
export class BottomSheetInteraction extends Interaction {
    static selector = ".bottom-sheet-demo-page";
    dynamicContent = {
        ".trigger-product-sheet": { "t-on-click": this.onTriggerProductSheet },
    };

    setup() {
        super.setup();
        // Get the bottom sheet service from the Odoo service registry
        this.bottomSheetService = this.services.bottom_sheet;
    }
    /**
     * Trigger product details bottom sheet
     */
    onTriggerProductSheet(ev) {
        const productData = {
            name: ev.target.dataset.productName || "Premium Wireless Headphones",
            price: ev.target.dataset.productPrice || "299.99",
            description: ev.target.dataset.productDescription ||
                "High-quality wireless headphones with active noise cancellation, premium sound quality, and 30-hour battery life. Perfect for music lovers and professionals.",
            image: ev.target.dataset.productImage || "/web/static/img/placeholder.png",
        };
        this.bottomSheetService.add(
            ev.target,
            ProductCard,
            {
                product: productData,
            },
            {
                class: "product-bottom-sheet",
                onClose: () => {
                    console.log("Product bottom sheet closed");
                },
            }
        );
    }
}
registry.category("public.interactions").add("BottomSheetInteraction", BottomSheetInteraction);

Шаг 3: Создайте контроллер

Контроллер обрабатывает HTTP-запрос и отображает шаблон.

Файл: controllers/main.py

from odoo import http
from odoo.http import request

class BottomSheetController(http.Controller):
    @http.route('/bottom-sheet-demo', type='http', auth='public', website=True)
    def bottom_sheet_demo(self, **kwargs):
        """Render the bottom sheet demo page"""
        products = request.env['product.product'].search_read([],['name', 'list_price', 'description'], limit=3)
        return request.render('your_module.bottom_sheet_demo_page', {
            'products': products,
        })

Шаг 4: Создайте шаблон

Создайте шаблон QWeb с кнопками для запуска нижних всплывающих окон.

Файл: views/bottom_sheet_demo.xmlBottm Shee Demo

 
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <template id="bottom_sheet_demo_page" name="Bottom Sheet Demo Page">
        <t t-call="website.layout">
            <div id="wrap" class="oe_structure bottom-sheet-demo-page">
                <section class="py-5" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
                    <div class="container">
                        <div class="row">
                            <div class="col-lg-8 mx-auto text-center">
                                <h1 class="display-4 mb-3">Bottom Sheet Demo</h1>
                                <p class="lead">Click any product to view details in a beautiful bottom sheet</p>
                            </div>
                        </div>
                    </div>
                </section>
                <section class="container py-5">
                    <div class="row g-4">
                        <t t-foreach="products" t-as="product">
                            <div class="col-md-4">
                                <div class="card h-100 shadow-sm">
                                    <img t-attf-src="/web/image?model=product.product&amp;id={{product['id']}}&amp;field=image_1920"
                                         class="card-img-top"
                                         alt="Product"
                                         style="height: 200px; object-fit: cover;"/>
                                    <div class="card-body">
                                        <h5 class="card-title" t-esc="product['name']"/>
                                        <p class="card-text text-muted" t-esc="product['description']"/>
                                        <div class="d-flex justify-content-between align-items-center">
                                            <span class="h4 mb-0 text-primary">
                                                $<t t-esc="product['list_price']"/>
                                            </span>
                                            <button class="btn btn-primary trigger-product-sheet"
                                                    t-att-data-product-name="product['name']"
                                                    t-att-data-product-price="product['list_price']"
                                                    t-att-data-product-description="product['description']"
                                                    t-attf-data-product-image="/web/image?model=product.product&amp;id={{product['id']}}&amp;field=image_1920">
                                                View Details
                                            </button>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </t>
                    </div>
                    <!-- Info Section -->
                    <div class="row mt-5">
                        <div class="col-lg-8 mx-auto">
                            <div class="alert alert-info" role="alert">
                                <h5 class="alert-heading">
                                    <i class="fa fa-info-circle"/> How It Works
                                </h5>
                                <p class="mb-2">Bottom sheets can be dismissed by:</p>
                                <ul class="mb-0">
                                    <li>Scrolling down past the dismiss threshold</li>
                                    <li>Pressing the ESC key</li>
                                    <li>Using the browser back button</li>
                                    <li>Clicking the close button or action buttons</li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </section>
            </div>
        </t>
    </template>
    <!-- Menu Item -->
    <record id="menu_bottom_sheet_demo" model="website.menu">
        <field name="name">Bottom Sheet Demo</field>
        <field name="url">/bottom-sheet-demo</field>
        <field name="parent_id" ref="website.main_menu"/>
        <field name="sequence" type="int">99</field>
    </record>
</odoo>

Шаг 6: Манифест модуля

Обновите файл __manifest__.py :

Файл: __manifest__.py

{
    'name': 'Bottom Sheet Demo',
    'version': '1.0',
    'category': 'Website',
    'summary': 'Bottom Sheet with Interaction Class in Odoo 19',
    'depends': ['website'],
    'data': [
        'views/bottom_sheet_demo.xml',
    ],
    'assets': {
        'web.assets_frontend': [
            'your_module/static/src/components/product_card/product_card.xml',
            'your_module/static/src/components/product_card/product_card.js',
            'your_module/static/src/interactions/bottom_sheet_interaction.js',
        ],
    },
    'installable': True,
    'application': False,
    'license': 'LGPL-3',
}

Основные отличия от publicWidget:

  • Расширяет класс Interaction вместо publicWidget.Widget.
  • Использует свойство dynamicContent для привязки событий.
  • Доступ к услугам осуществляется через this.services
  • Более чистый и современный API

Основные характеристики

  • Современный класс взаимодействия (без publicWidget )
  • Красивый и функциональный компонент карточки товара.
  • Плавная анимация и переходы.
  • Адаптивный дизайн
  • Множественные методы увольнения
  • Данные передаются через атрибуты данных HTML.

Заключение

Данная реализация успешно демонстрирует, как использовать новый класс Interaction в Odoo 19 и встроенную службу Bottom Sheet для создания современного, ненавязчивого компонента пользовательского интерфейса. Заменив publicWidget на класс Interaction и интегрировав наш компонент OWL, мы создали чистый, поддерживаемый и высокоэффективный шаблон. Это рекомендуемый подход для предоставления привлекательной, удобной для мобильных устройств контекстной информации на веб-сайтах Odoo 19, значительно улучшающий пользовательский опыт за счет удержания пользователей на одной и той же странице.