odoo11,12实现树状视图,odoo前端开发的实例教程,引入ztree.js

  • A+

本文涉及模块可以在此下载,收费插件。希望童鞋们能学习后自行完成。

odoo的前端,比起很多平台,界面是十分优秀的,利用现有组件也能很快的实现功能。
但一旦实际和友商PK,就会发现有很多不足,这些硬伤在PK中会大大降低你的杀伤力。

树状视图算是最重要的一个,这个该是国内国际各大厂商的标配了,在财务、分组目录、地理位置等实际应用中十分常见。所以,实现父子关系的树状视图绝对是打单的重头戏,针对odoo11,12的前端大变化,我们开发了相关的模块,包括在单条记录中使用树状选择(如目录、科目),在列表、看板中使用树状列表导航查询,全部使用odoo标准的开发方式,widget实现,简单的xml设置。

先看最终效果,list, kanban, pivot, graph 中的效果

 

field中的效果。

 

这里写下开发过程,掌握后基本可以自己开发各种 odoo 的前端增强了。

原生ztree外观比较丑,为了更符合 odooer的审美,用了 fontawsome来改造样式。

  1. 引入 ztree.js
  2. 做 qweb 页面
  3. 继承原 odoo 的 FieldMany2One 组件
  4. 在需要树状处理的相应模块写 view,继承更改字段视图。

 

ztree widget是模块核心,主要代码如下:

引入ztree

<template id="assets_backend" name="odtree assets" inherit_id="web.assets_backend">
    <xpath expr="." position="inside">
        <link rel="stylesheet" type="text/css" href="/app_web_widget_ztree/static/src/lib/ztree_v3/css/awesomeStyle/fa.less"/>
        <link rel="stylesheet" type="text/css" href="/app_web_widget_ztree/static/src/lib/ztree_v3/css/awesomeStyle/awesome.less"/>
        <link rel="stylesheet" type="text/less" href="/app_web_widget_ztree/static/src/less/views.less"/>
        <script type="text/javascript" src="/app_web_widget_ztree/static/src/lib/ztree_v3/js/jquery.ztree.core.js"/>
        <script type="text/javascript" src="/app_web_widget_ztree/static/src/js/widget_ztree.js"/>
    </xpath>
</template>

qweb template 构建需要ztree的部份

<!--常规的-->
<div t-name="App.zTree" class="ztree"
                t-att-id="widget.ztree_id"
                t-att-data-ztree_index="widget.ztree_index"
                t-att-data-ztree_field="widget.ztree_field"
                t-att-data-ztree_model="widget.ztree_model"
                t-att-data-ztree_parent_key="widget.ztree_parent_key">
</div>

<!--field 中的widget-->
<t t-name="App.FieldZtree">
    <t t-if="widget.mode === 'readonly'">
        <a t-if="!widget.nodeOptions.no_open" class="o_form_uri" href="#"/>
        <span t-if="widget.nodeOptions.no_open"/>
    </t>
    <div t-if="widget.mode === 'edit'" class="o_field_widget o_field_many2one">
        <div class="o_input_dropdown">
            <input type="text" class="o_input"
                t-att-barcode_events="widget.nodeOptions.barcode_events"
                t-att-tabindex="widget.attrs.tabindex"
                t-att-autofocus="widget.attrs.autofocus"
                t-att-placeholder="widget.attrs.placeholder"
                t-att-id="widget.idForLabel"/>
            <span class="o_dropdown_button" draggable="false"/>
        </div>
        <button type="button" t-if="!widget.nodeOptions.no_open" class="fa fa-external-link btn btn-default o_external_button" tabindex="-1" draggable="false"/>
    </div>
</t>

实现widget的代码,这个主要按正常odoo js教程,在 init, start, render 几个标准widget执行过程中继承即可,多用 _super,同时按 ztree 教程设置好 setting 参数,传参生成ztree

buildTreeView: function (search_val) {
    var self = this;
    if (self.many2one) {
        self.many2one.destroy();
        self.many2one = undefined;
    }
    var setting = {
        callback: {
            onClick: function (event, treeId, treeNode, clickFlag) {
                self._selectNode(event, treeNode);
            }
        }
    };
    self._search(search_val).then(function (result) {
        //todo: 不能默认让node selected,会出现quick_create 混乱
        if (self.value && self.value.data.id && self.value.data.id > 0)
            var ztree_selected_id = self.value.data.id;
        self.many2one = new zTree(setting, {
            zNodes: result,
            ztree_field: self.field.name,
            ztree_model: self.field.relation,
            ztree_parent_key: self.ztree_parent_key,
            ztree_expend_level: self.ztree_expend_level,
            ztree_selected_id: ztree_selected_id,
        });
        self.many2one.appendTo(self.$input.parent());
        // self.$(".ztree").replaceWith(self.many2one);
        self.$input.css('height', 'auto');
    });
},

为了更像原生的 m2o ,做了很多附加代码,主要是把原生many2one的执行函数调整,用ztree方式进行赋值和点击事件处理,比如最重要的输入后查找过滤功能

_bindAutoComplete: function () {
    var self = this;
    this._super.apply(this, arguments);
    this.$input.autocomplete({
        source: function (req) {
            if (!self.many2one)
                self.buildTreeView(req.term);
        },
        focus: function (event, ui) {
            event.preventDefault(); // don't automatically select values on focus
        },
        close: function (event, ui) {
            if (event.which === $.ui.keyCode.ESCAPE) {
                event.stopPropagation();
            }
            console.log('ui close');
        },
        autoFocus: true,
        html: true,
        minLength: 0,
        delay: this.AUTOCOMPLETE_DELAY,
    });

    this.$input.autocomplete("option", "position", {my: "left top", at: "left bottom"});
},

最后,要在视图中使用。以产品目录的输入改为 树状视图 为例

<!-- Product -->
<record id="app_product_template_form_view" model="ir.ui.view">
    <field name="name">app.product.template.form</field>
    <field name="model">product.template</field>
    <field name="inherit_id" ref="product.product_template_form_view"/>
    <field name="arch" type="xml">
        <xpath expr="//field[@name='categ_id']" position="attributes">
            <!-- Add your fields or attributes here -->
          <attribute name="widget">ztree_select</attribute>
          <attribute name="ztree_expend_level">2</attribute>
          <attribute name="limit">16</attribute>
          <attribute name="order">name</attribute>
        </xpath>
    </field>
</record>

可以看到,核心就一个 widget="ztree_select",为了使用方便,可以做不少参数,比如默认展开的级别,排序名称等。

以上是field的处理,在list,kanban的处理会更复杂些,主要是对 search_view 操作实现过滤查询,和对 view_manager  中各种视图切换时,能实现不同的渲染,更改原生view的dom结构。

renderSuperbar: function (sender) {
    //todo: 在form 中的one2many表单,也是list,此时不能render
    var self = this;
    //不在主视图
    //如果视图不变,不处理
    if (!need_render)
        return false;
    if (!sender.getParent().getParent().$el.hasClass('o_view_manager_content'))
        return false;
    //没有数据就清理
    if (!self.bar_data) {
        if (self.$superbar)
            self.$superbar.destroy();
        return false;
    }
    //如果不在允许的view_mode,不处理,默认只有 list, kanban
    if (!self.bar_data.attrs.view_mode) {
        if (self.$superbar)
            self.$superbar.destroy();
        return false;
    }
    else {
        var views = self.bar_data.attrs.view_mode.split(',');
        var viewTag = sender.arch.tag;
        //o的list要改为tree
        if (views.indexOf(viewTag) < 0)  {
            if (self.$superbar)
                self.$superbar.destroy();
            return false;
        }
    }
    if (self.$superbar)
        self.$superbar.destroy();
    //一旦viewType换了,处理渲染
    need_render = false;
    self.$superbar = new Superbar(self.bar_data, this, sender);
    self.$superbar.appendTo(sender.getParent().$el);
    self.ztree_position = self.bar_data.attrs.position;
    sender.getParent().$el.css('display', 'flex');
    sender.getParent().$el.children('div:first').css('flex', '1');
    if (self.ztree_position && self.ztree_position.toLowerCase() == 'left') {
        sender.getParent().$el.children('div:first').css('order', '2');
        sender.getParent().$el.children('div:last').css('border-left', '0');
    } else {
        sender.getParent().$el.children('div:first').css('order', '-2')
    }
},

完成了 树状导航,就可以使用 view 在实际功能中定义了,比如实现订单按客户和状态来进行过滤导航。

几行xml代码搞定,你可以按自己的需要在任意的视图中增加树状导航。

    <xpath expr="//search">
        <superbar view_mode="kanban,tree">
            <field name="partner_id"/>
            <field name="state"/>
        </superbar>
    </xpath>

odoo 是个优秀的框架,努力让她更强大!

发表评论

您必须才能发表评论!