因为项目中用到在线交流模块,之前使用极光IM没有调试成功,接下来我们自己手写一版本。
准备工作:
1、新建聊天信息表--ChatMessage
messageId: string; //聊天信息ID userId: string; //主动发送消息的人 username: string; //发送消息人的姓名 userImgUrl: string; //发送消息人的头像 toUserId: string;//发送给谁的 time: number | string; //发送时间 message: string; //消息内容 status: string; //消息状态
2、新建聊天列表页面
①使用命令新建页面
ionic g page chat
②chat.html页面
<ion-header> <ion-navbar> <ion-title>聊天</ion-title> </ion-navbar> </ion-header> <ion-content> <ion-list> <!--循环item就得到了列表--> <ion-item [navPush]="ChatdetailsPage" [navParams]="userinfo" > <ion-avatar item-left> <img src="assets/imgs/123.jpg"> </ion-avatar> <h2>显示的用户名</h2> <p>备注或者消息简称</p> </ion-item> </ion-list> </ion-content>
注意:此页面中使用了
[navPush]和[navParams]来进行页面传值,需要提前新建好第三大步的聊天详情页面,后端接收方式:
this.chatUserName = navParams.get('username');
this.chatUserId = navParams.get('userid');③chat.ts页面
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
@IonicPage()
@Component({
selector: 'page-chat',
templateUrl: 'chat.html',
})
export class ChatPage {
userinfo: Object;
ChatdetailsPage: any;
constructor(public navCtrl: NavController, public navParams: NavParams) {
//你在这里也可以直接从你的 API 接口或者其他的方法实现用户列表的定义
this.userinfo = {
userid: '123321',
username: '用户1'
}
this.ChatdetailsPage = "ChatdetailsPage";
}
} ④效果图

4、聊天详情页面开发前奏--聊天表情组件开发
①、新建表情提供providers
ionic g provider emoji
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';
@Injectable()
export class EmojiProvider {
constructor(public http: Http) {
}
/**
* 获取所有表情的数组(已分组好了的)
*
* @memberof EmojiProvider
*/
getEmojis() {
const EMOJIS = "😀 😃 😄 😁 😆 😅 😂 🤣 😊 😇 🙂 🙃 😉 😌 😍 😘 😗 😙 😚 😋 😜 😝 😛 🤑 🤗 🤓 😎 🤡 🤠 😏 😒 😞 😔 😟 😕 🙁" +
" ☹️ 😣 😖 😫 😩 😤 😠 😡 😶 😐 😑 😯 😦 😧 😮 😲 😵 😳 😱 😨 😰 😢 😥 🤤 😭 😓 😪 😴 🙄 🤔 🤥 😬 🤐 🤢 🤧 😷 🤒 🤕 😈 👿" +
" 👹 👺 💩 👻 💀 ☠️ 👽 👾 🤖 🎃 😺 😸 😹 😻 😼 😽 🙀 😿 😾 👐 🙌 👏 🙏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚" +
" 🖐 🖖 👋 🤙 💪 🖕 ✍️ 🤳 💅 🖖 💄 💋 👄 👅 👂 👃 👣 👁 👀 🗣 👤 👥 👶 👦 👧 👨 👩 👱♀️ 👱 👴 👵 👲 👳♀️ 👳 👮♀️ 👮 👷♀️ 👷" +
" 💂♀️ 💂 🕵️♀️ 🕵️ 👩⚕️ 👨⚕️ 👩🌾 👨🌾 👩🍳 👨🍳 👩🎓 👨🎓 👩🎤 👨🎤 👩🏫 👨🏫 👩🏭 👨🏭 👩💻 👨💻 👩💼 👨💼 👩🔧 👨🔧 👩🔬 👨🔬" +
" 👩🎨 👨🎨 👩🚒 👨🚒 👩✈️ 👨✈️ 👩🚀 👨🚀 👩⚖️ 👨⚖️ 🤶 🎅 👸 🤴 👰 🤵 👼 🤰 🙇♀️ 🙇 💁 💁♂️ 🙅 🙅♂️ 🙆 🙆♂️ 🙋 🙋♂️ 🤦♀️ 🤦♂️ 🤷♀" +
"️ 🤷♂️ 🙎 🙎♂️ 🙍 🙍♂️ 💇 💇♂️ 💆 💆♂️ 🕴 💃 🕺 👯 👯♂️ 🚶♀️ 🚶 🏃♀️ 🏃 👫 👭 👬 💑 👩❤️👩 👨❤️👨 💏 👩❤️💋👩 👨❤️💋👨 👪 👨👩👧" +
" 👨👩👧👦 👨👩👦👦 👨👩👧👧 👩👩👦 👩👩👧 👩👩👧👦 👩👩👦👦 👩👩👧👧 👨👨👦 👨👨👧 👨👨👧👦 👨👨👦👦 👨👨👧👧 👩👦 👩👧" +
" 👩👧👦 👩👦👦 👩👧👧 👨👦 👨👧 👨👧👦 👨👦👦 👨👧👧 👚 👕 👖 👔 👗 👙 👘 👠 👡 👢 👞 👟 👒 🎩 🎓 👑 ⛑ 🎒 👝 👛 👜 💼 👓" +
" 🕶 🌂 ☂️";
//进行分组的操作
let array = EMOJIS.split(' ');
let groupNumber = Math.ceil(array.length / 24); //四舍五入,尽量取大数 15.1->16 , 15.6->16
let items = [];
//分组填充表情
for (let i = 0; i < groupNumber; i++) {
items.push(array.slice(24 * i, 24 * (i + 1)))
}
return items;
}
}②新建components表情拾取器emojipicker
ionic g component emojipicker
components组件类似网站开发中把通用的头部尾部抠出来,以便于其他页面复用的那么一块区域。
emojipicker.html
<!-- Generated template for the EmojipickerComponent component -->
<div class="emoji-picker">
<div class="emoji-items">
<ion-slides pager>
<ion-slide *ngFor="let items of emojiArray">
<span class="emoji-item" (click)="setValue(item)" *ngFor="let item of items">
{{item}}
</span>
</ion-slide>
</ion-slides>
</div>
</div>emojipicker.scss
emojipicker {
.emoji-picker{
height: 195px;
border-top:1px solid #999;
.emoji-items{
padding: 10px;
width: 100%;
height: 100%;
.emoji-item{
display: block;
float: left;
width: 12.5%;
height: 42px;
font-size: 1.2em;
line-height: 42px;
text-align: center;
margin-bottom: 10px;
}
}
}
}emojipicker.ts
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { EmojiProvider } from "../../providers/emoji/emoji";
/**
* Generated class for the EmojipickerComponent component.
*
* See https://angular.io/api/core/Component for more info on Angular
* Components.
*/
//实现 EmojipickerComponent 的 providers
export const EMOJI_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EmojipickerComponent),
multi: true
}
@Component({
selector: 'emojipicker',
templateUrl: 'emojipicker.html',
providers: [EMOJI_ACCESSOR]
})
// 实现接口 ControlValueAccessor
export class EmojipickerComponent implements ControlValueAccessor {
emojiArray = [];
content: string;
onChanged: Function;
onTouched: Function;
constructor(emojiProvider: EmojiProvider) {
this.emojiArray = emojiProvider.getEmojis();
}
writeValue(obj: any): void {
this.content = obj;
}
registerOnChange(fn: any): void {
this.onChanged = fn;
this.setValue(this.content);
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
//再次处理新的内容赋值以及函数的绑定
setValue(val: any): any {
this.content += val;
if (this.content) {
this.onChanged(this.content);
}
}
}components.module.ts
import { NgModule } from '@angular/core';
import { EmojipickerComponent } from './emojipicker/emojipicker';
import { IonicModule } from 'ionic-angular';
@NgModule({
declarations: [EmojipickerComponent],
imports: [IonicModule],
exports: [EmojipickerComponent]
})
export class ComponentsModule {}至此组件开发完成。如果在需要使用的页面中调用组件 需要引入一下 例如在以下聊天详情页面chatdetails.module.ts
import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { ChatdetailsPage } from './chatdetails';
import {ComponentsModule} from '../../components/components.module';
@NgModule({
declarations: [
ChatdetailsPage
],
imports: [
ComponentsModule,//在此引入组件
IonicPageModule.forChild(ChatdetailsPage),
],
})
export class ChatdetailsPageModule {}舒适
4、聊天详情页面的开发
①使用命令新建详情页面
ionic g page chatdetails
②chatdetails.html页面
<ion-header>
<ion-navbar>
<!--这个title显示的是跟谁聊天应该从后台取得-->
<ion-title>{{chatUserName}}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<!--message-wrap这个div是聊天内容页面,包括左边好友内容右边自己发送的内容-->
<div class="message-wrap">
<!-- [class.left]="m.userId === chatUserId" 当获取的消息列表中的消息体重的用户id等于和谁聊天时,消息体放在聊天窗口的左侧
[class.right]="m.userId=== userId"> 当用户ID为当前登录用户的时候显示在聊天窗口的右侧-->
<div class="message" *ngFor="let m of messageList"
[class.left]="m.userId === chatUserId"
[class.right]="m.userId=== userId">
<img [src]="m.userImgUrl" class="user-img">
<ion-spinner name="dots" *ngIf="m.status === 'pending'"></ion-spinner>
<div class="msg-detail">
<div class="msg-info">
<p>{{m.username}} {{m.time }}</p>
</div>
<div class="msg-content">
<p class="line-breaker">{{m.message}}</p>
</div>
</div>
</div>
</div>
</ion-content>
<!--footer 选取表情的笑脸按钮和 输入消息的文本框以及 发送按钮-->
<ion-footer no-border [style.height]="isOpenEmojiPicker? '255px': '55px'">
<ion-grid class="input-wrap">
<ion-row>
<ion-col col-2>
<button ion-button clear ion-only item-right (click)="switchEmojiPicker()">
<ion-icon name="md-happy"></ion-icon>
</button>
</ion-col>
<ion-col col-8>
<ion-textarea
#chatInput
[(ngModel)]="editorMessage"
(keyup.enter)="sendMessage()"
(focus)="focus()"
placeholder="输入内容"></ion-textarea>
</ion-col>
<ion-col col-2>
<button ion-button clear ion-only item-right (click)="sendMessage()">
<ion-icon name="send"></ion-icon>
</button>
</ion-col>
</ion-row>
</ion-grid>
<!--表情组件 使用了自定义组件-->
<emojipicker *ngIf="isOpenEmojiPicker" [(ngModel)]="editorMessage"></emojipicker>
</ion-footer>②chatdetails.scss
page-chatdetails {
$userBackgroundColor: #387ef5;
$toUserBackgroundColor: #fff;
ion-content .scroll-content {
background-color: #f5f5f5;
}
ion-footer {
box-shadow: 0 0 4px rgba(0, 0, 0, 0.11);
background-color: #fff;
height: 255px;
}
.line-breaker {
white-space: pre-line;
}
.input-wrap {
padding: 0 5px;
ion-textarea {
position: static;
}
ion-col.col {
padding: 0;
}
button {
width: 100%;
height: 55px;
font-size: 1.3em;
margin: 0;
}
textarea {
border-bottom: 1px #387ef5;
border-style: solid;
}
}
.message-wrap {
padding: 0 10px;
.message {
position: relative;
padding: 7px 0;
.user-img {
position: absolute;
border-radius: 45px;
width: 45px;
height: 45px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.36);
}
.msg-detail {
width: 100%;
display: inline-block;
p {
margin: 0;
}
.msg-info {
p {
font-size: .8em;
color: #888;
}
}
.msg-content {
position: relative;
margin-top: 5px;
border-radius: 5px;
padding: 8px;
border: 1px solid #ddd;
color: #fff;
width: auto;
span.triangle {
background-color: #fff;
border-radius: 2px;
height: 8px;
width: 8px;
top: 12px;
display: block;
border-style: solid;
border-color: #ddd;
border-width: 1px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
position: absolute;
}
}
}
}
.message.left {
.msg-content {
background-color: $toUserBackgroundColor;
float: left;
}
.msg-detail {
padding-left: 60px;
}
.user-img {
left: 0;
}
.msg-content {
color: #343434;
span.triangle {
border-top-width: 0;
border-right-width: 0;
left: -5px;
}
}
}
.message.right {
.msg-detail {
padding-right: 60px;
.msg-info {
text-align: right;
}
}
.user-img {
right: 0;
}
ion-spinner {
position: absolute;
right: 10px;
top: 50px;
}
.msg-content {
background-color: $userBackgroundColor;
float: right;
span.triangle {
background-color: $userBackgroundColor;
border-bottom-width: 0;
border-left-width: 0;
right: -5px;
}
}
}
}
}③chatdetials.ts
import { Component,ViewChild} from '@angular/core';
import { IonicPage, NavController, NavParams,Content,TextInput,Events } from 'ionic-angular';
import { Storage } from '@ionic/storage';
import { ChatserviceProvider, ChatMessage } from '../../providers/chatservice/chatservice';
import { RestProvider } from '../../providers/rest/rest';
@IonicPage()
@Component({
selector: 'page-chatdetails',
templateUrl: 'chatdetails.html',
})
export class ChatdetailsPage {
chatUserName: string;
chatUserId: string;
userId: string;
userName: string;
userImgUrl: string;
isOpenEmojiPicker = false;
messageList: ChatMessage[] = [];
errorMessage: any;
editorMessage: string;
@ViewChild(Content) content: Content; //全局的 content
@ViewChild('chatInput') messageInput: TextInput; //获取前台的输入框
constructor(public navCtrl: NavController,
public navParams: NavParams,
public rest: RestProvider,
public storage: Storage,
public event: Events,
public chatService: ChatserviceProvider) {
this.chatUserName = navParams.get('username');
this.chatUserId = navParams.get('userid');
}
ionViewDidEnter() {
this.storage.get('UserId').then((val) => {
if (val != null) {
this.rest.getUserInfo(val)
.subscribe(
userinfo => {
this.userId = '140000198202211138';
this.userName = userinfo["UserNickName"];
this.userImgUrl = userinfo["UserHeadface"] + "?" + (new Date()).valueOf();
},
error => this.errorMessage = <any>error);
}
});
this.getMessages()
.then(() => {
this.scrollToBottom();
});
//听取消息的发布,订阅
this.event.subscribe('chat.received', (msg, time) => {
this.messageList.push(msg);
this.scrollToBottom();
})
}
sendMessage() {
if (!this.editorMessage.trim())
return;
const id = Date.now().toString();
let messageSend: ChatMessage = {
messageId: id,
userId: this.userId,
username: this.userName,
userImgUrl: this.userImgUrl,
toUserId: this.chatUserId,
time: Date.now(),
message: this.editorMessage,
status: 'pending'
}
this.messageList.push(messageSend);
this.scrollToBottom();
this.editorMessage = '';
if (!this.isOpenEmojiPicker) {
this.messageInput.setFocus();
}
//发送消息并改变消息的状态
this.chatService.sendMessage(messageSend)
.then(() => {
let index = this.getMessageIndex(id);
if (index !== -1) {
this.messageList[index].status = 'success';
}
});
}
ionViewWillLeave() {
//进行事件的取消订阅
this.event.unsubscribe('chat.received');
}
focus() {
this.isOpenEmojiPicker = false;
this.content.resize();
this.scrollToBottom();
}
/**
* 调用 service 里面的方法进行属性的赋值
*
* @returns
* @memberof ChatdetailsPage
*/
getMessages() {
return this.chatService.getMessageList()
.then(res => {
this.messageList = res;
})
.catch(error => {
console.error(error);
})
}
/**
* 切换表情组件
*
* @memberof ChatdetailsPage
*/
switchEmojiPicker() {
this.isOpenEmojiPicker = !this.isOpenEmojiPicker;
}
scrollToBottom(): any {
setTimeout(() => {
if (this.content.scrollToBottom) {
this.content.scrollToBottom();
}
}, 400);
}
getMessageIndex(id: string) {
return this.messageList.findIndex(e => e.messageId === id);
}
}④效果图

5、聊天功能开发
新建providers chatservice
ionic g provider chatservice
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Events } from 'ionic-angular';
import 'rxjs/add/operator/map';
//聊天信息的属性
export class ChatMessage {
messageId: string;
userId: string;
username: string;
userImgUrl: string;
toUserId: string;//发送给谁的
time: number | string;
message: string;
status: string;
}
//用户信息的属性
export class UserInfo {
userId: string;
userName: string;
userImgUrl: string;
}
@Injectable()
export class ChatserviceProvider {
constructor(public http: Http, public event: Events) {
}
/**
* 获取消息列表
* 从 API 获取或者从模拟的 JSON 获取
* @returns null
* @memberof ChatserviceProvider
*/
getMessageList(): Promise<ChatMessage[]> {
const url = '../assets/mock/msg-list.json';
return this.http.get(url)
.toPromise()
.then(response => response.json().array as ChatMessage[])
.catch(error => Promise.reject(error || '错误信息'));
}
sendMessage(message: ChatMessage) {
return new Promise(resolve => setTimeout(() => {
resolve(message)
}, Math.random() * 1000))
.then(() => {
this.mockNewMessage(message);
});
}
/**
* 模拟对方回复了一个消息
* 这里要思考:前台如何即时地能接受到这个消息?
* 引入 Events 模块
*
* @param {*} message
* @memberof ChatserviceProvider
*/
mockNewMessage(message: any) {
const id = Date.now().toString();
let messageSend: ChatMessage = {
messageId: id,
userId: '123321',
username: '用户1',
userImgUrl: 'assets/imgs/123.jpg',
toUserId: message.userId,
time: Date.now(),
message: '「' + message.message + '」?',
status: 'success'
}
//进行消息的发布,类似大喇叭进行广播。
setTimeout(() => {
this.event.publish('chat.received', messageSend, Date.now())
}, Math.random() * 1000)
}
}6、日期格式化
①如何在typeScript中引用JavaScript包
包的安装:
npm install moment --save
全局转换pipe
ionic g pipe relativetime
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';
@Pipe({
name: 'relativetime',
})
export class RelativetimePipe implements PipeTransform {
/**
* 将日期格式转化成对应时间格式
*
* @param {string} value
* @param {any} args
* @returns
* @memberof RelativetimePipe
*/
transform(value: string, ...args) {
return moment(value).toNow();
}
}全局导入
import { RelativetimePipe } from "../pipes/relativetime/relativetime";
declarations中定义
2、1 效果图