MUI——聊天窗口

前言

前段时间使用MUI做了个APP,客户提出想法,能否增加一个可以聊天的功能,用来保证工单系统中所有参与角色的交流。

好的,安排!

聊天布局分析

需要实现一个可供聊天的窗口,需要优先分析其布局的构成。

参考QQ或者微信的聊天群,大致可以看出具有以下的共通点:

  • 1、群聊窗口需要有信息展示窗口。
  • 2、自己发送的消息显示在右边,别人发送的聊天消息显示在左边。
  • 3、需要有一个触发软键盘的输入框,和一个发送一个按钮。
  • 4、每个人有不同的信息,需要采取token进行判断。

除此之外,像语音聊天和动画表情等,现目前暂不做考虑,先实现简单的聊天窗口和信息展示吧。

页面布局

在MUI的界面模板中,存在一个im-chat.html聊天页面布局案例,参照其功能,去掉繁琐的其他功能选项。

消息展示模板

在案例中,存在一个模板类,当消息是自己发送时,将信息展示并显示在屏幕的右方,否则就显示于屏幕左方。

<script id='msg-template' type="text/template">
	<!--遍历消息数组对象-->
	<% for(var i in record){ var item=record[i]; %>
	<!-- 获取对象中的属性参数信息,判断当前消息的产生者,同时保存其他数据信息 -->
	<div class="msg-item <%= (item.sender=='self'?' msg-item-self':'') %>" msg-type='<%=(item.type)%>' msg-content='<%=(item.content)%>'>
		<!--消息时间显示-->
		<% if(item.time){ %>
		<div class="" style="text-align: center;">
			<span style="color: #C7C7CC;font-size: 12px;" id="chatTime">
				<%= item.time %>
			</span>
		</div>
		<% } %>

		<!--聊天内容头像-->
		<% if(item.sender=='self' ) { %>
		<i class="msg-user">
			<span style="display: block;font-size: 10px;overflow:hidden;line-height: 30px;" >
				<%= item.username %>
			</span>
		</i>
		<% } else { %>
		<!--<img class="msg-user-img" src="../images/logo.png" alt="" />-->
		<i class="msg-user-img">
			<span style="display: block;font-size: 10px;overflow:hidden;line-height: 30px;">
				<%= item.username %>
			</span>
		</i>
		<% } %>

		<!--聊天内容-->
		<div class="msg-content">
			<div class="msg-content-inner">
				<% if(item.type=='text' ) { %>
				<%=( item.content|| '&nbsp;&nbsp;') %>
				<% } %>
			</div>
			<!--div聊天内容边框布局-->
			<div class="msg-content-arrow"></div>
		</div>
		
		<!--每条聊天消息之间空格开-->
		<div class="mui-item-clear"></div>
		
	</div>
	<% } %>
</script>

消息展示和消息输入框,发送按钮布局

<!--中间内容显示-->
<div class="mui-content">
	<div id='msg-list'>
	</div>
</div>
<!--底部-->
<footer>
	<!--图片-->
	<div class="footer-left">
		<!--去除照片功能,保留布局-->
	</div>
	<!--输入框-->
	<div class="footer-center">
		<textarea id='msg-text' type="text" class='input-text'></textarea>
	</div>
	<!--图标  点击事件是label标签-->
	<label for="" class="footer-right">
		<i id='msg-type' class="mui-icon mui-icon-paperplane"></i>
	</label>
</footer>

完整页面布局和逻辑分析

上面截取部分ui显示布局的注意点,接下来展示完整的布局和其他js操作逻辑。

<!doctype html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<link href="../css/mui.min.css" rel="stylesheet" />
		<style type="text/css">
			html,
			body {
				height: 100%;
				margin: 0px;
				padding: 0px;
				overflow: hidden;
				-webkit-touch-callout: none;
				-webkit-user-select: none;
			}
			
			footer {
				position: fixed;
				width: 100%;
				height: 50px;
				min-height: 50px;
				border-top: solid 1px #bbb;
				left: 0px;
				bottom: 0px;
				overflow: hidden;
				padding: 0px 50px;
				background-color: #fafafa;
			}
			
			.footer-left {
				position: absolute;
				width: 50px;
				height: 50px;
				left: 0px;
				bottom: 0px;
				text-align: center;
				vertical-align: middle;
				line-height: 100%;
				padding: 12px 4px;
			}
			
			.footer-right {
				position: absolute;
				width: 50px;
				height: 50px;
				right: 0px;
				bottom: 0px;
				text-align: center;
				vertical-align: middle;
				line-height: 100%;
				padding: 12px 5px;
				display: inline-block;
			}
			
			.footer-center {
				height: 100%;
				padding: 5px 0px;
			}
			
			.footer-center [class*=input] {
				width: 100%;
				height: 100%;
				border-radius: 5px;
			}
			
			.footer-center .input-text {
				background: #fff;
				border: solid 1px #ddd;
				padding: 10px !important;
				font-size: 16px !important;
				line-height: 18px !important;
				font-family: verdana !important;
				overflow: hidden;
			}
			
			.footer-center .input-sound {
				background-color: #eee;
			}
			
			.mui-content {
				height: 100%;
				padding: 44px 0px 50px 0px;
				/*消息较多时,增加滚动条*/
				overflow: auto;
				background-color: #eaeaea;
			}
			
			#msg-list {
				height: 100%;
				overflow: auto;
				-webkit-overflow-scrolling: touch;
			}
			
			.msg-item {
				padding: 8px;
				clear: both;
			}
			
			.msg-item .mui-item-clear {
				clear: both;
			}
			/*.msg-item .msg-user {
				width: 38px;
				height: 38px;
				border: solid 1px #d3d3d3;
				display: inline-block;
				background: #fff;
				border-radius: 3px;
				vertical-align: top;
				text-align: center;*/
			/*不是自己发送的消息   向左浮动显示*/
			/*float: left;
				padding: 3px;
				color: #ddd;
			}*/
			
			.msg-item .msg-user {
				width: 40px;
				height: 40px;
				border: solid 1px #d3d3d3;
				display: inline-block;
				background: #fff;
				border-radius: 4px;
				vertical-align: top;
				text-align: center;
				/*不是自己发送的消息   向左浮动显示*/
				float: left;
				padding: 3px;
				background-color: #ddd;
			}
			
			.msg-item .msg-user-img {
				width: 40px;
				height: 40px;
				border: solid 1px #d3d3d3;
				display: inline-block;
				border-radius: 4px;
				vertical-align: top;
				text-align: center;
				float: left;
				padding: 3px;
				background-color: #ddd;
			}
			
			.msg-item .msg-content {
				display: inline-block;
				border-radius: 5px;
				border: solid 1px #d3d3d3;
				background-color: #FFFFFF;
				color: #333;
				padding: 8px;
				vertical-align: top;
				font-size: 15px;
				position: relative;
				margin: 0px 8px;
				max-width: 75%;
				min-width: 35px;
				float: left;
			}
			
			.msg-item .msg-content .msg-content-inner {
				overflow-x: hidden;
			}
			
			.msg-item .msg-content .msg-content-arrow {
				position: absolute;
				border: solid 1px #d3d3d3;
				border-right: none;
				border-top: none;
				background-color: #FFFFFF;
				width: 10px;
				height: 10px;
				left: -5px;
				top: 12px;
				-webkit-transform: rotateZ(45deg);
				transform: rotateZ(45deg);
			}
			/*自身发送小心  最后都进行了向右浮动显示*/
			
			.msg-item-self .msg-user,
			.msg-item-self .msg-content {
				float: right;
			}
			
			.msg-item-self .msg-content .msg-content-arrow {
				left: auto;
				right: -5px;
				-webkit-transform: rotateZ(225deg);
				transform: rotateZ(225deg);
			}
			
			.msg-item-self .msg-content,
			.msg-item-self .msg-content .msg-content-arrow {
				background-color: #4CD964;
				color: #fff;
				border-color: #2AC845;
			}
			
			footer .mui-icon {
				color: #000;
			}
			
			footer .mui-icon:active {
				color: #007AFF !important;
			}
			/*伪类  before  表示将content内容放在class内容之前*/
			
			footer .mui-icon-paperplane:before {
				content: "发送";
			}
			
			footer .mui-icon-paperplane {
				/*-webkit-transform: rotateZ(45deg);
				transform: rotateZ(45deg);*/
				font-size: 16px;
				word-break: keep-all;
				line-height: 100%;
				padding-top: 6px;
				color: rgba(0, 135, 250, 1);
			}
			/*语音*/
			
			#msg-sound {
				-webkit-user-select: none !important;
				user-select: none !important;
			}
			
			@-webkit-keyframes spin {
				0% {
					-webkit-transform: rotate(0deg);
				}
				100% {
					-webkit-transform: rotate(360deg);
				}
			}
			
			@keyframes spin {
				0% {
					transform: rotate(0deg);
				}
				100% {
					transform: rotate(360deg);
				}
			}
			
			#h {
				background: #fff;
				border: solid 1px #ddd;
				padding: 10px !important;
				font-size: 16px !important;
				font-family: verdana !important;
				line-height: 18px !important;
				overflow: visible;
				position: absolute;
				left: -1000px;
				right: 0px;
				word-break: break-all;
				word-wrap: break-word;
			}
			
			.cancel {
				background-color: darkred;
			}
		</style>
		<header class="mui-bar mui-bar-nav">
			<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
			<!--聊天组中各个角色名称信息(长度超长使用"..."代替)-->
			<h1 class="mui-title" id="headerText"></h1>
		</header>
	</head>

	<body contextmenu="return false;">
		<pre id='h'></pre>

		<script id='msg-template' type="text/template">
			<% for(var i in record){ var item=record[i]; %>
			<div class="msg-item <%= (item.sender=='self'?' msg-item-self':'') %>" msg-type='<%=(item.type)%>' msg-content='<%=(item.content)%>'>
				<!--消息时间显示-->
				<% if(item.time){ %>
				<div class="" style="text-align: center;">
					<span style="color: #C7C7CC;font-size: 12px;" id="chatTime">
						<%= item.time %>
					</span>
				</div>
				<% } %>

				<!--聊天内容头像-->
				<% if(item.sender=='self' ) { %>
				<i class="msg-user">
					<span style="display: block;font-size: 10px;overflow:hidden;line-height: 30px;" >
						<%= item.username %>
					</span>
				</i>
				<% } else { %>
				<!--<img class="msg-user-img" src="../images/logo.png" alt="" />-->
				<i class="msg-user-img">
					<span style="display: block;font-size: 10px;overflow:hidden;line-height: 30px;">
						<%= item.username %>
					</span>
				</i>
				<% } %>

				<!--聊天内容-->
				<div class="msg-content">
					<div class="msg-content-inner">
						<% if(item.type=='text' ) { %>
						<%=( item.content|| '&nbsp;&nbsp;') %>
						<% } %>
					</div>
					<!--div聊天内容边框布局-->
					<div class="msg-content-arrow"></div>
				</div>
				<!--每条聊天消息之间空格开-->
				<div class="mui-item-clear"></div>
			</div>
			<% } %>
		</script>
		<!--中间内容显示-->
		<div class="mui-content">
			<div id='msg-list'>
			</div>
		</div>
		<!--底部-->
		<footer>
			<!--图片-->
			<div class="footer-left">
				<!--去除照片功能,保留布局-->
			</div>
			<!--输入框-->
			<div class="footer-center">
				<textarea id='msg-text' type="text" class='input-text'></textarea>
			</div>
			<!--图标  点击事件是label标签-->
			<label for="" class="footer-right">
				<i id='msg-type' class="mui-icon mui-icon-paperplane"></i>
			</label>
		</footer>
		<script src="../js/apps.js" type="text/javascript" charset="utf-8"></script>
		<script src="../js/moment.min.js" type="text/javascript" charset="utf-8"></script>
		<script src="../js/mui.min.js"></script>
		<script src="../js/arttmpl.js"></script>
		<script type="text/javascript" charset="utf-8">
			(function($, doc) {
				$.init({
					//手势事件
					gestureConfig: {
						tap: true, //默认为true
						doubletap: true, //默认为false
						longtap: true, //默认为false
						swipe: true, //默认为true
						drag: true, //默认为true
						hold: true, //默认为false,不监听
						release: true //默认为false,不监听
					}
				});
				
				//js/arttmpl.js  必须引入该js文件
				template.config('escape', false);

				$.plusReady(function() {
					var self = plus.webview.currentWebview();
					var workId = self.workId;
					var workName = self.workName;
					
					//聊天框标题部分显示任务名称
					if(typeof(workName) == "undefined") {
						mui.alert("任务名称获取失败,请重新进入此页面", '温馨提示','确定',function(){},'div');
						return;
					}
					if(workName.length > 6) {
						workName = workName.substring(0, 5) + "...";
						console.log("修改拼接后的任务名称:" + workName);
					}
					document.getElementById("headerText").innerText = workName;

					//定义全局数组,每次消息接收或发送成功  都存储id至数组中(避免不断请求服务器获取数据信息重复显示)
					var chatMsgIdArray = new Array();
					
					// 输入框软键盘样式
					plus.webview.currentWebview().setStyle({
						softinputMode: "adjustResize"
					});

					

					//服务器先请求消息记录
					getChatMsg(workId, "");
					//定时刷新(每次传入的是当前最大对话id值)
					// 这里原则上使用 websocket 技术比较好
					setInterval(function() {
						getChatMsg(workId, "");
					}, 3500);

					//显示键盘
					// var showKeyboard = function() {
					// 	var Context = plus.android.importClass("android.content.Context");
					// 	var InputMethodManager = plus.android.importClass("android.view.inputmethod.InputMethodManager");
					// 	var main = plus.android.runtimeMainActivity();
					// 	var imm = main.getSystemService(Context.INPUT_METHOD_SERVICE);
					// 	imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);
					// 	//var view = ((ViewGroup)main.findViewById(android.R.id.content)).getChildAt(0);
					// 	imm.showSoftInput(main.getWindow().getDecorView(), InputMethodManager.SHOW_IMPLICIT);
					// 	//alert("ll");
					// };
					
					//记录信息(消息对象体,包含消息发送者和发送的内容,消息样式等,目前只考虑 text)
					var record = [
						//显示格式
						/*{
							sender: 'zs',
							type: 'text',
							content: 'Hi,我是 MUI 小管家!'
						}*/
					];
					
					// 有点类似面向对象,这里是将各个节点对象进行获取、保存、封装
					var ui = {
						body: doc.querySelector('body'),
						footer: doc.querySelector('footer'),
						footerRight: doc.querySelector('.footer-right'),
						btnMsgType: doc.querySelector('#msg-type'),
						boxMsgText: doc.querySelector('#msg-text'),
						areaMsgList: doc.querySelector('#msg-list'),
						h: doc.querySelector('#h'),
						content: doc.querySelector('.mui-content')
					};
					
					// 设置每条消息的分割
					ui.h.style.width = ui.boxMsgText.offsetWidth + 'px';
					//alert(ui.boxMsgText.offsetWidth );--261
					
					var footerPadding = ui.footer.offsetHeight - ui.boxMsgText.offsetHeight;

					//显示消息内容函数,同时重新计算消息显示区域的 scrollTop 属性值
					var bindMsgList = function() {
						//先调用template处理(避免乱码)  将信息显示在areaMsgList中
						//msg-template  遍历循环的script的id
						ui.areaMsgList.innerHTML = template('msg-template', {
							"record": record
						});
						//每次发送数据后   都应该显示至最新消息处
						ui.areaMsgList.scrollTop = ui.areaMsgList.scrollHeight + ui.areaMsgList.offsetHeight;
					};
					
					// 调用函数,显示内容
					bindMsgList();
					
					//界面窗口改变时的监听
					window.addEventListener('resize', function() {
						ui.areaMsgList.scrollTop = ui.areaMsgList.scrollHeight + ui.areaMsgList.offsetHeight;
					}, false);
					
					//发送消息函数
					var send = function(msg) {
						// 消息对象数组保存新的消息信息
						record.push(msg);
						// 发送消息,同时重新计算 scrollTop  属性
						bindMsgList();
						//服务器通信 (多热群聊,需要将消息传递给服务器)
						toRobot(msg.content);
					};
					
					//服务器请求  发送聊天信息
					var toRobot = function(info) {
						//发送对话
						console.log("发送对话内容:" + info);
						mui.ajax(config.host1 + "/work/workdialog/chat", {
							data: {
								workid: workId,
								content: info
							},
							dataType: "json",
							type: "post",
							timeout: 10000,
							headers: {
								'Content-Type': 'application/json',
								'authorization': localStorage.getItem('accessToken')
							},
							success: function(res) {
								console.log("发送成功返回:" + JSON.stringify(res));
								// 这里应该还需要考虑重发机制
							},
							error: function(xhr, type, errorThrown) {
								console.log(type);
								if(type == "abort") {
									mui.toast("请检查您的网络!");
								} else if(type == "timeout") {
									mui.toast("消息发送超时!");
								}
							}
						})
					};
					
					// 消息输入框 收到焦点
					function msgTextFocus() {
						ui.boxMsgText.focus();
						// 延迟双收,保证输入框一定会获取到焦点
						setTimeout(function() {
							ui.boxMsgText.focus();
						}, 100);
					}
					
					//解决长按“发送”按钮,导致键盘关闭的问题;
					ui.footerRight.addEventListener('touchstart', function(event) {
						if(ui.btnMsgType.classList.contains('mui-icon-paperplane')) {
							msgTextFocus();
							event.preventDefault();
						}
					});
					
					//解决长按“发送”按钮,导致键盘关闭的问题;
					ui.footerRight.addEventListener('touchmove', function(event) {
						if(ui.btnMsgType.classList.contains('mui-icon-paperplane')) {
							msgTextFocus();
							event.preventDefault();
						}
					});

					//发送按钮  长按后释放   手势监听事件
					ui.footerRight.addEventListener('release', function(event) {
						if(ui.btnMsgType.classList.contains('mui-icon-paperplane')) {
							//showKeyboard();
							ui.boxMsgText.focus();
							setTimeout(function() {
								ui.boxMsgText.focus();
							}, 150);
							//event.detail.gesture.preventDefault();
							
							// 获取当前缓存中的登录账户用户名信息
							var userName = localStorage.getItem('userName');
							if(userName.length >= 2){
								userName = userName.substring(0,2)+"...";
							}
							//本机输入的先显示到界面上
							send({
								sender: 'self',
								type: 'text',
								//g全局匹配  i区分大小写  m多行匹配
								content: ui.boxMsgText.value.replace(new RegExp('\n', 'gm'), '<br/>'),
								username: userName,
								time: moment().format('HH:mm:ss')
							});
							//清空输入框中的数据
							ui.boxMsgText.value = '';
							$.trigger(ui.boxMsgText, 'input', null);
						}
					}, false);

					//监听输入框中内容改变时的事件信息
					ui.boxMsgText.addEventListener('input', function(event) {
						//原代码
						//ui.btnMsgType.classList[ui.boxMsgText.value == '' ? 'remove' : 'add']('mui-icon-paperplane');
						//将remove更改为add  不管input标签内是否有值   都显示"伪类" ----  即显示  "发送" 字样
						ui.btnMsgType.classList['add']('mui-icon-paperplane');
						ui.btnMsgType.setAttribute("for", ui.boxMsgText.value == '' ? '' : 'msg-text');
						ui.h.innerText = ui.boxMsgText.value.replace(new RegExp('\n', 'gm'), '\n-') || '-';
						ui.footer.style.height = (ui.h.offsetHeight + footerPadding) + 'px';
						console.log("重要参数offsetHeight:" + ui.h.offsetHeight);
						console.log("重要参数:" + footerPadding);
						ui.content.style.paddingBottom = ui.footer.style.height;
					});
					
					var focus = false;
					ui.boxMsgText.addEventListener('tap', function(event) {
						ui.boxMsgText.focus();
						setTimeout(function() {
							ui.boxMsgText.focus();
						}, 0);
						focus = true;
						setTimeout(function() {
							focus = false;
						}, 1000);
						event.detail.gesture.preventDefault();

					}, false);
					
					//点击消息列表,关闭键盘
					ui.areaMsgList.addEventListener('click', function(event) {
						if(!focus) {
							// 发送后消息输入框失去焦点
							ui.boxMsgText.blur();
						}
					})

					/**
					 * 请求服务器聊天消息记录
					 * @param {Object} taskIDs  任务id
					 * @param {Object} dialogIDs  上次最大对话id(每次获取都需要存本地数据库并在此读取)
					 */
					function getChatMsg(taskIDs, dialogIDs) {
						mui.ajax(config.host1 + "/work/workdialog/chat", {
							data: {
								workid: taskIDs
							},
							dataType: "json",
							type: "get",
							timeout: 10000,
							headers: {
								'Content-Type': 'application/x-www-form-urlencoded',
								'authorization': localStorage.getItem('accessToken')
							},
							success: function(res) {
								console.log(JSON.stringify(res));
								if(res.code != 200) {
									//发送失败(后续在做处理)
									return;
								} 
								
								// 解析数据信息
								var datas = res.data;
								// 遍历数据信息
								for (var i = 0; i < datas.length; i++) {
									var obj = datas[i];
									// 获取消息id信息
									var msgId = obj.id;
									// 判断消息是否已经存在数组中
									if(idWhetherInArray(msgId)){
										return;
									}
									// 如果不存在数组中,则将id信息保存至数组
									chatMsgIdArray.push(msgId);
									
									// 获取发送者名字
									var user_name = obj.user_name?obj.user_name:"暂无";
									if(user_name.length >= 2){
										user_name = user_name.substring(0,2)+"...";
									}
									
									var content = obj.content;
									var create_time = obj.create_time;
									var nowDate = moment().format('YYYY-MM-DD');
									var serviceDate = moment.unix(create_time).utcOffset(8).format('YYYY-MM-DD');
									
									//今日显示时间  他日显示具体日期和时间
                                    var times;
                                    if(nowDate == serviceDate) {
                                        times = moment.unix(create_time).utcOffset(8).format('HH:mm:ss');
                                    } else {
                                        times = moment.unix(create_time).utcOffset(8).format('YYYY-MM-DD  HH:mm:ss');
                                    }
                                    
                                    // 获取区别标识,判断消息是自己发送的还是别人发送的
                                    var is_mine = obj.is_mine;
                                    if(1 == is_mine){
                                    	// 自己发送的消息
                                    	record.push({
                                            sender: 'self',
                                            type: 'text',
                                            content: content,
                                            username: user_name,
                                            time: times
                                        });
                                    }else{
                                    	// 不是自己发送的,显示在视窗左边
                                    	record.push({
                                            sender: 'zs',
                                            type: 'text',
                                            content: content,
                                            username: user_name,
                                            time: times
                                        });
                                    }
                                    
                                    //没有这个 不会显示(放在判断里面--避免每次成功回调会让布局重新计算下,导致不能向上看历史消息)
                                    bindMsgList();
								}
							},
							error: function(xhr, type, errorThrown) {

							}
						})
					}

					//判断ajax回调中的id是否在数组中有同样的值
					function idWhetherInArray(id) {
						var isExist = false;
						for(var i = 0; i < chatMsgIdArray.length; i++) {
							if(chatMsgIdArray[i] == id) {
								isExist = true;
								break;
							}
						}
						return isExist;
					}


				});
			}(mui, document));
		</script>
	</body>

</html>

注意事项

必须保证导入arttmpl.js文件。

获取详细库

Hbuiler编辑器右键创建APP应用,选择MUI 样式模板即可。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值