1. 程式人生 > >ionic3學習之聊天介面開發

ionic3學習之聊天介面開發

圖示

廢話就先不多說了,直接上圖顯示最後的效果。
聊天介面圖示

開發準備

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
由於訊息列表介面的佈局是相對比較固化,我們不需要特別複雜的元件來繪製這個介面。並且我們將介面展示出的資料使用mock 資料來模擬。介面上面使用 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資料,通過他來渲染出我們的介面。這個類裡面我們只是定義 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;
  }
}

到此我們就完成了。