684 lines
16 KiB
Vue
684 lines
16 KiB
Vue
<template>
|
|
<!-- 聊天界面展示https://www.bilibili.com/video/BV1hT4y1P75N?p=22 搭建1和2 -->
|
|
<view class="content">
|
|
<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 @click="show=!show" 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'" @tap="previewImg(item.query)">
|
|
<!-- 图像 -->
|
|
<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" @click="clickSprink()" @touchstart="touchstart" @touchend="touchend" 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" id="po_" v-else>
|
|
<view class="po_z" v-if="show">
|
|
<view class="size_">Voice</view>
|
|
<view class="size_">text </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"
|
|
//音频播放
|
|
const innerAudioContext = uni.createInnerAudioContext();
|
|
// 录音
|
|
const recorderManager = uni.getRecorderManager();
|
|
export default {
|
|
data() {
|
|
return {
|
|
//是否长按事件
|
|
islongPress:false,
|
|
timer:null,//长按计时器
|
|
show:false,
|
|
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: {
|
|
longpress(){
|
|
this.islongPress = true;
|
|
this.show = true
|
|
console.log("长按事件");
|
|
},
|
|
//点击事件
|
|
clickSprink(){
|
|
// 非长按
|
|
if(this.islongPress == false){
|
|
console.log("点击事件");
|
|
}else if(this.islongPress == true){
|
|
|
|
|
|
console.log("长按事件",this.show);
|
|
|
|
}
|
|
},
|
|
//手指触摸动作开始
|
|
touchstart(){
|
|
this.timer = setTimeout(()=>{
|
|
this.longpress();
|
|
},1000)
|
|
},
|
|
//手指触摸动作结束
|
|
touchend(){
|
|
//延时执行为了防止 click() 还未判断 islongPress 的值就被置为 fasle
|
|
clearTimeout(this.timer);
|
|
setTimeout(() => {
|
|
this.islongPress = false
|
|
}, 200)
|
|
},
|
|
// 回调参数为包含columnIndex、value、values
|
|
langConfirm(e) {
|
|
console.log('confirm', 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{
|
|
console.log('触发首页的消息回调', res);
|
|
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() {
|
|
if (!uni.getStorageSync(this.info.conversation)) {
|
|
return;
|
|
}
|
|
|
|
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> |