Odoo丨如何改造Odoo原生form表单使其更好看

news2024/11/23 7:59:17

文章目录

  • 前言
  • 一、原生表单实现方式
  • 二、问题发现与分析
    • 1.项目中遇到问题
    • 2.问题具体分析
  • 三、具体解决方法
    • 第一步:把Span变成输入框
    • 第二步:改写_renderFieldWidget


前言

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

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


一、原生表单实现方式

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

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

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

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

二、问题发现与分析

1.项目中遇到问题

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

2.问题具体分析

我们知道,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 = $('<div>');
 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就大功告成了!

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

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

如有疑问,欢迎联系我们~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/91244.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

踩坑记录:C++调用matlab生成的动态链接库

任务类别&#xff1a; 通常出现在项目中&#xff0c;使用 Matlab 设计算法&#xff0c;最后应用于 Qt 的应用程序中。 配置Vs2008环境&#xff1a;(PS:这里应该也同样能应用于其它版本) 一. 设置matlab库目录 选择“可执行文件”下拉框&#xff0c;添加&#xff1a;" ##…

【Redis】Docker 安装 Redis

Docker 安装 Redis 1、安装镜像 docker pull redis docker images docker run -d -p 6379:6379 redis docker ps docker exec -it 容器ID bash 2、验证Redis容器安装结果 redis- clipingset k1 v1 get k1 3、使用Redis需修改配置文件redis.conf。可通过&#xff1a;方法一&…

【Redis】Redis 内存淘汰策略

文章目录概述数据淘汰策略不进行数据淘汰策略进行数据淘汰策略在设置了过期时间的数据中进行淘汰在所有数据范围内进行淘汰查看与配置数据淘汰机制查看 Redis 的数据淘汰机制修改 Redis 的数据淘汰机制方法一方法二浅谈 LRU 算法和 LFU 算法LRU 算法LFU 算法概述 当我们往 Red…

勒索病毒防御 运维安全管控 | 某烟草公司数据安全建设实践

对于烟草行业而言&#xff0c;加快数字化转型是建设现代化烟草经济体系、实现高质量发展的重要支撑。但新技术的普及与应用&#xff0c;在给烟草行业带来便利、创造价值的同时&#xff0c;也使行业面临的数据安全威胁与日俱增。 在数据安全监管合规持续升级的大背景下&#xff…

《自己动手写CPU》学习记录(9)——第7章/Part 2

目录 引言 致谢 流水线暂停 指令说明 madd、maddu、msub、msubu 设计 宏定义文件 程序计数器模块 译码模块 执行模块 访存模块 HI LO 寄存器模块 通用寄存器模块 流水线控制模块 程序ROM MIPS32顶层 MIPS32 SOPC 仿真 仿真程序 TESTBENCH 仿真结果 引言 …

从 0 到 1 搞一个 Compose Desktop 版本的玩天气之绘制

从 0 到 1 搞一个 Compose Desktop 版本的玩天气之绘制 上一篇文章 “从 0 到 1 搞一个 Compose Desktop 版本的玩天气之踩坑” 中大概说了下刚开始使用 Compose Desktop 会遇到的一些问题&#xff0c;帮大家踩了踩坑&#xff0c;那么这一篇则会带大家一起来看下项目中绘制的一…

网易开发三年,现跳槽蚂蚁花呗,4面顺利通过,拿下Java岗offer

面试准备 不论是校招还是社招都避免不了各种面试、笔试&#xff0c;如何去准备这些东西就显得格外重要。 运筹帷幄之后&#xff0c;决胜千里之外&#xff01;不打毫无准备的仗&#xff0c;我觉得大家可以先从下面几个方面来准备面试&#xff1a; 1. 自我介绍。&#xff08;介…

ubuntu22.04LTS 内核源码编译,安装,卸载

下载内核源码 到网站 https://www.kernel.org/ 下载你自己版本的内核源码。 使用如下命令查看自己的内核版本 uname -r编译前准备 安装工具 sudo apt-get install libncurses5-dev libssl-dev build-essential openssl zlibc minizip libidn11-dev libidn11 libelf-dev bc…

困扰程序员50年的问题终于解决了,但好像又没完全解决......

闰秒&#xff0c;这个唯一能够让Meta、谷歌、微软等巨头同暴躁的Linux之父Linus Torvalds达成一致的存在&#xff0c;这个让无数程序员为之头疼的存在&#xff0c;终于要取消了&#xff01; 今年第27届国际计量大会上&#xff0c;与会代表通过了一项决议——从2035年起暂停在官…

【车辆计数】光流法行驶车辆检测计数【含Matlab源码 627期】

⛄一、光流场简介 1 案例背景 运动视觉研究的内容是如何从变化场景中的一系列不同时刻的图像中提取有关场景中物体的形状、位置和运动的信息。根据研究的方法&#xff0c;它可以分为两类&#xff1a;基于特征的方法和基于光流场的方法。基于特征的方法抽取特征点&#xff0c;是…

动态磨砂玻璃渐变背景

网页特效代码合集 动态磨砂玻璃渐变背景 妙用滤镜构建高级感拉满的磨砂玻璃渐变背景 一个磨砂&#xff08;毛玻璃&#xff09;质感效果的渐变背景图&#xff0c;看上去是比较高级的。 这个效果使用 CSS 其实也可以非常轻松制作出来。本文就讨论讨论&#xff1a; 使用 CSS …

WIN10环境下 MYSQL免安装版配置

之前用的旧版本Mysql&#xff0c;还安装Workbench,感觉很冗余&#xff0c;卸了重装一个免安装版&#xff0c; 1、 MYSQL下载解压 MySQL官网下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 点击Download下载免安装版&#xff0c;并进行解压 2、配置环…

02.Ioc容器加载过程-Bean的生命周期源码深度剖析

Spring源码编译教程 Spring IoC容器的加载过程 1.实例化化容器&#xff1a;AnnotationConfigApplicationContext &#xff1a; // 加载spring上下文 AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(MainConfig.class);AnnotationConfi…

24岁程序媛实现了人生第一个小目标 | 2022年终总结

前言 大家好&#xff0c;我是伊人a。2022这一年我实现了人生中的第一个小目标-25岁前能够全款拿下宝马3系。耶比耶比&#x1f389;&#x1f389;&#x1f389; 2022年我是一个满眼星辰的的攀登者。 满眼星辰指的是我对未来充满希望且笃定不移&#xff0c; 攀登者指的是我在…

策略模式(State)

参考&#xff1a; 策略设计模式 (refactoringguru.cn) [5. 策略模式 — Graphic Design Patterns (design-patterns.readthedocs.io)](https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/state.html) [design-patterns-cpp/Strategy.cpp at master …

[附源码]Node.js计算机毕业设计高校教务管理系统Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

从零开始搭建Vue3.0项目

从零开始搭建Vue3.0项目所使用的软件及工具&#xff0c;环境1.确保本机已安装nodejs和npm2.Vue-cli项目搭建所使用的软件及工具&#xff0c;环境 软件vscode&#xff1a; vscode地址下载&#xff0c; svn集中式管理&#xff1a; 是一个开源的代码版本控制系统&#xff0c;用于…

谷歌PR权重是什么意思?如何查询网站的谷歌PR权重

谷歌PR权重是什么意思&#xff1f; Google权重是SEO中的一个常见名词&#xff0c;谷歌权重最早的概念指的是GooglePageRank&#xff0c;简称谷歌的PR值&#xff0c;由网站的外链数据计算得出。 PR值的出现&#xff0c;导致很多人只注重做外链&#xff0c;忽略了网站自身…

认识文件、文件路径、File类

认识文件、文件路径、File类一、认识文件1.1 狭义与广义1.2 树型结构组织和目录1.3 其他相关知识二、文件路径 (Path)三、File类3.1 构造方法3.2 文件元信息操作方法3.3 代码示例一、认识文件 1.1 狭义与广义 狭义的文件&#xff1a; 存储在硬盘上的数据&#xff0c;以"…

DBSyncer

DBSyncer是一款开源的数据同步中间件&#xff0c;提供MySQL、Oracle、SqlServer、PostgreSQL、Elasticsearch(ES)、Kafka、File、SQL等同步场景。支持上传插件自定义同步转换业务&#xff0c;提供监控全量和增量数据统计图、应用性能预警等。 特点 组合驱动&#xff0c;自定义…