自己动手搭建聊天APP

自己动手实现聊天APP

成果

启动页
登录页面
联系人页面
在这里插入图片描述
发现页面
我的页面
尬友信息页面
尬友聊天页面
尬友聊天页面

我的二维码页面
扫一扫页面
搜索页面

关于页面

开始

时隔两年多,想再次看看 app 的开发。还记得两年前辛苦使用 andro studio 写 xml 的日子,五味杂陈。网上走了一圈,发现 dcloud 公司推出了 uni-app 和 5 + app 的方式开发 app , 为了知道这些方式和 andro studio 开发 app 的区别。我开始了探寻。

uni-app 和 5 + app

uni-app 最大的特点便是 编写一套代码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用 ,并且 基于vue 。听上去方便不少哦。

5 + app 是 HTML5 Plus移动App的意思。HTML5 plus(即HTML5 +)是W3C提供一套规范,属于工信部。扩展了 JavaScript 对象 plus,使得js可以调用摄像头、陀螺仪、文件系统等。HBuilder内置HTML5 + APP开发环境,可以在云端将代码打包为 apk 等文件。

经过测试发现 uni-app 编译过程稍微有点漫长,对于开发来说测试有点费时间。最终我选择 5 + app 的方式开发APP。

mui

打开 hbuilder ,新建项目可以发现有 mui 项目模板。

mui

mui 是 dcloud 公司推出的 5 + app 开发的一款 ui 框架 ,听说性能最接近原生APP。 官网是 【mui】,官网提供了大量示例,可以参考(也可以粘贴复制哦)。但总的来说文档不是很丰富吧,有些功能我还是在百度解决。

尬聊APP

来源

在学习了两天的 mui api后我想尝试写个 app 来锻炼下自己,也为了对比 5 + app 和 android studio 开发上的深层区别和感受,再加上之前对于 即时聊天 app 的 实现感兴趣,想摸索一番,于是取了这个名字便开始了开发。

技术选型

后端大致选择 springboot + netty + mybatisplus
前端大致选择 mui + vue
数据库依旧选择 mysql(当然后边可以换其他数据库)

项目注意点

前端代码需要注意几个问题 :
1 mui 配合 vue 一起玩的时候 @click 会不起作用,只能用 mui 去绑定 tap 事件后调用 this.click() 方法,因为 mui 禁用了 click 事件 ,点击会触发 tap 事件。当然本页面暂未使用到 vue , 其他页面注意。解决代码如下:

mui(document.body).on('tap', '#btn_clcik', function(e) {
 this.click()
})

2 mui 配合 vue 一起玩的时候 v-model 双向绑定不起作用。暂时我使用 document 去获取值。比如获取输入框内容时:

document.getElementById('galiao_userId').value

3 如果自定义的 js 文件用到 mui 、vue、axios 中的对象或方法时,尽量后引入。比如:

<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
<script src="js/mui.min.js"></script>
<script src="js/app.js"></script>

4 尽量使用 v-text 代替 {{ }} 避免插值闪烁。

后端项目

构建

新建maven工程,pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.junan</groupId>
    <artifactId>galiao</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>galiao</name>
    <description>尬聊 v1 服务端</description>

    <properties>
        <java.version>1.8</java.version>
        <fastjson.version>1.2.58</fastjson.version>
        <mybatis-plus.version>3.2.0</mybatis-plus.version>
        <mybatisplus-generation.version>3.2.0</mybatisplus-generation.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatisplus-generation.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

项目结构

项目结构

核心

核心功能便是 netty 提供的服务了,在 netty 包下三个类:

netty包

GaLiaoServer 表示 Netty 服务的启动类。
GaLiaoServerInitializer 表示 Netty 服务的初始化类。
GaLiaoHandler 表示Netty 服务的处理器类。(这里面包含消息的接受和发送,它就是核心)

至于代码方面大家感兴趣可以去【尬聊开源后端项目

项目启动页(index.html)

前端代码:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title>首页</title>
		<link href="css/mui.min.css" rel="stylesheet" />
	</head>
	<body>

	</body>

	<script src="js/mui.min.js"></script>
	<script type="text/javascript" charset="utf-8">
		
		mui.init();

		mui.plusReady(function() {
			// 弹到登录页面
			mui.openWindow({
				url: 'login.html',
				id: 'login'
			});
		})

	</script>
</html>

这个页面暂时只是负责打开了登录页面,后期可以做成判断是否登录或自动登录,根据情况跳转到不同页面。

登录(login.html)

架构图

登录架构

主要还是使用 mvc ,这里暂时不用 netty 。登录成功返回 token,客户端保存 token ,携带 token 访问服务端资源,包含后期使用 netty 发送消息也需要携带 token 。

前端登录页面代码:

<!doctype html>
<html lang="en">
	<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 rel="stylesheet" type="text/css" href="css/mui.min.css" />
		<link rel="stylesheet" type="text/css" href="css/app.css" />
		<style>
			body {
			background-color: #FFFFFF;
		}

		.title {
			margin: 0;
			background-color: #FFFFFF;
		}
		.logo {
			width: 100px;
			height: 100px;
			border-radius: 10px;
		}
		.form {
			margin-left: 10%;
			margin-right: 10%;
			background-color: #FFFFFF;
		}
		.form .galiao-user-id, .form .galiao-user-password {
			margin-bottom: 30px;
			border: none;
			border-radius: 50px;
			background-color: #F3F3F3;
		}
		.btn-ok {
			width: 70px;
			height: 70px;
			border-radius: 35px;
		}
		.btn-img {
			width: 40px;
			height: 40px;
			padding-top: 5px;
		}
	</style>
	</head>
	<body>

		<div class="mui-content">
			<div style="margin-top: 40%; text-align: center; background-color: #FFFFFF;">
				<h2 class="title">尬聊 v1.0</h2>
				<div style="background-color: #FFFFFF;margin: 30px 0;">
					<img class="logo" src="img/logo.png" />
				</div>

				<div class="form">
					<div>
						<input type="text" class="mui-input-clear galiao-user-id" value="1000" id="galiao_userId" placeholder="尬聊号">
					</div>
					<div class="mui-input-row">
						<input type="password" class="mui-input-password galiao-user-password" value="123456" id="galiao_password"
						 placeholder="密码">
					</div>
					<div>
						<button id="btn_clcik" type="button" class="mui-btn mui-btn-success btn-ok">
							<img class="btn-img" src="img/ok.png" />
						</button>
					</div>
				</div>
			</div>
		</div>

		<script type="text/javascript" src="js/config.js"></script>
		<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
		<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
		<script src="js/mui.min.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript" src="js/app.js"></script>
		<script type="text/javascript">
			mui.init()

			mui.plusReady(function() {
				// 关闭 index 页面
				plus.webview.currentWebview().opener().close()
			})

			// 登录操作
			mui(document.body).on('tap', '#btn_clcik', function(e) {
				// 关闭软键盘(否则老弹出来)
				document.activeElement.blur();

				let userId = document.getElementById('galiao_userId').value
				let userPwd = document.getElementById('galiao_password').value

				// 验证用户名密码是否输入
				if (userId === '') {
					mui.alert("请输入尬聊号!")
				}

				if (userId === '') return // 直接在上面if里面return alert就会没反应,一切为了用户体验

				if (userPwd === '') {
					mui.alert("请输入密码!")
				}

				if (userPwd === '') return // 直接在上面if里面return alert就会没反应 一切为了用户体验

				// 显示加载中动画
				mui.showLoading("正在登录....","div")
				// 发起登陆请求
				axios({
						url: config.app.baseUrl + '/user/login',
						method: 'post',
						headers: {
							'Content-Type': 'application/json'
						},
						data: {
							userId: userId,
							userPwd: userPwd
						}
					})
					.then(function(res) {
						var data = res.data
						if (data.code != 1) {
							mui.alert(data.data)
							return
						}
						// 登陆成功
						// 1 记录 登录信息  (user里面包含token)
						data.data.token = data.token
						let user = JSON.stringify(data.data)
						plus.storage.setItem("user", user)
						// 2 跳转页面
						mui.openWindow({
							url: 'main.html',
							id: 'main'
						});
						
						// 关闭加载
						mui.hideLoading()
					})
					.catch(function(error) {
						// 关闭加载
						mui.hideLoading()
						
						mui.alert("网络故障!")
					});
			})

			// 关闭其他页面
			window.addEventListener('clearOtherHtml', function(event) {
				// 获取当前webview窗口对象
				var curr = plus.webview.currentWebview()
				//获取所有已经打开的webview窗口
				var wvs = plus.webview.all()
				for (var i = 0, len = wvs.length; i < len; i++) {
					//关闭除当前页面外的其他页面
					if (wvs[i].id == curr.id)
						//遇到当前页跳过
						continue
						
					// 先hide,避免闪一下
					wvs[i].hide()
					//非当前页执行关闭
					wvs[i].close()
				}
			});
		</script>
	</body>
</html>

登录页面效果:

登录页面

后端主要代码:

   @Override
   public Message login(User user) {
       User byId = getById(user.getUserId());
       if (byId == null) {
           return new Message()
                   .code(MessageCode.FAIL.value())
                   .data("用户不存在!");
       }

       if (!encoder.matches(user.getUserPwd(), byId.getUserPwd())) {
           return new Message()
                   .code(MessageCode.FAIL.value())
                   .data("用户名或密码错误!");
       }

       // 密码正确,产生token并返回
       user.setUserPwd(null);     // 清理掉密码
       byId.setUserPwd(null);     // 清理掉密码
       String token = JwtTokenUtil.createToken(user);

       // redis 存储登录的用户的信息 (存储一天)
       redisUtil.set(token, FastJsonUtil.toJSONString(byId), 24 * 60 * 60);

       // 获取好友
       String[] friendIds = byId.getUserFriends().split(",");
       QueryWrapper<User> wrapper = new QueryWrapper<>();
       wrapper.in("user_id", friendIds);
       wrapper.select("user_id", "user_avatar", "user_nickname");
       List<User> users = userMapper.selectList(wrapper);
       byId.setFriends(users);

       return new Message().code(MessageCode.OK.value()).data(byId).token(token);
   }

后端主要用到 spring security 的 BCryptPasswordEncoder 对密码加密和解密,用的 sha256 算法,因此不可逆的,保证密码的安全性。登录成功后会将用户的基本信息(除了密码)放入redis ,key 为用户的token, 暂时默认存一天也是 token 的过期时间。然后把用户信息返回客户端,客户端保存在 localstorage 供页面之间共享。

主页面(main.html)

主页面主要是 main.html 页面,这个页面有一个底部导航栏来控制显示其他几个页面。
前端代码如下:

<!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.css" rel="stylesheet" />
		<style>
			.mui-bar-tab .mui-tab-item.mui-active {
				color: #4DB361; /* 这里放你底部导航栏颜色 */
			}
		</style>
	</head>

	<body>

		<!-- 底部导航栏 -->
		<nav class="mui-bar mui-bar-tab">
			<a class="mui-tab-item mui-active" tabindex="0">
				<span class="mui-icon mui-icon-chat"></span>
				<span class="mui-tab-label">消息</span>
			</a>
			<a class="mui-tab-item" tabindex="1">
				<span class="mui-icon mui-icon-list"></span>
				<span class="mui-tab-label">联系人</span>
			</a>
			<a class="mui-tab-item" tabindex="2">
				<span class="mui-icon mui-icon-paperplane"></span>
				<span class="mui-tab-label">发现</span>
			</a>
			<a class="mui-tab-item" tabindex="3">
				<span class="mui-icon mui-icon-home"></span>
				<span class="mui-tab-label">我的</span>
			</a>
		</nav>
		
		<div id="app">
			
		</div>


		<script src="js/mui.js"></script>
		<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
		<script type="text/javascript">
			mui.init()
			
			var vue = new Vue({
				el: '#app',
				created() {
					mui.plusReady(function() {
						if(plus.webview.currentWebview().opener()) {
							var curr = plus.webview.currentWebview()
							var wvs = plus.webview.all()
							for (var i = 0, len = wvs.length; i < len; i++) {
								if (wvs[i].id == 'login' || wvs[i].id == 'HBuilder') {
									// 先hide再关闭 ,避免闪一下
									wvs[i].hide()
									wvs[i].close()
								}
							}
						}
						init();
					});
				}
			})

			/**
			 * main页面初始化
			 */
			function init() {
				var sixinArray = [{
						pageUrl: 'message.html',
						pageId: 'message'
					},
					{
						pageUrl: 'friends.html',
						pageId: 'friends'
					},
					{
						pageUrl: 'found.html',
						pageId: 'found'
					},
					{
						pageUrl: 'mine.html',
						pageId: 'mine'
					}
				]

				var sixinStyle = {
					top: '0px',
					bottom: '51px'
				}
				//获取当前的webview对象
				var indexWebview = plus.webview.currentWebview();

				//向当前的主页webview追加子页的4张webview对象
				for (var i = 0; i < sixinArray.length; i++) {

					var sixinPage = plus.webview.create(sixinArray[i].pageUrl,
						sixinArray[i].pageId, sixinStyle);
					//创建完后不需要马上显示,只有点击的时候才显示,所以隐藏webview窗口
					sixinPage.hide();

					//追加每一个子页面到当前主页面
					indexWebview.append(sixinPage);
				}
				//设置默认显示页面
				plus.webview.show(sixinArray[0].pageId);

				//批量绑定tap(点击)事件,展示不同的页面
				//通过mui选择器选择唯一的class ,on是表示触发的事件,tap表示手指触摸点击事件类型
				//第二个参数表示link的对象,也可以用共同的a标签代替
				mui(".mui-bar-tab").on("tap", ".mui-tab-item", function() {
					//或者使用a标签选择器  mui(".mui-bar-tab").on("tap", "a", function() { 
					//在要点击的标签处添加事件名 并且获取到相应的对象
					var tabindex = this.getAttribute("tabindex");
					//显示tap点击的页面,第一个id,第二个动画效果,第三个延迟时间
					plus.webview.show(sixinArray[tabindex].pageId, "none", 400);

					//隐藏不需要的页面
					for (var i = 0; i < sixinArray.length; i++) {
						if (i != tabindex) {
							plus.webview.hide(sixinArray[i].pageId, "none", 400);
						}
					}
				});

			}
		</script>
	</body>
</html>

该页面效果如下:

主页面

该页面难点在于底部导航栏每个 a 标签控制每个页面显示与隐藏,而官网只提供了基于a 标签 href 的代码块的显示与隐藏,这种方式需要将其他页面以 div 的方式写在 main 页面,会导致 main 页面代码过于冗杂。本次使用的切换 html 的方式核心代码在于 init 方法,通过 a 标签上的 tabindex 的值来显示不同页面。

消息页面(message.html)

前端代码:

<!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>
			.mui-bar-nav {
				background-color: #4DB361;
			}
			
			.galiao-title {
				color: #FFFFFF;
			}
			
			.galiao-message img {
				border-radius: 5px;
			}
		</style>
	</head>

	<body>
		<!-- 头部 -->
		<header class="mui-bar mui-bar-nav">
			<h1 id="title" class="mui-title galiao-title">消息</h1>
		</header>

		<div class="mui-content" id="app">
			<div>
				<ul class="mui-table-view">
					<li v-for="receiver in receivers" :key="receiver.userId" class="mui-table-view-cell mui-media" id="openChat" @click="openChat(receiver)">
						<div class="galiao-message">
							<img class="mui-media-object mui-pull-left" :src="receiver.userAvatar">
							<div class="mui-media-body">
								<span v-text="receiver.userNickname"></span>
								<p class="mui-ellipsis" v-text="receiver.messages[receiver.messages.length - 1].data"></p>
							</div>
						</div>
					</li>
				</ul>
			</div>
		</div>

		<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
		<script src="js/config.js"></script>
		<script src="js/mui.min.js"></script>
		<script type="text/javascript">
			mui.init()

			document.galiao = {
				// 后端接口
				url: config.app.sockUrl
			}

			/**
			 * 尬聊app初始化
			 */
			function galiao_init() {
				// 创建 socket
				if (window.WebSocket) {
					// 构建socket
					window.sock = new WebSocket(document.galiao.url)
					// 刚建立连接时需要mark一下(让服务端把你的channel和尬聊号建立联系)
					window.sock.onopen = function(event) {
						window.sock.send(JSON.stringify({
							action: 'mark',
							token: vue.$data.user.token
						}))
					}

					// 服务端有消息推送
					window.sock.onmessage = function(event) {
						var chat = plus.webview.getWebviewById('chat');
						if (chat) {
							mui.fire(chat, 'receiveMessage', {
								message: event.data
							})
							
							// 添加到消息
							vue.receiveNewMessage(event.data)
						}
					}
				}
			}

			/**
			 * 调用本页面的 socket 发送消息
			 * @param {Object} message
			 */
			function sendMessage(message) {
				window.sock.send(message)
				addNewMessage(JSON.parse(message))
			}
			
			/**
			 * 新增消息栏位
			 */
			function addReceiver(receiver) {
				vue.addReceiver(JSON.parse(receiver))
			}

			// 打开聊天窗口
			mui(document.body).on('tap', '#openChat', function(e) {
				this.click()
			})
				
			// 添加最新的消息
			function addNewMessage(message) {
				vue.addNewMessage(message)
			}
			
			/**
			 * vue实例
			 */
			var vue = new Vue({
				el: '#app',
				data: {
					user: {},
					receivers: []
				},
				methods: {
					addReceiver(receiver) {
						// 先判断是否已经存在
					    for(receiver1 of this.receivers) {
							if(receiver1.userId === receiver.userId) 
							this.openChat(receiver1)
							return
						}
						// 添加到消息列表
						receiver.messages = []
						this.receivers.push(receiver)
						this.openChat(receiver)
					},
					openChat(receiver) {
						mui.openWindow({
							url: 'chat.html',
							id: 'chat',
							extras: {
								receiver: receiver
							}
						});
					},
					// 添加最新消息
					addNewMessage(message) {
						for(receiver of this.receivers) {
							if(receiver.userId === message.receiver)  {
								receiver.messages.push(message)
							}
						}
					},
					// 收到新消息
					receiveNewMessage(message) {
						let inMessage = JSON.parse(message)
						for(receiver of this.receivers) {
							if(receiver.userId === inMessage.sender)  {
								receiver.messages.push(inMessage)
							}
						}
					}
				},
				created() {
					let that = this
					mui.plusReady(function() {
						// 初始化
						galiao_init();
						that.user = JSON.parse(plus.storage.getItem('user'))
					})
				}
			})
		</script>
	</body>
</html>

消息页面效果:

联系人页面

该页面构建了一个websocket对象供聊天信息的发送和接受,但该对象只能在本页面使用,其他页面(chat.html)需要调用本页面方法来发送消息。该页面收到消息直接传给chat页面,chat页面进行判断后选择显示或忽略。该页面涉及到消息数据的保存功能请参见代码。

联系人页面(friends.html)

前端代码:

<!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">
			.mui-bar-nav {
				background-color: #4DB361;
			}

			.galiao-title {
				color: #FFFFFF;
			}
			
			.galiao-friend img {
				border-radius: 5px;
			}
			
			#topPopover {
				position: fixed;
				top: 16px;
				right: 6px;
			}
			#topPopover .mui-popover-arrow {
				left: auto;
				right: 6px;
			}
			.mui-popover {
				width: 140px;
				height: 100px;
			}
			
		</style>
	</head>

	<body>

		<!-- 头部 -->
		<header class="mui-bar mui-bar-nav">
			<h1 id="title" class="mui-title galiao-title">联系人</h1>
			<a href="#topPopover" class="mui-icon mui-icon-plus mui-pull-right galiao-title"></a>
		</header>

		<div class="mui-content" id="app">
			<div>
				<ul class="mui-table-view">
					<li v-for="friend in user.friends" v-if="friend.userId != user.userId" @click="openFriendInfo(friend)" :key="friend.userId" class="mui-table-view-cell mui-media"
					 id="open_friend_info">
						<div class="galiao-friend">
							<img class="mui-media-object mui-pull-left" :src="friend.userAvatar">
							<div style="padding-top: 10px;" v-text="friend.userNickname">
							</div>
						</div>
					</li>
				</ul>
			</div>
			
			<!--右上角弹出菜单-->
			<div id="topPopover" class="mui-popover">
				<div class="mui-popover-arrow"></div>
				<div class="mui-scroll-wrapper">
					<div class="mui-scroll">
						<ul class="mui-table-view">
							<li class="mui-table-view-cell" id="btn_sao" @click="saoClick">
								<a href="javascript:;">
									<img class="mui-media-object mui-pull-left" style="width: 18px; height: 18px;" src="img/sao.png" />
									扫一扫
								</a>
							</li>
							<li class="mui-table-view-cell" id="btn_ssou" @click="souClick">
								<a href="javascript:;">
									<img class="mui-media-object mui-pull-left" style="width: 18px; height: 18px;" src="img/sou.png" />
									搜索好友
								</a>
							</li>
						</ul>
					</div>
				</div>
			</div>
		</div>

		<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
		<script src="js/mui.min.js"></script>
		<script type="text/javascript">
			mui.init()

			// 点击事件
			mui(document.body).on('tap', '#open_friend_info', function(e) {
				this.click()
			})
			// 点击扫一扫
			mui(document.body).on('tap', '#btn_sao', function(e) {
				this.click()
			})
			// 点击搜索
			mui(document.body).on('tap', '#btn_sou', function(e) {
				this.click()
			})

			/**
			 * vue实例
			 */
			var vue = new Vue({
				el: '#app',
				data: {
					user: {}
				},
				methods: {
					init() {
						this.user = JSON.parse(plus.storage.getItem('user'))
					},
					openFriendInfo(friend) {
						mui.openWindow({
							url: 'friend_info.html',
							id: 'friend_info',
							extras: {
								userId: friend.userId
							}
						});
					},
					saoClick() {
						mui.openWindow({
							url: 'sao.html',
							id: 'sao'
						});
						mui('#topPopover').popover('toggle');  // 关闭弹出菜单
					},
					souClick() {
						mui.openWindow({
							url: 'sou.html',
							id: 'sou'
						});
						mui('#topPopover').popover('toggle');
					}
				},
				created() {
					let that = this
					mui.plusReady(function() {
						that.init()
					})
				}
			})
		</script>
	</body>
</html>

页面效果:

在这里插入图片描述

联系人页面和消息页面比较相似,可以 copy 一部分。

发现页面 (found.html)

前端代码:

<!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">
			.mui-bar-nav {
				background-color: #4DB361 ;
			}
			.galiao-title {
				color: #FFFFFF;
			}
			.galiao-li {
				height: 45px;
			}
			.galiao-li img {
				width: 25px;
				padding-bottom: 15px;
			}
		</style>
	</head>

	<body>
		
		<!-- 头部 -->
		<header class="mui-bar mui-bar-nav">
			<h1 id="title" class="mui-title galiao-title">发现</h1>
		</header>
		
		<div class="mui-content">
			<div>
				<ul class="mui-table-view">
					<!-- 毒鸡汤栏目 -->
					<li id="dujitang" class="mui-table-view-cell mui-media galiao-li">
						<div class="mui-navigate-right">
							<img class="mui-media-object mui-pull-left" src="img/du.png">
							<div style="padding-top: 3px;">
								毒鸡汤(每日一毒)
							</div>
						</div>
					</li>
					<!-- 代做决定栏目 -->
					<li id="decision" class="mui-table-view-cell mui-media galiao-li">
						<div class="mui-navigate-right">
							<img class="mui-media-object mui-pull-left" src="img/decision.png">
							<div style="padding-top: 3px;">
								代做决定
							</div>
						</div>
					</li>
				</ul>
			</div>
		</div>
		
		<script src="js/mui.min.js"></script>
		<script type="text/javascript">
			mui.init()
			
			// 打开毒鸡汤页面
			mui(document.body).on('tap', '#dujitang', function(e) {
				 mui.openWindow({
				    url: 'du.html', 
				    id:'du'
				  });
			})
			
			// 打开代做决定页面
			mui(document.body).on('tap', '#decision', function(e) {
				 mui.openWindow({
				    url: 'decision.html', 
				    id:'decision'
				  });
			})
		</script>
	</body>
</html>

这个页面主要负责打开其他页面,目前主要打开 du.html 和 decision.html,这两个页面目前主要还是 js 在玩,没有 ajax 操作。可在【尬聊开源地址】查看。

我的页面(mine.html)

前端代码:

<!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">
			.mui-bar-nav {
				background-color: #4DB361;
			}

			.galiao-li {
				height: 45px;
			}

			.galiao-title {
				color: #FFFFFF;
			}

			.galiao-mine-header {
				width: 100%;
				height: 150px;
				background-color: #FFFFFF;
				padding-top: 16%;
				padding-left: 10%;
			}

			.galiao-mine-header img {
				width: 60px;
				height: 60px;
				border-radius: 6px;
				margin-right: 22px;
			}

			.galiao-mine-header div {
				padding-top: 10px;
			}

			.galiao-mine-header-title {
				color: #000000;
				font-size: 24px;
			}

			.galiao-mine-header-desc {
				font-size: 14px;
				color: #8F8F94;
			}

			.galiao-mine ul,
			.galiao-mine div {
				margin-bottom: 20px;
			}

			.galiao-quit-login {
				width: 100%;
				height: 40px;
			}
		</style>
	</head>

	<body>

		<!-- 头部 -->
		<header class="mui-bar mui-bar-nav">
			<h1 id="title" class="mui-title galiao-title">我的</h1>
		</header>

		<div class="mui-content">
			<div class="galiao-mine" id="galiao_mine">
				<div class="galiao-mine-header">
					<img style="float: left;" :src="user.userAvatar" />
					<div>
						<p class="galiao-mine-header-title" v-text="user.userNickname"></p>
						<span class="galiao-mine-header-desc">尬聊号:<span v-text="user.userId">></span></span>
					</div>
				</div>

				<ul class="mui-table-view">
					<li class="mui-table-view-cell" id="btn_replace_avatar">
						<a class="mui-navigate-right">头像
							<img class="mui-pull-right" style="width: 35px; height: 35px; margin-right: 20px;" :src="user.userAvatar" />
						</a>
					</li>
					<li class="mui-table-view-cell" id="btn_replace_nickname">
						<a class="mui-navigate-right">昵称
							<p class="mui-pull-right" style=" margin-right: 20px;" v-text="user.userNickname"></p>
						</a>
					</li>
					<li class="mui-table-view-cell">
						<a class="mui-navigate-right">尬聊号
							<p class="mui-pull-right" style=" margin-right: 20px;" v-text="user.userId"></p>
						</a>
					</li>
					<li class="mui-table-view-cell" id="btn_show_qrcode" @click="showQrcode">
						<a class="mui-navigate-right">我的二维码</a>
					</li>
				</ul>

				<ul class="mui-table-view">
					<li class="mui-table-view-cell" id="btn_show_admire">
						<a class="mui-navigate-right">赞赏</a>
					</li>
					<li class="mui-table-view-cell" id="btn_show_about">
						<a class="mui-navigate-right">关于</a>
					</li>
				</ul>

				<button type="button" id="btn_quit_login" class="mui-btn mui-btn-danger galiao-quit-login">退出登录</button>
			</div>
		</div>

		<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
		<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
		<script src="js/mui.min.js"></script>
		<script src="js/app.js"></script>
		<script type="text/javascript">
			mui.init()

			// 更换头像
			mui(document.body).on('tap', '#btn_replace_avatar', function(e) {
				mui.alert("该功能暂无法使用哦!")
			})

			// 更换昵称
			mui(document.body).on('tap', '#btn_replace_nickname', function(e) {
				mui.alert("该功能暂无法使用哦!")
			})

			// 展示二维码
			mui(document.body).on('tap', '#btn_show_qrcode', function(e) {
				this.click()
			})

			// 赞赏
			mui(document.body).on('tap', '#btn_show_admire', function(e) {
				mui.alert("小朋友,省点钱买辣条吃!")
			})

			// 关于
			mui(document.body).on('tap', '#btn_show_about', function(e) {
				//打开about页面
				mui.openWindow({
					url: 'about.html',
					id: 'about'
				});
			})

			// 退出登录
			mui(document.body).on('tap', '#btn_quit_login', function(e) {
				// 清理缓存
				clearCache()
				//打开login页面
				mui.openWindow({
					url: 'login.html',
					id: 'login'
				});
				// 调用login的方法清除其他页面
				var login = plus.webview.getWebviewById('login');
				mui.fire(login, 'clearOtherHtml')
			})

			var vue = new Vue({
				el: '#galiao_mine',
				data: {
					user: {
						userId: '',
						userNickname: '',
						userAvatar: ''
					}
				},
				methods: {
					showQrcode() {
						let that = this
						mui.openWindow({
							url: 'mine_qrcode.html',
							id: 'mine_qrcode',
							extras: {
								userId: that.user.userId
							}
						});
					}
				},
				created() {
					let that = this
					mui.plusReady(function() {
						that.user = JSON.parse(plus.storage.getItem('user'))
					})
				}
			})
		</script>
	</body>
</html>

页面效果:
我的页面
这个页面目前主要实现了我的二维码展示、关于、退出三个功能,后续可以做其他功能。

好友信息页面(friend_info.html)

<!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">
			.mui-bar-nav {
				background-color: #4DB361;
			}

			.galiao-btn-return {
				color: #FFFFFF;
			}

			.galiao-li {
				height: 45px;
			}

			.galiao-title {
				color: #FFFFFF;
			}

			.galiao-mine-header {
				width: 100%;
				height: 150px;
				background-color: #FFFFFF;
				padding-top: 16%;
				padding-left: 10%;
			}

			.galiao-mine-header img {
				width: 60px;
				height: 60px;
				border-radius: 6px;
				margin-right: 22px;
			}

			.galiao-mine-header div {
				padding-top: 10px;
			}

			.galiao-mine-header-title {
				color: #000000;
				font-size: 24px;
			}

			.galiao-mine ul {
				margin-bottom: 20px;
			}

			.galiao-mine-header-desc {
				font-size: 14px;
				color: #8F8F94;
			}

			.galiao-quit-login {
				width: 100%;
				height: 40px;
			}
		</style>
	</head>

	<body>

		<header class="mui-bar mui-bar-nav">
			<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left galiao-btn-return"></a>
		</header>

		<div class="mui-content" id="app">
			<div class="galiao-mine" id="galiao_mine">
				<div class="galiao-mine-header">
					<img style="float: left;" :src="user.userAvatar" />
					<div>
						<p class="galiao-mine-header-title" v-text="user.userNickname"></p>
						<span class="galiao-mine-header-desc">尬聊号:<span v-text="user.userId"></span> </span>
					</div>
				</div>

				<!-- 已经是好友显示 -->
				<ul class="mui-table-view" v-if="user.isFriend">
					<li class="mui-table-view-cell" id="btn_set_remark">
						<a class="mui-navigate-right">
							设置备注和标签
						</a>
					</li>
				</ul>
				<ul class="mui-table-view" v-if="user.isFriend">
					<li class="mui-table-view-cell" id="btn_send_message" @click="openChat">
						<a style="text-align: center; color: dimgray;">
							<span class="mui-icon mui-icon-chatbubble" style="width: 25px; height: 25px;"></span>
							发消息
						</a>
					</li>
				</ul>

				<!-- 不是好友显示 -->
				<ul class="mui-table-view" v-if="user.isFriend == false" style="margin-top: 20px;">
					<li class="mui-table-view-cell" id="btn_add_friend" @click="addFriend">
						<a style="text-align: center; color: dimgray;">
							<img src="img/add_friend.png" class="mui-icon mui-icon-chatbubble" style="width: 20px; height: 20px; margin-bottom: -3px;"></img>
							添加好友
						</a>
					</li>
				</ul>

			</div>
		</div>

		<script type="text/javascript" src="js/config.js"></script>
		<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
		<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
		<script src="js/mui.min.js"></script>
		<script type="text/javascript">
			mui.init()

			// 点击发消息按钮
			mui(document.body).on('tap', '#btn_send_message', function(e) {
				this.click()
			})

			// 点击设置备注按钮
			mui(document.body).on('tap', '#btn_set_remark', function(e) {
				mui.alert("该功能暂无法使用哦!")
			})

			// 点击添加好友
			mui(document.body).on('tap', '#btn_add_friend', function(e) {
				// this.click()
				mui.alert("该功能暂无法使用哦!")
			})

			/**
			 * vue实例
			 */
			var vue = new Vue({
				el: '#app',
				data: {
					user: {
						userId: '',
						userNickname: '',
						userAvatar: '',
						isFriend: false
					}
				},
				methods: {
					// 打开聊天界面
					openChat() {
						let that = this
						// 不能和自己聊天
						if (that.user.userId == JSON.parse(plus.storage.getItem('user')).userId) {
							mui.alert("无法和自己聊天!")
							return
						}

						// message 页面新增消息栏 
						this.messageView.evalJS("addReceiver('" + JSON.stringify(that.user) + "')")
					},
					// 添加好友
					addFriend() {
						let that = this
						axios.post(config.app.baseUrl + '/user/friend/' + that.user.userId, null, {
								params: {
									token: JSON.parse(plus.storage.getItem('user')).token
								}
							})
							.then(function(res) {
								var data = res.data
								if (data.code != 1) {
									mui.alert(data.data)
									return
								}
								mui.alert("添加成功!")
								that.openChat()
								// 更新本地数据
								
								
							})
							.catch(function(error) {
								mui.alert("网络异常!")
							});

					}
				},
				created() {
					let that = this
					mui.plusReady(function() {
						// 获取上个页面传来的参数
						that.user.userId = plus.webview.currentWebview().userId

						//获取 message 页面
						var wvs = plus.webview.all()
						for (var i = 0, len = wvs.length; i < len; i++) {
							// 获取建立socket连接的 message 页面
							if (wvs[i].id == 'message') {
								vue.messageView = wvs[i]
								break
							}
						}

						// 获取好友信息
						if (that.user.userId) {
							axios.get(config.app.baseUrl + '/user/' + that.user.userId, {
									params: {
										token: JSON.parse(plus.storage.getItem('user')).token
									}
								})
								.then(function(res) {
									var data = res.data
									if (data.code != 1) {
										mui.alert(data.data)
										return
									}
									that.user = data.data
								})
								.catch(function(error) {
									console.log(error)
								});
						}

					})

				}
			})
		</script>
	</body>
</html>

页面效果:

尬友信息页面

这个页面会根据情况判断,如果不是自己的好友,会显示 添加好友 按钮,如果是自己的好友就显示 发消息 按钮。这一块的判断在服务端,主要逻辑如下:

    @Override
    public Message userById(Long userId, String token) {
        // 验证token
        if(checkToken(token)) {
            QueryWrapper<User> wrapper = new QueryWrapper<>();
            wrapper.eq("user_id", userId);
            wrapper.select("user_id", "user_avatar", "user_nickname");
            User user = userMapper.selectOne(wrapper);
            if(user == null)
                return new Message().code(MessageCode.USER_NOT_FOUND.value()).data("该用户不存在!");

            // 用户存在,检查是否是自己的好友
            boolean isFriend = false;
            // 获取自己的好友列表
            String[] split = FastJsonUtil.parseObject(
                    (String) redisUtil.get(token), User.class)
                    .getUserFriends()
                    .split(",");
            for (String s : split) {
            	// 判断是否是自己好友
                if(user.getUserId().equals(Long.valueOf(s))) {
                    isFriend = true;
                    break;
                }
            }
            user.setIsFriend(isFriend);
            return new Message().code(MessageCode.OK.value()).data(user);

        }
        // token不合法
        return new Message().code(MessageCode.FAIL.value()).data("token不合法");
    }

聊天页面(chat.html)

前端代码:

<!doctype html>
<html lang="en">
	<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 rel="stylesheet" type="text/css" href="css/mui.min.css" />
		<style>
			body {
			background-color: #F2F2F2;
		}
		.mui-bar-nav {
			background-color: #4DB361 ;
		}
		.galiao-btn-return {
			color: #FFFFFF;
		}
		.galiao-chat-title {
			color: #FFFFFF;
		}
		.galiao-chat-tab {
			padding: 7px 12px 3px 12px;
		}
		.galiao-avator {
			width: 50px;
			height: 50px;
		}
		.mui-table-view {
			background-color: #F2F2F2;
		}
		.mui-table-view:after{ height:0}
		.mui-table-view:before{ height:0}
		.mui-table-view-cell:after{ height:0}
		.mui-table-view-cell:before{ height:0}
		
		.galiao-message-bubble-left {
			position: relative;
			display: inline-block;
			padding: 10px 15px 10px 20px;
			margin-top: 5px;
			background-color: #FFFFFF;
			font-size: 14px;
			border-radius: 5px;
			margin-left: 20px;
			max-width: 260px;
		}
		
		.galiao-message-bubble-left:before, .galiao-message-bubble-left:after {
			content: ""; /*:before和:after必带技能,重要性为满5颗星*/
			display: block;
			position: absolute; /*日常绝对定位*/
			top: 15px;
			left: -12px;
			border: 6px solid transparent;
			border-right-color: #FFFFFF;
			width: 0px;
			height: 0px;
		}
		
		.galiao-message-bubble-right {
			position: relative;
			display: inline-block;
			padding: 10px 15px 10px 20px;
			margin-top: 5px;
			background-color: #9fe766;
			font-size: 14px;
			border-radius: 5px;
			margin-right: 20px;
			max-width: 260px;
		}
		
		.galiao-message-bubble-right:before, .galiao-message-bubble-right:after {
			content: ""; /*:before和:after必带技能,重要性为满5颗星*/
			display: block;
			position: absolute; /*日常绝对定位*/
			top: 15px;
			right: -10px;
			border: 6px solid transparent;
			border-left-color: #9fe766;
			width: 0px;
			height: 0px;
		}
		
		.galiao-avator{
			border-radius: 5px;
		}
	</style>
	</head>
	<body>
		<div id="app">
			<header class="mui-bar mui-bar-nav">
				<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left galiao-btn-return"></a>
				<h1 class="mui-title galiao-chat-title" v-text="receiver.userNickname"></h1>
			</header>

			<div class="mui-scroll-wrapper" style="margin: 50px 0 70px 0;">
				<div class="mui-scroll">
					<!-- 左气泡 -->
					<ul class="mui-table-view" class="galiao-chat-message">
						<li v-for="(message, index) in messageData" :key="index" class="mui-table-view-cell">
							<img v-if="message.action != 'out'" class="galiao-avator mui-pull-left" :src="receiver.userAvatar" />
							<div v-if="message.action != 'out'" class="galiao-message-bubble-left mui-pull-left" v-text="message.data"></div>
							<img v-if="message.action == 'out'" class="galiao-avator mui-pull-right" :src="user.userAvatar" />
							<div v-if="message.action == 'out'" class="galiao-message-bubble-right mui-pull-right" v-text="message.data"></div>
						</li>
					</ul>
				</div>
			</div>


			<nav class="mui-bar mui-bar-tab galiao-chat-tab">
				<div>
					<span class="mui-icon mui-icon-pengyouquan" style="width: 10%; padding-right: 3px;"></span>
					<input id="concurrentMessage" type="text" style="width: 60%;border: none;">
					<span class="mui-icon mui-icon-star" style="width: 10%; padding-left: 7px;"></span>
					<button type="button" id="btn_send_message" @click="sendMessage" class="mui-btn mui-btn-success" style="width: 14%;">发送</button>
				</div>
			</nav>

		</div>

		<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
		<script src="js/mui.min.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			mui.init()

			/**
			 * 收到消息
			 * @param {Object} event
			 */
			window.addEventListener('receiveMessage', function(event) {
				//获得事件参数
				var message = event.detail.message;
				vue.receiveMessage(message)
			});

			// 按钮点击事件 
			mui(document.body).on('tap', '#btn_send_message', function(e) {
				this.click()
			})

			/**
			 * vue实例
			 */
			var vue = new Vue({
				el: '#app',
				data: {
					messageData: [],
					user: {},
					receiver: {}
				},
				methods: {
					// 发消息
					sendMessage() {
						let message = document.getElementById('concurrentMessage').value
						if (message != '') {
							// 构建传输的消息格式
							let msg = {
								action: 'out', // 操作类型
								receiver: this.receiver.userId, // 接受者尬聊号
								data: message, // 信息
								token: this.user.token // token
							}
							// 调用父页面发送消息
							this.socketView.evalJS("sendMessage('" + JSON.stringify(msg) + "')")
							this.messageData.push(msg)
							document.getElementById('concurrentMessage').value = ''
							let that = this
							setTimeout(function() {
								that.movieToBottom()
							}, 100)
						}
					},
					// 接收消息
					receiveMessage(message) {
						let inMessage = JSON.parse(message)
						if (inMessage.sender === this.receiver.userId) {
							let that = this
							that.messageData.push({
								action: 'in',
								data: inMessage.data
							})
							// 如果直接调用是没效果的,可能还没挂载吧,等个100ms后已经挂载完成
							setTimeout(function() {
								that.movieToBottom()
							}, 100)
						}
					},
					// 窗口移动到底部(每次发完消息时和初始化时)
					movieToBottom() {
						// 让页面一直显示最底部
						var scroll = mui('.mui-scroll-wrapper').scroll();
						scroll.reLayout();
						//滚动到底部
						scroll.scrollToBottom(100);
					}
				},
				created() {
					let that = this
					mui.plusReady(function() {
						// 获取登录信息
						that.user = JSON.parse(plus.storage.getItem('user'))
						// 获取接收人信息
						that.receiver = plus.webview.currentWebview().receiver
						// 渲染最新消息数据
						that.messageData = that.receiver.messages != undefined ? that.receiver.messages : []
						// 设置scroll
						mui('.mui-scroll-wrapper').scroll({
							scrollY: true, //是否竖向滚动
							scrollX: false, //是否横向滚动
							startX: 0, //初始化时滚动至x
							startY: 0, //初始化时滚动至y
							indicators: true, //是否显示滚动条
							deceleration: 0.0005, //阻尼系数,系数越小滑动越灵敏
							bounce: true //是否启用回弹
						})
						//获取 message 页面
						var wvs = plus.webview.all()
						for (var i = 0, len = wvs.length; i < len; i++) {
							// 获取建立socket连接的 message 页面
							if (wvs[i].id == 'message') {
								// 有socket连接的view
								vue.socketView = wvs[i]
								break
							}
						}
					})
				}
			})
		</script>
	</body>
</html>

页面效果如下:

尬友聊天页面

这个页面的聊天气泡功能稍微有点复杂,可以参考项目中 chat_bubble.html 的页面的简化代码实现。另外,这个页面在发消息时需要调用 message 页面来发,因为只有 message 建立了 websocket 对象。调用其他页面的方法有两种方式,一种是 mui.fire() 方法,另一种是 evalJS() 方法。注意 evalJS() 方式只能传字符串参数,无法直接传对象,可以使用 JSON.stringify() 转为 json 串传参,被调用页面使用JSON.parse() 方法将 json 串转对象

二维码页面(mine_qrcode.html)

前端代码:

<!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">
			.mui-bar-nav {
				background-color: #4DB361;
			}

			.galiao-qrcode-header {
				color: #FFFFFF;
			}

			.galiao-qrcode {
				margin: 30% 0 20% 0;
				text-align: center;
			}

			.qrcode_bg {
				width: 340px;
				height: 340px;
				background-image: url(img/qrcode_bg.png);
				background-size: cover;
			}

			.qrcode {
				padding-top: 50%;
			}
		</style>
	</head>

	<body>

		<header class="mui-bar mui-bar-nav">
			<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left galiao-qrcode-header"></a>
			<h1 class="mui-title galiao-qrcode-header">我的二维码</h1>
		</header>

		<div class="mui-content galiao-qrcode" id="app">
			<div class="mui-card">
				<!--页眉,放置标题-->
				<div class="mui-card-header">
					<img :src="user.userAvatar" /> <span v-text="user.userNickname"></span>
				</div>
				<div class="mui-card-content" style="padding: 10px;">
					<div class="qrcode_bg" id="qrcode_bg">
						<div class="qrcode" id="qrcode">
						</div>
					</div>
				</div>
			</div>
		</div>

		<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
		<script src="js/qrcode.min.js"></script>
		<script src="js/mui.min.js"></script>
		<script type="text/javascript">
			mui.init()


			var vue = new Vue({
				el: '#app',
				data: {
					user: {}
				},
				created() {
					let that = this
					mui.plusReady(function() {
						that.user = JSON.parse(plus.storage.getItem('user'))
						// 设置参数方式
						var qrcode = new QRCode('qrcode', {
							text: that.user.userId + "",
							width: 130,
							height: 130,
							colorDark: '#000000',
							colorLight: '#569362',
							correctLevel: QRCode.CorrectLevel.H
						});
					})
				}
			})
		</script>
	</body>
</html>

页面效果:

我的二维码页面

这个页面主要使用【qrcode.js】对尬聊号生成二维码。

扫一扫页面(sao.html)

前端代码:

<!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">
			.mui-bar-nav {
				background-color: #4DB361;
			}

			.galiao-sao-header {
				color: #FFFFFF;
			}

			.sao-qrcode {
				width: 350px;
				height: 350px;
				margin: 25% auto;
			}
		</style>
	</head>

	<body>
		<header class="mui-bar mui-bar-nav">
			<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left galiao-sao-header"></a>
			<h1 class="mui-title galiao-sao-header">扫一扫识别</h1>
		</header>

		<div class="mui-content">
			<div id="saoQrcode" class="sao-qrcode">
				<!--盛放扫描控件的div-->
			</div>
		</div>


		<script src="js/mui.min.js"></script>
		<script type="text/javascript">
			mui.init()

			mui.plusReady(function() {
				startRecognize(); //开始扫描
			});

			// 扫描组件
			var scan;

			//开启扫描方法
			function startRecognize() {
				try {
					var filter;
					//自定义的扫描控件样式
					var styles = {
						frameColor: "#4DB361",
						scanbarColor: "#4DB361",
						background: "",
						width: '80%',
						height: '80%'
					}
					//扫描控件构造
					scan = new plus.barcode.Barcode('saoQrcode', filter, styles);
					scan.onmarked = onmarked // 扫描成功
					scan.onerror = onerror; // 扫描错误
					scan.start();
				} catch (e) {
					onerror(e)
				}
			};


			// 扫描成功回调 result是返回的结果
			function onmarked(type, result, file) {
				switch (type) {
					case plus.barcode.QR:
						type = 'QR';
						break;
					default:
						type = '其它' + type;
						break;
				}
				// 跳转页面
				mui.openWindow({
					url: 'friend_info.html',
					id: 'friend_info',
					extras: {
						userId: result
					}
				});
				setTimeout(function() {
					scan.start()
				}, 1000)
			};

			// 扫描失败回调 
			function onerror(error) {
				mui.alert("扫描失败!")
				scan.start()
			};
		</script>
	</body>
</html>

页面效果:

扫一扫页面

该页面主要使用 plus.barcode.Barcode 开启一个扫一扫的效果,设置回调函数。扫描成功就弹到好友信息页面,这个页面会发 ajax 去查好友信息。

搜索页面(sou.html)

前端代码:

<!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">
			.mui-bar-nav {
				background-color: #4DB361;
			}

			.galiao-sou-header {
				color: #FFFFFF;
			}

			.galiao-sou-search {
				margin: 10px 10px 0 10px;
			}
		</style>
	</head>

	<body>

		<header class="mui-bar mui-bar-nav">
			<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left galiao-sou-header"></a>
			<h1 class="mui-title galiao-sou-header">搜索</h1>
		</header>

		<div class="mui-content" id="app">
			<div class="mui-input-row mui-search galiao-sou-search">
				<input type="search" id="searchValue" class="mui-input-clear mui-input-speech" placeholder="请输入尬聊号" @click="search">
			</div>

			<ul v-show="stranger.userId != null" class="mui-table-view">
				<li class="mui-table-view-cell" id="btn_open_info" style="height: 60px; text-align: center;" @click="openInfo">
					<img class="mui-media-object mui-pull-left" :src="stranger.userAvatar" style="border-radius: 5px;">
					<div class="mui-media-body mui-pull-left" style="padding-top: 10px;" v-text="stranger.userNickname">
					</div>
				</li>
			</ul>

			<ul v-show="notFindUser" class="mui-table-view">
				<li class="mui-table-view-cell" style="height: 60px; text-align: center; padding-top: 20px;">
					用户不存在
				</li>
			</ul>

			<p class="galiao-sou-search">注:输入好友的尬聊号搜索添加好友。尬聊号可以在 ‘我的’ &nbsp; -> &nbsp;‘尬聊号’ 处查看。</p>
		</div>

		<script type="text/javascript" src="js/config.js"></script>
		<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
		<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
		<script src="js/mui.min.js"></script>
		<script type="text/javascript">
			mui.init()

			// 点击开始搜索
			mui(document.body).on('keypress', '#searchValue', function(e) {
				this.click();
			})
			
			// 点击打开详情
			mui(document.body).on('tap', '#btn_open_info', function(e) {
				this.click();
			})

			// vue实例
			var vue = new Vue({
				el: '#app',
				data: {
					notFindUser: false,
					stranger: {
						userId: null,
						userAvatar: '',
						userNickname: ''
					}
				},
				methods: {
					// 搜索
					search() {
						let that = this
						that.notFindUser = false
						let searchValue = document.getElementById('searchValue').value
						if(searchValue === '') return 
						axios.get(config.app.baseUrl + '/user/search/' + searchValue, {
								params: {
									token: JSON.parse(plus.storage.getItem('user')).token
								}
							})
							.then(function(res) {
								var data = res.data
								if (data.code == 3) {
									that.stranger = {
										userId: null,
										userAvatar: '',
										userNickname: ''
									}
									that.notFindUser = true
									return
								}
								if (data.code != 1) {
									mui.alert(data.data)
									return
								}
								that.stranger = data.data
							})
							.catch(function(error) {
								console.log(error)
							});
					},
					// 打开详情页
					openInfo() {
						mui.openWindow({
							url: 'friend_info.html',
							id: 'friend_info',
							extras: {
								userId: this.stranger.userId
							}
						});
					}
				},
				created() {
					mui.plusReady(function() {

					});
				}
			})
		</script>
	</body>
</html>

页面效果:

搜索页面

这个页面难点在于输入框的回车事件的监听,如果是 vue 的 @keyup.enter.native 就监听不到,需要使用 mui 监听 keypress 事件。输入框 添加 mui-input-speech 的 class ,mui 实现了语音输入。

其他

至于其他页面功能稍微简单一些,可以去项目里面克隆源代码交流。【尬聊前端源码】、【尬聊后端源码

展望

虽然开发了一些功能,但是还有很多问题需要去发现和解决。比如消息无法保存等 问题。

后边我希望增加有趣的功能,比如过滤 不良消息词汇,用美好的词代替,净化网络暴力。比如 : 你是傻逼 代替为 你是靓仔。让我们的交流更加美好。

当然了,如果你想到了有趣的功能欢迎留言(说不定哪天就去实现了)。要是愿意参与到开源项目的开发当然更好了。走过路过,留个 star 呗。

博客地址:https://www.anlazy.top/index.php/archives/308/
本人公众号:一只小安仔
公众号

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值