# 一、顶部导航栏开发
- 新建即时通讯聊天页
pages/chat/chat.nvue在pages.json中
{
"path" : "pages/chat/chat",
"style" :
{
"navigationBarTitleText" : "聊天详情",
"navigationStyle": "custom"
}
}
- 进入聊天即时通讯聊天页
pages/xiaoxi/xiaoxi.nvue
...
methods: {
...
openChat(e) {
console.log('进入聊天详情页', e);
uni.navigateTo({
url: '/pages/chat/chat',
});
},
...
}
...
在页面 pages/chat/chat.nvue
<template>
<view>
<!-- 导航栏 -->
<chat-navbar title="聊天" :fixed="true"
:showPlus="false" :showUser="false"
:showBack="true" navbarClass="bg-light">
<chat-navbar-icon-button slot="right"
@click="openMore" >
<text class="iconfont font-md"></text>
</chat-navbar-icon-button>
</chat-navbar>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
openMore(){
console.log('点击打开更多');
}
}
}
</script>
<style>
/* #ifdef H5 */
@import '/common/css/common.nvue.vue.css';
/* #endif */
</style>
- 优化顶部导航栏组件
/components/chat-navbar/chat-navbar.vue
<template>
<view>
<!-- 导航栏 -->
<view :class="[fixed ? 'fixed-top' : '',navbarClass]">
<!-- 状态栏 -->
...
<!-- 导航 -->
<!-- #ifdef APP || H5 -->
<view class="flex justify-between align-center"
style="height: 90rpx;">
<!-- 左边 -->
...
<!-- 右边 -->
<view class="flex align-center">
...
<slot name="right"></slot>
</view>
</view>
<!-- #endif -->
<!-- #ifdef MP -->
...
<!-- #endif -->
</view>
<!-- 占位符:占用 状态栏 + 导航栏的高度 -->
...
<!-- 导航栏点击加号的弹出菜单 -->
...
</view>
</template>
<script>
...
export default {
...
props:{
...
// 导航栏动态class
navbarClass:{
type:String,
default:'bg-light'
},
},
...
}
</script>
<style>
...
</style>
# 二、 底部聊天输入区域和聊天内容区域开发
在页面 pages/chat/chat.nvue
<template>
<view>
<!-- 导航栏 -->
<chat-navbar title="聊天" :fixed="true"
:showPlus="false" :showUser="false"
:showBack="true" navbarClass="bg-light">
<chat-navbar-icon-button slot="right"
@click="openMore" >
<text class="iconfont font-lg"></text>
</chat-navbar-icon-button>
</chat-navbar>
<!-- 聊天内容区域 -->
<scroll-view scroll-y class="bg-primary position-fixed left-0 right-0"
:style="chatContentStyle">
<view class="px-3" v-for="i in 5" :key="i">
<text>第一季课程我们称之为 知识过渡课,前面第一学期、第二学期我们主要面对PC端进行的学习和网站开发,虽然我们开发的网站后台也做的是响应式,可以兼容我们的手机端,但是随着现在移动互联网的到来,客户更多希望他的网站可以在手机微信上进行浏览、可以在小程序上面搜索(小程序包括微信小程序、支付宝小程序、抖音小程序等),甚至有的客户希望他的项目可以做app,可以进行安装。
</text>
</view>
</scroll-view>
<!-- 底部聊天输入区域 -->
<view class="position-fixed bottom-0 border-top
flex flex-row align-center justify-between"
style="background-color: #f7f7f7;width: 750rpx;
min-height: 90rpx;max-height: 320rpx;
padding-top: 12rpx;"
:style="chatBottomStyle">
<!-- 左边 -->
<view class="flex align-center">
<!-- 图标 -->
<chat-navbar-icon-button>
<text class="iconfont font-lg"></text>
</chat-navbar-icon-button>
<!-- 输入框 -->
<view class="flex align-center font-sm
bg-white px-2 py-1 border rounded">
<textarea fixed auto-height :maxlength="-1"
style="width: 440rpx;min-height: 60rpx;
max-height: 274rpx;overflow-y: scroll;
text-align: justify;"></textarea>
</view>
</view>
<!-- 右边 -->
<view class="flex align-center">
<chat-navbar-icon-button>
<text class="iconfont font-lg"></text>
</chat-navbar-icon-button>
<chat-navbar-icon-button>
<text class="iconfont font-lg"></text>
</chat-navbar-icon-button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
statusBarHeight:0,//状态栏高度动态计算
fixedHeight:0, //占位:状态栏+导航栏
bottomSafeAreaHeight:0, // 底部安全距离
}
},
mounted() {
let info = uni.getSystemInfoSync();
this.statusBarHeight = info.statusBarHeight;
this.fixedHeight = this.statusBarHeight + uni.upx2px(90);
this.bottomSafeAreaHeight = info.safeAreaInsets.bottom;
},
computed:{
chatContentStyle(){
let pbottom = this.bottomSafeAreaHeight == 0 ?
uni.upx2px(12) : this.bottomSafeAreaHeight;
let bottom = pbottom + uni.upx2px(90 + 12);
return `top:${this.fixedHeight}px;bottom:${bottom}px;`;
},
chatBottomStyle(){
let pbottom = this.bottomSafeAreaHeight == 0 ?
uni.upx2px(12) : this.bottomSafeAreaHeight;
return `padding-bottom: ${pbottom}px;`;
}
},
methods: {
openMore(){
console.log('点击了三个点图标');
}
}
}
</script>
<style>
/* #ifdef H5 */
@import '/common/css/common.nvue.vue.css';
/* #endif */
</style>
# 三、 聊天内容区域开发:聊天气泡和封装组件
在页面 pages/chat/chat.nvue
# 1. 聊天气泡样式
<!-- 聊天内容区域 -->
<scroll-view scroll-y
class="bg-light position-fixed left-0 right-0 px-3"
:style="chatContentStyle">
<!-- 对话部分 -->
<!-- 左边 -->
<view class="flex align-start justify-start mb-3 position-relative">
<!-- 头像 -->
<u--image
src="https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-07.png"
mode="widthFix" width="80rpx" height="80rpx" radius="10rpx"></u--image>
<!-- 气泡 -->
<!-- 三角标 -->
<text class="iconfont font-md text-white"
style="left: 25rpx;top: 20rpx;z-index: 100;"></text>
<!-- 内容 -->
<view class="bg-white p-2 rounded ml-1"
style="max-width: 500rpx;">
<text class="font" style="text-align: justify;">
老师你好,我想咨询一下本季课程,如果我不学习上一个季度,可以直接学习本季度吗?
</text>
</view>
</view>
<!-- 右边 -->
<view class="flex align-start justify-end mb-3 position-relative">
<!-- 气泡 -->
<!-- 内容 -->
<view class="p-2 rounded mr-1"
style="max-width: 500rpx;background-color: #95EC69;">
<text class="font" style="text-align: justify;">
同学你好,如果不学习上一个季度课程,如果你有vue的基础和js的基础知识,也可以学习本季度课程
</text>
</view>
<!-- 三角标 -->
<text class="iconfont font-md text-white"
style="right: 25rpx;top: 20rpx;z-index: 100;color:#95EC69;"></text>
<!-- 头像 -->
<u--image
src="https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-08.png"
mode="widthFix" width="80rpx" height="80rpx" radius="10rpx"></u--image>
</view>
</scroll-view>
# 2. 提取不同样式封装成class类
...
<!-- 聊天内容区域 -->
<scroll-view scroll-y
class="bg-light position-fixed left-0 right-0 px-3"
:style="chatContentStyle">
<!-- 对话部分 -->
<!-- 左边 -->
<view class="flex align-start justify-start mb-3 position-relative">
<!-- 头像 -->
<u--image
src="https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-07.png"
mode="widthFix" width="80rpx" height="80rpx" radius="10rpx"></u--image>
<!-- 气泡 -->
<!-- 三角标 -->
<text class="iconfont font-md chat-left-icon"></text>
<!-- 内容 -->
<view class="p-2 rounded ml-1 chat-left-content-bg"
style="max-width: 500rpx;">
<text class="font" style="text-align: justify;">
老师你好,我想咨询一下本季课程,如果我不学习上一个季度,可以直接学习本季度吗?
</text>
</view>
</view>
<!-- 右边 -->
<view class="flex align-start justify-end mb-3 position-relative">
<!-- 气泡 -->
<!-- 内容 -->
<view class="p-2 rounded mr-1 chat-right-content-bg"
style="max-width: 500rpx;">
<text class="font" style="text-align: justify;">
同学你好,如果不学习上一个季度课程,如果你有vue的基础和js的基础知识,也可以学习本季度课程
</text>
</view>
<!-- 三角标 -->
<text class="iconfont font-md chat-right-icon"></text>
<!-- 头像 -->
<u--image
src="https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-08.png"
mode="widthFix" width="80rpx" height="80rpx" radius="10rpx"></u--image>
</view>
</scroll-view>
...
<style>
...
.chat-left-icon{
left: 25rpx;top: 20rpx;z-index: 100;color: #ffffff;
}
.chat-right-icon{
right: 25rpx;top: 20rpx;z-index: 100;color:#95EC69;
}
.chat-left-content-bg{
background-color: white;
}
.chat-right-content-bg{
background-color: #95EC69;
}
</style>
# 3. 封装聊天气泡
# 1. 先循环绑定数据
...
<!-- 聊天内容区域 -->
<scroll-view scroll-y class="bg-light position-fixed left-0 right-0 px-3"
:style="chatContentStyle">
<!-- 对话部分 -->
<view v-for="(item,index) in chatDataList" :key="index">
<view class="flex align-start mb-3 position-relative"
:class="[item.user_id != 2 ? 'justify-start':'justify-end']">
<!-- 好友 -->
<!-- 头像 -->
<u--image v-if="item.user_id != 2"
:src="item.avatar" mode="widthFix"
width="80rpx" height="80rpx" radius="10rpx"></u--image>
<!-- 三角形 -->
<text v-if="item.user_id != 2"
class="iconfont font-md chat-left-icon"></text>
<!-- 内容 -->
<view class="p-2 rounded"
style="max-width: 500rpx;"
:class="[item.user_id != 2 ? 'chat-left-content-bg ml-1' :
'chat-right-content-bg mr-1']">
<text class="font" style="text-align: justify;">
{{item.data}}
</text>
</view>
<!-- 我 -->
<text v-if="item.user_id == 2"
class="iconfont font-md chat-right-icon"></text>
<u--image v-if="item.user_id == 2"
:src="item.avatar"
mode="widthFix"
width="80rpx" height="80rpx" radius="10rpx"></u--image>
</view>
</view>
<!-- 右边 -->
<!-- <view class="flex justify-end align-start mb-3 position-relative">
<view class="chat-right-content-bg p-2 rounded mr-1"
style="max-width: 500rpx;background-color: #95ec69;">
<text class="font" style="text-align: justify;">
同学你好,如果不学习上一个季度课程,如果你有vue的基础和js的基础知识,也可以学习本季度课程
</text>
</view>
<text class="iconfont font-md chat-right-icon"></text>
<u--image
src="https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-08.png"
mode="widthFix"
width="80rpx" height="80rpx" radius="10rpx"></u--image>
</view> -->
</scroll-view>
...
<script>
export default {
data() {
return {
...
chatDataList:[
{
avatar: 'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-06.png',
nickname: '彦祖',
chat_time: '16:11',
data: '老师你好,我想咨询一下本季课程,如果我不学习上一个季度,可以直接学习本季度吗?',
type: 'text', // image,video
user_id:1,
},
{
avatar: 'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-07.png',
nickname: '小二哥',
chat_time: '16:11',
data: '同学你好,如果不学习上一个季度课程,如果你有vue的基础和js的基础知识,也可以学习本季度课程',
type: 'text', // image,video
user_id:2,
},
],
}
},
...
}
</script>
# 2. 封装成组件
# 1. 创建组件
/components/chat-item/chat-item.vue<template> <view class="flex align-start mb-3 position-relative" :class="[!isMe ? 'justify-start':'justify-end']"> <!-- 好友 --> <!-- 头像 --> <u--image v-if="!isMe" :src="item.avatar" mode="widthFix" width="80rpx" height="80rpx" radius="10rpx"></u--image> <!-- 三角形 --> <text v-if="!isMe" class="iconfont font-md chat-left-icon"></text> <!-- 内容 --> <view class="p-2 rounded" style="max-width: 500rpx;" :class="[!isMe ? 'chat-left-content-bg ml-1' : 'chat-right-content-bg mr-1']"> <text class="font" style="text-align: justify;"> {{item.data}} </text> </view> <!-- 我 --> <text v-if="isMe" class="iconfont font-md chat-right-icon"></text> <u--image v-if="isMe" :src="item.avatar" mode="widthFix" width="80rpx" height="80rpx" radius="10rpx"></u--image> </view> </template> <script> export default{ name:"chat-item", props:{ item:Object, index:Number }, computed:{ // 我的判断, 假设我的id=2,后期由实际数据在更换 isMe(){ let user_id = 2; return this.item.user_id === user_id; } } } </script> <style> /* #ifdef H5 */ @import '/common/css/common.nvue.vue.css'; /* #endif */ .chat-left-icon{ left: 25rpx;top: 20rpx;z-index: 100;color: #ffffff; } .chat-right-icon{ right: 25rpx;top: 20rpx;z-index: 100;color:#95EC69; } .chat-left-content-bg{ background-color: #ffffff; } .chat-right-content-bg{ background-color: #95ec69; } </style># 2. 在聊天页使用组件
pages/chat/chat.nvue<!-- 聊天内容区域 --> <scroll-view scroll-y class="bg-light position-fixed left-0 right-0 px-3" :style="chatContentStyle"> <!-- 对话部分 --> <view v-for="(item,index) in chatDataList" :key="index"> <chat-item :item="item" :index="index"></chat-item> </view> </scroll-view>
# 4. 聊天时间处理
聊天时间比较短,比如5分钟内,不会频繁显示聊天时间
另外针对时间使用时间戳处理,在网上随便搜索在线时间戳,如:https://www.beijing-time.org/shijianchuo/ (opens new window)
# 1. 组件 /components/chat-item/chat-item.vue
<template>
<view>
<!-- 时间 -->
<view v-if="chatShowTime"
class="flex align-center justify-center pt-2 pb-2">
<text class="font-sm text-light-muted">{{chatShowTime}}</text>
</view>
<!-- 聊天内容 -->
<view class="flex align-start mb-3 position-relative"
:class="[!isMe ? 'justify-start' : 'justify-end']">
<!-- 好友 -->
...
<!-- 我 -->
...
</view>
</view>
</template>
<script>
import parseTimeJs from '@/common/mixins/parseTime.js';
export default{
name:"chat-item",
mixins:[parseTimeJs],
props:{
...
//上一条消息时间
prevTime:[Number,String]
},
computed:{
...
// 处理聊天时间
chatShowTime(){
return parseTimeJs.getChatTime(this.item.chat_time,this.prevTime);
}
}
}
</script>
<style scoped>
...
</style>
# 2. 处理时间的js工具 /common/mixins/parseTime.js
export default{
//当前时间
CurentTime(){
var now = new Date();
var year = now.getFullYear();
var month = now.getMonth() + 1;
var day = now.getDate();
var hh = now.getHours();
var mm = now.getMinutes();
var ss = now.getSeconds();
var clock = year + "-";
if(month < 10) clock += "0";
clock += month + "-";
if(day < 10) clock += "0";
clock += day + " ";
if(hh < 10) clock += "0";
clock += hh + ":";
if (mm < 10) clock += '0';
clock += mm + ":";
if (ss < 10) clock += '0';
clock += ss;
return(clock);
},
// 计算当前日期星座
getHoroscope(date) {
let c = ['摩羯','水瓶','双鱼','白羊','金牛','双子','巨蟹','狮子','处女','天秤','天蝎','射手','摩羯']
date=new Date(date);
let month = date.getMonth() + 1;
let day = date.getDate();
let startMonth = month - (day - 14 < '865778999988'.charAt(month));
return c[startMonth]+'座';
},
// 计算指定时间与当前的时间差
sumAge(data){
let dateBegin = new Date(data.replace(/-/g, "/"));
let dateEnd = new Date();//获取当前时间
let dateDiff = dateEnd.getTime() - dateBegin.getTime();//时间差的毫秒数
let dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000));//计算出相差天数
let leave1=dateDiff%(24*3600*1000) //计算天数后剩余的毫秒数
let hours=Math.floor(leave1/(3600*1000))//计算出小时数
//计算相差分钟数
let leave2=leave1%(3600*1000) //计算小时数后剩余的毫秒数
let minutes=Math.floor(leave2/(60*1000))//计算相差分钟数
//计算相差秒数
let leave3=leave2%(60*1000) //计算分钟数后剩余的毫秒数
let seconds=Math.round(leave3/1000)
return dayDiff+"天 "+hours+"小时 ";
},
// 获取聊天时间(相差300s内的信息不会显示时间)
getChatTime(v1,v2){
v1=v1.toString().length<13 ? v1*1000 : v1;
v2=v2.toString().length<13 ? v2*1000 : v2;
if(((parseInt(v1)-parseInt(v2))/1000) > 300){
return this.gettime(v1);
}
},
// 人性化时间格式
gettime(shorttime){
shorttime=shorttime.toString().length<13 ? shorttime*1000 : shorttime;
let now = (new Date()).getTime();
let cha = (now-parseInt(shorttime))/1000;
if (cha < 43200) {
// 当天
return this.dateFormat(new Date(shorttime),"{A} {t}:{ii}");
} else if(cha < 518400){
// 隔天 显示日期+时间
return this.dateFormat(new Date(shorttime),"{Mon}月{DD}日 {A} {t}:{ii}");
} else {
// 隔年 显示完整日期+时间
return this.dateFormat(new Date(shorttime),"{Y}-{MM}-{DD} {A} {t}:{ii}");
}
},
parseNumber(num) {
return num < 10 ? "0" + num : num;
},
dateFormat(date, formatStr) {
let dateObj = {},
rStr = /\{([^}]+)\}/,
mons = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];
dateObj["Y"] = date.getFullYear();
dateObj["M"] = date.getMonth() + 1;
dateObj["MM"] = this.parseNumber(dateObj["M"]);
dateObj["Mon"] = mons[dateObj['M'] - 1];
dateObj["D"] = date.getDate();
dateObj["DD"] = this.parseNumber(dateObj["D"]);
dateObj["h"] = date.getHours();
dateObj["hh"] = this.parseNumber(dateObj["h"]);
dateObj["t"] = dateObj["h"] > 12 ? dateObj["h"] - 12 : dateObj["h"];
dateObj["tt"] = this.parseNumber(dateObj["t"]);
dateObj["A"] = dateObj["h"] > 12 ? '下午' : '上午';
dateObj["i"] = date.getMinutes();
dateObj["ii"] = this.parseNumber(dateObj["i"]);
dateObj["s"] = date.getSeconds();
dateObj["ss"] = this.parseNumber(dateObj["s"]);
while(rStr.test(formatStr)) {
formatStr = formatStr.replace(rStr, dateObj[RegExp.$1]);
}
return formatStr;
},
// 获取年龄
getAgeByBirthday(data){
let birthday=new Date(data.replace(/-/g, "\/"));
let d=new Date();
return d.getFullYear()-birthday.getFullYear()-((d.getMonth()<birthday.getMonth()|| d.getMonth()==birthday.getMonth() && d.getDate()<birthday.getDate())?1:0);
},
}
# 3. 聊天页调用 /pages/chat/chat.nvue
<template>
<view>
<!-- 导航栏 -->
...
<!-- 聊天内容区域 -->
<scroll-view scroll-y
class="bg-light position-fixed left-0 right-0 px-3"
:style="chatContentStyle">
<!-- 对话部分 -->
<view v-for="(item,index) in chatDataList" :key="index">
<chat-item :item="item" :index="index"
:prevTime="index>0 ? chatDataList[index-1].chat_time : 0">
</chat-item>
</view>
</scroll-view>
<!-- 底部聊天输入区域 -->
...
</view>
</template>
<script>
export default {
data() {
return {
...
chatDataList:[
{
avatar: 'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-06.png',
nickname: '彦祖',
chat_time: 1750145785,
data: '老师你好,我想咨询一下本季课程,如果我不学习上一个季度,可以直接学习本季度吗?',
user_id: 1,
type:'text', //image,video
},
{
avatar: 'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-07.png',
nickname: '小二哥',
chat_time: 1750145800,
data: '同学你好,如果不学习上一个季度课程,如果你有vue的基础和js的基础知识,也可以学习本季度课程',
user_id: 2,
type:'text', //image,video
},
{
avatar: 'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/avatar-06.png',
nickname: '彦祖',
chat_time: 1750145807,
data: '好的,我了解了,谢谢老师',
user_id: 1,
type:'text', //image,video
},
],
}
},
}
</script>
<style>
...
</style>
# 5. 聊天撤回处理
# 1. 聊天组件 /components/chat-item/chat-item.vue
<template>
<view>
<!-- 时间 -->
...
<!-- 聊天内容 -->
<view
class="flex align-start mb-3 position-relative"
:class="[!isMe ? 'justify-start' : 'justify-end']">
<!-- 好友 -->
...
<!-- 内容 -->
<view class="p-2 rounded"
style="max-width: 500rpx;"
:class="[!isMe ? 'chat-left-content-bg ml-1'
: 'chat-right-content-bg mr-1',`chatItem${index}`]"
:ref="'chatItem' + index"
@longpress="onLongpress($event,index,item)">
<text class="font" style="text-align: justify;">
{{item.data}}
</text>
</view>
<!-- 我 -->
...
</view>
<!-- 弹出菜单 -->
<chat-tooltip ref="chatTooltip" :mask="true"
:maskTransparent="true" :isBottom="false"
:tooltipWidth="tooltipWidth"
:tooltipHeight="60"
transformOrigin="center center"
tooltipClass="bg-dark text-white border-0">
<view class="flex flex-row flex-1">
<view class="flex-1 align-center justify-center"
hover-class="bg-hover-dark"
v-for="(item,index) in menuList" :key="index"
@click="clickType(item.type)">
<text class="text-white">{{item.name}}</text>
</view>
</view>
<!-- 向下箭头 -->
<text class="position-fixed iconfont text-dark"
style="font-size: 40rpx;"
:style="jiantouStyle"></text>
</chat-tooltip>
</view>
</template>
<script>
...
export default{
...
data(){
return {
tooltipLeft:0, // 弹出菜单组件left x
tooltipTop:0, // 弹出菜单组件top y
//组件超过这个宽度,菜单居中显示,小于它随着点击点显示
rectmaxWidth:0,
//长按获取相应数据
longpressObj:null,
menuList: [
{
name: "复制",
type: 'copy'
},
{
name: "撤回",
type: 'reset'
},
],
}
},
computed:{
...
tooltipWidth() {
return this.menuList.length * 120;
},
jiantouStyle(){
let left = (uni.upx2px(750-40)) / 2;
let top = this.tooltipTop + uni.upx2px(40 + 5);
let jiantouCss = ``;
if(this.longpressObj && this.longpressObj.rect.width < this.rectmaxWidth){
top = this.longpressObj.y - 10;
left = this.longpressObj.x + 5;
jiantouCss = `transform: rotate(180deg);`;
}
return `left:${left}px;top:${top}px;${jiantouCss}`;
}
},
methods:{
//长按消息
onLongpress(e,index,item){
console.log('组件里面的事件对象',e);
let x = 0,
y = 0;
// #ifdef H5 || MP
x = e.changedTouches[0].clientX;
y = e.changedTouches[0].clientY;
// #endif
// #ifdef APP
x = e.changedTouches[0].screenX;
y = e.changedTouches[0].screenY;
// #endif
/*
this.$emit('Longpress',{
x,
y,
index,
item
});
*/
// #ifdef H5 || MP
const query = uni.createSelectorQuery().in(this);
query.select(`.chatItem${index}`).boundingClientRect(res => {
if (res) {
console.log('组件距离各个方向距离:', res);
// 可以在这里处理菜单弹出的位置逻辑
/*
this.$emit('Longpress',{
x,
y,
index,
item,
rect:res
});
*/
this.Longpressfn({
x,
y,
index,
item,
rect:res
});
}
}).exec();
// #endif
// #ifdef APP
const refName = 'chatItem' + index;
const ref = this.$refs[refName];
if (!ref) {
console.error('未找到元素引用: ' + refName);
return;
}
// 使用 Weex 的 dom 模块获取位置
const dom = weex.requireModule('dom');
dom.getComponentRect(ref, result => {
if (result && result.result) {
const rect = result.size;
console.log('组件距离各个方向距离:', rect);
// 传递位置信息给父组件
/*
this.$emit('Longpress', {
x: x,
y: y,
index,
item,
rect:rect
});
*/
this.Longpressfn({
x,
y,
index,
item,
rect:rect
});
} else {
console.error('获取位置失败', result);
}
});
// #endif
},
Longpressfn(e) {
console.log('长按', e);
this.longpressObj = e;
console.log('长按的是longpressObj', this.longpressObj);
//超过这个宽度,菜单居中显示,小于它随着点击点显示
this.rectmaxWidth = uni.upx2px((750 - 60 - 80 - (35 + 10 - 5)) / 2);
console.log('最大组件宽度',this.rectmaxWidth);
if(e.rect.width >= this.rectmaxWidth){
this.tooltipLeft = (uni.upx2px(750 - this.tooltipWidth)) / 2;
this.tooltipTop = e.y - uni.upx2px(60 + 40 - 15);
}else{
this.tooltipLeft = e.x;
this.tooltipTop = e.y;
}
this.$refs.chatTooltip.show(this.tooltipLeft, this.tooltipTop);
},
clickType(e) {
console.log('点击菜单',e);
switch (e){
case 'copy':
break;
case 'reset':
break;
}
this.$refs.chatTooltip.hide();
},
}
}
</script>
<style scoped>
...
</style>
# 2. 聊天撤回处理
# ① 优化弹窗
/components/chat-item/chat-item.vue弹窗透明
<!-- 弹出菜单 -->
:maskTransparent="true"
/components/chat-tooltip/chat-tooltip.vue自行调整
props: {
...
// 底部tabbar高度(非原生tabbar)
tabbarHeight:{
type: Number,
default:90+12+12,//tabbar高90rpx,上下内边距12rpx
}
},
mounted() {
...
// 应该还要去掉底部tabbar高度,因为不是原生tabbar(同时要调整箭头位置)
//this.maxTop = info.windowHeight - uni.upx2px(this.tooltipHeight) - uni.upx2px(this.tabbarHeight);
this.maxTop = info.windowHeight - uni.upx2px(this.tooltipHeight);
},
# ② 不能撤回好友消息
/components/chat-item/chat-item.vue
<!-- 弹出菜单 -->
<chat-tooltip ref="chatTooltip" :mask="true"
:maskTransparent="true" :isBottom="false"
:tooltipWidth="tooltipWidth"
:tooltipHeight="60"
tooltipClass="bg-dark border-0 text-white">
<view class="flex flex-row flex-1">
<view class="flex-1 align-center justify-center"
hover-class="bg-hover-dark"
v-for="(item,index) in getmenuList" :key="index"
@click="clickType(item.type)">
<text class="text-white">{{item.name}}</text>
</view>
</view>
<!-- 箭头 -->
<text class="position-fixed iconfont text-dark"
style="font-size: 40rpx;"
:style="jiantouStyle"></text>
</chat-tooltip>
...
computed:{
tooltipHeight() {
return this.getmenuList.length * this.menuEveHeight;
},
tooltipWidth(){
return this.getmenuList.length * 120;
},
// 弹窗菜单处理
getmenuList(){
return this.menuList.filter(v=>{
if(v.name === '撤回' && !this.isMe){
return false;
}
return true;
});
}
}
# ③ 撤回消息处理
首先在消息列表,给每条消息一个是否撤回字段做标记,字段如:isremove:false
在组件: /components/chat-item/chat-item.vue
<!-- 时间 -->
...
<!-- 撤回消息 -->
<view v-if="item.isremove"
class="flex align-center justify-center pt-2 pb-2">
<text class="font-sm text-light-muted">你撤回了一条信息</text>
</view>
<!-- 聊天内容 -->
<view v-else ...></view>
...
methods:{
clickType(e) {
...
switch (e){
...
case 'removeChatItem':// 撤回消息
this.item.isremove = true;
break;
}
...
},
}
# 四、聊天内容区域开发:底部发送内容区域
内容过多,在新页面打开,具体查看:聊天内容区域开发:底部发送内容区域