layim-mobile.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171
  1. /**
  2. @Name:layim mobile 2.2.0
  3. @Author:贤心
  4. @Site:http://layim.layui.com
  5. @License:LGPL
  6. */
  7. layui.define(['laytpl', 'upload', 'layer-mobile', 'zepto'], function(exports){
  8. var v = '2.2.0';
  9. var $ = layui.zepto;
  10. var laytpl = layui.laytpl;
  11. var layer = layui['layer-mobile'];
  12. var device = layui.device();
  13. var SHOW = 'layui-show', THIS = 'layim-this', MAX_ITEM = 20;
  14. //回调
  15. var call = {};
  16. //对外API
  17. var LAYIM = function(){
  18. this.v = v;
  19. touch($('body'), '*[layim-event]', function(e){
  20. var othis = $(this), methid = othis.attr('layim-event');
  21. events[methid] ? events[methid].call(this, othis, e) : '';
  22. });
  23. };
  24. //避免tochmove触发touchend
  25. var touch = function(obj, child, fn){
  26. var move, type = typeof child === 'function', end = function(e){
  27. var othis = $(this);
  28. if(othis.data('lock')){
  29. return;
  30. }
  31. move || fn.call(this, e);
  32. move = false;
  33. othis.data('lock', 'true');
  34. setTimeout(function(){
  35. othis.removeAttr('data-lock');
  36. }, othis.data('locktime') || 0);
  37. };
  38. if(type){
  39. fn = child;
  40. }
  41. obj = typeof obj === 'string' ? $(obj) : obj;
  42. if(!isTouch){
  43. if(type){
  44. obj.on('click', end);
  45. } else {
  46. obj.on('click', child, end);
  47. }
  48. return;
  49. }
  50. if(type){
  51. obj.on('touchmove', function(){
  52. move = true;
  53. }).on('touchend', end);
  54. } else {
  55. obj.on('touchmove', child, function(){
  56. move = true;
  57. }).on('touchend', child, end);
  58. }
  59. };
  60. //是否支持Touch
  61. var isTouch = /Android|iPhone|SymbianOS|Windows Phone|iPad|iPod/.test(navigator.userAgent);
  62. //底部弹出
  63. layer.popBottom = function(options){
  64. layer.close(layer.popBottom.index);
  65. layer.popBottom.index = layer.open($.extend({
  66. type: 1
  67. ,content: options.content || ''
  68. ,shade: false
  69. ,className: 'layim-layer'
  70. }, options));
  71. };
  72. //基础配置
  73. LAYIM.prototype.config = function(options){
  74. options = options || {};
  75. options = $.extend({
  76. title: '我的IM'
  77. ,isgroup: 0
  78. ,isNewFriend: !0
  79. ,voice: 'default.mp3'
  80. ,chatTitleColor: '#36373C'
  81. }, options);
  82. init(options);
  83. };
  84. //监听事件
  85. LAYIM.prototype.on = function(events, callback){
  86. if(typeof callback === 'function'){
  87. call[events] ? call[events].push(callback) : call[events] = [callback];
  88. }
  89. return this;
  90. };
  91. //打开一个自定义的会话界面
  92. LAYIM.prototype.chat = function(data){
  93. if(!window.JSON || !window.JSON.parse) return;
  94. return popchat(data, -1), this;
  95. };
  96. //打开一个自定义面板
  97. LAYIM.prototype.panel = function(options){
  98. return popPanel(options);
  99. };
  100. //获取所有缓存数据
  101. LAYIM.prototype.cache = function(){
  102. return cache;
  103. };
  104. //接受消息
  105. LAYIM.prototype.getMessage = function(data){
  106. return getMessage(data), this;
  107. };
  108. //添加好友/群
  109. LAYIM.prototype.addList = function(data){
  110. return addList(data), this;
  111. };
  112. //删除好友/群
  113. LAYIM.prototype.removeList = function(data){
  114. return removeList(data), this;
  115. };
  116. //设置好友在线/离线状态
  117. LAYIM.prototype.setFriendStatus = function(id, type){
  118. var list = $('.layim-friend'+ id);
  119. list[type === 'online' ? 'removeClass' : 'addClass']('layim-list-gray');
  120. };
  121. //设置当前会话状态
  122. LAYIM.prototype.setChatStatus = function(str){
  123. var thatChat = thisChat(), status = thatChat.elem.find('.layim-chat-status');
  124. return status.html(str), this;
  125. };
  126. //标记新动态
  127. LAYIM.prototype.showNew = function(alias, show){
  128. showNew(alias, show);
  129. };
  130. //解析聊天内容
  131. LAYIM.prototype.content = function(content){
  132. return layui.data.content(content);
  133. };
  134. //列表内容模板
  135. var listTpl = function(options){
  136. var nodata = {
  137. friend: "该分组下暂无好友"
  138. ,group: "暂无群组"
  139. ,history: "暂无任何消息"
  140. };
  141. options = options || {};
  142. //如果是历史记录,则读取排序好的数据
  143. if(options.type === 'history'){
  144. options.item = options.item || 'd.sortHistory';
  145. }
  146. return ['{{# var length = 0; layui.each('+ options.item +', function(i, data){ length++; }}'
  147. ,'<li layim-event="chat" data-type="'+ options.type +'" data-index="'+ (options.index ? '{{'+ options.index +'}}' : (options.type === 'history' ? '{{data.type}}' : options.type) +'{{data.id}}') +'" class="layim-'+ (options.type === 'history' ? '{{data.type}}' : options.type) +'{{data.id}} {{ data.status === "offline" ? "layim-list-gray" : "" }}"><div><img src="{{data.avatar}}"></div><span>{{ data.username||data.groupname||data.name||"佚名" }}</span><p>{{ data.remark||data.sign||"" }}</p><span class="layim-msg-status">new</span></li>'
  148. ,'{{# }); if(length === 0){ }}'
  149. ,'<li class="layim-null">'+ (nodata[options.type] || "暂无数据") +'</li>'
  150. ,'{{# } }}'].join('');
  151. };
  152. //公共面板
  153. var comTpl = function(tpl, anim, back){
  154. return ['<div class="layim-panel'+ (anim ? ' layui-m-anim-left' : '') +'">'
  155. ,'<div class="layim-title" style="background-color: {{d.base.chatTitleColor}};">'
  156. ,'<p>'
  157. ,(back ? '<i class="layui-icon layim-chat-back" layim-event="back">&#xe603;</i>' : '')
  158. ,'{{ d.title || d.base.title }}<span class="layim-chat-status"></span>'
  159. ,'{{# if(d.data){ }}'
  160. ,'{{# if(d.data.type === "group"){ }}'
  161. ,'<i class="layui-icon layim-chat-detail" layim-event="detail">&#xe613;</i>'
  162. ,'{{# } }}'
  163. ,'{{# } }}'
  164. ,'</p>'
  165. ,'</div>'
  166. ,'<div class="layui-unselect layim-content">'
  167. ,tpl
  168. ,'</div>'
  169. ,'</div>'].join('');
  170. };
  171. //主界面模版
  172. var elemTpl = ['<div class="layui-layim">'
  173. ,'<div class="layim-tab-content layui-show">'
  174. ,'<ul class="layim-list-friend">'
  175. ,'<ul class="layui-layim-list layui-show layim-list-history">'
  176. ,listTpl({
  177. type: 'history'
  178. })
  179. ,'</ul>'
  180. ,'</ul>'
  181. ,'</div>'
  182. ,'<div class="layim-tab-content">'
  183. ,'<ul class="layim-list-top">'
  184. ,'{{# if(d.base.isNewFriend){ }}'
  185. ,'<li layim-event="newFriend"><i class="layui-icon">&#xe654;</i>新的朋友<i class="layim-new" id="LAY_layimNewFriend"></i></li>'
  186. ,'{{# } if(d.base.isgroup){ }}'
  187. ,'<li layim-event="group"><i class="layui-icon">&#xe613;</i>群聊<i class="layim-new" id="LAY_layimNewGroup"></i></li>'
  188. ,'{{# } }}'
  189. ,'</ul>'
  190. ,'<ul class="layim-list-friend">'
  191. ,'{{# layui.each(d.friend, function(index, item){ var spread = d.local["spread"+index]; }}'
  192. ,'<li>'
  193. ,'<h5 layim-event="spread" lay-type="{{ spread }}"><i class="layui-icon">{{# if(spread === "true"){ }}&#xe61a;{{# } else { }}&#xe602;{{# } }}</i><span>{{ item.groupname||"未命名分组"+index }}</span><em>(<cite class="layim-count"> {{ (item.list||[]).length }}</cite>)</em></h5>'
  194. ,'<ul class="layui-layim-list {{# if(spread === "true"){ }}'
  195. ,' layui-show'
  196. ,'{{# } }}">'
  197. ,listTpl({
  198. type: "friend"
  199. ,item: "item.list"
  200. ,index: "index"
  201. })
  202. ,'</ul>'
  203. ,'</li>'
  204. ,'{{# }); if(d.friend.length === 0){ }}'
  205. ,'<li><ul class="layui-layim-list layui-show"><li class="layim-null">暂无联系人</li></ul>'
  206. ,'{{# } }}'
  207. ,'</ul>'
  208. ,'</div>'
  209. ,'<div class="layim-tab-content">'
  210. ,'<ul class="layim-list-top">'
  211. ,'{{# layui.each(d.base.moreList, function(index, item){ }}'
  212. ,'<li layim-event="moreList" lay-filter="{{ item.alias }}">'
  213. ,'<i class="layui-icon {{item.iconClass||\"\"}}">{{item.iconUnicode||""}}</i>{{item.title}}<i class="layim-new" id="LAY_layimNew{{ item.alias }}"></i>'
  214. ,'</li>'
  215. ,'{{# }); if(!d.base.copyright){ }}'
  216. ,'<li layim-event="about"><i class="layui-icon">&#xe60b;</i>关于<i class="layim-new" id="LAY_layimNewAbout"></i></li>'
  217. ,'{{# } }}'
  218. ,'</ul>'
  219. ,'</div>'
  220. ,'</div>'
  221. ,'<ul class="layui-unselect layui-layim-tab">'
  222. ,'<li title="消息" layim-event="tab" lay-type="message" class="layim-this"><i class="layui-icon">&#xe611;</i><span>消息</span><i class="layim-new" id="LAY_layimNewMsg"></i></li>'
  223. ,'<li title="联系人" layim-event="tab" lay-type="friend"><i class="layui-icon">&#xe612;</i><span>联系人</span><i class="layim-new" id="LAY_layimNewList"></i></li>'
  224. ,'<li title="更多" layim-event="tab" lay-type="more"><i class="layui-icon">&#xe670;</i><span>更多</span><i class="layim-new" id="LAY_layimNewMore"></i></li>'
  225. ,'</ul>'].join('');
  226. //聊天主模板
  227. var elemChatTpl = ['<div class="layim-chat layim-chat-{{d.data.type}}">'
  228. ,'<div class="layim-chat-main">'
  229. ,'<ul></ul>'
  230. ,'</div>'
  231. ,'<div class="layim-chat-footer">'
  232. ,'<div class="layim-chat-send"><input type="text" autocomplete="off"><button class="layim-send layui-disabled" layim-event="send">发送</button></div>'
  233. ,'<div class="layim-chat-tool" data-json="{{encodeURIComponent(JSON.stringify(d.data))}}">'
  234. ,'<span class="layui-icon layim-tool-face" title="选择表情" layim-event="face">&#xe60c;</span>'
  235. ,'{{# if(d.base && d.base.uploadImage){ }}'
  236. ,'<span class="layui-icon layim-tool-image" title="上传图片" layim-event="image">&#xe60d;<input type="file" name="file" accept="image/*"></span>'
  237. ,'{{# }; }}'
  238. ,'{{# if(d.base && d.base.uploadFile){ }}'
  239. ,'<span class="layui-icon layim-tool-image" title="发送文件" layim-event="image" data-type="file">&#xe61d;<input type="file" name="file"></span>'
  240. ,'{{# }; }}'
  241. ,'{{# layui.each(d.base.tool, function(index, item){ }}'
  242. ,'<span class="layui-icon {{item.iconClass||\"\"}} layim-tool-{{item.alias}}" title="{{item.title}}" layim-event="extend" lay-filter="{{ item.alias }}">{{item.iconUnicode||""}}</span>'
  243. ,'{{# }); }}'
  244. ,'</div>'
  245. ,'</div>'
  246. ,'</div>'].join('');
  247. //补齐数位
  248. var digit = function(num){
  249. return num < 10 ? '0' + (num|0) : num;
  250. };
  251. //转换时间
  252. layui.data.date = function(timestamp){
  253. var d = new Date(timestamp||new Date());
  254. return digit(d.getMonth() + 1) + '-' + digit(d.getDate())
  255. + ' ' + digit(d.getHours()) + ':' + digit(d.getMinutes());
  256. };
  257. //转换内容
  258. layui.data.content = function(content){
  259. //支持的html标签
  260. var html = function(end){
  261. return new RegExp('\\n*\\['+ (end||'') +'(pre|div|p|table|thead|th|tbody|tr|td|ul|li|ol|li|dl|dt|dd|h2|h3|h4|h5)([\\s\\S]*?)\\]\\n*', 'g');
  262. };
  263. content = (content||'').replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&amp;')
  264. .replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/'/g, '&#39;').replace(/"/g, '&quot;') //XSS
  265. .replace(/@(\S+)(\s+?|$)/g, '@<a href="javascript:;">$1</a>$2') //转义@
  266. .replace(/face\[([^\s\[\]]+?)\]/g, function(face){ //转义表情
  267. var alt = face.replace(/^face/g, '');
  268. return '<img alt="'+ alt +'" title="'+ alt +'" src="' + faces[alt] + '">';
  269. })
  270. .replace(/img\[([^\s]+?)\]/g, function(img){ //转义图片
  271. return '<img class="layui-layim-photos" src="' + img.replace(/(^img\[)|(\]$)/g, '') + '">';
  272. })
  273. .replace(/file\([\s\S]+?\)\[[\s\S]*?\]/g, function(str){ //转义文件
  274. var href = (str.match(/file\(([\s\S]+?)\)\[/)||[])[1];
  275. var text = (str.match(/\)\[([\s\S]*?)\]/)||[])[1];
  276. if(!href) return str;
  277. return '<a class="layui-layim-file" href="'+ href +'" download target="_blank"><i class="layui-icon">&#xe61e;</i><cite>'+ (text||href) +'</cite></a>';
  278. })
  279. .replace(/audio\[([^\s]+?)\]/g, function(audio){ //转义音频
  280. return '<div class="layui-unselect layui-layim-audio" layim-event="playAudio" data-src="' + audio.replace(/(^audio\[)|(\]$)/g, '') + '"><i class="layui-icon">&#xe652;</i><p>音频消息</p></div>';
  281. })
  282. .replace(/video\[([^\s]+?)\]/g, function(video){ //转义音频
  283. return '<div class="layui-unselect layui-layim-video" layim-event="playVideo" data-src="' + video.replace(/(^video\[)|(\]$)/g, '') + '"><i class="layui-icon">&#xe652;</i></div>';
  284. })
  285. .replace(/a\([\s\S]+?\)\[[\s\S]*?\]/g, function(str){ //转义链接
  286. var href = (str.match(/a\(([\s\S]+?)\)\[/)||[])[1];
  287. var text = (str.match(/\)\[([\s\S]*?)\]/)||[])[1];
  288. if(!href) return str;
  289. return '<a href="'+ href +'" target="_blank">'+ (text||href) +'</a>';
  290. }).replace(html(), '\<$1 $2\>').replace(html('/'), '\</$1\>') //转移HTML代码
  291. .replace(/\n/g, '<br>') //转义换行
  292. return content;
  293. };
  294. var elemChatMain = ['<li class="layim-chat-li{{ d.mine ? " layim-chat-mine" : "" }}">'
  295. ,'<div class="layim-chat-user"><img src="{{ d.avatar }}" alt="{{ d.uid || d.id }}"><cite>'
  296. ,'{{ d.username||"佚名" }}'
  297. ,'</cite></div>'
  298. ,'<div class="layim-chat-text">{{ layui.data.content(d.content||"&nbsp;") }}</div>'
  299. ,'</li>'].join('');
  300. //处理初始化信息
  301. var cache = {message: {}, chat: []}, init = function(options){
  302. var init = options.init || {}
  303. mine = init.mine || {}
  304. ,local = layui.data('layim-mobile')[mine.id] || {}
  305. ,obj = {
  306. base: options
  307. ,local: local
  308. ,mine: mine
  309. ,history: local.history || []
  310. }, create = function(data){
  311. var mine = data.mine || {};
  312. var local = layui.data('layim-mobile')[mine.id] || {}, obj = {
  313. base: options //基础配置信息
  314. ,local: local //本地数据
  315. ,mine: mine //我的用户信息
  316. ,friend: data.friend || [] //联系人信息
  317. ,group: data.group || [] //群组信息
  318. ,history: local.history || [] //历史会话信息
  319. };
  320. obj.sortHistory = sort(obj.history, 'historyTime');
  321. cache = $.extend(cache, obj);
  322. popim(laytpl(comTpl(elemTpl)).render(obj));
  323. layui.each(call.ready, function(index, item){
  324. item && item(obj);
  325. });
  326. };
  327. cache = $.extend(cache, obj);
  328. if(options.brief){
  329. return layui.each(call.ready, function(index, item){
  330. item && item(obj);
  331. });
  332. };
  333. create(init)
  334. };
  335. //显示好友列表面板
  336. var layimMain, popim = function(content){
  337. return layer.open({
  338. type: 1
  339. ,shade: false
  340. ,shadeClose: false
  341. ,anim: -1
  342. ,content: content
  343. ,success: function(elem){
  344. layimMain = $(elem);
  345. fixIosScroll(layimMain.find('.layui-layim'));
  346. if(cache.base.tabIndex){
  347. events.tab($('.layui-layim-tab>li').eq(cache.base.tabIndex));
  348. }
  349. }
  350. });
  351. };
  352. //弹出公共面板
  353. var popPanel = function(options, anim){
  354. options = options || {};
  355. var data = $.extend({}, cache, {
  356. title: options.title||''
  357. ,data: options.data
  358. });
  359. return layer.open({
  360. type: 1
  361. ,shade: false
  362. ,shadeClose: false
  363. ,anim: -1
  364. ,content: laytpl(comTpl(options.tpl, anim === -1 ? false : true, true)).render(data)
  365. ,success: function(elem){
  366. var othis = $(elem);
  367. othis.prev().find('.layim-panel').addClass('layui-m-anim-lout');
  368. options.success && options.success(elem);
  369. options.isChat || fixIosScroll(othis.find('.layim-content'));
  370. }
  371. ,end: options.end
  372. });
  373. }
  374. //显示聊天面板
  375. var layimChat, layimMin, To = {}, popchat = function(data, anim, back){
  376. data = data || {};
  377. if(!data.id){
  378. return layer.msg('非法用户');
  379. }
  380. layer.close(popchat.index);
  381. return popchat.index = popPanel({
  382. tpl: elemChatTpl
  383. ,data: data
  384. ,title: data.name
  385. ,isChat: !0
  386. ,success: function(elem){
  387. layimChat = $(elem);
  388. hotkeySend();
  389. viewChatlog();
  390. delete cache.message[data.type + data.id]; //剔除缓存消息
  391. showNew('Msg');
  392. //聊天窗口的切换监听
  393. var thatChat = thisChat(), chatMain = thatChat.elem.find('.layim-chat-main');
  394. layui.each(call.chatChange, function(index, item){
  395. item && item(thatChat);
  396. });
  397. fixIosScroll(chatMain);
  398. //输入框获取焦点
  399. thatChat.textarea.on('focus', function(){
  400. setTimeout(function(){
  401. chatMain.scrollTop(chatMain[0].scrollHeight + 1000);
  402. }, 500);
  403. });
  404. }
  405. ,end: function(){
  406. layimChat = null;
  407. sendMessage.time = 0;
  408. }
  409. }, anim);
  410. };
  411. //修复IOS设备在边界引发无法滚动的问题
  412. var fixIosScroll = function(othis){
  413. if(device.ios){
  414. othis.on('touchmove', function(e){
  415. var top = othis.scrollTop();
  416. if(top <= 0){
  417. othis.scrollTop(1);
  418. e.preventDefault(e);
  419. }
  420. if(this.scrollHeight - top - othis.height() <= 0){
  421. othis.scrollTop(othis.scrollTop() - 1);
  422. e.preventDefault(e);
  423. }
  424. });
  425. }
  426. };
  427. //同步置灰状态
  428. var syncGray = function(data){
  429. $('.layim-'+data.type+data.id).each(function(){
  430. if($(this).hasClass('layim-list-gray')){
  431. layui.layim.setFriendStatus(data.id, 'offline');
  432. }
  433. });
  434. };
  435. //获取当前聊天面板
  436. var thisChat = function(){
  437. if(!layimChat) return {};
  438. var cont = layimChat.find('.layim-chat');
  439. var to = JSON.parse(decodeURIComponent(cont.find('.layim-chat-tool').data('json')));
  440. return {
  441. elem: cont
  442. ,data: to
  443. ,textarea: cont.find('input')
  444. };
  445. };
  446. //将对象按子对象的某个key排序
  447. var sort = function(data, key, asc){
  448. var arr = []
  449. ,compare = function (obj1, obj2) {
  450. var value1 = obj1[key];
  451. var value2 = obj2[key];
  452. if (value2 < value1) {
  453. return -1;
  454. } else if (value2 > value1) {
  455. return 1;
  456. } else {
  457. return 0;
  458. }
  459. };
  460. layui.each(data, function(index, item){
  461. arr.push(item);
  462. });
  463. arr.sort(compare);
  464. if(asc) arr.reverse();
  465. return arr;
  466. };
  467. //记录历史会话
  468. var setHistory = function(data){
  469. var local = layui.data('layim-mobile')[cache.mine.id] || {};
  470. var obj = {}, history = local.history || {};
  471. var is = history[data.type + data.id];
  472. if(!layimMain) return;
  473. var historyElem = layimMain.find('.layim-list-history');
  474. data.historyTime = new Date().getTime();
  475. data.sign = data.content;
  476. history[data.type + data.id] = data;
  477. local.history = history;
  478. layui.data('layim-mobile', {
  479. key: cache.mine.id
  480. ,value: local
  481. });
  482. var msgItem = historyElem.find('.layim-'+ data.type + data.id)
  483. ,msgNums = (cache.message[data.type+data.id]||[]).length //未读消息数
  484. ,showMsg = function(){
  485. msgItem = historyElem.find('.layim-'+ data.type + data.id);
  486. msgItem.find('p').html(data.content);
  487. if(msgNums > 0){
  488. msgItem.find('.layim-msg-status').html(msgNums).addClass(SHOW);
  489. }
  490. };
  491. if(msgItem.length > 0){
  492. showMsg();
  493. historyElem.prepend(msgItem.clone());
  494. msgItem.remove();
  495. } else {
  496. obj[data.type + data.id] = data;
  497. var historyList = laytpl(listTpl({
  498. type: 'history'
  499. ,item: 'd.data'
  500. })).render({data: obj});
  501. historyElem.prepend(historyList);
  502. showMsg();
  503. historyElem.find('.layim-null').remove();
  504. }
  505. showNew('Msg');
  506. };
  507. //标注底部导航新动态徽章
  508. var showNew = function(alias, show){
  509. if(!show){
  510. var show;
  511. layui.each(cache.message, function(){
  512. show = true;
  513. return false;
  514. });
  515. }
  516. $('#LAY_layimNew'+alias)[show ? 'addClass' : 'removeClass'](SHOW);
  517. };
  518. //发送消息
  519. var sendMessage = function(){
  520. var data = {
  521. username: cache.mine ? cache.mine.username : '访客'
  522. ,avatar: cache.mine ? cache.mine.avatar : (layui.cache.dir+'css/pc/layim/skin/logo.jpg')
  523. ,id: cache.mine ? cache.mine.id : null
  524. ,mine: true
  525. };
  526. var thatChat = thisChat(), ul = thatChat.elem.find('.layim-chat-main ul');
  527. var To = thatChat.data, maxLength = cache.base.maxLength || 3000;
  528. var time = new Date().getTime(), textarea = thatChat.textarea;
  529. data.content = textarea.val();
  530. if(data.content === '') return;
  531. if(data.content.length > maxLength){
  532. return layer.msg('内容最长不能超过'+ maxLength +'个字符')
  533. }
  534. if(time - (sendMessage.time||0) > 60*1000){
  535. ul.append('<li class="layim-chat-system"><span>'+ layui.data.date() +'</span></li>');
  536. sendMessage.time = time;
  537. }
  538. ul.append(laytpl(elemChatMain).render(data));
  539. var param = {
  540. mine: data
  541. ,to: To
  542. }, message = {
  543. username: param.mine.username
  544. ,avatar: param.mine.avatar
  545. ,id: To.id
  546. ,type: To.type
  547. ,content: param.mine.content
  548. ,timestamp: time
  549. ,mine: true
  550. };
  551. pushChatlog(message);
  552. layui.each(call.sendMessage, function(index, item){
  553. item && item(param);
  554. });
  555. To.content = data.content;
  556. setHistory(To);
  557. chatListMore();
  558. textarea.val('');
  559. textarea.next().addClass('layui-disabled');
  560. };
  561. //消息声音提醒
  562. var voice = function() {
  563. var audio = document.createElement("audio");
  564. audio.src = layui.cache.dir+'css/modules/layim/voice/'+ cache.base.voice;
  565. audio.play();
  566. };
  567. //接受消息
  568. var messageNew = {}, getMessage = function(data){
  569. data = data || {};
  570. var group = {}, thatChat = thisChat(), thisData = thatChat.data || {}
  571. ,isThisData = thisData.id == data.id && thisData.type == data.type; //是否当前打开联系人的消息
  572. data.timestamp = data.timestamp || new Date().getTime();
  573. data.system || pushChatlog(data);
  574. console.log(data)
  575. messageNew = JSON.parse(JSON.stringify(data));
  576. if(cache.base.voice){
  577. voice();
  578. }
  579. if((!layimChat && data.content) || !isThisData){
  580. if(cache.message[data.type + data.id]){
  581. cache.message[data.type + data.id].push(data)
  582. } else {
  583. cache.message[data.type + data.id] = [data];
  584. }
  585. }
  586. //记录聊天面板队列
  587. var group = {};
  588. if(data.type === 'friend'){
  589. var friend;
  590. layui.each(cache.friend, function(index1, item1){
  591. layui.each(item1.list, function(index, item){
  592. if(item.id == data.id){
  593. data.type = 'friend';
  594. data.name = item.username;
  595. return friend = true;
  596. }
  597. });
  598. if(friend) return true;
  599. });
  600. if(!friend){
  601. data.temporary = true; //临时会话
  602. }
  603. } else if(data.type === 'group'){
  604. layui.each(cache.group, function(index, item){
  605. if(item.id == data.id){
  606. data.type = 'group';
  607. data.name = data.groupname = item.groupname;
  608. group.avatar = item.avatar;
  609. return true;
  610. }
  611. });
  612. } else {
  613. data.name = data.name || data.username || data.groupname;
  614. }
  615. var newData = $.extend({}, data, {
  616. avatar: group.avatar || data.avatar
  617. });
  618. if(data.type === 'group'){
  619. delete newData.username;
  620. }
  621. setHistory(newData);
  622. if(!layimChat || !isThisData) return;
  623. var cont = layimChat.find('.layim-chat')
  624. ,ul = cont.find('.layim-chat-main ul');
  625. //系统消息
  626. if(data.system){
  627. ul.append('<li class="layim-chat-system"><span>'+ data.content +'</span></li>');
  628. } else if(data.content.replace(/\s/g, '') !== ''){
  629. if(data.timestamp - (sendMessage.time||0) > 60*1000){
  630. ul.append('<li class="layim-chat-system"><span>'+ layui.data.date(data.timestamp) +'</span></li>');
  631. sendMessage.time = data.timestamp;
  632. }
  633. ul.append(laytpl(elemChatMain).render(data));
  634. }
  635. chatListMore();
  636. };
  637. //存储最近MAX_ITEM条聊天记录到本地
  638. var pushChatlog = function(message){
  639. var local = layui.data('layim-mobile')[cache.mine.id] || {};
  640. var chatlog = local.chatlog || {};
  641. if(chatlog[message.type + message.id]){
  642. chatlog[message.type + message.id].push(message);
  643. if(chatlog[message.type + message.id].length > MAX_ITEM){
  644. chatlog[message.type + message.id].shift();
  645. }
  646. } else {
  647. chatlog[message.type + message.id] = [message];
  648. }
  649. local.chatlog = chatlog;
  650. layui.data('layim-mobile', {
  651. key: cache.mine.id
  652. ,value: local
  653. });
  654. };
  655. //渲染本地最新聊天记录到相应面板
  656. var viewChatlog = function(){
  657. var local = layui.data('layim-mobile')[cache.mine.id] || {};
  658. var thatChat = thisChat(), chatlog = local.chatlog || {};
  659. var ul = thatChat.elem.find('.layim-chat-main ul');
  660. layui.each(chatlog[thatChat.data.type + thatChat.data.id], function(index, item){
  661. if(new Date().getTime() > item.timestamp && item.timestamp - (sendMessage.time||0) > 60*1000){
  662. ul.append('<li class="layim-chat-system"><span>'+ layui.data.date(item.timestamp) +'</span></li>');
  663. sendMessage.time = item.timestamp;
  664. }
  665. ul.append(laytpl(elemChatMain).render(item));
  666. });
  667. chatListMore();
  668. };
  669. //添加好友或群
  670. var addList = function(data){
  671. var obj = {}, has, listElem = layimMain.find('.layim-list-'+ data.type);
  672. if(cache[data.type]){
  673. if(data.type === 'friend'){
  674. layui.each(cache.friend, function(index, item){
  675. if(data.groupid == item.id){
  676. //检查好友是否已经在列表中
  677. layui.each(cache.friend[index].list, function(idx, itm){
  678. if(itm.id == data.id){
  679. return has = true
  680. }
  681. });
  682. if(has) return layer.msg('好友 ['+ (data.username||'') +'] 已经存在列表中',{anim: 6});
  683. cache.friend[index].list = cache.friend[index].list || [];
  684. obj[cache.friend[index].list.length] = data;
  685. data.groupIndex = index;
  686. cache.friend[index].list.push(data); //在cache的friend里面也增加好友
  687. return true;
  688. }
  689. });
  690. } else if(data.type === 'group'){
  691. //检查群组是否已经在列表中
  692. layui.each(cache.group, function(idx, itm){
  693. if(itm.id == data.id){
  694. return has = true
  695. }
  696. });
  697. if(has) return layer.msg('您已是 ['+ (data.groupname||'') +'] 的群成员',{anim: 6});
  698. obj[cache.group.length] = data;
  699. cache.group.push(data);
  700. }
  701. }
  702. if(has) return;
  703. var list = laytpl(listTpl({
  704. type: data.type
  705. ,item: 'd.data'
  706. ,index: data.type === 'friend' ? 'data.groupIndex' : null
  707. })).render({data: obj});
  708. if(data.type === 'friend'){
  709. var li = listElem.children('li').eq(data.groupIndex);
  710. li.find('.layui-layim-list').append(list);
  711. li.find('.layim-count').html(cache.friend[data.groupIndex].list.length); //刷新好友数量
  712. //如果初始没有好友
  713. if(li.find('.layim-null')[0]){
  714. li.find('.layim-null').remove();
  715. }
  716. } else if(data.type === 'group'){
  717. listElem.append(list);
  718. //如果初始没有群组
  719. if(listElem.find('.layim-null')[0]){
  720. listElem.find('.layim-null').remove();
  721. }
  722. }
  723. };
  724. //移出好友或群
  725. var removeList = function(data){
  726. var listElem = layimMain.find('.layim-list-'+ data.type);
  727. var obj = {};
  728. if(cache[data.type]){
  729. if(data.type === 'friend'){
  730. layui.each(cache.friend, function(index1, item1){
  731. layui.each(item1.list, function(index, item){
  732. if(data.id == item.id){
  733. var li = listElem.children('li').eq(index1);
  734. var list = li.find('.layui-layim-list').children('li');
  735. li.find('.layui-layim-list').children('li').eq(index).remove();
  736. cache.friend[index1].list.splice(index, 1); //从cache的friend里面也删除掉好友
  737. li.find('.layim-count').html(cache.friend[index1].list.length); //刷新好友数量
  738. //如果一个好友都没了
  739. if(cache.friend[index1].list.length === 0){
  740. li.find('.layui-layim-list').html('<li class="layim-null">该分组下已无好友了</li>');
  741. }
  742. return true;
  743. }
  744. });
  745. });
  746. } else if(data.type === 'group'){
  747. layui.each(cache.group, function(index, item){
  748. if(data.id == item.id){
  749. listElem.children('li').eq(index).remove();
  750. cache.group.splice(index, 1); //从cache的group里面也删除掉数据
  751. //如果一个群组都没了
  752. if(cache.group.length === 0){
  753. listElem.html('<li class="layim-null">暂无群组</li>');
  754. }
  755. return true;
  756. }
  757. });
  758. }
  759. }
  760. };
  761. //查看更多记录
  762. var chatListMore = function(){
  763. var thatChat = thisChat(), chatMain = thatChat.elem.find('.layim-chat-main');
  764. var ul = chatMain.find('ul'), li = ul.children('.layim-chat-li');
  765. if(li.length >= MAX_ITEM){
  766. var first = li.eq(0);
  767. first.prev().remove();
  768. if(!ul.prev().hasClass('layim-chat-system')){
  769. ul.before('<div class="layim-chat-system"><span layim-event="chatLog">查看更多记录</span></div>');
  770. }
  771. first.remove();
  772. }
  773. chatMain.scrollTop(chatMain[0].scrollHeight + 1000);
  774. };
  775. //快捷键发送
  776. var hotkeySend = function(){
  777. var thatChat = thisChat(), textarea = thatChat.textarea;
  778. var btn = textarea.next();
  779. textarea.off('keyup').on('keyup', function(e){
  780. var keyCode = e.keyCode;
  781. if(keyCode === 13){
  782. e.preventDefault();
  783. sendMessage();
  784. }
  785. btn[textarea.val() === '' ? 'addClass' : 'removeClass']('layui-disabled');
  786. });
  787. };
  788. //表情库
  789. var faces = function(){
  790. var alt = ["[微笑]", "[嘻嘻]", "[哈哈]", "[可爱]", "[可怜]", "[挖鼻]", "[吃惊]", "[害羞]", "[挤眼]", "[闭嘴]", "[鄙视]", "[爱你]", "[泪]", "[偷笑]", "[亲亲]", "[生病]", "[太开心]", "[白眼]", "[右哼哼]", "[左哼哼]", "[嘘]", "[衰]", "[委屈]", "[吐]", "[哈欠]", "[抱抱]", "[怒]", "[疑问]", "[馋嘴]", "[拜拜]", "[思考]", "[汗]", "[困]", "[睡]", "[钱]", "[失望]", "[酷]", "[色]", "[哼]", "[鼓掌]", "[晕]", "[悲伤]", "[抓狂]", "[黑线]", "[阴险]", "[怒骂]", "[互粉]", "[心]", "[伤心]", "[猪头]", "[熊猫]", "[兔子]", "[ok]", "[耶]", "[good]", "[NO]", "[赞]", "[来]", "[弱]", "[草泥马]", "[神马]", "[囧]", "[浮云]", "[给力]", "[围观]", "[威武]", "[奥特曼]", "[礼物]", "[钟]", "[话筒]", "[蜡烛]", "[蛋糕]"], arr = {};
  791. layui.each(alt, function(index, item){
  792. arr[item] = layui.cache.dir + 'images/face/'+ index + '.gif';
  793. });
  794. return arr;
  795. }();
  796. var stope = layui.stope; //组件事件冒泡
  797. //在焦点处插入内容
  798. var focusInsert = function(obj, str, nofocus){
  799. var result, val = obj.value;
  800. nofocus || obj.focus();
  801. if(document.selection){ //ie
  802. result = document.selection.createRange();
  803. document.selection.empty();
  804. result.text = str;
  805. } else {
  806. result = [val.substring(0, obj.selectionStart), str, val.substr(obj.selectionEnd)];
  807. nofocus || obj.focus();
  808. obj.value = result.join('');
  809. }
  810. };
  811. //事件
  812. var anim = 'layui-anim-upbit', events = {
  813. //弹出聊天面板
  814. chat: function(othis){
  815. var local = layui.data('layim-mobile')[cache.mine.id] || {};
  816. var type = othis.data('type'), index = othis.data('index');
  817. var list = othis.attr('data-list') || othis.index(), data = {};
  818. if(type === 'friend'){
  819. data = cache[type][index].list[list];
  820. } else if(type === 'group'){
  821. data = cache[type][list];
  822. } else if(type === 'history'){
  823. data = (local.history || {})[index] || {};
  824. }
  825. data.name = data.name || data.username || data.groupname;
  826. if(type !== 'history'){
  827. data.type = type;
  828. }
  829. popchat(data, true);
  830. $('.layim-'+ data.type + data.id).find('.layim-msg-status').removeClass(SHOW);
  831. }
  832. //展开联系人分组
  833. ,spread: function(othis){
  834. var type = othis.attr('lay-type');
  835. var spread = type === 'true' ? 'false' : 'true';
  836. var local = layui.data('layim-mobile')[cache.mine.id] || {};
  837. othis.next()[type === 'true' ? 'removeClass' : 'addClass'](SHOW);
  838. local['spread' + othis.parent().index()] = spread;
  839. layui.data('layim-mobile', {
  840. key: cache.mine.id
  841. ,value: local
  842. });
  843. othis.attr('lay-type', spread);
  844. othis.find('.layui-icon').html(spread === 'true' ? '&#xe61a;' : '&#xe602;');
  845. }
  846. //底部导航切换
  847. ,tab: function(othis){
  848. var index = othis.index(), main = '.layim-tab-content';
  849. othis.addClass(THIS).siblings().removeClass(THIS);
  850. layimMain.find(main).eq(index).addClass(SHOW).siblings(main).removeClass(SHOW);
  851. }
  852. //返回到上一个面板
  853. ,back: function(othis){
  854. var layero = othis.parents('.layui-m-layer').eq(0)
  855. ,index = layero.attr('index')
  856. ,PANEL = '.layim-panel';
  857. setTimeout(function(){
  858. layer.close(index);
  859. }, 300);
  860. othis.parents(PANEL).eq(0).removeClass('layui-m-anim-left').addClass('layui-m-anim-rout');
  861. layero.prev().find(PANEL).eq(0).removeClass('layui-m-anim-lout').addClass('layui-m-anim-right');
  862. layui.each(call.back, function(index, item){
  863. setTimeout(function(){
  864. item && item();
  865. }, 200);
  866. });
  867. }
  868. //发送聊天内容
  869. ,send: function(){
  870. sendMessage();
  871. }
  872. //表情
  873. ,face: function(othis, e){
  874. var content = '', thatChat = thisChat(), input = thatChat.textarea;
  875. layui.each(faces, function(key, item){
  876. content += '<li title="'+ key +'"><img src="'+ item +'"></li>';
  877. });
  878. content = '<ul class="layui-layim-face">'+ content +'</ul>';
  879. layer.popBottom({
  880. content: content
  881. ,success: function(elem){
  882. var list = $(elem).find('.layui-layim-face').children('li')
  883. touch(list, function(){
  884. focusInsert(input[0], 'face' + this.title + ' ', true);
  885. input.next()[input.val() === '' ? 'addClass' : 'removeClass']('layui-disabled');
  886. return false;
  887. });
  888. }
  889. });
  890. var doc = $(document);
  891. if(isTouch){
  892. touch(doc, function(){
  893. events.faceHide();
  894. })
  895. //doc.off('touchend', events.faceHide).on('touchend', events.faceHide);
  896. } else {
  897. doc.off('click', events.faceHide).on('click', events.faceHide);
  898. }
  899. stope(e);
  900. } ,faceHide: function(){
  901. layer.close(layer.popBottom.index);
  902. $(document).off('touchend', events.faceHide)
  903. .off('click', events.faceHide);
  904. }
  905. //图片或一般文件
  906. ,image: function(othis){
  907. var type = othis.data('type') || 'images', api = {
  908. images: 'uploadImage'
  909. ,file: 'uploadFile'
  910. }
  911. ,thatChat = thisChat(), conf = cache.base[api[type]] || {};
  912. //执行上传
  913. layui.upload.render({
  914. url: conf.url || ''
  915. ,method: conf.type
  916. ,elem: othis.find('input')[0]
  917. ,accept: type
  918. ,done: function(res){
  919. if(res.code == 0){
  920. res.data = res.data || {};
  921. if(type === 'images'){
  922. focusInsert(thatChat.textarea[0], 'img['+ (res.data.src||'') +']');
  923. } else if(type === 'file'){
  924. focusInsert(thatChat.textarea[0], 'file('+ (res.data.src||'') +')['+ (res.data.name||'下载文件') +']');
  925. }
  926. sendMessage();
  927. } else {
  928. layer.msg(res.msg||'上传失败');
  929. }
  930. }
  931. });
  932. }
  933. //扩展工具栏
  934. ,extend: function(othis){
  935. var filter = othis.attr('lay-filter')
  936. ,thatChat = thisChat();
  937. layui.each(call['tool('+ filter +')'], function(index, item){
  938. item && item.call(othis, function(content){
  939. focusInsert(thatChat.textarea[0], content);
  940. }, sendMessage, thatChat);
  941. });
  942. }
  943. //弹出新的朋友面板
  944. ,newFriend: function(){
  945. layui.each(call.newFriend, function(index, item){
  946. item && item();
  947. });
  948. }
  949. //弹出群组面板
  950. ,group: function(){
  951. popPanel({
  952. title: '群聊'
  953. ,tpl: ['<div class="layui-layim-list layim-list-group">'
  954. ,listTpl({
  955. type: 'group'
  956. ,item: 'd.group'
  957. })
  958. ,'</div>'].join('')
  959. ,data: {}
  960. });
  961. }
  962. //查看群组成员
  963. ,detail: function(){
  964. var thatChat = thisChat();
  965. layui.each(call.detail, function(index, item){
  966. item && item(thatChat.data);
  967. });
  968. }
  969. //播放音频
  970. ,playAudio: function(othis){
  971. var audioData = othis.data('audio')
  972. ,audio = audioData || document.createElement('audio')
  973. ,pause = function(){
  974. audio.pause();
  975. othis.removeAttr('status');
  976. othis.find('i').html('&#xe652;');
  977. };
  978. if(othis.data('error')){
  979. return layer.msg('播放音频源异常');
  980. }
  981. if(!audio.play){
  982. return layer.msg('您的浏览器不支持audio');
  983. }
  984. if(othis.attr('status')){
  985. pause();
  986. } else {
  987. audioData || (audio.src = othis.data('src'));
  988. audio.play();
  989. othis.attr('status', 'pause');
  990. othis.data('audio', audio);
  991. othis.find('i').html('&#xe651;');
  992. //播放结束
  993. audio.onended = function(){
  994. pause();
  995. };
  996. //播放异常
  997. audio.onerror = function(){
  998. layer.msg('播放音频源异常');
  999. othis.data('error', true);
  1000. pause();
  1001. };
  1002. }
  1003. }
  1004. //播放视频
  1005. ,playVideo: function(othis){
  1006. var videoData = othis.data('src')
  1007. ,video = document.createElement('video');
  1008. if(!video.play){
  1009. return layer.msg('您的浏览器不支持video');
  1010. }
  1011. layer.close(events.playVideo.index);
  1012. events.playVideo.index = layer.open({
  1013. type: 1
  1014. ,anim: false
  1015. ,style: 'width: 100%; height: 50%;'
  1016. ,content: '<div style="background-color: #000; height: 100%;"><video style="position: absolute; width: 100%; height: 100%;" src="'+ videoData +'" autoplay="autoplay"></video></div>'
  1017. });
  1018. }
  1019. //聊天记录
  1020. ,chatLog: function(othis){
  1021. var thatChat = thisChat();
  1022. layui.each(call.chatlog, function(index, item){
  1023. item && item(thatChat.data, thatChat.elem.find('.layim-chat-main>ul'));
  1024. });
  1025. }
  1026. //更多列表
  1027. ,moreList: function(othis){
  1028. var filter = othis.attr('lay-filter');
  1029. layui.each(call.moreList, function(index, item){
  1030. item && item({
  1031. alias: filter
  1032. });
  1033. });
  1034. }
  1035. //关于
  1036. ,about: function(){
  1037. layer.open({
  1038. content: '<p style="padding-bottom: 5px;">LayIM属于付费产品,欢迎通过官网获得授权,促进良性发展!</p><p>当前版本:layim mobile v'+ v + '</p><p>版权所有:<a href="http://layim.layui.com" target="_blank">layim.layui.com</a></p>'
  1039. ,className: 'layim-about'
  1040. ,shadeClose: false
  1041. ,btn: '我知道了'
  1042. });
  1043. }
  1044. };
  1045. //暴露接口
  1046. exports('layim-mobile', new LAYIM());
  1047. }).addcss(
  1048. 'modules/layim/mobile/layim.css?v=2.20'
  1049. ,'skinlayim-mobilecss'
  1050. );