# 九、聊天页设置界面开发和数据渲染

# 1. 聊天页设置相关信息获取

在类文件 /common/js/chatClass.js

...
// 聊天页设置相关信息获取
getChatPageSet(to_id, chatType){
    let xiaoxiList = this.getXiaoXiList();
    // 找到当前聊天
    let index = xiaoxiList.findIndex(v => v.id == to_id && v.chatType == chatType);
    if(index != -1){
        // 找到了
        return xiaoxiList[index];
    }
    return null;
}

# 2. 进入设置页面

在页面 /pages/chat/chat.nvue

//点击了三个点图标
openMore(){
    console.log('点击了三个点图标',this.arg);
    let op = {
        action:'chatpageset',
        title:'设置',
        id: this.arg.id,
        chatType: this.arg.chatType,
        avatar : this.arg.avatar,
        name : this.arg.name,
    };
    uni.navigateTo({
        url: `/pages/setpageInfo/setpageInfo?action=${op.action}&title=${encodeURIComponent(op.title)}&id=${op.id}&chatType=${op.chatType}&avatar=${encodeURIComponent(op.avatar)}&name=${encodeURIComponent(op.name)}`,
    });
},

# 3. 设置页面开发

在页面 /pages/setpageInfo/setpageInfo.vue

<template>
	<view>
		<!-- 导航栏 -->
		...
		<!-- 内容 -->
		<view>
			<!-- 聊天相关 -->
			...
			<!-- 聊天页设置 -->
			<view v-if="setdata.action == 'chatpageset'">
				<view v-if="chatpageset.id && chatpageset.chatType">
					<!-- 头像部分 -->
				    <view class="flex flex-wrap p-3">
						<!-- 单聊 -->
						<view v-if="chatpageset.chatType == 'single'"
						class="flex flex-column justify-center align-center mr-2">
							<u-avatar :src="chatpageset.avatar" shape="square"
							customStyle="border:1px solid #eeeeee;"></u-avatar>
							<text class="font-sm mt-1 text-muted">{{chatpageset.name}}</text>
						</view>
						<!-- 群聊 循环头像昵称 -->
						<!-- 加人组群 -->
						<view class="border flex align-center justify-center" 
						style="width: 40px;height: 40px;border-style: dashed;"
						@click="clickCell('加人组群', 'addUserToGroup')">
						   <u-icon name="plus" size="24" color="#999999"></u-icon>
						</view>
						<!-- 删除群聊用户 -->
						<view v-if="chatpageset.chatType == 'group'"
						class="border flex align-center justify-center ml-2" 
						style="width: 40px;height: 40px;border-style: dashed;"
						@click="clickCell('群组删除某个用户', 'deleteUserFromGroup')">
						   <u-icon name="minus" size="24" color="#999999"></u-icon>
						</view>
					</view>
					<u-gap height="10" bgColor="#EDEDED"></u-gap>
					<!-- 查找聊天内容 -->
					<u-cell-group>
						<u-cell title="查找聊天内容"
						:border="false" clickable isLink
						:customStyle="customStyles"
						:iconStyle="iconStyles"
						:titleStyle="titleStyles"
						@click="clickCell('查找聊天内容', 'searchChatData')"></u-cell>
					</u-cell-group>
					<u-gap height="10" bgColor="#EDEDED"></u-gap>
					<!-- 消息设置 -->
					<u-cell-group>
						<u-cell title="消息免打扰"
						:border="false" clickable
						:customStyle="customStyles"
						:iconStyle="iconStyles"
						:titleStyle="titleStyles">
						   <view slot="value">
							   <u-switch v-model="chatpageset.nowarn" @change="switchChange('nowarn')"></u-switch>
						   </view>
						</u-cell>
						<u-cell title="置顶聊天"
						:border="false" clickable
						:customStyle="customStyles"
						:iconStyle="iconStyles"
						:titleStyle="titleStyles">
						   <view slot="value">
							   <u-switch v-model="chatpageset.istop" @change="switchChange('istop')"></u-switch>
						   </view>
						</u-cell>
						<u-cell title="提醒"
						:border="false" clickable
						:customStyle="customStyles"
						:iconStyle="iconStyles"
						:titleStyle="titleStyles">
						   <view slot="value">
							   <u-switch v-model="chatpageset.stongwarn" @change="switchChange('stongwarn')"></u-switch>
						   </view>
						</u-cell>
					</u-cell-group>
					<u-gap height="10" bgColor="#EDEDED"></u-gap>
					<!-- 清空聊天记录 -->
					<u-cell-group>
						<u-cell title="清空聊天记录"
						:border="false" clickable 
						:customStyle="customStyles"
						:iconStyle="iconStyles"
						:titleStyle="titleStyles"
						@click="clickCell('清空聊天记录', 'deleteChatData')"></u-cell>
					</u-cell-group>
					<u-gap height="10" bgColor="#EDEDED"></u-gap>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	...
	export default {
		mixins:[toolJs],
		data() {
			return {
				...,
				chatpageset:{},
			}
		},
		onLoad(e) {
			...
			// 动态生成数据
			if(e.action){
				this.setdata.action = e.action;
				if(this.setdata.action == 'chatset'){
					...
				}else if(this.setdata.action == 'chatpageset'){
					this.chatpageset.id = e.id;
					this.chatpageset.chatType = e.chatType;
					this.chatpageset.avatar = decodeURIComponent(e.avatar);
					this.chatpageset.name = decodeURIComponent(e.name);
					// 聊天页设置相关信息获取
					let getChatPageSet = this.chatClass.getChatPageSet(this.chatpageset.id,this.chatpageset.chatType);
					console.log('聊天页设置信息', getChatPageSet);
					// 设置相关
					this.chatpageset = {
						...this.chatpageset,
						// 单聊
						istop: getChatPageSet.istop,
						shownickname: getChatPageSet.shownickname ? true : false,
						nowarn: getChatPageSet.nowarn ? true : false,
						stongwarn: getChatPageSet.stongwarn ? true : false,
						// 群聊还有以下字段
						user_id: 0,
						remark:'',
						invite_confirm: 0,
					};
					console.log('设置相关',this.chatpageset);
					
				}
			}
		},
		computed:{
			...mapState({
				me : state => state.Chatuser.regloginUser,
				chatClass : state => state.Chatuser.chatClass,
			}),
			customStyles(){
				return `padding-top:20rpx;padding-bottom:20rpx`;
			},
			iconStyles(){
				return `font-size:52rpx;margin-right:30rpx;color:#5696da`;
			},
			titleStyles(){
				return `font-size:32rpx;`;
			},
		},
		methods: {
			switchChange(key){
				console.log(`switch开关${key}`, this.chatpageset[key]);
			},
			// 点击某一项
			clickCell(title, type){
				console.log(title, type);
			},
			submitSet(){
				...
			}
		}
	}
</script>

<style>

</style>

# 十、添加好友到群聊

# 1. 新增进入聊天页设置的功能入口

考虑到微信小程序和H5(微信打开)没有三个点进入聊天页设置, 我们在聊天页扩展菜单新增一个入口。
在文件 /pages/chat/plusIconAction.js

data(){
    return {
	    ...
	    plusMenus:[ // 加号扩展菜单栏目
			{ name:"聊天设置", icon:"setting-fill", iconType:"uview", eventType:"openMore" },
			...
	    ],
	    ...
    }
},
methods:{
	//点击加号扩展菜单的某一项
	async swiperItemClick(item, itemIndex) {
		...
		if (this.sendMessageMode === 'icon') {
			...
		} else {
			...
			switch (item.eventType){
				case 'openMore':
					this.openMore();
					break;
				case 'photo':
					...
					break;
				case 'video':
					...
					break;
				case 'cameraPhoto':
					...
					break;
				case 'cameraVideo':
					...
					break;
				case 'map':
					break;
				case 'mingpian':
					break;
				
			}
		}
	},
	...
},

# 2. 聊天页设置点击加号进入添加好友到群聊

在页面 /pages/setpageInfo/setpageInfo.vue

clickCell(title, type){
	console.log(title, type);
	if(type == 'addUserToGroup'){
		let op = {
			action:'addUserToGroup',
			title:encodeURIComponent(title),
		};
		uni.navigateTo({
			url: `/pages/friendsList/friendsList?action=${op.action}&title=${op.title}`,
		});
	}
},

# 3. 添加好友到群聊页面布局开发

在页面 /pages/friendsList/friendsList.nvue

<template>
	<view>
		<!-- 导航栏 -->
		<chat-navbar :title="title" ...></chat-navbar>
		
		<!-- 朋友列表 -->
		<u-index-list :indexList="indexList">
			<view slot="header" class="list" v-if="action == 'friendList'">
				...
			</view>
			<!-- 列表 -->
			<!-- 选择框 -->
			<checkbox-group @change="checkboxChange">
				<template v-for="(item, index) in itemArr">
					<!-- #ifdef APP-NVUE -->
					<u-index-anchor :text="indexList[index]" :key="index"></u-index-anchor>
					<!-- #endif -->
					<u-index-item :key="index">
						<!-- #ifndef APP-NVUE -->
						<u-index-anchor :text="indexList[index]"></u-index-anchor>
						<!-- #endif -->
						<view class="list" v-for="(item1, index1) in item" :key="index1">
							<view class="list__item"
							@click="openUserInfo(item1,'info')">
								<!-- 选择框 -->
								<view class="mr-2">
									<checkbox iconColor="#4cd964" :value="`${item1.id}`"></checkbox>
								</view>
								<image class="list__item__avatar" :src="item1.url"></image>
								<text class="list__item__user-name">{{item1.name}}</text>
							</view>
							<u-line></u-line>
						</view>
					</u-index-item>
				</template>
			</checkbox-group>
			<view slot="footer" ...>
				...
			</view>
		</u-index-list>
		
		<!-- 底部完成选择按钮 -->
		<view v-if="action == 'addUserToGroup'">
			<!-- 占位 -->
			<view :style="btnBottomHeight"></view>
			<!-- 底部按钮 -->
			<view class="position-fixed left-0 right-0 bottom-0 bg-light 
			flex flex-row p-3 align-center justify-end"
			style="z-index: 100;"
			:style="btnBottomStyle">
			    <view style="height: 90rpx;" class="flex flex-row align-center">
					<text v-if="addUserToGroup.userIds"
					class="text-muted flex-shrink mr-2">已选:
					{{addUserToGroup.userIds.length}} 个好友</text>
					<u-button text="确定加入群聊" type="success" 
					@click="btnBottomSubmit"></u-button>
				</view>
			</view>
		</view>
		
	</view>
</template>

<script>
	...
	export default {
		mixins:[toolJs],
		data() {
			return {
				...,
				title:'好友列表',
				action:'friendList',
				bottomSafeAreaHeight:0, // 底部安全距离
				addUserToGroup:{
					userIds:false, // 选择加入群聊的用户id
				}, 
			}
		},
		onLoad(e) {
			...
			
			//选择好友加入群聊
			if(e && e.action == 'addUserToGroup' ){
				this.title = e.title ? decodeURIComponent(e.title) : '选择好友';
				uni.setNavigationBarTitle({title:this.title});
				this.action = 'addUserToGroup';
			}
		},
		onShow() {
			...
		},
		mounted() {
			let info = uni.getSystemInfoSync();
			this.bottomSafeAreaHeight = info.safeAreaInsets.bottom;
	    },
		computed: {
			...mapState({
				...
			}),
			btnBottomStyle(){
				let pbottom = this.bottomSafeAreaHeight == 0 ?
				uni.upx2px(30) : this.bottomSafeAreaHeight + uni.upx2px(30);
				return `padding-bottom: ${pbottom}px;`;
			},
			btnBottomHeight(){
				let pbottom = this.bottomSafeAreaHeight == 0 ?
				uni.upx2px(30) : this.bottomSafeAreaHeight + uni.upx2px(30);
                return `height:${uni.upx2px(30 + 90) + pbottom}px;`;
			},
			itemArr() {
				...
				return newList && newList.map(item => {
					const arr = [];
					for(let i = 0; i < item.list.length; i++){
						...
						arr.push({
							...,
							id: item.list[i].id,
						})
					}
					return arr
				});
			},
			indexList(){
				...
			},
		},
		methods: {
			checkboxChange(e){
				console.log('选择框',e);
				this.addUserToGroup.userIds = e.detail.value;
				console.log('已选',this.addUserToGroup.userIds);
			},
			btnBottomSubmit(){
				if(!this.addUserToGroup.userIds || !this.addUserToGroup.userIds.length){
					return uni.showToast({title: '请选择要加入群聊的好友',icon:'none'});
				}
				let userIds = this.addUserToGroup.userIds.filter(v=> parseInt(v))
				.map(i=> parseInt(i));
				console.log('提交选择的好友加入群聊',userIds);
			},
			...,
			openUserInfo(item1,action){
				if(this.action == 'addUserToGroup') return;
				uni.navigateTo({
					url: `/pages/userinfo/userinfo?uuid=${item1.uuid}`,
				});
			},
		}
	}
</script>

# 十一、创建群聊

# 1. 创建群聊接口说明

创建群聊接口,具体查看: 二十一、创建群聊(成功后通过webSocket通知群聊用户)

# 2. 提交创建群聊

在页面 /pages/friendsList/friendsList.nvue

btnBottomSubmit(){
	...
	console.log('提交选择的好友加入群聊',userIds);
	uni.showLoading({title: '正在创建中...',mask: false});
	uni.$u.http.post(requestUrl.http + `/api/chat/group/create`, {
		userIds: userIds,
	}, {
		header: {
			token: this.me.token,
		},
	}).then(res => {
		console.log('加入群聊服务器返回',res);
		if(res.data.data == 'ok'){
			uni.switchTab({
				url:'/pages/xiaoxi/xiaoxi'
			});
		}
	}).finally(()=>{
		uni.hideLoading();
	});
},

如果没有出现提示,看一下类文件 /common/js/chatClass.js 中 方法(消息页的聊天列表更新一下): updateXiaoXiList 中有没有添加群聊的逻辑。没有的话,替换到这个方法:六、发消息前把消息添加到本地历史记录,消息页的聊天列表更新一下,发完之后更新一下本地历史记录

# 3. 创建群聊成功后,消息页通知信息头像、标题等处理

在组件 /components/chat-chatlist/chat-chatlist.vue

<template>
	<view ...>
		<!-- 头像 -->
		<view ...>
			<!-- 头像地址 -->
			<!-- <image :src="item.avatar" mode="widthFix" style="width: 90rpx;height: 90rpx;" 
			class="rounded">
			</image> -->
			<view v-if="avatar">
				<!-- 一张 -->
				<view v-if="avatar.length == 1">
					<!-- <u--image :src="avatar[0]" mode="widthFix"
					width="90rpx" height="90rpx" radius="8rpx"></u--image> -->
					<u-avatar :src="avatar[0]" shape="square" size="90rpx"></u-avatar>
				</view>
				<!-- 多张 -->
				<view v-else style="width: 90rpx;background-color: #eeeeee;"
				class="rounded">
				    <!-- 2张 3张 4张-->
					<view v-if="avatar.length == 2 || avatar.length == 3 || avatar.length == 4"
					class="flex flex-row flex-wrap align-center justify-center"
					style="align-content: center;height:90rpx;">
						<u-avatar v-for="(v,k) in avatar" :key="k"
						:src="v" shape="square" size="45rpx"></u-avatar>
					</view>
					<!-- 5张到9张 -->
					<view v-if="avatar.length >= 5"
					class="flex flex-row flex-wrap align-center justify-center"
					style="align-content: center;height:90rpx;">
						<u-avatar v-for="(v,k) in avatar" :key="k"
						:src="v" shape="square" size="30rpx"></u-avatar>
					</view>
				</view>
			</view>
			<!-- 消息数量 角标-->
			<!-- <text class="bg-danger rounded-circle text-white font-sm position-absolute"
			style="padding-left: 14rpx;padding-right: 14rpx;
			padding-top: 2rpx;padding-bottom: 2rpx;
			top: 16rpx;right:10rpx;z-index: 100;">9</text> -->
			<u-badge :isDot="false" :value="item.datacount" 
			absolute :offset="['10rpx','18rpx']"
			max="999" shape="circle" numberType="limit"></u-badge>
		</view>
		<!-- 右边 -->
		<view class="flex flex-column border-bottom border-light-secondary flex-1 pr-3"
		style="padding-top: 24rpx;padding-bottom: 24rpx;">
			<!-- 上面:昵称 + 时间 -->
			<view class="flex justify-between align-center mb-1">
				<text style="font-size: 32rpx;">{{nickname}}</text>
				<text class="font-small text-light-muted">{{item.chat_time}}</text>
			</view>
			<!-- 下面:聊天内容 -->
			<view class="pr-5">
				<text class="text-light-muted u-line-1" 
				style="font-size: 24rpx;">{{item.data}}</text>
			</view>
		</view>
	</view>
</template>

<script>
	...
	import {requestUrl} from '@/common/mixins/configData.js';
	export default{
		...
		methods:{
			...
		},
		computed:{
			...,
			//昵称显示
			nickname(){
				if(this.item.nickname.length > 10){
					return this.item.nickname.substring(0,10) + '...';
				}
				return this.item.nickname;
			},
			// 头像显示
			avatar(){
				let arr = this.item.avatar && this.item.avatar.split(',');
				arr = arr.map(v => {
					if(v.startsWith('http')){
						return v;
					}else{
						return requestUrl.http + v;
					}
				});
				console.log('头像',arr);
				return arr;
			}
		}
	}
</script>

...

# 十二、进入群聊发消息及接收群聊消息

# 1. 点击群聊进入聊天页显示群聊提示

在组件 /components/chat-item/chat-item.vue

<template>
	<view class="px-3">
		<!-- 时间 -->
		<view v-if="chatShowTime"
		class="flex align-center justify-center py-3">
			<text class="font-sm text-light-muted">{{chatShowTime}}</text>
		</view>
		<!-- 撤回消息 -->
		<view v-if="item.isremove"
		class="flex align-center justify-center py-3">
			<text class="font-sm text-light-muted">您撤回了一条信息</text>
		</view>
		<!-- 进群首条消息提示 -->
		<view v-if="item.type == 'systemNotice'"
		class="flex align-center justify-center py-3">
			<text class="font-sm text-light-muted">{{item.nickname + `创建了一个群聊,大家可以聊天了`}}</text>
		</view>
		<!-- 聊天内容 -->
		<view v-if="!item.isremove && item.type != 'systemNotice'"
		class="flex align-start position-relative py-3"
		:class="[!isMe ? 'justify-start' : 'justify-end']">
			<!-- 好友 -->
			<!-- 头像 -->
			...
			<!-- 气泡 -->
			<!-- 三角形 -->
			...
			<!-- 内容 -->
			<view class="flex flex-column">
				<!-- 昵称显示 -->
				<view class="position-absolute"
				style="max-width: 500rpx;top:-36rpx;"
				:style="contentStyle"
				v-if="shownickname"
				:class="[isMe ? 'right-0':'left-0']">
				   <text class="text-light-muted" style="font-size: 20rpx;">{{item.nickname}}</text>
				</view>
				<!-- 消息内容 -->
				...
			</view>
			
			<!-- 我 -->
			...
		</view>
    
	    <!-- 给服务器发消息状态 -->
		...
	
	    <!-- 弹出菜单 -->
	    ...
	
	
	</view>
</template>

<script>
	...
	export default{
		...
		props:{
			...,
			// 是否显示昵称
			shownickname:{
				type: Boolean,
				default: false,
			}
		},
		...
	}
</script>


# 2. 聊天页接收消息及标题等处理

在页面 /pages/chat/chat.nvue

<template>
	<view>
		<!-- 导航栏 -->
		<chat-navbar :title="pagetitle" ...>
            ...
		</chat-navbar>
		
		<!-- 聊天内容区域 -->
		...
		
		
		<!-- 底部聊天输入区域 --><!-- 修改:添加ref获取textarea实例 -->
		...
	
	
	    <!-- 弹出菜单 --><!-- 主要修改区域 -->
		...
		 
		<!-- 提示用户正在录音的界面 -->
		...
		 
	</view>
</template>

<script>
    ...
	export default {
		...,
		onLoad(e) {
			...
			try{
				...
				// 监听处理接收到的消息
				uni.$on('onMessage', v => {
					console.log('监听处理接收到的消息在聊天页',v);	
					// 这是私聊的判断
					/*
					if(v.from_id == this.arg.id && v.chatType == this.arg.chatType){
						this.chatDataList.push(this.formatServerMsg(v));
					}
					*/
				    if((v.from_id == this.arg.id && v.chatType == 'single') ||
					(v.chatType == 'group' && this.arg.id == v.to_id)){
						this.chatDataList.push(this.formatServerMsg(v));
					}
				});
			}catch{
				...
			}

		},
		...,
		computed:{
			...mapState({
				...
			}),
			//标题
			pagetitle(){
				if(this.arg && this.arg.name){
					// #ifdef APP || H5
					if(this.arg.name.length > 15){
						return this.arg.name.substring(0,15) + '...';
					}
					return this.arg.name;
					// #endif
					// #ifdef MP
					if(this.arg.name.length > 7){
						return this.arg.name.substring(0,7) + '...';
					}
					return this.arg.name;
					// #endif
				}
				return ``;
			},
			...
			
		},
		
		
	}
</script>

# 十三、获取离线消息(不在线的时候别人给你发的或者群里面发的消息)

此消息是通过服务器websocket推送的消息(亮点:游客因为也有token, 所以他再次上线也可以接收客服给他发的消息)

# 1. 获取离线消息接口说明

接口文档查看:二十二、获取离线消息(不在线的时候别人给你发的或者群里面发的消息)

# 2. 执行获取离线消息方法

在聊天类文件 /common/js/chatClass.js

...
// 连接成功
onOpen() {
	// 用户上线
	this.isOnline = true;
	// 获取离线消息(不在线的时候别人给你发的消息)
	this.chatGetmessageOffLine();
}
...
// 获取离线消息(不在线的时候别人给你发的消息)
chatGetmessageOffLine(){
	uni.$u.http.post(requestUrl.http + `/api/chat/chatGetmessageOffLine`, {}, {
		header: {
			token: this.user.token,
		},
	}).then(res => {
		console.log('服务器返回离线消息', res);
	});
}

# 十四、我的群聊列表

# 1. 我的群聊列表接口说明

接口文档查看:二十三、我的群聊列表

# 2. 从好友列表和我的两个页面进入群聊列表

我的好友列表页面 /pages/friendsList/friendsList.nvue

export default {
...
data() {
	return {
		...
		topMenus:[
			{icon:'man-add-fill', title:'新的朋友', url:'/pages/applyMyfriend/applyMyfriend'},
			// {icon:'tags-fill', title:'标签', url:''},
			// {icon:'chrome-circle-fill', title:'朋友圈', url:''},
			{icon:'plus-people-fill', title:'我的群聊列表', 
			url:`/pages/applyMyfriend/applyMyfriend?
			action=grouplist&title=${encodeURIComponent('我的群聊列表')}`},
		],
		...
	}
},

在我的页面 /pages/wode/wode.nvue

data() {
	return {
		...,
		uCellMenus:[
			...,
			{icon:'man-add-fill',title:'我的群聊',url:`/pages/applyMyfriend/applyMyfriend?
			action=grouplist&title=${encodeURIComponent('我的群聊列表')}`},
			...
		],
	}
},

# 3. 我的群聊列表

在页面 /pages/applyMyfriend/applyMyfriend.vue

<template>
	<view>
		<!-- 导航栏 -->
		<chat-navbar :title="title"  :fixed="true" :showPlus="false" :showUser="false" :showBack="true"
			navbarClass="bg-light" :h5WeiXinNeedNavbar="false">
		</chat-navbar>
		<!-- 新的朋友 -->
		<view class="p-3" v-if="goodfriendapply.alldata.length > 0">
		    <!-- 申请加我为好友的用户列表  -->
			<u-cell-group v-if="action == 'applyMyfriend'"
			title="申请加我为好友的用户列表" :border="false">
				<u-cell v-for="(item,index) in goodfriendapply.alldata"
				:key="index">
					<view slot="title" class="flex align-center"
					@click="openUserInfo(item,'info')">
						<u-avatar :src="item|avatarShow" shape="square"></u-avatar>
						<!-- <u--image mode="widthFix"
						:src="item|avatarShow"        
						width="40px" height="40px" radius="10rpx"></u--image> -->
						<view class="flex flex-column justify-center ml-2 mr-1">
							<text class="font-md">{{item.user.nickname || item.user.username}}</text>
							<text class="font-sm text-light-muted">{{item.nickname}}</text>
						</view>
					</view>
					<view slot="value">
						<view v-if="item.status == 'pending'"
						@click="openUserInfo(item,'doapply')">
							<u-button text="处理" type="success" size="small"></u-button>
						</view>
						<view v-else>
							<text class="font-sm text-primary ">{{item|statusText}}</text>
						</view>
					</view>
				</u-cell>
			</u-cell-group>
			<!-- 我的群聊列表 -->
			<u-cell-group v-if="action == 'grouplist'"
			title="我加入过的群聊列表" :border="false">
			     <u-cell v-for="(item,index) in goodfriendapply.alldata"
			     :key="index">
			     	<view slot="title" class="flex align-center"
			     	@click="openUserInfo(item,'chatGroup')"
					style="height: 500px;">
						<!-- 群头像组合展示 -->
						<view class="group-avatar-container">
							<view v-if="item.avatarArr && item.avatarArr.length > 0">
							    <u-avatar 
									v-for="(avatar, idx) in item.avatarArr" 
									:key="idx"
									:src="avatar" 
									shape="square"
									:size="getAvatarSize(item.avatarArr.length)"
									:customStyle="getAvatarStyle(idx, item.avatarArr.length)"
									imgMode="aspectFill"
								></u-avatar>
							</view>
						</view>
						<!-- 群昵称 -->
						<view class="flex flex-column justify-center ml-2 mr-1">
							<text class="font-md">{{item|groupname}}</text>
							<text class="font-sm text-light-muted">{{item.create_time + `加入该群聊`}}</text>
						</view>
			     	</view>
			     	<view slot="value">
			     		<view @click="openUserInfo(item,'chatGroup')">
			     			<u-button text="进群聊天" type="success" size="small"></u-button>
			     		</view>
			     	</view>
			     </u-cell>
			</u-cell-group>
			<!-- 上拉加载更多 --><!-- 正在加载中 --><!-- 没有更多数据了 -->
			<view>
				<!-- 上拉加载更多 -->
				<view v-if="!loadingIcon.show && moreData"
				class="flex align-center justify-center py-3">
					<text class="font-sm text-light-muted">上拉加载更多数据</text>
				</view>
				<!-- 正在加载中 -->
				<u-loading-icon :text="loadingIcon.text" :textSize="loadingIcon.textSize"
				:mode="loadingIcon.mode" :show="loadingIcon.show"></u-loading-icon>
				<!-- 没有更多数据了 -->
				<view v-if="!loadingIcon.show && !moreData"
				class="w-100">
					<u-divider text="我是有底线的"></u-divider>
				</view>
			</view>
		</view>
		<view v-else>
			<u-empty></u-empty>
		</view>
		
	</view>
</template>

<script>
	import toolJs from '@/common/mixins/tool.js';
	import {requestUrl} from '@/common/mixins/configData.js';
	import {mapState,mapGetters,mapMutations,mapActions} from 'vuex';
	export default {
		mixins:[toolJs],
		data() {
			return {
				page:1,
				loadingIcon:{
					text:'数据加载中',
					textSize:12,
					mode:'circle',
					show:false,
				},
				moreData:true, // 是否可加载更多数据
				action: 'applyMyfriend',
				title:'',
			}
		},
		onLoad(e) {
			console.log('我的信息',this.me);
			console.log('好友申请信息',this.goodfriendapply);
			// if(!this.me || this.me.role == 'visitor') return this.navigateBack();
			if(!this.me) return this.navigateBack();
			if(!e || !e.action){
				if(this.me.role == 'visitor') return this.navigateBack();
			}
			this.title = e.title ? decodeURIComponent(e.title) : '新的朋友';
			uni.setNavigationBarTitle({
				title: this.title
			});
			if(e.action == 'grouplist'){
				this.action = 'grouplist';
			}
		},
		computed:{
			...mapState({
				me : state => state.Chatuser.regloginUser,
				goodfriendapply : state => state.Chatuser.goodfriendapply,
			}),
		},
		filters:{
			//头像
			avatarShow(item){
				if(item && item.user && item.user.avatar){
					let avatar = item.user.avatar;
					avatar = avatar && avatar.startsWith('http') ? avatar : `${requestUrl.http}${avatar}`;
					return avatar;
				}else if(item && item.avatar){
					let avatar = item.avatar;
					// 如果是群聊且包含逗号,取第一个头像
					if(avatar && avatar.indexOf(',') > 0){
						avatar = avatar.split(',')[0];
						avatar = avatar && avatar.startsWith('http') ? avatar : `${requestUrl.http}${avatar}`;
						return avatar;
					}
				}
				return ``;
			},
			// 处理状态的显示
			statusText(item){
				let status = item && item.status;
				let text = {
					agree:'已同意',
					refuse:'已拒绝',
					ignore:'已忽略'
				}
				return text[status];
			},
			// 群名称
			groupname(item){
				if(item){
					return item.name && item.name.length > 10 ? 
					item.name.substring(0,10) + `...` : item.name;
				}
				return ``;
			}
		},
		// 监听用户下拉
		onPullDownRefresh() {
			this.getNewData();
		},
		//监听触底
		onReachBottom() {
			console.log('拉到底部了');
			this.loadMore();
		},
		onShow() {
			this.getNewData();
		},
		methods: {
			// 获取最新数据
			getNewData(){
				this.page = 1;
				const limit = 3;
				if(this.action == 'applyMyfriend'){
					this.$store.dispatch('getGoodfriendapply',{ page:this.page, limit:limit }).then(res=>{
						uni.stopPullDownRefresh();
						// uni.showToast({title: '刷新数据成功',icon: 'none'});
					});
				}else if(this.action == 'grouplist'){
					this.getGroupList(this.page,limit).then(list => {
						console.log('获取群列表最新数据',list);
						this.goodfriendapply.alldata = list;
						this.moreData = true;
					});
				}
				
			},
			// 获取群列表
			getGroupList(page,limit){
				return new Promise((resolve,reject)=>{
					uni.$u.http.get(requestUrl.http + `/api/chat/grouplist/${page}`, {
						params:{
							limit:limit
						},
						header: {
							token: this.me.token,
						},
					}).then(res => {
						console.log('服务器返回的群列表数据', res);
						let list = res.data.data;
						if(list.length == 0) return resolve([]);
						// 处理群头像数据
						list.forEach(item => {
							// 确保avatar存在
							if (!item.avatar) {
								item.avatarArr = [];
								return;
							}
							// 拆分头像字符串
							let avatars = item.avatar.split(',');
							// 处理每个头像URL
							item.avatarArr = avatars.filter(avatar => avatar && avatar.trim() !== '') // 过滤空值
											.slice(0, 4) // 最多取4个
											.map(avatar => {
												// 处理相对路径
												if (avatar.startsWith('/')) {
													return requestUrl.http + avatar;
												}
												// 处理可能缺少协议的URL
												if (!avatar.startsWith('http')) {
													return requestUrl.http + '/' + avatar;
												}
												return avatar;
											});
						    resolve(list);
						});
					}).finally(()=>{
						uni.stopPullDownRefresh();
					});
				});
			},
			// 获取头像大小
			getAvatarSize(count) {
				if (count === 1) return 40;
				return 18; // 多个头像时使用小尺寸
			},
			// 获取头像样式
			getAvatarStyle(index, count) {
				if (count === 1) {
					return {};
				}
				
				// 位置计算
				const positions = {
					2: [
						{ top: '0', left: '0' }, 
						{ top: '22px', left: '22px' }
					],
					3: [
						{ top: '0', left: '11px' }, 
						{ top: '22px', left: '0' }, 
						{ top: '22px', left: '22px' }
					],
					4: [
						{ top: '0', left: '0' }, 
						{ top: '0', left: '22px' }, 
						{ top: '22px', left: '0' }, 
						{ top: '22px', left: '22px' }
					]
				};
				
				const style = {
					position: 'absolute',
					'z-index': 1,
					'border': '1px solid #ffffff'
				};
				
				if (positions[count] && positions[count][index]) {
					return Object.assign(style, positions[count][index]);
				}
				
				return style;
			},
			//触底加载下一页
			loadMore(){
				if(this.moreData){
					this.loadingIcon.show = true;
					const limit = 3;
					this.page ++;
					if(this.action == 'applyMyfriend'){
						this.$store.dispatch('getGoodfriendapply',{ page:this.page, limit:limit })
						.then(res=>{
							console.log('页面显示是否有下一页',this.goodfriendapply.moredata);
							this.moreData = this.goodfriendapply.moredata;
							console.log('看一下此时的加载状态',this.goodfriendapply.loadingStatus);
							this.loadingIcon.show = this.goodfriendapply.loadingStatus;
							if(!this.moreData){
								this.loadingIcon.show = false;
							}
						});
					}else if(this.action == 'grouplist'){
						console.log('执行下拉加载更多');
						this.getGroupList(this.page,limit).then(list => {
							console.log('获取群列表加载更多数据',list);
							this.goodfriendapply.alldata = [...this.goodfriendapply.alldata, ...list];
							this.loadingIcon.show = false;
							if(this.goodfriendapply.alldata.length < this.page * limit){
								this.moreData = false;
							}
							
						});
					}
				}
			},
			openUserInfo(item,action){
				if(action == 'chatGroup'){
					console.log('进群聊天',item);
					// 进入聊天页 传一下用户的信息过去在页面展示
					const userchat = {
						id: item.id,
						name: item.name,
						avatar: item.avatar,
						chatType: 'group', // 单聊 single 群聊 group
					};
					uni.navigateTo({
						url: `/pages/chat/chat?arg=${encodeURIComponent(JSON.stringify(userchat))}`,
					});
				}else{
					uni.navigateTo({
						url: `/pages/userinfo/userinfo?uuid=${item.user.uuid}&action=${action}`,
					});
				}
				
			},
		}
	}
</script>

<style>
.group-avatar-container {
	position: relative;
	width: 40px;
	height: 40px;
	border-radius: 10rpx;
	overflow: hidden;
	background-color: #f5f5f5;
}
</style>

# 十五、获取群资料信息进行群设置

# 1. 获取群资料信息接口说明

接口文档查看:二十四、获取群资料信息

# 2. 获取群资料并展示群用户头像

在页面 /pages/setpageInfo/setpageInfo.vue

<template>
	<view>
		<!-- 导航栏 -->
		...
		<!-- 内容 -->
		<view>
			<!-- 聊天相关 -->
			<view v-if="setdata.action == 'chatset'">
				...
			</view>
			<!-- 聊天页设置 -->
			<view v-if="setdata.action == 'chatpageset'">
				<view v-if="chatpageset.id && chatpageset.chatType">
					<!-- 头像部分 -->
					<view class="flex flex-wrap p-3">
						<!-- 单聊 -->
						<view v-if="chatpageset.chatType == 'single'"
						class="flex flex-column align-center justify-center mr-2">
							<u-avatar :src="chatpageset|avatarShow" shape="square"
							customStyle="border:1px solid #eeeeee;" :size="36"></u-avatar>
							<text class="font-small mt-1 text-muted">{{chatpageset.name}}</text>
						</view>
						<!-- 群聊 循环头像昵称 -->
						<view v-if="chatpageset.chatType == 'group'"
						class="flex flex-row flex-wrap">
							<view v-for="(item,index) in chatpageset.users" :key="index"
							class="flex flex-column align-center justify-center mr-1 mb-2">
								<u-avatar :src="item|avatarShow" shape="square"
								customStyle="border:1px solid #eeeeee;" :size="36"></u-avatar>
								<text class="font-small mt-1 text-muted">{{item|groupusername}}</text>
							</view>
						</view>
						<!-- 加入群聊 -->
						<view class="..."
						style="width: 36px;height: 36px;border-style: dashed;"
						@click="clickCell('选择好友', 'addUserToGroup')">
						   <u-icon name="plus" size="24" color="#999999"></u-icon>
						</view>
						<!-- 删除群聊用户 -->
						<view v-if="chatpageset.chatType == 'group'"
						class="border flex align-center justify-center ml-1"
						style="width: 36px;height: 36px;border-style: dashed;"
						@click="clickCell('群组删除某个用户', 'deleteUserFromGroup')">
						   <u-icon name="minus" size="24" color="#999999"></u-icon>
						</view>
					</view>
					<u-gap height="10" bgColor="#EDEDED"></u-gap>
					...
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	...
	export default {
		mixins:[toolJs],
		data() {
			return {
				...,
				chatpageset:{
					users:[],
				},
			}
		},
		onLoad(e) {
			...
			// 动态生成数据
			if(e.action){
				this.setdata.action = e.action;
				if(this.setdata.action == 'chatset'){
					...
				}else if(this.setdata.action == 'chatpageset'){
					...
					console.log('设置相关', this.chatpageset);
					//群聊设置
					if(this.chatpageset.chatType == 'group'){
						uni.$u.http.get(requestUrl.http + 
						`/api/chat/groupinfo/${this.chatpageset.id}`,{
							header:{
								token: this.me.token,
							}
						}).then(res =>{
							console.log('服务器返回群相关信息',res);
							let info = res.data.data;
							this.chatpageset.user_id = info.user_id;
							this.chatpageset.remark = info.remark;
							this.chatpageset.invite_confirm = info.invite_confirm;
							this.chatpageset.users = info.group_users;
							//console.log('显示到页面的群信息',this.chatpageset);
							this.chatpageset.user_id = 
							this.chatpageset.user_id == this.me.id ? true : false;
							console.log('显示到页面的群信息',this.chatpageset);
							
						});
					}
				}
			}
		},
		filters:{
			//头像
			avatarShow(item){
				if(item){
					if(item.avatar){
						return item.avatar.startsWith('http') ? item.avatar : 
						`${requestUrl.http}${item.avatar}`;
					}
					return item.user.avatar.startsWith('http') ? item.user.avatar : 
					`${requestUrl.http}${item.user.avatar}`;
				}
				return ``;
			},
			// 群用户称呼
			groupusername(item){
				if(item){
					if(item.nickname){
						return  item.nickname.length > 3 ? 
						item.nickname.substring(0,3) + `...` : item.nickname;
					}
					if(item.user.nickname){
						return  item.user.nickname.length > 3 ? 
						item.user.nickname.substring(0,3) + `...` : item.user.nickname;
					}
					if(item.user.username){
						return  item.user.username.length > 3 ? 
						item.user.username.substring(0,3) + `...` : item.user.username;
					}
				}
				return ``;
			}
		},
		methods: {
			...,
			clickCell(title, type){
				console.log(title, type);
				if(type == 'addUserToGroup'){
					// 单独建群
					if(this.chatpageset.chatType == 'single'){
						let op = {
							action: 'addUserToGroup',
							title: encodeURIComponent(title),
						};
						uni.navigateTo({
							url: `/pages/friendsList/friendsList?action=${op.action}&title=${op.title}`,
						});
					}else if(this.chatpageset.chatType == 'group'){
						console.log('往当前群里拉人')
					}
				}
			},
		}	
	}
</script>

# 十六、修改群名称

# 1. 修改群名称接口说明

接口文档查看:二十五、修改群名称(成功后通过webSocket通知群聊用户)

# 2. 页面布局

在页面 /pages/setpageInfo/setpageInfo.vue

<template>
	<view>
		<!-- 导航栏 -->
		...
		<!-- 内容 -->
		<view>
			<!-- 聊天相关 -->
			<view v-if="setdata.action == 'chatset'">
				...
			</view>
			<!-- 聊天页设置 -->
			<view v-if="setdata.action == 'chatpageset'">
				<view v-if="chatpageset.id && chatpageset.chatType">
					<!-- 头像部分 -->
					<view class="flex flex-wrap p-3">
						...
					</view>
					<u-gap height="10" bgColor="#EDEDED"></u-gap>
					<!-- 群相关项目 -->
					<view v-if="chatpageset.chatType == 'group'">
						<!-- 群聊名称 -->
						<u-cell-group>
							<u-cell>
								<view slot="title">
									<view class="mb-2">
										<text class="font-weight-bold">群名称</text>
									</view>
									<u-input v-model="chatpageset.name"
									border="false" :maxlength="20"
									:adjustPosition="false" clearable
									placeholder="群名称最多20个字"
									@confirm="groupUpdateName"
									:disabled="!chatpageset.user_id"></u-input>
								</view>
							</u-cell>
						</u-cell-group>
						<u-gap height="10" bgColor="#EDEDED"></u-gap>
						<!-- 群公告 -->
						<u-cell-group>
							<u-cell>
								<view slot="title">
									<view class="mb-2">
										<text class="font-weight-bold">群公告</text>
									</view>
									<u-textarea v-model="chatpageset.remark"
									border="false" placeholder="群公告设置" 
									:adjustPosition="false"
									:maxlength="200" autoHeight count></u-textarea>
								</view>
							</u-cell>
						</u-cell-group>
						<u-gap height="10" bgColor="#EDEDED"></u-gap>
					</view>
					
					{# 查找聊天内容 #}
					...
					<!-- 消息设置 -->
					...
					
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	...
	export default {
		...,
		onLoad(e) {
			...
			// 动态生成数据
			if(e.action){
				this.setdata.action = e.action;
				if(this.setdata.action == 'chatset'){
					...
				}else if(this.setdata.action == 'chatpageset'){
					...
					//群聊设置
					if(this.chatpageset.chatType == 'group'){
						uni.$u.http.get(requestUrl.http + 
						`/api/chat/groupinfo/${this.chatpageset.id}`,{
							header:{
								token: this.me.token,
							}
						}).then(res =>{
							...
							//console.log('显示到页面的群信息',this.chatpageset);
							this.chatpageset.user_id = 
							this.chatpageset.user_id == this.me.id ? true : false;
							console.log('显示到页面的群信息',this.chatpageset);
							
						});
					}
				}
			}
		},
		...,
		methods: {
			// 更新群名称
			groupUpdateName(){
				uni.$u.http.post(requestUrl.http + `/api/chat/groupUpdateName`, {
					id: this.chatpageset.id,
					name: this.chatpageset.name,
				}, {
					header: {
						token: this.me.token,
					},
				}).then(res => {
					console.log('服务器返回群更新结果', res);
					if(res.data.data == 'ok'){
						uni.showToast({title: '群名称修改成功', icon:'none'});
						// 全局通知更新群名称(nvue页面H5端页面标题有兼容问题)
						// uni.$emit('groupUpdateName',{
						// 	name: this.chatpageset.name,
						// });
						// 使用vuex兼容H5端nvue页面标题
						this.$store.dispatch('groupUpdateName',{
							name: this.chatpageset.name,
						});
						
					}
				}).catch(err =>{
					console.log('群名称更新失败',err);
					uni.showModal({
						content: err.data.data,
						showCancel:false,
						confirmText:'我知道了'
					});
				});
			},
			...
		}
	}
</script>

# 3. 更新群名称完成之后返回聊天页修改导航栏群名称和页面名称

采用vuex方式修改群名称(全局通知更新群名称(nvue页面H5端页面标题有兼容问题)) 在文件 /store/modules/chatuser.js

export default {
	state: {
		...
		// 群名称更新处理
		groupname:'',
	},
	actions: {
        ...  
		// 更新群名称
		groupUpdateName({commit,state,dispatch}, payload = {}){
			state.groupname = payload.name;
		},
	},
}

# 4. 聊天页更新群名称

在页面 /pages/chat/chat.nvue

<template>
	<view>
		... 
	</view>
</template>

<script>
    ...
	export default {
		...,
		onLoad(e) {
			...
			try{
				...
				// 监听处理接收到的消息
				...
				
				// 监听更新群名称
				// uni.$on('groupUpdateName', res => {
				// 	this.groupUpdateName(res);
				// });
				// uni.$on('groupUpdateName', this.groupUpdateName);
				
				
			}catch{
				...
			}

		},
		onShow() {
			this.arg.name = this.groupname;
			// #ifdef H5
			document.title = this.groupname;
			// #endif
		},
		destroyed() {
			//销毁聊天对象信息
			this.chatClass.destroyChatToObject();
			//销毁监听更新群名称
			// uni.$off('groupUpdateName', this.groupUpdateName);
		},
		computed:{
			...mapState({
				...,
				groupname :state=>state.Chatuser.groupname,
			}),
			//标题
			pagetitle(){
				if(this.arg && this.arg.name){
					// #ifdef H5 || APP
					if(this.arg.name.length > 15){
						return this.arg.name.substring(0,15) + '...';
					}
					return this.arg.name;
					// #endif
					// #ifdef MP
					if(this.arg.name.length > 5){
						return this.arg.name.substring(0,5) + '...';
					}
					return this.arg.name;
					// #endif
				}
				return ``;
			},
			chatContentStyle(){
				...
				//如果是h5端用微信打开并且不需要导航栏的时候
				if(this.isWeixinBrowser() && !this.h5WeiXinNeedNavbar){
					...
					uni.setNavigationBarTitle({
						title: this.pagetitle,
					});
				}
			},
			...
		},
		methods: {
			...,
			// 更新聊天页群名称
			groupUpdateName(e){
				console.log('更新聊天页群名称',e);
				// 不要直接给计算属性赋值,计算属性默认只有getter,没有setter
				// 修改计算属性所依赖的数据
				// this.pagetitle = e.name;
				// 更新arg中的name,这样计算属性pagetitle会自动更新
				this.arg.name = e.name;
				// 同时更新页面标题
				console.log('页面标题',this.pagetitle);
				uni.setNavigationBarTitle({
					title: this.groupname || this.pagetitle, 
				});
				
			},
		},
		
	}
</script>

# 5. 群名称修改完成之后,通知其他群成员

在组件 /components/chat-item/chat-item.vue

<!-- 进群首条消息提示 -->
<view v-if="item.type == 'systemNotice'"
class="flex flex-row align-center justify-center py-3">
	<text class="font-sm text-light-muted">{{item.data}}</text>
</view>

# 十七、修改群公告

内容较多,在新页面查看:聊天通讯群组更多内容

更新时间: 2025年8月21日星期四晚上6点58分