flinfo/dc-App/pages/Chat/newChat.vue
2025-03-01 10:26:49 +08:00

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>