layim-mobile.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167
  1. /**
  2. @Name:layim mobile 2.1.0
  3. @Author:贤心
  4. @Site:http://layim.layui.com
  5. @License:LGPL
  6. */
  7. layui.define(['laytpl', 'upload-mobile', 'layer-mobile', 'zepto'], function(exports){
  8. var v = '2.1.0';
  9. var $ = layui.zepto;
  10. var laytpl = layui.laytpl;
  11. var layer = layui['layer-mobile'];
  12. var upload = layui['upload-mobile'];
  13. var device = layui.device();
  14. var SHOW = 'layui-show', THIS = 'layim-this', MAX_ITEM = 20;
  15. //回调
  16. var call = {};
  17. //对外API
  18. var LAYIM = function(){
  19. this.v = v;
  20. touch($('body'), '*[layim-event]', function(e){
  21. var othis = $(this), methid = othis.attr('layim-event');
  22. events[methid] ? events[methid].call(this, othis, e) : '';
  23. });
  24. };
  25. //避免tochmove触发touchend
  26. var touch = function(obj, child, fn){
  27. var move, type = typeof child === 'function', end = function(e){
  28. var othis = $(this);
  29. if(othis.data('lock')){
  30. return;
  31. }
  32. move || fn.call(this, e);
  33. move = false;
  34. othis.data('lock', 'true');
  35. setTimeout(function(){
  36. othis.removeAttr('data-lock');
  37. }, othis.data('locktime') || 0);
  38. };
  39. if(type){
  40. fn = child;
  41. }
  42. obj = typeof obj === 'string' ? $(obj) : obj;
  43. if(!isTouch){
  44. if(type){
  45. obj.on('click', end);
  46. } else {
  47. obj.on('click', child, end);
  48. }
  49. return;
  50. }
  51. if(type){
  52. obj.on('touchmove', function(){
  53. move = true;
  54. }).on('touchend', end);
  55. } else {
  56. obj.on('touchmove', child, function(){
  57. move = true;
  58. }).on('touchend', child, end);
  59. }
  60. };
  61. //是否支持Touch
  62. var isTouch = /Android|iPhone|SymbianOS|Windows Phone|iPad|iPod/.test(navigator.userAgent);
  63. //底部弹出
  64. layer.popBottom = function(options){
  65. layer.close(layer.popBottom.index);
  66. layer.popBottom.index = layer.open($.extend({
  67. type: 1
  68. ,content: options.content || ''
  69. ,shade: false
  70. ,className: 'layim-layer'
  71. }, options));
  72. };
  73. //基础配置
  74. LAYIM.prototype.config = function(options){
  75. options = options || {};
  76. options = $.extend({
  77. title: '我的IM'
  78. ,isgroup: 0
  79. ,isNewFriend: !0
  80. ,voice: 'default.mp3'
  81. ,chatTitleColor: '#36373C'
  82. }, options);
  83. init(options);
  84. };
  85. //监听事件
  86. LAYIM.prototype.on = function(events, callback){
  87. if(typeof callback === 'function'){
  88. call[events] ? call[events].push(callback) : call[events] = [callback];
  89. }
  90. return this;
  91. };
  92. //打开一个自定义的会话界面
  93. LAYIM.prototype.chat = function(data){
  94. if(!window.JSON || !window.JSON.parse) return;
  95. return popchat(data, -1), this;
  96. };
  97. //打开一个自定义面板
  98. LAYIM.prototype.panel = function(options){
  99. return popPanel(options);
  100. };
  101. //获取所有缓存数据
  102. LAYIM.prototype.cache = function(){
  103. return cache;
  104. };
  105. //接受消息
  106. LAYIM.prototype.getMessage = function(data){
  107. return getMessage(data), this;
  108. };
  109. //添加好友/群
  110. LAYIM.prototype.addList = function(data){
  111. return addList(data), this;
  112. };
  113. //删除好友/群
  114. LAYIM.prototype.removeList = function(data){
  115. return removeList(data), this;
  116. };
  117. //设置好友在线/离线状态
  118. LAYIM.prototype.setFriendStatus = function(id, type){
  119. var list = $('.layim-friend'+ id);
  120. list[type === 'online' ? 'removeClass' : 'addClass']('layim-list-gray');
  121. };
  122. //设置当前会话状态
  123. LAYIM.prototype.setChatStatus = function(str){
  124. var thatChat = thisChat(), status = thatChat.elem.find('.layim-chat-status');
  125. return status.html(str), this;
  126. };
  127. //标记新动态
  128. LAYIM.prototype.showNew = function(alias, show){
  129. showNew(alias, show);
  130. };
  131. //解析聊天内容
  132. LAYIM.prototype.content = function(content){
  133. return layui.data.content(content);
  134. };
  135. //列表内容模板
  136. var listTpl = function(options){
  137. var nodata = {
  138. friend: "该分组下暂无好友"
  139. ,group: "暂无群组"
  140. ,history: "暂无任何消息"
  141. };
  142. options = options || {};
  143. //如果是历史记录,则读取排序好的数据
  144. if(options.type === 'history'){
  145. options.item = options.item || 'd.sortHistory';
  146. }
  147. return ['{{# var length = 0; layui.each('+ options.item +', function(i, data){ length++; }}'
  148. ,'<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>'
  149. ,'{{# }); if(length === 0){ }}'
  150. ,'<li class="layim-null">'+ (nodata[options.type] || "暂无数据") +'</li>'
  151. ,'{{# } }}'].join('');
  152. };
  153. //公共面板
  154. var comTpl = function(tpl, anim, back){
  155. return ['<div class="layim-panel'+ (anim ? ' layui-m-anim-left' : '') +'">'
  156. ,'<div class="layim-title" style="background-color: {{d.base.chatTitleColor}};">'
  157. ,'<p>'
  158. ,(back ? '<i class="layui-icon layim-chat-back" layim-event="back">&#xe603;</i>' : '')
  159. ,'{{ d.title || d.base.title }}<span class="layim-chat-status"></span>'
  160. ,'{{# if(d.data){ }}'
  161. ,'{{# if(d.data.type === "group"){ }}'
  162. ,'<i class="layui-icon layim-chat-detail" layim-event="detail">&#xe613;</i>'
  163. ,'{{# } }}'
  164. ,'{{# } }}'
  165. ,'</p>'
  166. ,'</div>'
  167. ,'<div class="layui-unselect layim-content">'
  168. ,tpl
  169. ,'</div>'
  170. ,'</div>'].join('');
  171. };
  172. //主界面模版
  173. var elemTpl = ['<div class="layui-layim">'
  174. ,'<div class="layim-tab-content layui-show">'
  175. ,'<ul class="layim-list-friend">'
  176. ,'<ul class="layui-layim-list layui-show layim-list-history">'
  177. ,listTpl({
  178. type: 'history'
  179. })
  180. ,'</ul>'
  181. ,'</ul>'
  182. ,'</div>'
  183. ,'<div class="layim-tab-content">'
  184. ,'<ul class="layim-list-top">'
  185. ,'{{# if(d.base.isNewFriend){ }}'
  186. ,'<li layim-event="newFriend"><i class="layui-icon">&#xe654;</i>新的朋友<i class="layim-new" id="LAY_layimNewFriend"></i></li>'
  187. ,'{{# } if(d.base.isgroup){ }}'
  188. ,'<li layim-event="group"><i class="layui-icon">&#xe613;</i>群聊<i class="layim-new" id="LAY_layimNewGroup"></i></li>'
  189. ,'{{# } }}'
  190. ,'</ul>'
  191. ,'<ul class="layim-list-friend">'
  192. ,'{{# layui.each(d.friend, function(index, item){ var spread = d.local["spread"+index]; }}'
  193. ,'<li>'
  194. ,'<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>'
  195. ,'<ul class="layui-layim-list {{# if(spread === "true"){ }}'
  196. ,' layui-show'
  197. ,'{{# } }}">'
  198. ,listTpl({
  199. type: "friend"
  200. ,item: "item.list"
  201. ,index: "index"
  202. })
  203. ,'</ul>'
  204. ,'</li>'
  205. ,'{{# }); if(d.friend.length === 0){ }}'
  206. ,'<li><ul class="layui-layim-list layui-show"><li class="layim-null">暂无联系人</li></ul>'
  207. ,'{{# } }}'
  208. ,'</ul>'
  209. ,'</div>'
  210. ,'<div class="layim-tab-content">'
  211. ,'<ul class="layim-list-top">'
  212. ,'{{# layui.each(d.base.moreList, function(index, item){ }}'
  213. ,'<li layim-event="moreList" lay-filter="{{ item.alias }}">'
  214. ,'<i class="layui-icon {{item.iconClass||\"\"}}">{{item.iconUnicode||""}}</i>{{item.title}}<i class="layim-new" id="LAY_layimNew{{ item.alias }}"></i>'
  215. ,'</li>'
  216. ,'{{# }); if(!d.base.copyright){ }}'
  217. ,'<li layim-event="about"><i class="layui-icon">&#xe60b;</i>关于<i class="layim-new" id="LAY_layimNewAbout"></i></li>'
  218. ,'{{# } }}'
  219. ,'</ul>'
  220. ,'</div>'
  221. ,'</div>'
  222. ,'<ul class="layui-unselect layui-layim-tab">'
  223. ,'<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>'
  224. ,'<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>'
  225. ,'<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>'
  226. ,'</ul>'].join('');
  227. //聊天主模板
  228. var elemChatTpl = ['<div class="layim-chat layim-chat-{{d.data.type}}">'
  229. ,'<div class="layim-chat-main">'
  230. ,'<ul></ul>'
  231. ,'</div>'
  232. ,'<div class="layim-chat-footer">'
  233. ,'<div class="layim-chat-send"><input type="text" autocomplete="off"><button class="layim-send layui-disabled" layim-event="send">发送</button></div>'
  234. ,'<div class="layim-chat-tool" data-json="{{encodeURIComponent(JSON.stringify(d.data))}}">'
  235. ,'<span class="layui-icon layim-tool-face" title="选择表情" layim-event="face">&#xe60c;</span>'
  236. ,'{{# if(d.base && d.base.uploadImage){ }}'
  237. ,'<span class="layui-icon layim-tool-image" title="上传图片" layim-event="image">&#xe60d;<input type="file" name="file" accept="image/*"></span>'
  238. ,'{{# }; }}'
  239. ,'{{# if(d.base && d.base.uploadFile){ }}'
  240. ,'<span class="layui-icon layim-tool-image" title="发送文件" layim-event="image" data-type="file">&#xe61d;<input type="file" name="file"></span>'
  241. ,'{{# }; }}'
  242. ,'{{# layui.each(d.base.tool, function(index, item){ }}'
  243. ,'<span class="layui-icon {{item.iconClass||\"\"}} layim-tool-{{item.alias}}" title="{{item.title}}" layim-event="extend" lay-filter="{{ item.alias }}">{{item.iconUnicode||""}}</span>'
  244. ,'{{# }); }}'
  245. ,'</div>'
  246. ,'</div>'
  247. ,'</div>'].join('');
  248. //补齐数位
  249. var digit = function(num){
  250. return num < 10 ? '0' + (num|0) : num;
  251. };
  252. //转换时间
  253. layui.data.date = function(timestamp){
  254. var d = new Date(timestamp||new Date());
  255. return digit(d.getMonth() + 1) + '-' + digit(d.getDate())
  256. + ' ' + digit(d.getHours()) + ':' + digit(d.getMinutes());
  257. };
  258. //转换内容
  259. layui.data.content = function(content){
  260. //支持的html标签
  261. var html = function(end){
  262. 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');
  263. };
  264. content = (content||'').replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&amp;')
  265. .replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/'/g, '&#39;').replace(/"/g, '&quot;') //XSS
  266. .replace(/@(\S+)(\s+?|$)/g, '@<a href="javascript:;">$1</a>$2') //转义@
  267. .replace(/face\[([^\s\[\]]+?)\]/g, function(face){ //转义表情
  268. var alt = face.replace(/^face/g, '');
  269. return '<img alt="'+ alt +'" title="'+ alt +'" src="' + faces[alt] + '">';
  270. })
  271. .replace(/img\[([^\s]+?)\]/g, function(img){ //转义图片
  272. return '<img class="layui-layim-photos" src="' + img.replace(/(^img\[)|(\]$)/g, '') + '">';
  273. })
  274. .replace(/file\([\s\S]+?\)\[[\s\S]*?\]/g, function(str){ //转义文件
  275. var href = (str.match(/file\(([\s\S]+?)\)\[/)||[])[1];
  276. var text = (str.match(/\)\[([\s\S]*?)\]/)||[])[1];
  277. if(!href) return str;
  278. return '<a class="layui-layim-file" href="'+ href +'" download target="_blank"><i class="layui-icon">&#xe61e;</i><cite>'+ (text||href) +'</cite></a>';
  279. })
  280. .replace(/audio\[([^\s]+?)\]/g, function(audio){ //转义音频
  281. 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>';
  282. })
  283. .replace(/video\[([^\s]+?)\]/g, function(video){ //转义音频
  284. return '<div class="layui-unselect layui-layim-video" layim-event="playVideo" data-src="' + video.replace(/(^video\[)|(\]$)/g, '') + '"><i class="layui-icon">&#xe652;</i></div>';
  285. })
  286. .replace(/a\([\s\S]+?\)\[[\s\S]*?\]/g, function(str){ //转义链接
  287. var href = (str.match(/a\(([\s\S]+?)\)\[/)||[])[1];
  288. var text = (str.match(/\)\[([\s\S]*?)\]/)||[])[1];
  289. if(!href) return str;
  290. return '<a href="'+ href +'" target="_blank">'+ (text||href) +'</a>';
  291. }).replace(html(), '\<$1 $2\>').replace(html('/'), '\</$1\>') //转移HTML代码
  292. .replace(/\n/g, '<br>') //转义换行
  293. return content;
  294. };
  295. var elemChatMain = ['<li class="layim-chat-li{{ d.mine ? " layim-chat-mine" : "" }}">'
  296. ,'<div class="layim-chat-user"><img src="{{ d.avatar }}"><cite>'
  297. ,'{{ d.username||"佚名" }}'
  298. ,'</cite></div>'
  299. ,'<div class="layim-chat-text">{{ layui.data.content(d.content||"&nbsp;") }}</div>'
  300. ,'</li>'].join('');
  301. //处理初始化信息
  302. var cache = {message: {}, chat: []}, init = function(options){
  303. var init = options.init || {}
  304. mine = init.mine || {}
  305. ,local = layui.data('layim-mobile')[mine.id] || {}
  306. ,obj = {
  307. base: options
  308. ,local: local
  309. ,mine: mine
  310. ,history: local.history || []
  311. }, create = function(data){
  312. var mine = data.mine || {};
  313. var local = layui.data('layim-mobile')[mine.id] || {}, obj = {
  314. base: options //基础配置信息
  315. ,local: local //本地数据
  316. ,mine: mine //我的用户信息
  317. ,friend: data.friend || [] //联系人信息
  318. ,group: data.group || [] //群组信息
  319. ,history: local.history || [] //历史会话信息
  320. };
  321. obj.sortHistory = sort(obj.history, 'historyTime');
  322. cache = $.extend(cache, obj);
  323. popim(laytpl(comTpl(elemTpl)).render(obj));
  324. layui.each(call.ready, function(index, item){
  325. item && item(obj);
  326. });
  327. };
  328. cache = $.extend(cache, obj);
  329. if(options.brief){
  330. return layui.each(call.ready, function(index, item){
  331. item && item(obj);
  332. });
  333. };
  334. create(init)
  335. };
  336. //显示好友列表面板
  337. var layimMain, popim = function(content){
  338. return layer.open({
  339. type: 1
  340. ,shade: false
  341. ,shadeClose: false
  342. ,anim: -1
  343. ,content: content
  344. ,success: function(elem){
  345. layimMain = $(elem);
  346. fixIosScroll(layimMain.find('.layui-layim'));
  347. if(cache.base.tabIndex){
  348. events.tab($('.layui-layim-tab>li').eq(cache.base.tabIndex));
  349. }
  350. }
  351. });
  352. };
  353. //弹出公共面板
  354. var popPanel = function(options, anim){
  355. options = options || {};
  356. var data = $.extend({}, cache, {
  357. title: options.title||''
  358. ,data: options.data
  359. });
  360. return layer.open({
  361. type: 1
  362. ,shade: false
  363. ,shadeClose: false
  364. ,anim: -1
  365. ,content: laytpl(comTpl(options.tpl, anim === -1 ? false : true, true)).render(data)
  366. ,success: function(elem){
  367. var othis = $(elem);
  368. othis.prev().find('.layim-panel').addClass('layui-m-anim-lout');
  369. options.success && options.success(elem);
  370. options.isChat || fixIosScroll(othis.find('.layim-content'));
  371. }
  372. ,end: options.end
  373. });
  374. }
  375. //显示聊天面板
  376. var layimChat, layimMin, To = {}, popchat = function(data, anim, back){
  377. data = data || {};
  378. if(!data.id){
  379. return layer.msg('非法用户');
  380. }
  381. layer.close(popchat.index);
  382. return popchat.index = popPanel({
  383. tpl: elemChatTpl
  384. ,data: data
  385. ,title: data.name
  386. ,isChat: !0
  387. ,success: function(elem){
  388. layimChat = $(elem);
  389. hotkeySend();
  390. viewChatlog();
  391. delete cache.message[data.type + data.id]; //剔除缓存消息
  392. showNew('Msg');
  393. //聊天窗口的切换监听
  394. var thatChat = thisChat(), chatMain = thatChat.elem.find('.layim-chat-main');
  395. layui.each(call.chatChange, function(index, item){
  396. item && item(thatChat);
  397. });
  398. fixIosScroll(chatMain);
  399. //输入框获取焦点
  400. thatChat.textarea.on('focus', function(){
  401. setTimeout(function(){
  402. chatMain.scrollTop(chatMain[0].scrollHeight + 1000);
  403. }, 500);
  404. });
  405. }
  406. ,end: function(){
  407. layimChat = null;
  408. sendMessage.time = 0;
  409. }
  410. }, anim);
  411. };
  412. //修复IOS设备在边界引发无法滚动的问题
  413. var fixIosScroll = function(othis){
  414. if(device.ios){
  415. othis.on('touchmove', function(e){
  416. var top = othis.scrollTop();
  417. if(top <= 0){
  418. othis.scrollTop(1);
  419. e.preventDefault(e);
  420. }
  421. if(this.scrollHeight - top - othis.height() <= 0){
  422. othis.scrollTop(othis.scrollTop() - 1);
  423. e.preventDefault(e);
  424. }
  425. });
  426. }
  427. };
  428. //同步置灰状态
  429. var syncGray = function(data){
  430. $('.layim-'+data.type+data.id).each(function(){
  431. if($(this).hasClass('layim-list-gray')){
  432. layui.layim.setFriendStatus(data.id, 'offline');
  433. }
  434. });
  435. };
  436. //获取当前聊天面板
  437. var thisChat = function(){
  438. if(!layimChat) return {};
  439. var cont = layimChat.find('.layim-chat');
  440. var to = JSON.parse(decodeURIComponent(cont.find('.layim-chat-tool').data('json')));
  441. return {
  442. elem: cont
  443. ,data: to
  444. ,textarea: cont.find('input')
  445. };
  446. };
  447. //将对象按子对象的某个key排序
  448. var sort = function(data, key, asc){
  449. var arr = []
  450. ,compare = function (obj1, obj2) {
  451. var value1 = obj1[key];
  452. var value2 = obj2[key];
  453. if (value2 < value1) {
  454. return -1;
  455. } else if (value2 > value1) {
  456. return 1;
  457. } else {
  458. return 0;
  459. }
  460. };
  461. layui.each(data, function(index, item){
  462. arr.push(item);
  463. });
  464. arr.sort(compare);
  465. if(asc) arr.reverse();
  466. return arr;
  467. };
  468. //记录历史会话
  469. var setHistory = function(data){
  470. var local = layui.data('layim-mobile')[cache.mine.id] || {};
  471. var obj = {}, history = local.history || {};
  472. var is = history[data.type + data.id];
  473. if(!layimMain) return;
  474. var historyElem = layimMain.find('.layim-list-history');
  475. data.historyTime = new Date().getTime();
  476. data.sign = data.content;
  477. history[data.type + data.id] = data;
  478. local.history = history;
  479. layui.data('layim-mobile', {
  480. key: cache.mine.id
  481. ,value: local
  482. });
  483. var msgItem = historyElem.find('.layim-'+ data.type + data.id)
  484. ,msgNums = (cache.message[data.type+data.id]||[]).length //未读消息数
  485. ,showMsg = function(){
  486. msgItem = historyElem.find('.layim-'+ data.type + data.id);
  487. msgItem.find('p').html(data.content);
  488. if(msgNums > 0){
  489. msgItem.find('.layim-msg-status').html(msgNums).addClass(SHOW);
  490. }
  491. };
  492. if(msgItem.length > 0){
  493. showMsg();
  494. historyElem.prepend(msgItem.clone());
  495. msgItem.remove();
  496. } else {
  497. obj[data.type + data.id] = data;
  498. var historyList = laytpl(listTpl({
  499. type: 'history'
  500. ,item: 'd.data'
  501. })).render({data: obj});
  502. historyElem.prepend(historyList);
  503. showMsg();
  504. historyElem.find('.layim-null').remove();
  505. }
  506. showNew('Msg');
  507. };
  508. //标注底部导航新动态徽章
  509. var showNew = function(alias, show){
  510. if(!show){
  511. var show;
  512. layui.each(cache.message, function(){
  513. show = true;
  514. return false;
  515. });
  516. }
  517. $('#LAY_layimNew'+alias)[show ? 'addClass' : 'removeClass'](SHOW);
  518. };
  519. //发送消息
  520. var sendMessage = function(){
  521. var data = {
  522. username: cache.mine ? cache.mine.username : '访客'
  523. ,avatar: cache.mine ? cache.mine.avatar : (layui.cache.dir+'css/pc/layim/skin/logo.jpg')
  524. ,id: cache.mine ? cache.mine.id : null
  525. ,mine: true
  526. };
  527. var thatChat = thisChat(), ul = thatChat.elem.find('.layim-chat-main ul');
  528. var To = thatChat.data, maxLength = cache.base.maxLength || 3000;
  529. var time = new Date().getTime(), textarea = thatChat.textarea;
  530. data.content = textarea.val();
  531. if(data.content === '') return;
  532. if(data.content.length > maxLength){
  533. return layer.msg('内容最长不能超过'+ maxLength +'个字符')
  534. }
  535. if(time - (sendMessage.time||0) > 60*1000){
  536. ul.append('<li class="layim-chat-system"><span>'+ layui.data.date() +'</span></li>');
  537. sendMessage.time = time;
  538. }
  539. ul.append(laytpl(elemChatMain).render(data));
  540. var param = {
  541. mine: data
  542. ,to: To
  543. }, message = {
  544. username: param.mine.username
  545. ,avatar: param.mine.avatar
  546. ,id: To.id
  547. ,type: To.type
  548. ,content: param.mine.content
  549. ,timestamp: time
  550. ,mine: true
  551. };
  552. pushChatlog(message);
  553. layui.each(call.sendMessage, function(index, item){
  554. item && item(param);
  555. });
  556. To.content = data.content;
  557. setHistory(To);
  558. chatListMore();
  559. textarea.val('');
  560. textarea.next().addClass('layui-disabled');
  561. };
  562. //消息声音提醒
  563. var voice = function() {
  564. var audio = document.createElement("audio");
  565. audio.src = layui.cache.dir+'css/modules/layim/voice/'+ cache.base.voice;
  566. audio.play();
  567. };
  568. //接受消息
  569. var messageNew = {}, getMessage = function(data){
  570. data = data || {};
  571. var group = {}, thatChat = thisChat(), thisData = thatChat.data || {}
  572. ,isThisData = thisData.id == data.id && thisData.type == data.type; //是否当前打开联系人的消息
  573. data.timestamp = data.timestamp || new Date().getTime();
  574. data.system || pushChatlog(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. doc.off('touchend', events.faceHide).on('touchend', events.faceHide);
  893. } else {
  894. doc.off('click', events.faceHide).on('click', events.faceHide);
  895. }
  896. stope(e);
  897. } ,faceHide: function(){
  898. layer.close(layer.popBottom.index);
  899. $(document).off('touchend', events.faceHide)
  900. .off('click', events.faceHide);
  901. }
  902. //图片或一般文件
  903. ,image: function(othis){
  904. var type = othis.data('type') || 'images', api = {
  905. images: 'uploadImage'
  906. ,file: 'uploadFile'
  907. }
  908. ,thatChat = thisChat(), conf = cache.base[api[type]] || {};
  909. upload({
  910. url: conf.url || ''
  911. ,method: conf.type
  912. ,elem: othis.find('input')[0]
  913. ,unwrap: true
  914. ,type: type
  915. ,success: function(res){
  916. if(res.code == 0){
  917. res.data = res.data || {};
  918. if(type === 'images'){
  919. focusInsert(thatChat.textarea[0], 'img['+ (res.data.src||'') +']');
  920. } else if(type === 'file'){
  921. focusInsert(thatChat.textarea[0], 'file('+ (res.data.src||'') +')['+ (res.data.name||'下载文件') +']');
  922. }
  923. sendMessage();
  924. } else {
  925. layer.msg(res.msg||'上传失败');
  926. }
  927. }
  928. });
  929. }
  930. //扩展工具栏
  931. ,extend: function(othis){
  932. var filter = othis.attr('lay-filter')
  933. ,thatChat = thisChat();
  934. layui.each(call['tool('+ filter +')'], function(index, item){
  935. item && item.call(othis, function(content){
  936. focusInsert(thatChat.textarea[0], content);
  937. }, sendMessage, thatChat);
  938. });
  939. }
  940. //弹出新的朋友面板
  941. ,newFriend: function(){
  942. layui.each(call.newFriend, function(index, item){
  943. item && item();
  944. });
  945. }
  946. //弹出群组面板
  947. ,group: function(){
  948. popPanel({
  949. title: '群聊'
  950. ,tpl: ['<div class="layui-layim-list layim-list-group">'
  951. ,listTpl({
  952. type: 'group'
  953. ,item: 'd.group'
  954. })
  955. ,'</div>'].join('')
  956. ,data: {}
  957. });
  958. }
  959. //查看群组成员
  960. ,detail: function(){
  961. var thatChat = thisChat();
  962. layui.each(call.detail, function(index, item){
  963. item && item(thatChat.data);
  964. });
  965. }
  966. //播放音频
  967. ,playAudio: function(othis){
  968. var audioData = othis.data('audio')
  969. ,audio = audioData || document.createElement('audio')
  970. ,pause = function(){
  971. audio.pause();
  972. othis.removeAttr('status');
  973. othis.find('i').html('&#xe652;');
  974. };
  975. if(othis.data('error')){
  976. return layer.msg('播放音频源异常');
  977. }
  978. if(!audio.play){
  979. return layer.msg('您的浏览器不支持audio');
  980. }
  981. if(othis.attr('status')){
  982. pause();
  983. } else {
  984. audioData || (audio.src = othis.data('src'));
  985. audio.play();
  986. othis.attr('status', 'pause');
  987. othis.data('audio', audio);
  988. othis.find('i').html('&#xe651;');
  989. //播放结束
  990. audio.onended = function(){
  991. pause();
  992. };
  993. //播放异常
  994. audio.onerror = function(){
  995. layer.msg('播放音频源异常');
  996. othis.data('error', true);
  997. pause();
  998. };
  999. }
  1000. }
  1001. //播放视频
  1002. ,playVideo: function(othis){
  1003. var videoData = othis.data('src')
  1004. ,video = document.createElement('video');
  1005. if(!video.play){
  1006. return layer.msg('您的浏览器不支持video');
  1007. }
  1008. layer.close(events.playVideo.index);
  1009. events.playVideo.index = layer.open({
  1010. type: 1
  1011. ,anim: false
  1012. ,style: 'width: 100%; height: 50%;'
  1013. ,content: '<div style="background-color: #000; height: 100%;"><video style="position: absolute; width: 100%; height: 100%;" src="'+ videoData +'" autoplay="autoplay"></video></div>'
  1014. });
  1015. }
  1016. //聊天记录
  1017. ,chatLog: function(othis){
  1018. var thatChat = thisChat();
  1019. layui.each(call.chatlog, function(index, item){
  1020. item && item(thatChat.data, thatChat.elem.find('.layim-chat-main>ul'));
  1021. });
  1022. }
  1023. //更多列表
  1024. ,moreList: function(othis){
  1025. var filter = othis.attr('lay-filter');
  1026. layui.each(call.moreList, function(index, item){
  1027. item && item({
  1028. alias: filter
  1029. });
  1030. });
  1031. }
  1032. //关于
  1033. ,about: function(){
  1034. layer.open({
  1035. 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>'
  1036. ,className: 'layim-about'
  1037. ,shadeClose: false
  1038. ,btn: '我知道了'
  1039. });
  1040. }
  1041. };
  1042. //暴露接口
  1043. exports('layim-mobile', new LAYIM());
  1044. }).addcss(
  1045. 'modules/layim/mobile/layim.css?v=2.10'
  1046. ,'skinlayim-mobilecss'
  1047. );