图示
废话就先不多说了,直接上图显示最后的效果。
开发准备
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.tspipes.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...。有兴趣的话,大家可以去看看。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。