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>
相关推荐
Ws_3 小时前
leetcode LCR 068 搜索插入位置
数据结构·python·算法·leetcode
lx学习3 小时前
Python学习26天
开发语言·python·学习
qq_273900234 小时前
pytorch register_buffer介绍
人工智能·pytorch·python
大今野4 小时前
python习题练习
开发语言·python
camellias_5 小时前
SpringBoot(二十三)SpringBoot集成JWT
java·spring boot·后端
tebukaopu1485 小时前
springboot如何获取控制层get和Post入参
java·spring boot·后端
昔我往昔5 小时前
SpringBoot 创建对象常见的几种方式
java·spring boot·后端
q567315235 小时前
用 PHP或Python加密字符串,用iOS解密
java·python·ios·缓存·php·命令模式
灭掉c与java5 小时前
第三章springboot数据访问
java·spring boot·后端
啊松同学5 小时前
【Java】设计模式——工厂模式
java·后端·设计模式