You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

546 lines
14 KiB

<template>
<view class="chat-message-item">
<view class="message-tip" v-if="msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT">
{{ msgInfo.content }}
</view>
<view class="message-tip" v-else-if="msgInfo.type == $enums.MESSAGE_TYPE.TIP_TIME">
{{ $date.toTimeText(msgInfo.sendTime) }}
</view>
<view class="message-normal" v-else-if="isNormal" :class="{ 'message-mine': msgInfo.selfSend }">
<head-image class="avatar" @longpress.prevent="$emit('longPressHead')" :id="msgInfo.sendId" :url="headImage"
:name="showName" size="small"></head-image>
<view class="content">
<view v-if="msgInfo.groupId && !msgInfo.selfSend" class="top">
<text class="name">{{ showName }}</text>
</view>
<view class="bottom">
<view class="message-content-wrapper">
<view v-if="msgInfo.type == $enums.MESSAGE_TYPE.TEXT">
<long-press-menu :items="menuItems" @select="onSelectMenu">
<!-- up-parse支持点击a标签,但是不支持显示emo表情,也不支持换行 -->
<up-parse v-if="$url.containUrl(msgInfo.content)&&!$emo.containEmoji(msgInfo.content)"
class="message-text" :showImgMenu="false" :content="nodesText"></up-parse>
<!-- rich-text支持显示emo表情以及消息换行,但是不支持点击a标签 -->
<rich-text v-else class="message-text" :nodes="nodesText"></rich-text>
</long-press-menu>
</view>
<view class="message-image" v-else-if="msgInfo.type == $enums.MESSAGE_TYPE.IMAGE">
<long-press-menu :items="menuItems" @select="onSelectMenu">
<view class="image-box">
<image class="send-image" :style="imageStyle" mode="aspectFill"
:src="contentData.thumbUrl" lazy-load="true" @click.stop="onShowFullImage()">
</image>
<custom-loading v-if="sending"></custom-loading>
</view>
</long-press-menu>
</view>
<view class="message-file" v-else-if="msgInfo.type == $enums.MESSAGE_TYPE.FILE">
<long-press-menu :items="menuItems" @select="onSelectMenu">
<view class="file-box">
<view class="file-info">
<uni-link class="file-name" :text="contentData.name" showUnderLine="true"
color="#007BFF" :href="contentData.url"></uni-link>
<view class="file-size">{{ fileSize }}</view>
</view>
<view class="file-icon iconfont icon-file"></view>
<custom-loading v-if="sending"></custom-loading>
</view>
</long-press-menu>
</view>
<long-press-menu v-else-if="msgInfo.type == $enums.MESSAGE_TYPE.AUDIO" :items="menuItems"
@select="onSelectMenu">
<view class="message-audio message-text" @click="onPlayAudio()">
<text class="iconfont icon-voice-play"></text>
<text class="chat-audio-text">{{ contentData.duration + '"' }}</text>
<text v-if="audioPlayState == 'PAUSE'" class="iconfont icon-play"></text>
<text v-if="audioPlayState == 'PLAYING'" class="iconfont icon-pause"></text>
</view>
</long-press-menu>
<long-press-menu v-if="isAction" :items="menuItems" @select="onSelectMenu">
<view class="chat-realtime message-text" @click="$emit('call')">
<text v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VOICE"
class="iconfont icon-chat-voice"></text>
<text v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VIDEO"
class="iconfont icon-chat-video"></text>
<text>{{ msgInfo.content }}</text>
</view>
</long-press-menu>
<view v-if="sending&&(isTextMessage||isAudioMessage)" class="sending">
<custom-loading :size="40" icon-color="#656adf" :mask="false"></custom-loading>
</view>
<view v-else-if="sendFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></view>
</view>
<view class="message-status" v-if="!isAction && msgInfo.selfSend && !msgInfo.groupId">
<text class="chat-readed" v-if="msgInfo.status == $enums.MESSAGE_STATUS.READED">已读</text>
<text class="chat-unread" v-else>未读</text>
</view>
<view class="chat-receipt" v-if="msgInfo.receipt" @click="onShowReadedBox">
<text v-if="msgInfo.receiptOk" class="tool-icon iconfont icon-ok"></text>
<text v-else>{{ msgInfo.readedCount }}人已读</text>
</view>
</view>
</view>
</view>
<chat-group-readed ref="chatGroupReaded" :groupMembers="groupMembers" :msgInfo="msgInfo"></chat-group-readed>
</view>
</template>
<script>
export default {
name: "chat-message-item",
props: {
headImage: {
type: String,
required: true
},
showName: {
type: String,
required: true
},
msgInfo: {
type: Object,
required: true
},
groupMembers: {
type: Array
}
},
data() {
return {
audioPlayState: 'STOP',
innerAudioContext: null,
menu: {
show: false,
style: ""
}
}
},
methods: {
onSendFail() {
this.$emit("resend", this.msgInfo);
},
onPlayAudio() {
// 初始化音频播放器
if (!this.innerAudioContext) {
this.innerAudioContext = uni.createInnerAudioContext();
let url = this.contentData.url;
this.innerAudioContext.src = url;
this.innerAudioContext.onEnded((e) => {
console.log('停止')
this.audioPlayState = "STOP"
this.emit();
})
this.innerAudioContext.onError((e) => {
this.audioPlayState = "STOP"
console.log("播放音频出错");
console.log(e)
this.emit();
});
}
if (this.audioPlayState == 'STOP') {
this.innerAudioContext.play();
this.audioPlayState = "PLAYING";
} else if (this.audioPlayState == 'PLAYING') {
this.innerAudioContext.pause();
this.audioPlayState = "PAUSE"
} else if (this.audioPlayState == 'PAUSE') {
this.innerAudioContext.play();
this.audioPlayState = "PLAYING"
}
this.emit();
},
onSelectMenu(item) {
this.$emit(item.key.toLowerCase(), this.msgInfo);
this.menu.show = false;
},
onShowFullImage() {
let imageUrl = this.contentData.originUrl;
uni.previewImage({
urls: [imageUrl]
})
},
onShowReadedBox() {
this.$refs.chatGroupReaded.open();
},
emit() {
this.$emit("audioStateChange", this.audioPlayState, this.msgInfo);
},
stopPlayAudio() {
if (this.innerAudioContext) {
this.innerAudioContext.stop();
this.innerAudioContext = null;
this.audioPlayState = "STOP"
}
}
},
computed: {
sending() {
return this.msgInfo.status == this.$enums.MESSAGE_STATUS.SENDING;
},
sendFail() {
return this.msgInfo.status == this.$enums.MESSAGE_STATUS.FAILED;
},
contentData() {
return JSON.parse(this.msgInfo.content)
},
fileSize() {
let size = this.contentData.size;
if (size > 1024 * 1024) {
return Math.round(size / 1024 / 1024) + "M";
}
if (size > 1024) {
return Math.round(size / 1024) + "KB";
}
return size + "B";
},
menuItems() {
let items = [];
if (this.msgInfo.type == this.$enums.MESSAGE_TYPE.TEXT) {
items.push({
key: 'COPY',
name: '复制',
icon: 'bars'
});
}
if (this.msgInfo.selfSend && this.msgInfo.id > 0) {
items.push({
key: 'RECALL',
name: '撤回',
icon: 'refreshempty'
});
}
items.push({
key: 'DELETE',
name: '删除',
icon: 'trash',
color: '#e64e4e'
});
if (this.msgInfo.type == this.$enums.MESSAGE_TYPE.FILE) {
items.push({
key: 'DOWNLOAD',
name: '下载并打开',
icon: 'download'
});
}
return items;
},
isTextMessage() {
return this.msgInfo.type == this.$enums.MESSAGE_TYPE.TEXT
},
isAudioMessage() {
return this.msgInfo.type == this.$enums.MESSAGE_TYPE.AUDIO
},
isAction() {
return this.$msgType.isAction(this.msgInfo.type);
},
isNormal() {
const type = this.msgInfo.type;
return this.$msgType.isNormal(type) || this.$msgType.isAction(type)
},
nodesText() {
let color = this.msgInfo.selfSend ? 'white' : '';
let text = this.$str.html2Escape(this.msgInfo.content)
text = this.$url.replaceURLWithHTMLLinks(text, color)
return this.$emo.transform(text, 'emoji-normal')
},
imageStyle() {
console.log(uni.getSystemInfo())
let maxSize = uni.getSystemInfoSync().windowWidth * 0.6;
let minSize = uni.getSystemInfoSync().windowWidth * 0.2;
let width = this.contentData.width;
let height = this.contentData.height;
if (width && height) {
let ratio = Math.min(width, height) / Math.max(width, height);
let w = Math.max(width > height ? maxSize : ratio * maxSize, minSize);
let h = Math.max(width > height ? ratio * maxSize : maxSize, minSize);
return `width: ${w}px;height:${h}px;`
} else {
// 兼容历史版本,历史数据没有记录宽高
return `max-width: ${maxSize}px;min-width:100px;max-height: ${maxSize}px;min-height:100px;`
}
}
}
}
</script>
<style scoped lang="scss">
.chat-message-item {
padding: 2rpx 20rpx;
.message-tip {
line-height: 60rpx;
text-align: center;
color: $im-text-color-lighter;
font-size: $im-font-size-smaller-extra;
padding: 10rpx;
}
.message-normal {
position: relative;
margin-bottom: 22rpx;
padding-left: 110rpx;
min-height: 80rpx;
.avatar {
position: absolute;
top: 0;
left: 0;
}
.content {
text-align: left;
.top {
display: flex;
flex-wrap: nowrap;
align-items: center;
.name {
color: $im-text-color-lighter;
font-size: $im-font-size-smaller;
line-height: $im-font-size-smaller;
height: $im-font-size-smaller;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.bottom {
display: inline-block;
padding-right: 80rpx;
margin-top: 5rpx;
.message-content-wrapper {
position: relative;
display: flex;
align-items: center;
.sending {
position: relative;
margin: 0 6rpx;
.icon-loading {
color: $im-color-primary;
}
}
.send-fail {
color: #e60c0c;
font-size: 50rpx;
margin: 0 5rpx;
}
.message-text {
position: relative;
line-height: 1.6;
margin-top: 10rpx;
padding: 16rpx 24rpx;
background-color: $im-bg;
border-radius: 20rpx;
color: $im-text-color;
font-size: $im-font-size;
text-align: left;
display: block;
word-break: break-word;
white-space: pre-line;
&:after {
content: "";
position: absolute;
left: -20rpx;
top: 26rpx;
width: 6rpx;
height: 6rpx;
border-style: solid dashed dashed;
border-color: $im-bg transparent transparent;
overflow: hidden;
border-width: 18rpx;
}
}
.message-image {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
.image-box {
position: relative;
.send-image {
cursor: pointer;
border-radius: 10rpx;
background: $im-bg;
border: 6rpx solid $im-color-primary-light-5;
}
}
.send-fail {
color: $im-color-danger;
font-size: $im-font-size;
cursor: pointer;
margin: 0 20px;
}
}
.message-file {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
cursor: pointer;
.file-box {
position: relative;
display: flex;
flex-wrap: nowrap;
align-items: center;
min-height: 60px;
border-radius: 4px;
padding: 10px 15px;
box-shadow: $im-box-shadow-dark;
.file-info {
flex: 1;
height: 100%;
text-align: left;
font-size: 14px;
width: 300rpx;
.file-name {
font-weight: 600;
margin-bottom: 15px;
word-break: break-all;
}
}
.file-icon {
font-size: 80rpx;
color: #d42e07;
}
}
.send-fail {
color: #e60c0c;
font-size: 50rpx;
cursor: pointer;
margin: 0 20rpx;
}
}
.message-audio {
display: flex;
align-items: center;
.chat-audio-text {
padding-right: 8px;
}
.icon-voice-play {
font-size: 18px;
padding-right: 8px;
}
}
}
.chat-realtime {
display: flex;
align-items: center;
.iconfont {
font-size: 20px;
padding-right: 8px;
}
}
.message-status {
line-height: $im-font-size-smaller-extra;
font-size: $im-font-size-smaller-extra;
padding-top: 2rpx;
.chat-readed {
display: block;
padding-top: 2rpx;
color: $im-text-color-lighter;
}
.chat-unread {
color: $im-color-danger;
}
}
.chat-receipt {
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
font-weight: 600;
.icon-ok {
font-size: 20px;
color: $im-color-success;
}
}
}
}
&.message-mine {
text-align: right;
padding-left: 0;
padding-right: 110rpx;
.avatar {
left: auto;
right: 0;
}
.content {
text-align: right;
.bottom {
padding-left: 80rpx;
padding-right: 0;
.message-content-wrapper {
flex-direction: row-reverse;
}
.message-text {
margin-left: 10px;
background-color: $im-color-primary-light-2;
color: #fff;
&:after {
left: auto;
right: -9px;
border-top-color: $im-color-primary-light-2;
}
}
.message-audio {
flex-direction: row-reverse;
.chat-audio-text {
padding-right: 0;
padding-left: 8px;
}
.icon-voice-play {
transform: rotateY(180deg);
}
}
.chat-realtime {
display: flex;
flex-direction: row-reverse;
.iconfont {
transform: rotateY(180deg);
}
}
}
}
}
}
}
</style>