PC端聊天系统——基于angular

做后台管理系统遇到一个要写聊天系统的功能,我用的是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;
      }
    }
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目系统结构——前后分离是一种常见的Web应用程序开发模式,它采用了一种分离前和后的策略,将应用程序分为两个独立的部分:前和后。这种模式通常用于构建复杂的应用程序,如企业级管理系统、在线购物平台等。 以下是项目系统结构——前后分离的主要组成部分: 前: 1. 客户应用程序:通常使用JavaScript框架(如React、Vue、Angular等)或前Web框架(如Django、Flask等)开发,用于处理用户界面、数据请求和响应等功能。 2. 静态资源:包括CSS、图片、JavaScript等静态资源文件,通常存储在Web服务器上,供前应用程序使用。 后: 1. API服务:提供RESTful或GraphQL风格的API接口,用于处理业务逻辑和数据操作。后服务通常使用服务器语言(如Python、Java、Node.js等)编写,并使用数据库存储数据。 2. 数据库:用于存储和管理应用程序的数据,通常使用关系型数据库(如MySQL、PostgreSQL等)或非关系型数据库(如MongoDB、Redis等)。 前后分离的优点: 1. 开发效率高:前和后可以由不同的团队或个人独立开发,减少了沟通和协作的难度。 2. 可扩展性好:前后分离的应用程序可以根据需要灵活地添加新的前或后组件,提高了系统的可扩展性。 3. 灵活性高:前可以使用不同的技术栈,如移动应用程序、小程序等,提高了应用的灵活性。 前后分离的缺点: 1. 安全性问题:前后分离的应用程序可能存在安全风险,如跨站脚本攻击(XSS)和SQL注入等。因此,需要采取适当的措施来保护应用程序的安全性。 2. 集成问题:前后分离的应用程序需要将数据从后传输到前,需要处理数据格式转换、数据验证等问题。 3. 调试和测试难度大:前后分离的应用程序需要分别进行调试和测试,增加了开发和测试的难度。 总之,项目系统结构——前后分离是一种灵活、可扩展的开发模式,适用于构建复杂的应用程序。在开发过程中,需要关注安全性和集成问题,并采取适当的措施来确保应用程序的稳定性和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值