-
2019-12-02 11:29:44
制作语音聊天程序源码不是一件容易的事情,或许一些前辈的做法能够给我们一些启示,以下代码为转载,来自云豹科技程序员——知乎作者111,原文链接如上,感谢授权
首先是客户端代码,这些代码主要实现内容有:
- 当用户进入退出直播间时,其他人会收到通知
- 聊天室内消息互通
- 管理员功能
具体代码如下:
# 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 是 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 包下三个类:
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">注:输入好友的尬聊号搜索添加好友。尬聊号可以在 ‘我的’ -> ‘尬聊号’ 处查看。</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和微信的聊天界面.zip
2019-07-13 23:03:58几乎涵盖了所有apicloud开发APP的知识点。用到了比较深层次的界面技术。仿微信和QQ的聊天界面,包括输入法和表情包,选择图片,发送图片的,模拟两个人的对话。适合学习apicloud的开发模式。 -
EasyChat-Netty:基于Netty+SpringBoot+WebSocket+MUI+HTML5+制作的仿微信聊天APP,该仓库为后端系统,主要...
2021-05-13 14:05:31一款使用Netty+SpringBoot+MUI+HTML5+制作的仿微信的聊天APP,包括聊天,通讯录,发现,个人等模块。 此工程为后端工程,前端工程参照: 安卓版V1.0版 欢迎扫码下载apk测试,或者到前端工程的releases中下载 功能... -
android安卓app开发之自己制作 语聊(发送录音) 功能.zip
2021-01-12 17:19:16android安卓app开发之自己制作 语聊(发送录音) 功能.zip -
react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面
2020-10-16 00:30:13主要介绍了react-native聊天室|RN版聊天App仿微信实例|RN仿微信界面,需要的朋友可以参考下 -
chat-app:我制作的聊天应用!
2021-05-28 09:45:46聊天应用 我做了一个很酷的聊天应用! :) -
Chat-App:这是我制作的一个简单的聊天应用
2021-05-23 21:26:06这是我自己制作的一个简单的聊天应用程序。 这些是您可以用来运行该项目的步骤。 如果您有任何github帐户,请首先克隆此代码,或者可以轻松将其zip文件下载到您的设备上。 然后将其解压缩到您的特定文件夹中 按照... -
app网页版聊天界面设计代码.rar
2019-06-29 16:45:04基于css3制作的微信社交app网页版,在线问答聊天,网页聊天界面布局,聊天模块设计代码。这是一款非常实用的网页聊天工具。 -
Whatsapp_UI:在原生基础的帮助下,使用 expo react-native 制作 WhatsApp 的 UI
2021-05-29 05:09:11使用 React Native 制作 WhatsApp 用户界面 开发了 whatsapp 移动应用程序的用户界面,支持在选项卡之间滑动(通话、聊天等)等功能 使用 ReactNative 框架使用库 native_base 和 expo-camera 实现它。 入门 这个 ... -
无代码app在线制作网站:自己做app的制作方法
2020-09-15 18:27:52现在,无代码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制作。
-
ThinkPHP聊天室即时通讯系统 H5聊天系统APP源码 类似微信的聊天APP
2021-03-03 17:33:50用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界面的设置 首先我们新建一个项目,在这里我用的是... -
node-whatsapp-bot:简单的whatsapp机器人,用于从图像和其他一些小功能制作贴纸
2021-05-02 09:35:16简单的whatsapp机器人,用于从图像和其他一些小功能制作贴纸 需要的东西 具有LTS版本的Node的服务器。 s WhatsApp帐户已在智能手机上登录 使用指南 克隆此仓库 安装所有必需的软件包npm install 运行bot node . ... -
android如何制作出一个简单的聊天app
2016-06-14 08:48:41HttpURLConnection可以发送http请求,那么我可以自己用tomcat建一个简单的服务器,用来接收请求完成响应,那么,当用户向用户发送一条消息时,可以在url中包含这个消息的一些信息,比如发送人的账号,消息内容,目标... -
nodejs-reactjs-chat-app:使用React和Socket.io-client制作前端的聊天应用程序,使用Nodejs,Express和...
2021-05-11 05:28:29字符串到颜色-每个不同用户的唯一颜色(如Whatsapp)。 React-Emoji-在聊天中支持表情符号。 头像-https: API,用于为每个新用户生成首字母的头像(如Google中一样)。 RTL支持RTL语言消息。 后端 该服务器由... -
安卓AndroidStudio大学生交友聊天社交app设计
2021-10-10 21:31:27学生和老师登录后可以查询通知新闻信息,收藏新闻,查看好友推荐,论坛发帖回复交流,查找添加好友及加好友聊天,发布视频动态,根据标签寻找兴趣相同的人,设置自己的课表,查看自己的收藏,修改个人信息。... -
用APICloud开发仿微信聊天App制作经验分享
2021-04-16 16:52:01在网上查资料,去学校图书馆借书发现居然还有《30天,App开发从0到1》这本书,官方有点牛批额。最终发现Workerman这个框架可以做即时通信,而且还容易上手。装好了后发现Workerman基础框架只能做服务推送,不能实现... -
chatapp:无服务器React聊天应用
2021-05-20 17:37:16为了制作自己的应用,请遵循JavaScriptMastery的 报名 制作注册表格: 首先,使用您的ProjectID和一个聊天管理员用户获取ChatID: 其次,使用ChatID,ProjectID和私钥作为常量来创建SignUp组件。 第三,使用 ,... -
ListView实现列表数据与聊天对话框
2018-05-12 10:49:48ListView实现列表数据与聊天对话框,具体说明请见CSDN博客介绍https://blog.csdn.net/weimeig/article/details/80279983 -
Android实现聊天机器人——课程设计报告,优秀课设
2021-08-12 21:57:49这篇课程设计报告是基于Android实现的聊天机器人,包含实验目的、实验内容、程序设计和说明,给出了各个界面的设计思路,类的说明和运行界面截图,非常详细。 具体的源码获取可以参考我对应的博客《Android Studio... -
ChatApp:聊天服务器和客户端
2021-05-03 00:30:48#聊天应用 基于网络。 可扩展服务器。 多个客户。 为了学习而制作的一系列简单应用程序的一部分。 献给我的KU朋友。 -
app制作教程简单易学:傻瓜简单自建app,步骤如下
2021-05-22 15:58:37但是,现在,这里给大家分享一个简单易学的App制作方法,让普通人也可以开发制作属于自己的app。傻瓜简单自建app步骤如下:一、开发前的准备需要准备一台电脑即可,不需要下载其他的软件等。二、需要的技能不需要懂... -
Axure中级教程:用中继器做聊天APP原型
2021-01-03 11:04:54以上所有的沟通页面,都能用到聊天APP的原型;所以今天作者就教大家,如何用中继器做一个高保真的聊天对话原型。 一、效果介绍 原型预览地址:https://axhub.im/ax9/fb74652a483f3654/#g=1 二.. -
直播APP制作时即时聊天功能实现
2019-09-16 15:13:59目前直播app制作很火,直播中的即时聊天功能必不可少。云豹直播作为优质的直播平台源码提供商,在即时聊天功能方面必有其独到之处,下面为大家从如何实现即时聊天这个功能上答疑解惑。 即时聊天这个需求在很多app... -
chat-app:node-express-socket.io聊天应用
2021-05-24 19:59:06它是使用Node.js,Express.js和Socket.io制作的对于前端,我使用了香草JS和Mustache模板。 UI是使用CSS设计的。 屏幕截图 应用登录 聊天室 要求 Node.js 12+ npm 6+ 安装 如果您想测试我的项目或做任何您想做的... -
ChatApp:KIJ任务组的通信媒体和工作监控,即使用c编程语言制作具有客户端服务器架构的聊天应用程序
2021-06-11 01:31:18聊天应用 通信媒体和监控 KIJ 任务组的工作,即使用编程语言制作具有客户端服务器架构的聊天应用程序 c. -
uni-app+nodejs 制作简易聊天室
2020-03-14 18:07:41uni-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