1

图示

废话就先不多说了,直接上图显示最后的效果。
聊天界面图示

开发准备

step1:任务分析
我们需要有两个界面:

  • 消息列表页
  • 聊天界面

step2:生成界面

cd 当前工程目录下
// 生成消息列表页
ionic generate page chat

// 生成聊天界面
ionic generate page chat-message

step3:添加一个自定义的管道
这个自定义的管道用于我们发送消息之后,显示多长时间之前发送。这里只是一个简版。

在工程的目录下,新建一个 pipes 的文件夹

新建管道:moment.pipe.ts

import { Pipe } from '@angular/core';
import moment from 'moment';

@Pipe({
  name: 'moment'
})
export class MomentPipe {
  transform(value, args) {
    args = args || '';
    return args === 'ago' ? moment(value).fromNow() : moment(value).format(args);
  }
}

新建 module:pipes.module.ts
pipes.module.ts 用于存放之后所新增的个性化的管道

import { NgModule } from '@angular/core';

import { MomentPipe } from './moment.pipe';

export const pipes = [
  MomentPipe
];

@NgModule({
  declarations:[pipes],
  exports: [pipes]
})

export class PipesModule { }

pipes.module.ts 加载到 app.module.ts 中

@NgModule({
    imports: [PipesModule]
})

界面开发

chat.html
界面上面使用 ion-list 组件作为基础组件,也是用这个组件来区分出不同时间段(天)的聊天信息。我们可以模版语法*ngFor来动态生成今天的消息。

<ion-header>
  <ion-navbar>
    <ion-title>聊天</ion-title>
  </ion-navbar>
</ion-header>、
<ion-content class="chats">

  <!-- 显示今天的消息 -->
  <ion-list>
    <ion-list-header>今天</ion-list-header>
    <!-- 点击的时候跳转界面 -->
    <ion-item *ngFor="let chat of chats" (click)="viewMessages(chat)">
      <ion-avatar item-start>
        <img [src]="chat.imageUrl">
      </ion-avatar>
      <h2>{{chat.title}}</h2>
      <p>{{chat.lastMessage}}</p>
      <ion-note item-end>{{chat.timestamp | date:'HH:mm:ss'| lowercase}}</ion-note>
    </ion-item>
  </ion-list>

  <ion-list>
    <ion-list-header>昨天</ion-list-header>
    <ion-item>
      <ion-avatar item-start>
        <img src="assets/img/avatar/marty-avatar.png">
      </ion-avatar>
      <h2>大逼哥</h2>
      <p>拉萨挺不错的。。。。</p>
      <ion-note item-end>11:11</ion-note>
    </ion-item>

    <ion-item>
      <ion-avatar item-start>
        <img src="assets/img/avatar/marty-avatar.png">
      </ion-avatar>
      <h2>人事主管</h2>
      <p>这次给你涨工资了。你查看下工资白条</p>
      <ion-note item-end>11:11</ion-note>
    </ion-item>
  </ion-list>

  <ion-list>
    <ion-list-header>前天</ion-list-header>
    <ion-item>
      <ion-avatar item-start>
        <img src="assets/img/avatar/ian-avatar.png">
      </ion-avatar>
      <h2>祥</h2>
      <p>下次我请客吃饭。。。。</p>
      <ion-note item-end>11:11</ion-note>
    </ion-item>

    <ion-item>
      <ion-avatar item-start>
        <img src="assets/img/avatar/marty-avatar.png">
      </ion-avatar>
      <h2>涛</h2>
      <p>什么时候回武汉?</p>
      <ion-note item-end>11:11</ion-note>
    </ion-item>
  </ion-list>

</ion-content>

chat.ts
我们定义一个 mock数据,通过他来渲染出我们的界面

import {Component} from '@angular/core';
import {IonicPage, NavController, NavParams} from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-chat',
  templateUrl: 'chat.html',
})
export class ChatPage {
  // mock 数据
  chats = [{
    id: '001',
    imageUrl: 'assets/img/avatar/marty-avatar.png',
    title: '房东',
    lastMessage: '这个月的房租怎么还没有交?',
    timestamp: new Date()
  },
    {
      id: '002',
      imageUrl: 'assets/img/avatar/ian-avatar.png',
      title: '房产中介',
      lastMessage: '上次给你推荐的房子,你看了没有?我这边有新的房源,你要不要过来看看?',
      timestamp: new Date()
    },
    {
      id: '003',
      imageUrl: 'assets/img/avatar/sarah-avatar.jpg',
      title: '公司前台',
      lastMessage: '你有新的快递,请注意查收',
      timestamp: new Date()
    }];

  constructor(public navCtrl: NavController, public navParams: NavParams) {
  }

  ionViewDidLoad() {
    console.log('ionViewDidLoad ChatPage');
  }

 // 界面跳转并且传值
  viewMessages(chat) {
    this.navCtrl.push('ChatMessagePage', {chatId: chat.id});
  }

}

chat-message.html
聊天界面分为两大块:消息展示区域以及输入消息区域。
消息展示区域分为自己发送以及别人发送
消息输入区:就固定在屏幕下方。

<ion-header>
  <ion-navbar>
    <ion-title>chat</ion-title>
  </ion-navbar>
</ion-header>
<ion-content>
  <div *ngFor="let message of messages" class="message-wrapper" on-hold="onMessageHold($event, $index, message)">
    <!-- 判断消息是发送 -->
    <div *ngIf="user._id !== message.userId">
      <img (click)="viewProfile(message)" class="profile-pic left" [src]="toUser.pic" onerror="onProfilePicError(this)" />
      <!--  wave-->
      <div class="chat-bubble left slide-left">
        <div class="message" [innerHTML]="message.text" autolinker> </div>
        <div class="message-detail">
          <span (click)="viewProfile(message)" class="bold">{{toUser.username}}</span>,
          <span>{{message.date | moment:"ago" | lowercase}}</span>
        </div>
      </div>
    </div>

    <!-- 判断消息是发送 -->
    <div *ngIf="user._id === message.userId">
      <img (click)="viewProfile(message)" class="profile-pic right" [src]="user.pic" onerror="onProfilePicError(this)" />
      <div class="chat-bubble right slide-right">
        <div class="message" [innerHTML]="message.text" autolinker></div>
        <div class="message-detail">
          <span (click)="viewProfile(message)" class="bold">{{user.username}}</span>,
          <span>{{message.date | moment:"ago" | lowercase}}</span>
        </div>
      </div>
    </div>
    <div class="cf"></div>
  </div>
</ion-content>

<!-- 底部固定的输入框 -->
<ion-footer>
  <form [formGroup]="messageForm" (submit)="send(chatBox)" novalidate>
    <ion-item>
      <ion-input formControlName="message" [(ngModel)]="chatBox" placeholder="Send {{toUser.username}} a message..."></ion-input>
      <button ion-button clear (click)="send(chatBox)" item-end><ion-icon class="footer-btn" name="send"></ion-icon></button>
    </ion-item>
  </form>
</ion-footer>

chat-message.ts
我们新建一个 messages 列表,有了新的消息就向这个模型中添加就可以了。

import { FormControl, FormBuilder } from '@angular/forms';
import { Component, ViewChild } from '@angular/core';
import {IonicPage, NavController, Content, NavParams} from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-chat-message',
  templateUrl: 'chat-message.html',
})
export class ChatMessagePage {

  toUser = {
    _id: '534b8e5aaa5e7afc1b23e69b',
    pic: 'assets/img/avatar/ian-avatar.png',
    username: 'Venkman',
  };

  user = {
    _id: '534b8fb2aa5e7afc1b23e69c',
    pic: 'assets/img/avatar/marty-avatar.png',
    username: 'Marty',
  };

  doneLoading = false;

  messages = [
    {
      _id: 1,
      date: new Date(),
      userId: this.user._id,
      username: this.user.username,
      pic: this.user.pic,
      text: 'OH CRAP!!'
    },
    {
      _id: 2,
      date: new Date(),
      userId: this.toUser._id,
      username: this.toUser.username,
      pic: this.toUser.pic,
      text: 'what??'
    },
    {
      _id: 3,
      date: new Date(),
      userId: this.toUser._id,
      username: this.toUser.username,
      pic: this.toUser.pic,
      text: 'Pretty long message with lots of content'
    },
    {
      _id: 4,
      date: new Date(),
      userId: this.user._id,
      username: this.user.username,
      pic: this.user.pic,
      text: 'Pretty long message with even way more of lots and lots of content'
    },
    {
      _id: 5,
      date: new Date(),
      userId: this.user._id,
      username: this.user.username,
      pic: this.user.pic,
      text: '哪尼??'
    },
    {
      _id: 6,
      date: new Date(),
      userId: this.toUser._id,
      username: this.toUser.username,
      pic: this.toUser.pic,
      text: 'yes!'
    }
  ];

  @ViewChild(Content) content: Content;

  public messageForm: any;
  chatBox: any;

  constructor(public navParams: NavParams,
              public navCtrl: NavController,
              public formBuilder: FormBuilder) {
    this.messageForm = formBuilder.group({
      message: new FormControl('')
    });
    this.chatBox = '';
  }

  ionViewDidLoad() {
    let modelData: string = '用户名:' + this.navParams.get('chatId');
    console.log(modelData);
  }

  // 发送消息
  send(message) {
    if (message && message !== '') {
      // this.messageService.sendMessage(chatId, message);

      const messageData =
        {
          toId: this.toUser._id,
          _id: 6,
          date: new Date(),
          userId: this.user._id,
          username: this.toUser.username,
          pic: this.toUser.pic,
          text: message
        };

      this.messages.push(messageData);
      this.scrollToBottom();

      setTimeout(() => {
        const replyData =
          {
            toId: this.toUser._id,
            _id: 6,
            date: new Date(),
            userId: this.toUser._id,
            username: this.toUser.username,
            pic: this.toUser.pic,
            text: 'Just a quick reply'
          };
        this.messages.push(replyData);
        this.scrollToBottom();
      }, 1000);
    }
    this.chatBox = '';
  }

  scrollToBottom() {
    setTimeout(() => {
      this.content.scrollToBottom();
    }, 100);
  }

  viewProfile(message: string){
    console.log(message);
  }

}

chat-message.scss

page-chat-message {
  /* allows the bar-footer to be elastic /*
  /* optionally set a max-height */
  /* maxlength on the textarea will prevent /*
  /* it from getting too large also */
  .bar-footer {
    overflow: visible !important;
  }

  .bar-footer textarea {
    resize: none;
    height: 25px;
  }

  /* fixes an ios bug bear */
  button.ion-android-send {
    padding-top: 2px;
  }

  .footer-btn {
    font-size: x-large;
  }

  img.profile-pic {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    position: absolute;
    bottom: 10px;
  }

  img.profile-pic.left {
    left: 10px;
  }

  img.profile-pic.right {
    right: 10px;
  }

  .ion-email {
    float: right;
    font-size: 32px;
    vertical-align: middle;
  }

  .message {
    font-size: 14px;
  }

  .message-detail {
    white-space: nowrap;
    font-size: 14px;
  }

  .bar.item-input-inset .item-input-wrapper input {
    width: 100% !important;
  }

  .message-wrapper {
    position: relative;
  }

  .message-wrapper:last-child {
    margin-bottom: 10px;
  }

  .chat-bubble {
    border-radius: 5px;
    display: inline-block;
    padding: 10px 18px;
    position: relative;
    margin: 10px;
    max-width: 80%;
  }

  .chat-bubble:before {
    content: "\00a0";
    display: block;
    height: 16px;
    width: 9px;
    position: absolute;
    bottom: -7.5px;
  }

  .chat-bubble.left {
    background-color: #e6e5eb;
    float: left;
    margin-left: 55px;
  }

  .chat-bubble.left:before {
    background-color: #e6e5eb;
    left: 10px;
    -webkit-transform: rotate(70deg) skew(5deg);
  }

  .chat-bubble.right {
    background-color: #158ffe;
    color: #fff;
    float: right;
    margin-right: 55px;
  }

  .chat-bubble.right:before {
    background-color: #158ffe;
    right: 10px;
    -webkit-transform: rotate(118deg) skew(-5deg);
  }

  .chat-bubble.right a.autolinker {
    color: #fff;
    font-weight: bold;
  }

  .user-messages-top-icon {
    font-size: 28px;
    display: inline-block;
    vertical-align: middle;
    position: relative;
    top: -3px;
    right: 5px;
  }

  .msg-header-username {
    display: inline-block;
    vertical-align: middle;
    position: relative;
    top: -3px;
  }

  input, textarea, .item-input, .item-input-wrapper {
    background-color: #f4f4f4 !important;
  }

  .bold {
    font-weight: bold;
  }

  .cf {
    clear: both !important;
  }

  a.autolinker {
    color: #3b88c3;
    text-decoration: none;
  }

  /* loading */
  .loader-center {
    height: 100%;
    display: -webkit-box;
    display: -moz-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
    -webkit-box-direction: normal;
    -moz-box-direction: normal;
    -webkit-box-orient: horizontal;
    -moz-box-orient: horizontal;
    -webkit-flex-direction: row;
    -ms-flex-direction: row;
    flex-direction: row;
    -webkit-flex-wrap: nowrap;
    -ms-flex-wrap: nowrap;
    flex-wrap: nowrap;
    -webkit-box-pack: center;
    -moz-box-pack: center;
    -webkit-justify-content: center;
    -ms-flex-pack: center;
    justify-content: center;
    -webkit-align-content: stretch;
    -ms-flex-line-pack: stretch;
    align-content: stretch;
    -webkit-box-align: center;
    -moz-box-align: center;
    -webkit-align-items: center;
    -ms-flex-align: center;
    align-items: center;
  }

  .loader .ion-loading-c {
    font-size: 64px;
  }
}

到此我们就完成了。

在做这个的时候,参考了 https://github.com/yannbf/ion...。有兴趣的话,大家可以去看看。


Wayfreem
241 声望33 粉丝

一个后端工程师,偏偏喜欢前端。


引用和评论

1 篇内容引用
0 条评论