开发内容:开发聊天用户的列表布局与绑定
ion-item上使用click事件不是太好,故直接navPush属性设置点击跳转到的组件,通过navParam属性设置点击跳转组件时传递的参数
<ion-item [navPush]="ChatdetailsPage" [navParam]="userInfo.userId">
创建聊天详情页面ionic g page chatdetails
chatbubbles.html
<ion-header>
<ion-navbar>
<ion-title>冒泡ion-title>
ion-navbar>
ion-header>
<ion-content>
<ion-list>
<ion-item [navPush]="ChatdetailsPage" [navParams]="userInfo">
<ion-avatar item-left>
<img src="">
ion-avatar>
<h2>迪丽热巴h2>
<p>热巴小姐姐Q你了一下哦~p>
ion-item>
ion-list>
ion-content>
chatbubbles.ts
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import {ChatdetailsPage} from '../chatdetails/chatdetails';
@Component({
selector: 'page-chatbubbles',
templateUrl: 'chatbubbles.html',
})
export class ChatbubblesPage {
userInfo:any;
ChatdetailsPage:any;
constructor(
public navCtrl: NavController,
public navParams: NavParams
) {
//模拟返回聊天用户列表
this.userInfo = {
userId:'1',
userName:'迪丽热巴',
}
this.ChatdetailsPage = ChatdetailsPage;
}
}
navParams.get('userName')
chatdeatils.html
<ion-header>
<ion-navbar>
<ion-title>{{chatUserName}}ion-title>
ion-navbar>
ion-header>
<ion-content>
<div class="message-wrap">
<div class="message right">
<img src="../../assets/imgs/logo.png" class="user-img"/>
<div class="msg-detail">
<div class="msg-info">
<p>Jack 1分钟前p>
div>
<div class="msg-content">
<p class="line-breaker">自己发的消息内容p>
div>
div>
div>
div>
ion-content>
chatdetails.ts
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';
@Component({
selector: 'page-chatdetails',
templateUrl: 'chatdetails.html',
})
export class ChatdetailsPage {
chatUserName:string;
constructor(
public navCtrl: NavController,
public navParams: NavParams,
public viewCtrl: ViewController) {
//获得传递过来的username
this.chatUserName = navParams.get('userName')
}
}
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;
}
}
}
}
}
chatdetails.html
<ion-header>
<ion-navbar>
<ion-title>{{chatUserName}}ion-title>
ion-navbar>
ion-header>
<ion-content>
<div class="message-wrap">
<div class="message right">
<img src="../../assets/imgs/logo.png" class="user-img"/>
<div class="msg-detail">
<div class="msg-info">
<p>Jack 1分钟前p>
div>
<div class="msg-content">
<p class="line-breaker">Hi,热巴小姐姐p>
div>
div>
div>
div>
ion-content>
<ion-footer no-border style="height: 55px;">
<ion-grid class="input-wrap">
<ion-row>
<ion-col col-2>
<button ion-button clear ion-only item-right>
<ion-icon name="md-happy">ion-icon>
button>
ion-col>
<ion-col col-8>
<ion-textarea placeholder="请输入内容">ion-textarea>
ion-col>
<ion-col col-2>
<button ion-button clear ion-only item-right>
<ion-icon name="send">ion-icon>
button>
ion-col>
ion-row>
ion-grid>
ion-footer>
ionic g provider emoji
ionic g component emojipicker
,注意自动创建module,需要添加imports并注册到app.modules中
)和chatdetails页面内容emoji.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable()
export class EmojiProvider {
constructor(public http: HttpClient) {
console.log('Hello EmojiProvider Provider');
}
//获取所有表情的数组(已经分组好了的)
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;
}
}
emojipicker.ts
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { EmojiProvider } from '../../providers/emoji/emoji';
import { _ParseAST } from '@angular/compiler';
//实现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]
})
//Angular表单——实现接口ControlValueAccessor
//可以将dom转成angular form
export class EmojipickerComponent implements ControlValueAccessor{
//emoji表情
emojiArray = [];
//输入信息内容
content:string;
onChange:Function;
onTouched:Function;
constructor(
emojiProvider:EmojiProvider
) {
this.emojiArray = emojiProvider.getEmojis();//获得所有emoji表情
}
//写一个值到element上面
writeValue(obj: any): void {
this.content = obj;
}
registerOnChange(fn: any): void {
this.onChange = fn;
this.setValue(this.content);
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
//在此处新的内容的赋值以及函数的绑定
setValue(val:any){
this.content += val;
if(this.content){
this.onChange(this.content);
}
}
}
emojipicker.html
<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;
}
}
}
}
component.module.ts
import { NgModule } from '@angular/core';
import { EmojipickerComponent } from './emojipicker/emojipicker';
import { IonicPageModule } from 'ionic-angular';
@NgModule({
declarations: [EmojipickerComponent],
imports: [IonicPageModule.forChild(EmojipickerComponent)],
exports: [EmojipickerComponent]
})
export class ComponentsModule {}
chatdetails.ts
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';
@Component({
selector: 'page-chatdetails',
templateUrl: 'chatdetails.html',
})
export class ChatdetailsPage {
chatUserName:string;
//决定emoji是否显示
isOpenEmojiPicker = false;
constructor(
public navCtrl: NavController,
public navParams: NavParams,
public viewCtrl: ViewController) {
//获得传递过来的username
this.chatUserName = navParams.get('userName')
}
//切换emoji是否显示
swichEmojiPicker() {
this.isOpenEmojiPicker = !this.isOpenEmojiPicker;
}
}
chatdetails.html
<ion-header>
<ion-navbar>
<ion-title>{{chatUserName}}ion-title>
ion-navbar>
ion-header>
<ion-content>
<div class="message-wrap">
<div class="message right">
<img src="../../assets/imgs/logo.png" class="user-img"/>
<div class="msg-detail">
<div class="msg-info">
<p>Jack 1分钟前p>
div>
<div class="msg-content">
<p class="line-breaker">Hi,热巴小姐姐p>
div>
div>
div>
div>
ion-content>
<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)="swichEmojiPicker()">
<ion-icon name="md-happy">ion-icon>
button>
ion-col>
<ion-col col-8>
<ion-textarea placeholder="请输入内容">ion-textarea>
ion-col>
<ion-col col-2>
<button ion-button clear ion-only item-right>
<ion-icon name="send">ion-icon>
button>
ion-col>
ion-row>
ion-grid>
<emojipicker *ngIf="isOpenEmojiPicker">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;
}
}
}
}
}
实现进入冒泡页面查看详情,通过mock的json数据查看历史消息,创建emojipicker组件实现发送表情,通过消息订阅实现消息返回并被用户监听查看
使用mock的json模拟后台的返回数据
如何在开发中mock数据
创建mock的json文件
请求返回数据的内容与发送请求访问服务一样
getMessageList():Promise{
const url = '../../assets/mock/msg-list.json';
return this.http.get(url)
.toPromise()
.then((response:any) => response.array as ChatMessage[])
.catch(error => Promise.reject(error || '错误信息'));
}
使用ViewChild获取页面DOM节点
this.content.scrollToBottom();
可以将页面滚到底部#名称
标记DOM,在ts中通过@ViewChild('名称') messageInput:TextInput;
获取Events模块实现消息订阅[订阅者模式]
订阅消息使用:subscribe方法
//参数:订阅topic、回调函数(消息内容,时间)
this.events.subscribe('chat.received',(msg,time)=>{
this.messageList.push(msg);
this.scrollToBottom();
})
发布消息使用:publish方法
//将消息发布出去,参数: 事件topic,消息内容,时间
this.events.publish("chat.received",messageSend,Date.now());
取消订阅使用:unsubscribe方法
//取消订阅,参数: 事件topic
this.events.unsubscribe('chat.received');
安装moment:npm install moment --save
,moment是npm轻量级的js数据库,引入moment的方式import * as moment from 'moment';
[所有引入javascript包都是如此]
管道(像管子一样将你定义的方法“流到”所有你需要的地方去),创建管道ionic g pipe relativtime
处理时间,获得与当前时间差情况的pipe文件,relativetime.ts
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';
@Pipe({
name: 'relativetime',
})
export class RelativetimePipe implements PipeTransform {
transform(value: string, ...args) {
//使用moment(时间戳).toNow()则可以得到时间戳距现在多久了
return moment(value).toNow();
}
}
聊天消息处理,用于查看、发送、模拟消息处理与订阅发布的service,chatservice.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Events } from 'ionic-angular';
//聊天信息的属性
export class ChatMessage {
messageId:string;
userId:string;
userName:string;
userImgUrl:string;
toUserId:string;//发送给谁的Id
time:number | string;
message:string;
status:string;
}
//用户信息的属性
export class UserInfo {
userId:string;
userName:string;
userImgUrl:string;
}
@Injectable()
export class ChatserviceProvider {
constructor(
public http: HttpClient,
public events: Events
) {}
/**
* 获取消息列表
* 从获取的JSON中获取
*/
getMessageList():Promise{
const url = '../../assets/mock/msg-list.json';
return this.http.get(url)
.toPromise()
.then((response:any) => response.array as ChatMessage[])
.catch(error => Promise.reject(error || '错误信息'));
}
/**
* 发送消息
* @param message
*/
sendMessage(message:ChatMessage) {
return new Promise(resolve => setTimeout(()=>{
resolve(message)
},Math.random()*1000))
.then(()=>{
//模拟返回消息
this.mockNewMessage(message);
});
}
/**
* 模拟对方返回消息
* 使用发布订阅模式实现即时接受消息
* 引入Events模块
* Events is a public-subscribe style event system for sending and responding to application-level event across your app
*
* @param message
*/
mockNewMessage(message:ChatMessage) {
const id = Date.now().toString();
let messageSend:ChatMessage = {
messageId : id,
userId : '123321',
userName : '迪丽热巴',
userImgUrl : '../../assets/imgs/girl.jpg',
toUserId : message.userId,
time : Date.now(),
message : '不要跟我说:'+message.message+'我只想给你一个么么哒!! ?',
status : 'seccess'
}
//模拟网络请求卡一下
setTimeout(()=>{
//将消息发布出去,参数: 事件主题,消息内容,时间
this.events.publish("chat.received",messageSend,Date.now());
}, Math.random()*1000);
}
}
聊天详情页面布局,chatdetails.html
<ion-header>
<ion-navbar>
<ion-title>{{chatUserName}}ion-title>
ion-navbar>
ion-header>
<ion-content>
<div class="message-wrap">
<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 | relativetime}}p>
div>
<div class="msg-content">
<p class="line-breaker">{{m.message}}p>
div>
div>
div>
div>
ion-content>
<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)="swichEmojiPicker()">
<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.ts
import { Component, ViewChild } from '@angular/core';
import { NavController, NavParams, ViewController, Content, TextInput, Events } from 'ionic-angular';
import { ChatserviceProvider, ChatMessage } from '../../providers/chatservice/chatservice';
import { Storage } from '@ionic/storage';
@Component({
selector: 'page-chatdetails',
templateUrl: 'chatdetails.html',
})
export class ChatdetailsPage {
//和哪个username聊天
chatUserName:string;
//和哪个userid聊天
chatUserId:string;
//当前用户信息
userId:string;
userName:string;
userImgUrl:string;
//决定emoji是否显示
isOpenEmojiPicker = false;
//对话消息列表
messageList:ChatMessage[] = [];
//获取页面DOM节点
//获取整个页面是Content
@ViewChild(Content) content:Content;
//用户获取用户输入的输入框
@ViewChild('chatInput') messageInput:TextInput;
editorMessage:string='';
constructor(
public navCtrl: NavController,
public navParams: NavParams,
public viewCtrl: ViewController,
public chatService: ChatserviceProvider,
public storage: Storage,
public events: Events) {
//获得传递过来的username
this.chatUserName = navParams.get('userName');
//获取当前和谁聊天的userId
this.chatUserId = navParams.get('userId');
}
//页面进来时发送的请求ionViewDidEnter[注意不能使用ionViewDidLoad,如果使用页面会闪一下,ionViewDidLoad是页面渲染后完成的内容]
ionViewDidEnter() {
this.storage.get('token').then((val)=>{
if(val !== null){
this.userId = '140000198202211138';
this.userName = 'Jack汪喆';
this.userImgUrl = '../../assets/imgs/avatar.jpg';
}
})
this.getMessage()
.then(()=>{
this.scrollToBottom();//消息显示完成后需要将页面滚到最下面
})
//听取消息的发布和订阅
//此处通过消息主题实现订阅
this.events.subscribe('chat.received',(msg,time)=>{
this.messageList.push(msg);
this.scrollToBottom();
})
}
//在将要离开页面时,退订订阅的内容
ionViewWillLeave() {
this.events.unsubscribe('chat.received');
}
//切换emoji是否显示
swichEmojiPicker() {
this.isOpenEmojiPicker = !this.isOpenEmojiPicker;
}
//获得消息内容,调用service中属性的方法实现属性的赋值
getMessage() {
//返回的是promise
return this.chatService.getMessageList()
.then(res=>{
this.messageList = res;
})
.catch((error)=>{
console.log(error);
});
}
//页面滚动到最下面
scrollToBottom() {
setTimeout(()=>{
if(this.content.scrollToBottom){
this.content.scrollToBottom();
}
},400);
}
//发送消息
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'//表示消息的状态,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';
}
});
}
//聚焦事件:当光标移到textarea时触发
focus() {
this.isOpenEmojiPicker = false;
this.content.resize();//重新计算整个页面DOM节点的内容
this.scrollToBottom();
}
//从当前消息列表中获取对应messageId的下标的message
getMessageIndex(id:string){
return this.messageList.findIndex(e=>{
return e.messageId === id;
})
}
}