做后台管理系统遇到一个要写聊天系统的功能,我用的是angular,界面样式如下,长连接部分使用的是laravel-echo和socket.io
1、下载laravel-echo和socket.io
npm install laravel-echo --save
npm install socket.io-client --save
2、代码:
html:
<nz-row class="container">
<nz-col [nzSpan]="8" class="left">
<div class="top">form:<span>{{adminInfo.name}}</span></div>
<ul class="people">
<li class="person" [class.active]="selected==idx" *ngFor="let item of chatList,let idx = index" (click)="showChat(item, idx)">
<img [src]="item.avatar || avatarUrl" class="avatar">
<span class="name">{{item.name}}</span>
<span class="time">{{item.time}}</span>
<span class="preview">{{item.preview}}</span>
</li>
</ul>
</nz-col>
<nz-col [nzSpan]="16" class="right">
<div class="top">to: <span>{{chatUser.name}}</span><span class="clear" (click)="clear()">清空</span></div>
<div class="chat" id="chat">
<div class="more-msg" *ngIf="hasMoreMsg" (click)="getMoreMsg()">查看更多消息</div>
<div class="more-msg" *ngIf="!hasMoreMsg">没有更多消息了</div>
<div class="bubble" [class.you]="item.type==0" [class.me]="item.type==1" *ngFor="let item of read">{{item.content}}</div>
<div class="unread-time" *ngIf="unread.length">--{{unread[0].created_at}}--</div>
<div class="bubble" [class.you]="item.type==0" [class.me]="item.type==1" *ngFor="let item of unread">{{item.content}}</div>
</div>
<div class="write">
<input type="text" [(ngModel)]="sendMsg" [disabled]="onlyRead" (keyup)="($event.which === 13)?send():0">
<img src="../../../../assets/outline/send.svg" class="send" (click)="send()">
</div>
</nz-col>
</nz-row>
ts:
import { OnInit, Component, ViewChild } from '@angular/core';
import { _HttpClient } from '@delon/theme';
import { ActivatedRoute } from '@angular/router';
import { NzModalService, NzMessageService } from 'ng-zorro-antd';
import { UploadImgComponent } from '../../../shared/components/upload-img/index';
import io from 'socket.io-client';
import Echo from 'laravel-echo'
@Component({
selector: 'list-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.less']
})
export class ChatComponent implements OnInit {
@ViewChild(UploadImgComponent) uploadImg: UploadImgComponent;
constructor(
private httpClient: _HttpClient,
private modalService: NzModalService,
private message: NzMessageService,
private activeRoute: ActivatedRoute,
) {
}
url = 'https://xinyuan.uiit.org/'
visibleDialog = false;
clientData = {}; // 用户详情
avatarUrl = '../../../../assets/person.png';
chatList = [];
chatUser = {
name: '',
id: ''
}
sendMsg = '';
connection;
io;
selected = 0;
onlyRead = false;
unread = [
{
created_at: ''
}
];
read = [];
adminInfo = {
name: '',
id: ''
}
customer_token = ''
hasMoreMsg = true;
pageNum = 1;
pageSize = 15
ngOnInit() {
this.getChatList();
}
// 初始化socket
init() {
window['io'] = io;
window['Echo'] = new Echo({
broadcaster: 'socket.io',
host: this.url,
path: '/socket.io',
transports: ['websocket', 'polling', 'flashsocket'],
auth: { headers: { 'Authorization': 'Bearer ' + this.customer_token } }
});
// 参与会话的用户
window['Echo'].join('system.room').here((users) => {
console.log(users)
}).listen('NewPersonJoin', (e) => {
console.log(e)
});
// 监听其他用户的消息
window['Echo'].join('system.room').here((users) => {
console.log(users)
}).listen(`server.new.message.${this.adminInfo.id}`, (e) => {
this.getUserList(false);
});
// 监听会话消息接收
window['Echo'].join(`chat.room.${this.adminInfo.id}_chat_${this.chatUser.id}`).joining((user) => {
console.log(user.name);
}).leaving((user) => {
console.log(user.name);
}).listen('SendMessageEvent', (e) => {
console.log(e, e.message)
this.read = [...this.read, {
type: 0,
content: e.message
}]
this.toScrollBottom();
});
}
// 获取聊天列表
getChatList() {
let self = this
self.activeRoute.queryParams.subscribe(params => {
if (params['id']) {
self.adminInfo = {
id: params['id'],
name: params['name']
}
this.onlyRead = true;
} else {
let data = JSON.parse(localStorage.getItem('user')).user;
this.adminInfo = {
name: data.name,
id: data.customer_id,
}
this.customer_token = data.customer_token
this.onlyRead = false;
}
});
this.getUserList(true);
}
// 获取聊天的app用户列表
getUserList(isFirstGet: boolean) {
let self = this;
self.httpClient.get('/api/admin/message/userList?id=' + self.adminInfo.id).subscribe((res: {}) => {
const resData: any = res;
let list = resData.result.message
list.forEach(el => {
let one = {
name: el.mobileUser_info.name,
avatar: el.mobileUser_info.avatar,
phone: el.mobileUser_info.phone,
id: el.mobileUser_info.id,
messageCount: el.messageCount,
time: '',
preview: ''
}
// 获取未读消息列表
self.httpClient.get('/api/admin/message/unread?user_id=' + el.mobileUser_info.id).subscribe((res: {}) => {
const resData: any = res;
let data = resData.result.message.data;
if (data && data.length != 0) {
one.time = this.changeTime(data[data.length - 1].created_at);
one.preview = data[data.length - 1].content;
} else {
// 获取历史消息的第一条
self.httpClient.get('/api/admin/message/history', {
user_id: el.mobileUser_info.id,
page: 1,
limit: 15
}).subscribe((res: {}) => {
const resData: any = res;
let list = resData.result.message.data;
one.time = this.changeTime(list[0].created_at);
one.preview = list[0].content;
})
}
})
this.chatList.push(one)
});
if (isFirstGet) {
this.showChat(this.chatList[0], 0);
}
});
}
// 聊天的时间转化
changeTime(time) {
let date = time.toString(),
year = date.substring(0, 4),
month = date.substring(5, 7),
day = date.substring(8, 10),
min = date.substring(11, 16);
let d1 = year + '/' + month + '/' + day;
let dd = new Date(),
y = dd.getFullYear(),
m = dd.getMonth() + 1,
d = dd.getDate();
if(month != m) {
return d1;
}else {
switch(d-day) {
case 0: return min; break;
case 1: return '昨天'; break;
default: return d1; break;
}
}
}
// 更新某用户的消息阅读状态
alreadyRead() {
this.httpClient.post('/api/admin/message/finishRead', { user_id: this.chatUser.id }).subscribe((res: {}) => {
const resData: any = res;
})
}
// 获取与某个用户的聊天记录
showChat(item?, index?) {
if (index || index == 0) {
this.selected = index;
}
if (item) {
this.chatUser = item;
this.alreadyRead();
}
// 获取未读消息列表
this.httpClient.get('/api/admin/message/unread?user_id=' + this.chatUser.id).subscribe((res: {}) => {
const resData: any = res;
this.unread = resData.result.message;
// 获取第一页历史消息时滚动条到底部
setTimeout(() => {
this.toScrollBottom();
}, 1000)
if (this.unread.length == 0) {
// 获取已读消息列表
this.getMoreMsg();
}
})
if (!this.onlyRead) {
this.init();
}
}
// 聊天区域的滚动条移到最底部
toScrollBottom() {
var div = document.getElementById('chat');
div.scrollTop = div.scrollHeight;
}
// 获取更多历史记录
getMoreMsg() {
this.httpClient.get('/api/admin/message/history', {
user_id: this.chatUser.id,
page: this.pageNum++,
limit: this.pageSize
}).subscribe((res: {}) => {
const resData: any = res;
let list = resData.result.message.data;
if (list.length <= 0) {
this.hasMoreMsg = false;
return;
} else {
if (list.length < this.pageSize) {
this.hasMoreMsg = false;
}
let newList = [];
list.forEach((el, index) => {
newList[list.length - index - 1] = el;
})
this.read = [...newList, ...this.read];
}
console.log(this.read)
})
this.unread = [];
}
// 清空与某用户的聊天记录
clear() {
this.modalService.confirm({
nzTitle: '<b>确定清除聊天记录?</b>',
nzContent: '<i>该操作不可撤回</i>',
nzOnOk: () => {
this.httpClient.delete(`/api/admin/message/${this.adminInfo.id}/${this.chatUser.id}/history`).subscribe((res: {}) => {
const resData: any = res;
this.message.success('操作成功!')
})
}
});
}
// 发送消息
send() {
let data = {
to_user: this.chatUser.id,
content: this.sendMsg
}
this.httpClient.post('/api/admin/message/send', data, {}, {
headers: {
'X-Socket-Id': window['Echo'].socketId()
}
}).subscribe((res: {}) => {
const resData: any = res;
this.read = [...this.read, {
type: 1,
content: this.sendMsg
}]
setTimeout(() => { this.sendMsg = ''; this.toScrollBottom(); }, 500);
})
}
// dialog关闭事件
handleCancel() {
this.visibleDialog = false;
}
}
less:
:host {
display: block;
margin: 0 auto;
::ng-deep {
.container{
margin: 50px auto;
width: 1000px;
height: 600px;
box-shadow: 0 0 6px #ccc;
background: #fff;
}
.left {
border-right:1px solid #e6e6e6;
height: 100%;
}
.top {
width: 100%;
height: 50px;
padding: 15px;
background-color: #eceff1;
border-bottom: 1px solid #e6e6e6;
color: #999;
font-size: 15px;
}
.top span {
font-weight: 600;
color: #1a1a1a;
margin-left: 10px;
}
.left .top {
background-color: #fff;
}
.people {
width: 100%;
list-style: none;
padding: 0;
margin: 0;
}
.person {
padding: 12px 10% 16px;
cursor: pointer;
position: relative;
}
.person:hover, .active.active {
background: #eceff1;
color: #999;
}
.person .avatar {
float: left;
width: 40px;
height: 40px;
margin-right: 12px;
border-radius: 50%;
}
.person .name {
font-size: 14px;
line-height: 22px;
color: #000;
font-family: 'Source Sans Pro', sans-serif;
font-weight: 600;
overflow: hidden !important;
max-width: 60%;
white-space: nowrap;
text-overflow: ellipsis;
}
.person .time {
font-size: 14px;
position: absolute;
top: 16px;
right: 10%;
padding: 0 0 5px 5px;
}
.person .preview {
font-size: 14px;
display: inline-block;
overflow: hidden !important;
width: 80%;
white-space: nowrap;
text-overflow: ellipsis;
}
.right {
height: 100%;
position: relative;
.clear {
color: red;
float: right;
cursor: pointer;
}
}
.chat {
position: relative;
padding: 0 35px 92px;
border-width: 1px 1px 1px 0;
border-style: solid;
border-color: #ffffff;
height: calc(100% - 150px);
overflow: auto;
justify-content: flex-end;
flex-direction: column;
margin-top: 20px;
}
.chat::-webkit-scrollbar{
width: 6px;
height: 10px;
background-color: #fff;
}
.chat::-webkit-scrollbar-track{
border-radius: 10px;
background-color: #fff;
}
.chat::-webkit-scrollbar-thumb{
border-radius: 10px;
box-shadow: inset 0 0 2px rgba(0,0,0,.3);
background-color: #ccc;
}
.chat .unread-time {
position: relative;
width: 100%;
margin-bottom: 27px;
text-align: center;
font-size: 14px;
color: #999;
}
.chat .more-msg {
margin-bottom: 27px;
text-align: center;
font-size: 12px;
color: #00b0ff;
cursor: pointer;
}
.chat .bubble {
font-size: 16px;
position: relative;
display: inline-block;
clear: both;
margin-bottom: 8px;
padding: 13px 14px;
vertical-align: top;
border-radius: 5px;
&:before {
position: absolute;
top: 19px;
display: block;
width: 8px;
height: 6px;
content: '\00a0';
-webkit-transform: rotate(29deg) skew(-35deg);
transform: rotate(29deg) skew(-35deg);
}
}
.chat .bubble.you {
float: left;
color: #fff;
background-color: #00b0ff;
&:before {
left: -3px;
background-color: #00b0ff;
}
}
.chat .bubble.me {
float: right;
color: #000;
background-color: #eceff1;
&:before {
right: -3px;
background-color: #eceff1;
}
}
.right .write {
position: absolute;
bottom: 29px;
left: 30px;
height: 42px;
padding-left: 8px;
border: 1px solid #e6e6e6;
background-color: #eceff1;
width: calc(100% - 58px);
border-radius: 5px;
}
.write input {
font-size: 16px;
float: left;
width: 92%;
height: 40px;
padding: 0 10px;
color: #000;
border: 0;
outline: none;
background-color: #eceff1;
font-family: 'Source Sans Pro', sans-serif;
font-weight: 400;
}
.send {
display: inline-block;
width: 20px;
height: 42px;
margin-left: 11px;
cursor: pointer;
}
}
}