ufutx_love_mp/src/pages/news/chitchat.wpy

1623 lines
48 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<style lang="less" scoped>
@import url(../../styles/theme.less);
page {
background-color: #f5f5f5;
}
.m_tips_icon {
width: 140rpx;
height: 140rpx;
position: fixed;
top: 32rpx;
right: -4rpx;
z-index: 999;
}
.ui-scroll-view{
-webkit-overflow-scrolling: touch;
will-change: transform;
background: #f2f2f2;
}
.ui-top-placeholder {
width: 100vw;
height: 30rpx;
}
.ui-tips-pic{
width: 76%;
height: 62rpx;
border-radius: 16rpx;
margin: 0 0 10rpx 50rpx;
}
.ui-tips-box{
display: inline-block;
padding: 20rpx;
border-radius: 16rpx;
width: 76%;
margin: 0 50rpx 30rpx 50rpx;
background: #ffffff;
.ui-tips_icon{
width: 28rpx;
height: 28rpx;
vertical-align: middle;
margin-top: -8rpx;
margin-right: 6rpx;
}
.ui-tips_text{
text-align: justify;
text-align-last: left;
line-height: 1.6;
margin-top: 10rpx;
}
}
.ui-msg-data-box {
padding: 0 30rpx 160rpx 30rpx;
.ui-msg-data {
overflow: hidden;
padding-bottom: 36rpx;
}
.ui-left-msg-box {
float: left;
}
.ui-right-msg-box {
float: right;
}
.ui-error-icon, .ui-error-icon-v2{
width: 40rpx;
height: 40rpx;
position: absolute;
top: 50%;
left: -50rpx;
transform: translateY(-50%);
}
.ui-error-icon-v2{
left: initial;
right: -60rpx
}
.ui-user-pic {
width: 86rpx;
height: 86rpx;
border: 2rpx solid #eaeaea;
box-shadow: 0 0 12rpx #cccccc;
border-radius: 50%;
display: block;
vertical-align: top;
}
.ui-user-name {
color: #86889a;
letter-spacing: 2rpx;
}
.ui-msg-left,
.ui-msg-right {
max-width: 480rpx;
padding: 20rpx 24rpx;
background: #ffffff;
line-height: 42rpx;
letter-spacing: 2rpx;
word-wrap: break-word;
word-break: break-all;
.emoji_small {
width: 42rpx;
height: 42rpx;
vertical-align: middle;
}
}
.ui-msg-left-pic, .ui-msg-right-pic{
max-width: 200rpx;
max-height: 200rpx;
border-radius: 20rpx;
display: block;
vertical-align: top;
}
.ui-right-audio{
background: #ff7d9f;
}
.ui-left-audio{
background: #ffffff;
}
.ui-audio-box{
padding: 10rpx 24rpx;
box-sizing: content-box;
height: 100%;
justify-content: space-between;
overflow-x: hidden;
.ui-audio-icon,.ui-audio-icon-l-play,.ui-audio-icon-r-play{
position: relative;
width: 28rpx;
height: 36rpx;
overflow: hidden;
}
.ui-audio-icon-l-play:before,.ui-audio-icon-r-play:before{
content: '';
position: absolute;
top:0;
width: 28rpx;
height: 36rpx;
}
.ui-audio-icon-l-play:before {
left: 0;
background: #ffffff;
animation: volumeLPlay 1.5s linear infinite;
}
.ui-audio-icon-r-play:before{
right:0;
background: #ff7d9f;
animation: volumeRPlay 1.5s linear infinite;
}
@keyframes volumeLPlay{
0%{
left: 8rpx;
}
25%{
left: 8rpx;
}
50%{
left: 20rpx;
}
75%{
left: 28rpx;
}
100%{
left: 28rpx;
}
}
@keyframes volumeRPlay{
0%{
right: 8rpx;
}
25%{
right: 8rpx;
}
50%{
right: 20rpx;
}
75%{
right: 28rpx;
}
100%{
right: 28rpx;
}
}
}
.liveImg {
position: relative;
border-radius: 20rpx;
.ui-video-box, .ui-video-r-box{
position: relative;
.ui-vide-poster{
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
text-align: center;
}
.ui-video_play_icon{
width: 52rpx;
height:52rpx;
}
.ui-video_play-text{
width: 120rpx;
}
}
.ui-video-box{
width: auto;
border-radius: 16rpx;
}
.ui-video-r-box{
width: auto;
border-radius: 16rpx;
}
}
.ui-source-text {
width: fit-content;
border-radius: 0 8rpx 8rpx 0;
background: linear-gradient(90deg, rgba(221, 216, 250, 0.14) 0%, rgba(221, 216, 250, 1) 100%);
padding: 4rpx 16rpx 4rpx 36rpx;
margin-top: 10rpx;
float: right;
}
.ui-msg-right {
background: #FF7D9F;
}
.ui-copy-icon-box {
width: 54rpx;
height: 54rpx;
background: #ffffff;
border-radius: 50%;
position: absolute;
right: -70rpx;
top: 0;
.ui-copy-icon {
width: 32rpx;
height: 32rpx;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.ui-left-msg-radius {
border-radius: 0 32rpx 32rpx 32rpx;
}
.ui-right-msg-radius {
border-radius: 32rpx 0 32rpx 32rpx;
}
}
.ui-bottom-input-box {
width: 100vw;
padding: 32rpx 30rpx 72rpx 30rpx;
box-sizing: border-box;
background: #ffffff;
border-radius: 42rpx 42rpx 0 0;
box-shadow: 0 0 12rpx #f8f8f8;
position: fixed;
bottom: 0;
left: 50%;
transform: translateX(-50%);
z-index: 999;
.replyView{
width: 100vw;
position: fixed;
bottom: 180rpx;
left: 0;
padding: 32rpx;
z-index: 9999999;
.replyContent{
border-radius: 32rpx;
max-height: 78vh;
overflow: auto;
padding: 16rpx 26rpx;
background: #FFFFFF;
line-height: 42rpx;
letter-spacing: 4rpx;
word-break: break-all;
}
.main-content-box {
display: inline-block;
white-space: pre-line;
vertical-align: center;
align-items: center;
margin-top: 4rpx;
word-break: break-all; // 纯数字、单词换行
.m_rich {
margin-right: -4rpx;
}
.emoji_text {
align-items: center;
padding: 4rpx;
line-height: 40rpx;
.emoji_small {
width: 42rpx;
height: 42rpx;
vertical-align: middle;
}
}
}
}
}
.ui-input-box {
width: 85vw;
height: 72rpx;
line-height: 72rpx;
padding: 20rpx 76rpx 20rpx 26rpx;
margin-right: 20rpx;
background: #f5f5f7;
border-radius: 100rpx;
position: relative;
.ui-emoji-icon{
width: 44rpx;
height: 44rpx;
position: absolute;
right: 20rpx;
z-index: 9;
}
.ui-input {
width: 100%;
position: relative;
z-index: 3;
background: initial;
word-break: break-all;
}
}
.ui-send-box {
.ui-send-icon{
width: 0;
height: 66rpx;
transition: width .2s;
transition-timing-function: linear;
}
.ui-send-icon.active {
width: 90rpx;
height: 90rpx;
color: white;
border-radius: 12rpx;
}
.ui_add_icon{
width: 44rpx;
height: 44rpx;
transition-delay: .2s;
}
.ui_add_icon.active {
width: 0;
height: 44rpx;
transition-delay: 0s;
}
}
.ui-microphone-box{
margin-right: 20rpx;
width: 36rpx;
height: 44rpx;
.ui-microphone-image{
width: 36rpx;
height: 44rpx;
}
}
.ui-microphone-close,
.ui-microphone-open{
width: 100%;
text-align: center;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
}
.ui-microphone-open{
background: #FFFFFF;
border: 2rpx solid #E8E8E8;
}
.ui-microphone-close{
background: #E8E8E8;
}
.ui-microphone-message{
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
width: 320rpx;
height: 320rpx;
background: #757575;
border-radius: 24rpx;
text-align: center;
.ui-icon-change-stop .ui-icon-change:before{
animation-play-state: paused!important;
}
.ui-icon-box{
margin:86rpx 0 0 88rpx;
display: flex;
align-items: flex-end;
.ui-icon-microphone{
margin-right: 20rpx;
width: 76rpx;
height: 110rpx;
}
.ui-icon-change{
margin-bottom: 12rpx;
position: relative;
width: 52rpx;
height: 88rpx;
background-size: cover;
background-repeat: no-repeat;
background-image: url("https://image.fulllinkai.com/202308/15/f5461afc6ade909fa61a6be0b8e2a084.png");
overflow: hidden;
}
.ui-icon-change:before{
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 52rpx;
height: 88rpx;
background: #757575;
animation: volumeChange 2.5s linear infinite;
}
@keyframes volumeChange {
0%{
bottom: 0rpx;
}
16%{
bottom: 14rpx;
}
32%{
bottom: 28rpx;
}
48%{
bottom: 42rpx;
}
64%{
bottom: 56rpx;
}
78%{
bottom: 70rpx;
}
94%{
bottom: 88rpx;
}
100%{
bottom: 88rpx;
}
}
}
.ui-icon-text{
margin: 52rpx auto;
}
}
.m_inp_ct {
border: 0;
width: 100%;
height: 0rpx;
transition: height .2s;
-moz-transition: height .2s; /* Firefox 4 */
-webkit-transition: height .2s; /* Safari 和 Chrome */
-o-transition: height .2s; /* Opera */
transition-timing-function: linear;
overflow: hidden;
padding-top: 0rpx;
.u_xi {
width: 100%;
height: 2rpx;
background-color: #f5f5f7;
}
.u_imp_lst {
padding: 30rpx;
.m_impLst_img {
width: 100rpx;
height: 100rpx;
border-radius: 20rpx;
background-color: white;
margin-bottom: 8rpx;
}
}
}
.m_inp_ct.sel {
height: 200rpx;
padding-top: 30rpx;
}
.m_inp_ct.emojiSel {
height: 400rpx;
padding-top: 30rpx;
overflow: hidden;
}
.m_prog {
background-color: #a8a8a8;
border-radius: 30rpx;
width: 200rpx;
.conic-progress {
margin: 50rpx auto;
position: relative;
width: 100rpx;
height: 100rpx;
border-radius: 50%;
}
.conic-progress::before {
content: "";
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 86%;
height: 86%;
border-radius: 50%;
background-color: #a8a8a8;
overflow: hidden;
}
}
.ui-report-box{
border-radius: 30rpx 30rpx 0 0;
.ui-report-item, .ui-report-item-v2{
height: 102rpx;
border-top: 2rpx solid #f5f5f5;
}
.ui-report-item-v2{
border-top: 8rpx solid #f5f5f5;
}
}
.g_bt_tips_one,
.g_bt_tips_two,
.g_bt_tips_three {
position: fixed;
bottom: 0;
z-index: 999;
background-color: white;
width: 100vw;
padding: 30rpx 0 80rpx;
box-shadow: 0px -2rpx 8rpx 0rpx rgba(0, 0, 0, 0.03);
border-radius: 32rpx 32rpx 0rpx 0rpx;
.g_bt_tips_one_bu {
width: 300rpx;
height: 64rpx;
border-radius: 100rpx;
font-weight: 500;
margin-top: 40rpx;
background-image: linear-gradient(to right, #96CAFF, #63A5FF);
}
}
</style>
<template>
<view class="ui-chitchat">
<image v-show="!hiddenHtml" class="m_tips_icon" src="https://image.fulllinkai.com/202305/05/2172ea48286be34e3a4e80a8c2a4571a.png" mode="aspectFit" @tap="reportShow = true"></image>
<scroll-view class="ui-scroll-view" style="height: {{windowHeight - bottomBoxH}}rpx;" :scroll-y="scrollY" upper-threshold="50" bindscrolltoupper="getHistoryMsg" scroll-into-view="{{toView}}" enable-flex="{{true}}">
<view class="ui-top-placeholder"></view>
<block v-show="loading">
<image v-if="otherUserReal != 1" class="ui-tips-pic" src="https://image.fulllinkai.com/202203/22/2a0d6bba0289f28b8d816e4deb1efa4c.png" mode="widthFix"></image>
<view class="ui-tips-box">
<view class="font_28 color333 bold">
<image src="https://images.ufutx.com/202012/24/dcf6c6454e7bb280c07346dcdceef767.png" mode="widthFix" class="ui-tips_icon"></image>
聊天小贴士
</view>
<view class="font_24 ui-tips_text">
请各位会员自觉遵守法律法规,<span class="color-theme">遇到转款切勿相信,谨防上当受骗,</span>未实名认证的会员保持警惕;交友期保持必要的界限,保证自身安全,请勿轻易委身于人,<span class="color-theme">请勿和用户发生借贷关系,</span>否则后果自己承担。
</view>
</view>
</block>
<view class="ui-msg-data-box" v-show="msgList.length > 0">
<!-- <view class="text-center font_2 ui-pb-24 color666" v-if="finished">没有更多消息了</view>-->
<view v-for="(item, index) in msgList" :key="index" class="ui-msg-data" id="{{item.id}}">
<view class="text-center color999 font_24 ui-pb-16" v-if="item.showTime">{{item.time}}</view>
<view class="ui-relative {{!item.isSelf ? 'ui-left-msg-box f-fl' : 'ui-right-msg-box f-fr'}}">
<image v-if="item.isSelf && item.isSend == 2" class="ui-error-icon" src="https://image.fulllinkai.com/202408/16/26bce9092cd2c703f7d5b2d56ef9d132.png" mode="widthFix"></image>
<image v-if="!item.isSelf && item.isSend == 2" class="ui-error-icon-v2" src="https://image.fulllinkai.com/202408/16/26bce9092cd2c703f7d5b2d56ef9d132.png" mode="widthFix"></image>
<image v-if="!item.isSelf" class="ui-user-pic" :src="item.avatar" mode="aspectFill"></image>
<view class="{{item.isSelf ? 'ui-pr-12' : 'ui-pl-12'}}">
<view class="ui-relative">
<!--文本加表情信息-->
<view class="{{!item.isSelf ? 'ui-left-msg-radius ui-msg-left color3' : 'ui-right-msg-radius ui-msg-right white'}} font_28" v-if="item.msgType == 'text' || item.msgType == 'TEXT'">
<rich-text nodes="{{item.text}}"></rich-text>
</view>
<!--图片信息-->
<image v-if="item.msgType == 'image' || item.msgType == 'PICTURE'" class="ui-msg-left-pic" :style="{maxWidth: item.attach.w + 'rpx', maxHeight: item.attach.h + 'rpx'}" :src="item.attach.url" mode="aspectFill" @tap.stop="previewImage(item.attach.url)"></image>
<!--音频信息-->
<view v-if="item.msgType == 'audio' || item.msgType == 'AUDIO'" class="{{!item.isSelf ? 'ui-left-msg-radius ui-left-audio' : 'ui-right-msg-radius ui-right-audio white' }} ui-audio-box font_36 f-fc" :style="{width: item.audioDur < 20 ? '120rpx' :item.audioDur < 40 ? '180rpx' : '320rpx', maxWidth: '320rpx!important', minWidth: '120rpx!important'}" @tap.stop="playAudio(item.attach, index)">
<image v-if="!item.isSelf" class="{{!audioState || playAudioIndex != index ? 'ui-audio-icon' : 'ui-audio-icon-l-play'}}" src="https://image.fulllinkai.com/202308/09/a0e320ef4f498309745d35f6798e970f.png" mode="aspectFit" lazy-load="false"></image>
<image v-else class="{{!audioState || playAudioIndex != index ? 'ui-audio-icon' : 'ui-audio-icon-r-play'}}" src="https://image.fulllinkai.com/202308/09/5514071f2d388102b5672e46ea1922f4.png" mode="aspectFit" lazy-load="false"></image>
<view v-if="!audioState || playAudioIndex != index">{{item.audioDur+ '"'}}</view>
<view v-else>{{audioTime + '"'}}</view>
</view>
<!--视频信息-->
<view v-if="item.msgType == 'video' || item.msgType == 'VIDEO'" :style="{width: item.attach.w + 'rpx', height: item.attach.h + 'rpx'}" class="liveImg" mode="widthFix" @tap.stop="playVideo(index)">
<image v-if="videoIndex != index" class="ui-video-box" :src="item.attach.poster" :style="{width: item.attach.w + 'rpx', height: item.attach.h + 'rpx'}">
<view class="ui-vide-poster">
<image class="ui-video_play_icon" src="https://image.fulllinkai.com/202308/17/42c88bf039bb56f85cfcb9af0392ffb6.png"></image>
<view class="ui-video_play-text font_20 white">{{item.videoDur}}</view>
</view>
</image>
<video id="chatVideo-{{index}}" :src="item.attach.url" v-if="videoIndex == index" play-btn-position="center" show-fullscreen-btn="{{false}}" controls custom-cache="{{true}}" direction="0" object-fit="{{videoIndex == index ? 'contain' : 'cover'}}" referrer-policy="origin" bindfullscreenchange="leaveVideo" class="videoCenter ui-video-r-box" :style="{width: item.attach.w + 'rpx', height: item.attach.h + 'rpx'}" :key="index">
</video>
</view>
</view>
</view>
<image v-if="item.isSelf" class="ui-user-pic" :src="item.avatar" mode="aspectFill"></image>
</view>
</view>
</view>
</scroll-view>
<view class="ui-bottom-input-box" v-show="!hiddenHtml">
<!--输入文本加表情预览信息-->
<view class="replyView f-fcc animation-slide-bottom" v-if="msg.length > 17 && showReplyView" style="bottom: {{inputBoxH}}px;">
<view class="replyContent main-content-box" bindlongpress="copy" data-text="{{msg}}">
<rich-text nodes="{{replyContent}}"></rich-text>
</view>
</view>
<view class="f-fbc">
<view class="ui-microphone-box">
<image class="ui-microphone-image" @tap.stop="changeMicrophone" src="https://image.fulllinkai.com/202308/05/a6adc00602e0b26aa642c2551c84bf03.png" mode="aspectFit" lazy-load="false"></image>
</view>
<block v-if="!microphoneShow">
<view class="ui-input-box f-fcl">
<image class="ui-emoji-icon" src="https://images.ufutx.com/202108/03/2f4661c8d19e56faa068ffac52ac9762.png" mode="widthFix" @tap="changeSelectEmoji"></image>
<input class="ui-input font_28 color3 text-left" confirm-hold="{{true}}" v-model="msg" type="text" maxlength="-1" placeholder="真诚一些,收获好感..." confirm-type="send" cursorSpacing="26" bindinput="changeMsg" bindenter="changeMsg" bindfocus="InputFocus" @confirm="send">
</view>
<view class="ui-send-box f-fcr">
<image class="font_32 ui-send-icon f-fcc {{msg ? 'active' : ''}}" @tap="send" src="https://image.fulllinkai.com/202305/05/4a47a0db6e60853dedfcfdf08a5ca249.png" mode="aspectFit" lazy-load="false"></image>
<image class="ui_add_icon {{msg ? 'active' : ''}}" @tap="changeSelectPic" src="https://images.ufutx.com/202107/20/055e2a820dd94b6eb9fcb09db3622ec7.png" mode="aspectFit" lazy-load="false"></image>
</view>
</block>
<block v-else>
<view class="{{microphoneType ? 'ui-microphone-close' : 'ui-microphone-open color-333 font_32'}}" @longpress="handleRecordStart" @touchmove="handleTouchMove" @touchend.stop="handleRecordStop">{{ mircrophoneType ? '松开结束' :'按住说话' }}</view>
</block>
</view>
<view class="m_inp_ct {{emojiShow ? 'emojiSel' : ''}}">
<block v-if="emojiShow">
<chatEmoji :emojiArray="emojiList.emoji" @selectEmoji="selectEmoji"></chatEmoji>
</block>
</view>
<view class="m_inp_ct {{openShow ? 'sel' : ''}}">
<view class="u_xi"></view>
<view class="f-fc">
<view class="u_imp_lst" v-for="(item,index) in operationList" :key="index" @tap="changeOperation(item.title)">
<view class="m_impLst_img f-fcc">
<image src="{{item.icon}}" mode="aspectFit" lazy-load="false"></image>
</view>
<view class="color-333 text-center font_22">{{ item.title }}</view>
</view>
</view>
</view>
</view>
</view>
<!--长按录音-->
<view v-if="microphoneType" class="ui-microphone-message">
<view class="{{sendLock ? 'ui-icon-change-stop' : ''}} ui-icon-box">
<image class="ui-icon-microphone" @tap="changeMicrophone" src="https://image.fulllinkai.com/202308/05/97e1aa711ec3bf688f0853ea8d3444f0.png" mode="aspectFit" lazy-load="false"></image>
<view class="ui-icon-change"></view>
</view>
<view class="ui-icon-text white font_24">{{sendLock ? '松开手指,取消发送' : '手指上滑,取消发送'}}</view>
</view>
<!--上传进度展示-->
<view class="cu-modal {{uploadState ? 'show' : ''}}" style="background: rgba(0,0,0,0);">
<view class="cu-dialog m_prog">
<view class="conic-progress" style="background-image: conic-gradient(#fff {{progressValue}}%,#a8a8a8 0%);">
</view>
</view>
</view>
<view class="cu-modal bottom-modal" :class="{'show' : reportShow}" @tap="reportShow = false">
<view class="cu-dialog dialog ui-pb-40 ui-report-box">
<view class="f-fcc font_28 color333 ui-report-item" @tap="deleteFriends">删除好友</view>
<view class="f-fcc font_28 color333 ui-report-item" @tap="shield">拉黑并屏蔽</view>
<view class="f-fcc font_28 color333 ui-report-item" @tap="report">举报用户</view>
<view class="f-fcc font_28 color999 ui-report-item-v2" @tap="reportShow = false">取消</view>
</view>
</view>
<view class="g_bt_tips_two" v-if="userInfo.is_real_approved == 2 && !hiddenHtml">
<view class="font_24 color333 text-center ui-pr-40 ui-pl-40">认证已提交审核审核结果将在13个工作日内通知请耐心等待如有需要请直接联系客服
<text class="color-theme">18922809346</text>
</view>
</view>
<view class="g_bt_tips_one f-fcc f-fdc" v-else-if="userInfo.is_real_approved != 1 && !hiddenHtml">
<view class="font_24 color333">
<view class="text-center">你还未真人认证,为了安全起见,</view>
<view class="text-center">需要双方认证后才能畅聊,赶快去认证吧!</view>
</view>
<view class="g_bt_tips_one_bu f-fcc colorF" @tap="jumpPath(`/pages/users/realName?chat_user_id=${otherUserId}`)">一键认证
</view>
</view>
</template>
<script>
import wepy from '@wepy/core'
import https from '../../mixins/https'
import base from '../../mixins/base'
import {service} from '../../config'
import {getTime, format, timeContrast, getVideoTime} from '../../mixins/plugins'
import emojiObj from '../../components/chatEmojiFile/emoji'
const app = getApp().$wepy.$options
wepy.page({
config: {},
mixins: [https, base],
data: {
loading: false,
userInfo: {},
myAvatar: '',
myUserId: '',
myName: '',
myType: '',
otherAvatar: '',
otherUserId: '',
otherUserType: '',
otherUserName: '',
otherUserReal: 0, // 对方是否真人认证
reportShow: false, // 举报
throttle: true,
recorderManager: wx.getRecorderManager(),
startPoint: 0,
scrollY: true,
sendLock: true, // audio发送锁
microphoneShow: false,
microphoneType: false, // 按住或松开
openShow: false,
emojiList: [],
emojiShow: false,
emojiType: 'session',
operationList: [
{
icon: 'https://image.fulllinkai.com/202308/09/ef0d4e64d4f6fdfbe0f0ef21723e4d7b.png',
title: '图片'
},
{
icon: 'https://image.fulllinkai.com/202308/09/05550f0306f9b6057ea42ed3e4246a0e.png',
title: '相机'
},
{
icon: 'https://image.fulllinkai.com/202308/09/5154e58fe8f46a7a26a8bf6c507c6def.png',
title: '视频'
}
],
videoIndex: -1,
videoContext: null,
videoState: null,
hiddenHtml: null,
playAudioIndex: -1,
audioTime: null,
audioState: null,
toView: '',
uploadState: false,
progressValue: 0, // 上传进度
lastId: '', // 历史记录分页数据最后一条id
firstTime: '', // 历史记录分页数据第一条的时间戳
lastTime: '', // 历史记录分页数据最后一条的时间戳
lastMsgId: '', // 历史记录分页数据最后一条的id
beginTime: '', // 历史记录分页数据最后一条的时间
finished: false, // 数据已全部获取完成
msgList: [],
showReplyView: true,
inputBoxH: 86,
bottomBoxH: 0,
windowHeight: 0,
replyContent: '',
msg: ''
},
methods: {
// 发送文本和表情包消息
send() {
let vm = this
let timeData = new Date().getTime()
if (!vm.msg) {
vm.$showToast('请输入聊天内容')
return
}
let msg = vm.msg
if (/\[[^\]]+\]/.test(vm.msg)) {
msg = vm.transitionMsg(vm.msg)
}
vm.msgList.push({
text: msg,
attach: '',
id: `id_${timeData}`,
isSelf: true,
avatar: vm.myAvatar,
name: vm.myName,
userId: vm.myUserId,
isSend: 0,
msgType: 'text'
})
vm.toView = `id_${timeData}`
let msgIndex = 0
let msgNewObj = {}
msgIndex = vm.msgList.findIndex((e) => e.timestamp == timeData)
app.globalData.nim.msg.sendTextMsg({
scene: 'p2p',
to: vm.otherUserId,
body: vm.msg,
isSend: 0,
onSendBefore: function (msg) {
console.log('get msg before', msg)
if (/\[[^\]]+\]/.test(vm.msg)) {
vm.msg = vm.transitionMsg(vm.msg)
}
msgNewObj = {
text: vm.msg,
attach: '',
id: `id_${msg.time}`,
isSelf: true,
avatar: vm.myAvatar,
time: getTime(format(msg.time)),
showTime: timeContrast(vm.firstTime, format(msg.time)),
name: vm.myName,
userId: msg.from,
isSend: 1,
msgType: 'text'
}
setTimeout(() => {
vm.msgList.splice(msgIndex, 1, msgNewObj)
vm.firstTime = format(msg.time)
vm.lastTime = format(msg.time)
vm.callbackSend('text')
})
}
})
},
// 发送图片消息
sendPictures(file) {
let vm = this
vm.openShow = false
vm.uploadState = true
vm.progressValue = 5
app.globalData.nim.msg.sendImageMsg({
scene: 'p2p',
to: vm.otherUserId,
type: 'image',
filePath: file,
onUploadProgress: function (progress) {
console.log(progress, '上传进度')
vm.progressValue = progress.percentage
},
onSendBefore: function (msg) {
console.log(msg, '上传完成, 图片信息')
vm.msgList.push({
text: '',
attach: vm.calculatePic(msg.attach, msg.type),
id: `id_${msg.time}`,
isSelf: true,
avatar: vm.myAvatar,
time: getTime(format(msg.time)),
showTime: timeContrast(vm.firstTime, format(msg.time)),
name: msg.fromNick,
userId: msg.from,
isSend: msg.status == 'sendFailed' ? 2 : 1, // sendFailed 发送失败
msgType: 'image'
})
vm.firstTime = format(msg.time)
vm.lastTime = format(msg.time)
vm.callbackSend('picture')
vm.$nextTick(() => {
vm.toView = `id_${msg.time}`
})
// 防止图片撑开触发加载历史数据
vm.$nextTick(() => {
setTimeout(() => {
vm.uploadState = false
}, 500)
})
}
})
},
// 发送视频
sendVideos(file) {
let vm = this
vm.openShow = false
vm.uploadState = true
vm.progressValue = 5
app.globalData.nim.msg.sendVideoMsg({
scene: 'p2p',
to: vm.otherUserId,
type: 'video',
filePath: file,
onUploadProgress: function (progress) {
console.log(progress, '上传进度')
vm.progressValue = progress.percentage
},
onSendBefore: function (msg) {
console.log(msg, '上传完成,视频信息')
vm.msgList.push({
text: '',
attach: vm.calculatePic(msg.attach, msg.type),
id: `id_${msg.time}`,
timestamp: msg.time,
isSelf: true,
avatar: vm.myAvatar,
time: getTime(format(msg.time)),
showTime: timeContrast(vm.firstTime, format(msg.time)),
videoDur: getVideoTime(msg.attach.dur),
name: msg.fromNick,
userId: msg.from,
isSend: 1,
msgType: 'video'
})
vm.firstTime = format(msg.time)
vm.lastTime = format(msg.time)
vm.callbackSend('video')
vm.$nextTick(() => {
vm.toView = `id_${msg.time}`
})
// 防止视频撑开触发加载历史数据
vm.$nextTick(() => {
setTimeout(() => {
vm.uploadState = false
}, 500)
})
}
})
},
// 发送语音
sendAudio(file) {
let vm = this
vm.openShow = false
vm.uploadState = true
vm.progressValue = 5
app.globalData.nim.msg.sendAudioMsg({
scene: 'p2p',
to: vm.otherUserId,
type: 'audio',
filePath: file,
onUploadProgress: function (progress) {
console.log(progress, '上传进度')
vm.progressValue = progress.percentage
},
onSendBefore: function (msg) {
console.log(msg, '上传完成,音频信息')
vm.msgList.push({
text: '',
attach: msg.attach,
id: `id_${msg.time}`,
timestamp: msg.time,
isSelf: true,
avatar: vm.myAvatar,
time: getTime(format(msg.time)),
showTime: timeContrast(vm.firstTime, format(msg.time)),
videoDur: getVideoTime(msg.attach.dur),
audioDur: (msg.attach.dur / 1000).toFixed(0),
name: msg.fromNick,
userId: msg.from,
isSend: 1,
msgType: 'audio'
})
vm.firstTime = format(msg.time)
vm.lastTime = format(msg.time)
vm.callbackSend('audio')
vm.$nextTick(() => {
vm.toView = `id_${msg.time}`
// 防止音频撑开触发加载历史数据
setTimeout(() => {
vm.uploadState = false
}, 500)
})
},
done: sendMsgDone
})
function sendMsgDone(error, file) {
console.log('上传' + (!error ? '成功' : '失败'), file)
}
},
// 发送图片或视频失败
sendFileError() {
let vm = this
let timeData = new Date().getTime()
vm.msgList.push({
text: '',
attach: {url: 'https://image.fulllinkai.com/202408/19/fc68d5ba0d7c952f08e2ae3f04ed70a9.png', w: 200, h: 162},
id: `id_${timeData}`,
isSelf: true,
avatar: vm.myAvatar,
name: vm.myName,
userId: vm.myUserId,
isSend: 2,
msgType: 'image'
})
vm.firstTime = format(timeData)
vm.lastTime = format(timeData)
vm.$nextTick(() => {
vm.toView = `id_${timeData}`
// 防止音频撑开触发加载历史数据
setTimeout(() => {
vm.uploadState = false
}, 500)
})
},
// 发送信息后回调
callbackSend(type) {
let vm = this
let data = {
other_user_id: vm.otherUserId * 1,
field_7: vm.msg,
field_6: type
}
vm.$post({url: `${service.host}/chat/message/send`, data}).then(() => {
wx.hideLoading()
vm.msg = ''
vm.emojiShow = false
vm.replyContent = ''
}).catch(() => {
vm.uploadState = false
wx.hideLoading()
})
},
// 标记信息已读
sendMsgReceipt(e) {
app.globalData.nim.msg.sendMsgReceipt({
msg: e
})
},
// 在当前页面接收到别人发送过来的消息
reception(e) {
let vm = this
vm.sendMsgReceipt(e)
if (e.type == 'text' && /\[[^\]]+\]/.test(e.body)) {
e.body = vm.transitionMsg(e.body)
}
vm.msgList.push({
text: e.body,
attach: vm.calculatePic(e.attach, e.type),
id: `id_${e.time}`,
timestamp: e.time,
isSelf: false,
avatar: vm.otherAvatar,
time: getTime(format(e.time)),
showTime: timeContrast(vm.firstTime, format(e.time)),
videoDur: e.type == 'video' ? getVideoTime(e.attach.dur) : '',
audioDur: e.type == 'audio' ? (e.attach.dur / 1000).toFixed(0) : '',
name: e.fromNick,
userId: e.from,
isSend: 1,
msgType: e.type
})
vm.toView = `id_${e.time}`
vm.firstTime = format(e.time)
vm.lastTime = format(e.time)
},
// 获取历史消息数据
getHistoryMsg() {
let vm = this
let data = {
min_id: vm.lastId
}
if (vm.uploadState || vm.videoState) {
return
}
if (vm.finished) {
return
}
vm.$showLoading('')
vm.$get({url: `${service.host}/chat/user/${vm.otherUserId}/message/list`, data}).then(({code, data}) => {
if (code === 0) {
if (data && data.length > 0) {
data.forEach((item, index) => {
item.time = new Date(item.create_time.replace(/[-]/g, '/').replace(/[-]/, '')).getTime()
item.attach = item.attach ? JSON.parse(item.attach) : ''
item.showTime = timeContrast(format(item.time), vm.lastTime)
item.timing = getTime(format(item.time))
if (item.showTime && index > 0) {
data[index].showTime = false
data[index - 1].showTime = true
}
if ((item.content_type == 'text' || item.content_type == 'TEXT') && /\[[^\]]+\]/.test(item.content)) {
item.body = vm.transitionMsg(item.content)
} else {
item.body = item.content
}
if (!vm.lastId) {
vm.firstTime = format(data[0].time)
}
vm.lastTime = format(item.time)
})
vm.lastMsgId = data[data.length - 1].id
vm.beginTime = data[data.length - 1].time
setTimeout(() => {
data.forEach((item) => {
vm.msgList.unshift({
text: item.body,
attach: vm.calculatePic(item.attach, item.content_type),
id: `id_${item.time}`,
timestamp: item.time,
isSelf: item.is_mine == 0 ? false : true,
avatar: item.is_mine == 0 ? vm.otherAvatar : vm.myAvatar,
time: item.timing,
showTime: item.showTime,
videoDur: item.content_type == 'video' || item.content_type == 'VIDEO' ? getVideoTime(item.attach.dur) : '',
audioDur: item.content_type == 'audio' || item.content_type == 'AUDIO' ? (item.attach.dur / 1000).toFixed(0) : '',
name: item.is_mine == 0 ? vm.otherUserName : vm.myName,
isSend: 1,
userId: item.user_id,
msgType: item.content_type
})
})
console.log(vm.msgList)
vm.$nextTick(() => {
vm.toView = `id_${data[0].time}`
if (vm.msgList.length > 15) {
vm.scrollY = false
}
vm.lastId = `${data[data.length - 1].id}`
vm.loading = true
// 防止撑开触发加载历史数据
setTimeout(() => {
vm.uploadState = false
vm.scrollY = true
}, 500)
})
})
} else {
vm.loading = true
vm.scrollY = true
}
if (data && data.length < 15) {
vm.finished = true
}
}
}).catch(err => {
wx.hideLoading()
console.log(err)
})
},
// 删除好友
deleteFriends() {
let vm = this
wx.showModal({
title: '提示',
content: '是否确认删除该好友',
success: function (res) {
if (res.confirm) {
vm.$showLoading('')
vm.$post({url: `${service.host}/friend/users/${vm.otherUserId}`}).then(({code, data}) => {
if (code == 0) {
vm.$showToast(`好友已删除`)
setTimeout(() => {
wx.switchTab({url: '/pages/tabBar/news'})
}, 1200)
}
wx.hideLoading()
}).catch(() => {
wx.hideLoading()
})
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
},
// 加入黑名单并屏蔽
shield() {
let vm = this
wx.showModal({
title: '加入黑名单',
content: '拉黑后,对方将无法访问、关注、发送好友请求,也无法给你发消息,可以在【设置-黑名单】中取消',
success: function (res) {
if (res.confirm) {
vm.$showLoading('')
vm.$post({url: `${service.host}/blacklist/friends/${vm.otherUserId}`}).then(({code, data}) => {
if (code == 0) {
vm.$showToast(`已加入黑名单`)
setTimeout(() => {
wx.switchTab({url: '/pages/tabBar/news'})
}, 1200)
}
wx.hideLoading()
}).catch(() => {
wx.hideLoading()
})
} else if (res.cancel) {
console.log('用户点击取消')
}
}
})
},
// 举报
report() {
let vm = this
vm.reportShow = false
wx.navigateTo({url: `/pages/users/report?id=${vm.otherUserId}&type=details`})
},
// 监听底部输入框的高度
changeInputHeight(e) {
let vm = this
let query = wx.createSelectorQuery()
query.select('.ui-bottom-input-box').boundingClientRect((res) => {
if (res) {
vm.inputBoxH = res.height
}
if (e) {
vm.bottomBoxH = res.height * 2
}
}).exec()
},
// 监听输入框内容
changeMsg(e) {
console.log(e, '8888')
let vm = this
if (/\[[^\]]+\]/.test(e.$wx.detail.value)) {
vm.replyContent = vm.transitionMsg(e.$wx.detail.value)
} else {
vm.replyContent = e.$wx.detail.value
}
vm.changeInputHeight()
},
// 输入框聚焦事件
InputFocus() {
let vm = this
vm.emojiShow = false
vm.openShow = false
vm.showReplyView = false
setTimeout(() => {
vm.changeInputHeight()
vm.showReplyView = true
}, 500)
},
// 录音授权
changeMicrophone() {
let vm = this
wx.authorize({
scope: 'scope.record',
success() {
console.log('录音授权成功')
// 第一次成功授权后
vm.microphoneShow = !vm.microphoneShow
},
fail() {
wx.showModal({
title: '提示',
content: '您未授权录音,功能将无法使用',
showCancel: true,
confirmText: '授权',
confirmColor: '#52a2d8',
success: function (res) {
if (res.confirm) {
wx.openSetting({
success: (res) => {
if (!res.authSetting['scope.record']) {
// 未设置录音授权
wx.showModal({
title: '提示',
content: '您未授权录音,功能将无法使用',
showCancel: false,
success: function (res) {
}
})
} else {
// 第二次成功授权
vm.microphoneShow = !vm.microphoneShow
}
},
fail: function () {
console.log('授权设置录音失败')
}
})
} else if (res.cancel) {
console.log('cancel')
}
}
})
}
})
},
handleRecordStart(e) { // 长按语音
let vm = this
const options = {
sampleRate: 16000, // 采样率
numberOfChannels: 1, // 录音通道数
encodeBitRate: 96000, // 编码码率
format: 'mp3', // 音频格式,有效值 aac/mp3
frameSize: 50// 指定帧大小,单位 KB
}
vm.recorderManager.start(options)
vm.startPoint = e.touches[0]// 记录长按时开始点信息,后面用于计算上划取消时手指滑动的距离。
vm.microphoneType = true
vm.sendLock = false // 长按时是不上锁的。
vm.recorderManager.onStart(() => {
console.log('recorder start')
})
vm.recorderManager.onError((err) => {
console.log(err, 'err--')
})
},
handleTouchMove(e) { // 上滑取消
let vm = this
let moveLenght = e.touches[e.touches.length - 1].clientY - vm.startPoint.clientY // 移动距离
vm.sendLock = Math.abs(moveLenght) > 50
},
// 松开发送语音
handleRecordStop() {
let vm = this
vm.microphoneType = false
vm.recorderManager.stop()
vm.recorderManager.onStop((res) => {
console.log(res, 'res----')
console.log(res.tempFilePath, 'res----')
if (!vm.sendLock) {
vm.sendAudio(res.tempFilePath)
}
})
vm.recorderManager.onError((err) => {
console.log(err, 'err--')
})
},
// 展示/关闭弹出表情包
changeSelectEmoji() {
let vm = this
vm.showReplyView = false
vm.openShow = false
vm.emojiShow = !vm.emojiShow
setTimeout(() => {
vm.changeInputHeight()
vm.showReplyView = true
}, 500)
},
// 展示/关闭弹出选择图片
changeSelectPic() {
let vm = this
vm.emojiShow = false
vm.openShow = !vm.openShow
setTimeout(() => {
vm.changeInputHeight()
}, 500)
},
// 选择表情包
selectEmoji(e) {
let vm = this
vm.msg = vm.msg + e
if (/\[[^\]]+\]/.test(vm.msg)) {
vm.replyContent = vm.transitionMsg(vm.msg)
}
vm.changeInputHeight()
},
// 转换msg表情数据
transitionMsg(e) {
let vm = this
let emojiItems = e.match(/\[[^\]]+\]/g)
emojiItems.forEach(text => {
let emojiCnt = vm.emojiList.emoji.list
emojiCnt.forEach(emItem => {
if (emItem.key == text) {
e = e.replace(text, `<img class="emoji_small" src="${emItem.img}" />`)
}
})
})
e = '<div class="emoji_text">' + e + '</div>'
return e
},
// 获取emoji表情包数据
emojiCts() {
return this.genEmojiList('emoji', emojiObj.emojiList)
},
// 获取表情包
genEmojiList(type, emojiList) {
let result = {}
for (let name in emojiList) {
let emojiMap = emojiList[name]
let list = []
for (let key in emojiMap) {
list.push({
type,
name,
key,
img: emojiMap[key].img
})
}
if (list.length > 0) {
result[name] = {
type,
name,
list,
album: list[0].img
}
}
}
return result
},
// 图片大小计算
calculatePic(e, type) {
if (!e) {
return ''
}
let wRatio = 0
if ((type == 'video' || type == 'VIDEO') && e.url && e.url.includes('?')) {
e.poster = `${e.url}&vframe`
} else if ((type == 'video' || type == 'VIDEO') && e.url) {
e.poster = `${e.url}?vframe`
}
if (e.w > e.h) {
wRatio = e.w / 200
e.w = 200
e.h = e.h / wRatio
} else {
wRatio = e.h / 200
e.w = e.w / wRatio
e.h = 200
}
return e
},
// 更多操作
changeOperation(title) {
let vm = this
if (title == '图片') {
vm.onAlbum(['album'])
} else if (title == '相机') {
vm.onAlbum(['camera'])
} else {
vm.chooseVideo()
}
},
// 选择图片发送
onAlbum(sourceType) {
let vm = this
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType, // 可以指定来源是相册还是相机,默认二者都有
success: (res) => {
console.log('res--------------------------', res)
let imgSrc = res.tempFiles[0].tempFilePath
vm.sendPictures(imgSrc)
}
})
},
// 选择视频
chooseVideo() {
let vm = this
wx.chooseMedia({
count: 1,
mediaType: ['video'],
sizeType: ['compressed'],
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: (res) => {
console.log('res--------------------------', res)
let imgSrc = res.tempFiles[0].tempFilePath
vm.sendVideos(imgSrc)
}
})
},
// 播放视频
playVideo(index) {
let vm = this
vm.videoState = true
vm.hiddenHtml = true
vm.videoIndex = index
vm.videoContext = wx.createVideoContext('chatVideo-' + index, vm)
vm.videoContext.requestFullScreen({direction: 0})
setTimeout(() => {
// 将点击视频进行播放
vm.videoContext.play()
}, 500)
},
// 关闭视频
leaveVideo(e, row) {
let vm = this
let fullScreen = e.$wx.detail.fullScreen
vm.videoState = true
vm.hiddenHtml = true
if (!fullScreen) {
vm.toView = `${row}`
vm.videoContext.stop()
vm.videoContext = null
vm.videoIndex = -1
vm.hiddenHtml = false
setTimeout(() => {
vm.videoState = false
}, 500)
}
},
// 播放音频
playAudio(row, index) {
let vm = this
vm.audioState = !vm.audioState
vm.playAudioIndex = index
const innerAudioContext = wx.createInnerAudioContext({
useWebAudioImplement: false // 是否使用 WebAudio 作为底层音频驱动,默认关闭。对于短音频、播放频繁的音频建议开启此选项,开启后将获得更优的性能表现。由于开启此选项后也会带来一定的内存增长,因此对于长音频建议关闭此选项
})
let myTime = null
if (vm.audioState) {
innerAudioContext.src = row.url
innerAudioContext.play() // 播放
vm.audioTime = 0
myTime = setInterval(() => {
vm.audioTime += 1
if (vm.audioTime * 1000 > row.dur) {
clearInterval(myTime)
vm.audioState = false
vm.playAudioIndex = -1
}
}, 1000)
} else {
clearInterval(myTime)
vm.audioState = false
vm.playAudioIndex = -1
}
},
// 预览图片
previewImage(imge) {
let vm = this
let imageArr = []
vm.msgList.forEach((item) => {
if (item.msgType == 'image' || item.msgType == 'PICTURE') {
imageArr.push(item.attach.url)
}
})
// 倒序处理阅览顺序不对的问题
imageArr = imageArr.reverse()
vm.$previewImages(imge, imageArr)
},
jumpPath(url) {
wx.navigateTo({url: url})
}
},
onShow() {
let vm = this
vm.userInfo = wx.getStorageSync('userInfo')
vm.myUserId = wx.getStorageSync('user_id')
vm.myType = wx.getStorageSync('userInfo').type
vm.myName = wx.getStorageSync('userInfo').name
vm.myAvatar = wx.getStorageSync('userInfo').avatar
},
onLoad(e) {
let vm = this
vm.otherAvatar = e.pic
vm.otherUserId = e.id
vm.otherUserType = e.type
vm.otherUserReal = e.real
vm.otherUserName = decodeURIComponent(e.name)
// 获取表情包数据
vm.emojiList = vm.emojiCts()
// 获取手机屏幕高度
wx.getSystemInfo({
success: (res) => {
vm.windowHeight = res.windowHeight * 2
}
})
// 底部输入框高度
vm.changeInputHeight(true)
// vm.$nextTick(() => {
// setTimeout(() => {
// // 获取历史消息
// vm.getHistoryMsg()
// // 实时获取对方发送的消息
// app.globalData.nim.on('msg', function (e) {
// vm.reception(e)
// })
// }, 3000)
// })
vm.$nextTick(() => {
// 获取历史消息
vm.getHistoryMsg()
// 实时获取对方发送的消息
app.globalData.nim.on('msg', function (e) {
vm.reception(e)
})
})
wx.setNavigationBarTitle({
title: vm.otherUserName,
success: function () {}
})
},
created() {
}
})
</script>
<config>
{
navigationBarTitleText: '聊天',
enablePullDownRefresh: false,
backgroundColorTop: '#f2f2f2',
backgroundColorBottom: '#f2f2f2',
usingComponents: {
chatEmoji: '~@/components/chatEmojiFile/ChatEmoji'
}
}
</config>