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

541 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>