├── LICENSE ├── README.md ├── readme_imgs ├── chat_demo_chatBox.jpg ├── chat_demo_friends.jpg ├── chat_demo_jiagou_book.png ├── chat_demo_login.jpg └── chat_demo_wechat_qrcode.png └── src ├── css └── index.css ├── images ├── Arrow-Left.png ├── Avatar-1.png ├── Avatar-2.png ├── Avatar-3.png ├── Avatar-4.png ├── Vector.png ├── group-icon.png └── group.png ├── index.html └── js ├── controller.js ├── imservice.js └── restapi.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 GoEasySupport 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 手把手教你用GoEasy实现Websocket IM聊天 2 | 本文会持续更新,最新版本请访问https://github.com/GoEasySupport/goeasy-chat-demo 3 | 4 | 经常有朋友问起GoEasy如何实现IM,今天就手把手的带大家从头到尾用GoEasy实现一个完整IM聊天,全套代码已经放在了github。 5 | 6 | 今日的前端技术发展可谓百花争鸣,为了确保本文能帮助到使用任何技术栈的前端工程师,Demo的源码实现上选择了最简单的HTML+JQuery的方式,所以,不论您是准备用Uniapp开发移动APP,还是准备写个小程序,不论你喜欢用React还是VUE,还是React-native或ionic, 或者您直接用原生Javascript和Typescript,都是可以轻松理解,全套代码已经放在github上了,下载后不需要搭建任何环境,直接用浏览器打开,就可以用来聊天了。 7 | 8 | ![image](./readme_imgs/chat_demo_login.jpg) ![image](./readme_imgs/chat_demo_friends.jpg) ![image](./readme_imgs/chat_demo_chatBox.jpg) 9 | 10 | 作为一名程序员,在编码之前,首先要做的当然是架构设计!什么?确认不是装逼?当然,别忘了星爷的那句话:我是一名程序员!没有思想的程序员,跟咸鱼有什么区别呢? 11 | ![image](./readme_imgs/chat_demo_jiagou_book.png) 12 | 13 | 咳咳咳,正文开始: 14 | 15 | **首先我们代码层将整个功能分为四层:** 16 | 17 | 1. 华丽的展示层(index.html):你们负责功能优雅强大,我负责貌美如花。展示层其实就是纯静态的html,显示界面,高端点说,就是负责人机交互的。 18 | 2. 承上启下的控制层(controller.js): 控制层作用就是接受页面操作的参数,调用service层,根据页面的操作指令或者service层的反馈,负责对页面的展示做出控制。不可以编写任何与展示逻辑无关的代码,也就是不能侵入任何业务逻辑。衡量这一层做的好不好的的标准,就是假设删掉controller和view层,service能准确完整的描述所有的业务逻辑。 19 | 3. 运筹帷幄的关键核心业务层(service.js): 接受controller层的指令,实现业务逻辑,必要时候调用goeasy提供网络通讯支持,或调用restapi层完成数据的查询和保存。这一层包含所有的业务逻辑,任何业务逻辑相关的代码,都不可以漏到其他层,确保只要service存在,整个项目的灵魂就存在,确保service层完全是原生代码实现业务逻辑,而没有类似于vue或者小程序前端框架的语法和代码渗入,从而达到service层能够在任何前端框架通用。 20 | 4. 神通广大的服务器交互层(restapi.js): 根据传入的参数完成服务器端接口的调用,来实现数据查询或、修改或保存,并且返回结果,不参与任何业务逻辑。在实践中大部分是负责发送http请求和服务器交互。 21 | 22 | 分层的目标就是为了确保除了在核心业务层以外的其他层次能够被轻易的替换。举例:我们当前的版本是使用html+jquery完成,如果希望再开发一个Uniapp实现的小程序或者app,只需要用Uniapp画个新外壳,对controller层做一些修改,就可轻松完成一个小程序或者APP版的IM聊天,不需要对service和restapi做任何修改 。同理,如果服务器端发生变化,或者更换了与服务器的交互方式,只需要对restapi做出修改,其他三层则不受任何影响。 23 | 24 | 25 | OK, 有了如此清晰而优秀的代码结构分层设计,就差一段优雅的代码来实现了。 26 | 27 | 28 | **运行步骤:** 29 | 1. 在imservice.js里将appkey替换为您自己的common key 30 | 2. 不用搭建任何环境,在浏览器里直接打开index.html,就可以运行 31 | 2. 在restapi.js 里 可以找到用户名和密码 32 | 3. 部分高级功能,默认是关闭的,可以在我的应用->查看详情,高级功能里自助开通~ 33 | -------------------------------------------------------------------------------- /readme_imgs/chat_demo_chatBox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/readme_imgs/chat_demo_chatBox.jpg -------------------------------------------------------------------------------- /readme_imgs/chat_demo_friends.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/readme_imgs/chat_demo_friends.jpg -------------------------------------------------------------------------------- /readme_imgs/chat_demo_jiagou_book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/readme_imgs/chat_demo_jiagou_book.png -------------------------------------------------------------------------------- /readme_imgs/chat_demo_login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/readme_imgs/chat_demo_login.jpg -------------------------------------------------------------------------------- /readme_imgs/chat_demo_wechat_qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/readme_imgs/chat_demo_wechat_qrcode.png -------------------------------------------------------------------------------- /src/css/index.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | margin: 0; 4 | height: 100%; 5 | padding: 0px; 6 | font-family: Source Han Sans CN; 7 | } 8 | 9 | .toast { 10 | position: fixed; 11 | left: 50%; 12 | top: 50%; 13 | width: 200px; 14 | height: 100px; 15 | background: #f0f0f0; 16 | margin: -50px -100px; 17 | color: red; 18 | display: none; 19 | font-size: 16px; 20 | text-align: center; 21 | padding: 10px; 22 | box-sizing: border-box; 23 | } 24 | 25 | /*手机版*/ 26 | @media screen and (max-width: 480px) { 27 | #app { 28 | height: 100%; 29 | color: #2c3e50; 30 | -moz-osx-font-smoothing: grayscale; 31 | -webkit-font-smoothing: antialiased; 32 | } 33 | 34 | .login { 35 | 36 | } 37 | .login-box { 38 | margin: auto; 39 | padding:0px 20px; 40 | overflow: hidden; 41 | } 42 | 43 | .login-box-title { 44 | color: #D02129; 45 | font-size: 42px; 46 | font-weight: bold; 47 | line-height: 25px; 48 | font-style: normal; 49 | text-align: center; 50 | } 51 | .login-box-input{ 52 | margin-top:70px; 53 | overflow: hidden; 54 | } 55 | 56 | .login-input { 57 | width:100%; 58 | padding:0px; 59 | border: none; 60 | outline: none; 61 | color: #999999; 62 | font-size: 16px; 63 | font-weight: 350; 64 | line-height: 24px; 65 | font-style: normal; 66 | } 67 | .login-input-box { 68 | margin-top: 20px; 69 | padding-left:10px; 70 | background: #FFFFFF; 71 | line-height: 50px; 72 | box-sizing: border-box; 73 | border: 1px solid #E0E0E0; 74 | } 75 | .login-error-box{ 76 | width:360px; 77 | margin-top:20px; 78 | line-height: 60px; 79 | padding-left:10px; 80 | background: rgba(208, 33, 41, 0.1); 81 | } 82 | .error-icon{ 83 | width:16px; 84 | vertical-align: middle; 85 | } 86 | .error-info{ 87 | color: #D02129; 88 | font-size: 16px; 89 | font-weight: 350; 90 | line-height: 24px; 91 | font-style: normal; 92 | margin-left:10px; 93 | vertical-align: middle; 94 | } 95 | .input-description { 96 | line-height: 32px; 97 | margin-right: 10px; 98 | } 99 | 100 | .login-btn-box { 101 | height:50px; 102 | margin-top: 32px; 103 | background: #D02129; 104 | } 105 | 106 | .login-btn-box-info{ 107 | margin:auto; 108 | cursor: pointer; 109 | text-align: center; 110 | } 111 | 112 | .login-btn { 113 | border: none; 114 | outline: none; 115 | color: #FFFFFF; 116 | font-size: 18px; 117 | cursor: pointer; 118 | font-weight: 350; 119 | line-height: 50px; 120 | background: #D02129; 121 | } 122 | 123 | .obtain-username { 124 | display: flex; 125 | color: red; 126 | margin-top: 50px; 127 | justify-content: center; 128 | } 129 | 130 | .error { 131 | display: none; 132 | color: orangered; 133 | } 134 | 135 | .obtain-random-username { 136 | cursor: pointer; 137 | color: #FF4500; 138 | } 139 | 140 | .test-username { 141 | text-align: left; 142 | } 143 | 144 | .test-username-title { 145 | margin-right: 10px; 146 | } 147 | 148 | /*好友列表*/ 149 | .friends{ 150 | 151 | } 152 | .current-user-box{ 153 | height:90px; 154 | padding-top:80px; 155 | background: #EFEFEF; 156 | } 157 | .current-user-icon{ 158 | margin:auto; 159 | text-align: center; 160 | } 161 | .user-avatar{ 162 | width: 48px;height:48px; 163 | } 164 | .current-user-name{ 165 | color: #262628; 166 | font-size: 18px; 167 | font-weight: 500; 168 | line-height: 25px; 169 | font-style: normal; 170 | text-align: center; 171 | } 172 | .friend-item-box{ 173 | padding-left:16px; 174 | } 175 | 176 | .friend-item{ 177 | display: flex; 178 | height:60px; 179 | padding-top:12px; 180 | justify-content: space-between; 181 | border-bottom: 1px solid #EFEFEF; 182 | } 183 | .friend-item:hover{ 184 | background: lightyellow; 185 | } 186 | .friend-avatar{ 187 | width:48px; 188 | height:48px; 189 | vertical-align: middle; 190 | } 191 | .friend-name{ 192 | margin-left: 16px; 193 | } 194 | .friend-item-message-badge{ 195 | display: flex; 196 | justify-content: center; 197 | width:30px; 198 | height:20px; 199 | text-align: center; 200 | border-radius: 15px; 201 | background: #D02129; 202 | margin:auto 20px auto auto; 203 | } 204 | .message-count{ 205 | color: #FFFFFF; 206 | } 207 | /* 图片变成黑白色 */ 208 | .friend-avatar-desaturate { 209 | -webkit-filter: grayscale(100%); 210 | -moz-filter: grayscale(100%); 211 | -ms-filter: grayscale(100%); 212 | -o-filter: grayscale(100%); 213 | filter: grayscale(100%); 214 | filter: gray; 215 | } 216 | 217 | /* 聊天面板 */ 218 | .chat-box { 219 | height:100%; 220 | overflow: auto; 221 | position: relative; 222 | background: #ffffff; 223 | } 224 | .chat-box-current-friend{ 225 | position: fixed; 226 | left: 0; 227 | top: 0; 228 | right: 0; 229 | z-index: 100; 230 | line-height:64px; 231 | text-align: center; 232 | background: linear-gradient(0deg, #D02129, #D02129), linear-gradient(195.96deg, #F33A3A 9.83%, #D02129 105.7%); 233 | } 234 | .friends-box-title{ 235 | position: fixed; 236 | left: 0; 237 | top: 0; 238 | right: 0; 239 | z-index: 100; 240 | line-height:64px; 241 | text-align: center; 242 | background: linear-gradient(0deg, #D02129, #D02129), linear-gradient(195.96deg, #F33A3A 9.83%, #D02129 105.7%); 243 | } 244 | 245 | .friends-box-title span{ 246 | color: #ffffff; 247 | font-style: normal; 248 | font-weight: 500; 249 | font-size: 18px; 250 | } 251 | 252 | .exit-current-chat-box{ 253 | float: left; 254 | padding-left: 10px; 255 | } 256 | .exit-icon{ 257 | height:18px; 258 | } 259 | .current-friend-name{ 260 | color: #ffffff; 261 | font-size: 18px; 262 | font-weight: 500; 263 | line-height: 25px; 264 | font-style: normal; 265 | } 266 | 267 | .chat-box-content { 268 | height: 100%; 269 | padding: 80px 0; 270 | overflow-y: scroll; 271 | box-sizing: border-box; 272 | } 273 | 274 | #top, #group-top { 275 | font-size: 16px; 276 | height: 30px; 277 | line-height: 30px; 278 | color: blue; 279 | text-decoration: underline; 280 | text-align: center; 281 | } 282 | 283 | .chat-box-friend-message-template { 284 | display: flex; 285 | padding: 10px 70px 10px 20px; 286 | } 287 | 288 | .chat-box-friend-content { 289 | font-size: 18px; 290 | margin-left: 15px; 291 | line-height: 22px; 292 | padding: 10px 10px; 293 | border-radius: 6px; 294 | background: #EFEFEF; 295 | display: inline-block; 296 | list-style-type: none; 297 | } 298 | 299 | .chat-message { 300 | color: #262628; 301 | word-wrap: break-word; 302 | word-break: break-all; 303 | } 304 | .chat-left-box{ 305 | position: relative; 306 | } 307 | .chat-right-box{ 308 | position: relative; 309 | } 310 | .left-triangle{ 311 | width:0px; 312 | height:0px; 313 | position: absolute; 314 | top:10px; 315 | border:solid 8px #ffffff; 316 | border-right-color:#EEEEEE; 317 | } 318 | .right-triangle{ 319 | width:0px; 320 | height:0px; 321 | position: absolute; 322 | top:10px; 323 | right:0px; 324 | border:solid 8px #ffffff; 325 | border-left-color:#D02129; 326 | } 327 | .chat-box-self-message-template { 328 | display: flex; 329 | justify-content: flex-end; 330 | padding: 10px 20px 10px 70px; 331 | } 332 | 333 | .chat-box-self-content { 334 | font-size: 18px; 335 | padding: 10px 10px; 336 | line-height: 22px; 337 | border-radius: 4px; 338 | margin-right: 15px; 339 | list-style-type: none; 340 | display: inline-block; 341 | background-color: #D02129; 342 | } 343 | 344 | .chat-message-self { 345 | color: #FFFFFF; 346 | word-wrap: break-word; 347 | word-break: break-all; 348 | } 349 | 350 | .chat-send-box { 351 | position: fixed; 352 | left: 0px; 353 | right:0px; 354 | bottom: 0px; 355 | width: 100%; 356 | height:80px; 357 | z-index: 1000; 358 | background: #FAFAFA; 359 | box-shadow: 1px 0px 2px #EBEBEB; 360 | } 361 | 362 | .chat-send-box-info { 363 | overflow: hidden; 364 | padding-top: 16px; 365 | padding-left: 20px; 366 | } 367 | .input-box{ 368 | width:75%; 369 | height:50px; 370 | float: left; 371 | padding-left:10px; 372 | border-radius: 6px; 373 | padding-right:10px; 374 | background: #EFEFEF; 375 | } 376 | .send-input-box{ 377 | width:100%; 378 | border:none; 379 | padding:0px; 380 | outline: none; 381 | line-height: 50px; 382 | background: #EFEFEF; 383 | } 384 | .send-box-btn{ 385 | width:15%; 386 | float: left; 387 | height:50px; 388 | margin-left: 10px; 389 | } 390 | 391 | .send-btn { 392 | border: none; 393 | outline: none; 394 | color: #95949A; 395 | font-size: 15px; 396 | background: none; 397 | line-height: 50px; 398 | font-style: normal; 399 | font-weight: normal; 400 | } 401 | 402 | .group-member { 403 | position: absolute; 404 | width: 30px; 405 | height: 30px; 406 | right: 10px; 407 | top: 10px; 408 | } 409 | 410 | #group-member-layer { 411 | position: fixed; 412 | width: 100%; 413 | height: 100%; 414 | background: #2c3e50; 415 | z-index: 1999; 416 | top: 0; 417 | left: 0; 418 | display: none; 419 | } 420 | 421 | .layer-container { 422 | width: 100%; 423 | height: 100%; 424 | padding: 80px 10px; 425 | display: flex; 426 | flex-wrap: wrap; 427 | } 428 | 429 | .layer-container img { 430 | width: 50px; 431 | height: 50px; 432 | border-radius: 50px; 433 | margin-right: 10px; 434 | } 435 | } 436 | /* 437 | web版 438 | */ 439 | @media screen and (min-width: 481px){ 440 | #app { 441 | width: 373px; 442 | height: 600px; 443 | color: #2c3e50; 444 | box-shadow: 0px 0px 5px gray; 445 | margin: 0px auto; 446 | -webkit-font-smoothing: antialiased; 447 | -moz-osx-font-smoothing: grayscale; 448 | overflow: hidden; 449 | } 450 | 451 | .login { 452 | padding-top: 60px; 453 | } 454 | .login-box { 455 | margin: auto; 456 | padding:0px 20px; 457 | overflow: hidden; 458 | } 459 | 460 | .login-box-title { 461 | color: #D02129; 462 | font-size: 42px; 463 | font-weight: bold; 464 | line-height: 25px; 465 | font-style: normal; 466 | text-align: center; 467 | } 468 | .login-box-input { 469 | margin-top: 40px; 470 | overflow: hidden; 471 | } 472 | 473 | .login-input { 474 | width:100%; 475 | padding:0px; 476 | border: none; 477 | outline: none; 478 | color: #999999; 479 | font-size: 16px; 480 | font-weight: 350; 481 | line-height: 24px; 482 | font-style: normal; 483 | } 484 | .login-input-box { 485 | margin-top: 20px; 486 | padding-left:10px; 487 | background: #FFFFFF; 488 | line-height: 50px; 489 | box-sizing: border-box; 490 | border: 1px solid #E0E0E0; 491 | } 492 | .login-error-box{ 493 | width:360px; 494 | margin-top:20px; 495 | line-height: 60px; 496 | padding-left:10px; 497 | background: rgba(208, 33, 41, 0.1); 498 | } 499 | .error-icon{ 500 | width:16px; 501 | vertical-align: middle; 502 | } 503 | .error-info{ 504 | color: #D02129; 505 | font-size: 16px; 506 | font-weight: 350; 507 | line-height: 24px; 508 | font-style: normal; 509 | margin-left: 10px; 510 | vertical-align: middle; 511 | } 512 | 513 | .input-description { 514 | line-height: 32px; 515 | margin-right: 10px; 516 | } 517 | 518 | .group-member { 519 | position: absolute; 520 | width: 30px; 521 | height: 30px; 522 | right: 10px; 523 | top: 10px; 524 | } 525 | 526 | #group-member-layer { 527 | position: fixed; 528 | width: 375px; 529 | height: 600px; 530 | background: #2c3e50; 531 | z-index: 1999; 532 | top: 0; 533 | display: none; 534 | } 535 | 536 | .layer-container { 537 | width: 100%; 538 | height: 100%; 539 | padding: 80px 10px; 540 | display: flex; 541 | flex-wrap: wrap; 542 | } 543 | 544 | .layer-container img { 545 | width: 50px; 546 | height: 50px; 547 | border-radius: 50px; 548 | margin-right: 10px; 549 | } 550 | 551 | .login-btn-box { 552 | height: 50px; 553 | margin-top: 32px; 554 | background: #D02129; 555 | } 556 | 557 | .login-btn-box-info { 558 | margin: auto; 559 | cursor: pointer; 560 | text-align: center; 561 | } 562 | 563 | .login-btn { 564 | border: none; 565 | outline: none; 566 | color: #FFFFFF; 567 | font-size: 18px; 568 | cursor: pointer; 569 | font-weight: 350; 570 | line-height: 50px; 571 | background: #D02129; 572 | } 573 | 574 | #top, #group-top { 575 | font-size: 16px; 576 | height: 30px; 577 | line-height: 30px; 578 | color: blue; 579 | text-decoration: underline; 580 | text-align: center; 581 | } 582 | 583 | .obtain-username { 584 | display: flex; 585 | color: red; 586 | margin-top: 50px; 587 | justify-content: center; 588 | } 589 | 590 | .error { 591 | display: none; 592 | color: orangered; 593 | } 594 | 595 | .obtain-random-username { 596 | cursor: pointer; 597 | color: #FF4500; 598 | } 599 | 600 | .test-username { 601 | text-align: left; 602 | } 603 | 604 | .test-username-title { 605 | margin-right: 10px; 606 | } 607 | 608 | .current-user-box{ 609 | height:90px; 610 | padding-top:80px; 611 | background: #EFEFEF; 612 | } 613 | .current-user-icon{ 614 | margin:auto; 615 | text-align: center; 616 | } 617 | .user-avatar{ 618 | width: 48px;height:48px; 619 | } 620 | .current-user-name{ 621 | color: #262628; 622 | font-size: 18px; 623 | font-weight: 500; 624 | line-height: 25px; 625 | font-style: normal; 626 | text-align: center; 627 | } 628 | .friend-item-box{ 629 | padding-left:16px; 630 | } 631 | .friend-item{ 632 | display: flex; 633 | height:60px; 634 | padding-top:12px; 635 | justify-content: space-between; 636 | border-bottom: 1px solid #EFEFEF; 637 | } 638 | .friend-item:hover{ 639 | background: lightyellow; 640 | } 641 | .friend-avatar{ 642 | width:48px; 643 | height:48px; 644 | vertical-align: middle; 645 | } 646 | .friend-name{ 647 | margin-left: 16px; 648 | } 649 | .friend-item-message-badge{ 650 | display: flex; 651 | justify-content: center; 652 | width:30px; 653 | height:20px; 654 | /*display: none;*/ 655 | text-align: center; 656 | border-radius: 15px; 657 | background: #D02129; 658 | margin:auto 20px auto auto; 659 | } 660 | .message-count{ 661 | color: #FFFFFF; 662 | } 663 | /* 图片变成黑白色 */ 664 | .friend-avatar-desaturate { 665 | -webkit-filter: grayscale(100%); 666 | -moz-filter: grayscale(100%); 667 | -ms-filter: grayscale(100%); 668 | -o-filter: grayscale(100%); 669 | filter: grayscale(100%); 670 | filter: gray; 671 | } 672 | 673 | /* 聊天面板 */ 674 | .chat-box { 675 | height:520px; 676 | position: relative; 677 | background: #ffffff; 678 | } 679 | .chat-box-current-friend{ 680 | width:375px; 681 | margin:auto; 682 | position: fixed; 683 | left: 0; 684 | top: 0; 685 | right: 0; 686 | z-index: 100; 687 | line-height:64px; 688 | text-align: center; 689 | background: linear-gradient(0deg, #D02129, #D02129), linear-gradient(195.96deg, #F33A3A 9.83%, #D02129 105.7%); 690 | } 691 | .friends-box-title{ 692 | width: 375px; 693 | margin:auto; 694 | position: fixed; 695 | left: 0; 696 | top: 0; 697 | right: 0; 698 | z-index: 100; 699 | line-height:64px; 700 | text-align: center; 701 | box-shadow: 0px ; 702 | background: linear-gradient(0deg, #D02129, #D02129), linear-gradient(195.96deg, #F33A3A 9.83%, #D02129 105.7%); 703 | } 704 | 705 | .friends-box-title span{ 706 | color: #ffffff; 707 | font-style: normal; 708 | font-weight: 500; 709 | font-size: 18px; 710 | } 711 | 712 | .exit-current-chat-box{ 713 | float: left; 714 | padding-left: 10px; 715 | } 716 | .exit-icon{ 717 | height:18px; 718 | } 719 | .current-friend-name{ 720 | color: #ffffff; 721 | font-size: 18px; 722 | font-weight: 500; 723 | line-height: 25px; 724 | font-style: normal; 725 | } 726 | .chat-box-content { 727 | height: 440px; 728 | padding-top: 80px; 729 | overflow: auto; 730 | } 731 | 732 | .chat-box-friend-message-template { 733 | display: flex; 734 | padding: 10px 70px 10px 20px; 735 | } 736 | 737 | .chat-box-friend-content { 738 | font-size: 18px; 739 | margin-left:15px; 740 | line-height: 22px; 741 | padding: 10px 10px; 742 | border-radius: 6px; 743 | background: #EFEFEF; 744 | display: inline-block; 745 | list-style-type: none; 746 | } 747 | 748 | .chat-message { 749 | color: #262628; 750 | word-wrap: break-word; 751 | word-break: break-all; 752 | } 753 | .chat-left-box{ 754 | position: relative; 755 | } 756 | .chat-right-box{ 757 | position: relative; 758 | } 759 | .left-triangle{ 760 | width:0px; 761 | height:0px; 762 | position: absolute; 763 | top:10px; 764 | border:solid 8px #ffffff; 765 | border-right-color:#EEEEEE; 766 | } 767 | .right-triangle{ 768 | width:0px; 769 | height:0px; 770 | position: absolute; 771 | top:10px; 772 | right:0px; 773 | border:solid 8px #ffffff; 774 | border-left-color:#D02129; 775 | } 776 | .chat-box-self-message-template { 777 | display: flex; 778 | justify-content: flex-end; 779 | padding: 10px 20px 10px 70px; 780 | } 781 | 782 | .chat-box-self-content { 783 | font-size: 18px; 784 | padding: 10px 10px; 785 | line-height: 22px; 786 | border-radius: 4px; 787 | margin-right: 15px; 788 | list-style-type: none; 789 | display: inline-block; 790 | background-color: #D02129; 791 | } 792 | 793 | .chat-message-self { 794 | color: #FFFFFF; 795 | word-wrap: break-word; 796 | word-break: break-all; 797 | } 798 | 799 | .chat-send-box { 800 | margin:auto; 801 | height:80px; 802 | z-index: 1000; 803 | background: #FAFAFA; 804 | } 805 | 806 | .chat-send-box-info { 807 | overflow: hidden; 808 | padding-top: 16px; 809 | padding-left: 20px; 810 | } 811 | .input-box{ 812 | width:75%; 813 | height:50px; 814 | float: left; 815 | padding-left:10px; 816 | border-radius: 6px; 817 | padding-right:10px; 818 | background: #EFEFEF; 819 | } 820 | .send-input-box{ 821 | width:100%; 822 | border:none; 823 | padding:0px; 824 | outline: none; 825 | line-height: 50px; 826 | background: #EFEFEF; 827 | } 828 | .send-box-btn{ 829 | width:15%; 830 | float: left; 831 | height:50px; 832 | margin-left: 10px; 833 | } 834 | .send-btn{ 835 | border: none; 836 | outline: none; 837 | color: #95949A; 838 | font-size: 15px; 839 | background: none; 840 | line-height: 50px; 841 | font-style: normal; 842 | font-weight: normal; 843 | } 844 | } 845 | 846 | -------------------------------------------------------------------------------- /src/images/Arrow-Left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/src/images/Arrow-Left.png -------------------------------------------------------------------------------- /src/images/Avatar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/src/images/Avatar-1.png -------------------------------------------------------------------------------- /src/images/Avatar-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/src/images/Avatar-2.png -------------------------------------------------------------------------------- /src/images/Avatar-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/src/images/Avatar-3.png -------------------------------------------------------------------------------- /src/images/Avatar-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/src/images/Avatar-4.png -------------------------------------------------------------------------------- /src/images/Vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/src/images/Vector.png -------------------------------------------------------------------------------- /src/images/group-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/src/images/group-icon.png -------------------------------------------------------------------------------- /src/images/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goeasy-git/goeasy-chat-demo/239255cfecaaa9835954329a7a9750171b668613/src/images/group.png -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 登录页面 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 39 |
40 | 41 | 59 | 60 | 84 | 85 | 112 | 113 |
114 |
115 | 116 |
117 | 118 |
119 | 120 |
121 |
122 |
123 |
124 | 125 |
126 | 127 |
128 |
129 | 130 | 131 |
132 |
133 | 134 |
135 |
136 | 137 |
138 |
139 | 140 | 141 |
142 |
143 | 144 |
145 |
146 | 147 |
148 | 149 |
150 |
  • 151 | 152 |
  • 153 |
    154 |
    155 |
    156 | 157 |
    158 |
    159 |
    160 |
  • 161 | 162 |
  • 163 |
    164 | 165 |
    166 |
    167 |
    168 | 169 |
    170 |
    171 | 172 | 173 | -------------------------------------------------------------------------------- /src/js/controller.js: -------------------------------------------------------------------------------- 1 | var chatService = new IMService(); 2 | 3 | //枚举,代表三个页面 4 | var Pages = { 5 | //登录页面 6 | login: 0, 7 | //联系人 8 | contacts: 1, 9 | //私聊界面 10 | privateChat: 2, 11 | //群聊界面 12 | groupChat: 3 13 | }; 14 | 15 | //标记所处的页面和当前聊天的好友 16 | var currentPage = { 17 | page: Pages.login, 18 | currentUser: null, 19 | currentChatFriend: null, 20 | currentChatGroup: null 21 | }; 22 | 23 | //为了避免controller层侵入到service层,用controller层的代码覆盖service层的预置方法 24 | chatService.onNewPrivateMessageReceive = onNewPrivateMessageReceive; 25 | chatService.onPrivateHistoryLoad = onPrivateHistoryLoad; 26 | chatService.onNewGroupMessageReceive = onNewGroupMessageReceive; 27 | chatService.onGroupHistoryLoad = onGroupHistoryLoad; 28 | chatService.onFriendListChange = onFriendListChange; 29 | chatService.onGroupListChange = onGroupListChange; 30 | 31 | //登录 32 | function login() { 33 | var username = $("#username").val(); 34 | var password = $("#password").val(); 35 | var resultStatus = chatService.login(username, password); 36 | if (resultStatus) { 37 | //初始化GoEasy和本地好友列表 38 | chatService.connectIM(); 39 | $("#login-box").hide(); 40 | //切换到联系人 41 | switchToContacts(); 42 | } else { 43 | //显示错误信息 44 | $("#login-error-box").show(); 45 | } 46 | } 47 | 48 | //切换到联系人界面 49 | function switchToContacts() { 50 | //设置当前页面为好友列表页面 51 | currentPage.page = Pages.contacts; 52 | //修改页面的title 53 | $("title").text("联系人"); 54 | $('.login').hide() 55 | 56 | //初始化当前用户 57 | currentPage.currentUser = chatService.currentUser; 58 | $("#current-user-avatar").attr("src", currentPage.currentUser.avatar); 59 | $("#current-user-name").text(currentPage.currentUser.name); 60 | 61 | //隐藏聊天窗口 62 | $("#chat-box").hide(); 63 | $("#group-chat-box").hide(); 64 | 65 | //获取好友数据 66 | var friendsMap = chatService.friends; 67 | //绘制好友列表 68 | renderFriendList(friendsMap); 69 | //显示好友列表 70 | $("#friends-box").show(); 71 | 72 | //获取群数据 73 | var groupsMap = chatService.groups; 74 | //绘制群列表 75 | renderGroupList(groupsMap); 76 | //显示群列表 77 | $("#group-box").show() 78 | } 79 | 80 | //绘制好友列表 81 | function renderFriendList(friends) { 82 | var friendListDiv = $("#friend-list"); 83 | friendListDiv.empty(); 84 | for (var key in friends) { 85 | var item = $("#friend-item-template").clone();//好友信息的模版 86 | item.remove('#friend-item-template'); 87 | var friend = friends[key]; 88 | 89 | //更新好友uuid 90 | item.attr("id", friend.uuid); 91 | //设置好友头像 92 | var friendAvatarImg = item.find("img"); 93 | friendAvatarImg.attr("src", friend.avatar); 94 | friendAvatarImg.attr("id", "avatar_" + key); 95 | //设置好友名称 96 | item.find(".friend-name").html(friend.name); 97 | //更新未读消息数 98 | var unReadMessage = friend.unReadMessage; 99 | item.find(".message-count").text(unReadMessage); 100 | 101 | //显示或隐藏未读消息数 102 | var messageBadge = item.find(".friend-item-message-badge"); 103 | if (unReadMessage == 0) { 104 | //没有未读消息,隐藏消息数量 105 | messageBadge.hide(); 106 | } else { 107 | //有未读消息,展现未读消息量 108 | messageBadge.show(); 109 | } 110 | 111 | //添加点击事件,点击好友条目后,进入聊天窗口 112 | (function (key) { 113 | var friend = friends[key] 114 | item.click(function () { 115 | switchToPrivateChat(friend); 116 | }); 117 | })(key) 118 | 119 | //将一条好友添加到好友的列表中 120 | friendListDiv.append(item); 121 | 122 | if (friend.online) { 123 | $("#avatar_" + key).removeClass("friend-avatar-desaturate"); 124 | } 125 | } 126 | } 127 | 128 | //绘制群列表 129 | function renderGroupList(groups) { 130 | var groupListDiv = $("#group-list"); 131 | groupListDiv.empty(); 132 | for (var key in groups) { 133 | var item = $("#group-item-template").clone();//群信息的模版 134 | item.remove('#group-item-template'); 135 | var group = groups[key]; 136 | //设置群id 137 | item.attr("id", key); 138 | //设置群名称 139 | item.find(".group-name").html(group.name); 140 | //更新未读消息数 141 | var unReadMessage = group.unReadMessage; 142 | item.find(".message-count").text(unReadMessage); 143 | 144 | //显示或隐藏未读消息数 145 | var messageBadge = item.find(".friend-item-message-badge"); 146 | if (unReadMessage == 0) { 147 | //没有未读消息,隐藏消息数量 148 | messageBadge.hide(); 149 | } else { 150 | //有未读消息,展现未读消息量 151 | messageBadge.show(); 152 | } 153 | 154 | //添加点击事件,点击好友条目后,进入聊天窗口 155 | (function (key) { 156 | var group = groups[key] 157 | item.click(function () { 158 | switchToGroupChat(group); 159 | }); 160 | })(key) 161 | 162 | //将一条好友添加到好友的列表中 163 | groupListDiv.append(item); 164 | } 165 | } 166 | 167 | //切换到私聊界面 168 | function switchToPrivateChat(friend) { 169 | //设置当前页面为聊天页面和聊天的好友 170 | currentPage.page = Pages.privateChat; 171 | currentPage.currentChatFriend = friend; 172 | $("title").text("私聊界面"); 173 | 174 | //先清空之前的聊天内容 175 | var chatBoxContent = $("#chat-box-content"); 176 | chatBoxContent.empty(); 177 | 178 | //隐藏好友列表 179 | $("#friends-box").hide(); 180 | //更新当前聊天的好友名称 181 | $(".current-friend-name").text(friend.name); 182 | 183 | //显示聊天窗口 184 | $("#chat-box").show(); 185 | 186 | $("#sendMessageButton").off('click').on("click", function () { 187 | sendPrivateChatMessage(friend.uuid); 188 | }); 189 | 190 | //绘制聊天消息 191 | var messages = chatService.getPrivateMessages(friend.uuid); 192 | if (messages.length != 0) { 193 | renderPrivateChatMessage(messages,true) 194 | } 195 | } 196 | 197 | //切换到群聊界面 198 | function switchToGroupChat(group) { 199 | //设置当前页面为聊天页面和聊天的好友 200 | currentPage.page = Pages.groupChat; 201 | currentPage.currentChatGroup = group; 202 | $("title").text("群聊界面"); 203 | 204 | //先清空之前的聊天内容 205 | var chatBoxContent = $("#group-chat-box-content"); 206 | chatBoxContent.empty(); 207 | 208 | //隐藏好友列表 209 | $("#friends-box").hide(); 210 | //更新当前聊天的好友名称 211 | $(".current-friend-name").text(group.name); 212 | 213 | //显示聊天窗口 214 | $("#group-chat-box").show(); 215 | 216 | $("#groupSendMessageButton").off('click').on("click", function () { 217 | sendGroupChatMessage(group.uuid); 218 | }); 219 | 220 | //绘制界面聊天消息 221 | var messages = chatService.getGroupMessages(group.uuid) 222 | renderGroupChatMessage(messages,true) 223 | } 224 | 225 | //私聊回到联系人 226 | function privateChatBackToContacts() { 227 | chatService.resetFriendUnReadMessage(currentPage.currentChatFriend); 228 | switchToContacts() 229 | } 230 | 231 | // 发送私聊消息 232 | function sendPrivateChatMessage() { 233 | //获取content并赋值 234 | var messageInputBox = $("#send-input-box"); 235 | 236 | var content = messageInputBox.val(); 237 | if (content != '' && content.trim().length > 0) { 238 | // 发送消息 239 | chatService.sendPrivateMessage(currentPage.currentChatFriend.uuid, content); 240 | //发送消息后输入框清空 241 | messageInputBox.val(""); 242 | } 243 | } 244 | 245 | //加载私聊历史消息 246 | function loadPrivateHistory() { 247 | var messages = chatService.getPrivateMessages(currentPage.currentChatFriend.uuid); 248 | let earliestMessageTimeStamp = Date.now(); 249 | let earliestMessage = messages[0]; 250 | if (earliestMessage) { 251 | earliestMessageTimeStamp = earliestMessage.timestamp; 252 | } 253 | this.chatService.loadPrivateHistoryMessage(currentPage.currentChatFriend.uuid, earliestMessageTimeStamp) 254 | } 255 | 256 | //监听私聊消息加载 257 | function onPrivateHistoryLoad(friendId, messages) { 258 | if (messages.length == 0) { 259 | $('#top').html('已经没有更多的历史消息'); 260 | $('#top').css({color: 'gray', textDecoration: 'none'}); 261 | return 262 | } 263 | var chatMessages = chatService.getPrivateMessages(friendId) 264 | renderPrivateChatMessage(chatMessages) 265 | } 266 | 267 | //绘制界面私聊消息 268 | function renderPrivateChatMessage(privateMessages, scrollToBottom) { 269 | var chatBoxContent = $("#chat-box-content"); 270 | chatBoxContent.empty(); 271 | privateMessages.forEach(function (message) { 272 | var messageTemplate; 273 | //判断这条消息是谁发的 274 | if (message.senderId === chatService.currentUser.uuid) { 275 | //自己发送的消息展示在右边 276 | messageTemplate = $("#chat-box-self-message-template").clone(); 277 | messageTemplate.remove('#chat-box-self-message-template'); 278 | //更新头像 279 | messageTemplate.find("img").attr("src", chatService.currentUser.avatar); 280 | } else { 281 | //如果该为好友发送的消息展示在左边 282 | messageTemplate = $("#chat-box-friend-message-template").clone(); 283 | messageTemplate.find("img").attr("src", currentPage.currentChatFriend.avatar); 284 | } 285 | messageTemplate.find(".chat-message").text(message.payload); 286 | //显示一条消息到页面上 287 | chatBoxContent.append(messageTemplate); 288 | }); 289 | 290 | //将滚动条拉到最下 291 | scrollToBottom && $('#private-box').scrollTop($('#private-box')[0].scrollHeight); 292 | } 293 | 294 | //群聊回到联系人 295 | function groupChatBackToContacts() { 296 | chatService.resetGroupUnReadMessage(currentPage.currentChatGroup); 297 | switchToContacts() 298 | } 299 | 300 | //发送群聊消息 301 | function sendGroupChatMessage() { 302 | //获取content并赋值 303 | var messageInputBox = $("#group-send-input-box"); 304 | 305 | var content = messageInputBox.val(); 306 | if (content != '' && content.trim().length > 0) { 307 | // 发送消息 308 | chatService.sendGroupMessage(currentPage.currentChatGroup.uuid, content); 309 | //发送消息后输入框清空 310 | messageInputBox.val(""); 311 | } 312 | } 313 | 314 | //加载群聊历史消息 315 | function loadGroupHistory() { 316 | var messages = chatService.getGroupMessages(currentPage.currentChatGroup.uuid); 317 | let earliestMessageTimeStamp = Date.now(); 318 | let earliestMessage = messages[0]; 319 | if (earliestMessage) { 320 | earliestMessageTimeStamp = earliestMessage.timestamp; 321 | } 322 | this.chatService.loadGroupHistoryMessage(currentPage.currentChatGroup.uuid, earliestMessageTimeStamp) 323 | } 324 | 325 | //监听群聊历史消息加载 326 | function onGroupHistoryLoad(groupId, messages) { 327 | if (messages.length == 0) { 328 | $('#group-top').html('已经没有更多的历史消息'); 329 | $('#group-top').css({color: 'gray', textDecoration: 'none'}); 330 | return 331 | } 332 | ; 333 | var chatMessage = chatService.getGroupMessages(groupId) 334 | renderGroupChatMessage(chatMessage) 335 | } 336 | 337 | //绘制群聊界面消息 338 | function renderGroupChatMessage(groupMessages, scrollToBottom) { 339 | var currentUser = chatService.currentUser; 340 | var chatBoxContent = $("#group-chat-box-content"); 341 | chatBoxContent.empty(); 342 | groupMessages.forEach(function (message) { 343 | var messageTemplate; 344 | //判断这条消息是谁发的 345 | if (message.senderId === currentUser.uuid) { 346 | //自己发送的消息展示在右边 347 | messageTemplate = $("#chat-box-self-message-template").clone(); 348 | messageTemplate.remove('#chat-box-self-message-template'); 349 | //更新头像 350 | messageTemplate.find("img").attr("src", currentUser.avatar); 351 | } else { 352 | //如果该为好友发送的消息展示在左边 353 | messageTemplate = $("#chat-box-friend-message-template").clone(); 354 | messageTemplate.remove('#chat-box-self-message-template'); 355 | 356 | var friend = chatService.friends[message.senderId] 357 | messageTemplate.find("img").attr("src", friend.avatar); 358 | } 359 | messageTemplate.find(".chat-message").text(message.payload); 360 | //显示一条消息到页面上 361 | chatBoxContent.append(messageTemplate); 362 | }); 363 | 364 | //将滚动条拉到最下 365 | scrollToBottom && $('#group-box').scrollTop($('#group-box')[0].scrollHeight); 366 | } 367 | 368 | //监听接收私聊消息 369 | function onNewPrivateMessageReceive(friendId, chatMessage) { 370 | let friend = chatService.friends[friendId]; 371 | //如果当前窗口是在好友列表页面,只显示未读消息数 372 | if (currentPage.page == Pages.contacts) { 373 | renderFriendList(chatService.friends) 374 | } else { 375 | if (friendId == currentPage.currentChatFriend.uuid) { 376 | renderPrivateChatMessage(chatService.getPrivateMessages(friendId), true) 377 | } 378 | } 379 | } 380 | 381 | //监听接收群聊消息 382 | function onNewGroupMessageReceive(groupId, chatMessage) { 383 | let group = chatService.groups[groupId]; 384 | 385 | //如果当前窗口是在好友列表页面,只显示未读消息数 386 | if (currentPage.page == Pages.contacts) { 387 | var groupItem = $("#" + groupId); 388 | groupItem.find(".message-count").text(group.unReadMessage); 389 | groupItem.find(".friend-item-message-badge").show(); 390 | } else { 391 | if (groupId == currentPage.currentChatGroup.uuid) { 392 | renderGroupChatMessage(chatService.getGroupMessages(groupId), true) 393 | } 394 | } 395 | } 396 | 397 | //更新好友列表 398 | function onFriendListChange(friends) { 399 | renderFriendList(friends) 400 | } 401 | 402 | //更新群列表 403 | function onGroupListChange(groups) { 404 | renderGroupList(groups) 405 | } 406 | 407 | //显示群成员 408 | function showGroupMember() { 409 | $('#group-member-layer').show(); 410 | var members = chatService.getGroupMembers(currentPage.currentChatGroup.uuid); 411 | $('.group-member-amount').html("成员(" + Object.keys(members).length + ')') 412 | let str = ""; 413 | for (var key in members) { 414 | str += '' 415 | } 416 | $('.layer-container').html(str) 417 | } 418 | 419 | //隐藏群成员 420 | function hideGroupMember() { 421 | $('#group-member-layer').hide(); 422 | } 423 | 424 | -------------------------------------------------------------------------------- /src/js/imservice.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: jack.lu 3 | * @Date: 2020-4-21 10:10:20 4 | * @Last Modified by: jack.lu 5 | * @Last Modified time: 2020-4-21 15:01:41 6 | */ 7 | 8 | function Friend(uuid, name, avatar) { 9 | this.uuid = uuid; 10 | this.name = name; 11 | this.avatar = avatar; 12 | this.online = false; 13 | this.unReadMessage = 0; 14 | } 15 | 16 | function Group(uuid, name, avatar) { 17 | this.uuid = uuid; 18 | this.name = name; 19 | this.avatar = avatar; 20 | this.unReadMessage = 0; 21 | } 22 | 23 | function CurrentUser(uuid, name, avatar) { 24 | this.uuid = uuid; 25 | this.name = name; 26 | this.avatar = avatar; 27 | } 28 | 29 | function IMService() { 30 | this.im = GoEasyIM.getInstance({ 31 | appkey: '您的appkey', 32 | host: 'hangzhou.goeasy.io', 33 | }); 34 | //当前“我” 35 | this.currentUser = null; 36 | //我的好友 37 | this.friends = {}; 38 | //我的群 39 | this.groups = {}; 40 | //私聊消息记录,map格式,每个好友对应一个数组 41 | this.privateMessages = {}; 42 | //群聊消息记录,map格式,每个群对应一个数组 43 | this.groupMessages = {}; 44 | /* 45 | * 监听器们 46 | * 47 | * 可以在页面里,根据需求,重写以下监听器, 48 | * 便于当各种事件触发时,页面能够执行对应的响应 49 | * 50 | */ 51 | //收到一条私聊消息 52 | this.onNewPrivateMessageReceive = function (friendId, message) { 53 | }; 54 | //完成一次私聊历史加载 55 | this.onPrivateHistoryLoad = function (friendId, messages) { 56 | }; 57 | //收到一条群聊消息 58 | this.onNewGroupMessageReceive = function (groupId, message) { 59 | }; 60 | //完成一次群聊历史加载 61 | this.onGroupHistoryLoad = function (groupId, messages) { 62 | }; 63 | //好友列表发生改变 64 | this.onFriendListChange = function (friends) { 65 | }; 66 | //群列表发生改变 67 | this.onGroupListChange = function (groups) { 68 | }; 69 | } 70 | 71 | //登录 72 | IMService.prototype.login = function (username, password) { 73 | var user = restApi.findUser(username, password); 74 | if (user) { 75 | //初始化当前用户 76 | this.currentUser = new CurrentUser(user.uuid, user.name, user.avatar); 77 | //初始化联系人信息,包括群 78 | this.initialContacts(); 79 | return true; 80 | } else { 81 | return false; 82 | } 83 | }; 84 | 85 | //初始化联系人信息 86 | IMService.prototype.initialContacts = function () { 87 | //查询并初始化好友信息 88 | let friendList = restApi.findFriends(this.currentUser); 89 | 90 | //将用户列表初始化为一个map,便于后续根据friendId得到friend 91 | friendList.map(friend => { 92 | this.friends[friend.uuid] = new Friend(friend.uuid, friend.name, friend.avatar); 93 | }); 94 | 95 | //查询并初始化与自己相关的群信息 96 | let groupList = restApi.findGroups(this.currentUser); 97 | 98 | //将群列表初始化为一个map,方便后续根据groupId索引 99 | groupList.map(group => { 100 | this.groups[group.uuid] = new Group(group.uuid, group.name, group.avatar); 101 | }); 102 | }; 103 | 104 | //获取群成员 105 | IMService.prototype.getGroupMembers = function (groupId) { 106 | let members = restApi.findGroupMembers(groupId); 107 | let membersMap = {}; 108 | members.map(item => { 109 | membersMap[item.uuid] = item 110 | }); 111 | return membersMap; 112 | }; 113 | 114 | IMService.prototype.getGroupMessages = function (groupId) { 115 | if (!this.groupMessages[groupId]) { 116 | this.groupMessages[groupId] = []; 117 | } 118 | return this.groupMessages[groupId] 119 | }; 120 | 121 | IMService.prototype.getPrivateMessages = function (friendId) { 122 | if (!this.privateMessages[friendId]) { 123 | this.privateMessages[friendId] = []; 124 | } 125 | return this.privateMessages[friendId] 126 | }; 127 | 128 | //重置群聊未读消息 129 | IMService.prototype.resetGroupUnReadMessage = function (group) { 130 | this.groups[group.uuid].unReadMessage = 0; 131 | this.onGroupListChange(this.groups); 132 | }; 133 | 134 | //将好友的未读消息数字清零 135 | IMService.prototype.resetFriendUnReadMessage = function (friend) { 136 | this.friends[friend.uuid].unReadMessage = 0; 137 | this.onFriendListChange(this.friends); 138 | }; 139 | 140 | //连接GoEasy 141 | IMService.prototype.connectIM = function () { 142 | //初始化IM相关的监听器 143 | this.initialIMListeners(); 144 | this.im.connect({ 145 | id: this.currentUser.uuid, 146 | data: { 147 | avatar: this.currentUser.avatr, 148 | name: this.currentUser.name 149 | } 150 | }).then(() => { 151 | console.log('连接成功') 152 | //订阅与自己相关的群信息 153 | this.subscribeGroupMessage(); 154 | //初始化好友们的在线状态 155 | this.initialFriendOnlineStatus(); 156 | //订阅我的好友们的上下线信息 157 | this.subscribeFriendsPresence(); 158 | }).catch(error => { 159 | console.log('连接失败,请确保网络正常,appkey和host正确,code:' + error.code + " content:" + error.content); 160 | }); 161 | }; 162 | 163 | //IM监听 164 | IMService.prototype.initialIMListeners = function () { 165 | this.im.on(GoEasyIM.EVENT.CONNECTED, () => { 166 | console.log('连接成功.') 167 | }); 168 | 169 | this.im.on(GoEasyIM.EVENT.DISCONNECTED, () => { 170 | console.log('连接断开.') 171 | }); 172 | 173 | //监听好友上下线 174 | this.im.on(GoEasyIM.EVENT.USER_PRESENCE, (user) => { 175 | //更新好友在线状态 176 | let onlineStatus = user.action == 'online' ? true : false; 177 | let friend = this.friends[user.userId]; 178 | friend.online = onlineStatus; 179 | 180 | //如果页面传入了相应的listener,执行listener 181 | this.onFriendListChange(this.friends); 182 | }); 183 | 184 | //监听私聊消息 185 | this.im.on(GoEasyIM.EVENT.PRIVATE_MESSAGE_RECEIVED, (message) => { 186 | //如果不是自己发的,朋友未读消息数 +1 187 | if (message.senderId != this.currentUser.uuid) { 188 | let friend = this.friends[message.senderId]; 189 | friend.unReadMessage++; 190 | this.onFriendListChange(this.friends); 191 | } 192 | 193 | //更新私聊消息记录 194 | let friendId; 195 | if (this.currentUser.uuid == message.senderId) { 196 | friendId = message.receiverId; 197 | } else { 198 | friendId = message.senderId; 199 | } 200 | 201 | let friendMessages = this.getPrivateMessages(friendId); 202 | friendMessages.push(message); 203 | 204 | //如果页面传入了相应的listener,执行listener 205 | this.onNewPrivateMessageReceive(friendId, message); 206 | }); 207 | 208 | //监听群聊消息 209 | this.im.on(GoEasyIM.EVENT.GROUP_MESSAGE_RECEIVED, (message) => { 210 | //群未读消息+1 211 | let groupId = message.groupId; 212 | let group = this.groups[groupId]; 213 | group.unReadMessage++; 214 | 215 | //如果页面传入了相应的listener,执行listener 216 | this.onGroupListChange(this.groups); 217 | 218 | //更新群聊消息记录 219 | let groupMessages = this.getGroupMessages(groupId); 220 | groupMessages.push(message); 221 | 222 | //如果页面传入了相应的listener,执行listener 223 | this.onNewGroupMessageReceive(groupId, message); 224 | }) 225 | }; 226 | 227 | //订阅群消息 228 | IMService.prototype.subscribeGroupMessage = function () { 229 | let groupIds = Object.keys(this.groups); 230 | this.im.subscribeGroup(groupIds) 231 | .then(() => { 232 | console.log('订阅群消息成功') 233 | }) 234 | .catch(error => { 235 | console.log('订阅群消息失败') 236 | console.log(error) 237 | }) 238 | }; 239 | 240 | //初始化好友在线状态 241 | IMService.prototype.initialFriendOnlineStatus = function () { 242 | let friendIds = Object.keys(this.friends); 243 | this.im.hereNow({ 244 | userIds: friendIds 245 | }).then(result => { 246 | let onlineFriends = result.content; 247 | onlineFriends.map(user => { 248 | let friend = this.friends[user.userId]; 249 | friend.online = true; 250 | }); 251 | this.onFriendListChange(this.friends); 252 | }).catch(error => { 253 | console.log(error) 254 | if (error.code == 401) { 255 | console.log("获取在线用户失败,您尚未开通用户在线状态,请登录GoEasy,查看应用详情里自助启用."); 256 | } 257 | }) 258 | }; 259 | 260 | //监听所有好友上下线 261 | IMService.prototype.subscribeFriendsPresence = function () { 262 | let friendIds = Object.keys(this.friends); 263 | this.im.subscribeUserPresence(friendIds) 264 | .then(() => { 265 | console.log('监听好友上下线成功') 266 | }) 267 | .catch(error => { 268 | console.log(error); 269 | if (error.code == 401) { 270 | console.log("监听好友上下线失败,您尚未开通用户状态提醒,请登录GoEasy,查看应用详情里自助启用."); 271 | } 272 | }); 273 | }; 274 | 275 | //加载单聊历史消息 276 | IMService.prototype.loadPrivateHistoryMessage = function (friendId, timeStamp) { 277 | this.im.history({ 278 | friendId: friendId, 279 | lastTimestamp: timeStamp 280 | }).then(result => { 281 | let history = result.content; 282 | let friendMessages = this.getPrivateMessages(friendId); 283 | for (let i = history.length - 1; i >= 0; i--) { 284 | friendMessages.unshift(history[i]) 285 | } 286 | //如果页面传入了相应的listener,执行listener 287 | this.onPrivateHistoryLoad(friendId, history); 288 | }).catch(error => { 289 | console.log(error); 290 | if (error.code == 401) { 291 | console.log("您尚未开通历史消息,请登录GoEasy,查看应用详情里自助启用."); 292 | } 293 | }) 294 | }; 295 | 296 | //发送私聊消息 297 | IMService.prototype.sendPrivateMessage = function (friendId, message) { 298 | let chatMessage = this.im.createMessage(message); 299 | this.im.sendPrivateMessage(friendId, chatMessage) 300 | .then(() => { 301 | console.log('发送私聊成功') 302 | }).catch(error => { 303 | console.log(error) 304 | }) 305 | }; 306 | 307 | //发送群聊消息 308 | IMService.prototype.sendGroupMessage = function (groupId, message) { 309 | let chatMessage = this.im.createMessage(message); 310 | this.im.sendGroupMessage(groupId, chatMessage) 311 | .then(() => { 312 | console.log('发送群聊成功') 313 | }).catch(error => { 314 | console.log(error) 315 | }) 316 | }; 317 | 318 | //群聊历史消息 319 | IMService.prototype.loadGroupHistoryMessage = function (groupId, timeStamp) { 320 | this.im.history({ 321 | groupId: groupId, 322 | lastTimestamp: timeStamp 323 | }).then(result => { 324 | let history = result.content; 325 | let groupMessages = this.getGroupMessages(groupId); 326 | for (let i = history.length - 1; i >= 0; i--) { 327 | groupMessages.unshift(history[i]); 328 | } 329 | this.onGroupHistoryLoad(groupId, history); 330 | }).catch(error => { 331 | console.log(error) 332 | if (error.code == 401) { 333 | console.log("您尚未开通历史消息,请登录GoEasy,查看应用详情里自助启用."); 334 | } 335 | }) 336 | }; -------------------------------------------------------------------------------- /src/js/restapi.js: -------------------------------------------------------------------------------- 1 | //用户数据示例 2 | let users = [ 3 | { 4 | "uuid": "08c0a6ec-a42b-47b2-bb1e-15e0f5f9a19a", 5 | "name": "Mattie", 6 | "password": "123", 7 | "avatar": '/GoEasyDemo-IM-Chat/src/images/Avatar-1.png' 8 | }, 9 | { 10 | "uuid": "3bb179af-bcc5-4fe0-9dac-c05688484649", 11 | "name": "Wallace", 12 | "password": "123", 13 | "avatar": '/GoEasyDemo-IM-Chat/src/images/Avatar-2.png' 14 | }, 15 | { 16 | "uuid": "fdee46b0-4b01-4590-bdba-6586d7617f95", 17 | "name": "Tracy", 18 | "password": "123", 19 | "avatar": '/GoEasyDemo-IM-Chat/src/images/Avatar-3.png' 20 | }, 21 | { 22 | "uuid": "33c3693b-dbb0-4bc9-99c6-fa77b9eb763f", 23 | "name": "Juanita", 24 | "password": "123", 25 | "avatar": '/GoEasyDemo-IM-Chat/src/images/Avatar-4.png' 26 | } 27 | ]; 28 | 29 | //群数据示例 30 | let groups = [ 31 | { 32 | "uuid": "group-a42b-47b2-bb1e-15e0f5f9a19a", 33 | "name": "群1", 34 | "userList": ['08c0a6ec-a42b-47b2-bb1e-15e0f5f9a19a', '3bb179af-bcc5-4fe0-9dac-c05688484649', 'fdee46b0-4b01-4590-bdba-6586d7617f95', '33c3693b-dbb0-4bc9-99c6-fa77b9eb763f'] 35 | }, 36 | { 37 | "uuid": "group-4b01-4590-bdba-6586d7617f95", 38 | "name": "群2", 39 | "userList": ['08c0a6ec-a42b-47b2-bb1e-15e0f5f9a19a', 'fdee46b0-4b01-4590-bdba-6586d7617f95', '33c3693b-dbb0-4bc9-99c6-fa77b9eb763f'] 40 | }, 41 | { 42 | "uuid": "group-dbb0-4bc9-99c6-fa77b9eb763f", 43 | "name": "群3", 44 | "userList": ['08c0a6ec-a42b-47b2-bb1e-15e0f5f9a19a', '3bb179af-bcc5-4fe0-9dac-c05688484649'] 45 | } 46 | ]; 47 | 48 | function RestApi() { 49 | 50 | } 51 | 52 | RestApi.prototype.findFriends = function (user) { 53 | var friendList = users.filter(v => v.uuid != user.uuid); 54 | return friendList; 55 | } 56 | 57 | RestApi.prototype.findGroups = function (user) { 58 | var groupList = groups.filter(v => v.userList.find(id => id == user.uuid)); 59 | return groupList; 60 | } 61 | 62 | RestApi.prototype.findUser = function (username, password) { 63 | var user = users.find(user => (user.name == username && user.password == password)) 64 | return user; 65 | } 66 | 67 | RestApi.prototype.findGroupMembers = function (groupId) { 68 | let members = []; 69 | let group = groups.find(v => v.uuid == groupId); 70 | users.map(user => { 71 | if (group.userList.find(v => v == user.uuid)) { 72 | members.push(user) 73 | } 74 | }); 75 | return members; 76 | }; 77 | 78 | var restApi = new RestApi() --------------------------------------------------------------------------------