|
|
<template>
|
|
|
<!-- 聊天详情页 -->
|
|
|
<view class="chat_message_page">
|
|
|
<!-- 页面内容 -->
|
|
|
<view class="page_content">
|
|
|
|
|
|
<!-- 聊天内容区域
|
|
|
TODO 键盘滚动高度判断-->
|
|
|
<scroll-view id="scroll" :scroll-y="true" :scroll-into-view="scrollToId" :scroll-top="scrollTop"
|
|
|
:scroll-anchoring="true" @scrolltoupper="getUpList"
|
|
|
:style="{height: 'calc(100vh - '+ bottomHight + 'rpx - env(safe-area-inset-bottom))'}"
|
|
|
style="overflow-anchor:auto;">
|
|
|
|
|
|
<view class="message-block padding-bottom-30">
|
|
|
<!-- ty组件:列表 -->
|
|
|
<ty-list-list :status="load_status" type="2">
|
|
|
|
|
|
<!-- 循环消息列表 -->
|
|
|
<template v-slot:up_list>
|
|
|
<view :id="'scroll'+item.id" v-for="(item,index) in up_list" :key="index"
|
|
|
class="message-item padding-lr-30 text-28"
|
|
|
:class="{ 'self-message': item.user_id == userId, 'friend-message': item.user_id != userId }">
|
|
|
<!-- 消息时间 -->
|
|
|
<view class="margin-top-40 text-center text-98" v-if="item.is_show_time == 1">
|
|
|
{{item.show_time}}
|
|
|
</view>
|
|
|
|
|
|
<!-- 消息内容 -->
|
|
|
<view class="content margin-top-40 flex align-start">
|
|
|
|
|
|
<!-- 用户头像 -->
|
|
|
<image class="jc-image-80 radius-10" :src="item.user_head_img"></image>
|
|
|
|
|
|
<!-- 消息内容 -->
|
|
|
<view class="margin-lr-25">
|
|
|
<!-- 文本类型 -->
|
|
|
<view v-if="item.type == 1"
|
|
|
class="send-text padding-20 radius-10 bg-ff line-40">
|
|
|
<!-- {{item.content}} -->
|
|
|
<rich-text :nodes="replaceEmoji(item.content)"></rich-text>
|
|
|
</view>
|
|
|
|
|
|
<!-- 语音类型
|
|
|
1秒80像素长,60秒464像素最长-->
|
|
|
<view class="send-voice padding-20 radius-10 bg-main-light line-40"
|
|
|
:style="{width: 100 + (464 - 100 ) / 59 * item.attach_data.record_seconds +'rpx'}"
|
|
|
v-if="item.type == 2" @click="playVoice(index)">
|
|
|
<text class="icon tyIcon-yuyin1 text-28 text-33"></text>
|
|
|
<view class="voice_text">{{item.attach_data.record_seconds}}''</view>
|
|
|
<!-- <view class="image-red-circul"></view> -->
|
|
|
</view>
|
|
|
|
|
|
<!-- 图片类型
|
|
|
lazy-load 图片懒加载。只针对page与scroll-view下的image有效 -->
|
|
|
<view v-if="item.type == 3" class="message-image radius-10"
|
|
|
@click="previewImage(item.content)">
|
|
|
<!-- 宽图片
|
|
|
宽度为230,高度计算,最低为100rpx 保证图片的短边能完全显示出来-->
|
|
|
<image class="jc-image radius-10" :src="item.content" mode="aspectFill"
|
|
|
style="width: 230rpx;"
|
|
|
:style="{height: (item.attach_data.height * 230 / item.attach_data.width < 100 ? 100 :item.attach_data.height * 230 / item.attach_data.width) + 'rpx' }"
|
|
|
v-if="item.attach_data.width / item.attach_data.height >= 230 / 300">
|
|
|
</image>
|
|
|
<!-- 长图片
|
|
|
高度为300,宽度计算,最低为100rpx 保证图片的短边能完全显示出来-->
|
|
|
<image class="jc-image radius-10" :src="item.content" mode="aspectFill"
|
|
|
style="height: 300rpx;"
|
|
|
:style="{width: (item.attach_data.width * 300 / item.attach_data.height < 100 ? 100 : item.attach_data.width * 300 / item.attach_data.height) + 'rpx' }"
|
|
|
v-if="item.attach_data.width / item.attach_data.height < 230 / 300">
|
|
|
</image>
|
|
|
</view>
|
|
|
|
|
|
<!-- 位置类型 -->
|
|
|
<view class="overflow-hidden radius-10 bg-ff" style="width: 460rpx;"
|
|
|
v-if="item.type == 4" @click="openLocation(item.attach_data)">
|
|
|
<view class="padding-20">
|
|
|
<view class="line-height-40 text-28 text-cut-one">{{item.content}}
|
|
|
</view>
|
|
|
<view class="margin-top-10 line-height-35 text-98 text-cut-one">
|
|
|
{{item.attach_data.address}}
|
|
|
</view>
|
|
|
</view>
|
|
|
<map style="width: 100%; height: 175rpx;" :latitude="item.attach_data.lat"
|
|
|
:longitude="item.attach_data.lng">
|
|
|
</map>
|
|
|
</view>
|
|
|
<!-- 商品类型 -->
|
|
|
<view class="overflow-hidden radius-10 bg-ff" style="width: 460rpx;"
|
|
|
v-if="item.type == 5">
|
|
|
<view class="padding-tb-10 padding-left-10 padding-right-40 flex ">
|
|
|
<view class="goodsImg">
|
|
|
<image :src="item.attach_data.product_cover_img" class="width-100p height-100p block"></image>
|
|
|
</view>
|
|
|
<view class="flex-one margin-left-10 text-26 text-33 text-bold height-80 line-40 text-cut-two">
|
|
|
{{item.attach_data.product_name}}
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
</ty-list-list>
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
|
|
|
<!-- jc组件:聊天底部栏 -->
|
|
|
<jc-chat-chatBtn @sendMessage="sendMessage" @bottomHightChange="bottomHightChange"
|
|
|
@recordStart="recordStart"></jc-chat-chatBtn>
|
|
|
</view>
|
|
|
|
|
|
<!-- 页面浮层 -->
|
|
|
<view class="page_layer">
|
|
|
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import chat from './api/chat.js'
|
|
|
import emoji from "@/components/hzjc/components/utils/emoji.js";
|
|
|
const innerAudioContext = uni.createInnerAudioContext()
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
// 私聊房间ID
|
|
|
room_id: '',
|
|
|
userId: '',
|
|
|
// 加载状态,默认为空
|
|
|
load_status: '',
|
|
|
// 聊天数据列表
|
|
|
up_list: [],
|
|
|
// 底部区域高度
|
|
|
bottomHight: 100,
|
|
|
// 语音是否正在播放
|
|
|
isVoicePlay: false,
|
|
|
// 滚动到当前的ID
|
|
|
scrollToId: '',
|
|
|
// 设置竖向滚动条位置 scroll-into-view 的优先级高于 scroll-top
|
|
|
scrollTop: 0,
|
|
|
// emoji列表
|
|
|
emojiList: emoji.list,
|
|
|
// 商品信息
|
|
|
productId: '',
|
|
|
productData: {},
|
|
|
}
|
|
|
},
|
|
|
|
|
|
onLoad(options) {
|
|
|
// 获取商品id
|
|
|
// this.productId = options.productId ? options.productId : '';
|
|
|
// options.room_id=401399706038272
|
|
|
// userId赋值
|
|
|
this.userId = uni.getStorageSync('user_id')
|
|
|
|
|
|
// 获取私聊房间ID
|
|
|
this.room_id = options.room_id || 0;
|
|
|
|
|
|
// if (this.productId) {
|
|
|
// this.getProductData()
|
|
|
// }
|
|
|
},
|
|
|
|
|
|
onReady() {
|
|
|
// 获取聊天室详情
|
|
|
chat.getChatRoomDetail(this.room_id).then(res => {
|
|
|
// 设置标题
|
|
|
this.cn.setTitle(res.data.room_user.to_user_nick_name)
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 监听音频停止事件
|
|
|
innerAudioContext.onStop(() => {
|
|
|
// 更改音频播放状态
|
|
|
this.isVoicePlay = false
|
|
|
})
|
|
|
},
|
|
|
|
|
|
onShow() {
|
|
|
chat.curPage = 2
|
|
|
chat.curRoomId = this.room_id
|
|
|
chat.that = this
|
|
|
|
|
|
console.log('详情页 websocket是否打开', this.websocket.isSocketOpen)
|
|
|
|
|
|
// 已经打开长连接
|
|
|
if (this.websocket.isSocketOpen) {
|
|
|
// 加载第一页聊天列表
|
|
|
this.loadUpList(1)
|
|
|
|
|
|
// 更改用户在线状态
|
|
|
chat.updateUserOnlineStatus(this.room_id, 1)
|
|
|
|
|
|
// 还没有打开长连接
|
|
|
} else {
|
|
|
// 连接长连接
|
|
|
this.websocket.init().then((res) => {
|
|
|
// 加载第一页聊天列表
|
|
|
this.loadUpList(1)
|
|
|
|
|
|
// 更改用户在线状态
|
|
|
chat.updateUserOnlineStatus(this.room_id, 1)
|
|
|
})
|
|
|
|
|
|
// 监听WebSocket接受到服务器的消息事件
|
|
|
chat.onMessage()
|
|
|
}
|
|
|
},
|
|
|
|
|
|
onUnload() {
|
|
|
// 更改当前聊天信息
|
|
|
chat.curPage = 3
|
|
|
chat.curRoomId = ''
|
|
|
chat.that = {}
|
|
|
|
|
|
// 更改用户为离线状态
|
|
|
chat.updateUserOnlineStatus(this.room_id, 0)
|
|
|
|
|
|
// 停止所有音频播放
|
|
|
innerAudioContext.stop()
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
// 获取商品数据
|
|
|
// getProductData() {
|
|
|
// this.rq.getData('fire/api/Fire/getProductDetail', {
|
|
|
// product_id: this.productId
|
|
|
// }).then(res => {
|
|
|
// if (res.code == 0) {
|
|
|
// this.productData = res.data.detail;
|
|
|
// this.sendMessage({message_type:5,content:{},attach:{}})
|
|
|
// }
|
|
|
// })
|
|
|
// },
|
|
|
/**
|
|
|
* 加载上一页的数据
|
|
|
* @date 2022-08-09
|
|
|
*/
|
|
|
getUpList() {
|
|
|
this.loadUpList(0)
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 发送信息
|
|
|
* @date 2022-08-15
|
|
|
*/
|
|
|
sendMessage(e) {
|
|
|
var product = {};
|
|
|
// if (this.productId) {
|
|
|
// product.id = this.productData.id;
|
|
|
// product.name = this.productData.name;
|
|
|
// product.cover_img = this.productData.cover_img;
|
|
|
// }
|
|
|
chat.insertChatMessage(this.room_id, e.message_type, e.content, e.attach, product).then(res => {
|
|
|
|
|
|
})
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 底部栏高度变化事件
|
|
|
* @date 2022-08-15
|
|
|
*/
|
|
|
bottomHightChange(e) {
|
|
|
this.bottomHight = e.bottomHight
|
|
|
|
|
|
// 滚动到最新消息
|
|
|
setTimeout(res => {
|
|
|
this.scrollToBottom()
|
|
|
}, 10)
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 开始录音事件
|
|
|
* @date 2022-08-15
|
|
|
*/
|
|
|
recordStart() {
|
|
|
// 开始录音时,停止所有音频播放
|
|
|
innerAudioContext.stop()
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 图片预览
|
|
|
* @param {string} image_url 预览图片地址
|
|
|
* @date 2022-08-15
|
|
|
*/
|
|
|
previewImage: function(image_url) {
|
|
|
// 需要展示的图片列表
|
|
|
let imageUrls = []
|
|
|
|
|
|
let message_list = this.up_list
|
|
|
|
|
|
for (let index in message_list) {
|
|
|
if (message_list[index]['type'] == 3) {
|
|
|
imageUrls.push(message_list[index]['content'])
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 预览图片
|
|
|
this.image.previewImage(imageUrls, imageUrls.indexOf(image_url))
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 播放语音
|
|
|
* @param {int} index 第X条消息
|
|
|
* @date 2022-08-15
|
|
|
*/
|
|
|
playVoice(index) {
|
|
|
// 更改音频播放状态
|
|
|
this.isVoicePlay = true
|
|
|
|
|
|
// 音频的数据链接
|
|
|
innerAudioContext.src = this.up_list[index].content
|
|
|
innerAudioContext.play()
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 使用应用内置地图查看位置
|
|
|
* @date 2022-09-21
|
|
|
*/
|
|
|
openLocation(e) {
|
|
|
// 打开内置地图
|
|
|
this.cn.openLocation(e.lat, e.lng, e.name, e.address)
|
|
|
},
|
|
|
|
|
|
// ------------------------------ 以下为封装方法---------------------------------
|
|
|
/**
|
|
|
* 加载聊天列表
|
|
|
* @param {int} first_page 1--加载第一页,自动清空列表 0--加载上一页,不清空列表
|
|
|
* @date 2022-09-15
|
|
|
*/
|
|
|
loadUpList(first_page) {
|
|
|
// 当前消息列表最上面一条消息ID
|
|
|
let cur_first_message_id = this.up_list.length ? this.up_list[0].id : ''
|
|
|
|
|
|
// 加载聊天列表
|
|
|
chat.listChatRoomMessage(this.room_id, cur_first_message_id, first_page, this).then(res => {
|
|
|
// 第一次加载,滚动到最新消息
|
|
|
if (first_page == 1) {
|
|
|
setTimeout(() => {
|
|
|
if (this.up_list.length) {
|
|
|
// 滚动到最新消息
|
|
|
this.scrollToId = 'scroll' + this.up_list[this.up_list.length - 1].id
|
|
|
}
|
|
|
}, 10)
|
|
|
} else {
|
|
|
this.scrollToId = 'scroll' + cur_first_message_id
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 滚动到聊天底部(底部栏发生变化时,有新消息时,第一次加载)
|
|
|
* @param {Object} str
|
|
|
*/
|
|
|
scrollToBottom() {
|
|
|
// 返回一个 SelectorQuery 对象实例
|
|
|
let query = uni.createSelectorQuery()
|
|
|
|
|
|
// 在当前页面下选择匹配选择器 selector 的所有节点,返回一个 NodesRef 对象实例,可以用于获取节点信息
|
|
|
query.select('.message-block').boundingClientRect()
|
|
|
// 在当前页面下选择第一个匹配选择器 selector 的节点,返回一个 NodesRef 对象实例,可以用于获取节点信息
|
|
|
query.select('#scroll').boundingClientRect()
|
|
|
// 执行所有的请求。请求结果按请求次序构成数组,在callback的第一个参数中返回
|
|
|
query.exec((res) => {
|
|
|
let messageBlockHeight = res[0].height
|
|
|
let scrollViewHeight = res[1].height
|
|
|
console.log(8111,res)
|
|
|
// 消息内容高度 > 滚动区域高度
|
|
|
if (messageBlockHeight > scrollViewHeight) {
|
|
|
setTimeout(() => {
|
|
|
// 滚动到最后一屏,即消息底部
|
|
|
this.scrollTop = messageBlockHeight - scrollViewHeight + 300
|
|
|
}, 10)
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
* 替换表情符号为图片
|
|
|
* @param {Object} str 带emoji表情的文本
|
|
|
* @date 2022-09-20
|
|
|
*/
|
|
|
replaceEmoji(str) {
|
|
|
let replacedStr = str.replace(/\[([^(\]|\[)]*)\]/g, (item, index) => {
|
|
|
for (let i = 0; i < this.emojiList.length; i++) {
|
|
|
if (this.emojiList[i].alt == item) {
|
|
|
// 在线表情路径,图文混排必须使用网络路径,请上传一份表情到你的服务器后再替换此路径
|
|
|
// 比如你上传服务器后,你的100.gif路径为https://www.xxx.com/emoji/100.gif 则替换onlinePath填写为https://www.xxx.com/emoji/
|
|
|
/* let onlinePath = 'https://www.xxx.com/emoji/'
|
|
|
let imgstr = '<img width="40px" src="' + onlinePath + EM.url + '">'; */
|
|
|
let imgstr = '<img width="20rpx" src="https://jucheng-bjhedasx.oss-cn-qingdao.aliyuncs.com/bjhedasx/uid2/emoji/' + this.emojiList[i].url +
|
|
|
'">'
|
|
|
|
|
|
return imgstr
|
|
|
}
|
|
|
|
|
|
}
|
|
|
});
|
|
|
// return '<div style="display: flex;align-items: center;word-wrap:break-word;">' + replacedStr + '</div>';
|
|
|
return '<div style="word-wrap:break-word;">' + replacedStr + '</div>';
|
|
|
},
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style>
|
|
|
/* 个人发送的消息内容 */
|
|
|
.self-message .content {
|
|
|
flex-direction: row-reverse;
|
|
|
}
|
|
|
|
|
|
/* 文本消息 */
|
|
|
.send-text {
|
|
|
/* 屏幕宽度 - 两边留白宽度 - 头像宽度 - 头像与文本间距宽度 */
|
|
|
max-width: calc(100vw - 60rpx - 160rpx - 50rpx);
|
|
|
position: relative;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
/* 好友发送的消息前面加小三角 */
|
|
|
.friend-message .send-text::before {
|
|
|
content: '';
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
position: absolute;
|
|
|
top: 34rpx;
|
|
|
left: -12rpx;
|
|
|
border-right: 12rpx solid #fff;
|
|
|
border-top: 12rpx solid transparent;
|
|
|
border-bottom: 12rpx solid transparent;
|
|
|
}
|
|
|
|
|
|
/* 个人发送的消息后面加小三角 */
|
|
|
.self-message .send-text::after {
|
|
|
content: '';
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
position: absolute;
|
|
|
top: 34rpx;
|
|
|
right: -12rpx;
|
|
|
border-left: 12rpx solid #fff;
|
|
|
border-top: 12rpx solid transparent;
|
|
|
border-bottom: 12rpx solid transparent;
|
|
|
}
|
|
|
|
|
|
/* 图片 */
|
|
|
.message-image {
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
/* 语音消息 */
|
|
|
.send-voice {
|
|
|
position: relative;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
/* 个人发的语音元素要倒序排列 */
|
|
|
.self-message .send-voice {
|
|
|
flex-direction: row-reverse;
|
|
|
}
|
|
|
|
|
|
/* 好友发送的消息前面加小三角 */
|
|
|
.friend-message .send-voice::before {
|
|
|
content: '';
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
position: absolute;
|
|
|
top: 34rpx;
|
|
|
left: -12rpx;
|
|
|
border-right: 12rpx solid var(--mainLight);
|
|
|
border-top: 12rpx solid transparent;
|
|
|
border-bottom: 12rpx solid transparent;
|
|
|
}
|
|
|
|
|
|
/* 个人发送的消息后面加小三角 */
|
|
|
.self-message .send-voice::after {
|
|
|
content: '';
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
position: absolute;
|
|
|
top: 34rpx;
|
|
|
right: -12rpx;
|
|
|
border-left: 12rpx solid var(--mainLight);
|
|
|
border-top: 12rpx solid transparent;
|
|
|
border-bottom: 12rpx solid transparent;
|
|
|
}
|
|
|
|
|
|
.friend-message .icon {
|
|
|
transform: rotateY(-180deg);
|
|
|
}
|
|
|
|
|
|
/* 商品 */
|
|
|
.goodsImg{
|
|
|
width: 140rpx;
|
|
|
height: 140rpx;
|
|
|
border-radius: 5rpx;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
</style>
|