在使用O2OA进行项目定制化开发时,我们可以开发新的前端组件(x_component)以扩展O2OA来实现更多的业务。这种新增前端组件或者前端业务的开发通常会配合后端自定义应用实现的服务来完成系统内数据的交互。在当系统默认的界面不符合系统UI/UE设计需要,希望重新设计现有功能的前端展现时,我们也需要新增或者修改原有的组件源码。
上述要求我们除了使用O2OA平台的门户页面设计达到目标,针对于复杂的前端交互设计,开发属于自己的x_component也是一个有效的方式。
本文主要介绍几种前端二次开发的方式,以及与流行前端框架(Vue、React)的集成。
先决条件
-
获取到O2OA的源码,参照前面的课程所介绍的内容。
-
有一台运行的O2OA服务器,作为开发服务器。
-
开发机安装了Node.js环境,版本要求16及以上版本。
-
为了方便开发,最好有您熟悉的前端开发IDE工具,如WebStorm 或 VSCode。但这并不是强制的。本文后续的操作,我们以VSCode为例。
用VSCode打开O2OA的源码目录,并打开终端,输入以下命令:
npm install -g @o2oa/component-tools
此命令全局安装了@o2oa/component-tools,用于创建O2OAcomponent。建议每次需要创建component时运行此命令,以获取最新的更新。
然后进入o2web/source目录,然后运行npm install。
cd o2web/source npm install
执行完成后,就可以创建component了。
o2oa原生方式的component
创建component组件
创建component的命令格式是:o2-cmpt create name ,其中的“name”就是要创建的component名称。如创建一个名为“custom.homepage”的component。
o2-cmpt create custom.homepage
运行后需要选择使用什么框架模式来创建component,目前支持两种方式:vue3、o2oa原生方式。(对于其它框架如react或者Angular的集成可以查询官方文档,有比较详细的步骤和说明。)
现在我们先介绍o2oa原生方式。选择“o2_native”然后回车,看到以下界面,就说明创建成功了。
此时就可以在目录列表中看到“x_component_custom_homepage”这个目录了。
展开目录后,看到文件结构是:
$Main/目录:存放静态文件资源,包括图标、css、html模板文件等
lp/目录:存放国际化语言包,zh-cn.js、en.js
Main.js:component入口js文件。
如果在运行命令时,使用的是windowss的PowerShell,可能会报错“…… 因为在此系统上禁止运行脚本……”的错误,有两个办法解决:1、用管理员权限打开Windows PowerShell,运行 set-executionpolicy remotesigned 命令,然后先择 Y ,回车即可运行脚本;2、是切换到Command,如下图:
部署组件
前端的打包部署使用了gulp,为了能快速部署组件到开发服务器,需要增加一个配置文件:o2web/gulpconfig.js,用于设置部署到服务器的信息。
内容如下:
module.exports = { "dev": { //"dev"为配置名称 'upload': 'sftp', //sftp, ftp, 或 local 'location': 'E:/o2server/servers/webServer/', //本地服务器部署路径,仅upload为local时有效 'host': 'px.o2oa.net', //sftp或ftp时的服务器地址 'user': 'root', //sftp或ftp 的登录用户 'pass': '********', //sftp或ftp 登录用户的密码 "port": 22, //sftp或ftp 的端口 "remotePath": "/data/o2server/servers/webServer/", //sftp或ftp 的服务器部署路径 "dest": "dest" //打包后生成的本地路径 } };
上面的这个配置表示,如果在打包时传入"dev"作为参数的话,会自动打包,并通过sftp部署到px.o2oa.net服务器的“/data/o2server/servers/webServer/”路径下,这就是正在运行开发服务器的web路径,所以当我们部署上去后,就可以在开发服务器直接运行component了。
配置完成后,就可以运行打包部署脚本了:
gulp x_component_custom_homepage --ev dev
如果不出意外,运行正确后,出现下面的信息:
此时我们新创建的component就部署到开发服务器上了。
运行组件
然后就可以访问开发服务器查看custom.homepage的运行情况,有三种方法:
1、通过浏览器打开:http://hostaname/x_desktop/index.html?app=custom.homepage
2、通过浏览器打开:http://hostaname/x_desktop/app.html?app=custom.homepage
以上两种方法中的“hostname”替换为开发服务器地址。
3、通过浏览器打开:http://hostaname/x_desktop/index.html,然后用管理员身份登录,打开主菜单-系统设置
进入应用后选择“系统部署”,点击最后的“部署组件”:
然后填入合适的信息
组件名称:可随意填写,但不能与已有组件重名
组件标题:可随意填写,此信息其实已经无效,实际的组件标题会自动获取component的语言包的title数据。
组件路径:这个必须和我们创建组件时的名称一致,这个例子中,就是“custom.homepage”
组件图标:会自动获取component组件目录下的appicon.png
是否可见:表示此组件是否会在主菜单中出现。
可访问列表:可以选择个人、群组和角色,设定此组件只有哪些人可访问,为空时表示不限制,
拒绝访问列表:可以选择个人、群组和角色,设定此组件哪些人不可访问,为空时表示不限制,
当“可访问列表”和“拒绝访问列表”都为空时,表示所有人都可以访问。
填写好信息后,点击“部署组件”。
然后就可以在主菜单中看到这个组件了,点击就可以打开它。
打开后组件页面是这个样子的:
这个就是默认的O2OAcomponent组件,它主要实现了列示当前用户最新的5个待办,以及打开日程安排、打开组织管理,启动流程等样例功能。
组件源码介绍
在组件源码目录下的$Main/下存放了静态资源文件,几个主要文件如下:
appicon.png:作为组件在平台主菜单中显示的图标
default/style.css:组件使用的css文件,此文件的css内容仅对当前组件生效
lp/目录存放语言包文件,包含了zh-cn.js和en.js,其中的title就是组件显示的标题。
Main.js文件是组件的入口,它定义了一个类,我们简单介绍一下主要的方法和参数:
//这一行定义了custom.homepage组件是否可以在平台中打开多个窗口 MWF.xApplication.custom.homepage.options.multitask = false; //此处定义了MWF.xApplication.custom.homepage.Main类,组件被打开时,会实例化这个类 MWF.xApplication.custom.homepage.Main = new Class({ //它继承了MWF.xApplication.Common.Main类,这是系统定义的所有组件的基类 Extends: MWF.xApplication.Common.Main, Implements: [Options, Events], options: { "style1": "default", //此处定义了组件使用的默认样式,它对应$Main/default目录,系统会自动加载对应目录的css和资源 "style": "default", "name": "custom.homepage", //组件的名称 "mvcStyle": "style.css", //要加载的css文件名 "icon": "icon.png", //图标的文件名 "title": MWF.xApplication.custom.homepage.LP.title //组件的标题,从lp/目录下的语言包获取 }, //此方法在组件被加载之前运行,此处只是赋值了语言信息 onQueryLoad: function(){ this.lp = MWF.xApplication.custom.homepage.LP; }, //loadApplication是组件对象加载的入口方法,在此处为组件创建dom对象 //一个最简单的组件,只需要实现此方法即可。它是组件中唯一一个必须要实现的方法 //组件的this.conent是一个div容器,它是组件的容器对象,组件的所有dom元素都应该被this.conent包裹 loadApplication: function(callback){ var url = this.path+this.options.style+"/view.html"; this.content.loadHtml(url, {"bind": {"lp": this.lp}, "module": this}, function(){ this.loadTaskView(); }.bind(this)); }, //--------------------------------------------------------------------- //之后所有的方法都是当前组件特有的方法,这需要根据组件的功能自行添加 loadTaskView: function(){ o2.Actions.load("x_processplatform_assemble_surface").TaskAction.listMyPaging(1,5, function(json){ debugger; this.taskListView.loadHtml(this.path+this.options.style+"/taskView.html", {"bind": {"lp": this.lp, "data": json.data}, "module": this}, function(){ this.doSomething(); }.bind(this)); }.bind(this)); }, doSomething: function(){ }, //通过o2.api获取平台api运行环境,然后可以在api文档钟查询具体方法和对象的使用方法 openTask: function(id, e, data){ o2.api.page.openWork(id); }, openCalendar: function(){ o2.api.page.openApplication("Calendar"); }, openOrganization: function(){ o2.api.page.openApplication("Org"); }, openInBrowser: function() { this.openInNewBrowser(true); }, startProcess: function(){ o2.api.page.startProcess(); }, createDocument: function(){ o2.api.page.createDocument(); } });
接着来看一下 loadApplication 方法做了什么
loadApplication: function(callback){ //先计算view.html的路径 var url = this.path+this.options.style+"/view.html"; //然后通过dom对象的loadHtml方法,将view.html的内容,渲染到this.content中。 //在 view.html 内容被加载完成后,通过回调方法,调用loadTaskView方法。 //loadHtml是O2OA为dom对象的一个扩展方法,它实现了一个简单的模板引擎。 //第一个参数是要加载的html模板的url //第二个参数是options对象,其中bind是要绑定到模板的json格式数据,此处我们就是绑定了语言信息 // module是html中的元素要绑定到的组件。 //第三个参数是加载完成后的回调函数。 this.content.loadHtml(url, {"bind": {"lp": this.lp}, "module": this}, function(){ this.loadTaskView(); }.bind(this)); },
那就需要看一下view.html的内容了:
<div class="hello"> <img class="logo" alt="O2OA logo" src="../x_component_Empty/$Main/default/icons/o2logo.png"> <h1>{{ $.lp.welcome }}</h1> <p> For more O2OA development document,<br> check out the <a href="https://www.o2oa.net/develop.html" target="_blank" rel="noopener">O2OA develop documentation</a>. </p> <div class="task" data-o2-element="taskListView"></div> </div>
其中的{{$.xxx}}的内容引用了绑定过来的bind信息。“$”就是绑定的数据(前面所说的bind的数据)。
其中data-o2-element属性是给这个dom对象一个名称,这样我们就可以在组件脚本中,通过this.[名称]获取到这个dom对象了。这个例子中,就是this.taskListView 了。
在view.html加载完成后,会执行 this.loadTaskView方法,那我们就详细看看这个方法
loadTaskView: function(){ //o2.Actions对象的load方法可以载入指定上下文的后端服务,并将其转换为前端可调用的方法 //所有的平台restful api,都可以通过http://develop.o2oa.net/x_program_center来查看 //这里载入了x_processplatform_assemble_surface服务,此服务定义了与流程相关的一组restful api //TaskAction.listMyPaging方法用于获取当前用户的待办列表 //第一个参数是获取待办的页码;第二个参数是每页获取几条待办;第三个参数的是获取成功的回调函数 o2.Actions.load("x_processplatform_assemble_surface").TaskAction.listMyPaging(1,5, function(json){ //成功回调后,调用 this.taskListView.loadHtml方法,这里的this.taskListView就是上述view.html //中data-o2-element属性为“taskListView”的div元素。 //通过loadHtml方法载入了taskView.html,并绑定json.data,json.data就是获取的待办列表对象数组 this.taskListView.loadHtml(this.path+this.options.style+"/taskView.html", {"bind": {"lp": this.lp, "data": json.data}, "module": this}, function(){ //载入taskView.html后,可以做一些其他事情... this.doSomething(); }.bind(this)); }.bind(this)); }
再来看看 taskView.html
<h3>{{$.lp.taskListTitle}}</h3> <br> <table align="center" class="taskListTable" border="1"> <tr> <th>{{$.lp.taskTitle}}</th> <th>{{$.lp.taskProcess}}</th> <th>{{$.lp.taskTime}}</th> </tr> {{each $.data}} <tr> <td><a href="#" data-o2-events="click:openTask:{{$.work}}">{{$.title}}</a></td> <td>{{$.processName}}</td> <td>{{$.startTime}}</td> </tr> {{end each}} </table> <br> <button data-o2-events="click:openCalendar">{{$.lp.openCalendar}}</button> <button data-o2-events="click:openOrganization">{{$.lp.openOrganization}}</button> <button data-o2-events="click:startProcess">{{$.lp.startProcess}}</button> <br> <button data-o2-events="click:openInBrowser">{{$.lp.openInBrowser}}</button>
这里有一个 {{each $.data}} 和 {{end each}},这代表循环$.data数据,并处理内部的html内容。$.data必须是一个可迭代的数据。
data-o2-events自定义属性用于绑定事件,属性值中,如果有多个事件绑定,使用分号“;”分隔,每个事件绑定的格式为:“事件名:方法:[参数1]:[参数2]...”,在loadHtml时,将component组件绑定到了“module”属性,所以这里的“方法”需要在component组件中定义。如: data-o2-events="click:openCalendar",在Main.js中就定义了这一个方法,用于打开日程安排组件。
openCalendar: function(){ layout.openApplication(null, "Calendar"); }
组件修改
接着来修改组件内容,一般情况首先要修改的是组件标题,所以我们打开语言包,修改title内容:
//zh-cn.js MWF.xApplication.custom.homepage.LP = { "title": "我的主页", "welcome": "Welcome to O2OA Component", "taskListTitle": "此处列出您的5个最新待办", "taskTitle": "标题", "taskProcess": "流程", "taskTime": "到达时间", "openCalendar": "打开日程管理", "openOrganization": "打开组织管理", "startProcess": "启动流程", "openInBrowser": "在新浏览器窗口中打开" }; //en.js MWF.xApplication.custom.homepage.LP = { "title": "MyHomepage", "welcome": "Welcome to O2OA Component", "taskListTitle": "Here are your top 5 task", "taskTitle": "Title", "taskProcess": "Process", "taskTime": "Time", "openCalendar": "Open Calendar", "openOrganization": "Open Organization", "startProcess": "Start Process", "openInBrowser": "Open In Browser" };
保存后,再次运行部署命令:
gulp x_component_custom_homepage --ev dev
运行完成后在刷新浏览器就可以看到修改后的效果了。
每次修改后运行部署命令,是令人不愉快的,所以我们可以运行以下命令,来监听文件变化,并自动部署:
gulp watch --src x_component_custom_homepage --ev dev
之后每次修改组件的任何文件时,就会自动部署到开发环境“dev”了。
至此,native类型的component的框架搭建好了。
vue3类型的component
O2OA支持使用vue3创建component
创建组件
我们再次打开终端,到o2web/source目录,运行创建component命令,创建一个名为“custom.workplace”的component。
o2-cmpt create custom.workplace
然后选择“vue3”,会自动创建基于vue3的component。在安装完部分依赖之后,会询问几个问题:
1、需要输入开发服务器的hosts
2、需要输入开发服务器的center服务端口,默认是80
3、需要输入开发服务器的web服务端口,默认是80
4、确认开发服务器是否使用https,默认为 否(N)
其中开发服务器我们使用 px.o2oa.net, 其他选项都保持默认即可。
看到以下界面,说明component创建完成。
然后选择“vue3”,会自动创建基于vue3的component。在安装完部分依赖之后,会询问几个问题:
1、需要输入开发服务器的hosts
2、需要输入开发服务器的center服务端口,默认是80
3、需要输入开发服务器的web服务端口,默认是80
4、确认开发服务器是否使用https,默认为 否(N)
其中开发服务器我们使用 px.o2oa.net, 其他选项都保持默认即可。
看到以下界面,说明component创建完成。
此时就可以在目录列表中看到“x_component_custom_workplace”这个目录了。
展开目录后,就可以看到一个标准的vue应用的文件结构。其中有o2.config.js文件内容如下:
module.exports = { "server": { "host": "develop.o2oa.net", "port": "80", "httpPort": "80", "https": false } }
这里的内容就是在创建component时询问的四个问题答案,它主要用于在启动本地开发服务器时,确定要连接的后端服务地址。我们可以随时修改这个配置,来连接不同的O2OA开发服务器。
在看到public/$Main/和public/lp/目录,这和原生component的$Main目录和lp目录内容完全一致。
运行组件
vue类型的component,是可以运行在本地开发服务器上的,所以我们只需要在组件目录下运行npm run serve即可
cd x_component_custom_workplace npm run serve
运行后,会启动本地开发服务器,并自动打开浏览器,访问。
添加路由插件
我们还可以为vue component添加vue router插件,这和所有vue应用一样,可以通过vue ui添加,也可以使用命令:vue add router.
Shell复制代码
1
vue add router
如果有源码有未提交的commit,会提示,建议先提交后再安装插件。这里我们选择y,继续安装。
安装一些依赖包后,会询问是否使用histroy模式,此处我们选择 n ,使用 hash 模式。
接着安装完成,我们可以看到src目录下,增加了router/index.js和view/目录下的两个视图。
重新启动本地开发服务器:npm run serve,可以在浏览器中看到效果:
修改组件
此时再修改组件内容,如修改src/components/O2Task.vue文件,中的create()方法,将获取5个待办,改获取10个待办:
... <script> ... created(){ //此处将第二个参数从5改为10 o2.Actions.load("x_processplatform_assemble_surface").TaskAction.V2ListPaging(1, 10, null).then((json)=>{ this.$data.taskList = json.data; }); } ... </script> ...
保存文件后,本地服务器会自动编译,浏览器会自动体现修改的内容。
这样我们就可以方便的开发component组件了。
集成自定义应用
部署组件
此时组件都是在本地服务器运行,要部署到服务器的方法和native类型的组件是一样的。在确保存在了gulpconfig.js文件,并正确配置后,在o2web/目录下运行gulp命令:
gulp x_component_custom_workplace --ev dev
就可以将组件部署到服务器了。
可以和native组件一样的方式,访问custom.workplace组件了。
React类型的component
O2OA支持使用React创建component组件,其方法与Vue3的组件非常类似。
创建组件
我们再次打开终端,到o2web/source目录,运行创建component命令,创建一个名为“custom.liveplace”的component。
o2-cmpt create custom.liveplace
然后选择“react”,就会自动创建基于React的component。在安装完部分依赖之后,同样会询问几个问题:
1、需要输入开发服务器的hosts
2、需要输入开发服务器的center服务端口,默认是80
3、需要输入开发服务器的web服务端口,默认是80
4、确认开发服务器是否使用https,默认为 否(N)
其中开发服务器我们使用 px.o2oa.net, 其他选项都保持默认即可。
看到以下界面,说明component创建完成。(React应用创建过程较长,需要耐心等待一下)
此时就可以在目录列表中看到“x_component_custom_liveplace”这个目录了。
展开目录后,就可以看到一个标准的React应用的文件结构。其中有o2.config.js文件内容如下:
module.exports = { "server": { "host": "develop.o2oa.net", "port": "80", "httpPort": "80", "https": false } }
这里的内容就是在创建component时询问的四个问题答案,它主要用于在启动本地开发服务器时,确定要连接的后端服务地址。我们可以随时修改这个配置,来连接不同的O2OA开发服务器。
再看到public/$Main/和public/lp/目录,这和原生component的$Main目录和lp目录内容完全一致。
运行组件
React类型的component,是可以运行在本地开发服务器上的,所以我们只需要在组件目录下运行npm run start即可
cd x_component_custom_liveplace npm run start
运行后,会启动本地开发服务器,并自动打开浏览器,访问。
此时可以修改组件源码,这里我们先修改一下欢迎信息。
修改中文语言信息:打开组件源码目录的public/lp/zh-cn.js,修改一下welcome:
MWF.xApplication.custom.liveplace.LP = { "title": "custom.liveplace", "welcome": "欢迎使用O2OA和React!", "taskListTitle": "此处列出您的5个最新待办", "taskTitle": "标题", "taskProcess": "流程", "taskTime": "到达时间", "openCalendar": "打开日程管理", "openOrganization": "打开组织管理", "startProcess": "启动流程", "createDocument": "创建信息", "openInBrowser": "在新浏览器窗口中打开" }
刷新浏览器窗口后,就可以看到:
我们可以切换语言信息,来看一下,点击右上角的个人头像,点击“个人设置”,将“语言设置”改为“English”,“保存个人信息”后,刷新页面。
就可以看到,组件的语言信息,已经切换过来了:
接着就需要根据需求的内容,进行组件开发了。
部署组件
部署React类型的组件,也非常容易。和上述两种模式是一样的。
在确保存在了gulpconfig.js文件,并正确配置后,在o2web/目录下运行gulp命令:
gulp x_component_custom_liveplace --ev dev
就可以将组件部署到服务器了。
可以和native组件一样的方式,访问custom.liveplace组件了。
总结
本文我们讲述了如何对O2OA进行前端的二次开发,介绍了创建O2OA component的方式,包括原生方式、基于Vue3的方式和基于React的方式。各种O2OA component脚手架工具帮助开发者更方便地搭建了O2OA组件的开发框架。无论那种方式,都完整的保留了O2OA平台的所有可用API,认证体系,多语言特性、后端服务方法转换等特性,使开发者可以更好地专注于需要实现的业务。