689 lines
16 KiB
Vue
689 lines
16 KiB
Vue
<template>
|
||
<!-- 聊天界面展示https://www.bilibili.com/video/BV1hT4y1P75N?p=22 搭建1和2 -->
|
||
<view class="content" @click="clickContent">
|
||
<view class="top_po">
|
||
<view class="" @click="goback()"><u-icon name="arrow-left" color="#fff" size="20"></u-icon></view>
|
||
<view style="display: flex;">
|
||
<text>{{info.title}}</text>
|
||
|
||
<view v-if="info.conversation=='Translator'" class="sm-text" @click="chooseLang">{{lang}} <u-icon style="margin-top: 6rpx;margin-left: 5rpx;" name="arrow-down" color="#fff"
|
||
size="12"></u-icon></view>
|
||
</view>
|
||
|
||
<view class="right_top"></view>
|
||
</view>
|
||
<!-- 聊天内容 -->
|
||
<scroll-view class="chat" scroll-y="true" scroll-with-animation="true" :scroll-into-view="scrollToView">
|
||
<view class="chat-main" :style="{paddingBottom:inputh+'px'}">
|
||
<view class="chat-ls" v-for="(item,index) in messagesList" :key="index" :id="'msg'+ index">
|
||
<view class="msg-m msg-right">
|
||
<image class="user-img" :src="imagesUrl+userAvatar"></image>
|
||
<view class="message" v-if="item.inputs.type == 'text'">
|
||
<!-- 文字 -->
|
||
<view class="msg-text">
|
||
<text>{{item.query}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="message" v-if="item.inputs.type == 'image'" >
|
||
<!-- 图像 -->
|
||
<image :src="item.query" class="msg-img" mode="widthFix"></image>
|
||
</view>
|
||
<view class="message" v-if="item.inputs.type == 'voice'" @tap="playVoice(item.filePath)">
|
||
<!-- 音频 -->
|
||
<view class="msg-text voice" :style="{width:item.time*4+'rpx'}">
|
||
<image src="/static/chat/sy.png" class="voice-img"></image>
|
||
{{item.time}}″
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="msg-m msg-left" v-if="item.answer">
|
||
|
||
<image class="user-img" :src="info.icon"></image>
|
||
<view class="msg-text" v-if="item.inputs.type == 'image'">
|
||
<image :src="item.answer" class="msg-img" mode="widthFix"></image>
|
||
</view>
|
||
|
||
<view class="msg-text" @click="clickSprink(index)" id="po_" v-else>
|
||
<view class="po_z" v-if="show=='1'&&clickIdx==index">
|
||
<view class="size_" @click="voiceTxt(item.answer)">Voice</view>
|
||
|
||
</view>
|
||
{{item.answer}}</view>
|
||
</view>
|
||
</view>
|
||
<span id="bottomId"></span>
|
||
</view>
|
||
</scroll-view>
|
||
<submit @inputs="inputs" @heights="heights"></submit>
|
||
<u-picker :show="langShow" ref="langPicker" :columns="columns" @confirm="langConfirm"
|
||
@cancel="langCancel"></u-picker>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import dateTime from './newChat/dateTime.js';
|
||
import submit from './newChat/submit.vue';
|
||
import config from '@/config'
|
||
import {
|
||
startMsgSocket,
|
||
msgSocketConnect,
|
||
sendMsg,
|
||
closeMsgSocket
|
||
} from './msgSocket'
|
||
import requestChat from '../../utils/requestChat'
|
||
import permision from "@/js_sdk/wa-permission/permission.js"
|
||
import request from '../../utils/request'
|
||
//音频播放
|
||
const innerAudioContext = uni.createInnerAudioContext();
|
||
// 录音
|
||
const recorderManager = uni.getRecorderManager();
|
||
export default {
|
||
data() {
|
||
return {
|
||
audioSrc: '', // 用于存储音频的 URL
|
||
clickIdx:null,
|
||
//是否长按事件
|
||
timer:null,//长按计时器
|
||
show:'0',
|
||
columns: [
|
||
['Arabic-阿拉伯语',
|
||
'German-德语',
|
||
'English-英语',
|
||
'Spanish-西班牙语',
|
||
'French-法语',
|
||
'Hindi-印地语',
|
||
'Indonesian-印度尼西亚语',
|
||
'Italian-意大利语',
|
||
'Japanese-日语',
|
||
'Korean-韩语',
|
||
'Russian-俄语'
|
||
]
|
||
],
|
||
langShow: false,
|
||
lang: 'English',
|
||
imagesUrl: config.imagesUrl,
|
||
msgSocket: null,
|
||
// 反转数据接收
|
||
messagesList: [],
|
||
imgMsg: [],
|
||
scrollToView: '',
|
||
oldTime: new Date(),
|
||
inputh: '60',
|
||
info: {},
|
||
userId: null,
|
||
userAvatar: null,
|
||
firstId: null,
|
||
limit: 20,
|
||
socketId: null,
|
||
// 请求参数
|
||
ajax: {
|
||
rows: 20, //每页数量
|
||
page: 1, //页码
|
||
flag: true, // 请求开关
|
||
sendFlag: false
|
||
},
|
||
scrollId: 'bottomId',
|
||
storeList: 'msgHisList'
|
||
}
|
||
},
|
||
onLoad(option) {
|
||
if (option) {
|
||
let infoData = JSON.parse(option.data)
|
||
let tempInfo = {
|
||
icon: infoData.icon,
|
||
token: infoData.token,
|
||
conversation: infoData.conversation,
|
||
userId: infoData.userId,
|
||
userAvatar: infoData.userAvatar,
|
||
title: infoData.title
|
||
}
|
||
uni.setStorageSync('userId', infoData.userId)
|
||
this.info = tempInfo
|
||
this.userId = infoData.userId
|
||
this.userAvatar = infoData.userAvatar
|
||
this.getMessageByStore()
|
||
}
|
||
this.getRecordsToken()
|
||
|
||
},
|
||
components: {
|
||
submit,
|
||
},
|
||
methods: {
|
||
async voiceTxt(text){
|
||
let res = await request({
|
||
url: 'youDaoApi/tts',
|
||
method: 'post',
|
||
data: {
|
||
q: text,
|
||
voiceName: this.lang ,
|
||
}
|
||
})
|
||
this.playBase64Mp3(res.data)
|
||
this.show = false
|
||
|
||
},
|
||
// 播放 base64 编码的 MP3 文件
|
||
playBase64Mp3(base64Data) {
|
||
innerAudioContext.src = config.baseUrl+base64Data; // 不推荐,仅用于演示
|
||
innerAudioContext.play();
|
||
// 注意:如果你使用的是音频组件,确保在模板中正确绑定和使用它
|
||
},
|
||
clickContent(index){
|
||
if(this.info.conversation=='Translator'){
|
||
// 非长按
|
||
this.show = '0'
|
||
this.clickIdx = null
|
||
}
|
||
},
|
||
//点击事件
|
||
clickSprink(index){
|
||
if(this.info.conversation=='Translator'){
|
||
// 非长按
|
||
setTimeout(() => {
|
||
this.show = '1'
|
||
this.clickIdx = index
|
||
}, 10); // 延时200毫秒,即0.2秒
|
||
|
||
}
|
||
},
|
||
// 回调参数为包含columnIndex、value、values
|
||
langConfirm(e) {
|
||
this.lang = e.value[0].split('-')[0]
|
||
this.langShow = false
|
||
},
|
||
langCancel() {
|
||
this.langShow = false
|
||
},
|
||
chooseLang() {
|
||
//选择语言
|
||
this.langShow = true
|
||
},
|
||
async getRecordsToken() {
|
||
var result = await permision.requestAndroidPermission('android.permission.RECORD_AUDIO')
|
||
console.log(result, 124);
|
||
var strStatus
|
||
if (result == 1) {
|
||
strStatus = "已获得授权"
|
||
} else if (result == 0) {
|
||
strStatus = "未获得授权"
|
||
|
||
} else {
|
||
strStatus = "被永久拒绝权限"
|
||
|
||
}
|
||
|
||
|
||
},
|
||
startSocket() {
|
||
this.socketId = this.userId + "_" + Date.now()
|
||
this.msgSocket = startMsgSocket(this.socketId)
|
||
this.msgInfo()
|
||
//追加心跳机制
|
||
},
|
||
msgInfo() {
|
||
if (this.msgSocket) {
|
||
this.msgSocket.onMessage(res => {
|
||
if(this.info.conversation=='Translator'){
|
||
this.messagesList[this.messagesList.length - 1].answer = this.messagesList[this
|
||
.messagesList.length - 1].answer + res.data
|
||
this.goBottom()
|
||
uni.setStorageSync(this.storeList + '_' + this.info.conversation, this.messagesList)
|
||
}else{
|
||
if (res.data.indexOf("conversation_id") > -1) {
|
||
uni.setStorageSync(this.info.conversation, JSON.parse(res.data).conversation_id);
|
||
} else if (res.data.indexOf("workflow_finished") > -1) {
|
||
//代表结束
|
||
uni.setStorageSync(this.storeList + '_' + this.info.conversation, this.messagesList)
|
||
|
||
} else {
|
||
this.messagesList[this.messagesList.length - 1].answer = this.messagesList[this
|
||
.messagesList.length - 1].answer + res.data
|
||
this.goBottom()
|
||
}
|
||
}
|
||
|
||
|
||
})
|
||
}
|
||
},
|
||
//缓存中获取历史消息
|
||
getMessageByStore() {
|
||
let tempList = uni.getStorageSync(this.storeList + '_' + this.info.conversation)||[]
|
||
if (tempList && tempList.length > 30) {
|
||
//截取最新的30条
|
||
this.messagesList = tempList.slice(-30);
|
||
} else {
|
||
this.messagesList = tempList
|
||
}
|
||
|
||
// 数据挂载后执行,不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
|
||
this.$nextTick(() => {
|
||
this.$forceUpdate()
|
||
// 设置当前滚动的位置
|
||
this.scrollToView = this.scrollId;
|
||
})
|
||
},
|
||
// 获取历史消息
|
||
getMessage() {
|
||
|
||
if (!uni.getStorageSync(this.info.conversation)) {
|
||
return;
|
||
}
|
||
console.log(uni.getStorageSync(this.info.conversation), 136);
|
||
let url = 'v1/messages?user=' + this.userId + '&conversation_id=' + uni.getStorageSync(this.info
|
||
.conversation) + '&limit=' + this.limit
|
||
if (this.firstId) {
|
||
url = url + "&first_id=" + this.firstId
|
||
}
|
||
let that = this
|
||
let get = async () => {
|
||
that.ajax.flag = false;
|
||
let res = await requestChat({
|
||
url: url,
|
||
method: 'get',
|
||
token: that.info.token
|
||
})
|
||
let data = res.data
|
||
// 获取待滚动元素选择器,解决插入数据后,滚动条定位时使用。取当前消息数据的第一条信息元素
|
||
console.log(res, 144);
|
||
for (var i = 0; i < data.length; i++) {
|
||
if (i == 0) {
|
||
that.firstId = data[i].id
|
||
}
|
||
that.messagesList.push(data[i])
|
||
}
|
||
that.$set(that.messagesList, that.messagesList.length - 1, that.messagesList[that.messagesList
|
||
.length - 1])
|
||
// 数据挂载后执行,不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
|
||
that.$nextTick(() => {
|
||
that.$forceUpdate()
|
||
// 设置当前滚动的位置
|
||
that.scrollToView = this.scrollId;
|
||
})
|
||
|
||
}
|
||
get();
|
||
},
|
||
goback() {
|
||
uni.navigateBack()
|
||
},
|
||
changeTime(date) {
|
||
return dateTime.dateTime1(date);
|
||
},
|
||
// 进行图片的预览
|
||
previewImg(e) {
|
||
let index = 0;
|
||
for (let i = 0; i < this.imgMsg.length; i++) {
|
||
if (this.imgMsg[i] == e) {
|
||
index = i;
|
||
}
|
||
}
|
||
console.log("index", index)
|
||
// 预览图片
|
||
uni.previewImage({
|
||
current: index,
|
||
urls: this.imgMsg,
|
||
longPressActions: {
|
||
itemList: ['发送给朋友', '保存图片', '收藏'],
|
||
success: function(data) {
|
||
console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片');
|
||
},
|
||
fail: function(err) {
|
||
console.log(err.errMsg);
|
||
}
|
||
}
|
||
});
|
||
},
|
||
//音频播放
|
||
playVoice(e) {
|
||
console.log(e);
|
||
innerAudioContext.src = e;
|
||
|
||
innerAudioContext.play(() => {
|
||
console.log('开始播放');
|
||
});
|
||
},
|
||
//地图定位
|
||
covers(e) {
|
||
let map = [{
|
||
latitude: e.latitude,
|
||
longitude: e.longitude,
|
||
iconPath: '/static/chat/sy.png'
|
||
}]
|
||
return (map);
|
||
},
|
||
//跳转地图信息
|
||
openLocation(e) {
|
||
uni.openLocation({
|
||
latitude: e.latitude,
|
||
longitude: e.longitude,
|
||
name: e.name,
|
||
address: e.address,
|
||
success: function() {
|
||
console.log('success');
|
||
}
|
||
});
|
||
},
|
||
//接受输入内容
|
||
inputs(inputData) {
|
||
console.log(inputData, 220);
|
||
let that = this;
|
||
if (this.msgSocket) {
|
||
closeMsgSocket(this.msgSocket);
|
||
this.msgSocket = null
|
||
}
|
||
|
||
this.startSocket()
|
||
let typeStr = "text"
|
||
let msgItem = {
|
||
"inputs": {
|
||
"type": typeStr
|
||
},
|
||
"query": inputData.message,
|
||
"response_mode": 'streaming',
|
||
"conversation_id": uni.getStorageSync(this.info.conversation) || null,
|
||
"user": this.userId,
|
||
'token': this.info.token,
|
||
'answer': '',
|
||
'time': inputData.time,
|
||
'filePath': inputData.filePath
|
||
};
|
||
if (this.info.conversation == 'Translator') {
|
||
msgItem.response_mode = 'blocking'
|
||
msgItem.inputs.lang = this.lang
|
||
}
|
||
if (inputData.type == 0) {
|
||
msgItem.inputs.type = "text"
|
||
} else if (inputData.type == 1) {
|
||
typeStr = "image"
|
||
msgItem.inputs.type = "image"
|
||
msgItem.query = inputData.base64
|
||
} else if (inputData.type == 2) {
|
||
typeStr = "voice"
|
||
msgItem.inputs.type = "voice"
|
||
msgItem.query = inputData.base64
|
||
}
|
||
console.log(this.messagesList,111);
|
||
this.messagesList.push(msgItem);
|
||
// 数据挂载后执行,不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
|
||
this.goBottom()
|
||
//时间间隔处理
|
||
let requestData = {
|
||
"inputs": {
|
||
"type": typeStr
|
||
},
|
||
"query": inputData.message,
|
||
"response_mode": 'streaming',
|
||
"conversation_id": uni.getStorageSync(this.info.conversation) || null,
|
||
"user": this.userId,
|
||
'token': this.info.token,
|
||
'answer': '',
|
||
'socketId': this.socketId
|
||
};
|
||
if (this.info.conversation == 'Translator') {
|
||
requestData.response_mode = 'blocking'
|
||
requestData.inputs.lang = this.lang
|
||
}
|
||
setTimeout(() => {
|
||
sendMsg(that.msgSocket, JSON.stringify(requestData))
|
||
|
||
}, 500)
|
||
},
|
||
//输入框高度
|
||
heights(e) {
|
||
console.log("高度:", e)
|
||
this.inputh = e;
|
||
this.goBottom();
|
||
},
|
||
// 滚动到底部
|
||
goBottom() {
|
||
this.scrollToView = '';
|
||
this.$nextTick(() => {
|
||
|
||
// 设置当前滚动的位置
|
||
this.scrollToView = this.scrollId;
|
||
this.$forceUpdate()
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
page {
|
||
height: 100%;
|
||
}
|
||
#po_{
|
||
position: relative;
|
||
}
|
||
.po_z{
|
||
position: absolute;
|
||
top: -60px;
|
||
left: 0px;
|
||
box-sizing: border-box;
|
||
padding: 10px;
|
||
height: 50px;
|
||
background: #3d3d3d;
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
color: #fff;
|
||
border-radius: 8px;
|
||
}
|
||
.size_{
|
||
margin-right: 10px;
|
||
}
|
||
.content {
|
||
height: 100%;
|
||
background-color: rgba(244, 244, 244, 1);
|
||
}
|
||
|
||
.top_po {
|
||
position: fixed;
|
||
z-index: 9999;
|
||
width: 750rpx;
|
||
height: 180rpx;
|
||
box-sizing: border-box;
|
||
padding-top: 80rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
font-weight: bold;
|
||
font-size: 36rpx;
|
||
color: #FFFFFF;
|
||
padding-left: 30rpx;
|
||
padding-right: 30rpx;
|
||
left: 0px;
|
||
top: 0px;
|
||
background: #32714f;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.right_top {
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
|
||
.chat {
|
||
height: 90%;
|
||
margin-top: 180rpx;
|
||
margin-bottom: 30rpx;
|
||
|
||
.chat-main {
|
||
padding-left: 32rpx;
|
||
padding-right: 32rpx;
|
||
padding-top: 20rpx;
|
||
// padding-bottom: 120rpx; //获取动态高度
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.chat-ls {
|
||
.chat-time {
|
||
font-size: 24rpx;
|
||
color: rgba(39, 40, 50, 0.3);
|
||
line-height: 34rpx;
|
||
padding: 10rpx 0rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.msg-m {
|
||
display: flex;
|
||
padding: 20rpx 0;
|
||
|
||
.user-img {
|
||
flex: none;
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 20rpx;
|
||
}
|
||
|
||
.message {
|
||
flex: none;
|
||
max-width: 480rpx;
|
||
}
|
||
|
||
.msg-text {
|
||
max-width: 540rpx;
|
||
font-size: 32rpx;
|
||
color: rgba(39, 40, 50, 1);
|
||
line-height: 44rpx;
|
||
padding: 18rpx 24rpx;
|
||
white-space: pre-wrap;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
.msg-img {
|
||
max-width: 400rpx;
|
||
border-radius: 20rpx;
|
||
}
|
||
|
||
.msg-map {
|
||
background: #fff;
|
||
width: 464rpx;
|
||
height: 284rpx;
|
||
overflow: hidden;
|
||
|
||
.map-name {
|
||
font-size: 32rpx;
|
||
color: rgba(39, 40, 50, 1);
|
||
line-height: 44rpx;
|
||
padding: 18rpx 24rpx 0 24rpx;
|
||
//下面四行是单行文字的样式
|
||
display: -webkit-box;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.map-address {
|
||
font-size: 24rpx;
|
||
color: rgba(39, 40, 50, 0.4);
|
||
padding: 0 24rpx;
|
||
//下面四行是单行文字的样式
|
||
display: -webkit-box;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.map {
|
||
padding-top: 8rpx;
|
||
width: 464rpx;
|
||
height: 190rpx;
|
||
}
|
||
}
|
||
|
||
.voice {
|
||
// width: 200rpx;
|
||
min-width: 100rpx;
|
||
max-width: 400rpx;
|
||
}
|
||
|
||
.voice-img {
|
||
width: 28rpx;
|
||
height: 36rpx;
|
||
}
|
||
}
|
||
|
||
.msg-left {
|
||
flex-direction: row;
|
||
|
||
.msg-text {
|
||
max-width: 540rpx;
|
||
margin-left: 16rpx;
|
||
background-color: #fff;
|
||
border-radius: 0rpx 20rpx 20rpx 20rpx;
|
||
white-space: pre-wrap;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
.ms-img {
|
||
margin-left: 16rpx;
|
||
}
|
||
|
||
.msh-map {
|
||
margin-left: 16rpx;
|
||
border-radius: 0rpx 20rpx 20rpx 20rpx;
|
||
}
|
||
|
||
.voice {
|
||
text-align: right;
|
||
|
||
}
|
||
|
||
.voice-img {
|
||
float: left;
|
||
transform: rotate(180deg);
|
||
width: 28rpx;
|
||
height: 36rpx;
|
||
padding-bottom: 4rpx;
|
||
}
|
||
}
|
||
|
||
.msg-right {
|
||
flex-direction: row-reverse;
|
||
|
||
.msg-text {
|
||
max-width: 540rpx;
|
||
margin-right: 16rpx;
|
||
background-color: rgba(255, 228, 49, 0.8);
|
||
border-radius: 20rpx 0rpx 20rpx 20rpx;
|
||
white-space: pre-wrap;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
.ms-img {
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.msh-map {
|
||
margin-left: 16rpx;
|
||
border-radius: 20rpx 0rpx 20rpx 20rpx;
|
||
}
|
||
|
||
.voice {
|
||
text-align: left;
|
||
|
||
}
|
||
|
||
.voice-img {
|
||
float: right;
|
||
padding: 4rpx;
|
||
width: 28rpx;
|
||
height: 36rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.sm-text {
|
||
font-size: 12px;
|
||
margin-left: 5px;
|
||
display: flex;
|
||
align-items: center;
|
||
line-height: 29px;
|
||
}
|
||
</style>
|