扫码阅读
手机扫码阅读

Odoo丨Get原生form表单变“美”小技巧~

305 2023-09-21


Odoo

神州数码云基地

在 Odoo 上的尝试、调研与分享



本期内容

Odoo原生form表单改造

Odoo作为快速搭建系统的框架,我们在利用它便捷高效功能的同时,有没有觉得有些页面不太好看呢?


今天我们一起来聊聊如何让odoo原生的form表单更美观更符合用户体验~

原生表单实现方式

Odoo为了极致的简约,字段的定义直接通过xml,然后渲染到页面上展示

如果需要调整整体布局,将字段分成两列三列展示,可以使用group标签进行分组,如果想做成分页样式可以使用page标签。

虽然Odoo提供的整体样式,能够满足常见的业务场景,但是odoo对字段底层样式却没有那么灵活的配置选项;样式改变只能改变简单的必填与否,如果需求对字段进行比较复杂的操作或者指令,就必须通过widget来实现

Odoo给我们也提供了很多原生的widget,比如实现多对多用widget="many2many_tags",枚举把下拉框变成单选框用widget="radio",时间格式只保留年月日用widget="date"等等


问题发现与分析

项目中遇到问题

问题起源于我们细致入微的产品经理,拿到我们做的表单之后,就说了一句话:丑,那一片空白是什么?多行之间是错行的?


/ 改造之前

问题的具体分析

我们知道,Odoo原生页面就是字段为空值显示空白,不够美观,而且空白样式和输入样式的高度不同,就会使得group分组之后,每一行不是在一条水平线上。

在编辑模式下,可编辑字段是输入框,不可编辑字段仍然是空白,或者一段数据,对比一下,视觉上冲击力更强。

调试模式下,输入框是input标签,而只读字段的被渲染成span,稍微了解一点前端知识的同学都知道,span本身在页面上是看不到的,只有span中有文字才会显示出文字。

为了解决这种问题,让表格更好看,让整个页面看起来更赏心悦目。我们想办法对页面进行改造。根据以上发现,既然字段的属性不能解决这个问题,那就通过widget来实现原生方法不可能考虑到所有使用场景,根据实际场景还是要自定义。

具体解决方法

第一步:把Span变成输入框

明确目标是想要把显示的span变成显示输入框,那就去找到加载页面的时候,渲染字段的方法

对于不同的数据类型,加载的时候执行的模块和方法不同,那就要根据数据类型分批改造方法,以字符串类型为例,在保持原有数据功能不变的情况下,对其方法进行拓展。

在渲染字段的时候进行判断,如果是只读属性,就把本来要渲染的数据,放到输入框里,同时定义好输入框的宽度,和原生输入框保持一致,避免长短不一的情况

同样的我们可以对多对一关系、浮点类型、日期类型等进行改造,显示输入框。

以上改造的主要逻辑都是一样的,只不过不同数据类型封装的数组格式不同,需要从中取出展示在页面上的值。

除此之外,关于多对多类型有另外的方法,因为多对多类型本来就带有输入框,只是没有显示出来,而且页面上的值是通过关联表查出的自己改写显示起来比较复杂。需要把它原本的输入框显示出来即可。显示的样式和颜色从别的输入框抓取。


改写完成,使用的时候给字段绑上特定的widget就可以啦。

看下效果:

是不是舒服多了,编辑功能保留原有的就行,只是把只读的输入框和编辑的默认框高度宽度保持一致就好啦!

第二步:改写_renderFieldWidget

到这里,进度条已经走完80%了。

这时候你会发现,如果该字段有值时会显示输入框,但是为空时,还是一片空白,好像并没有完成成功……

这是为字段加载空值的时候不会通过 _render方法;

我们需要改写 _renderFieldWidget 方法,在刚开始加载的时候,对所有情况进行统一,无论什么情况都执行我们改写的代码。


return FormRenderer.extend({ _renderFieldWidget: function (node, record, options) { if (!this.renderInvisible && node.attrs.modifiers.invisible === true) { return $(); } options = options || {}; var fieldName = node.attrs.name; // Register the node-associated modifiers var mode = options.mode || this.mode; var modifiers = this._registerModifiers(node, record, null, options); // Initialize and register the widget // Readonly status is known as the modifiers have just been registered var Widget = record.fieldsInfo[this.viewType][fieldName].Widget; const legacy = !(Widget.prototype instanceof owl.Component); const widgetOptions = { mode: modifiers.readonly ? 'readonly' : mode, viewType: this.viewType, }; let widget; if (legacy) { widget = new Widget(this, fieldName, record, widgetOptions); } else { widget = new FieldWrapper(this, Widget, { fieldName, record, options: widgetOptions, }); }  // Register the widget so that it can easily be found again if (this.allFieldWidgets[record.id] === undefined) { this.allFieldWidgets[record.id] = []; } this.allFieldWidgets[record.id].push(widget);  widget.__node = node;  // Prepare widget rendering and save the related promise var $el = $('
'); let def; if (legacy) { def = widget._widgetRenderAndInsert(function () { }); } else { def = widget.mount(document.createDocumentFragment()); } this.defs.push(def); // Update the modifiers registration by associating the widget and by // giving the modifiers options now (as the potential callback is // associated to new widget) var self = this; def.then(function () { // when the caller of renderFieldWidget uses something like // this.renderFieldWidget(...).addClass(...), the class is added on // the temporary div and not on the actual element that will be // rendered. As we do not return a promise and some callers cannot // wait for this.defs, we copy those classnames to the final element. widget.$el.addClass($el.attr('class')); $el.replaceWith(widget.$el); self._registerModifiers(node, record, widget, { callback: function (element, modifiers, record) { element.$el.toggleClass('o_field_empty', !!( record.data.id && (modifiers.readonly || mode === 'readonly') && false //我们需要改写的地方 )); }, keepBaseMode: !!options.keepBaseMode, mode: mode, }); self._postProcessField(widget, node); }); return $el; },});

修改的位置源码是:

element.widget.isEmpty(),我们改成false,字段数据为空的时候不生效

在注册widget的时候引入这个文件就可以啦

再去页面看看,是不是好看多了~

/ 改造之后

最后,给页面上所有字段都绑定widget就大功告成了!

本次分享就到这里啦~~~


最后感谢小伙伴丁涛的技术支持和指导~

Odoo原生form表单改造

就到这里啦~!

有更好的办法或疑问

欢迎加入社群一起讨论哦⬇

本期作者

张备

原文链接: http://mp.weixin.qq.com/s?__biz=Mzg5MzUyOTgwMQ==&mid=2247515584&idx=1&sn=4a1f8da390726a784d85cf8dc8d61a33&chksm=c02f8666f7580f70c933d18be3f00802e5819cc1e48d9477b70de1c77f2380f6516fe95905c6#rd