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

550 lines
12 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="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标签随意不影响页面展示即可
propprop随便取名options是变量
:change:[name]name和prop保持一致就行
renderScript和新加的script标签的module名字一样
onChangerenderScript标签中的方法
思路是通过修改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>