新增语音输入和语音转文字接口对接,并调整部分样式
This commit is contained in:
parent
eb1523516c
commit
dd42b26d1f
@ -1,9 +1,6 @@
|
||||
import request from '@/service/index.js'
|
||||
import qs from 'qs'
|
||||
import {
|
||||
useTalkStore,
|
||||
useDialogueStore
|
||||
} from '@/store'
|
||||
import { useTalkStore, useDialogueStore } from '@/store'
|
||||
|
||||
// 获取聊天列表服务接口
|
||||
export const ServeGetTalkList = (data) => {
|
||||
@ -44,7 +41,11 @@ export const ServeTopTalkList = (data) => {
|
||||
// 清除聊天消息未读数服务接口
|
||||
export const ServeClearTalkUnreadNum = (data, unReadNum) => {
|
||||
console.log('=======chatApp==UnreadNum', unReadNum)
|
||||
if(!useTalkStore().items[useTalkStore().findTalkIndex(useDialogueStore().index_name)]?.is_disturb){
|
||||
if (
|
||||
!useTalkStore().items[
|
||||
useTalkStore().findTalkIndex(useDialogueStore().index_name)
|
||||
]?.is_disturb
|
||||
) {
|
||||
if (typeof plus !== 'undefined') {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview) => {
|
||||
@ -230,3 +231,12 @@ export const ServeMessageReadDetail = (data) => {
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// 语音转文字
|
||||
export const ServeConvertText = (data) => {
|
||||
return request({
|
||||
url: '/api/v1/talk/message/voice-to-text',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
@ -25,7 +25,15 @@
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<tm-image :width="40" :height="40" :src="copy07"></tm-image>
|
||||
<div>复制</div>
|
||||
<div class="mt-1">复制</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.isShowConvertText"
|
||||
@click="() => itemClick('convertText')"
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<tm-image :width="40" :height="40" :src="copy07"></tm-image>
|
||||
<div class="mt-1">转文字</div>
|
||||
</div>
|
||||
<div
|
||||
@click="() => itemClick('multipleChoose')"
|
||||
@ -36,7 +44,7 @@
|
||||
:height="40"
|
||||
:src="multipleChoices"
|
||||
></tm-image>
|
||||
<div>多选</div>
|
||||
<div class="mt-1">多选</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.isShowCite"
|
||||
@ -44,7 +52,7 @@
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<tm-image :width="40" :height="40" :src="cite"></tm-image>
|
||||
<div>引用</div>
|
||||
<div class="mt-1">引用</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="props.isShowWithdraw"
|
||||
@ -52,14 +60,14 @@
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<tm-image :width="40" :height="40" :src="withdraw"></tm-image>
|
||||
<div>撤回</div>
|
||||
<div class="mt-1">撤回</div>
|
||||
</div>
|
||||
<div
|
||||
@click="() => itemClick('actionDelete')"
|
||||
class="flex flex-col items-center justify-center"
|
||||
>
|
||||
<tm-image :width="40" :height="40" :src="delete07"></tm-image>
|
||||
<div>删除</div>
|
||||
<div class="mt-1">删除</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :style="data.iconStyle" class="icon"></div>
|
||||
@ -78,8 +86,8 @@
|
||||
// 组件
|
||||
|
||||
// uniapp & vue
|
||||
import { onLoad, onReady } from "@dcloudio/uni-app";
|
||||
import { defineEmits, defineProps } from "vue";
|
||||
import { onLoad, onReady } from '@dcloudio/uni-app'
|
||||
import { defineEmits, defineProps } from 'vue'
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
@ -88,17 +96,17 @@ import {
|
||||
nextTick,
|
||||
getCurrentInstance,
|
||||
onMounted,
|
||||
onBeforeUnmount
|
||||
} from "vue";
|
||||
import copy07 from "@/static/image/chatList/copy07@2x.png";
|
||||
import multipleChoices from "@/static/image/chatList/multipleChoices@2x.png";
|
||||
import cite from "@/static/image/chatList/cite@2x.png";
|
||||
import withdraw from "@/static/image/chatList/withdraw@2x.png";
|
||||
import delete07 from "@/static/image/chatList/delete@2x.png";
|
||||
onBeforeUnmount,
|
||||
} from 'vue'
|
||||
import copy07 from '@/static/image/chatList/copy07@2x.png'
|
||||
import multipleChoices from '@/static/image/chatList/multipleChoices@2x.png'
|
||||
import cite from '@/static/image/chatList/cite@2x.png'
|
||||
import withdraw from '@/static/image/chatList/withdraw@2x.png'
|
||||
import delete07 from '@/static/image/chatList/delete@2x.png'
|
||||
|
||||
// pinia
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const bubbleRef = ref(null);
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
const bubbleRef = ref(null)
|
||||
|
||||
const props = defineProps({
|
||||
isShowCopy: {
|
||||
@ -113,109 +121,114 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
isShowConvertText: {
|
||||
//是否显示转文字
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(["clickMenu"]);
|
||||
const emits = defineEmits(['clickMenu'])
|
||||
|
||||
/**
|
||||
* @name 生成UUID
|
||||
*/
|
||||
const uuid = () => {
|
||||
const reg = /[xy]/g;
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
|
||||
const reg = /[xy]/g
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
|
||||
.replace(reg, function (c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
.replace(/-/g, "");
|
||||
};
|
||||
.replace(/-/g, '')
|
||||
}
|
||||
|
||||
const popoverBoxId = `ID${uuid()}`;
|
||||
const popoverContentId = `ID${uuid()}`;
|
||||
const instance = getCurrentInstance();
|
||||
const popoverBoxId = `ID${uuid()}`
|
||||
const popoverContentId = `ID${uuid()}`
|
||||
const instance = getCurrentInstance()
|
||||
const data = reactive({
|
||||
popoverShow: false,
|
||||
defaultStyle: {},
|
||||
showStyle: {
|
||||
left: 0,
|
||||
right: "",
|
||||
transform: "",
|
||||
right: '',
|
||||
transform: '',
|
||||
},
|
||||
iconStyle: {
|
||||
left: "",
|
||||
right: "",
|
||||
transform: "",
|
||||
left: '',
|
||||
right: '',
|
||||
transform: '',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
/**
|
||||
* @name 获取DOM
|
||||
*/
|
||||
const getDom = (dom) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const query = uni.createSelectorQuery().in(instance);
|
||||
let select = query.select(dom);
|
||||
const query = uni.createSelectorQuery().in(instance)
|
||||
let select = query.select(dom)
|
||||
const boundingClientRect = select.boundingClientRect((data) => {
|
||||
resolve(data);
|
||||
});
|
||||
boundingClientRect.exec();
|
||||
});
|
||||
};
|
||||
resolve(data)
|
||||
})
|
||||
boundingClientRect.exec()
|
||||
})
|
||||
}
|
||||
|
||||
const itemClick = (item) => {
|
||||
emits("clickMenu", item);
|
||||
};
|
||||
emits('clickMenu', item)
|
||||
}
|
||||
|
||||
// 弹起 长按5
|
||||
let pressDownTime = 0;
|
||||
let time = null;
|
||||
let pressDownTime = 0
|
||||
let time = null
|
||||
const onTouchstart = () => {
|
||||
time && clearTimeout(time);
|
||||
time = setTimeout(open, 500);
|
||||
};
|
||||
time && clearTimeout(time)
|
||||
time = setTimeout(open, 500)
|
||||
}
|
||||
|
||||
const onTouchend = () => {
|
||||
time && clearTimeout(time);
|
||||
};
|
||||
time && clearTimeout(time)
|
||||
}
|
||||
|
||||
const open = async () => {
|
||||
let popoverContent = await getDom(`#${popoverContentId}`);
|
||||
let popoverBox = await getDom(`#${popoverBoxId}`);
|
||||
let popoverContent = await getDom(`#${popoverContentId}`)
|
||||
let popoverBox = await getDom(`#${popoverBoxId}`)
|
||||
|
||||
// 缩放中心点
|
||||
let originX = popoverBox.width / 2;
|
||||
let originX = popoverBox.width / 2
|
||||
// 根据距离 初始化位置 判断是顶部还是底部
|
||||
let isTop = popoverBox.top - 50 > popoverContent.height;
|
||||
let isTop = popoverBox.top - 50 > popoverContent.height
|
||||
// 上面还是下面
|
||||
data.defaultStyle = {
|
||||
top: isTop ? "60rpx" : "auto",
|
||||
bottom: !isTop ? "-20rpx" : "auto",
|
||||
transform: `translateY(${isTop ? "-100%" : "100%"}) scale(.8)`,
|
||||
};
|
||||
top: isTop ? '60rpx' : 'auto',
|
||||
bottom: !isTop ? '-20rpx' : 'auto',
|
||||
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(.8)`,
|
||||
}
|
||||
// 左边还是右边
|
||||
if (popoverBox.left > systemInfo.windowWidth - popoverBox.right) {
|
||||
data.defaultStyle.right = 0;
|
||||
data.defaultStyle.right = 0
|
||||
// 动画缩放中心点
|
||||
data.defaultStyle["transform-origin"] = `${
|
||||
data.defaultStyle['transform-origin'] = `${
|
||||
popoverContent.width - originX
|
||||
}px ${isTop ? "100%" : "0%"}`;
|
||||
}px ${isTop ? '100%' : '0%'}`
|
||||
} else {
|
||||
data.defaultStyle.left = 0;
|
||||
data.defaultStyle.left = 0
|
||||
// 动画缩放中心点
|
||||
data.defaultStyle["transform-origin"] = `${originX}px ${
|
||||
isTop ? "100%" : "0%"
|
||||
}`;
|
||||
data.defaultStyle['transform-origin'] = `${originX}px ${
|
||||
isTop ? '100%' : '0%'
|
||||
}`
|
||||
}
|
||||
data.showStyle = { ...data.defaultStyle };
|
||||
data.showStyle = { ...data.defaultStyle }
|
||||
// icon位置样式
|
||||
let iconDefsultStyle = {
|
||||
transform: `translate(0%, ${isTop ? "20%" : "-20%"})`,
|
||||
"border-top-color": isTop ? "#333333" : "",
|
||||
"border-bottom-color": !isTop ? "#333333" : "",
|
||||
top: !isTop ? "-20rpx" : "auto",
|
||||
bottom: isTop ? "-20rpx" : "auto",
|
||||
};
|
||||
transform: `translate(0%, ${isTop ? '20%' : '-20%'})`,
|
||||
'border-top-color': isTop ? '#333333' : '',
|
||||
'border-bottom-color': !isTop ? '#333333' : '',
|
||||
top: !isTop ? '-20rpx' : 'auto',
|
||||
bottom: isTop ? '-20rpx' : 'auto',
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (popoverBox.left > systemInfo.windowWidth - popoverBox.right) {
|
||||
@ -224,55 +237,55 @@ const open = async () => {
|
||||
...data.defaultStyle,
|
||||
// 显示
|
||||
opacity: 1,
|
||||
transform: `translateY(${isTop ? "-100%" : "100%"}) scale(1)`,
|
||||
"pointer-events": "auto",
|
||||
};
|
||||
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(1)`,
|
||||
'pointer-events': 'auto',
|
||||
}
|
||||
data.iconStyle = {
|
||||
right: `${originX}px`,
|
||||
left: "auto",
|
||||
left: 'auto',
|
||||
...iconDefsultStyle,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
data.showStyle = {
|
||||
// 位置
|
||||
...data.defaultStyle,
|
||||
// 显示
|
||||
opacity: 1,
|
||||
transform: `translateY(${isTop ? "-100%" : "100%"}) scale(1)`,
|
||||
"pointer-events": "auto",
|
||||
};
|
||||
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(1)`,
|
||||
'pointer-events': 'auto',
|
||||
}
|
||||
data.iconStyle = {
|
||||
left: `${originX}px`,
|
||||
right: "auto",
|
||||
right: 'auto',
|
||||
...iconDefsultStyle,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.popoverShow) data.popoverShow = true;
|
||||
}, 200);
|
||||
};
|
||||
if (!data.popoverShow) data.popoverShow = true
|
||||
}, 200)
|
||||
}
|
||||
|
||||
const close = (time) => {
|
||||
setTimeout(() => {
|
||||
data.popoverShow = false;
|
||||
data.showStyle = data.defaultStyle;
|
||||
}, time || 0);
|
||||
};
|
||||
data.popoverShow = false
|
||||
data.showStyle = data.defaultStyle
|
||||
}, time || 0)
|
||||
}
|
||||
const handleClickOutside = (event) => {
|
||||
if (data.popoverShow = false) {
|
||||
if ((data.popoverShow = false)) {
|
||||
return false
|
||||
}
|
||||
if (bubbleRef.value && !bubbleRef.value.contains(event.target)) {
|
||||
close();
|
||||
close()
|
||||
}
|
||||
};
|
||||
}
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
});
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
});
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -196,6 +196,7 @@
|
||||
@clickMenu="(menuType) => onContextMenu(menuType, item)"
|
||||
:isShowCopy="isShowCopy(item)"
|
||||
:isShowWithdraw="isRevoke(talkParams.uid, item) || isLeader"
|
||||
:isShowConvertText="isShowConvertText(item)"
|
||||
>
|
||||
<component
|
||||
class="component-content"
|
||||
@ -218,6 +219,11 @@
|
||||
|
||||
</div> -->
|
||||
</div>
|
||||
<div style="display: flex; justify-content: flex-end;">
|
||||
<div class="talk-tools voice-content" v-if="item.voiceContent">
|
||||
<span>{{ item.voiceContent }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="talk-tools have_read_num"
|
||||
@ -226,7 +232,11 @@
|
||||
>
|
||||
<span v-if="talkParams.type === 1">未读</span>
|
||||
<span v-if="talkParams.type === 2">
|
||||
已读 (0/{{ (Number(talkParams.num)-1) > 0 ? Number(talkParams.num)-1 : 0 }})
|
||||
已读 (0/{{
|
||||
Number(talkParams.num) - 1 > 0
|
||||
? Number(talkParams.num) - 1
|
||||
: 0
|
||||
}})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -714,7 +724,10 @@ import {
|
||||
ServeTalkRecords,
|
||||
ServeReadConditionList,
|
||||
ServeMessageReadDetail,
|
||||
uploadImg,
|
||||
ServeConvertText,
|
||||
} from '@/api/chat'
|
||||
import { uniqueId } from '@/utils'
|
||||
import copy07 from '@/static/image/chatList/copy07@2x.png'
|
||||
import multipleChoices from '@/static/image/chatList/multipleChoices@2x.png'
|
||||
import cite from '@/static/image/chatList/cite@2x.png'
|
||||
@ -857,7 +870,13 @@ watch(
|
||||
readNumElement.textContent = readNum > 0 ? '已读' : '未读'
|
||||
} else {
|
||||
readNumElement.textContent =
|
||||
'已读 (' + readNum + '/' + (Number(talkParams.num) - 1 > 0 ? Number(talkParams.num) - 1 : 0) + ')'
|
||||
'已读 (' +
|
||||
readNum +
|
||||
'/' +
|
||||
(Number(talkParams.num) - 1 > 0
|
||||
? Number(talkParams.num) - 1
|
||||
: 0) +
|
||||
')'
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1159,6 +1178,16 @@ const isShowCopy = (item) => {
|
||||
}
|
||||
}
|
||||
|
||||
//只有语音消息才可以转文字
|
||||
const isShowConvertText = (item) => {
|
||||
switch (item.msg_type) {
|
||||
case 4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const selectedMessage = computed(() => {
|
||||
return virtualList.value.filter((item) => item.isCheck)
|
||||
})
|
||||
@ -1379,7 +1408,9 @@ const onContextMenu = (menuType, item) => {
|
||||
case 'actionDelete':
|
||||
actionDelete(item)
|
||||
break
|
||||
|
||||
case 'convertText':
|
||||
convertText(item)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -2559,12 +2590,20 @@ const getMessageReadDetail = (isUnread) => {
|
||||
if (code == 200) {
|
||||
if (Number(isUnread) === 0) {
|
||||
state.value.msgReadDetailTabs[0].title =
|
||||
'未读 (' + (Number(talkParams.num) - 1 - data.count > 0 ? Number(talkParams.num) - 1 - data.count : 0) + ')'
|
||||
'未读 (' +
|
||||
(Number(talkParams.num) - 1 - data.count > 0
|
||||
? Number(talkParams.num) - 1 - data.count
|
||||
: 0) +
|
||||
')'
|
||||
state.value.msgReadDetailTabs[1].title = '已读 (' + data.count + ')'
|
||||
} else if (Number(isUnread) === 1) {
|
||||
state.value.msgReadDetailTabs[0].title = '未读 (' + data.count + ')'
|
||||
state.value.msgReadDetailTabs[1].title =
|
||||
'已读 (' + (Number(talkParams.num) - 1 - data.count > 0 ? Number(talkParams.num) - 1 - data.count : 0) + ')'
|
||||
'已读 (' +
|
||||
(Number(talkParams.num) - 1 - data.count > 0
|
||||
? Number(talkParams.num) - 1 - data.count
|
||||
: 0) +
|
||||
')'
|
||||
}
|
||||
if (state.value.readNumPage === 1) {
|
||||
state.value.msgReadOrNotDetail = data.data
|
||||
@ -2591,6 +2630,74 @@ const loadMoreReadDetails = () => {
|
||||
state.value.readNumPage += 1
|
||||
getMessageReadDetail(state.value.currentIsUnread)
|
||||
}
|
||||
|
||||
const onProgressFn = (progress, id) => {
|
||||
// console.log((progress.loaded / progress.total) * 100, 'progress')
|
||||
}
|
||||
|
||||
//录音结束
|
||||
const endRecord = (file, url, duration) => {
|
||||
console.log(file, url, duration)
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
form.append('source', 'fonchain-chat')
|
||||
form.append('type', 'file')
|
||||
let randomId = uniqueId()
|
||||
const resp = uploadImg(form, (e) => onProgressFn(e, randomId))
|
||||
// console.log(resp)
|
||||
resp.then(({ code, data }) => {
|
||||
// console.log(code, data)
|
||||
if (code === 0) {
|
||||
const mediaUrl = data.ori_url
|
||||
sendMediaMessage(mediaUrl, duration, file.size)
|
||||
} else {
|
||||
}
|
||||
})
|
||||
|
||||
resp.catch(() => {})
|
||||
}
|
||||
|
||||
//发送语音消息
|
||||
const sendMediaMessage = (mediaUrl, duration, size) => {
|
||||
// console.log(mediaUrl, 'mediaUrl')
|
||||
let message = {
|
||||
type: 'voice', //聊天类型:语音消息
|
||||
content: '',
|
||||
quote_id: '',
|
||||
receiver: {
|
||||
receiver_id: talkParams.receiver_id, //目标用户id或者群id
|
||||
talk_type: talkParams.type, //1私聊;2群聊
|
||||
},
|
||||
url: mediaUrl,
|
||||
duration: duration,
|
||||
size: size,
|
||||
}
|
||||
console.log(message, 'message')
|
||||
|
||||
onSendMessage(
|
||||
message,
|
||||
(result) => {
|
||||
console.log(result, 'result')
|
||||
},
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
//语音转文字
|
||||
const convertText = (msgItem) => {
|
||||
const resp = ServeConvertText({
|
||||
voiceUrl: msgItem.extra.url,
|
||||
msgId: msgItem.msg_id,
|
||||
})
|
||||
// console.log(resp, 'resp')
|
||||
resp.then(({ code, data }) => {
|
||||
// console.log(code, data, 'data')
|
||||
if (code === 200) {
|
||||
console.log(data.convText, 'convText')
|
||||
msgItem.voiceContent = data.convText
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.dialog-page {
|
||||
@ -2780,6 +2887,19 @@ const loadMoreReadDetails = () => {
|
||||
}
|
||||
}
|
||||
|
||||
.voice-content {
|
||||
text-align: right;
|
||||
color: #7a58de;
|
||||
font-size: 22rpx;
|
||||
font-weight: 400;
|
||||
line-height: 34rpx;
|
||||
margin: 5rpx 0 0;
|
||||
background-color: #f5f5f5;
|
||||
padding: 10rpx;
|
||||
border-radius: 10rpx;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.have_read_num {
|
||||
text-align: right;
|
||||
color: #7a58de;
|
||||
@ -2899,7 +3019,7 @@ const loadMoreReadDetails = () => {
|
||||
|
||||
:deep(.round-3) {
|
||||
max-height: 320rpx;
|
||||
margin: 0 0 0 16rpx!important;
|
||||
margin: 0 0 0 16rpx !important;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user