550 lines
12 KiB
Vue
550 lines
12 KiB
Vue
<template>
|
||
<view class="container" :style="pageHeight">
|
||
<view class="box-1">
|
||
<scroll-view
|
||
scroll-y
|
||
refresher-background="transparent"
|
||
style="height: 100%;"
|
||
@scrolltoupper="refresherrefresh"
|
||
|
||
:refresher-enabled="false"
|
||
:scroll-with-animation="false"
|
||
:refresher-triggered="scrollView.refresherTriggered"
|
||
:scroll-into-view="scrollView.intoView"
|
||
>
|
||
<!-- <view id="topId"></view> -->
|
||
<view class="talk-list" >
|
||
<view class=""
|
||
v-for="(item,index) in messagesList"
|
||
:key="item.id"
|
||
>
|
||
<view class="item flex-col push" v-if="item.query" >
|
||
<image :src="imagesUrl+userAvatar" mode="aspectFill" class="pic"></image>
|
||
<view class="content">
|
||
<template v-if="item.contentType === 'image'">
|
||
<image :src="item.content" mode="widthFix" style="width: 400rpx;"></image>
|
||
</template>
|
||
<template v-else>
|
||
{{item.query}}
|
||
</template>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="item flex-col pull " id="po_" v-if="item.answer" >
|
||
<view class="po_z"></view>
|
||
<image :src="info.icon" mode="aspectFill" class="pic"></image>
|
||
<view class="content" >
|
||
<template v-if="item.contentType === 'image'">
|
||
<image :src="item.content" mode="widthFix" style="width: 400rpx;"></image>
|
||
</template>
|
||
<template v-else>
|
||
|
||
<text class="" >
|
||
{{item.answer}}
|
||
</text>
|
||
|
||
</template>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view id="bottomId"></view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<view class="box-2">
|
||
<view class="flex-col">
|
||
|
||
<view class="flex-grow">
|
||
<input type="text" class="content" v-model="content" placeholder="Please enter what you want to know" placeholder-style="color:#DDD;" :cursor-spacing="6">
|
||
</view>
|
||
<!-- <view style="margin-right: 20rpx;">
|
||
<uni-icons type="image" size="24" color="#32714F" @tap="handleImageClick"></uni-icons>
|
||
|
||
</view> -->
|
||
<button class="send" @tap="handleSendClick">send</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!--
|
||
text标签随意,不影响页面展示即可
|
||
prop:prop随便取名,options是变量
|
||
:change:[name]:name和prop保持一致就行
|
||
renderScript:和新加的script标签的module名字一样
|
||
onChange:renderScript标签中的方法,
|
||
思路是,通过修改options,触发onChange方法
|
||
-->
|
||
<!-- <text :prop="options" :change:prop="renderScript.onChange"></text> -->
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
// @ts-nocheck - Multiple exports allowed for Vue component and renderjs
|
||
import { getHistoryMsg } from "@/request/template-talk/history-msg.js";
|
||
import requestChat from '../../utils/requestChat'
|
||
import config from '@/config'
|
||
import {startMsgSocket,msgSocketConnect,sendMsg,closeMsgSocket} from './msgSocket'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
//是否长按事件
|
||
islongPress:false,
|
||
timer:null,//长按计时器
|
||
options: {},
|
||
imagesUrl:config.imagesUrl,
|
||
info:{},
|
||
title:'',
|
||
// 聊天列表数据
|
||
messagesList:[],
|
||
// 滚动容器
|
||
scrollView:{
|
||
refresherTriggered:false,
|
||
intoView:"",
|
||
safeAreaHeight:0
|
||
},
|
||
|
||
// 请求参数
|
||
ajax:{
|
||
rows:20, //每页数量
|
||
page:1, //页码
|
||
flag:true, // 请求开关
|
||
sendFlag:false
|
||
},
|
||
// 发送内容
|
||
content:'',
|
||
userId:null,
|
||
userAvatar:null,
|
||
taskChat:null,
|
||
firstId:null,
|
||
limit:100,
|
||
scrollId:'bottomId',
|
||
tempList :[],
|
||
requestTask: null,
|
||
tempBuffer: '',
|
||
msgSocket:null,
|
||
msgTimer:null
|
||
}
|
||
},
|
||
computed:{
|
||
// 页面高度
|
||
pageHeight(){
|
||
const safeAreaHeight = this.scrollView.safeAreaHeight;
|
||
if(safeAreaHeight > 0){
|
||
return `height: calc(${safeAreaHeight}px - var(--window-top));`
|
||
}
|
||
return "";
|
||
}
|
||
},
|
||
|
||
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
|
||
}
|
||
uni.setStorageSync('userId',infoData.userId)
|
||
this.info = tempInfo
|
||
this.userId = infoData.userId
|
||
this.userAvatar = infoData.userAvatar
|
||
this.getMessage()
|
||
}
|
||
|
||
},
|
||
|
||
onShow() {
|
||
|
||
|
||
},
|
||
|
||
methods: {
|
||
longpress(){
|
||
this.islongPress = true;
|
||
console.log("长按事件");
|
||
},
|
||
//点击事件
|
||
clickSprink(){
|
||
// 非长按
|
||
if(this.islongPress == false){
|
||
console.log("点击事件");
|
||
}else if(this.islongPress == true){
|
||
console.log("长按事件");
|
||
}
|
||
},
|
||
//手指触摸动作开始
|
||
touchstart(){
|
||
this.timer = setTimeout(()=>{
|
||
this.longpress();
|
||
},2000)
|
||
},
|
||
//手指触摸动作结束
|
||
touchend(){
|
||
//延时执行为了防止 click() 还未判断 islongPress 的值就被置为 fasle
|
||
clearTimeout(this.timer);
|
||
setTimeout(() => {
|
||
this.islongPress = false
|
||
}, 200)
|
||
},
|
||
|
||
startSocket(){
|
||
this.msgSocket = startMsgSocket(this.userId)
|
||
this.msgInfo()
|
||
//追加心跳机制
|
||
},
|
||
msgInfo() {
|
||
if (this.msgSocket) {
|
||
this.msgSocket.onMessage(res => {
|
||
console.log('触发首页的消息回调',res);
|
||
if(res.data.indexOf("conversation_id")>-1){
|
||
uni.setStorageSync(this.info.conversation, JSON.parse(res.data).conversation_id);
|
||
}else{
|
||
this.messagesList[0].answer = this.messagesList[0].answer+res.data
|
||
}
|
||
})
|
||
}
|
||
},
|
||
|
||
async sendMessage(query) {
|
||
if(this.msgSocket){
|
||
closeMsgSocket(this.msgSocket);
|
||
this.msgSocket= null
|
||
}
|
||
|
||
this.startSocket()
|
||
this.content = '';
|
||
this.ajax.sendFlag = true;
|
||
|
||
const msgItem = {
|
||
query: query,
|
||
answer: "",
|
||
contentType: 'text'
|
||
};
|
||
|
||
const requestData = {
|
||
"inputs": { "type": "text" },
|
||
"query": query,
|
||
"response_mode": "streaming",
|
||
"conversation_id": uni.getStorageSync(this.info.conversation) || null,
|
||
"user": this.userId,
|
||
'token': this.info.token,
|
||
'answer':''
|
||
};
|
||
|
||
this.messagesList.unshift(msgItem);
|
||
let that = this;
|
||
setTimeout(()=>{
|
||
sendMsg(that.msgSocket,JSON.stringify(requestData))
|
||
},500)
|
||
|
||
|
||
|
||
},
|
||
|
||
|
||
// 在renderScript中触发的方法
|
||
acceptData(data) {
|
||
|
||
let that = this
|
||
let msgItem = this.messagesList[0]
|
||
if(data.line){
|
||
if(data.sendStatus&&data.sendStatus == 'ok'){
|
||
const lastIndex = data.line.lastIndexOf('}');
|
||
const validJsonString = data.line.slice(0, lastIndex + 1);
|
||
let item = JSON.parse(validJsonString.replace('data: ',''))
|
||
uni.setStorageSync(that.info.conversation,item.conversation_id)
|
||
that.ajax.sendFlag = false
|
||
return
|
||
}
|
||
|
||
if(data.line.indexOf('answer')>-1&&data.line.indexOf('{')>-1&&data.line.indexOf('}')>-1){
|
||
try{
|
||
// 查找最后一个出现的'}'字符的索引
|
||
const lastIndex = data.line.lastIndexOf('}');
|
||
|
||
// 截取从开头到这个'}'字符(包括它)的所有内容
|
||
const validJsonString = data.line.slice(0, lastIndex + 1);
|
||
let item = JSON.parse(validJsonString.replace('data: ',''))
|
||
if(item.answer&&!this.tempList.includes(item.answer+'')){
|
||
this.tempList.push(item.answer+'')
|
||
msgItem.answer = msgItem.answer+(item.answer)
|
||
}
|
||
that.$set(that.messagesList,0,msgItem)
|
||
that.scrollView.intoView = 'bottomId';
|
||
}catch (error) {
|
||
console.log(177,data.line,177);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
},
|
||
|
||
goBack(){
|
||
uni.navigateBack()
|
||
},
|
||
// 下拉刷新
|
||
refresherrefresh(e){
|
||
this.getMessage();
|
||
this.scrollView.refresherTriggered = true;
|
||
},
|
||
// 获取历史消息
|
||
getMessage(){
|
||
|
||
if(!this.ajax.flag||!uni.getStorageSync(this.info.conversation)){
|
||
return;
|
||
}
|
||
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
|
||
that.scrollView.refresherTriggered = false;
|
||
|
||
// 获取待滚动元素选择器,解决插入数据后,滚动条定位时使用。取当前消息数据的第一条信息元素
|
||
|
||
for (var i = 0; i < data.length; i++) {
|
||
if(i==0){
|
||
that.firstId = data[i].id
|
||
}
|
||
that.messagesList.unshift(data[i])
|
||
}
|
||
that.$set(that.messagesList,0,that.messagesList[0])
|
||
// 数据挂载后执行,不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
|
||
that.$nextTick(()=>{
|
||
that.$forceUpdate()
|
||
// 设置当前滚动的位置
|
||
that.scrollView.intoView = this.scrollId;
|
||
})
|
||
that.ajax.flag = true;
|
||
}
|
||
get();
|
||
},
|
||
// 发送信息
|
||
handleSendClick() {
|
||
console.log('Send button clicked'); // 调试日志
|
||
if(!this.content) {
|
||
uni.showToast({
|
||
title: 'Please enter valid content',
|
||
icon: 'none'
|
||
})
|
||
return;
|
||
}
|
||
this.sendMessage(this.content);
|
||
}
|
||
|
||
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
@import "../../lib/global.scss";
|
||
|
||
.AirWall{
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
.top_{
|
||
width: 100%;
|
||
position: fixed;
|
||
left: 0px;
|
||
top: 0px;
|
||
height: 180rpx;
|
||
background: #32714F;
|
||
z-index: 99999;
|
||
box-sizing: border-box;
|
||
|
||
padding: 15px;
|
||
padding-top: 100rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
color: #fff;
|
||
justify-content: space-between;
|
||
}
|
||
page{
|
||
background-color: #F3F3F3;
|
||
font-size: 28rpx;
|
||
}
|
||
.container{
|
||
height: calc(100vh - var(--window-top));
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex-wrap: nowrap;
|
||
align-content: center;
|
||
justify-content: space-between;
|
||
align-items: stretch;
|
||
}
|
||
|
||
/* 加载数据提示 */
|
||
.tips{
|
||
position: fixed;
|
||
left: 0;
|
||
top:var(--window-top);
|
||
width: 100%;
|
||
z-index: 9;
|
||
background-color: rgba(0,0,0,0.15);
|
||
height: 72rpx;
|
||
line-height: 72rpx;
|
||
transform:translateY(-80rpx);
|
||
transition: transform 0.3s ease-in-out 0s;
|
||
|
||
&.show{
|
||
transform:translateY(0);
|
||
}
|
||
}
|
||
|
||
.box-1{
|
||
width: 100%;
|
||
height: 0;
|
||
flex: 1 0 auto;
|
||
box-sizing: content-box;
|
||
|
||
}
|
||
.box-2{
|
||
height: auto;
|
||
z-index: 2;
|
||
border-top: #e5e5e5 solid 1px;
|
||
box-sizing: content-box;
|
||
background-color: #F3F3F3;
|
||
|
||
/* 兼容iPhoneX */
|
||
padding-bottom: 0;
|
||
padding-bottom: constant(safe-area-inset-bottom);
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
|
||
>view{
|
||
padding: 0 20rpx;
|
||
height: 100rpx;
|
||
}
|
||
|
||
.content{
|
||
background-color: #fff;
|
||
height: 64rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 32rpx;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.send{
|
||
background-color: #32714F;
|
||
color: #fff;
|
||
height: 64rpx;
|
||
margin-left: 20rpx;
|
||
border-radius: 32rpx;
|
||
padding: 0;
|
||
width: 120rpx;
|
||
line-height: 62rpx;
|
||
|
||
&:active{
|
||
background-color: #32714F;
|
||
}
|
||
}
|
||
}
|
||
|
||
.talk-list{
|
||
padding-bottom: 20rpx;
|
||
display: flex;
|
||
flex-direction: column-reverse;
|
||
flex-wrap: nowrap;
|
||
align-content: flex-start;
|
||
justify-content: flex-end;
|
||
align-items: stretch;
|
||
|
||
// 添加弹性容器,让内容自动在顶部
|
||
&::before{
|
||
content: '.';
|
||
display: inline;
|
||
visibility: hidden;
|
||
line-height: 0;
|
||
font-size: 0;
|
||
flex: 1 0 auto;
|
||
height: 1px;
|
||
}
|
||
|
||
|
||
/* 消息项,基础类 */
|
||
.item{
|
||
padding: 20rpx 20rpx 0 20rpx;
|
||
align-items:flex-start;
|
||
align-content:flex-start;
|
||
color: #333;
|
||
|
||
.pic{
|
||
width: 92rpx;
|
||
height: 92rpx;
|
||
border-radius: 50%;
|
||
border: #fff solid 1px;
|
||
}
|
||
|
||
.content{
|
||
padding: 20rpx;
|
||
border-radius: 4px;
|
||
max-width: 500rpx;
|
||
word-break: break-all;
|
||
line-height: 52rpx;
|
||
position: relative;
|
||
}
|
||
|
||
/* 收到的消息 */
|
||
&.pull{
|
||
.content{
|
||
margin-left: 32rpx;
|
||
background-color: #fff;
|
||
|
||
&::after{
|
||
content: '';
|
||
display: block;
|
||
width: 0;
|
||
height: 0;
|
||
border-top: 16rpx solid transparent;
|
||
border-bottom: 16rpx solid transparent;
|
||
border-right: 20rpx solid #fff;
|
||
position: absolute;
|
||
top: 30rpx;
|
||
left: -18rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* 发出的消息 */
|
||
&.push{
|
||
/* 主轴为水平方向,起点在右端。使不修改DOM结构,也能改变元素排列顺序 */
|
||
flex-direction: row-reverse;
|
||
|
||
.content{
|
||
margin-right: 32rpx;
|
||
background-color: #32714F;
|
||
color: #fff;
|
||
|
||
&::after{
|
||
content: '';
|
||
display: block;
|
||
width: 0;
|
||
height: 0;
|
||
border-top: 16rpx solid transparent;
|
||
border-bottom: 16rpx solid transparent;
|
||
border-left: 20rpx solid #32714F;
|
||
position: absolute;
|
||
top: 30rpx;
|
||
right: -18rpx;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|