Что такое подстилка под дно?
Нижний экран — это модальное диалоговое окно, которое появляется снизу окна просмотра. В Odoo 19 компонент «Нижний экран» обеспечивает плавную анимацию, взаимодействие при прокрутке и автоматическое закрытие.
Обзор архитектуры
Наша реализация включает в себя:
- Компонент OWL : Содержимое, отображаемое в нижнем листе.
- Класс Interaction : обрабатывает взаимодействие с пользователем (заменяет publicWidget в Odoo 19)
- Сервис по обслуживанию нижних простыней : управление жизненным циклом.
- Контроллер : Обслуживает веб-страницу
- Шаблон : Отображает страницу с кнопками-триггерами.
Шаг 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&id={{product['id']}}&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&id={{product['id']}}&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, значительно улучшающий пользовательский опыт за счет удержания пользователей на одной и той же странице.