本系列所有文章请访问:概述
web界面的ui和微信机器人的ui的区别在于:
web界面组合多种控件完成所有信息的整体呈现与输入,信息丰富、操作高效,用户获取的信息量大、容易理解,但注意力易分散
微信机器人则输入输出相分离,输出是一块一块离散输出【文本按行汇集为一块,markdown、文件、图片、语音、视频都是一条消息为一块,如果有菜单,菜单也是独立的一块】;输入则是交互式的,机器人提示-用户输入,一问一答。信息的呈现较为离散,输入更是稀碎到一问一答。所以缺乏整体,而且如果输出太多,也很容易看了后面忘了前面,用户不容易理解,无法快速明确意图,但便于用户集中注意力
为降低开发维护两套ui的工作量,微信机器人利用了web界面的定义来输出信息。
微信机器人中共涉及到三类输出:
容器表的输出
数据表的输出
向前端发出的提示
在本系统的第一篇文章中,笔者就谈过:微信机器人ui的开发,必须最小化变动,最大限度的确保低成本快速定制的实现。所以针对容器表和数据表的输出都是基于web中的定义而不增加任何额外的属性,只增加一些规范性约定,对于遵守这些规范的web界面,可直接用于微信机器人的输出而不需要增加任何额外的工作量。
jxTMS中的web界面是基于web文件定义然后动态生成的:web界面定义。基于此,对容器表的约束就是:
1、用于数据输入输出的控件都是成对的:一个文本控件用来提示说明,然后紧跟着一个绑定了数据名的控件用于输入输出数据
2、只接受部分控件
未绑定数据名的控件支持:text类型的控件用于提取其text属性作为数据显示的提示词;a、button类型的控件提取为操作,用于动态添加子菜单选项
绑定数据名的控件支持:text【文本】、input【单行输入】、dtpicker【时间】、combobox【下拉框】类型的控件直接输出,textarea【多行输入】、htmlContainer【html文本】类型的控件需要用base64解码后输出,此外还支持markdown【md格式文本】、file【文件】、img【图片】、voice【语音】、video【视频】等类型
注1:对于markdown类型来说,由于企业微信机API文档提示目前仅支持markdown语法子集,所以在编写此类知识时得考虑到企业微信的支持能力。同时,markdown控件在web界面中是显示用的输出型控件,markdown文本的编辑则需要codeEditor控件,由于用codeEditor控件直接编辑markdown文本需要非常熟悉markdown语法,所以建议用外部的markdown编辑器编辑后直接复制到codeEditor控件中。此外,jxTMS中常用的编辑器lightweightEditor控件,其编辑出来的是用于web界面显示的html格式的文本,无法用于markdown输出。markdown类型输出示例如下:
注2:对于voice、video类型来说,web界面中并没有相对应的控件,所以虽然微信机器人可以发送这些类型的信息给用户,但jxTMS后台却没有相应的管理界面。因此是暂时保留
注3:对于file类型来说,web界面中主要用于excel导入【下载就是一个链接】,所以和voice、video一样,都已经实现了,但还需要在具体应用中逐步成熟
如,本系列开始我们所演示的容器表测试的web定义为:
//容器表带操作按钮
web wxTestCT type div;
web wxTestCTt1 parent wxTestCT type table title='完成',width=600;
with wxTestCTt1 row 0 col c0 web n type text text='说明:',width=100;
with wxTestCTt1 row 0 col c1 web n bind testIn1 type text width=500;
with wxTestCTt1 row 1 col c0 web n type a width=80,text='接受',motion=cmd,demand=wxTestCTAccept,
params={'active':'accept'};
with wxTestCTt1 row 1 col c1 web n type a width=80,text='拒绝',motion=cmd,demand=wxTestCTReject,
params={'active':'reject'};
其对应的初始化事件响应函数为:
@myModule.wxDisp('tms','容器表测试'.decode('utf-8'),'测试'.decode('utf-8'))
@myModule.event('prepareDisp', 'wxTestCT')
def wxTest(self, db, ctx):
jx.log('wxTestCT disp')
self.setOutput('testIn1','from wxTestCT')
在web界面的显示是:
而在微信机器人推送给我们时的显示是:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kzMsESsG-1636512124349)(http://115.29.52.95:10002/images/wxRobot-1-4.png)]
即,微信机器人对web定义的处理是
//跳过,如果有多个则会自动插入一个回车
web wxTestCT type div;
//跳过,如果有多个则会自动插入一个回车
web wxTestCTt1 parent wxTestCT type table title='完成',width=600;
//一个没有bind的text控件和其后的一个带bind的控件被转换为微信输出中的一行
//其中前者的text作为提示词直接输出
with wxTestCTt1 row 0 col c0 web n type text text='说明:',width=100;
//后者将接收事件响应函数中输出到所绑定的数据名testIn1的值拼接到提示词之后
with wxTestCTt1 row 0 col c1 web n bind testIn1 type text width=500;
//两个不带绑定的控件,被提取为操作,作为子菜单动态添加到容器表测试的菜单中
//此时,如果用户输入1,则执行wxTestCTAccept命令;输入2,则执行wxTestCTReject,同时将参数合并到两命令的输入流中
with wxTestCTt1 row 1 col c0 web n type a width=80,text='接受',motion=cmd,demand=wxTestCTAccept,
params={'active':'accept'};
with wxTestCTt1 row 1 col c1 web n type a width=80,text='拒绝',motion=cmd,demand=wxTestCTReject,
params={'active':'reject'};
容器表的输出在大多数情况下是比较简单的:
按一个提示控件和一个数据控件成对提取,然后在输出时,将提示与数据控件的实时值拼接为一行
操作按钮提取为动态生成的菜单中的一项
但是,有一种容器表就会非常麻烦了,那就是流程,如报销审批,各环节都可录入意见、执行同意或拒绝操作,但绝不能让部门经理的同意操作取代掉总经理的同意操作。即一个流程是共享读、分段操作的,即所有信息都能看到,但输入和可点击的操作按钮是按任务流转到执行人才能操作自己所负责的那一部分的。所以流程的容器表的输入输出控制是非常麻烦的,需要微信机器人跟踪流程的流转来确定该如何动态控制输入、输出与操作菜单。本文暂不展开。
web端的数据表一般是下面这样的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sSVHRwQE-1636512124351)(http://115.29.52.95:10002/images/wxRobot-1-5.png)]
包括三个部分:
最上面的工具条【多见于容器表,数据表用的比较少】
查询条件设置面板,包括相应的查询条件设置和一个【搜索】按钮,以实现条件查询
分页的数据列表,但其分页需要web端的分页控件配合来实现;此外,每行数据还可以执行多种操作
由于带查询的分页数据表是针对web端定制的,所以其就不如容器表这么简单的就可以转换给微信机器人使用。所以微信机器人所使用的数据表,一般是web端数据表的简化复制品,即从web的数据表定义中复制过来,然后去掉:
查询面板【因为前面已经说过了:微信机器人输入输出相分离】,条件查询当然也可以实现,但需要配合下节的交互式输入,目前暂不考虑
分页控件,微信机器人根本就没有前端界面,所以web界面中的前端分页后端配合的机制是无法实现的,因此微信机器人中的数据表改由后台来控制分页,默认每页8行数据
限制每行只能有一个操作,这个原因很简单,数据表就是一个多行的输出,如果每行再有两个或以上的操作,那菜单显示与选中的操作就太复杂了
同时,由于数据表是一个多行输出,而考虑到手机宽度很小,所以数据表中的每一列数据都会转成和容器表中相同的一行【提示:值】来显示,所以数据表中的每一行就转换为一块,如下图中的第二个输出:
显然,用于微信机器人的数据表的信息密度是和不能和web端一样的,必须得精干再精干一些。
上图的数据表在web文件中的定义是:
//数据表
web wxTestDT type div;
web wxTestDTt1 parent wxTestDT type table title="noUsed";
with wxTestDTt1 col taskID head taskID hide=true;
with wxTestDTt1 col taskName head 工作内容 width=160;
with wxTestDTt1 col taskState head 状态 width=60;
with wxTestDTt1 col op head 操作 web n type a
width=60,motion=cmd,demand=wxTest,text='查看',require=[{"paramName":"taskID"}];
大家注意观察这个数据表的定义,再和上面的实际输出进行对比,就能观察到数据表实际定义了四列,其中:
工作内容和状态这两列正常显示了出来
taskID这一列被定义为hide,所以没有显示,其主要用于保存每行任务所对应的任务ID,以提供给【查看】操作,使得用户在选择了相应的行后可以正确的显示出相应的任务
操作这一列,也没有显示,但笔者在列表后又按了一个1,其实就相当于请求执行第一行的【查看】按钮所对应的wxTest命令,并使用了第一行隐藏起来的taskID进行调用,以查看该任务的详情
在web界面中,命令几乎不会独立出现,基本都是依附于容器表和数据表的,这是因为命令没有自己的界面,所以需要容器表和数据表来提供输入输出和用户交流,所以不需要任何输入输出就意味着这是一个有着清晰目的的固定指令。这种脱离上下文的固定指令一般都是系统动作,如个人的登出、系统的关闭、热机刷新等等。
注:即便是【热机刷新】,笔者都制作了一个提示界面和一个按钮【还带有操作前确认】,让用户在执行这种对系统有重大意义的操作前冷静再冷静,确认是真要这么干
但在微信机器人的ui中,由于其输入与输出相分离,所以独立的命令就有了意义,如前面说过的【新成员注册】功能就是一个命令,因为其不需要输出。
严格来说,【新成员注册】也不是不需要输出,因为总是需要一个执行结果的提示吧。但考虑到这样的输出既非常简单,使用起来又是非常频繁的,如果每个独立的命令就为了一个执行结果提示还得制作一个界面,显然是太过低效了。所以针对执行结果的提示,jxTMS中特别定义了一个名为execResultDescr的输出变量,只要是向其输出,jxTMS就会自动通过微信机器人发送给当前用户。
注1:如果web界面未定义绑定了execResultDescr的输出控件,则数据会发送到前端作为本地变量保存,即web界面中如果定义了绑定了execResultDescr的输出控件,则该控件显示用户输出内容;如果未定义,则浏览器中jxTMS前端的运行环境中会增加一个名为execResultDescr的本地变量
注2:如果是静态web界面,则execResultDescr会放入请求结果中
对于捆绑给带有界面的容器表的命令,可以正常使用self.setOutput函数输出,由于容器表可能有很多数据项,而其捆绑的命令可能只会涉及到其中一部分,所以命令的输出是增量的,即jxTMS会收集命令中用self.setOutput函数输出的那些数据项,并只将其与对应的提示成对输出。
我们到目前所讲述的都是和某个用户在和其会话中向对方发送消息,如果在公告、主动通知【如有新任务到达等】等非会话场景下,向其发送消息则如下调用:
#发送消息
self.sendWXMsg(db,ctx,appName,peopleAbbr,msg,vs)
其中:
appName:就是机器人名
peopleAbbr:成员在组织中的别名【没有重名就是其姓名】,不给则是发给所有人
msg:以花括号对为占位符的消息模板。如:你有新工作:{},发布人是:{}
vs:变参的值数组,就是用vs中的值,逐一替换掉msg中的花括号对
如:
self.sendWXMsg(db,ctx,'tms','测试用户123'.decode('utf-8'),'你有新工作:{},发布人是:{}'.decode('utf-8'),'起来干活了'.decode('utf-8'),'你老板'.decode('utf-8'))
则,用户【测试用户123】就会在企业微信中收到tms机器人发布的:
你有新工作:起来干活了,发布人是:你老板
还可以发送图片和文件:
#发送图片
self.sendWXMsg(db,ctx,appName,peopleAbbr,path)
#发送文件
self.sendWXMsg(db,ctx,appName,peopleAbbr,path)
jxTMS目前已打包为docker容器,可以下拉jxTMS的docker镜像并按jxTMS使用示例尝试使用。