很高兴看到odoo13包含了树状视图的功能,虽然没用ztree,但主要实现效果和界面基本完全克隆了我们这个树状视图superbar的实现,为用户着想啊!!
可以在odoo"文档"这个模块中找到代码和使用案例,社区版只在这个模块实现,可以好好学习。
与时俱进,我们的superbar也升级到了13,实现上向官方靠拢,增加了各主要模块的实例,欢迎在市场获取。
本文涉及模块可以在此下载,支持 odoo13,12,11,10 社区和企业版,支持多语言。不同版本界面略有不同,以下截图仅为参考,以odoo官网模块描述为准。
5月22号更新支持多公司,支持组安全策略,增加了pos,mail,管理等3个免费视图,优化界面。已购买的同学直接官网下载获取更新。
主模块是收费插件,提供几十个免费的业务周边,基本上覆盖odoo所有业务模块。希望童鞋们能学习后自行完成。
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 官方应用市场下载。
https://apps.odoo.com/apps/modules/browse?author=Sunpop.cn
odoo 是个优秀的框架,努力让她更强大!