Files
YGChatCS/pages/chat/ChatMainList.vue

318 lines
8.8 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="chat-container" @touchend="handleTouchEnd">
<!-- 顶部的背景 -->
<ChatTopBgImg class="chat-container-bg"></ChatTopBgImg>
<view class="chat-content">
<!-- 顶部自定义导航栏 -->
<view class="nav-bar-container" :style="{
paddingTop: statusBarHeight + 'px',
}">
<ChatTopNavBar @openDrawer="openDrawer"></ChatTopNavBar>
</view>
<!-- 消息列表可滚动区域 -->
<scroll-view
scroll-y
:scroll-into-view="lastMsgId"
:scroll-with-animation="true"
class="area-msg-list"
>
<!-- welcome栏 -->
<ChatTopWelcome class="chat-container-top-bannar"
:initPageImages="mainPageDataModel.initPageImages"
:welcomeContent="mainPageDataModel.welcomeContent">
</ChatTopWelcome>
<view class="area-msg-list-content" v-for="item in chatMsgList" :key="item.msgId" :id="item.msgId">
<template v-if="item.msgType === MessageRole.AI">
<ChatCardAI class="message-item-ai" :text="item.msg">
</ChatCardAI>
</template>
<template v-else-if="item.msgType === MessageRole.ME">
<ChatCardMine class="message-item-mine" :text="item.msg">
</ChatCardMine>
</template>
<template v-else>
<ChatCardOther class="message-item-other" :text="item.msg">
<OneFeelMK001 v-if="mainPageDataModel.activityList.length > 0" :activityList="mainPageDataModel.activityList"/>
<template v-if="mainPageDataModel.recommendTheme.length > 0" v-for="(item) in mainPageDataModel.recommendTheme">
<OneFeelMK002 :recommendTheme="item"/>
</template>
</ChatCardOther>
</template>
</view>
<!-- 底部锚点用于滚动到底部 -->
<view :id="lastMsgId" class="lastMsgId"></view>
</scroll-view>
<!-- 输入框区域 -->
<view class="footer-area">
<ChatMoreTips @replySent="handleReply" :itemList="mainPageDataModel.guideWords"></ChatMoreTips>
<ChatQuickAccess @replySent="handleReplyInstruct"></ChatQuickAccess>
<ChatInputArea
v-model:inputMessage="inputMessage"
:holdKeyboard="holdKeyboard"
@send="sendMessageAction"
@noHideKeyboard="handleNoHideKeyboard"
/>
</view>
</view>
</view>
</template>
<script setup >
import { onMounted, nextTick } from 'vue'
import { ref } from 'vue'
import { defineEmits } from 'vue'
import { onLoad } from '@dcloudio/uni-app';
import ChatTopWelcome from './ChatTopWelcome.vue';
import ChatTopBgImg from './ChatTopBgImg.vue';
import ChatTopNavBar from './ChatTopNavBar.vue';
import ChatCardAI from './ChatCardAI.vue';
import ChatCardMine from './ChatCardMine.vue';
import ChatCardOther from './ChatCardOther.vue';
import ChatQuickAccess from './ChatQuickAccess.vue';
import ChatMoreTips from './ChatMoreTips.vue';
import ChatInputArea from './ChatInputArea.vue'
import CommandWrapper from '@/components/CommandWrapper/index.vue'
import { MessageRole, MessageType } from '../../model/ChatModel';
import OneFeelMK001 from '../module/OneFeelMK001.vue';
import OneFeelMK002 from '../module/OneFeelMK002.vue';
import { agentChatStream } from '../../request/api/AgentChatStream';
import { mainPageData } from '../../request/api/MainPageData';
// 导航栏相关
const statusBarHeight = ref(20);
const timer = ref(null)
const holdKeyboard = ref(false) // focus时点击页面的时候不收起键盘
const holdKeyboardFlag = ref(true) // 是否在键盘弹出,点击界面时关闭键盘
const chatMsgList = ref([])
const inputMessage = ref('')
/// 从个渠道获取如二维,没有的时候就返回首页的数据
const sceneId = ref('')
/// agentId 首页接口中获取
const agentId = ref('1')
/// 会话ID 历史数据接口中获取
const conversationId = ref('1931957498711957505')
const mainPageDataModel = ref({})
// 锚点ID控制滚动位置
const lastMsgId = ref('anchor-bottom');
// 打开抽屉
const emits = defineEmits(['openDrawer'])
const openDrawer = () => {
emits('openDrawer')
console.log('=============打开抽屉')
}
const handleTouchEnd = () => {
// #ifdef MP-WEIXIN
clearTimeout(timer.value)
timer.value = setTimeout(() => {
// 键盘弹出时点击界面则关闭键盘
if (handleNoHideKeyboard) {
uni.hideKeyboard()
}
holdKeyboardFlag.value = true
}, 50)
// #endif
}
// 点击输入框、发送按钮时,不收键盘
const handleNoHideKeyboard = () => {
// #ifdef MP-WEIXIN
holdKeyboardFlag.value = false
// #endif
}
const scrollToBottom = () => {
// 短暂指向最新消息的ID触发滚动
lastMsgId.value = `${chatMsgList.value[chatMsgList.value.length - 1].msgId}`;
// 等待DOM更新后切回底部锚点确保下次能继续滚动到底
nextTick(() => {
nextTick(() => {
lastMsgId.value = 'anchor-bottom';
});
});
}
/// 发送普通消息
const handleReply = (text) => {
sendMessage(text)
scrollToBottom()
};
/// 是发送指令
const handleReplyInstruct = (text) => {
sendMessage(text, true)
scrollToBottom()
}
onLoad(() => {
uni.getSystemInfo({
success: (res) => {
statusBarHeight.value = res.statusBarHeight || 20;
}
});
});
onMounted(() => {
getMainPageData()
})
/// 获取首页数据
const getMainPageData = async() => {
const res = await mainPageData(sceneId.value)
if(res.code === 0) {
mainPageDataModel.value = res.data
agentId.value = res.data.agentId
initData()
}
}
/// 初始化数据
const initData = () => {
const msg = {
msgId: `msg_${0}`,
msgType: MessageRole.OTHER,
msg: '',
}
chatMsgList.value.push(msg)
}
/// 输入区的发送消息事件
const sendMessageAction = (inputText) => {
console.log("输入消息:", inputText)
if (!inputText.trim()) return;
handleNoHideKeyboard()
sendMessage(inputText)
inputMessage.value = ''
scrollToBottom()
}
const sendMessage = (message, isInstruct = false) => {
const newMsg = {
msgId: `msg_${chatMsgList.value.length}`,
msgType: MessageRole.ME,
msg: message,
msgContent: {
type: MessageType.TEXT,
text: message
}
}
chatMsgList.value.push(newMsg)
sendChat(message, isInstruct)
console.log("发送的新消息:",JSON.stringify(newMsg))
}
let loadingTimer = null;
let typeWriterTimer = null;
let aiMsgBuffer = ''; // 全局缓冲区
let isTyping = false; // 是否正在打字
/// 发送获取AI聊天消息
const sendChat = (message, isInstruct = false) => {
const args = {
conversationId: conversationId.value,
agentId: agentId.value,
messageType: isInstruct ? 1 : 0,
messageContent: message
}
// 插入AI消息
const aiMsg = {
msgId: `msg_${chatMsgList.value.length}`,
msgType: MessageRole.AI,
msg: '加载中.',
msgContent: {
type: MessageType.TEXT,
url: ''
}
}
chatMsgList.value.push(aiMsg)
const aiMsgIndex = chatMsgList.value.length - 1
// 动态加载中动画
let dotCount = 1;
loadingTimer && clearInterval(loadingTimer);
loadingTimer = setInterval(() => {
dotCount = dotCount % 3 + 1;
chatMsgList.value[aiMsgIndex].msg = '加载中' + '.'.repeat(dotCount);
}, 400);
aiMsgBuffer = '';
isTyping = false;
if (typeWriterTimer) {
clearTimeout(typeWriterTimer);
typeWriterTimer = null;
}
// 2. 流式接收内容
agentChatStream(args, (chunk) => {
console.log('分段内容:', chunk)
if (chunk && chunk.content) {
// 收到内容,停止动画
if (loadingTimer) {
clearInterval(loadingTimer);
loadingTimer = null;
}
// 把新内容追加到缓冲区
aiMsgBuffer += chunk.content;
// 启动打字机(只启动一次)
if (!isTyping) {
isTyping = true;
chatMsgList.value[aiMsgIndex].msg = '';
typeWriter();
}
}
if (chunk && chunk.finish) {
// 结尾处理:确保剩余内容全部输出
const finishInterval = setInterval(() => {
if (aiMsgBuffer.length === 0) {
clearInterval(finishInterval);
isTyping = false;
scrollToBottom();
}
}, 50);
}
});
// 打字机函数
function typeWriter() {
if (aiMsgBuffer.length > 0) {
chatMsgList.value[aiMsgIndex].msg += aiMsgBuffer[0];
aiMsgBuffer = aiMsgBuffer.slice(1);
nextTick(() => {
scrollToBottom();
});
typeWriterTimer = setTimeout(typeWriter, 30);
} else {
// 等待新内容到来,不结束
typeWriterTimer = setTimeout(typeWriter, 30);
}
}
}
</script>
<style lang="scss" scoped>
@import "styles/ChatMainList.scss";
</style>