Odoo Web框架的Layout组件

Odoo客户端的展示区域大部分使用一个通用布局,顶部是一个控制面板,面板下方就是主要内容的展示区域。这个布局使用Layout组件来实现,Layout是一个通用组件,可以通过t-set-slot指令向Layout组件中插入特定内容。

  • t-set-slot指令插入的内容实际上是插入到控制面板组件中。Layout组件只接收默认插槽插入的内容(主要内容)
  • Layout的源码位于/web/static/src/search

组件应用场景

  • 在视图中使用,例如form、list、kanban...
  • 在action中使用,例如ReportAction

组件解析

xml 复制代码
<t t-name="web.Layout" owl="1">
    <!-- 如果在Dialog组件中使用Layout,t-set-slot插入的内容会被挂载(t-portal指令)到Dialog的底部 -->
    <!-- 如果不是在Dialog中,会插入面板组件的control-panel-bottom-left-buttons -->
    <t t-if="env.inDialog" t-portal="'#' + env.dialogId + ' .modal-footer'">
        <t t-slot="layout-buttons"/>
    </t>
    <!-- 接收的控制面板组件,这个组件是动态组件,支持对控制面板的扩展 -->
    <t t-component="components.ControlPanel" slots="controlPanelSlots" t-if="display.controlPanel" display="display.controlPanel"/>
    <t t-component="components.Banner" t-if="display.banner"/>
    <div t-ref="content" class="o_content" t-attf-class="{{props.className}}" t-att-class="{ o_component_with_search_panel: display.searchPanel }">
        <t t-component="components.SearchPanel" t-if="display.searchPanel"/>
        <!-- 默认插槽,主要内容展示区域 -->
        <t t-slot="default" contentRef="contentRef" />
    </div>
</t>
js 复制代码
// ControlPanel默认使用"@web/search/control_panel/control_panel"中定义的面板组件
// 可以使用自定义的控制面板组件,比如form视图使用FormControlPanel
export function extractLayoutComponents(params) {
    // 从对象中取出"ControlPanel", "SearchPanel", "Banner"对应的组件类
    return pick(params, "ControlPanel", "SearchPanel", "Banner");
}

export class Layout extends Component {
    setup() {
        this.components = extractLayoutComponents(this.env.config);
        this.contentRef = useRef("content");
    }
    get controlPanelSlots() {
        // 通过t-set-slot向Layout组件插入的内容都会通过slots插入到控制面板组件       
        const slots = { ...this.props.slots };
        // 注意这里Layout插入的layout-buttons,会被转换为control-panel-bottom-left-buttons
        // 默认面板组件的模板中还有一个control-panel-bottom-left插槽,也可以接收一些按钮,例如list视图中,选择记录后会在面板底部左侧渲染一些新的按钮
        slots["control-panel-bottom-left-buttons"] = slots["layout-buttons"];
        delete slots["layout-buttons"];
        delete slots.default;
        return slots;
    }
    ...
}

组件的使用

ReportAction中使用Layout组件

xml 复制代码
<t t-name="web.ReportAction" owl="1">
    <div class="o_action">
        <!-- 隐藏控制面板的右上与右下部分 -->
        <Layout display="{ controlPanel: { 'top-right' : false, 'bottom-right': false } }">
            <!-- 布局中没有插入layout-buttons,因此面板的control-panel-bottom-left-buttons插槽不会有内容 -->
            <t t-set-slot="control-panel-bottom-left">
                <button t-on-click="print" type="button" class="btn btn-primary" title="Print">Print</button>
            </t>
            <!-- 插入到默认插槽 -->
            <iframe t-ref="iframe" t-on-load="onIframeLoaded" class="h-100 w-100" t-att-src="reportUrl" />
        </Layout>
    </div>
</t>

控制面板的布局

控制面板的扩展

控制面板是Layout组件的重要组成部分,开发者可以扩展控制面板,实现特定的展示效果。

FormControlPanel

form视图中对控制面板进行了扩展,为控制面板添加了面包屑导航、动作按钮、分页等功能

js 复制代码
import { ControlPanel } from "@web/search/control_panel/control_panel";

export class FormControlPanel extends ControlPanel {}
// 使用新模板替换原模板
FormControlPanel.template = "web.FormControlPanel";
xml 复制代码
<t t-name="web.FormControlPanel" owl="1">
    <div class="o_control_panel" t-ref="root">
        <div t-if="display['top']" class="o_cp_top" t-att-class="{ 'flex-wrap': env.isSmall }">
            <div class="o_cp_top_left d-flex flex-grow-1 align-items-center" t-att-class="{ 'w-100': env.isSmall }">
                <t t-if="display['top-left']">
                    <!-- 面板屑导航 -->
                    <t t-slot="control-panel-breadcrumb">
                        <t t-if="env.isSmall">                           
                            <t t-call="web.Breadcrumbs.Small" t-if="!env.config.noBreadcrumbs"/>
                        </t>
                        <t t-else="">
                            <!-- 这里使用的是子模板,子模板是内联在主模板中的,因此子模板也会接收到Layout通过slots插入到面板组件的内容 -->
                            <!-- "web.Breadcrumbs子模板中定义了插槽<t t-slot="control-panel-status-indicator" /> -->
                            <!-- 这也是为什么在FormView中使用了该插槽,面板中却找不到,这个插槽是在子模板中定义的 -->
                            <t t-call="web.Breadcrumbs" t-if="!env.config.noBreadcrumbs"/>
                        </t>
                    </t>
                </t>
            </div>
            <div class="o_cp_bottom_right w-auto flex-shrink-0 justify-content-between align-items-center"
                 t-att-class="{ 'flex-grow-1' : env.isSmall }">
                <t t-if="env.isSmall">
                    <!-- 移动端生效 -->
                    <t t-slot="control-panel-status-indicator" />
                </t>
                <!-- 动作 -->
                <t t-slot="control-panel-action-menu" t-if="display['bottom-left']"/>
                <div t-if="pagerProps and pagerProps.total > 0" class="o_cp_pager" role="search">
                    <Pager t-props="pagerProps"/>
                </div>
                <!-- 新建 -->
                <t t-slot="control-panel-create-button" />
            </div>
        </div>
    </div>
</t>

form视图的模板中向扩展后的控制面板插入内容

  • 这里通过t-set-slot插入到Layout组件的内容都会通过slots转插到面板中
  • 只有Render会插入到Layout的默认插槽
xml 复制代码
<t t-name="web.FormView" owl="1">
    <div t-att-class="className" t-ref="root">
        <div class="o_form_view_container">
            <Layout className="model.useSampleModel ? 'o_view_sample_data' : ''" display="display">
                <!-- 插入到form view dialog 不会插入到form的控制面板 -->
                <t t-set-slot="layout-buttons">
                    <t t-if="footerArchInfo and env.inDialog">
                        <t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="footerArchInfo" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons"/>
                    </t>
                    <t t-else="">                        
                        <t t-call="{{ props.buttonTemplate }}"/>
                    </t>
                </t>
                <t t-set-slot="control-panel-action-menu">
                    <t t-if="props.info.actionMenus">
                        <ActionMenus ... />
                    </t>
                </t>
                <!-- control-panel-status-indicator对应的内容会插入到子模板 web.Breadcrumbs -->
                <t t-set-slot="control-panel-status-indicator">
                    <t t-if="canEdit">
                        <FormStatusIndicator model="model" discard.bind="discard" save.bind="saveButtonClicked" isDisabled="state.isDisabled" fieldIsDirty="state.fieldIsDirty" />
                    </t>
                </t>
                <t t-set-slot="control-panel-create-button">
                    <t t-if="canCreate">
                        <button type="button" class="btn btn-outline-primary o_form_button_create" data-hotkey="c" t-on-click.stop="create" t-att-disabled="state.isDisabled">New</button>
                    </t>
                </t>
                <t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="archInfo" setFieldAsDirty.bind="setFieldAsDirty" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons" onNotebookPageChange.bind="onNotebookPageChange" activeNotebookPages="props.state and props.state.activeNotebookPages"/>
            </Layout>
        </div>
    </div>
</t>
相关推荐
风象南8 分钟前
SpringBoot 时间轮实现延时任务
后端
Victor35612 分钟前
Redis(93)Redis的数据加密机制是什么?
后端
Victor35615 分钟前
Redis(92)如何配置Redis的ACL?
后端
有你有我OK1 小时前
springboot Admin 服务端 客户端配置
spring boot·后端·elasticsearch
xiaoopin3 小时前
简单的分布式锁 SpringBoot Redisson‌
spring boot·分布式·后端
万邦科技Lafite5 小时前
京东按图搜索京东商品(拍立淘) API (.jd.item_search_img)快速抓取数据
开发语言·前端·数据库·python·电商开放平台·京东开放平台
丁浩6666 小时前
Python机器学习---6.集成学习与随机森林
python·随机森林·机器学习
charlie1145141917 小时前
现代 Python 学习笔记:Statements & Syntax
笔记·python·学习·教程·基础·现代python·python3.13
你的人类朋友8 小时前
设计模式有哪几类?
前端·后端·设计模式
Yeats_Liao9 小时前
Go Web 编程快速入门 10 - 数据库集成与ORM:连接池、查询优化与事务管理
前端·数据库·后端·golang