一、chat目录结构
图(一)
铛铛前端代码的目录结构如图(一)所示。base文件夹下的.w页面为所有页面的基础页面。Bex5版铛铛和wex5版铛铛的移动端都是从base文件夹中继承的。铛铛Bex5版和wex5版的PC端页面是继承各自的移动端页面。
在base目录下我们要关注的是base/js文件夹中的im.js和im.impl.js。
- js是dangchat-server所提供的接口,在页面中用到dangchat-server中的方法需要将im.js文件引入。
- impl.js实现im.js中的接口。
对于bex5或wex5有关的接口的实现(例如:铛铛的登录)我们放在各自文件夹中的js目录下,如图(二)所示。
图(二)
图(三)
如图(三)所示,lib/actor目录是跟dangchat-server有关的API。调用服务端方法都在actorClient.js中。如图(四)所示:
图(四)
- Promise-pollfill.js是兼容低版本不支持promise的Chrome浏览器。photoswipe是消息页面调用的查看图片的工具。
- superInput是输入框有关的组件。比如在PC端移动端会有不同状态、表情、语音都在js中处理。
- min.js是PC端复制功能调用的第三方。
- electron-app.js是构建桌面端铛铛一些功能。
- js是全局页面的一些公共方法。如:会话列表中的最后一条消息的内容、发送人、发送时间时间等。
- min.css.xml和chat.min.js.xml是合并资源时所需要的配置文件。
- js是配置dangchat-server端口的。
- chat根目录下w是兼容以前版本铛铛的首页跳转。默认跳转到bex5版铛铛的移动端首页。
铛铛源码引入步骤及注意点
1.源码准备
用户可在https://github.com/wex5中下载。点击图(五)所示的标题,进入后下载的chat目录为铛铛所有的前端源码。
图(五)
2.将下载铛铛源码(chat文件夹)的导入x5平台的UI2目录下。
3.Wex5版铛铛源码需要将baas/org文件夹(https://github.com/wex5/wex5-org可下载)导入x5平台下的Baas目录进行模型编译。
注:如果wex5铛铛用bex5数据库中的示例数据。需要注意人员头像问题,wex5版铛铛的头像在数据库中的字段为varchar类型。Bex5版铛铛中示例组织人员的头像为longblob类型。
4. Bex5版铛铛的搜索文档,搜索历史消息功能为x5的拓展服务,点击图(六)所示标题,进入页面进行下载。下载的文件名修改为chat,放入BIZ目录下。如图(七)所示。
图(六)
图(七)
5. dangchat-server准备。下载铛铛绿色安装包:http://www.wex5.com/downloads/。如图(八)所示,红框内为dangchat-server的重要文件,可单独拷贝到服务器作为dangchat-server。注:需要修改dangchat-server/conf/server.conf文件中的所在服务器的端口,修改chat/config.js的端口修改为dangchat-server的端口。
图(八)
- 以上步骤完成后,启动dangchat服务,启动tomcat,运行chat/bex5/index.w或chat/wex5/index.w即可。
二chat修改代码重点
1登录逻辑
如果开发者只想用铛铛的服务实现自己登录逻辑。我们需要自己写自己业务层的实现。
以wex5版铛铛为例(UI2\chat\wex5\js\im.wex5.impl.js):登录代码
//param:登录参数 doLogin : function(param) { returnloginByWex5(param); }, var loginByWex5 = function(params) { varret; justep.Baas.sendRequest({ "url" : "/org/login", "action" : "loginAction", "async" : false, "params" : { "userName" : params.username, "password" : params.password }, "success" : function(data) { //返回参数为flag(登录是是否成功), // personID(登录人id), // personName(登录人name), // message(登录失败消息)。 // isInOrg(是否在组织中) ret = data; }); returnret; };
注:返回personID和personName是为了用这两个字段在dangchat-server中进行注册。
loadPerson为加载组织人员。
加载组织人员是将组织人员构造为person对象,组织人员通过person对象在dangchat-server中进行交互(如:组织人员在dangchat-server注册,获取uid等);
loadPerson : function(persons, pid) { var deferred = $.Deferred(); this.getOrgPersons(pid).done(function(orgPersons) { //将组织人员信息写到新的person对象中 $.each(orgPersons, function(i, v) { var p = { id : v.sPersonID, name : v.sName, uid : v.sNumb,//数据库存放uid的字段 avatar : getPersonAvatar(v.sPhoto), nick : '', phones : [], about : '' }; persons[v.sPersonID] = new Person(p); }); deferred.resolve(persons); }); return deferred.promise(); },
2主页面的修改
登录逻辑只是实现当前的人在dangchat-server的中的注册,这时当前人还没在dangchat-server中登录。如果开发者想要基于dangchat-server的API对主页面进行修改,我们必须要等当前人在dangchat-server登录之后,当前人才能调用dangchat-server提供的方法。
例如:会话列表页面(UI2\chat\wex5\main.js)
//登录dangchat-server的方法 this.loginActorDfd = IM.loginActor(); this.loginActorDfd.done(function() { //登录dangchat-server之后我们调用API获取会话列表 IM.bindDialogs(function(dialogs) { }) })
注:修改主页面结构时应该考虑页面是否依赖登录dangchat-server。
Wex5工作页面(UI2\chat\wex5\js\im.wex5.impl.js):
在登录时我们将工作页面的需要的登录信息存到了store中
var loginByWex5 = function(params) { var ret; var userParams = {}; userParams.CurrentPersonID = ""; userParams.CurrentPersonName = ""; userParams.CurrentPersonFID = ""; userParams.CurrentPersonFName = ""; userParams.CurrentOgnID = ""; userParams.CurrentOgnName = ""; userParams.CurrentDeptID = ""; userParams.CurrentDeptName = ""; userParams.CurrentDeptFID = ""; userParams.CurrentDeptFName = ""; userParams.CurrentOgnFID = ""; userParams.CurrentOgnFName = ""; userParams.CurrentFunRole = "3";// 默认为3:普通员工。(1:公司领导;2:部门主管;3:普通员工) justep.Baas.sendRequest({ "url" : "/org/login", "action" : "loginAction", "async" : false, "params" : { "userName" : params.username, "password" : params.password }, "success" : function(data) { ret = data; if (ret.flag) { userParams.CurrentPersonID = ret.personID; userParams.CurrentPersonName = ret.personName; userParams.CurrentPersonFID = ret.CurrentFID; userParams.CurrentPersonFName = ret.CurrentFName; userParams.CurrentOgnID = ret.CurrentOgnID; userParams.CurrentOgnName = ret.CurrentOgnName; userParams.CurrentFunRole = ret.CurrentFunRole; userParams.CurrentOgnFID="/"+ret.CurrentFID.split("/")[1]; userParams.CurrentOgnFName = "/" + ret.CurrentFName.split("/")[1]; if (ret.CurrentFID.indexOf("dpt") > 0) { userParams.CurrentDeptID = ret.CurrentDeptID; userParams.CurrentDeptName = ret.CurrentDeptName; userParams.CurrentDeptFID = ret.CurrentFID.substring(0, ret.CurrentFID.indexOf("dpt") + 3); userParams.CurrentDeptFName = ret.CurrentFName.substring(0, ret.CurrentFName.indexOf("/", ret.CurrentFName.indexOf("/", 2) + 1)); } saveLoginDataToStore(userParams); } } }); return ret; }; var saveLoginDataToStore = function(data) { data = JSON.stringify(data); store.set('work_loginData', encrypt(data, password)); };
工作页面用到当前人的信息只需调用getCurrentInfo()方法。
注:关于发送消息的方法还需要登录dangchat-server。
三、页面结构
如图(九)所示为所有页面的基础页面。bex5铛铛和wex5铛铛调用有关与dangchat-server有关的公共API都在base文件下各自页面的js文件中,bex5铛铛和wex5铛铛继承base文件下页面,调用各自的不同实现。如:doLogin()、loadPerson()、updatePersonUid()。Im.impl.js中的定义的空方法,都是需要bex5的biz或wex5的baas提供相应的方法提供实现。
如图(十)所示,img文件夹放的是界面用到的一些图片。Js文件中im.js为抽离dangchat-server提供API的接口,im.impl.js是实现im.js中的接口。
图(九)
图(十)
四、页面重点
1会话页面(移动端:main.w PC端:dialogList.w)
main.w为移动端的会话页面;dialogList.w为PC端的会话页面。会话页面中我们调用dangchat-server中的bindDialogs()为返回当前人所有的会话数据。如图(十一)所示。
图(十一)
counter为未读消息数,date为最后一条消息的发送时间,标注的peer为会话的信息,进入发送消息页面时我们需要此对象。人员会话的peer.id就是我们前文提到的人员的uid。我们可以通过此id调用dangchat-server中的getUserPeer(id)或getGroupPeer(id)获取组的信息和组织人员在dangchat-server中的人员信息。Sender为最后一条消息的发送人。text为最后一条信息的内容。
删除会话功能:调用deleteChat(peer);注:只是人员或群组的删除。不会删除组织机构中人员。
群组的删除并退出:dangchat-server没有提供相应的API,我们调用leaveGroup(peer.id)和deleteChat(peer)实现群组的删除并退出。注:如果只是调用leaveGroup(peer.id)退出可以但是会话列表还会留着,造成多余的空会话列表。
注:每当有新的会话时,dangchat-server都会将全部的会列表推送给用户,当会话列表大于二十条时,用户退出重新登录时dangchat-server只会推送会话列表的前二十条。调用onDialogsEnd()查看历史会话。
2消息页面(message.w)
在消息页面我需要每个会话的peer对象。打开消息页面时调用onConversationOpen(peer)可以将会话列表中的counter设置为零。表明你已读未读消息。关闭页面时调用onConversationClosed(peer)。
通过bindMessages(peer,callback)方法在回调函数中取到与该联系人或该群组的聊天内容,每次新打开会话时dangchat-server只会推二十条聊天内容,调用onchatEnd(peer)查看历史消息,调用一次推二十条历史会话,与会话列表相似。消息推送过来后我们调用messageParse对消息进行预处理,分别对表情、文本、文件、图片、语音、和视频分别作了处理,到页面显示相应的页面样式。注:因为消息推送会推送多次,如果消息是文件,消息的推送第一次可能不会将文件的路径推送过来,所以做文件下载时需注意文件的路径。
sendTextMessage(peer,text)是发送文本API,sendFileMessage(peer,file)是发送文件API,在发送文件或文本时dangchat-server会推送新消息的pending(消息追加) sent (消息发送成功) recevied(对方接收) read(接收已读) error(发送失败) 四种状态。
消息页面x5平台为铛铛作了一个输入框组件,为适应不同端输入框的状态。在chat/lib目录下。输入框组件包含css(输入框样式) icon(输入框用到的icon) img(表情图片) js(表情API) lib(第三方录音插件) superInput.js(输入框的核心代码语音录制,手机端键盘高度的计算)。目录结构如图(十二)所示。
图(十二)
superInput.js中发送发消息,语音发送,截屏等方法。
//发送消息事件 this.$domNode.find('.x-btn-send').on('click',this._doSend.bind(this); //语音录制及发送事件 this.$domNode.find('.x-btn-voice').on('touchstart mousedown', this.startRecVoice.bind(this)).on('touchend mouseup', this.stopRecVoice.bind(this)); //截屏截屏发送(铛铛pc客户端功能) this.$domNode.find('.x-btn-prScrn').on('click',this._prScrn.bind(this));
clearChat(peer):删除与该会话的全部消息。
deleteMessage(peer, rid):
删除单条消息(rid为dangchat-server中的消息的id);
关注和取消关注是dangchat-server与bex5结合的一个功能。调用addLike(peer,rid,option);option传递参数如图(十三)所示:
图(十三)
这些参数是我们调用biz端所需参数。取消关注同理。这样我们就可以查看我们所关注的消息。
3人员列表页面(contactList.w)
此页面为组织机构中的人员信息,bex5铛铛与wex5铛铛实现有差异;bex5铛铛调用biz的方法获取当前人登录的所有组织人员和所有部门。Wex5铛铛调用baas服务获取获取当前人所在组织的所有组织人员和所有部门。bindGroupDialogs(callback)回调函数中获取当前人的所有群组会话,所有群组会话与组织人员构成通讯录页面。此页面差异比较大,不作为基础页面。存放在bex5和wex5目录下。
点击群组时传递群组的peer.id和type跳转到群详细页面。
点击组织成员时传递组织人员的id,在contact.w进行处理。对组织人员注册和加好友等操作。此页面为重用页面,点击消息页面消息的转发,wex5的创建群组用此页面进行选择人员操作。
4人员详细页面(contact.w)
在进入页面时通过其他页面传过来的组织人员的id。getPerson(id)获取组织人员的person对象。通过person对象调用addPerson(person),让当前登录人帮助组织人员在dangchat-server中注册并添加此组织人员为好友。调用bindUser(uid)获取该组织人员在dangchat-server中的信息。离开页面时对人员页面进行unbind。
5群组详细页面(group.w)
图(十四)
进入群组页面我们需要群组的peer.id。bindGroup(peer.id,callback)方法的回调函数中获取群组的详细信息。如图(十四)所示,群组的详细信息页面包含群组的peer.id、群关于(about)、群主的peer.id,群成员(members)、群名称。
6拓展页面
Bex5版铛铛还拓展了一些功能。比如搜索历史消息(findAllText.w),搜索历史文档(searchDocument.w),查看关注页面(msgLike)等。
搜索历史消息API为getSearchText(val, option),具体实现在im.biz.impl.js文件中。
Model.prototype.modelParamsReceive = function(event) { this.sId = this.params.sId; this.destId = this.params.destId; this.groupId = this.params.groupId; $(this.getElementByXid("input")).focus(); }; Model.prototype.searchClick = function(event) { var self = this; var rows = []; var message; var val = $(this.getElementByXid("input")).val(); var option = { sId : this.sId, destId : this.destId, groupId : this.groupId, }; IM.getSearchText(val, option).done(function(searchText){ if (searchText.length > 0) { for(var i = 0; i < searchText.length; i++){ message = searchText[i]; rows.push({ fID : message.rid, fContent : message.content, fMessageTime : message.date.substr(0, message.date.length - 6), fImg : IM.getPersonByUID(message.senderUserId).avatar, fSenderName : message.senderUserName, fDestUserName : message.receiverUserName }); } self.comp("textData").loadData(rows); $(self.comp("searchText").domNode).show(); } else { justep.Util.hint("暂无此条消息记录"); self.comp("textData").loadData(rows); $(self.comp("searchText").domNode).hide(); } }); };
在群组搜索聊天信息需要传递搜索关键字,option.sId为当前登录人的peer.id,option.destId可以传null也可传0.option.groupId为群组的peer.id.
与人的聊天会话中将option.destId设置为接收人的peer.id。option.groupId设置为null或0;
搜索文档页面与搜索聊天信息页面相似。只是没有搜索关键字。
关注页面是我们查看前文消息页面关注的那个信息。进入页面是调用getLikeMsgs(filter,limit,offset)获得消息页面关注的信息。页面展示时bind-html即可,关注的那个信息也都带着html标签。
评一波