因为项目中用到在线交流模块,之前使用极光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 效果图