ionic3學習之聊天介面開發
阿新 • • 發佈:2019-01-28
圖示
廢話就先不多說了,直接上圖顯示最後的效果。
開發準備
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;
}
}
到此我們就完成了。