精华内容
下载资源
问答
  • 如何制作语音聊天程序源码,制作语音社交交友APP
    千次阅读
    2019-12-02 11:29:44

    制作语音聊天程序源码不是一件容易的事情,或许一些前辈的做法能够给我们一些启示,以下代码为转载,来自云豹科技程序员——知乎作者111,原文链接如上,感谢授权

    首先是客户端代码,这些代码主要实现内容有:

    1. 当用户进入退出直播间时,其他人会收到通知
    2. 聊天室内消息互通
    3. 管理员功能

    具体代码如下:

    # client.py
    from socket import *
    import os
    import sys
    

    用户发送消息

    def send_msg(s, name, addr):
        while True:
            text = input('发言:')
            # 如果输入quit表示退出
            if text.strip() == 'quit':
                msg = 'Q ' + name
                s.sendto(msg.encode(), addr)
                sys.exit('退出聊天室')
            msg = 'C %s %s' % (name, text)
            s.sendto(msg.encode(), addr)
    

    接收消息

    def recv_msg(s):
        while True:
            data, addr = s.recvfrom(2048)
            if data.decode() == 'EXIT':
                sys.exit(0)
            print(data.decode() + '\n发言:', end = " ")
    

    创建套接字,登录,创建子进程

    def main():
        # 从命令行获取服务端地址
        if len(sys.argv) < 3:
            print('argv is error')
            return
        HOST = sys.argv[1]
        PORT = int(sys.argv[2])
        ADDR = (HOST, PORT)
    
    
    # 创建套接字
    s = socket(AF_INET, SOCK_DGRAM)
    
    while True:
        name = input('请输入姓名:')
        msg = 'L ' + name
        # 发送登录请求
        s.sendto(msg.encode(), ADDR)
        # 等待服务器回复
        data, addr = s.recvfrom(1024)
        if data.decode() == 'OK':
            print('您已进入聊天室')
            break
        else:
            # 不成功服务端会回复不允许登录原因
            print(data.decode())
    
    # 创建父子进程
    pid = os.fork()
    if pid < 0:
        sys.exit('创建子进程失败')
    elif pid == 0:
        send_msg(s, name, ADDR)
    else:
        recv_msg(s)
    
    if __name__ == '__main__':
        main()
    
    其次是服务端代码
    
    #!/usr/bin/env python3
    #coding = utf-8
    
    '''
    name = lisimeng
    email : 18844630644@163.com
    data = 2018-9
    instroduce: chatroom server
    env : python3.5
    '''
    from socket import *
    import os
    import sys
    
    
    def do_login(s, user, name, addr):
        if (name in user) or name == '管理员':
            s.sendto('该用户已存在'.encode(), addr)
            return
        # retrun到 do_parent()中的do_login(s, user, msgList[1], addr)
        s.sendto(b'OK', addr)
    

    通知其他人

    msg = '\n欢迎进入%s 聊天室' % name
    for i in user:
        s.sendto(msg.encode(), user[i])
    
    # 插入用户
    user[name] = addr
    

    服务端转发聊天消息

    def do_chat(s, user, name, text):
        msg = '\n%s 说: %s' % (name, text)
        for i in user:
            if i != name:
                s.sendto(msg.encode(), user[i])
    

    退出聊天室

    def do_quit(s, user, name):
        msg = '\n' + name + '退出聊天室'
        for i in user:
            if i == name:
                s.sendto(b'EXIT', user[i])
            else:
                s.sendto(msg.encode(), user[i])
        # 从字典中删除用户
        del user[name]
    

    接收客户端请求

    def do_parent(s):
        # 存储结构{'zhangsan':('0.0.0.0',9999)}
        user = {}
        while True:
            msg, addr = s.recvfrom(1024)
            msgList = msg.decode().split(' ')
    
        # 区分请求类型
        if msgList[0] == 'L':
            do_login(s, user, msgList[1], addr)
        elif msgList[0] == 'C':
            do_chat(s, user, msgList[1], ' '.join(msgList[2:]))
        elif msgList[0] == 'Q':
            do_quit(s, user, msgList[1])
    

    做管理员喊话

    def do_child(s, addr):
        while True:
            msg = input('管理员消息:')
            msg = 'C 管理员' + msg
            s.sendto(msg.encode(), addr)
    

    创建网络,创建进程,调用功能函数

    
    
    

    def main():
    # server address
    ADDR = (‘0.0.0.0’, 8888)

    
        # 创建套接字
        s = socket(AF_INET, SOCK_DGRAM)
        s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        s.bind(ADDR)
    
        # 创建一个单独的进程处理管理员的喊话功能
        pid = os.fork()
        if pid < 0:
            sys.exit('创建进程失败')
        elif pid == 0:
            do_child(s, ADDR)
        else:
            do_parent(s)
    if __name__ == "__main__":
        main()
    
    

    到这里,客户端代码便编写完毕,接下来准备编写服务端代码:

    
    #!/usr/bin/env python3
    #coding = utf-8
    
    '''
    name = lisimeng
    email : 18844630644@163.com
    data = 2018-9
    instroduce: chatroom server
    env : python3.5
    '''
    from socket import *
    import os
    import sys
    
    
    def do_login(s, user, name, addr):
        if (name in user) or name == '管理员':
            s.sendto('该用户已存在'.encode(), addr)
            return
        # retrun到 do_parent()中的do_login(s, user, msgList[1], addr)
        s.sendto(b'OK', addr)
    
        # 通知其他人
        msg = '\n欢迎进入%s 聊天室' % name
        for i in user:
            s.sendto(msg.encode(), user[i])
    
        # 插入用户
        user[name] = addr
    

    服务端转发聊天消息

    def do_chat(s, user, name, text):
        msg = '\n%s 说: %s' % (name, text)
        for i in user:
            if i != name:
                s.sendto(msg.encode(), user[i])
    

    退出聊天室

    `def do_quit(s, user, name):
        msg = '\n' + name + '退出聊天室'
        for i in user:
            if i == name:
                s.sendto(b'EXIT', user[i])
            else:
                s.sendto(msg.encode(), user[i])
        # 从字典中删除用户
        del user[name]`
    

    接收客户端请求

    def do_parent(s):
        # 存储结构{'zhangsan':('0.0.0.0',9999)}
        user = {}
        while True:
            msg, addr = s.recvfrom(1024)
            msgList = msg.decode().split(' ')
    
            # 区分请求类型
            if msgList[0] == 'L':
                do_login(s, user, msgList[1], addr)
            elif msgList[0] == 'C':
                do_chat(s, user, msgList[1], ' '.join(msgList[2:]))
            elif msgList[0] == 'Q':
                do_quit(s, user, msgList[1])
        # 做管理员喊话
        def do_child(s, addr):
        while True:
            msg = input('管理员消息:')
            msg = 'C 管理员' + msg
            s.sendto(msg.encode(), addr)
    

    创建网络,创建进程,调用功能函数

    def main():
    # server address
    ADDR = ('0.0.0.0', 8888)# 创建套接字
    s = socket(AF_INET, SOCK_DGRAM)
    s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    s.bind(ADDR)
    
    # 创建一个单独的进程处理管理员的喊话功能
    pid = os.fork()
    if pid < 0:
        sys.exit('创建进程失败')
    elif pid == 0:
        do_child(s, ADDR)
    else:
        do_parent(s)
    
    if __name__ == "__main__":
        main()
    
    接下来,是关于语音聊天程序源码搭建相关事宜
    安装好swoole后开始搭建
    前端
    
    <meta charset="UTF-8">
    <title>聊天室</title></head><style>
    #set_name{
        margin: auto;
        text-align: center;
    }</style><body><h3 style="text-align: center">亮亮的聊天室</h3><div id="set_name">
    
    姓名:<input name="name" id="name"><input type="button" onclick="set_name();" value="进入群聊"></div><div id="chat" style="width: 600px; border: red 1px solid;margin: auto;display: none">
        <div id="sayContent" style="height: 300px;border-bottom: silver 1px dashed;">
    
        </div>
        <div style="height: 75px;margin-top: 10px">
            <textarea id="content" style="height: 50px;width: 480px;background-color: #00a0e9;float: left;"></textarea>
            <button id="submit" style="float: right;height: 55px;width:50px;margin-right:45px;display: block;" onclick="send_msg();">发送</button>
        </div></div><script>
        var name = ws = '' ;
        //执行websock
        function chat() {
            var wsserver = 'ws://47.94.11.195:443';
            //调用WebSocket对象建立连接
            //ws  wss: // ip:port(字符串)
            ws = new WebSocket(wsserver);
            //获取聊天内容展示窗口
            var sayContent = document.getElementById('sayContent');
            //onopen监听连接打开
            ws.onopen = function (v) {
                var user = new Object();
                user.name = name;
                user.type = 1;//对用户设置姓名
                var json = JSON.stringify(user);
                ws.send(json);//发送数据
            }
            //onmessage监听服务器数据推送
            ws.onmessage = function (v) {
                var html = sayContent.innerHTML;
                sayContent.innerHTML = html + "<br>"+v.data;
            }
            //监听连接关闭
            ws.onclose = function (v) {
                var html = sayContent.innerHTML;
                sayContent.innerHTML = html + "<br>聊天室已关闭!";
            }
        }
        //设置用户名
        function set_name() {
            name = document.getElementById('name').value;
            if(name == ''){
                alert('请输入用户名!');
                return false;
            }
            document.getElementById('set_name').style.display='none';
            document.getElementById('chat').style.display='block';
            chat();
        }
        function send_msg() {
            var content = document.getElementById('content');
            if(content.value == ''){
                alert('请输入聊天内容!');
                return false;
            }
    
            var msg = new Object();
            msg.content = content.value;
            msg.type = 2;
            var str = JSON.stringify(msg);
            ws.send(str);
        }</script></body></html>
    后台
    $server = new swoole_websocket_server("0.0.0.0", 443);
    $server->users = [];
    $server->on('open', function (swoole_websocket_server $server, $request) {
        $server->users[$request->fd]['id'] = $request->fd;
    });
    $server->on('message', function (swoole_websocket_server $server, $frame) {
        $data = json_decode($frame->data,true);
        if($data['type'] == 1){
            $server->users[$frame->fd]['name']=$data['name'];
            $server->push($frame->fd,'欢迎您('.$data['name'].')进入聊天室!');
        }else{
            foreach($server->users as $v){
                $server->push($v['id'], $server->users[$frame->fd]['name'].'说:'.$data['content']);
            }
        }
    });
    $server->on('close', function ($ser, $fd) {
        file_put_contents('qq.txt',$server->users[$frame->fd],FILE_APPEND);
        unset($server->users[$frame->fd]);
    });
    $server->start();
    <?php# 定义 clientFds 数组 保存所有 websocket 连接
    $clientFds = [];
    

    创建 websocket 服务

    $server = new swoole_websocket_server("0.0.0.0", 8080);# 握手成功 触发回调函数
    $server->on('open', function (swoole_websocket_server $server, $request) use (&$clientFds) {
       # echo "server: handshake success with fd{$request->fd}\n";
       # 将所有客户端连接标识,握手成功后保存到数组中
       $clientFds[] = $request->fd;
    });# 收到消息 触发回调函数
    $server->on('message', function (swoole_websocket_server $server, $frame) use (&$clientFds) {
       # echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
       # $server->push($frame->fd, "this is server");
       # 当有用户发送信息,发送广播通知所有用户
       foreach ($clientFds as $fd) {
          $server->push($fd, $frame->data);
       }
    });# 关闭连接 触发回调函数
    $server->on('close', function ($ser, $fd) use (&$clientFds) {
       # echo "client {$fd} closed\n";
       # 关闭会话 销毁标识 fd
       # 根据 value 去数组中找对应的 key
       $res = array_search($fd, $clientFds, true);
       unset($clientFds[$res]);
    });# 启动 websocket 服务
    $server->start();
    <!DOCTYPE html><html lang="en"><head>
       <meta charset="UTF-8">
       <title>WebSocket 聊天室</title></head><body><div id="main" style="width:600px;height: 200px; overflow: auto;border: solid 2px black;"></div><textarea id="textarea"></textarea><br/><input type="button" value="发送数据" onclick="send()"><script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script><script type="text/javascript">
        var name =prompt("请输入您的昵称","匿名者"); //弹出 input 框
        // 打开一个 web socket
        var ws = new WebSocket("ws://ip:8080");
    
        ws.onopen = function() {
            console.log("连接成功");
        };
    
        //收到消息 触发回调
        ws.onmessage = function (evt) {
            var data = evt.data;
            console.log("收到 socket 服务消息,内容:" + data);
            $('#main').append("<p>" + data + "</p>");
        };
    
        function send() {
            var data = document.getElementById('textarea').value;
            ws.send(name+ ":"+ data);
        }
    
        ws.onclose = function() {
            // 关闭 websocket
            console.log("连接已关闭...");
        };</script></body></html>
    

    最后,关于语音聊天程序源码如何上架请参考我的上一篇文章:https://blog.csdn.net/weixin_45629732/article/details/103309906

    更多相关内容
  • 自己动手搭建聊天APP

    千次阅读 多人点赞 2020-04-29 17:13:12
    自己动手实现聊天APP 成果 开始 时隔两年多,想再次看看 app 的开发。还记得两年前辛苦使用 andro studio 写 xml 的日子,五味杂陈。网上走了一圈,发现 dcloud 公司推出了 uni-app 和 5 + app 的方式开发 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/
    本人公众号:一只小安仔
    公众号

    展开全文
  • 几乎涵盖了所有apicloud开发APP的知识点。用到了比较深层次的界面技术。仿微信和QQ的聊天界面,包括输入法和表情包,选择图片,发送图片的,模拟两个人的对话。适合学习apicloud的开发模式。
  • 一款使用Netty+SpringBoot+MUI+HTML5+制作的仿微信的聊天APP,包括聊天,通讯录,发现,个人等模块。 此工程为后端工程,前端工程参照: 安卓版V1.0版 欢迎扫码下载apk测试,或者到前端工程的releases中下载 功能...
  • android安卓app开发之自己制作 语聊(发送录音) 功能.zip
  • 主要介绍了react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面,需要的朋友可以参考下
  • 聊天应用 我做了一个很酷的聊天应用! :)
  • 这是我自己制作的一个简单的聊天应用程序。 这些是您可以用来运行该项目的步骤。 如果您有任何github帐户,请首先克隆此代码,或者可以轻松将其zip文件下载到您的设备上。 然后将其解压缩到您的特定文件夹中 按照...
  • 基于css3制作的微信社交app网页版,在线问答聊天,网页聊天界面布局,聊天模块设计代码。这是一款非常实用的网页聊天工具。
  • 使用 React Native 制作 WhatsApp 用户界面 开发了 whatsapp 移动应用程序的用户界面,支持在选项卡之间滑动(通话、聊天等)等功能 使用 ReactNative 框架使用库 native_base 和 expo-camera 实现它。 入门 这个 ...
  • 现在,无代码app制作平台,让小白也能自制app。 一个完整的app系统涉及安卓客户端、iOS客户端、管理后台、手机运营助手、UI设计、产品规划等领域的知识,即使是专业的app开发公司,也是有5人以上的团队分工合作才能...

    不懂技术可以开发app软件吗?如果按照过去的开发模式,即使你是专业的开发人员,一个人要想完成一个app系统开发也并不容易。现在,无代码app制作平台,让小白也能自制app。

    一个完整的app系统涉及安卓客户端、iOS客户端、管理后台、手机运营助手、UI设计、产品规划等领域的知识,即使是专业的app开发公司,也是有5人以上的团队分工合作才能搞定。很多大型的app开发项目,甚至说需要数10人开发几个月。仅凭个人单枪匹马,很难搞定。而且开发完成后还需要大量测试、上线发布、系统维护、功能更新等工作。
    在这里插入图片描述

    正是因为开发app需要的技术人员多,开发周期长,所以造成了app开发价格高。

    现在技术的进步,出现了全新app的制作方法,通过免编程app在线制作网站,用户自己就能轻松制作app了。

    比如应用公园就是国内较早上线的无代码app在线制作网站,让大家自己就能轻松制作出新闻资讯app、电商商城app、外卖配送app等等。

    无代码自制app具体方法流程:

    1、网站注册账号登录。

    2、填写app的应用名称图标。

    3、选择功能模块:比如图文视频、支付交易、外卖配送、社区聊天等等。这些功能就像app的零部件一样,用户可以直接使用,进行自由搭建。

    在这里插入图片描述

    4、图文排版:在对应的功能模块中上传图片文字内容,进行app页面装修。

    5、海量素材:在app制作过程中也可以选择平台内app素材,比如标签、导航、背景logo、图标等等,帮助你更快的制作app。平台还有近百套完全搭配好的app模板可以一键使用。

    6、一键生成:app制作完毕,一键生成安卓及iOS双客户端,服务器数据库、管理后台等后台自动配置。

    通过无代码APP在线制作网站,自己做app成本降低90%,几天甚至几分钟就能完成app制作。
    在这里插入图片描述

    展开全文
  • 用Dcloud 的 uni-app全系,基于vue.js和微信小程序开发模式。 目前支持APP(android、ios)、H5、微信小程序、支付宝小程序5端。 在特定场景可以用weex进行原生渲染。 APP用的是Dcloud 公司的H5+进行原生接口调用。 ...

    介绍:

    前端:

    用Dcloud 的 uni-app全系,基于vue.js和微信小程序开发模式。

    目前支持APP(android、ios)、H5、微信小程序、支付宝小程序5端。

    在特定场景可以用weex进行原生渲染。

    APP用的是Dcloud 公司的H5+进行原生接口调用。

    后端:

    php 7.2.x

    Thinkphp 5.1作HTTP服务(nginx)。

    getWanWork作socket服务(socket 数据交互方式是二进制数据格式)。

    redis缓存,特定模块用sqllite缓存模块数据。

    mysql、mongodb 数据库。

    JWT进行用户验证。

    使用 http 进行发送文件

    双端:

    带有双端app源码文件,vue全源开源文件,以及后端开源文件。

    源码功能:

    消息提醒:有新的消息可以提醒(数字提醒、声音提醒)。

    聊天列表:显示最近所有的聊天列表,点击列表某一项可以打开聊天窗口;还可以删除聊天列表项。

    聊天窗口:可以发生文字消息、图片消息、表情;图片可以预览。

    站内公告:在顶部显示可以及时显示后台发布的公告。

    添加朋友:可以搜索对方的帐号名称然后添加到通讯录;添加前需要好友验证才能通过。

    群聊:显示群聊信息。

    列表:根据字母分组显示联系人;点击联系人可以查看详情资料;可以删除联系人。

    游戏:可添加你的官方网站或者游戏网站其它~自定义嵌入第三方网站

    个人信息:显示头像、昵称、帐号;可以修改头像和昵称。

    修改密码:可以修改登录密码。

    朋友圈:可以发动态


    网盘下载地址:

    https://zijiewangpan.com/PnyTuFta7G9


    图片:


    展开全文
  • 模拟QQ聊天工具APP

    2015-11-11 10:37:48
    基于Android开发平台下利用ListView及Layout Activity等来进行模拟QQ聊天程序
  • 使用APP inventor来制作一个属于自己的蓝牙串口软件

    千次阅读 多人点赞 2021-03-05 11:33:33
    使用APP inventor来制作一个属于自己的蓝牙串口软件 本文主要讲述蓝牙的发送和接收功能的制作 一、准备 1.APPinventor的网址:http://app.gzjkw.net/ 二、蓝牙APP界面的设置 首先我们新建一个项目,在这里我用的是...
  • 简单的whatsapp机器人,用于从图像和其他一些小功能制作贴纸 需要的东西 具有LTS版本的Node的服务器。 s WhatsApp帐户已在智能手机上登录 使用指南 克隆此仓库 安装所有必需的软件包npm install 运行bot node . ...
  • android如何制作出一个简单的聊天app

    万次阅读 2016-06-14 08:48:41
    HttpURLConnection可以发送http请求,那么我可以自己用tomcat建一个简单的服务器,用来接收请求完成响应,那么,当用户向用户发送一条消息时,可以在url中包含这个消息的一些信息,比如发送人的账号,消息内容,目标...
  • 字符串到颜色-每个不同用户的唯一颜色(如Whatsapp)。 React-Emoji-在聊天中支持表情符号。 头像-https: API,用于为每个新用户生成首字母的头像(如Google中一样)。 RTL支持RTL语言消息。 后端 该服务器由...
  • 学生和老师登录后可以查询通知新闻信息,收藏新闻,查看好友推荐,论坛发帖回复交流,查找添加好友及加好友聊天,发布视频动态,根据标签寻找兴趣相同的人,设置自己的课表,查看自己的收藏,修改个人信息。...
  • 在网上查资料,去学校图书馆借书发现居然还有《30天,App开发从0到1》这本书,官方有点牛批额。最终发现Workerman这个框架可以做即时通信,而且还容易上手。装好了后发现Workerman基础框架只能做服务推送,不能实现...
  • 为了制作自己的应用,请遵循JavaScriptMastery的 报名 制作注册表格: 首先,使用您的ProjectID和一个聊天管理员用户获取ChatID: 其次,使用ChatID,ProjectID和私钥作为常量来创建SignUp组件。 第三,使用 ,...
  • ListView实现列表数据与聊天对话框,具体说明请见CSDN博客介绍https://blog.csdn.net/weimeig/article/details/80279983
  • 这篇课程设计报告是基于Android实现的聊天机器人,包含实验目的、实验内容、程序设计和说明,给出了各个界面的设计思路,类的说明和运行界面截图,非常详细。 具体的源码获取可以参考我对应的博客《Android Studio...
  • 聊天应用 基于网络。 可扩展服务器。 多个客户。 为了学习而制作的一系列简单应用程序的一部分。 献给我的KU朋友。
  • 但是,现在,这里给大家分享一个简单易学的App制作方法,让普通人也可以开发制作属于自己app。傻瓜简单自建app步骤如下:一、开发前的准备需要准备一台电脑即可,不需要下载其他的软件等。二、需要的技能不需要懂...
  • 以上所有的沟通页面,都能用到聊天APP的原型;所以今天作者就教大家,如何用中继器做一个高保真的聊天对话原型。 一、效果介绍 原型预览地址:https://axhub.im/ax9/fb74652a483f3654/#g=1 二..
  • 直播APP制作时即时聊天功能实现

    千次阅读 2019-09-16 15:13:59
    目前直播app制作很火,直播中的即时聊天功能必不可少。云豹直播作为优质的直播平台源码提供商,在即时聊天功能方面必有其独到之处,下面为大家从如何实现即时聊天这个功能上答疑解惑。 即时聊天这个需求在很多app...
  • 它是使用Node.js,Express.js和Socket.io制作的对于前端,我使用了香草JS和Mustache模板。 UI是使用CSS设计的。 屏幕截图 应用登录 聊天室 要求 Node.js 12+ npm 6+ 安装 如果您想测试我的项目或做任何您想做的...
  • 聊天应用 通信媒体和监控 KIJ 任务组的工作,即使用编程语言制作具有客户端服务器架构的聊天应用程序 c.
  • uni-app+nodejs 制作简易聊天

    千次阅读 2020-03-14 18:07:41
    uni-app开发实战环境搭建HBuilderX配置环境1.安装插件2.搭建项目3.试运行4.自定义配置项目示例1.前端(H5/小程序)2.后端服务部分后续再说~ - ~ 环境搭建 零基础推荐使用 HBuilderX HBuilderX Windows/Mac下载安装 ...
  • chatbot app.zip

    2020-05-29 21:38:32
    聊天机器人app uniapp制作,类似tim界面 具体介绍可见 https://blog.csdn.net/weixin_43476533/article/details/106119770

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,821
精华内容 4,328
关键字:

自己制作聊天app