flinfo/dc-App/pages/Chat/ChatDetails.vue

541 lines
13 KiB
Vue
Raw Normal View History

2025-03-01 10:26:49 +08:00
<template>
<view class="container" :style="pageHeight">
<!-- <view class="top_">
<u-icon @click="goBack()" name="arrow-left" color="#fff" size="20"></u-icon>
<view class="">{{info.title || "details"}}</view>
<view class="AirWall"></view>
</view> -->
<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 " v-if="item.answer" >
<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>
</view>
</template>
<script>
import { getHistoryMsg } from "@/request/template-talk/history-msg.js";
import requestChat from '../../utils/requestChat'
import config from '@/config'
export default {
data() {
return {
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:20,
scrollId:'bottomId'
}
},
computed:{
// 页面高度
pageHeight(){
const safeAreaHeight = this.scrollView.safeAreaHeight;
if(safeAreaHeight > 0){
return `height: calc(${safeAreaHeight}px - var(--window-top));`
}
return "";
}
},
created() {
let tempInfo={
icon:this.$route.query.icon,
token:this.$route.query.token,
conversation:this.$route.query.conversation,
userId:this.$route.query.userId,
userAvatar:this.$route.query.userAvatar
}
this.info = tempInfo
this.userId = this.$route.query.userId
this.userAvatar = this.$route.query.userAvatar
this.getMessage()
console.log(tempInfo,138);
},
onShow() {
},
methods: {
goBack(){
uni.navigateBack()
},
// 下拉刷新
refresherrefresh(e){
console.log(e,151);
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;
console.log(that.info.token,158);
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])
console.log(that.messagesList.length,JSON.stringify(that.messagesList[0]),185);
// 数据挂载后执行,不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
that.$nextTick(()=>{
that.$forceUpdate()
// 设置当前滚动的位置
that.scrollView.intoView = this.scrollId;
})
that.ajax.flag = true;
}
get();
},
// 发送信息
handleSendClick(){
if(!this.content){
uni.showToast({
title:'Please enter valid content',
icon:'none'
})
return;
}
this.sendMessage(this.content);
},
async sendMessage(query) {
console.log('Starting sendMessage...');
if(this.ajax.sendFlag) {
uni.showToast({
title: 'Please wait',
icon: 'none'
})
return;
}
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
};
// 添加到消息列表
this.messagesList.unshift(msgItem);
// #ifdef APP-PLUS
try {
const xhr = new plus.net.XMLHttpRequest();
const url = `${config.publicUrl}v1/chat-messages`;
xhr.open('POST', url, true);
// 设置请求头
xhr.setRequestHeader('Authorization', this.info.token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Accept', 'text/event-stream');
let tempList = [];
let that = this;
// 监听数据流
xhr.onprogress = function(event) {
console.log('Receiving data...');
const receivedText = event.target.responseText;
console.log('Received text length:', receivedText.length);
// 按数据块分割
const lines = receivedText.split('\ndata: ');
console.log('Processing lines:', lines.length);
// 处理每一行数据
lines.forEach((line, index) => {
try {
if (!line.trim()) return;
// 清理并解析数据
const cleanLine = line.replace('data: ', '');
const data = JSON.parse(cleanLine);
console.log('Parsed data:', data);
// 处理会话ID
if (data.conversation_id) {
uni.setStorageSync(that.info.conversation, data.conversation_id);
}
// 处理回答内容
if (data.answer) {
const uniqueKey = data.answer + '' + index;
if (!tempList.includes(uniqueKey)) {
console.log('New answer chunk:', data.answer);
tempList.push(uniqueKey);
msgItem.answer += data.answer;
that.$set(that.messagesList, 0, msgItem);
that.scrollView.intoView = 'bottomId';
}
}
} catch (error) {
console.log('Parse error for line:', error);
}
});
};
// 请求完成
xhr.onload = function() {
console.log('Request completed');
that.ajax.sendFlag = false;
};
// 请求错误
xhr.onerror = function(error) {
console.error('Request failed:', error);
that.ajax.sendFlag = false;
uni.showToast({
title: 'Request failed',
icon: 'none'
});
};
// 设置超时
xhr.timeout = 900000; // 15分钟
xhr.ontimeout = function() {
console.error('Request timeout');
that.ajax.sendFlag = false;
uni.showToast({
title: 'Request timeout',
icon: 'none'
});
};
// 发送请求
console.log('Sending request...');
xhr.send(JSON.stringify(requestData));
} catch (error) {
console.error('Error in sendMessage:', error);
this.ajax.sendFlag = false;
uni.showToast({
title: 'Request failed',
icon: 'none'
});
}
// #endif
}
}
}
</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>