This commit is contained in:
cansnow
2025-12-17 08:47:58 +08:00
parent 916cb22ecc
commit cf1ad1c24b
68 changed files with 2423 additions and 6104 deletions
@@ -0,0 +1,176 @@
<template>
<!-- #ifdef APP-PLUS -->
<view class="voice_title" @touchstart.stop.prevent="startVoice"
@touchmove.stop.prevent="moveVoice" @touchend.stop="endVoice"
@touchcancel.stop="cancelVoice" :style="{ background: recording ? '#c7c6c6' : '#FFFFFF' }">
<text>{{ voiceTitle }}</text>
</view>
<!-- #endif -->
</template>
<script>
import {mapActions,mapGetters} from "vuex";
export default {
name:"Recoder",
props: {
placeholder: {
type: String,
default: "",
},
maxlength: {
type: Number,
default: -1,
}
},
data() {
return {
recording:false,
sendMsgTimmer: null, //发送时间定时器
sendDuring: 0, //发送时间计数器 1分钟以内的信息不显示时间
sendTimeBetween: 60, //发送信息显示的间隔,60秒以内信息不显示发送时间
voiceTitle:"点击录音",
AudioExam:null,
isStopVoice : false,
voiceCanSend : true,
voiceIconText : "正在录音...",
PointX : 0,
PointY : 0,
voiceInterval:null,
voiceTime:0,
};
},
watch: {
voiceIconText(nv,ov){
this.$emit('RecodeEvent',{type:"voiceIconTextChange",text:nv})
},
recording(nv,ov){
this.$emit('RecodeEvent',{type:"recordingStateChange",state:nv})
}
},
created() {
const _this = this;
//录音器,getRecorderManager不支持H5
// #ifdef APP-PLUS
//获取全局唯一的录音管理器(https://uniapp.dcloud.net.cn/api/media/record-manager.html#getrecordermanager
_this.Recorder = uni.getRecorderManager();
//录音开始事件
_this.Recorder.onStart(e => {
_this.beginVoice();
});
//录音结束事件
_this.Recorder.onStop(res => {
clearInterval(_this.voiceInterval);
_this.handleRecorder(res);
});
// #endif
},
methods: {
...mapActions("message", ["updateCurrentMsg"]),
/*----------------------------------------------------H5不支持)录音相关 start-------------------------------------- */
//准备开始录音
startVoice(e) {
//如果音频正在播放 先暂停。
this.updateCurrentMsg={clientMsgID:""};
this.recording = true;
this.isStopVoice = false;
this.voiceCanSend = true;
this.voiceIconText = "正在录音..."
this.PointY = e.touches[0].clientY;
this.Recorder.start({
format: 'mp3'
});
},
//录音已经开始
beginVoice() {
let that = this;
if (that.isStopVoice) {
that.Recorder.stop();
return;
}
that.voiceTitle = '松开 结束'
that.voiceInterval = setInterval(() => {
console.log("that.voiceTime", that.voiceTime);
if (that.voiceTime > 49) {
that.voiceIconText = "录音结束倒计时[" + (60 - that.voiceTime) + "]s";
};
if (that.voiceTime == 60) {
clearInterval(that.voiceInterval);
that.endVoice();
}
that.voiceTime++;
}, 1000)
},
//move 正在录音中
moveVoice(e) {
const PointY = e.touches[0].clientY;
const slideY = this.PointY - PointY;
if (slideY > uni.upx2px(120)) {
this.voiceCanSend = false;
this.voiceIconText = '松开手指 取消发送 '
} else if (slideY > uni.upx2px(60)) {
this.voiceCanSend = true;
this.voiceIconText = '手指上滑 取消发送 '
} else {
this.voiceIconText = '正在录音... '
}
},
//结束录音
endVoice() {
this.isStopVoice = true; //加锁 确保已经结束录音并不会录制
this.Recorder.stop();
this.voiceTitle = '按住 说话'
},
//录音被打断
cancelVoice(e) {
console.log("路由被打断", e);
this.voiceTime = 0;
this.voiceTitle = '按住 说话';
this.voiceCanSend = false;
this.Recorder.stop();
},
//处理录音文件
handleRecorder({tempFilePath,duration }) {
if (this.voiceTime < 1) {
this.voiceIconText = "说话时间过短";
setTimeout(() => {
this.recording = false;
}, 500)
return;
}
let contentDuration = this.voiceTime;
this.voiceTime = 0;
this.recording = false;
clearInterval(this.voiceInterval);
//console.log("录音文件", tempFilePath);
//console.log("是否发送语音信息", this.voiceCanSend);
let voiceFile = {
tempFilePath: tempFilePath,
contentDuration: Math.ceil(contentDuration),
anmitionPlay: false,
};
if (this.voiceCanSend) {
//console.log("=====上传语音文件,并发送语音信息====");
this.$emit('RecodeEvent',{type:"sendVoiceMessage",audio:voiceFile})
return;
} else {
console.log("=====已经取消发送语音信息====")
return;
}
}
/*-------------------------------------录音相关方法块 end---------------------------------------------------*/
},
};
</script>
<style lang="scss" scoped>
.voice_title {
text-align: center;
background-color: #ffffff;
height: 70rpx;
line-height: 70rpx;
border-radius: 12rpx;
font-weight: bold;
font-size: 32rpx;
}
</style>
@@ -114,10 +114,6 @@
@include vCenterBox();
flex-direction: column;
&_single {
margin-bottom: 24rpx;
}
.conversation_info {
flex-direction: row;
justify-content: center;
@@ -126,7 +122,6 @@
.title {
@include nomalEllipsis();
max-width: 280rpx;
font-size: 14px;
font-weight: 500;
}
@@ -1,10 +1,13 @@
<template>
<view class="at_text_message_container bg_container">
消息
<u-parse :content="getContent" :previewImg="false" :showImgMenu="false" selectable></u-parse>
</view>
</template>
<script>
import {
parseBr
} from "@/util/common";
export default {
name: "AtTextMessageRender",
components: {},
@@ -12,6 +15,11 @@
message: Object,
conversationID:String,
},
computed: {
getContent() {
return parseBr(this.message.textElem?.content);
},
},
data() {
return {};
},
@@ -1,10 +1,34 @@
<template>
<view class="location_message_container bg_container">
消息
<u--image
:showLoading="true"
:width="selfWidth"
:height="maxHeight"
mode="widthFix"
v-if="src"
:src="src"
@load="onLoaded"
@click="clickMediaItem">
<template v-slot:loading>
<u-loading-icon color="red"></u-loading-icon>
</template>
</u--image>
<u--image
v-else
:showLoading="true"
:width="selfWidth"
mode="widthFix"
src="/static/images/sync_error.png">
</u--image>
<u--text class="address" :style="{
width:selfWidth+'px'
}" :lines="1" :size="20" :text="desc"></u--text>
</view>
</template>
<script>
import util from "@/util"
import md5 from "md5";
export default {
name: "LocationMessageRender",
components: {},
@@ -13,12 +37,85 @@
conversationID:String,
},
data() {
return {};
return {
selfWidth:200,
loadingWidth: "200px",
src:"",
coverDownloading:false,
coverDownloadProgress:"",
apisrc:"",
maxHeight:"",
desc:""
};
},
created() {
let loc = this.message.locationElem;
this.desc = loc.description;
this.apisrc = "http://api.tianditu.gov.cn/staticimage?"
+"center="+loc.longitude+","+loc.latitude
+"&width=400"
+"&height=300"
+"&zoom=10"
+"&markers="+loc.longitude+","+loc.latitude
+"&tk=5255a4be64441ba9fa2ebe605ca472bf";
//
//this.apisrc="http://192.168.1.222/staticimage.png";
this.init();
},
methods: {
getImageInfo(src){
const _this = this;
uni.getImageInfo({
src:src,
success(res) {
const imageHeight = res.height;
const imageWidth = res.width;
const aspectRatio = imageHeight / imageWidth;
_this.maxHeight = (_this.selfWidth * aspectRatio);
console.log(res)
_this.src = src;
}
})
},
async init(){
const self = this;
let url = "";
// 如果有远程 snapshotUrl,则下载到 coverCachePath
const snapshotUrl = this.apisrc ;
const key = md5(snapshotUrl || '');
const dir_name = `${this.conversationID}`;
const save_file_name = `loc_${key}.png`;
const coverCachePath = `${dir_name}/${save_file_name}`;
if (typeof plus === 'undefined' || !coverCachePath) return;
self.coverDownloading = true;
util.cacheFile(snapshotUrl,coverCachePath,(fn)=>{
self.coverDownloading = false;
self.getImageInfo(fn);
console.log(fn);
},(e)=>{
console.log(e);
},(e)=>{
console.log(e);
});
},
clickMediaItem() {
let loc = this.message.locationElem;
uni.navigateTo({
url:"/pages/common/map?type=viewlocation&lng="+loc.longitude+"&lat="+loc.latitude+"&address="+loc.description
})
},
onLoaded() {
this.loadingWidth = "auto";
},
},
};
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.location_message_container{
.address{
margin-top: 20rpx;
}
}
</style>
@@ -13,6 +13,13 @@
<u-loading-icon color="red"></u-loading-icon>
</template>
</u--image>
<u--image
v-else
:showLoading="true"
width="120"
mode="widthFix"
src="/static/images/sync_error.png">
</u--image>
</view>
</template>
@@ -54,41 +61,22 @@
// 如果有远程 snapshotUrl,则下载到 coverCachePath
const snapshotUrl = (this.message.pictureElem.snapshotPicture?.url ?? this.message.pictureElem.sourcePath );
const key = md5(snapshotUrl || '');
this.coverCachePath = `_doc/${this.conversationID}/img_${key}.jpg`;
if (typeof plus === 'undefined' || !this.coverCachePath) return;
try {
// 检查封面是否存在
const coverExists = await util.fileExsit(self.coverCachePath);
this.coverExists = !!coverExists;
if (this.coverExists) {
this.src = this.coverCachePath;
return;
}
if (!snapshotUrl) {
this.src="/static/images/sync_error.png";
return;
}
this.coverDownloading = true;
await new Promise((resolve, reject) => {
util.downloadFile(snapshotUrl, self.coverCachePath, function(localPath) {
self.coverDownloading = false;
self.coverExists = true;
resolve(localPath);
}, function(err) {
self.coverDownloading = false;
reject(err);
}, function(progress) {
self.coverDownloadProgress = progress;
});
});
} catch (e) {
this.coverDownloading = false;
}
this.coverCachePath = `${this.conversationID}/img_${key}.jpg`;
util.cacheFile(snapshotUrl,this.coverCachePath,(e)=>{
self.coverDownloading = false;
self.src = coverCachePath;
console.log(e);
},(e)=>{
console.log(e);
},(e)=>{
console.log(e);
});
},
clickMediaItem() {
uni.previewImage({
current: 0,
urls: [this.message.pictureElem.sourcePicture.url],
//urls: [this.message.pictureElem.sourcePicture.url],
urls: ["_doc/"+this.coverCachePath],
indicator: "none",
});
},
@@ -104,21 +92,5 @@
position: relative;
border-radius: 16rpx;
overflow: hidden;
.play_icon {
width: 48px;
height: 48px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.video_duration {
position: absolute;
bottom: 12rpx;
right: 24rpx;
color: #fff;
}
}
</style>
@@ -1,6 +1,6 @@
<template>
<view class="text_message_container bg_container">
<mp-html :previewImg="false" :showImgMenu="false" :lazyLoad="false" selectable :content="getContent" />
<u-parse :content="getContent" :previewImg="false" :showImgMenu="false" selectable></u-parse>
</view>
</template>
@@ -96,9 +96,7 @@
coverDownloadProgress: 0,
videoDownloading: false,
videoDownloadProgress: 0,
coverExists: false,
videoExists: false,
coverCachePath:"",
videoCachePath:"",
previewVideoFlag: false,
previewVideoSrc: "",
@@ -121,44 +119,19 @@
// 如果有远程 snapshotUrl,则下载到 coverCachePath
const snapshotUrl = this.message?.videoElem?.snapshotUrl;
const key = md5(this.message?.videoElem?.videoUrl || '');
this.coverCachePath = `_doc/${this.conversationID}/cover_${key}.jpg`;
this.videoCachePath = `_doc/${this.conversationID}/${key}.mp4`;
const coverExists = await util.fileExsit(this.coverCachePath);
// 自动缓存封面到 this.coverCachePath
const self = this;
if (typeof plus === 'undefined' || !this.coverCachePath) return;
try {
// 检查封面是否存在
const coverExists = await util.fileExsit(self.coverCachePath);
this.coverExists = !!coverExists;
// 同时检查视频缓存是否存在
const vidExists = await util.fileExsit(self.videoCachePath);
this.videoExists = !!vidExists;
if (this.coverExists) {
this.src = this.coverCachePath;
return;
}
if (!snapshotUrl) {
this.src="/static/images/sync_error.png";
return;
}
this.coverDownloading = true;
await new Promise((resolve, reject) => {
util.downloadFile(snapshotUrl, self.coverCachePath, function(localPath) {
self.coverDownloading = false;
self.coverExists = true;
resolve(localPath);
}, function(err) {
self.coverDownloading = false;
reject(err);
}, function(progress) {
self.coverDownloadProgress = progress;
});
});
} catch (e) {
this.coverDownloading = false;
}
const coverCachePath = `${this.conversationID}/cover_${key}.jpg`;
this.videoCachePath = `${this.conversationID}/${key}.mp4`;
this.videoExists = await util.fileExists(this.videoCachePath);
self.coverDownloading = true;
util.cacheFile(snapshotUrl,coverCachePath,(e)=>{
self.coverDownloading = false;
self.src = coverCachePath;
console.log(e);
},(e)=>{
console.log(e);
},(e)=>{
console.log(e);
});
},
clickMediaItem() {
uni.previewImage({
@@ -173,7 +146,7 @@
onOverlayClick() {
// 点击覆盖层:如果视频已缓存则直接播放,否则开始下载
if (this.videoExists) {
this.playVideo(this.videoCachePath);
this.playVideo("_doc/"+this.videoCachePath);
return;
}
const url = this.message?.videoElem?.videoUrl || this.message?.videoElem?.videoPath;
@@ -186,7 +159,7 @@
util.downloadFile(url, this.videoCachePath, (localPath) => {
this.videoDownloading = false;
this.videoExists = true;
this.playVideo(localPath);
this.playVideo("_doc/"+localPath);
}, (err) => {
this.videoDownloading = false;
uni.showToast({ title: '下载失败' });
@@ -1,24 +1,144 @@
<template>
<view class="voice_message_container bg_container">
消息
<view @tap="handleAudio"
:class="{'content-audio-container':true,'chat-audio-container-me':true}"
:style="'width:'+((message.soundElem.duration)*2+130)+'rpx'">
<view class="voice_icon"
:class="[
{ voice_icon_right: isSender },
{ voice_icon_left: !isSender },
{ voice_icon_right_an: isSender && message.clientMsgID == storeCurrentMsg.clientMsgID},
{ voice_icon_left_an: !isSender && message.clientMsgID == storeCurrentMsg.clientMsgID }
]">
</view>
<view class="">{{message.soundElem.duration}}s</view>
</view>
</view>
</template>v
<script>
import {mapGetters,mapActions} from "vuex";
import util from "@/util"
import md5 from "md5";
export default {
name: "VoiceMessageRender",
components: {},
props: {
message: Object,
isSender: {
type: Boolean,
default: false,
},
conversationID:String,
},
data() {
return {};
console.log(this.message);
return {
src:"",
};
},
computed:{
...mapGetters(["storeCurrentMsg"]),
},
created() {
this.init();
},
methods: {
async init(){
console.log(this.message);
const self = this;
let audio = this.message.soundElem;
//soundPath
// 如果有远程 snapshotUrl,则下载到 cachePath
const snapshotUrl = audio.sourceUrl;
const key = md5(snapshotUrl || '');
const save_file_name = `audio_${key}.${audio.soundType}`;
const cachePath = `${this.conversationID}/${save_file_name}`;
if (typeof plus === 'undefined' || !cachePath) return;
util.cacheFile(snapshotUrl,cachePath,(fn)=>{
self.src = fn;
console.log(fn);
},(e)=>{
console.log(e);
},(e)=>{
console.log(e);
});
},
handleAudio(){
const event = {
message:this.message,
src:this.src,
type:'audio_msg_click'
};
this.$emit("messageEvent",event);
}
},
};
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
//语音信息样式
.content-audio-container {
border-radius: 10rpx;
padding: 0 20rpx;
/* #ifndef APP-NVUE */
max-width: 500rpx;
/* #endif */
display: flex;
flex-direction: row;
align-items: center;
.voice_icon {
height: 34rpx;
width: 34rpx;
background-repeat: no-repeat;
background-size: 100%;
}
.voice_icon_right {
background-image: url('@/static/images/chat/voice/voice-left-3.png');
margin-left: 10rpx;
}
.voice_icon_left {
background-image: url('@/static/images/chat/voice/voice-right-3.png');
margin-right: 10rpx;
}
.voice_icon_right_an {
animation: voiceAn_right 1s linear alternate infinite;
}
.voice_icon_left_an {
animation: voiceAn_left 1s linear alternate infinite;
}
@keyframes voiceAn_right {
0% {
background-image: url('@/static/images/chat/voice/voice-left-1.png');
}
50% {
background-image: url('@/static/images/chat/voice/voice-left-2.png');
}
100% {
background-image: url('@/static/images/chat/voice/voice-left-3.png');
}
}
@keyframes voiceAn_left {
0% {
background-image: url('@/static/images/chat/voice/voice-right-1.png');
}
50% {
background-image: url('@/static/images/chat/voice/voice-right-2.png');
}
100% {
background-image: url('@/static/images/chat/voice/voice-right-3.png');
}
}
}
</style>
@@ -16,7 +16,12 @@
</view>
</view>
<view class="message_content_wrap message_content_wrap_shadow" :id="`message_content_wrap_${source.clientMsgID}`" @longtap.stop.prevent="longtapEvent($event)">
<component :is="component" :message="source" :conversationID="conversationID"></component>
<component :is="component"
@messageEvent="onMessageEvent"
:isSender="isSender"
:message="source"
:conversationID="conversationID"
></component>
</view>
</view>
</view>
@@ -172,7 +177,9 @@
console.log('longtapEvent');
this.$emit('userEvent',{type:"longtapMsgContent"},this.source);
},
onMessageEvent(e){
this.$emit('userEvent',e);
},
},
};
</script>
@@ -295,7 +302,7 @@
flex-direction: row-reverse;
.bg_container {
border-radius: 12rpx 0 12rpx 12rpx;
border-radius: 6rpx;
background-color: #94ec68 !important;
}
}