Odoo中文社区可以通过以下两个域名访问:shine-it.net , odoo.net.cn

原论坛用户的基本信息和发帖这里都予以保留,请注意:原论坛用户无需重新注册新用户,但是您的密码需要重置

开发人员可以登录gitter讨论组: http://gitter.im/odoo-china/Talk, 需要github账号

如果您登录系统碰到问题,请在微信公众号留言:

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



  • odoo的前端,比起很多平台,界面是十分优秀的,利用现有组件也能很快的实现功能。
    但一旦实际和友商PK,就会发现有很多不足,这些硬伤在PK中会大大降低你的杀伤力。
    替代文字
    树状视图算是最重要的一个,这个该是国内国际各大厂商的标配了,在财务、分组目录、地理位置等实际应用中十分常见。所以,实现父子关系的树状视图绝对是打单的重头戏,针对odoo11,12的前端大变化,我们开发了相关的模块,包括在单条记录中使用树状选择(如目录、科目),在列表、看板中使用树状列表导航查询,全部使用odoo标准的开发方式,widget实现,简单的xml设置。

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

    多层级树状导航高级搜索
    字段多层级树状视图选择

    ** 先看最终效果,list, kanban, pivot, graph 中的效果**
    替代文字

    ** field中的效果。**

    替代文字

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

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

    • 引入 ztree.js

    • 列表做 qweb 页面

    • 列表继承原 odoo 的 FieldMany2One 组件

    • 在需要树状列表处理的相应模块写 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 是个优秀的框架,努力让她更强大!


  • 对代码的显示没处理好,更清晰的可以看这里

    http://www.sunpop.cn/odoo_ztree_javascript_tutorial/

    0_1540834618310_bbc8d0c9-ffcf-4860-85f8-cb1df77c8503-image.png