# 一、和我聊天设置(聊天条数限制)
# 1. 入口设置
除了可以在设置进入外,后端也把设置入口推送给了游客和登录用户,在首页就可以看到。
# 2. 功能实现
# ① 在聊天页点击用户头像进入
在组件 /components/chat-item/chat-item.vue
<!-- 聊天内容 -->
<view ...>
<!-- 好友 -->
<!-- 头像 -->
<u--image v-if="!isMe"
:src="item.avatar"
mode="widthFix"
width="80rpx" height="80rpx" radius="10rpx"
@click="clickAvatar('notme')"></u--image>
<!-- 气泡 -->
<!-- 三角形 -->
...
<!-- 聊天内容主体 -->
...
<!-- 我 -->
<text v-if="isMe && needQipaoClass"
class="iconfont font-md chat-right-icon"></text>
<u--image v-if="isMe"
:src="item.avatar"
mode="widthFix"
width="80rpx" height="80rpx" radius="10rpx"
@click="clickAvatar('me')"></u--image>
</view>
...
methods:{
//点击头像
clickAvatar(who){
if(who == 'me'){
uni.switchTab({
url:'/pages/wode/wode'
});
}else{
uni.redirectTo({
url:`/pages/userinfo/userinfo?uuid=${this.item.sendUUid}`,
});
}
},
...
},
# ② 新建混入文件 /pages/userinfo/sendMessage.js
export default{
methods:{
//发消息
sendMessageFun(){
console.log('发消息的逻辑',this.me);
if(this.me.ismygoodfriend){
...
}else{
const role = this.me.role; //'visitor' | 'user'
let userset = this.user.userset;
console.log('看一下userset',userset);
if(!userset){
// 用户没有进行聊天的设置
userset = new Object();
userset.chatset = {
visitor: {
sendCount:1,
needFollow:false,
},
user:{
sendCount:1,
needFollow:false,
}
}
}else{
userset = JSON.parse(userset);
}
console.log('看一下userset',userset);
const { sendCount, needFollow } = userset.chatset[role];
// 1. 检查是否需要关注
// if(needFollow && this.me.hasFollowed){
// console.log('请先关注对方才能发消息');
// return; // 程序结束
// }
// 2. 根据对方设置的聊天权限处理
switch(sendCount){
case 0:
...
break;
case 1:
console.log('可以发一条消息');
this.btn.clicktype = 'sendOne';
this.btn.text = '发消息';
console.log('去聊天页',this.user);
// uni.navigateTo({
// url: '/pages/chat/chat',
// });
this.groupOrSingleChatRedirect('single', {
id: this.user.id,
name: this.user.nickname || this.user.username,
avatar: this.user.avatar,
chatType: 'single',
});
break;
default:
console.log('发消息不受限制');
this.groupOrSingleChatRedirect('single', {
id: this.user.id,
name: this.user.nickname || this.user.username,
avatar: this.user.avatar,
chatType: 'single',
});
break;
}
}
},
// 进群|单聊聊天跳转
groupOrSingleChatRedirect(chatType = 'single', item = {}){
// 进入聊天页 传一下用户的信息过去在页面展示
const userchat = {
id: item.id || this.chatpageset.id,
name: item.name || this.chatpageset.name,
avatar: item.avatar || this.chatpageset.avatar,
chatType: item.chatType || chatType, // 单聊 single 群聊 group
};
uni.navigateTo({
url: `/pages/chat/chat?arg=${encodeURIComponent(JSON.stringify(userchat))}`,
});
},
},
}
# ③ 聊天设置页判断是否显示删除好友按钮
在页面 /pages/setpageInfo/setpageInfo.vue
<!-- 聊天页设置 -->
<view v-if="setdata.action == 'chatpageset'">
<view v-if="chatpageset.id && chatpageset.chatType">
<!-- 头像部分 -->
<view class="flex flex-wrap p-3">
<!-- 单聊 -->
...
<!-- 群聊 循环头像昵称 -->
...
<!-- 加入群聊 -->
<view v-if="!(me && me.role == 'visitor')"
class="border flex align-center justify-center"
style="width: 36px;height: 36px;border-style: dashed;"
@click="clickCell('选择好友', 'addUserToGroup')">
<u-icon name="plus" size="24" color="#999999"></u-icon>
</view>
<!-- 删除群聊用户 -->
...
</view>
<u-gap height="10" bgColor="#EDEDED"></u-gap>
<!-- 群相关设置 -->
...
<!-- 查找聊天内容 -->
...
<!-- 消息设置 -->
...
<!-- 解散群或退群或者删除好友 -->
<view v-if="isShowDeleteOrQuit">
<view class="p-3">
<u-button type="primary" plain :text="deleteOrQuitGroup"
@click="deleteOrQuitGroupfn"></u-button>
</view>
<u-gap height="10" bgColor="#EDEDED"></u-gap>
</view>
</view>
</view>
...
data() {
return {
...
chatpageset:{
...
ismygoodfriend: false, // 是否是我的好友
},
}
},
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 == 'single'){
this.ismygoodfriend();
}
}else if(this.setdata.action == 'groupQrcode'){
...
}else if(this.setdata.action == 'autoAddGroup'){
...
}else if(this.setdata.action == 'userinfoSet'){
...
}else if(this.setdata.action == 'avatarCut'){
...
}else if(this.setdata.action == 'deleteUserFromGroup'){
...
}else if(this.setdata.action == 'addUserFromGroup'){
...
}else if(this.setdata.action == 'applyAddGroup'){
...
}else if(this.setdata.action == 'applyAddMeFriendSet'){
...
}
}
},
computed:{
...,
// 是否显示删除该好友按钮
isShowDeleteOrQuit(){
let show = true;
if(this.chatpageset.chatType == 'single'){
if(!this.chatpageset.ismygoodfriend){
show = false;
}
}
return show;
},
},
methods: {
// 查询某个用户是否我的好友
ismygoodfriend(){
// 我的信息
// console.log('此时的this.me',this.me);return;
uni.$u.http.post(requestUrl.http + `/api/chat/ismygoodfriend/${this.chatpageset.id}`,{},{
header:{
token: this.me.token,
}
}).then(res => {
console.log('是不是我的好友',res.data.data);
if(res.data.msg == 'ok'){
this.chatpageset.ismygoodfriend = res.data.data;
}
}).catch(err => {
console.log('不是好友');
this.chatpageset.ismygoodfriend = false;
});
},
...
},
# 二、使用场景体验
说明:
- 我们本套课程
即时通讯项目,首先是兼容多端:微信小程序、H5、APP,我们在开发的时候,也是多端同时开发,大家有所体验。 即时通讯作为现代前端项目中,非常重要的项目,其适合的场景非常的广泛,如:外卖、商城、聊天、视频聊天、点餐、物流、社区、教育、游戏、直播、招聘、培训等等,只要有用户参与的项目,这个功能都是必不可少的,你也可以将其作为你项目的亮点来说明。- 我们本套课程将
即时通讯的主要基础功能已经全部开发完成了,你可以把我们当前的代码嵌入到任何一个项目中,作为基础功能来使用,当然,你也可以根据你的项目需求,进行二次开发,增加更多功能,如:语音聊天、视频聊天、外卖、商城、点餐、物流、社区、教育、游戏、直播、招聘、培训等等,这些功能,我们也会在后续的课程中,进行讲解。 - 接下来,老师讲一个最简单的使用场景给大家体验一下。
# ① 将我们的即时通讯嵌入到某个项目中
在某个项目中,给一个链接即可
新建一个页面 /pages/case/case.vue
<template>
<view class="chat-container">
<!-- PC端布局 -->
<view v-if="!isMobile" class="pc-layout">
<!-- 左侧聊天列表 -->
<view class="chat-list-panel" :style="{width: leftPanelWidth + 'px'}">
<view class="resize-handle" @mousedown="startResize"></view>
<view class="panel-header">
<text class="title">聊天列表</text>
</view>
<scroll-view class="chat-list" scroll-y>
<view
v-for="(item, index) in chatList"
:key="index"
class="chat-item"
:class="{active: activeChatIndex === index}"
@click="selectChat(index)"
>
<image class="avatar" :src="item.avatar" mode="aspectFill"></image>
<view class="chat-info">
<text class="name">{{ item.name }}</text>
<text class="desc">{{ item.desc }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 右侧聊天界面 -->
<view class="chat-content-panel">
<!-- 使用iframe替代web-view,避免页面跳转 -->
<iframe
v-if="activeChatUrl && !isMobile"
:src="getFullUrl(activeChatUrl)"
class="chat-iframe"
frameborder="0"
></iframe>
<view v-else class="empty-chat">
<text>请选择聊天对象</text>
</view>
</view>
</view>
<!-- 移动端布局 -->
<view v-else class="mobile-layout">
<!-- 移动端使用web-view,但需要处理路径 -->
<view v-if="activeChatUrl" class="mobile-chat-content">
<iframe
:src="getFullUrl(activeChatUrl)"
class="mobile-chat-iframe"
frameborder="0"
></iframe>
</view>
<view v-else class="empty-chat-mobile">
<text>暂无聊天</text>
</view>
<!-- 移动端返回按钮 -->
<view v-if="isMobile && activeChatUrl" class="mobile-back" @click="showChatList = true">
<text>返回列表</text>
</view>
<!-- 移动端聊天列表 -->
<view v-if="isMobile && showChatList" class="mobile-chat-list">
<view class="list-header">
<text class="title">聊天列表</text>
<text class="close-btn" @click="showChatList = false">关闭</text>
</view>
<scroll-view class="chat-list-mobile" scroll-y>
<view
v-for="(item, index) in chatList"
:key="index"
class="chat-item-mobile"
@click="selectChatMobile(index)"
>
<image class="avatar-mobile" :src="item.avatar" mode="aspectFill"></image>
<view class="chat-info-mobile">
<text class="name-mobile">{{ item.name }}</text>
<text class="desc-mobile">{{ item.desc }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
chatList: [
{
chatType: 'single',
name: '睿晨编程客服老师',
avatar: 'https://thinkphp-eggjs.oss-cn-hangzhou.aliyuncs.com/images/20250924/1758697243774_0e5a2764831b.png',
desc: '跟课程相关的问题,可以咨询老师',
path: 'http://192.168.2.6:8081/#/pages/setpageInfo/setpageInfo',
params: {
action: 'autoAddGroup',
title: '加用户[睿晨编程客服老师]为好友',
id: '49',
chatType: 'single',
name: '睿晨编程客服老师',
avatar: 'https://thinkphp-eggjs.oss-cn-hangzhou.aliyuncs.com/images/20250924/1758697243774_0e5a2764831b.png',
redirect: 'true'
}
},
{
chatType: 'group',
name: '第三期第二季课程群',
avatar: 'https://thinkphp-eggjs.oss-cn-hangzhou.aliyuncs.com/images/20250924/1758697243774_0e5a2764831b.png',
desc: '学习第三期第二季课程的同学,可在群里进行问题答疑',
path: 'http://192.168.2.6:8081/#/pages/setpageInfo/setpageInfo',
params: {
action: 'autoAddGroup',
title: '群介绍',
id: '41',
chatType: 'group',
name: '第三期第二季课程群',
redirect: 'true'
}
}
],
isMobile: false,
activeChatIndex: 0,
leftPanelWidth: 450, // 默认宽度
isResizing: false,
showChatList: false, // 移动端是否显示聊天列表
baseUrl: '', // 基础URL
startX: 0, // 拖动起始位置
startWidth: 0 // 拖动起始宽度
}
},
computed: {
activeChatUrl() {
if (this.chatList.length > 0 && this.activeChatIndex >= 0) {
const chat = this.chatList[this.activeChatIndex];
console.log('看一下完整的url',this.buildUrl(chat.path, chat.params));
return this.buildUrl(chat.path, chat.params);
}
return '';
}
},
mounted() {
this.checkDeviceType();
window.addEventListener('resize', this.checkDeviceType);
// 获取基础URL
this.getBaseUrl();
if (this.chatList.length > 0) {
this.activeChatIndex = 0;
}
},
beforeDestroy() {
window.removeEventListener('resize', this.checkDeviceType);
},
methods: {
// 检查设备类型
checkDeviceType() {
const screenWidth = window.innerWidth;
this.isMobile = screenWidth < 750;
// 如果是PC端,根据屏幕大小调整左侧面板宽度
if (!this.isMobile) {
this.adjustPanelWidth();
}
},
// 根据屏幕大小调整面板宽度
adjustPanelWidth() {
const screenWidth = window.innerWidth;
// 确保左侧面板宽度在合理范围内
const minWidth = 350;
const maxWidth = 450;
if (this.leftPanelWidth < minWidth) {
this.leftPanelWidth = minWidth;
} else if (this.leftPanelWidth > maxWidth) {
this.leftPanelWidth = maxWidth;
}
},
// 获取基础URL
getBaseUrl() {
// 在H5环境中,获取当前页面的基础URL
if (typeof window !== 'undefined') {
this.baseUrl = window.location.origin;
// 如果是开发环境,可能需要特殊处理
if (process.env.NODE_ENV === 'development') {
// 开发环境的基础路径处理
const pathname = window.location.pathname;
const paths = pathname.split('/');
if (paths.length > 1) {
this.baseUrl += '/' + paths[1];
}
}
}
},
// 构建完整URL
buildUrl(path, params) {
if (!path) return '';
// 构建参数字符串
let paramStr = '';
if (params) {
paramStr = '?' + Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&');
}
console.log('看一下右侧完整的url', path + paramStr);
return path + paramStr;
},
// 获取完整的URL(用于iframe)
getFullUrl(url) {
if (!url) return '';
// 如果是完整URL,直接返回
if (url.startsWith('http')) {
return url;
}
// 如果是相对路径,转换为绝对路径
if (url.startsWith('/')) {
return this.baseUrl + url;
}
// 其他情况直接返回
return url;
},
// 选择聊天(PC端)
selectChat(index) {
this.activeChatIndex = index;
},
// 选择聊天(移动端)
selectChatMobile(index) {
this.activeChatIndex = index;
this.showChatList = false; // 关闭聊天列表
},
// 开始调整左侧面板宽度
startResize(e) {
this.isResizing = true;
this.startX = e.clientX;
this.startWidth = this.leftPanelWidth;
// 添加全局样式,改善拖动体验
document.body.style.userSelect = 'none';
document.body.style.cursor = 'col-resize';
document.addEventListener('mousemove', this.handleResize);
document.addEventListener('mouseup', this.stopResize);
e.preventDefault();
},
// 处理调整宽度
handleResize(e) {
if (!this.isResizing) return;
const deltaX = e.clientX - this.startX;
const newWidth = this.startWidth + deltaX;
// 设置宽度限制
const minWidth = 350;
const maxWidth = 450;
if (newWidth >= minWidth && newWidth <= maxWidth) {
this.leftPanelWidth = newWidth;
} else if (newWidth < minWidth) {
this.leftPanelWidth = minWidth;
} else if (newWidth > maxWidth) {
this.leftPanelWidth = maxWidth;
}
},
// 停止调整宽度
stopResize() {
this.isResizing = false;
// 恢复全局样式
document.body.style.userSelect = '';
document.body.style.cursor = '';
document.removeEventListener('mousemove', this.handleResize);
document.removeEventListener('mouseup', this.stopResize);
}
}
}
</script>
<style lang="scss" scoped>
.chat-container {
width: 100%;
height: 100vh;
overflow: hidden;
background-color: #f5f5f5;
}
/* PC端布局样式 */
.pc-layout {
display: flex;
width: 100%;
height: 100vh;
background-color: #ffffff;
overflow: hidden;
position: relative;
}
/* 屏幕宽度大于1000px时,整个聊天区域居中,最大宽度1000px */
@media (min-width: 1000px) {
.pc-layout {
width: 1000px;
max-width: 1000px;
height: 90vh;
margin: 5vh auto;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
}
/* 超大屏幕优化 */
@media (min-width: 1600px) {
.pc-layout {
height: 85vh;
margin: 7.5vh auto;
}
}
.chat-list-panel {
width: 450px;
min-width: 350px;
max-width: 450px;
background-color: #f8f9fa;
border-right: 1px solid #e0e0e0;
display: flex;
flex-direction: column;
position: relative;
transition: width 0.1s ease;
}
.resize-handle {
position: absolute;
right: -2px;
top: 0;
width: 4px;
height: 100%;
background-color: transparent;
cursor: col-resize;
z-index: 10;
transition: background-color 0.2s;
}
.resize-handle:hover,
.resize-handle:active {
background-color: #007AFF;
width: 6px;
right: -3px;
}
.panel-header {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
background-color: #ffffff;
flex-shrink: 0;
.title {
font-size: 18px;
font-weight: 600;
color: #333333;
}
}
.chat-list {
flex: 1;
overflow-y: auto;
}
.chat-item {
display: flex;
padding: 15px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: #f0f0f0;
}
&.active {
background-color: #e6f7ff;
border-left: 3px solid #007AFF;
}
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 12px;
flex-shrink: 0;
}
.chat-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
}
.name {
font-size: 16px;
font-weight: 500;
color: #333333;
margin-bottom: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.desc {
font-size: 14px;
color: #666666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-content-panel {
flex: 1;
display: flex;
min-width: 0; /* 允许缩小 */
overflow: hidden;
background-color: #f0f2f5;
/* 右侧聊天区域固定宽度550px */
width: 550px;
min-width: 550px;
max-width: 550px;
}
.chat-iframe {
width: 100%;
height: 100%;
border: none;
min-width: 550px;
}
.empty-chat {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 16px;
color: #999999;
width: 100%;
}
/* 移动端布局样式 */
.mobile-layout {
width: 100%;
height: 100vh;
position: relative;
}
.mobile-chat-content {
width: 100%;
height: 100%;
}
.mobile-chat-iframe {
width: 100%;
height: 100%;
border: none;
}
.empty-chat-mobile {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 16px;
color: #999999;
}
.mobile-back {
position: absolute;
top: 10px;
left: 10px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 8px 12px;
border-radius: 4px;
z-index: 100;
font-size: 14px;
cursor: pointer;
}
.mobile-chat-list {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: white;
z-index: 200;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #e0e0e0;
background-color: #f8f9fa;
.title {
font-size: 18px;
font-weight: 600;
}
.close-btn {
color: #007AFF;
font-size: 16px;
cursor: pointer;
}
}
.chat-list-mobile {
height: calc(100% - 60px);
}
.chat-item-mobile {
display: flex;
padding: 15px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
}
.avatar-mobile {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 12px;
}
.chat-info-mobile {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
}
.name-mobile {
font-size: 16px;
font-weight: 500;
margin-bottom: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.desc-mobile {
font-size: 14px;
color: #666666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 响应式调整 */
@media (max-width: 750px) {
.pc-layout {
display: none;
}
}
@media (min-width: 751px) {
.mobile-layout {
display: none;
}
}
/* 中等屏幕适配(751px-999px) */
@media (min-width: 751px) and (max-width: 999px) {
.pc-layout {
height: 100vh;
box-shadow: none;
border-radius: 0;
width: 100%;
max-width: 100%;
}
.chat-list-panel {
width: 40%;
min-width: 300px;
max-width: 400px;
}
.chat-content-panel {
width: 60%;
min-width: auto;
max-width: none;
flex: 1;
}
.chat-iframe {
min-width: auto;
}
}
/* 小屏幕PC适配(751px-850px) */
@media (min-width: 751px) and (max-width: 850px) {
.chat-list-panel {
width: 35%;
min-width: 250px;
max-width: 300px;
}
.chat-content-panel {
width: 65%;
}
}
</style>
# ② 配置文件
在文件 /pages.json, 我们这里做一个简单场景,对H5端开发一个适用PC端和移动端开发的页面
// #ifdef H5
,{
"path" : "pages/case/case",
"style" :
{
"navigationBarTitleText" : "客服中心",
"navigationStyle": "custom"
}
}
// #endif