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.
1947 lines
62 KiB
1947 lines
62 KiB
<template>
|
|
<view>
|
|
<!-- <uni-notice-bar show-icon scrollable showClose text="请注意防骗" style="margin: 0;z-index:1000;position: absolute;"/> -->
|
|
<z-paging ref="paging" :style="{backgroundImage:'url('+bgInfo.image+')'}" class="backsize" v-model="messageList" hide-empty-view auto-show-back-to-top use-chat-record-mode safe-area-inset-bottom bottom-bg-color="#f8f8f8" @query="scrollChat">
|
|
<template #top>
|
|
<cu-custom bgColor="bg-white" :isBack="true" class="cu-header">
|
|
<template #backText>
|
|
<view class="back-unread" v-if="unread>0 && unread<100">{{unread}}</view>
|
|
<view class="back-unread" v-if="unread>100">99+</view>
|
|
</template>
|
|
<template #content>
|
|
<view class="im-flex im-justify-content-center im-align-items-center">
|
|
<view style="width: 15px;"><statusPoint v-if="is_group==0 && contact.is_online==1 && globalConfig.chatInfo.online==1" type="success"></statusPoint></view>
|
|
<text class="text-overflow">{{contact.displayName}}</text>
|
|
<text v-if="is_group==1">({{groupInfo.groupUserCount ?? 0}})</text>
|
|
</view>
|
|
</template>
|
|
<template #right>
|
|
<view class="cuIcon-more mr-10 f-16" v-if="is_group!=2&&is_group!=4" @tap="openDetails"></view>
|
|
</template>
|
|
</cu-custom>
|
|
</template>
|
|
<view style="height: 85vh;" v-if="messageList.length==0" @click="closeInput"></view>
|
|
<view class="cu-chat" :style="{paddingBottom:paddingB+'px'}" @click="closeInput" v-else><!-- id="more-oprate" -->
|
|
<!-- <uni-load-more :status="loading" v-if="page!==1"></uni-load-more> -->
|
|
<template v-for="(item,index) in messageList" :key="index" :id="'chatItem_'+index">
|
|
<view class="cu-info" style="transform: scaleY(-1);display: flex;" v-if="item.type=='event'">
|
|
<!-- #ifdef H5 -->
|
|
<text style="display: flex;align-items: center;" v-html="item.content"></text>
|
|
<!-- #endif -->
|
|
<!-- #ifdef APP -->
|
|
<text>{{stripHtmlTags(item.content)}}</text>
|
|
<!-- #endif -->
|
|
<text class="c-primary" style="margin-left: 10px;" v-if="item.is_undo==1 && (getTime() - item.sendTime) < globalConfig.chatInfo.redoTime*1000" @tap="reEdit(item.oldContent ?? '')">重新编辑</text>
|
|
</view>
|
|
<template v-else>
|
|
<view class="cu-item" :class="[item.fromUser.id==user.user_id ? 'im-rows-reverse self im-justify-content-start' : '' ]" style="transform: scaleY(-1);">
|
|
<im-user :info="item.fromUser" :profile="isProfile" @longpress="at(item.fromUser)"></im-user>
|
|
<view class="main im-wrap" :class="[item.fromUser.id==user.user_id ? 'im-rows-reverse' : '' ]" @touchstart="moreOption($event,item,index)" @touchmove="ListTouchMove" @touchend="endTimer" @tap="dblclick(item)">
|
|
<view class="message-head f-12 c-666">
|
|
<!-- <uni-tag class="mr-5" v-if="item.role<3 && item.fromUser.id!=user.user_id" :type="item.role==1 ? 'warning' : 'primary'" size="mini" :text="item.role==1 ? '群主' : '管理员'"></uni-tag> -->
|
|
<text v-if="item.fromUser.id!=user.user_id" :class="bgInfo.image ? 'c-white' : ''" class="text-overflow" style="width: 50px;display: inline-block;">{{item.fromUser.realname1?item.fromUser.realname1:item.fromUser.displayName}} </text>
|
|
<text class="f-11" :class="bgInfo.image ? 'c-white' : 'c-999'">{{sendTime(item.sendTime)}}</text>
|
|
</view>
|
|
<view class="im-flex im-rows-reverse self im-align-items-end" :id="'msg_id_'+item.msg_id">
|
|
<!-- 文字消息 -->
|
|
<view v-if="item.type=='text'">
|
|
<view class="content shadow bg-light-green" :class="[item.fromUser.id==user.user_id ? 'bg-light-green' : '',contact.id === -2&&item.fromUser.id!==user.user_id ? 'bg-light-grey' : '' ]">
|
|
<!-- <view style="overflow: hidden;display:inline;word-break: break-all;" v-if="contact.id==-2" v-html="item.content"></view> -->
|
|
<mp-html container-style="overflow: hidden;display:inline;white-space: pre-wrap;max-width:460rpx;line-break: anywhere;" v-if="contact.id==-2" :content="item.content"/>
|
|
<view v-else>
|
|
<view style="overflow: hidden;display:inline;word-break: break-all;line-break: anywhere;" v-if="contenthtml(item.content)" v-html="item.content"></view>
|
|
<mp-html v-else container-style="overflow: hidden;display:inline;white-space: pre-wrap;line-break: anywhere;" :content="emojiToHtml(item.content)"/>
|
|
</view>
|
|
</view>
|
|
<view class="message-quote radius-6" v-if="item.extends && item.extends.content">
|
|
{{item.extends.content}}
|
|
</view>
|
|
</view>
|
|
<!-- 图片消息 -->
|
|
<template v-else-if="item.type=='image'">
|
|
<view v-for="(iteme,indexs) in imglist" :key="indexs" v-if="network_log=='none'">
|
|
<im-image v-if="item.imgname == iteme.name" :src="iteme.path" :info="item.extends" @showImgs="showImgs" @viewImgs="viewImg"></im-image>
|
|
</view>
|
|
<im-image v-else @viewImgs="viewImg" :src="item.content" :info="item.extends" :isview="{is_view:item.is_view,file_id:item.file_id,msg_id:item.msg_id}" @showImgs="showImgs"></im-image>
|
|
</template>
|
|
|
|
<!-- 语音消息 -->
|
|
<view v-else-if="item.type=='voice'" class="im-voice-msg im-flex im-rows im-nowrap im-align-items-center radius-20"
|
|
:class="[index == playIndex ? 'linear-green' : '', item.fromUser.id==user.user_id ? 'im-rows-reverse' : '' , ]" :data-voice="item.content" :data-index='index' @tap='playVoice'
|
|
:style="{'width':(item.extends.duration*3)+'px'}">
|
|
<text class="f-16 cuIcon-subscription rotate45" :class="[index == playIndex ? 'c-white' : '',item.fromUser.id==user.user_id ? 'rotate225' : '']"></text>
|
|
<text class="im-voice-msg-text" :class="[index == playIndex ? 'c-white' : '']">{{item.extends.duration}} "</text>
|
|
</view>
|
|
<!-- 视频消息 -->
|
|
<template v-else-if="item.type=='video'" >
|
|
<view v-for="(iteme,indexs) in imglist" :key="indexs" v-if="network_log=='none'">
|
|
<view v-if="item.imgname == iteme.name" class='course-video' :style="(item.extends && item.extends.width) ? $util.imageCoverStyle(item.extends.width,item.extends.height) : ''">
|
|
<view class="relative-shadow" @tap="handlePlay(item,iteme)">
|
|
<view class="cuIcon-video icon-center f-28 c-white"></view>
|
|
<view class="video-duration f-10 c-white" v-if="item.extends && item.extends.duration">{{$util.videoFormatTime(item.extends.duration)}}</view>
|
|
</view>
|
|
<im-image v-if="item.extends" :src="item.extends.poster" :info="item.extends"></im-image>
|
|
<!-- <image v-if="item.extends" :src="item.extends.poster" class="blur-image" mode="aspectFill" /> -->
|
|
</view>
|
|
</view>
|
|
<view v-else class='course-video' :style="(item.extends && item.extends.width) ? $util.imageCoverStyle(item.extends.width,item.extends.height) : ''">
|
|
<view class="relative-shadow" @tap="handlePlay(item)">
|
|
<view class="cuIcon-video icon-center f-28 c-white"></view>
|
|
<view class="video-duration f-10 c-white" v-if="item.extends && item.extends.duration">{{$util.videoFormatTime(item.extends.duration)}}</view>
|
|
</view>
|
|
<im-image v-if="item.extends" :src="item.extends.poster" :info="item.extends"></im-image>
|
|
<!-- <image v-if="item.extends" :src="item.extends.poster" class="blur-image" mode="aspectFill" /> -->
|
|
</view>
|
|
</template>
|
|
<!-- 文件消息 -->
|
|
<view v-else-if="item.type=='file'">
|
|
<view class="file-card bg-white radius-10 im-flex im-justify-content-start pd-10 im-align-items-center" @tap.stop="previewFile(item)">
|
|
<image :src="item.extUrl" style="width:64rpx;height:80rpx"></image>
|
|
<view class="im-flex im-columns ml-10">
|
|
<view class="text-overflow file-name">{{item.fileName}}</view>
|
|
<view class="text-gray file-size f-12">{{fileSize(item.fileSize)}}</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<!-- 音视频消息 -->
|
|
<view v-else-if="item.type=='webrtc'" @tap="calling(item.extends.type)" class="im-voice-msg im-flex im-rows im-nowrap im-align-items-center radius-20" :class="[item.fromUser.id==user.user_id ? 'im-rows-reverse' : '' , ]">
|
|
<text class="f-16" :class="[item.extends.type == 1 ? 'cuIcon-record' : 'cuIcon-dianhua',item.fromUser.id==user.user_id ? 'transform180' : '']" :style="{}"></text>
|
|
<text class="im-voice-msg-text">{{item.content}}</text>
|
|
</view>
|
|
<!-- 位置消息 -->
|
|
<view v-else-if="item.type=='location'" @tap="openLocation(item.extends)" class="im-location-msg im-flex im-rows im-nowrap im-align-items-center radius-8 pd-10">
|
|
<view class="f-24 cuIcon-location pr-5"></view>
|
|
<view>
|
|
<view class="f-14 mb-5">{{item.content}}</view>
|
|
<view class="c-999 f-12">{{item.extends && item.extends.address}}</view>
|
|
</view>
|
|
|
|
</view>
|
|
<!-- 名片消息 -->
|
|
<view v-else-if="item.type=='contact'" @tap="openContact(item.extends)" class="im-contact-msg radius-8 pt-10 pr-10 pl-10 pb-5">
|
|
<view class="im-flex im-rows im-nowrap im-align-items-center">
|
|
<view class='cu-avatar mr-10 radius' :style="[{backgroundImage:'url('+item.extends.avatar+')'}]">
|
|
</view>
|
|
<view class="c-333">{{item.extends.displayName}}</view>
|
|
</view>
|
|
<hr class="mt-10 c-999">
|
|
<view class="c-666 f-10">
|
|
个人名片
|
|
</view>
|
|
</view>
|
|
<!-- 动态表情消息 -->
|
|
<template v-else-if="item.type=='emoji'">
|
|
<view v-for="(iteme,indexs) in imglist" :key="indexs" v-if="network_log=='none'">
|
|
<image v-if="item.imgname == iteme.name" :src="iteme.path" class="radius" mode="aspectFit" @tap="showImgs" :data-img="iteme.path" style="width:300rpx;height:300rpx"></image>
|
|
</view>
|
|
<image v-else :src="item.content" class="radius" mode="aspectFit" @tap="showImgs" :data-img="item.content" style="width:300rpx;height:300rpx"></image>
|
|
</template>
|
|
<!-- 其他消息 -->
|
|
<imItem v-else :item="item" :index="index" :isSelf="true"></imItem>
|
|
<view class="mt-10 mr-5 f-20" v-if="item.fromUser.id==user.user_id">
|
|
<view class="cuIcon-icloading icon-spin c-999" v-if="item.status=='going'"></view>
|
|
<view class="cuIcon-infofill c-red" v-if="item.status=='failed'" @tap="reSend(item)"></view>
|
|
<view class="f-16" v-if="item.is_group==0 && item.status!='going' && item.status!='failed'" :class="item.is_read ? 'text-green cuIcon-roundcheckfill' : 'c-999 cuIcon-roundcheck'"></view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
</template>
|
|
</view>
|
|
|
|
<template #backToTop>
|
|
<view class="fixed-item radius-round" style="bottom: 70px;">
|
|
<uni-icons type="down" size="20" color="#0389fb" style="font-weight: bold;"></uni-icons>
|
|
</view>
|
|
</template>
|
|
|
|
<template #bottom>
|
|
<view id="im-input" style="position: relative;z-index: 1000;" v-if="is_group!=2">
|
|
<imInput :isAnswering="isAnswering" :contactid="contact.id" @sendChat="sendChatID" @send="sendMessage" @setPad="setPad" :boxStatus="boxStatus" :contact="contact" ref="imInput"></imInput>
|
|
</view>
|
|
</template>
|
|
</z-paging>
|
|
|
|
<!-- <scroll-view class="scroll-view-body blur-background" :class="bgInfo.filter ? 'filter-blur' : ''" ref="scrollView" scroll-y="true" :scroll-anchoring="true" :scroll-top="scrollTop" @scroll="scrollChat" :style="{height:scrollHeight+'rpx',position:'fixed',bottom:(is_group==2 ? 0 : bottomHeight)+'px',backgroundImage:'url('+bgInfo.image+')'}">
|
|
</scroll-view>
|
|
<view id="im-input" v-if="is_group!=2">
|
|
<imInput @send="sendMessage" @setPad="setPad" :boxStatus="boxStatus" :contact="contact" ref="imInput"></imInput>
|
|
</view> -->
|
|
|
|
<view class="add-modal" :class="modelName=='moreOpt'?'show':'none'" @tap="modelName=''">
|
|
<view class="add-dialog" :style="popStyle" v-if="curMsg">
|
|
<view class="add-dialog-tail" :style="tailStyle"></view>
|
|
<view class="add-item" @tap="undoMsg()" v-if="( (getTime() - curMsg.sendTime < globalConfig.chatInfo.redoTime*1000 && curMsg.fromUser.id==user.user_id) || contact.role<3 )
|
|
&& curMsg.type!=='webrtc'">
|
|
<text class="cuIcon-repeal"></text>
|
|
<view>撤回</view>
|
|
</view>
|
|
<view class="add-item" @tap="copyMsg()" v-if="['text','image','video','file'].includes(curMsg.type)&&curMsg.type!=='file'">
|
|
<text class="cuIcon-copy"></text>
|
|
<view>复制</view>
|
|
<!-- <view>复制{{copyTxt}}</view> -->
|
|
</view>
|
|
<view class="add-item" @tap="colEmoji()" v-if="curMsg.type=='emoji'">
|
|
<text class="cuIcon-emoji"></text>
|
|
<view>存表情</view>
|
|
</view>
|
|
<view class="add-item" @tap="forwardMsg()" v-if="curMsg.type!=='voice' && curMsg.type!=='webrtc' && curMsg.type!=='money' ">
|
|
<text class="cuIcon-forward"></text>
|
|
<view>转发</view>
|
|
</view>
|
|
<view class="add-item" @tap="quoteMsg()" v-if="curMsg.type!=='webrtc' && curMsg.type!=='money' ">
|
|
<text class="cuIcon-tag"></text>
|
|
<view>引用</view>
|
|
</view>
|
|
<view class="add-item" @tap="delMsg()">
|
|
<!-- v-if="globalConfig.chatInfo.dbDelMsg==1||curMsg.fromUser.id==user.user_id" -->
|
|
<text class="cuIcon-delete"></text>
|
|
<view>删除</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view class="cu-modal bottom-modal" :class="modelName=='copyModel'?'show':''">
|
|
<view class="cu-dialog">
|
|
<view class="cu-bar bg-white">
|
|
<view class="action text-gray" @tap="modelName=''">取消</view>
|
|
<view class="action text-green" @tap="copyMsg()">复制</view>
|
|
</view>
|
|
<!-- <scroll-view scroll-y="true" :style="{height:scrollHeight+'rpx'}"> -->
|
|
<scroll-view scroll-y="true" style="height:800rpx">
|
|
<view class="pd-20 text-container">
|
|
<mp-html :content="curMsg.content"></mp-html>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</view>
|
|
<view class="at-fixed-item" v-if="atCount" @tap="openAtModel" :style="{bottom:130+inlineTools+'rpx'}">
|
|
有{{atCount}}人提到我
|
|
</view>
|
|
<!-- <view class="at-fixed-item" v-else-if="!isBottom" @tap="scrollToBottom()" :style="{bottom:130+inlineTools+'rpx'}">
|
|
回到底部
|
|
</view> -->
|
|
<view class="cu-modal bottom-modal" :class="modelName=='atModel'?'show':''" @tap="modelName=''">
|
|
<view class="cu-dialog" v-if="modelName=='atModel'">
|
|
<view class="cu-bar bg-white">
|
|
<view class="action">提到我的人</view>
|
|
<view class="action text-green">已读</view>
|
|
</view>
|
|
<!-- <scroll-view scroll-y="true" :style="{height:scrollHeight+'rpx'}" @tap.stop=''> -->
|
|
<scroll-view scroll-y="true" style="height:800rpx" @tap.stop=''>
|
|
<view class="cu-chat" style="text-align: left;">
|
|
<view class="cu-item" v-for="(item,index) in atMsgList" :key="index" style="padding-bottom: 10rpx;">
|
|
<im-user :info="item.fromUser" :profile="isProfile" @longpress="at(item.fromUser)"></im-user>
|
|
<view class="main im-wrap" @tap="dblclick(item)">
|
|
<view class="f-12 c-666" style="width:100%;margin-bottom: 6rpx;">{{item.fromUser.realname}}<text class="text-gray"> {{$util.date("Y-m-d H:i:s",item.sendTime)}} </text></view>
|
|
<!-- 文字消息 -->
|
|
<view>
|
|
<view class="content shadow" v-if="item.type=='text'">
|
|
<mp-html container-style="overflow: hidden;display:inline;white-space: pre-wrap" :content="emojiToHtml(item.content)"/>
|
|
</view>
|
|
<view class="message-quote radius-6 align-left" v-if="item.extends && item.extends.content">
|
|
{{item.extends.content}}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</view>
|
|
<!-- 文件预览 -->
|
|
<view class="cu-modal bottom-modal" :class="modelName=='preview'?'show':''" @tap="modelName=''">
|
|
<view class="cu-dialog" v-if="modelName=='preview'">
|
|
<view class="cu-list menu bg-white">
|
|
<view class="cu-item" @tap="preview(1)" >
|
|
<view class="content padding-tb-sm">
|
|
<text class="text-center">本地预览(需下载)</text>
|
|
<view class="text-gray text-sm">需下载,仅支持office类型文件</view>
|
|
</view>
|
|
</view>
|
|
<view class="cu-item" @tap="preview(2)">
|
|
<view class="content padding-tb-sm">
|
|
<text>在线预览</text>
|
|
<view class="text-gray text-sm">支持常用的文件和文档</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
const innerAudioContext = uni.createInnerAudioContext();
|
|
import imInput from '@/components/message/im-input.vue';
|
|
import imItem from '@/components/message/im-item.vue';
|
|
import imImage from '@/components/message/im-image.vue';
|
|
import statusPoint from '@/components/status.vue';
|
|
import imUser from '@/components/im-user.vue';
|
|
import emoji from '@/utils/emoji.js'
|
|
import { chat } from '@/mixins/chat.js'
|
|
import { useloginStore } from '@/store/login';
|
|
import { useMsgStore } from '@/store/message';
|
|
import { storeToRefs } from 'pinia';
|
|
import pinia from '@/store/index'
|
|
import getchats from '@/service/getMessageList';
|
|
import groupInfo from '@/service/groupInfo';
|
|
// #ifdef APP-PLUS
|
|
import {getSavedImages2} from '@/utils/LocalFileSystemURL.js'
|
|
// #endif
|
|
|
|
import MarkdownIt from 'markdown-it';
|
|
import hljs from 'highlight.js';
|
|
import 'highlight.js/styles/github.css'
|
|
|
|
const md = new MarkdownIt({
|
|
html: true, // 允许HTML标签
|
|
linkify: true, // 自动转换链接
|
|
typographer: true, // 启用一些智能的标点转换
|
|
highlight: function (str, lang) {
|
|
if (lang && hljs.getLanguage(lang)) {
|
|
try {
|
|
return `<pre class="hljs" style="white-space: pre-wrap;border-radius: 20rpx;padding:0px 30rpx;background: rgb(230 238 245);">
|
|
<code>
|
|
${hljs.highlight(str, { language: lang, ignoreIllegals: true }).value}
|
|
</code>
|
|
</pre>`
|
|
} catch (e) {
|
|
console.error('Highlighting failed:', e)
|
|
}
|
|
}
|
|
// 4. 无语言标识的默认处理
|
|
return '<pre class="hljs"><code>' + md.utils.escapeHtml(str) + '</code></pre>' // 使用默认的文本替换,或者使用<pre><code>包裹的文本
|
|
}
|
|
});
|
|
|
|
const msgStore = useMsgStore(pinia)
|
|
const {newMessage,msgList,getContact,appendMsg,checkMsg,unread,network_log} = storeToRefs(msgStore);
|
|
const userStore = useloginStore(pinia)
|
|
const getTime = () => {
|
|
return new Date().getTime();
|
|
};
|
|
export default {
|
|
components: {
|
|
imInput,
|
|
imItem,
|
|
imUser,
|
|
statusPoint,
|
|
imImage
|
|
},
|
|
mixins:[chat],
|
|
data() {
|
|
return {
|
|
user:userStore.userInfo,
|
|
contact:{},
|
|
is_group:0,
|
|
boxStatus:0,
|
|
paddingT:0,
|
|
video:'',
|
|
videoUrl: '',
|
|
InputBottom: 0,
|
|
player : null,
|
|
playIndex : -1,
|
|
emojiMap:[],
|
|
fileExt:[],
|
|
page:1,
|
|
limit:10,
|
|
moreData:true, //是否有更多数据
|
|
newMessage:newMessage,
|
|
messageList:msgList,
|
|
newheight:0,
|
|
scrollTop:0,
|
|
loading:'more',
|
|
loadLock:false,
|
|
scrollHeight:0, //聊天界面滚动高度,移除原来的计算属性
|
|
paddingB:0,
|
|
contact_id:0,
|
|
bottomHeight:50,
|
|
globalConfig:userStore.globalConfig,
|
|
modelName:'',
|
|
curMsg:'',
|
|
curIndex:0,
|
|
isSending:false,
|
|
copyTxt:'',
|
|
wsData:null,
|
|
timer:null,
|
|
lastTapDiffTime: 0,
|
|
isProfile:false,
|
|
islongPress:false,
|
|
listTouchStart:0,
|
|
groupInfo:{
|
|
groupUserCount:0
|
|
},
|
|
atMsgList:[],
|
|
atCount:0,
|
|
unread:unread,
|
|
isBottom:true,
|
|
bgInfo:{
|
|
image:'',
|
|
filter:10
|
|
},
|
|
tailStyle: { // 小尾巴的样式
|
|
left: '0px',
|
|
top: '0px',
|
|
},
|
|
popStyle:{ //长按消息菜单
|
|
left: '0px',
|
|
top: '0px',
|
|
width:'0px',
|
|
height:'0px'
|
|
},
|
|
network_log:'',
|
|
imglist:[],
|
|
isAnswering: false,
|
|
};
|
|
},
|
|
watch:{
|
|
newMessage(val){
|
|
if(val.toContactId==this.contact.id && val.fromUser.id!=this.user.user_id){
|
|
// console.log(val,'123');
|
|
this.$api.msgApi.setMsgIsRead({
|
|
toContactId: this.contact.id,
|
|
is_group: this.contact.is_group,
|
|
messages: val,
|
|
fromUser: val.fromUser.id,
|
|
});
|
|
}
|
|
this.isBottom ? this.scrollToBottom() : '';
|
|
}
|
|
},
|
|
onLoad(options){
|
|
// 清空消息列表,避免串台
|
|
msgStore.msgList=[];
|
|
let bgInfo=uni.getStorageSync('chat-bg-info'+options.id+this.user.user_id);
|
|
if(bgInfo){
|
|
this.bgInfo=bgInfo;
|
|
}
|
|
// console.log('123',this.bgInfo);
|
|
// 获取当前联系人信息
|
|
let data=msgStore.getContact(options.id);
|
|
if(!data){
|
|
uni.showToast({
|
|
title:'联系人不存在',
|
|
icon:'none',
|
|
duration:1500,
|
|
complete:(res)=>{
|
|
uni.reLaunch({
|
|
url: '/pages/index/index'
|
|
})
|
|
}
|
|
})
|
|
return;
|
|
}
|
|
if(data.is_group==1 && (data.role<3 || data.setting.profile=='1')){
|
|
this.isProfile=true;
|
|
}
|
|
this.contact = data;
|
|
|
|
this.contact_id=0
|
|
this.contact_id=options.id;
|
|
this.is_group = data.is_group;
|
|
|
|
this.network_log = uni.getStorageSync('network_log')
|
|
// console.log(this.network_log);
|
|
if(this.network_log == 'none'){
|
|
this.getchatList()
|
|
this.getImagePath()
|
|
|
|
if(this.is_group==1){
|
|
this.obtainGroupInfo()
|
|
}
|
|
}else{
|
|
this.getMessageList();
|
|
// this.getchatList()
|
|
if(data.is_group==1){
|
|
this.getGroupInfo();
|
|
}
|
|
}
|
|
// this.getMessageList();
|
|
// if(data.is_group==1){
|
|
// this.getGroupInfo();
|
|
// }
|
|
|
|
let unread=msgStore.unread;
|
|
// 如果有未读,就将未读的角标置为0
|
|
if(data.unread>0){
|
|
let contacts=msgStore.updateContacts({
|
|
id:options.id,
|
|
unread:0
|
|
});
|
|
msgStore.unread = unread - data.unread;
|
|
}
|
|
// 监听消息更新请求
|
|
uni.$on('getPositonsOrder',(res) => {
|
|
let message=res.data;
|
|
switch (res['type']) {
|
|
//处理消息已读,将本地的未读消息修改为已读状态
|
|
case "isRead":
|
|
for (let i = 0; message.length > i; i++) {
|
|
const data = {
|
|
id: message[i]["id"],
|
|
is_read: 1
|
|
};
|
|
this.updateMessage(data);
|
|
}
|
|
break;
|
|
case "readAll":
|
|
// 如果某人阅读了消息,全部置为已读
|
|
if(message.toContactId==this.contact.id && this.is_group==0){
|
|
this.messageList.forEach((item)=>{
|
|
item.is_read=1;
|
|
})
|
|
}
|
|
break;
|
|
//上线、下线通知
|
|
case "isOnline":
|
|
if(message.id==this.contact.id){
|
|
this.contact.is_online=message.is_online;
|
|
}
|
|
break;
|
|
// 撤回消息
|
|
case "undoMessage":
|
|
// if(message.from_user==this.user.user_id && message.isMobile==1 && getTime()-message.sendTime<this.globalConfig.chatInfo.redoTime*1000){
|
|
if(message.from_user==this.user.user_id && message.isMobile==1 && !message.toContactId.includes("group") && getTime()-message.sendTime<this.globalConfig.chatInfo.redoTime*1000){
|
|
return false;
|
|
}
|
|
const tabid = uni.getStorageSync('tabid')
|
|
// console.log(message);
|
|
// console.log(tabid);
|
|
if(message.is_last){
|
|
this.updateMessage(message)
|
|
}else if(message.chat_identify&&message.from_user==tabid){
|
|
msgStore.checkMsg(message);
|
|
msgStore.appendMsg(message);
|
|
}else if(message.chat_identify&&message.toContactId==tabid&&message.is_group==1){
|
|
msgStore.checkMsg(message);
|
|
msgStore.appendMsg(message);
|
|
}else if(message.chat_identify&&message.toContactId==tabid&&message.is_group==0){
|
|
msgStore.checkMsg(message);
|
|
msgStore.appendMsg(message);
|
|
}
|
|
break;
|
|
// 删除消息
|
|
case "delMessage":
|
|
this.messageList = this.messageList.filter((item)=>{
|
|
return item.id!=message.id;
|
|
})
|
|
// this.$refs.paging.complete(this.messageList);
|
|
break;
|
|
// 更新消息
|
|
case "updateMessage":
|
|
this.updateMessage(message);
|
|
break;
|
|
// case "delMessageAll":
|
|
// this.getMessageList();
|
|
// if(data.is_group==1){
|
|
// this.getGroupInfo();
|
|
// }
|
|
// break;
|
|
case "editGroupName":
|
|
const tabid1 = uni.getStorageSync('tabid')
|
|
if(message.id==tabid1){
|
|
this.contact.displayName = message.displayName
|
|
// let GroupName = {type: "event",content:`${message.editUserName}修改了群名为${message.displayName}`,toContactId:message.id,create_time:getTime()}
|
|
// // console.log(GroupName)
|
|
// msgStore.checkMsg(GroupName);
|
|
// msgStore.appendMsg(GroupName);
|
|
}
|
|
break;
|
|
case "atMsgList":
|
|
case "simple":
|
|
case "group":
|
|
case "webrtc":
|
|
case "removeUser":
|
|
case "clearMessage":
|
|
case "delMessage":
|
|
let routes = getCurrentPages();
|
|
let curParam ={};
|
|
let routePages = routes[routes.length - 1].route;
|
|
let pages = 1;
|
|
// 如果是音视频聊天页面的话就需要上一个页面的参数
|
|
if(routePages=='pages/message/call'){
|
|
pages = 2;
|
|
}
|
|
// #ifdef MP-WEIXIN
|
|
curParam = routes[routes.length - pages].options ?? '';
|
|
// #endif
|
|
// #ifdef APP-PLUS
|
|
curParam = routes[routes.length - pages].$page.options ?? '';
|
|
// #endif
|
|
// 如果是h5需要单独去获取url的参数
|
|
// #ifdef H5
|
|
let url = location.href;
|
|
// 如果是音视频聊天页面的话就不要写入消息
|
|
if(url.indexOf('pages/message/call')!==-1){
|
|
return;
|
|
}
|
|
curParam=this.$util.parseUrl(url);
|
|
// #endif
|
|
if(res['type']=='atMsgList'){
|
|
if(message.toContactId==curParam.id){
|
|
this.atMsgList=message.list;
|
|
this.atCount=message.count;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 如果是删除的本人和当前群聊,返回到列表页
|
|
if(res['type']=='removeUser'){
|
|
if(message.group_id==curParam.id && message.user_id == this.user.user_id){
|
|
uni.showToast({
|
|
title:'您已被移出群聊!',
|
|
icon:'none',
|
|
complete: () => {
|
|
uni.reLaunch({
|
|
url: '/pages/index/index'
|
|
})
|
|
}
|
|
})
|
|
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(res['type']=='clearMessage'){
|
|
if(message.group_id==curParam.id){
|
|
this.messageList=[];
|
|
msgStore.updateContacts({
|
|
id:message.group_id,
|
|
lastContent:''
|
|
})
|
|
}
|
|
return;
|
|
}
|
|
if(res['type']=='delMessage'){
|
|
if(message.group_id==curParam.id){
|
|
this.messageList = this.messageList.filter(item => item.id != message.id)
|
|
this.$refs.paging.complete(this.messageList);
|
|
}
|
|
return;
|
|
}
|
|
if(message.type=='webrtc'){
|
|
if(!['calling','hangup','otherOpt'].includes(message.extends.event)){
|
|
return false;
|
|
}
|
|
if(message.extends.event=='calling'){
|
|
this.wsData=message;
|
|
}
|
|
let code=parseInt(message.extends.code);
|
|
if([902,903,904,905,906,907].includes(code)){
|
|
let wsData=this.wsData || message;
|
|
wsData.content=message.content;
|
|
message=wsData;
|
|
}
|
|
}
|
|
let isAppend=false;
|
|
if(message.is_group==1){
|
|
if(message.toUser==curParam.id){
|
|
isAppend=true;
|
|
}
|
|
}else{
|
|
// 如果是当前的聊天房间,才可以进行消息新增
|
|
if(message.toContactId==curParam.id || (message.fromUser.id==this.user.user_id && message.toUser==curParam.id)){
|
|
isAppend=true;
|
|
}
|
|
}
|
|
if(isAppend){
|
|
if(message.toContactId!="-2"){
|
|
msgStore.checkMsg(message);
|
|
msgStore.appendMsg(message);
|
|
}
|
|
let contact=msgStore.getContact(this.contact_id,message);
|
|
let at=contact.is_at;
|
|
if(message.fromUser.id!=this.user.user_id){
|
|
// 将姓名显示为备注信息的新名称
|
|
let fromContact=msgStore.getContact(message.fromUser.id,null);
|
|
if(fromContact){
|
|
message.fromUser.realname1=fromContact.displayName;
|
|
}
|
|
}
|
|
// if(message.user_id!=this.user.user_id&&message.fromUser.id!=="-2"){
|
|
// this.$refs.paging.addChatRecordData(message);
|
|
// }
|
|
if(message.fromUser.id=="-2"){
|
|
// console.info(message);
|
|
message.toUser = message.to_user
|
|
this.insertdata(message)
|
|
}
|
|
// 检查当前聊天的新消息是否有@数据,有的话直接清楚列表中的提醒
|
|
if(message.at.includes(this.user.user_id)){
|
|
msgStore.msgAt-=1;
|
|
at= contact.is_at-1;
|
|
}
|
|
msgStore.updateContacts({
|
|
id:curParam.id,
|
|
unread:0,
|
|
is_at:at
|
|
});
|
|
}
|
|
// this.scrollToBottom();
|
|
break;
|
|
}
|
|
})
|
|
},
|
|
onUnload(){
|
|
// 聊天记录置为空
|
|
msgStore.msgList=[];
|
|
// 停止所有声音播放
|
|
innerAudioContext.stop();
|
|
uni.$off("socketStatus");
|
|
},
|
|
beforeDestroy(){
|
|
uni.$off("socketStatus");
|
|
},
|
|
// 所有聊天页面都返回首页,避免层级过深
|
|
onBackPress(options) {
|
|
this.InputBottom=0;
|
|
uni.switchTab({
|
|
url: '/pages/index/index'
|
|
})
|
|
return true;
|
|
},
|
|
onShow(){
|
|
const detailrefresh = uni.getStorageSync('detailrefresh')
|
|
if(detailrefresh==1){
|
|
this.getMessageList();
|
|
uni.removeStorageSync('detailrefresh')
|
|
}
|
|
// 检测ws是否还在线
|
|
this.socketIo.send({type:'ping'});
|
|
const _this = this
|
|
this.network_log = uni.getStorageSync('network_log')
|
|
// console.log('1230',_this.network_log == 'none');
|
|
if(_this.network_log !== 'none'){
|
|
// _this.getchatList()
|
|
// _this.getImagePath()
|
|
// if(_this.is_group==1){
|
|
// _this.obtainGroupInfo()
|
|
// }
|
|
// }else{
|
|
// this.getMessageList();
|
|
if(this.is_group==1){
|
|
this.getGroupInfo();
|
|
}
|
|
}
|
|
},
|
|
created: function(){
|
|
let emojiMap=[];
|
|
// 解析所有表情
|
|
emoji.forEach(function (item) {
|
|
let child=item.children;
|
|
if(child.length>0){
|
|
child.forEach(function (val) {
|
|
let name=val.name;
|
|
let src=val.src;
|
|
emojiMap[name]=src;
|
|
})
|
|
}
|
|
});
|
|
this.emojiMap=emojiMap;
|
|
innerAudioContext.onPlay(() => {console.info('play');});
|
|
innerAudioContext.onEnded(() => {
|
|
innerAudioContext.stop();
|
|
this.playIndex = -1;
|
|
});
|
|
innerAudioContext.onError((E)=>{console.info(E);});
|
|
},
|
|
mounted () {
|
|
const _this = this
|
|
// 重新链接就更新消息列表
|
|
uni.$on('socketStatus',(e)=>{
|
|
if(e){
|
|
this.page=1;
|
|
|
|
if(_this.network_log == 'none'){
|
|
_this.getchatList()
|
|
if(data.is_group==1){
|
|
_this.obtainGroupInfo()
|
|
}
|
|
}else{
|
|
_this.getMessageList();
|
|
// _this.getchatList()
|
|
// if(_this.is_group==1){
|
|
// _this.getGroupInfo();
|
|
// }
|
|
}
|
|
}
|
|
})
|
|
// this.computeHeight();
|
|
},
|
|
methods: {
|
|
stripHtmlTags(content) {
|
|
return content.replace(/<\/?[^>]+(>|$)/g, ""); // 正则表达式去掉 HTML 标签
|
|
},
|
|
computeHeight(){
|
|
let sys = uni.getSystemInfoSync();
|
|
let winWidth = sys.windowWidth;
|
|
let winrate = 750/winWidth;
|
|
let bottomHeight=uni.upx2px(100);
|
|
this.bottomHeight=bottomHeight;
|
|
const query=this.$util.getQuery(this);
|
|
query.select('.cu-header').boundingClientRect();
|
|
query.exec(data => {
|
|
this.paddingT=data[0].height;
|
|
// #ifdef H5
|
|
let winHeight =parseInt((sys.windowHeight - this.navBarHeight - this.paddingT)*winrate);
|
|
// #endif
|
|
|
|
// #ifdef APP-PLUS
|
|
let winHeight =parseInt((sys.windowHeight - (this.inlineTools + this.paddingT+bottomHeight))*winrate);
|
|
// #endif
|
|
|
|
// #ifndef H5 || APP-PLUS
|
|
this.bottomHeight+=this.inlineTools;
|
|
// 微信小程序需要减去状态栏+底部导航栏+底部横线
|
|
let winHeight =parseInt((sys.windowHeight-(this.inlineTools + this.paddingT + this.navBarHeight))*winrate)
|
|
// #endif
|
|
if(this.is_group==2){
|
|
this.scrollHeight = winHeight+100;
|
|
}
|
|
this.scrollHeight = winHeight;
|
|
});
|
|
},
|
|
// 长按头像@人
|
|
at(item){
|
|
if(this.contact.is_group==0 || this.user.user_id==item.id){
|
|
return;
|
|
}
|
|
item.user_id=item.id;
|
|
this.$refs.imInput.setAtList(item);
|
|
},
|
|
openAtModel(){
|
|
this.modelName='atModel';
|
|
let msgAt=msgStore.msgAt;
|
|
let curAt=this.atCount;
|
|
this.$api.msgApi.readAtMsg({toContactId:this.contact_id}).then(res => {
|
|
if(res.code==0){
|
|
msgStore.msgAt=msgAt-curAt;
|
|
msgStore.updateContacts({
|
|
id:this.contact_id,
|
|
is_at:0
|
|
})
|
|
this.atCount=0;
|
|
}
|
|
})
|
|
},
|
|
calling(type){
|
|
this.$refs.imInput.calling(parseInt(type));
|
|
},
|
|
contenthtml(val){
|
|
return val && (val.includes('http://') || val.includes('https://'));
|
|
},
|
|
viewImg(e){
|
|
this.messageList.forEach((res)=>{
|
|
if(res.msg_id == e.msg_id){
|
|
res.content = e.src
|
|
}
|
|
})
|
|
// this.page = 1
|
|
// this.getMessageList();
|
|
},
|
|
handleLink(url){
|
|
// #ifdef H5
|
|
window.open(url, '_blank');
|
|
// #endif
|
|
// #ifdef APP-PLUS
|
|
plus.runtime.openURL(url);
|
|
// #endif
|
|
},
|
|
endTimer() {
|
|
clearTimeout(this.timer);
|
|
setTimeout(() => {
|
|
this.islongPress = false
|
|
}, 200)
|
|
|
|
},
|
|
// 双击
|
|
dblclick(item) {
|
|
const _this = this;
|
|
// 当前时间
|
|
const curTime = new Date().getTime();
|
|
// 上次点击时间
|
|
const lastTime = _this.lastTapDiffTime;
|
|
// 更新上次点击时间
|
|
_this.lastTapDiffTime = curTime;
|
|
// 两次点击间隔小于300ms, 认为是双击
|
|
const diff = curTime - lastTime;
|
|
if (diff < 300) {
|
|
this.curMsg=item;
|
|
this.modelName='copyModel';
|
|
}
|
|
},
|
|
getTime(){
|
|
return new Date().getTime();
|
|
},
|
|
setPad(padding){ //设置聊天内容的地步边距
|
|
// this.paddingB=padding;
|
|
// this.scrollToBottom();
|
|
},
|
|
// 获取群聊信息
|
|
getGroupInfo(){
|
|
this.$api.msgApi.groupInfo({
|
|
group_id: this.contact_id,
|
|
}).then(res => {
|
|
if(res.code==0){
|
|
this.groupInfo=res.data;
|
|
uni.setStorageSync('setgroupInfo',res.data);
|
|
this.insertGroupInfo(res.data)
|
|
}
|
|
})
|
|
},
|
|
|
|
async insertGroupInfo(val){
|
|
// #ifdef APP-PLUS
|
|
const list = []
|
|
list.push(val)
|
|
await groupInfo.batchInsertOrUpdate(list);
|
|
// #endif
|
|
},
|
|
|
|
async obtainGroupInfo(){
|
|
// #ifdef APP-PLUS
|
|
const [groups] = await groupInfo.getList({group_id: this.contact_id});
|
|
this.groupInfo = groups
|
|
// console.info('获取聊天信息数据',groups.length,groups);
|
|
// #endif
|
|
},
|
|
|
|
updateMessage(message){
|
|
let msgList = this.messageList;
|
|
// 更新联系人
|
|
msgList.forEach((item, index) => {
|
|
let msg = msgList[index];
|
|
if (item.id == message.id) {
|
|
msgList[index] = Object.assign(msg, message);
|
|
}
|
|
})
|
|
if(message.user_id||message.is_last){
|
|
// this.$refs.paging.addChatRecordData(message);
|
|
// this.$refs.paging.complete(msgList);
|
|
this.messageList=msgList;
|
|
}
|
|
},
|
|
getScrollHeight(){
|
|
// const query=this.$util.getQuery(this);
|
|
// setTimeout(() => {
|
|
// query.select('.cu-chat').boundingClientRect();
|
|
// query.exec(data => {
|
|
// // this.scrollHeight=data[0].height;
|
|
// this.scrollTop=data[0].height-this.newheight;
|
|
// this.loadLock=false;
|
|
// });
|
|
// }, 10)
|
|
},
|
|
scrollChat(pageNo, pageSize){
|
|
const _this = this
|
|
// const scrollView = e.detail;
|
|
// // 当前滚动条垂直位置
|
|
// const scrollTop = scrollView.scrollTop;
|
|
// // 内容高度
|
|
// const contentHeight = scrollView.scrollHeight;
|
|
// this.newheight=contentHeight;
|
|
// if(scrollTop<10 && this.loadLock==false){
|
|
// this.loadLock=true;
|
|
// this.page++;
|
|
// if(this.moreData){
|
|
// this.loading='loading';
|
|
this.page = pageNo
|
|
if(this.page>1){
|
|
if(this.network_log == 'none'){
|
|
_this.getchatList()
|
|
}else{
|
|
_this.getMessageList();
|
|
// _this.getchatList()
|
|
}
|
|
}
|
|
// }
|
|
// }
|
|
// console.log(pageNo);
|
|
// 创建选择器
|
|
const query = uni.createSelectorQuery().in(this);
|
|
// 选择div
|
|
query.select('.scroll-view-body').boundingClientRect(data => {
|
|
// data中包含了元素的尺寸信息,如宽、高等
|
|
if (data) {
|
|
// 判断是否滚动到底部
|
|
this.isBottom = scrollTop + data.height + 2 >= contentHeight ? true : false;
|
|
}
|
|
}).exec();
|
|
|
|
},
|
|
getMessageList() {
|
|
let params={
|
|
is_group: this.is_group,
|
|
toContactId: this.contact.id,
|
|
page: this.page,
|
|
limit: this.limit
|
|
};
|
|
this.$api.msgApi.getMessageList(params).then(res => {
|
|
|
|
if(this.page==1){
|
|
msgStore.msgList = [];
|
|
}
|
|
|
|
let data=res.data.slice().reverse();
|
|
if(params.toContactId==-2){
|
|
const processedMessages = data.map(msg =>({
|
|
...msg,
|
|
content:md.render(msg.content)
|
|
}))
|
|
|
|
this.$refs.paging.complete(processedMessages);
|
|
}else{
|
|
this.$refs.paging.complete(data);
|
|
}
|
|
// data.forEach(it => {
|
|
// msgStore.msgList.unshift(it)
|
|
// })
|
|
var _this=this;
|
|
// this.loading='more';
|
|
|
|
// 如果返回的数据小于每页的数量
|
|
// if (res.data.length < this.limit) {
|
|
// this.moreData=false
|
|
// this.loading='noMore'
|
|
// }
|
|
this.$nextTick(()=>{
|
|
if(this.page==1){
|
|
this.scrollToBottom();
|
|
}else{
|
|
this.getScrollHeight();
|
|
}
|
|
});
|
|
}).catch(res => {
|
|
this.$refs.paging.complete(false);
|
|
})
|
|
},
|
|
async insertdata(data){
|
|
// console.log('231',data);
|
|
await getchats.batchInsertOrUpdate(data)
|
|
},
|
|
async getchatList(){
|
|
let params = {
|
|
is_group: this.is_group,
|
|
toContactId: this.contact.id,
|
|
toUserId: this.user.user_id,
|
|
page: this.page,
|
|
limit: this.limit
|
|
};
|
|
try {
|
|
const list = await getchats.getList(params);
|
|
// console.info('聊天数据',list.data);
|
|
if (list.data) {
|
|
if (this.page == 1) {
|
|
msgStore.msgList = [];
|
|
}
|
|
// let data = list.data.slice().reverse();
|
|
list.data.forEach(it => {
|
|
it.at = JSON.parse(it.at)
|
|
it.fromUser = JSON.parse(it.fromUser)
|
|
it.extends = JSON.parse(it.extends)
|
|
it.toContactId = it.is_group==0?JSON.parse(it.toContactId):it.toContactId
|
|
|
|
if(it.imgname!=''){
|
|
const imgNames = it.imgname.split(','); // 分割为数组
|
|
const imgPathMap = {};
|
|
this.imglist.forEach(img => {
|
|
imgPathMap[img.name] = img.path; // 使用文件名作为键,路径作为值
|
|
});
|
|
// 替换 content 中的 src
|
|
imgNames.forEach(imgName => {
|
|
const imgPath = imgPathMap[imgName.trim()]; // 查找路径
|
|
if (imgPath) {
|
|
// 把 imgPath 填充到 content 的 src 中
|
|
const imgSrcRegex = new RegExp(`src="http://192.168.66.16:8007/static/img/emoji/twitter/${imgName.trim()}"`, 'g');
|
|
it.content = it.content.replace(imgSrcRegex, `src="${imgPath}"`);
|
|
}
|
|
});
|
|
}
|
|
// msgStore.msgList.unshift(it)
|
|
// console.log(it);
|
|
});
|
|
// console.log(list.data);
|
|
this.$refs.paging.complete(list.data);
|
|
// this.loading = 'more';
|
|
// if (list.data.length < this.limit) {
|
|
// this.moreData = false;
|
|
// this.loading = 'noMore';
|
|
// }
|
|
this.$nextTick(() => {
|
|
setTimeout(() => {
|
|
if (this.page == 1) {
|
|
this.scrollToBottom();
|
|
} else {
|
|
this.getScrollHeight();
|
|
}
|
|
}, 50);
|
|
});
|
|
}
|
|
} catch (error) {
|
|
this.$refs.paging.complete(false);
|
|
console.error(error);
|
|
}
|
|
},
|
|
|
|
//长按事件
|
|
moreOption(e,item,index){
|
|
|
|
this.timer = setTimeout(() => {
|
|
this.curMsg = item;
|
|
this.curIndex = index;
|
|
let count = 0;
|
|
let isMe = item.fromUser.id == this.user.user_id
|
|
let isRecall = ( (this.getTime() - this.curMsg.sendTime < this.globalConfig.chatInfo.redoTime*1000 && this.curMsg.fromUser.id==this.user.user_id) || this.contact.role<3 ) && this.curMsg.type!=='webrtc';
|
|
let isDelete = this.globalConfig.chatInfo.dbDelMsg && isMe;
|
|
if(item.type=="text" || item.type=="emoji"){
|
|
this.copyTxt="";
|
|
count = 3 //转发 引用
|
|
}if(['image','file','video'].includes(item.type)){
|
|
this.copyTxt="链接";
|
|
count = 3
|
|
}else if(item.type=="location" || item.type=="contact"){
|
|
count = 2 //转发 引用
|
|
}else if(item.type=="voice"){
|
|
count = 1;//引用
|
|
}
|
|
if(isRecall){
|
|
++count
|
|
}
|
|
if(isDelete){
|
|
++count
|
|
}
|
|
if(count==0) return false;
|
|
// 如果长按后没有移动才是长按事件
|
|
if(this.islongPress!='move'){
|
|
this.islongPress=true;
|
|
this.modelName='moreOpt';
|
|
this.showMenu(item.msg_id,count,isMe)
|
|
}
|
|
}, 800); // 设置为 1 秒
|
|
},
|
|
showMenu(msg_id,count,isMe) {
|
|
|
|
const query = this.$util.getQuery(this).select(`#msg_id_${msg_id}`).boundingClientRect();
|
|
query.exec((res) => {
|
|
if (res && res[0]) {
|
|
const element = res[0];
|
|
const windowWidth = this.width; // 获取窗口宽度
|
|
const windowHeight = this.height; // 获取窗口高度
|
|
const menuWidth = 62 * count; // 动态计算菜单宽度
|
|
const menuHeight = 60; // 动态计算菜单高度,按每行最多 4 个菜单项布局
|
|
let isBottom = false;
|
|
|
|
// 默认菜单显示在元素上方
|
|
let left = isMe?element.left + (element.width / 2) - (menuWidth / 1.3):element.left + (element.width / 2)- (menuWidth / 2);
|
|
let top = element.top - menuHeight - 10;
|
|
|
|
// 如果菜单超出左侧边界,将菜单固定到左侧边距
|
|
if (left < 10) {
|
|
left = 40; // 保留 10px 边距
|
|
}
|
|
|
|
// 如果菜单超出顶部边界,将菜单显示在元素下方
|
|
if (top < 10) {
|
|
isBottom = true;
|
|
top = element.top + element.height + 20;
|
|
}
|
|
|
|
// 更新菜单样式
|
|
this.popStyle = {
|
|
left: `${left}px`,
|
|
top: `${top}px`,
|
|
// width: `${menuWidth}px`,
|
|
height: `${menuHeight}px`,
|
|
};
|
|
|
|
// 计算小尾巴的样式
|
|
const tailLeft = element.left+ (element.width / 2) - 10; // 小尾巴居中
|
|
|
|
const tailTop = isBottom?top-8:top+menuHeight-15; // 根据菜单显示位置调整
|
|
|
|
this.tailStyle = {
|
|
left: `${tailLeft}px`,
|
|
top: `${tailTop}px`,
|
|
};
|
|
}
|
|
});
|
|
},
|
|
ListTouchMove(e){
|
|
this.islongPress='move';
|
|
},
|
|
undoMsg(){
|
|
let message=this.curMsg;
|
|
console.log(message);
|
|
this.modelName='';
|
|
this.$api.msgApi.undoMessage({ id: message.id })
|
|
.then(res => {
|
|
const data = {
|
|
id: message.id,
|
|
type: "event",
|
|
is_undo:1,
|
|
content: '你撤回了一条消息',
|
|
oldContent: message.is_group==1? message.plain_text : message.content,
|
|
toContactId: message.toContactId,
|
|
};
|
|
this.updateMessage(data);
|
|
const list = []
|
|
list.push(data)
|
|
// #ifdef APP-PLUS
|
|
this.insertdata(list)
|
|
// #endif
|
|
})
|
|
|
|
},
|
|
delMsg(){
|
|
let message=this.curMsg;
|
|
this.modelName='';
|
|
uni.showModal({
|
|
title: `删除消息会从${this.globalConfig.chatInfo.dbDelMsg=="1"?'双方':'当前'}聊天记录中抹掉,是否确定?`,
|
|
success: e => {
|
|
if (e.confirm) {
|
|
// if(this.globalConfig.chatInfo.dbDelMsg=="1"){
|
|
// this.$api.msgApi.delMessage({ id: message.id }).then(res => {
|
|
// if(res.code==0){
|
|
// this.messageList.splice(this.curIndex, 1);
|
|
// }
|
|
// })
|
|
// }else{
|
|
this.$api.msgApi.removeMessage({ id: message.id }).then(res => {
|
|
if(res.code==0){
|
|
const index = this.messageList.findIndex(item => item.msg_id === message.msg_id);
|
|
if(index !== -1){
|
|
this.messageList.splice(index, 1);
|
|
}
|
|
}
|
|
})
|
|
// }
|
|
}
|
|
}
|
|
});
|
|
},
|
|
copyMsg(){
|
|
let content=this.$util.htmlToLines(this.curMsg.content);
|
|
console.log(this.curMsg.content);
|
|
uni.setClipboardData({
|
|
data:content,
|
|
showToast:true
|
|
});
|
|
this.modelName='';
|
|
this.curMsg='';
|
|
},
|
|
colEmoji(){
|
|
let msg=this.curMsg;
|
|
this.$api.emojiApi.addEmoji({ file_id: msg.file_id })
|
|
.then(res => {
|
|
if(res.code==0){
|
|
// 添加后更新表情列表
|
|
this.$refs.imInput.getEmojiList();
|
|
}
|
|
})
|
|
},
|
|
// 转发消息
|
|
forwardMsg(){
|
|
let msg=this.curMsg;
|
|
const result = msg.content.replace(/<a\b[^>]*>(.*?)<\/a>/g, '$1');
|
|
// console.log(result);
|
|
uni.navigateTo({
|
|
url:'/pages/index/userSelection?contact_id='+this.contact_id+'&type=3&content='+result
|
|
})
|
|
},
|
|
// 引用消息
|
|
quoteMsg(){
|
|
let msg=this.curMsg;
|
|
let regex = /<[^>]+>/g; // 定义正则表达式,匹配所有的HTML标签
|
|
let content=msg.content.replace(regex, '');
|
|
if(!['text','event','location','contact','create'].includes(msg.type)){
|
|
content = this.$util.getMsgType(msg.type);
|
|
}
|
|
let quote={
|
|
msg_id:msg.msg_id,
|
|
content:msg.fromUser.displayName+':'+content,
|
|
user_id:msg.fromUser.id,
|
|
realname:msg.fromUser.displayName
|
|
};
|
|
this.$refs.imInput.quoteMessage(quote);
|
|
},
|
|
reEdit(text){
|
|
// console.log(text);
|
|
this.$refs.imInput.inputMsg1=text;
|
|
this.$refs.imInput.isFocus=true;
|
|
},
|
|
// 滚动到页面底部
|
|
scrollToBottom(){
|
|
// this.scrollTop=99999999;
|
|
// const query=this.$util.getQuery(this);
|
|
// setTimeout(() => {
|
|
// query.select('.cu-chat').boundingClientRect();
|
|
// query.exec(data => {
|
|
// let height=999999;
|
|
// if(data[0]){
|
|
// height=data[0].height;
|
|
// }
|
|
// this.scrollTop=height+3000;
|
|
// });
|
|
// }, 10);
|
|
},
|
|
// 打开聊天详情
|
|
openDetails(){
|
|
uni.navigateTo({
|
|
url:"/pages/message/detail?id="+this.contact.id+"&is_group="+this.contact.is_group
|
|
})
|
|
uni.setStorageSync('notice',this.groupInfo.notice);
|
|
},
|
|
// 重新发送
|
|
reSend(message){
|
|
message.status='going';
|
|
// message.msg_id = message.msg_id
|
|
this.sendMessage(JSON.parse(JSON.stringify(message)),'',true);
|
|
// this.$refs.paging.addChatRecordData(message);
|
|
},
|
|
sendChatID(message){
|
|
this.isAnswering = true;
|
|
this.$api.msgApi.sendChat({content:message}).then((res)=>{
|
|
// if(res.data.user_id==this.user.user_id){
|
|
const processedData = {
|
|
...res.data, // 保留原数据所有字段
|
|
content: '思考中...' // 替换处理后的content
|
|
};
|
|
this.$refs.paging.addChatRecordData(processedData);
|
|
|
|
setTimeout(() => {
|
|
// 完整的回复字符串,这里直接重复用户提问的内容
|
|
const totalAnswerStr = res.data.content;
|
|
// 当前显示回复的字符串
|
|
let currentAnswerStr = '';
|
|
this.streamTextAsync(totalAnswerStr, (char) => {
|
|
currentAnswerStr += char;
|
|
// 获取最后一条数据,也就是上面思考中这条数据,然后更新这条数据
|
|
this.messageList[0].content = md.render(currentAnswerStr);
|
|
// 这里是判断当前显示回复的字符串长度等于完整的回复字符串长度,也就是回答结束了,将回答中状态设置为false
|
|
if (currentAnswerStr.length === totalAnswerStr.length) {
|
|
this.isAnswering = false;
|
|
}
|
|
})
|
|
}, 100)
|
|
// this.$refs.paging.addChatRecordData(res.data);
|
|
// }
|
|
})
|
|
},
|
|
// 模拟生成流式数据,根据一个已知字符串每150毫秒返回一个字符
|
|
async streamTextAsync(text, callback, interval = 200) {
|
|
for (const char of text) {
|
|
callback(char); // 逐个返回字符
|
|
await new Promise(resolve => setTimeout(resolve, interval)); // 等待
|
|
}
|
|
},
|
|
// 发送消息
|
|
sendMessage(message,file,isReSend=false){
|
|
// console.log(message);
|
|
// 如果开启了群聊禁言或者关闭了单聊权限,就不允许发送消息
|
|
if(!this.nospeak()){
|
|
//已开启禁言
|
|
uni.showToast({
|
|
title: '群已开启禁言,无法发送消息',
|
|
icon: "none"
|
|
})
|
|
return;
|
|
}
|
|
let user=this.user;
|
|
user.id=user.user_id;
|
|
message.fromUser=user;
|
|
message.from_user=this.user.user_id;
|
|
message.toContactId=this.contact.id;
|
|
message.is_group=this.contact.is_group;
|
|
// console.log(message);
|
|
if(!isReSend&&message.fromUser.id!=="-2"){
|
|
// &&message.type!=='emoji'
|
|
this.messageList.unshift(message);
|
|
}
|
|
// console.log(imgSrcs);
|
|
this.scrollToBottom();
|
|
let fileTypes = ["image", "file", "video",'voice'];
|
|
let simpleType=['text','location','contact','emoji'];
|
|
if(message.imgid==1||isReSend==true&&message.type!=='text'&&message.type!=='location'&&message.type!=='contact'&&message.type!=='emoji'){
|
|
var self1=this;
|
|
console.log(message);
|
|
this.$api.msgApi.uploadFileImage({image_url:message.content,message:JSON.stringify(message),type:message.type=='image'?'image':'file'}).then((res) => {
|
|
// console.log('123456',res);
|
|
this.updateMessage(res.data);
|
|
const list = []
|
|
list.push(res.data)
|
|
// console.log('123456',res.data);
|
|
if(res.data.type=='image'){
|
|
list.forEach((res)=>{
|
|
const parts = res.content.split('/')
|
|
let lastPart = parts.pop() || parts.pop() || ''
|
|
res.imgname = lastPart
|
|
})
|
|
}
|
|
// console.log('123456',list);
|
|
// #ifdef APP-PLUS
|
|
this.insertdata(list)
|
|
if(res.data.type=='image'){
|
|
uni.downloadFile({ url: res.data.content,success: (downloadResult) => {
|
|
self1.saveToPermanentStorage(downloadResult.tempFilePath);
|
|
}})
|
|
}
|
|
// #endif
|
|
})
|
|
// console.log('123456',message);
|
|
|
|
return
|
|
}
|
|
// console.log('123456',message);
|
|
if(simpleType.includes(message.type)){
|
|
var self=this;
|
|
this.$api.msgApi.sendMessage(message)
|
|
.then((res) => {
|
|
if(res.code==0){
|
|
const list = []
|
|
list.push(res.data)
|
|
// #ifdef APP-PLUS
|
|
let imgSrcs = []
|
|
let emojiSrcs = []
|
|
list.forEach((res)=>{
|
|
const regex = /<img\s+[^>]*src="([^"]+)"/g;
|
|
let match;
|
|
while ((match = regex.exec(res.content)) !== null) {
|
|
imgSrcs.push(match[1]);
|
|
}
|
|
if(imgSrcs.length!==0){
|
|
imgSrcs.forEach((img)=>{
|
|
const parts = img.split('/')
|
|
let lastPart = parts.pop() || parts.pop() || ''
|
|
emojiSrcs.push(lastPart)
|
|
uni.downloadFile({ url: img,success: (downloadResult) => {
|
|
self.saveToPermanentStorage(downloadResult.tempFilePath);
|
|
}})
|
|
})
|
|
res.imgname = emojiSrcs
|
|
}
|
|
if(res.type=='emoji'){
|
|
const parts = res.content.split('/')
|
|
let lastPart = parts.pop() || parts.pop() || ''
|
|
res.imgname = lastPart
|
|
uni.downloadFile({ url: res.content,success: (downloadResult) => {
|
|
self.saveToPermanentStorage(downloadResult.tempFilePath);
|
|
}})
|
|
}
|
|
})
|
|
this.insertdata(list)
|
|
// #endif
|
|
this.updateMessage(res.data);
|
|
// console.log('123456',res);
|
|
}else if(res.code==401){
|
|
// 删除最后一条信息
|
|
// this.messageList.pop();
|
|
this.messageList.shift();
|
|
//已开启禁言
|
|
uni.showToast({
|
|
title: res.msg,
|
|
icon: "none"
|
|
})
|
|
}else{
|
|
this.sendFailed(message);
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
this.sendFailed(message);
|
|
});
|
|
}else if (fileTypes.includes(message.type)) {
|
|
var self=this;
|
|
let maxSize=this.globalConfig.fileUpload.size ?? 10;
|
|
// console.log(message.fileSize);
|
|
// console.log(maxSize*1024*1024);
|
|
if(message.fileSize>maxSize*1024*1024){
|
|
return uni.showToast({
|
|
title: '文件大小不能超过'+maxSize+'M',
|
|
icon:'none'
|
|
})
|
|
}
|
|
|
|
uni.uploadFile({
|
|
url: this.$api.msgApi.uploadUrl,
|
|
filePath: message.content,
|
|
name: 'file',
|
|
header: {
|
|
'Authorization': uni.getStorageSync('authToken')
|
|
},
|
|
formData: {
|
|
message: JSON.stringify(message)
|
|
},
|
|
success: (e) => {
|
|
let res=JSON.parse(e.data);
|
|
|
|
if(res.code==0){
|
|
this.updateMessage(res.data);
|
|
const list = []
|
|
list.push(res.data)
|
|
// console.log('123456',res.data);
|
|
if(res.data.type=='image'||res.data.type=='video'){
|
|
list.forEach((res)=>{
|
|
const parts = res.content.split('/')
|
|
let lastPart = parts.pop() || parts.pop() || ''
|
|
res.imgname = lastPart
|
|
})
|
|
}
|
|
// console.log('123456',list);
|
|
// #ifdef APP-PLUS
|
|
this.insertdata(list)
|
|
if(res.data.type=='image'||res.data.type=='video'){
|
|
uni.downloadFile({ url: res.data.content,success: (downloadResult) => {
|
|
self.saveToPermanentStorage(downloadResult.tempFilePath);
|
|
}})
|
|
}
|
|
// #endif
|
|
}else if(res.code==401){
|
|
// 删除最后一条信息
|
|
// this.messageList.pop();
|
|
this.messageList.shift();
|
|
//已开启禁言
|
|
uni.showToast({
|
|
title: res.msg,
|
|
icon: "none"
|
|
})
|
|
}else{
|
|
this.sendFailed(message);
|
|
}
|
|
},
|
|
fail: (res) => {
|
|
// console.log('123456',res);
|
|
this.sendFailed(message);
|
|
}
|
|
})
|
|
}
|
|
|
|
},
|
|
sendFailed(message){
|
|
message.status='failed';
|
|
this.updateMessage(JSON.parse(JSON.stringify(message)));
|
|
},
|
|
|
|
// App端持久化存储实现
|
|
saveToPermanentStorage(tempPath) {
|
|
return new Promise((resolve, reject) => {
|
|
// 获取应用文档目录(持久化存储)
|
|
plus.io.resolveLocalFileSystemURL(
|
|
'_doc',
|
|
(docDir) => {
|
|
// 创建目标路径
|
|
docDir.getDirectory(
|
|
'image',
|
|
{ create: true, exclusive: false },
|
|
(entry) => {
|
|
// 从临时路径获取文件名
|
|
const fileName = this.getFileName(tempPath);
|
|
const fileName1 = this.getFileName(docDir.fullPath + 'image/' +fileName);
|
|
// console.log(fileName);
|
|
// console.log(fileName1);
|
|
// 新增:检查文件是否存在
|
|
entry.getFile(fileName1,{ create: false }, // 不创建新文件
|
|
(fileEntry) => {
|
|
// console.log('文件已存在,拒绝操作');
|
|
// 文件已存在,拒绝操作
|
|
reject(new Error('File already exists: ' + fileName));
|
|
},
|
|
(error) => {
|
|
// console.log(error);
|
|
// 文件不存在(或发生其他错误),继续复制操作
|
|
if (error.code === 14) { // 1表示文件不存在(不同平台错误码可能不同)
|
|
this.copyFile(tempPath, entry, fileName, resolve, reject);
|
|
} else {
|
|
reject(error);
|
|
}
|
|
}
|
|
);
|
|
},
|
|
(error) => {
|
|
reject(error);
|
|
}
|
|
);
|
|
},
|
|
(error) => {
|
|
reject(error);
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
// 提取复制逻辑为独立方法
|
|
copyFile(tempPath, targetDir, fileName, resolve, reject) {
|
|
plus.io.resolveLocalFileSystemURL(
|
|
tempPath,
|
|
(tempEntry) => {
|
|
tempEntry.copyTo(
|
|
targetDir,
|
|
fileName,
|
|
(newEntry) => {
|
|
resolve(newEntry.toLocalURL());
|
|
},
|
|
(error) => {
|
|
reject(error);
|
|
}
|
|
);
|
|
},
|
|
(error) => {
|
|
reject(error);
|
|
}
|
|
);
|
|
},
|
|
|
|
// 获取文件名工具方法
|
|
getFileName(path) {
|
|
const index = path.lastIndexOf('/');
|
|
let fileName = path.substr(index + 1);
|
|
fileName = fileName.replace(/\(\d+\)(?=\.[^./]+$)/, '');
|
|
return fileName;
|
|
},
|
|
|
|
async getImagePath(){
|
|
this.imglist = await getSavedImages2()
|
|
this.imglist.map(item => {
|
|
item.path = plus.io.convertLocalFileSystemURL(item.path)
|
|
});
|
|
this.getchatList()
|
|
// console.info('读取地址',this.imglist);
|
|
},
|
|
|
|
// 语音播放基础函数
|
|
playNow: function (voicelUrl, index){
|
|
this.playIndex = index;
|
|
innerAudioContext.autoplay=true;
|
|
innerAudioContext.src = voicelUrl;
|
|
innerAudioContext.play();
|
|
return true;
|
|
},
|
|
// 播放语音
|
|
playVoice: function (e) {
|
|
var voicelUrl = e.currentTarget.dataset.voice;
|
|
var index = e.currentTarget.dataset.index;
|
|
if (this.playIndex == -1){
|
|
return this.playNow(voicelUrl, index);
|
|
}
|
|
if (this.playIndex == index) {
|
|
innerAudioContext.stop();
|
|
this.playIndex = -1;
|
|
} else {
|
|
innerAudioContext.stop();
|
|
this.playIndex = -1;
|
|
this.playNow(voicelUrl, index);
|
|
}
|
|
},
|
|
// 如果点击了聊天记录列表页,需要收起表情面板或者其他的面板
|
|
closeInput(e){
|
|
this.boxStatus++;
|
|
},
|
|
// 禁言时禁止发送消息
|
|
nospeak(){
|
|
if(this.is_group==1 && this.contact.setting.nospeak>0){
|
|
if(this.contact.setting.nospeak==1 && this.contact.role<3){
|
|
return true;
|
|
}else if(this.contact.setting.nospeak==2 && this.contact.role==1){
|
|
return true;
|
|
}else{
|
|
return false;
|
|
}
|
|
}else{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
page{
|
|
padding-bottom: 100upx;
|
|
}
|
|
|
|
#more-oprate{
|
|
min-height:100%;
|
|
justify-content: flex-end;
|
|
flex-direction: column;
|
|
}
|
|
.cu-chat .cu-item.self {
|
|
justify-content: flex-start;
|
|
text-align: right;
|
|
}
|
|
.bg-light-green{
|
|
background-color: #95ec69;
|
|
}
|
|
.bg-light-grey{
|
|
background-color: #fff;
|
|
}
|
|
.at-fixed-item{position:fixed;right:20rpx;bottom:120rpx;background-color: #fff;border-radius:30rpx;color:#18bc37;padding:12rpx 18rpx}
|
|
.im{padding:30rpx;}
|
|
.im-system-msg{color:#FFFFFF; font-size:26rpx; line-height:38rpx; padding:5px 10px; display:block; border-radius:6rpx;}
|
|
.im-msg{margin-bottom:28px; display:flex; flex-direction:row; flex-wrap:nowrap;}
|
|
.im-voice-msg{height:80rpx; padding:0 20rpx; background-color:#E7F0F3; color:#2B2E3D; min-width:160rpx; max-width:400rpx;}
|
|
.im-voice-msg-text{font-size:22rpx; margin:0 5rpx;}
|
|
.im-location-msg{ background-color:#E7F0F3; color:#2B2E3D;text-align: left !important;}
|
|
.im-contact-msg{ width:360rpx; background-color:#E7F0F3; color:#2B2E3D;text-align: left !important;}
|
|
.cu-chat .cu-item>.main {
|
|
max-width: calc(100% - 230rpx);
|
|
margin: 0 0.8rem;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
.course-video{
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.icon-center{
|
|
position: absolute;
|
|
top: 50%;
|
|
z-index: 4;
|
|
transform: translate(-50%, -50%);
|
|
left: 50%;
|
|
padding: 0 4rpx 0 6rpx;
|
|
}
|
|
.video-duration{
|
|
position: absolute;
|
|
bottom:5px;
|
|
right:5px;
|
|
}
|
|
.relative-shadow{
|
|
position: absolute;width:100%;height:100%;background: #8383833d;z-index:1;
|
|
}
|
|
.file-card{
|
|
width:420rpx;
|
|
height:120rpx;
|
|
.file-icon{
|
|
width:60rpx;
|
|
height:80rpx;
|
|
}
|
|
.file-name{
|
|
text-align: left !important;
|
|
width:300rpx;
|
|
}
|
|
.file-size{
|
|
text-align: left !important;
|
|
margin-top:8rpx;
|
|
}
|
|
}
|
|
|
|
.icon-spin{
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.main .content ::v-deep uni-text ,
|
|
.main .content ::v-deep uni-text span,
|
|
.main .content ::v-deep text,
|
|
.main .content ::v-deep uni-rich-text{
|
|
word-wrap: break-word !important;
|
|
word-break: break-all !important;
|
|
}
|
|
|
|
.main .content ::v-deep ._block ._a{
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
.text-container{
|
|
-webkit-user-select:text !important;
|
|
user-select:text !important;
|
|
font-size:48rpx;
|
|
word-wrap: break-word !important;
|
|
text-align: left;
|
|
line-height: 1.5;
|
|
letter-spacing: 1.2px;
|
|
color:#333;
|
|
}
|
|
::v-deep .checklist-group{
|
|
display: grid !important;
|
|
.checklist-box{
|
|
padding:20rpx;
|
|
.checkbox__inner{
|
|
width:40rpx !important;
|
|
height:40rpx !important;
|
|
overflow:hidden;
|
|
.checkbox__inner-icon{
|
|
position: absolute;
|
|
top: -8px !important;
|
|
left: -4px !important;
|
|
height: 20px !important;
|
|
width: 20px !important;
|
|
border-right-width: 2px !important;
|
|
border-bottom-width: 2px !important;
|
|
}
|
|
}
|
|
.checklist-content{
|
|
margin-left:20rpx;
|
|
.checklist-text{
|
|
font-size:36rpx !important;
|
|
}
|
|
|
|
}
|
|
}
|
|
.is-checked{
|
|
.checkbox__inner{
|
|
background-color: #18bc37 !important;
|
|
border-color: #18bc37 !important;
|
|
}
|
|
.checklist-content{
|
|
.checklist-text{
|
|
color: #18bc37 !important;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
.read-status{
|
|
font-weight: 600;
|
|
}
|
|
|
|
.code-highlight-container {
|
|
background-color: #f6f8fa; /* 浅灰色背景 */
|
|
}
|
|
</style>
|
|
|
|
<style lang="scss" scoped>
|
|
.message-quote{
|
|
padding:8rpx;
|
|
font-size:24rpx;
|
|
margin-top:16rpx;
|
|
background-color: #e3e3e3;
|
|
overflow: hidden !important;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap !important;
|
|
max-width:380rpx;
|
|
text-align: left;
|
|
}
|
|
// 设置表情图片居中
|
|
::v-deep .emoji-image{
|
|
vertical-align: text-top !important;
|
|
}
|
|
|
|
.cu-chat ::v-deep .cu-item {
|
|
padding: 20rpx;
|
|
}
|
|
.cu-chat ::v-deep .cu-item:last-child{
|
|
padding-bottom:60rpx;
|
|
}
|
|
.back-unread{
|
|
background-color: #e3e3e3;
|
|
padding:4rpx 10rpx;
|
|
border-radius: 50%;
|
|
font-size: 22rpx;
|
|
}
|
|
|
|
.add-modal {
|
|
.add-dialog {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
height: 100rpx; /* 与菜单高度一致 */
|
|
background-color: #4f4f4f;
|
|
color: #fff;
|
|
border-radius: 10rpx;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
position: absolute;
|
|
padding: 10rpx;
|
|
|
|
.add-item {
|
|
width: 90rpx; /* 每个菜单项的宽度 */
|
|
height: 70rpx; /* 每个菜单项的高度 */
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin: 6rpx;
|
|
line-height: 1.5;
|
|
view{
|
|
font-size: 22rpx;
|
|
}
|
|
}
|
|
|
|
.add-dialog-tail {
|
|
width: 20px;
|
|
height: 20px;
|
|
position: fixed;
|
|
transform: rotate(45deg);
|
|
background: #4f4f4f;
|
|
z-index: -1;
|
|
}
|
|
}
|
|
}
|
|
.show{
|
|
position: fixed;
|
|
top: 0;
|
|
z-index: 9999;
|
|
height: 100vh;
|
|
width: 100vw;
|
|
}
|
|
.none{
|
|
position: fixed;
|
|
top: 0;
|
|
right: 0;
|
|
z-index: -10;
|
|
opacity: 0;
|
|
}
|
|
.blur-background {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
background-size: cover;
|
|
background-position: center;
|
|
}
|
|
|
|
.filter-blur::before {
|
|
content: "";
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
backdrop-filter: blur(6px); /* 应用10px的背景模糊效果 */
|
|
z-index: 0;
|
|
}
|
|
|
|
.message-head{
|
|
width:100%;
|
|
margin-bottom: 6rpx;
|
|
}
|
|
|
|
.blur-image {
|
|
filter: blur(1px);
|
|
transform: scale(1.1);
|
|
opacity: 1;
|
|
}
|
|
|
|
.fixed-item{
|
|
position: fixed;
|
|
right: 0.625rem;
|
|
bottom: 3.75rem;
|
|
background-color: #fff;
|
|
border-radius: 0.9375rem;
|
|
color: #0389fb;
|
|
padding: 0.4375rem 0.5625rem;
|
|
}
|
|
|
|
.radius-round{
|
|
border-radius: 50% !important;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.transform180{
|
|
transform: rotateY(180deg);
|
|
}
|
|
.backsize{
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background-size: cover; /* 确保图片覆盖整个容器 */
|
|
background-position: center; /* 图片居中显示 */
|
|
background-repeat: no-repeat; /* 不重复图片 */
|
|
}
|
|
</style>
|
|
|