Blogger Information
Blog 57
fans 3
comment 0
visits 60511
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
仿百度商桥项目-DOM操作实现客服与多客户聊天/更换识别终端用户的方式/聊天记录保存到数据库
岂几岂几
Original
1343 people have browsed it

仿百度商桥项目-DOM操作实现客服与多客户聊天/更换识别终端用户的方式/聊天记录保存到数据库

学习心得

  • 都是复习, DOM操作, js,jQuery操作等. 基本实现西门老师上课讲的功能内容.

  • cURL的使用, 基础课没教, 在这里卡了蛮久, 需要找时间学习.

1. 实现思路

  • 切换客户聊天记录容器: 使用类似朱老师教的”页签菜单切换”demo.

  • 客服端刷新后, 找回正在沟通的客户: 客户端在创建链接前, 强制先输入手机号. 客服端通过后台管理系统, 用账号登录. 这样就可以用手机号或客服账号做客户连接对象全局数组/客服连接对象全局数组的key. 同时连接对象也设置属性identity, 为其赋值客户手机号或客服账号.

    • 客服刷新页面后, 可以遍历客户连接全局数组, 找到连接属性sendId值为客服账号的连接属性, 将其identity(即手机号)发送消息给客服页面, 客服页面遍历手机号数组, 把这些客户重新添加回客户列表. 然后以客户手机号, 客服账号做查询条件, 使用cURL模拟想laravel发送GET请求, 通过laravel从数据库中查询聊天记录, 并渲染回各个客户的”聊天页签”中(回填聊天记录没做了, 懒…).
  • 提供websocket通信的服务器(也就是warkerman的应用服务器), 使用cURL模拟发送POST请求到laravel, 通过laravel的控制器方法操作数据库, 把聊天记录保存到数据库.

    • 因为是服务器和服务器之间通信, 所以可以不必验证token, 在laravel中的VerifyCsrfToken中间件中, 把定位到保存聊天记录到数据库的控制器方法的路由加入到$except属性数组中, 即, 告诉laravel, 这个路由的post请求就不用验证token了.

2. 效果图

  • 1- 客服端界面

  • 2- 客户端界面

  • 3- 客服小姐姐分配到客户时的客服端界面

  • 4- 客户端收发消息

  • 5-客服端收发消息

  • 6-客服端切换聊天对象
  • 7- 数据库数据

3. 代码清单

  • 1- 使用warkerman搭建websocket通信服务器
  1. <?php
  2. use Workerman\Worker;
  3. require_once __DIR__ . '/workerman/Autoloader.php';
  4. // 加载发送请求的方法
  5. require_once 'save_msg.php';
  6. // 注意:这里与上个例子不同,使用的是websocket协议
  7. $ws_worker = new Worker("websocket://0.0.0.0:2000");
  8. // 启动4个进程对外提供服务
  9. $ws_worker->count = 4;
  10. /* 创建全局连接数组,key=连接的id,val=连接; 因为存在客户可能刷新浏览器页面, 触发websocket给同一个客户再分配一个新的连接id
  11. * 所以, 一般是用客户保存在数据表中的用户id来做key, 每次刷新, 用户id都跟最新分配的连接关联, 就能解决用户刷新的问题了.
  12. * 用户id从哪来? 可以在登录成功后把用户id保存到cookie中.
  13. * 另一种方案: 把$conns链接数组保存到缓存(如: Redis)中, 同样用用户id做key, 序列化/json格式化的连接做value.
  14. */
  15. /*
  16. * 新版本: key改为客户在前端录入的手机号, 使用消息数组中key为from_id的元素存储;
  17. * 客服数组的key改为管理员账号, 同样用from_id存储
  18. */
  19. // 客户连接数组
  20. $customerConns = [];
  21. // 客服连接数组
  22. $servicerConns = [];
  23. // 发送约定格式的消息数组(转换成json)
  24. function sendMessage($conn, $from_id, $type, $msg, $custom_id=null) {
  25. $sendInfo['from_id'] = $from_id;
  26. $sendInfo['type'] = $type;
  27. $sendInfo['msg'] = $msg;
  28. if($custom_id != null) {
  29. $sendInfo['custom_id'] = $custom_id;
  30. }
  31. $conn->send(json_encode($sendInfo));
  32. }
  33. function saveMessage($from, $to, $msg) {
  34. $msgInfo['from'] = $from;
  35. $msgInfo['to'] = $to;
  36. $msgInfo['msg'] = $msg;
  37. $msgInfo['send_time'] = time();
  38. saveMsg('cms.com/admin/servicer/savemsg', $msgInfo);
  39. }
  40. // 当收到客户端发来的数据后返回hello $data给客户端
  41. $ws_worker->onMessage = function($connection, $data)
  42. {
  43. global $customerConns;
  44. global $servicerConns;
  45. // 分辨用户类型, 只能客户和客服之间通信.
  46. // 判断客户端模拟用户登录状态的标识type, 若值为login标识登录成功. 则把用户的类型(客户/客服)
  47. // 以自定义属性的方式设置到$connection(即连接)中
  48. // json->数组
  49. $data = json_decode($data, true);
  50. if($data['type'] == 'login') {
  51. var_dump($connection->id);
  52. // 给连接动态加入group属性,标识[客户]和[客服]
  53. $connection->group = $data['group'];
  54. // 改用客户前端录入的手机号/客服小姐姐的登录账号作为连接的识别码, 不用$connection->id了
  55. $connection->identity = $data['from_id'];
  56. // 模拟"登录"的连接,放入到对应连接数组中
  57. if($data['group'] == 'admin') {// admin标识为[客服]
  58. var_dump('客服小姐姐' . $data['from_id'] . '上线了, 其分配到的链接id为: ' . $connection->id);
  59. // 改用客服小姐姐的登录账号作为key
  60. // $servicerConns[$connection->id] = $connection;
  61. $servicerConns[$data['from_id']] = $connection;
  62. // 遍历客户连接全局数组, 找到sendId为当前客服小姐姐的客户链接, 返回给这个客服小姐姐
  63. $customPhoneNumbers = [];
  64. foreach($customerConns as $phoneNumber => $customerConn) {
  65. if(!empty($customerConn->sendId) && $customerConn->sendId == $connection->identity) {
  66. $customPhoneNumbers[] = $phoneNumber;
  67. }
  68. }
  69. // 找到这位客服小姐姐服务的客户
  70. if(count($customPhoneNumbers) > 0) {
  71. sendMessage($connection, 'system', 'login', '继续为[' . implode(',', $customPhoneNumbers) . ']服务吧', $customPhoneNumbers);
  72. }
  73. } else {// member标识为[客户]
  74. // DEL: 改用用户在前端录入的手机号作为key
  75. // $customerConns[$connection->id] = $connection;
  76. $customerConns[$data['from_id']] = $connection;
  77. // 如果是客户登录, 还需要给他安排一个客服(array_rand()函数随机返回数组元素的key值)
  78. $connection->sendId = array_rand($servicerConns, 1);
  79. // 没有小姐姐可供分配
  80. // if(!is_numeric($connection->sendId)) {
  81. if(empty($connection->sendId)) {
  82. sendMessage($connection, 'system', 'msg', '暂无客服小姐姐在线, 请稍后刷新重试');
  83. var_dump('暂无客服小姐姐');
  84. return;
  85. }
  86. var_dump('给手机号为'.$connection->identity.'的客户(链接id: '.$connection->id.')分配的是'.$connection->sendId.'客服小姐姐(链接id: '.$servicerConns[$connection->sendId]->id.')');
  87. // 通知这位客服, 有新客户进来
  88. $target = $servicerConns[$connection->sendId];
  89. sendMessage($target, 'system', 'login', '有新客户登录, 电话号码为: ' . $connection->identity, $connection->identity);
  90. }
  91. } else if($data['type'] == 'msg') {// 模拟"发送数据"的连接
  92. // 判断发送数据的连接是[客服]还是[客户]
  93. if($connection->group == 'member') {// 客户
  94. // if(!is_numeric($connection->sendId)) {
  95. if(empty($connection->sendId)) {
  96. sendMessage($connection, 'system', 'msg', '为您服务的小姐姐可能已掉线, 请刷新');
  97. var_dump('暂无客服小姐姐');
  98. return;
  99. }
  100. // 获取在客户登录时指定的客服连接
  101. $target = $servicerConns[$connection->sendId];
  102. var_dump('客户' .$connection->identity . '给账号为' . $connection->sendId . '的客服小姐姐发信息');
  103. sendMessage($target, $connection->identity, 'msg', $data['msg']);
  104. // 保存消息到数据库
  105. saveMessage($connection->identity, $target->identity, $data['msg']);
  106. } else if($connection->group == 'admin') {// 客服
  107. // 跟谁说话
  108. $phoneNumber = $data['sendId'];
  109. if(empty($phoneNumber) || !isset($customerConns[$phoneNumber])) {
  110. sendMessage($connection, 'system', 'msg', '该用户不存在, 可能已下线');
  111. return;
  112. }
  113. // 要跟其说话的客户的链接对象
  114. $target = $customerConns[$phoneNumber];
  115. sendMessage($target, $connection->identity, 'msg', $data['msg']);
  116. saveMessage($connection->identity, $target->identity, $data['msg']);
  117. var_dump('客服' .$connection->identity . '给手机号为' . $phoneNumber . '的客户发信息');
  118. }
  119. }
  120. };
  121. // 当有连接断开时
  122. $ws_worker->onClose = function($connection) {
  123. global $servicerConns;
  124. global $customerConns;
  125. if($connection->group == 'admin') {// 客服断开
  126. $unconnId = $connection->identity;//$connection->id;
  127. // 把断开的客服连接移除出客服连接数组
  128. unset($servicerConns[$connection->id]);
  129. var_dump('客服' . $unconnId . '下线了');
  130. // 客服断开, 需要给该客服负责的客户重新分配新客服
  131. foreach($customerConns as $customerConn) {
  132. if($customerConn->sendId == $unconnId) {// 该客服小姐姐负责的客户
  133. $customerConn->sendId = array_rand($servicerConns, 1);
  134. // 没分配到客服小姐姐, 直接结束分配.
  135. if(empty($customerConn->sendId)) {
  136. sendMessage($connection, 'system', 'msg', '坏了, 客服小姐姐的网络开小差了, 请稍后刷新重试吧');
  137. var_dump('当前没有客服小姐姐在线');
  138. continue;
  139. }
  140. var_dump('重新给手机号为'.$customerConn->identity.'的客户(链接id: '.$customerConn->id.')分配的是'.$customerConn->sendId.'客服小姐姐(链接id: '.$servicerConns[$customerConn->sendId]->id.')');
  141. // 新分配的客服小姐姐的连接
  142. $target = $servicerConns[$customerConn->sendId];
  143. // 给新分配到的客服小姐姐发分配消息
  144. sendMessage($target, 'system', 'login', '有新客户登录, id为:' . $customerConn->identity, $customerConn->identity);
  145. }
  146. }
  147. } else {// 客户断开
  148. // 断开的客户连接id
  149. $unconnId = $connection->identity;
  150. var_dump('客户' . $unconnId . '下线了');
  151. // 负责该客户的客服小姐姐id
  152. $servicerId = $connection->sendId;
  153. // 把断开的客户连接移除出客户连接数组
  154. unset($customerConns[$connection->identity]);
  155. if(empty($servicerId)) {// 判断断开的客户是否有客服接待, 没有, 则直接断开客户连接;
  156. return;
  157. }
  158. // 客户断开, 系统给负责该客户的客服发消息, 不必再负责该客户
  159. $target = $servicerConns[$servicerId];
  160. sendMessage($target, 'system', 'logout', '客户' . $unconnId . '跟你说了声拜拜后, 下线了', $unconnId);
  161. }
  162. };
  163. // 运行worker
  164. Worker::runAll();
  • 2- 使用cURL模拟发送保存聊天记录的脚本
  1. <?php
  2. function saveMsg($url, $data) {
  3. // $header = ["Content-Type:application/x-www-form-urlencoded", "token:test", "client:h5"];
  4. $header = ["Content-Type:application/json", "token:test", "client:h5"];
  5. $res = curlPost($url, $data, 5, $header, 'json');
  6. var_dump($res);
  7. return $res;
  8. }
  9. /**
  10. * 传入数组进行HTTP POST请求
  11. */
  12. function curlPost($url, $post_data = array(), $timeout = 5, $header = "", $data_type = "") {
  13. $header = empty($header) ? '' : $header;
  14. //支持json数据数据提交
  15. if($data_type == 'json'){
  16. $post_string = json_encode($post_data);
  17. }elseif($data_type == 'array') {
  18. $post_string = $post_data;
  19. }elseif(is_array($post_data)){
  20. $post_string = http_build_query($post_data, '', '&');
  21. }
  22. var_dump($post_string);
  23. $ch = curl_init(); // 启动一个CURL会话
  24. curl_setopt($ch, CURLOPT_URL, $url); // 要访问的地址
  25. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查 // https请求 不验证证书和hosts
  26. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
  27. // curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
  28. //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
  29. curl_setopt($ch, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
  30. curl_setopt($ch, CURLOPT_POST, true); // 发送一个常规的Post请求
  31. curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string); // Post提交的数据包
  32. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); // 设置超时限制防止死循环
  33. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  34. //curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
  35. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 获取的信息以文件流的形式返回
  36. curl_setopt($ch, CURLOPT_HTTPHEADER, $header); //模拟的header头
  37. $result = curl_exec($ch);
  38. // 打印请求的header信息
  39. //$a = curl_getinfo($ch);
  40. //var_dump($a);
  41. curl_close($ch);
  42. return $result;
  43. }
  • 3- 客户端
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>"百度商桥"客服端</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css" media="all">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
  10. <style>
  11. * {
  12. padding: 0;
  13. margin: 0;
  14. box-sizing: border-box;
  15. }
  16. body {
  17. padding: 10px;
  18. width: 100vw;
  19. height: 100vh;
  20. background-color: #f0f0f0;
  21. display: grid;
  22. gap: 10px;
  23. grid-template-columns: 240px auto;
  24. grid-template-rows: 3fr 2fr;
  25. grid-template-areas:
  26. "customer-list message-list"
  27. "customer-list message-send";
  28. }
  29. .customer-list {
  30. grid-area: customer-list;
  31. background-color: white;
  32. border: 1px solid #ccc;
  33. }
  34. .message-list {
  35. grid-area: message-list;
  36. background-color: white;
  37. overflow-y: auto;
  38. padding: 10px;
  39. display: grid;
  40. grid-template-rows: 50px auto;
  41. }
  42. .message-list > .system-message {
  43. height: 40px;
  44. width: 100%;
  45. background-color: wheat;
  46. border: 1px solid #eaeaea;
  47. margin-bottom: 10px;
  48. border-radius: 5PX;
  49. overflow-y: hidden;
  50. }
  51. .message-list > .private-chat-box {
  52. background-color: #DFECEC;
  53. border-radius: 5px;
  54. }
  55. .show {
  56. display: block;
  57. }
  58. .hide {
  59. display: none;
  60. }
  61. .message-send {
  62. display: grid;
  63. grid-auto-rows: auto 40px;
  64. grid-area: message-send;
  65. background-color: white;
  66. padding: 10px;
  67. }
  68. .message-send > .message-input-area {
  69. padding: 5px;
  70. background-color: #fafafa;
  71. border: 1px solid #ccc;
  72. margin-bottom: 10px;
  73. border-radius: 5px;
  74. outline: none;
  75. overflow-y: auto;
  76. }
  77. .message-send > .message-input-area:hover {
  78. box-shadow: 0px 0px 1px #333;
  79. }
  80. .message-send > .btn-area {
  81. width: 100%;
  82. text-align: right;
  83. }
  84. .msg-box-friend {
  85. padding: 10px;
  86. width: 75%;
  87. margin: 5px auto 0 5px;
  88. background-color: lightblue;
  89. border: 1px solid #ccc;
  90. border-radius: 5px;
  91. /* overflow-x: wordwrap; */
  92. white-space:normal;
  93. }
  94. .msg-box-me {
  95. padding: 10px;
  96. width: 75%;
  97. margin: 5px 5px 0 auto;
  98. background-color: wheat;
  99. border: 1px solid #ccc;
  100. border-radius: 5px;
  101. /* overflow-x: wordwrap; */
  102. white-space:normal;
  103. }
  104. .customer-item {
  105. border: 1px solid #e0e0e0;
  106. border-radius: 5px;
  107. margin: 5px;
  108. padding: 5px 10px;
  109. }
  110. .active {
  111. background-color: skyblue;
  112. }
  113. .bling {
  114. color: red;
  115. background-color: linen;
  116. }
  117. </style>
  118. </head>
  119. <body>
  120. <input type="hidden" name="username" value="{{$username}}">
  121. <!-- 客户列表 -->
  122. <div class="customer-list">
  123. </div>
  124. <!-- 消息记录 -->
  125. <div class="message-list">
  126. <div class="system-message"></div>
  127. <!-- <div class="private-chat-box"></div> -->
  128. </div>
  129. <!-- 发送消息 -->
  130. <div class="message-send">
  131. <div class="message-input-area" contenteditable="true">
  132. </div>
  133. <div class="btn-area">
  134. <span class="layui-btn layui-btn-success" onclick="send()">发送</span>
  135. </div>
  136. </div>
  137. </body>
  138. <script>
  139. layui.use(['layer'], function() {
  140. layer = layui.layer;
  141. $ = layui.jquery;
  142. });
  143. // 假设服务端ip为127.0.0.1
  144. ws = new WebSocket("ws://127.0.0.1:2000");
  145. /* 当客户端连通服务器端的时候 */
  146. ws.onopen = function() {
  147. // 当客户端连通服务端时, 把当前客户端的用户标识(客户/客服)发给服务端
  148. var data = {};
  149. // js对象的属性可以自定义. type: login, 标识用户行为为声明登录;type: msg, 标识用户行为为发送消息。
  150. data.type = 'login';
  151. // 约定admin代表登录的用户是客服
  152. data.group = 'admin';
  153. // 使用登录账号作为客服小姐姐的登录标识(因为链接id在刷新或关闭浏览器后值会改变)
  154. data.from_id = $('input[name="username"]').val();
  155. // 发送JSON格式的数据
  156. ws.send(JSON.stringify(data));
  157. };
  158. ws.onmessage = function(e) {
  159. var data = JSON.parse(e.data);
  160. // alert("收到服务端的消息:" + e.data);
  161. // 系统发来的消息, 表示有新客户接入, 并分配到当前客服小姐姐
  162. if(data.from_id == 'system' && data.type == 'login') {
  163. if(!Array.isArray(data.custom_id)) {
  164. addOldCustomer(data.custom_id);
  165. } else {
  166. data.custom_id.forEach(function(item) {
  167. addOldCustomer(item);
  168. });
  169. }
  170. // 发送客户接入的系统消息
  171. var str = "<span style='margin-right: 20px; color: red;'>系统消息: "+data.msg+"</span>";
  172. $('.system-message').prepend($(str));
  173. } else if(data.from_id == 'system' && data.type == 'logout') {// 客户下线
  174. // 客户列表移除客户
  175. $('.customer-item[data-id="'+data.custom_id+'"]').remove();
  176. // 聊天记录窗口移除聊天容器
  177. $('.private-chat-box[data-id="'+data.custom_id+'"]').remove();
  178. // 系统消息走一波
  179. // 发送客户接入的系统消息
  180. var str = "<span style='margin-right: 20px;'>系统消息: 客户"+data.custom_id+"下线了</span>";
  181. $('.system-message').prepend($(str));
  182. } else if(data.type = 'msg') {// 客户给客服发消息
  183. // 客户列表中的客户项背景色变一下再还原
  184. // 客户列表中的当前客户项
  185. var customerItem = $('.customer-item[data-id="'+data.from_id+'"]');
  186. blingbling(customerItem);
  187. // 获取正在聊天的窗口的data-id值
  188. var chatting_id = $('.private-chat-box').filter('.show').data('id');
  189. // 当前没有正在聊天的窗口, 则显示跟发送消息的客户的聊天窗口
  190. if(chatting_id == undefined) {
  191. // 显示聊天对话框
  192. var chatbox = $('.private-chat-box').filter('[data-id="'+data.from_id+'"]');
  193. // chatbox.removeClass('hide').addClass('show');
  194. // 相当于点击当前发送消息的客户的客户向
  195. var customItem = $('.customer-item[data-id="'+data.from_id+'"]')
  196. talkTo(customerItem[0]);
  197. // 显示客户讲的消息
  198. var str = "<div class='msg-box-friend'>"+data.from_id+"说: "+data.msg+"</div>";
  199. $(str).appendTo(chatbox);
  200. } else if(chatting_id != data.from_id) {// 正在聊天的客户不是当前发送消息的客户
  201. // 把消息插入跟当前客户的聊天窗口
  202. var str = "<div class='msg-box-friend'>"+data.from_id+"说: "+data.msg+"</div>";
  203. $(str).appendTo($('.private-chat-box').filter('[data-id="'+data.from_id+'"]'));
  204. // 在客户列表中当前发送消息的客户项右边加上未读标签数量
  205. // 客户项中的未读消息数量框
  206. var msgCount = customerItem.find('.layui-badge');
  207. // 如果没有, 表示之前没有未读消息, 加上即可
  208. if(msgCount == undefined || msgCount.length < 1) {
  209. $('<span class="layui-badge">1</span>').appendTo(customerItem);
  210. } else {// 有, 未读消息加一
  211. var count = parseInt(msgCount.html()) + 1;
  212. msgCount.html(count);
  213. }
  214. } else if(chatting_id == data.from_id) {// 当前聊天窗口就是当前发送消息的客户
  215. // 逻辑可以直接用chatting_id是undefined的逻辑, 后期考虑合并
  216. // 显示聊天对话框
  217. var chatbox = $('.private-chat-box').filter('[data-id="'+data.from_id+'"]');
  218. chatbox.removeClass('hide').addClass('show');
  219. console.log(chatbox);
  220. // 显示客户讲的消息
  221. var str = "<div class='msg-box-friend'>"+data.from_id+"说: "+data.msg+"</div>";
  222. $(str).appendTo(chatbox);
  223. }
  224. }
  225. };
  226. // 客服刷新网页, 找回正在沟通的客户列表
  227. function addOldCustomer(custom_id) {
  228. // 把新登录的客户加到客户列表区
  229. var customer = $('<div class="customer-item" data-id="'+custom_id+'" onclick="talkTo(this)">客户'+custom_id+'</div>');
  230. customer.appendTo('.customer-list');
  231. // 闪一下
  232. blingbling(customer);
  233. // 在消息记录容器(message-list)中插入跟登录的客户聊天的对话框div
  234. var privateChatBox = '<div class="private-chat-box hide" data-id="'+custom_id+'"></div>';
  235. $(privateChatBox).appendTo('.message-list');
  236. }
  237. // 客户上线/有未读消息/有新消息, 变一下色, 2秒后复原
  238. function blingbling(jqele) {
  239. jqele.addClass('bling');
  240. setTimeout(() => {
  241. jqele.removeClass('bling');
  242. }, 2000);
  243. }
  244. // 点击客户列表区, 选择要聊天的客户对象
  245. function talkTo(ele) {
  246. $(ele).siblings().removeClass('active');
  247. $(ele).addClass('active');
  248. // 显示聊天窗口
  249. var dataId = $(ele).data('id');
  250. $('.private-chat-box').removeClass('show').addClass('hide');
  251. $('.private-chat-box[data-id="'+dataId+'"]').removeClass('hide').addClass('show');
  252. // 移除未读消息
  253. $(ele).find('.layui-badge').remove();
  254. }
  255. function send() {
  256. var str = "<div class='msg-box-me'>我说: "+$('.message-input-area').html()+"</div>";
  257. var data = {};
  258. // 标识此次发送的数据是发送消息
  259. data.type='msg';
  260. // 标识是从客服端发的
  261. data.group = 'admin'
  262. // 获取当前选中的客户
  263. var customer_id = $('div[class*=active]').data('id');
  264. if(isNaN(customer_id)) {
  265. return layer.alert('请先选中一个客户, 再发送消息.');
  266. }
  267. data.sendId = customer_id;
  268. // 要发送的消息
  269. data.msg = $('.message-input-area').html();
  270. $(str).appendTo('.message-list > .private-chat-box.show');
  271. // 转成json字符串发送。
  272. ws.send(JSON.stringify(data));
  273. $('.message-input-area').html('');
  274. }
  275. </script>
  276. </html>
  • 4- 客服端
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>"百度商桥"客户端</title>
  7. <link rel="stylesheet" href="/static/plugin/layui/css/layui.css" media="all">
  8. <script src="/static/plugin/layui/layui.js"></script>
  9. <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
  10. <style>
  11. * {
  12. margin: 0;
  13. padding: 0;
  14. box-sizing: border-box;
  15. }
  16. body {
  17. background-color: #fafafa;
  18. }
  19. .im {
  20. width: 400px;
  21. height: 520px;
  22. background-color: wheat;
  23. margin: 40px auto;
  24. padding: 10px;
  25. box-shadow: 0 0 5px #999;
  26. }
  27. .im .history {
  28. background-color: white;
  29. border: 1px solid #aaa;
  30. width: 100%;
  31. height: 290px;
  32. padding: 5px;
  33. overflow-y: auto;
  34. border-radius: 5px;
  35. }
  36. .im .inputing {
  37. background-color: white;
  38. border: 1px solid #aaa;
  39. width: 100%;
  40. height: 150px;
  41. margin-top: 10px;
  42. padding: 5px;
  43. outline: none;
  44. border-radius: 5px;
  45. }
  46. .im .btn-area {
  47. width: 100%;
  48. text-align: right;
  49. margin-top: 10px;
  50. }
  51. .msg-box-friend {
  52. padding: 10px;
  53. width: 75%;
  54. margin: 5px auto 0 5px;
  55. background-color: lightblue;
  56. border: 1px solid #ccc;
  57. border-radius: 5px;
  58. /* overflow-x: wordwrap; */
  59. white-space:normal;
  60. }
  61. .msg-box-me {
  62. padding: 10px;
  63. width: 75%;
  64. margin: 5px 5px 0 auto;
  65. background-color: wheat;
  66. border: 1px solid #ccc;
  67. border-radius: 5px;
  68. /* overflow-x: wordwrap; */
  69. white-space:normal;
  70. }
  71. </style>
  72. </head>
  73. <body>
  74. <input type="hidden" name="phone_number" value="">
  75. <div class="im">
  76. <div class="history">
  77. </div>
  78. <!-- contenteditable="true", 这个div就可编辑了 -->
  79. <div class="inputing" contenteditable="true">
  80. </div>
  81. <div class="btn-area">
  82. <span class="layui-btn layui-btn-success" onclick="send()">发送</span>
  83. </div>
  84. </div>
  85. </body>
  86. <script>
  87. layui.use(['layer'], function() {
  88. layer = layui.layer;
  89. $ = layui.jquery;
  90. // 在layer加载完成后再弹出, 否则无法上下左右居中显示弹出框.
  91. layer.ready(function() {
  92. // 用户填入手机号
  93. getPhoneNumber();
  94. });
  95. });
  96. function getPhoneNumber() {
  97. layer.prompt({
  98. formType: 0,
  99. title: '请输入你的手机号',
  100. area: ['800px', '350px'],
  101. btn: ['确定']
  102. }, function(value, index, elem) {
  103. // 验证是否是有效地手机号码
  104. if(!(/^1[3456789]\d{9}$/.test(value))){
  105. return layer.alert("手机号码有误,请重填");
  106. }
  107. // 保存电话号码到隐藏域
  108. $("input[name='phone_number']").val(value);
  109. // 连接websocket服务器
  110. connect(value);
  111. layer.close(index);
  112. });
  113. }
  114. function connect(value) {
  115. // 假设服务端ip为127.0.0.1
  116. ws = new WebSocket("ws://127.0.0.1:2000");
  117. /* 当客户端连通服务器端的时候 */
  118. ws.onopen = function() {
  119. // 当客户端连通服务端时, 把当前客户端的用户标识(客户/客服)发给服务端
  120. var data = {};
  121. // js对象的属性可以自定义. type: login, 标识用户行为为声明登录;type: msg, 标识用户行为为发送消息。
  122. data.type = 'login';
  123. // 约定admin表示登录的用户是客户
  124. data.group = 'member';
  125. // 识别客户的唯一码从连接id改为用户手机号, 因为刷新后, 连接id就会变.
  126. data.from_id = value;
  127. // 发送JSON格式的数据
  128. ws.send(JSON.stringify(data));
  129. };
  130. // 从服务器端获取到消息时
  131. ws.onmessage = function(e) {
  132. // alert("收到服务端的消息:" + e.data);
  133. var receive = JSON.parse(e.data);
  134. var str = "<div class='msg-box-friend'>"+receive.from_id+"说: "+receive.msg+"</div>";
  135. $(str).appendTo('.history');
  136. };
  137. }
  138. function send() {
  139. // 消息框
  140. var str = "<div class='msg-box-me'>我说: "+$('.inputing').html()+"</div>";
  141. var data = {};
  142. // 标识此次发送的数据是发送消息
  143. data.type='msg';
  144. // 标识是从客服端发的
  145. data.group = 'admin'
  146. // 标识私聊的对象id,0标识群发. DEL_客户连接时由系统随机分配客服小姐姐, 不需要手动指定了
  147. // data.sendId = 0;
  148. // 要发送的消息
  149. data.msg = $('.inputing').html();
  150. // 在消息历史中显示发送的消息
  151. $(str).appendTo('.history');
  152. // 发送JSON格式的数据
  153. ws.send(JSON.stringify(data));
  154. $('.inputing').html('');
  155. }
  156. </script>
  157. </html>
  • 5- 保存消息的控制器方法
  1. public function saveMsg(Request $req) {
  2. $msg = $req->all();
  3. DB::table('msg_list')->insert($msg);
  4. return json_encode(['status' => 0, 'message' => '保存成功']);
  5. }
Correcting teacher:GuanhuiGuanhui

Correction status:qualified

Teacher's comments:标题可以适当的简洁些!
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post