部分优化

This commit is contained in:
2026-07-07 18:02:55 +08:00
parent b8af01cc69
commit bf5203b081
48 changed files with 3228 additions and 1755 deletions

View File

@@ -34,9 +34,9 @@
</view>
</view>
<!-- ===== ai正在思考弹窗 ===== -->
<view v-if="isThinking" class="ncd-overlay" @click="closeNewChatModal">
<view class="ncd-card ai-card" @click.stop>
<!-- ===== ai正在思考弹窗仅本设备发起时显示 ===== -->
<view v-if="isThinking && isSelfSent" class="ncd-overlay" @click.stop>
<view class="ncd-card ai-card">
<view class="thinking-content">
<view class="loading-dots">
<view class="dot"></view>
@@ -44,10 +44,26 @@
<view class="dot"></view>
</view>
<text class="thinking-text">AI 正在思考中...</text>
<view class="stop-thinking-btn" @click="stopConversation">
<text> 中断对话</text>
</view>
</view>
</view>
</view>
<!-- ===== 消息详情弹窗 ===== -->
<view v-if="showMessageModal" class="ncd-overlay" @click="closeMessageDetail">
<view class="ncd-card message-detail-card" @click.stop>
<view class="ncd-header">
<text class="ncd-title-zh">消息详情</text>
<uni-icons type="closeempty" color="#ff0000" size="24" @click="closeMessageDetail"></uni-icons>
</view>
<scroll-view class="message-detail-content" scroll-y>
<text>{{ detailMessageContent }}</text>
</scroll-view>
</view>
</view>
<view class="chat-page page-container">
<!-- 弹窗出来时的笼罩层 -->
@@ -102,6 +118,7 @@
<!-- <view v-if="message.role ==='user'" class="iconfont icon-yonghuziliao"></view> -->
</view>
<view class="chat-content" :class="{'chat-content-user':message.role==='user'}"
@click="showMessageDetail(message)"
v-if="message.content && String(message.content).trim() !== ''">
<!-- 文本内容 -->
<view v-if="parseFileInfo(message.content).textContent"
@@ -202,27 +219,24 @@
</view>
</view>
</scroll-view>
<view class="chat-interactive-container">
<view class="chat-interactive-container" :class="{ 'interactive-disabled': isThinking }">
<view class="chat-interactive-group">
<view class="chat-interactive-btn" @click="uploadPhoto">拍照上传</view>
<view class="chat-interactive-btn" @click="uploadFile()">上传文件</view>
<view class="chat-interactive-btn" :class="{ disabled: isThinking }" @click="!isThinking && uploadPhoto()">拍照上传</view>
<view class="chat-interactive-btn" :class="{ disabled: isThinking }" @click="!isThinking && uploadFile()">上传文件</view>
</view>
<view class="image-list" v-if="previewFileArray.length > 0">
<view class="image-title">已选择 ({{ previewFileArray.length }})</view>
<view class="image-grid">
<view class="image-item" v-for="(item, index) in previewFileArray" :key="index">
<view class="image-info">
<text class="image-name">{{ item.name }}</text>
<text class="delete-btn" @click.stop="deleteImage(index)">删除</text>
</view>
<view class="image-tags">
<view class="image-tag" v-for="(item, index) in previewFileArray" :key="index">
<text class="tag-name">{{ item.name }}</text>
<text class="tag-close" @click.stop="deleteImage(index)">×</text>
</view>
</view>
</view>
<view class="chat-input-container">
<textarea class="message-input" placeholder="输入消息..." @confirm="sendMessage" confirm-type="send"
<view class="chat-input-container" :class="{ 'input-disabled': isThinking }">
<textarea class="message-input" :placeholder="isThinking ? (isSelfSent ? 'AI 正在回复中...' : '电脑正在运行,请稍后') : '输入消息...'" :disabled="isThinking"
v-model="textMessage" auto-height></textarea>
<view class="input-btn-group">
<view class="iconfont icon-tingzhi icon-btn"></view>
<view class="input-btn-group send-message-btn" :class="{ disabled: isThinking }" @click="!isThinking && sendMessage()">
<view class="iconfont icon-fasong"></view>
</view>
</view>
</view>
@@ -242,7 +256,8 @@
watch
} from 'vue';
import {
onShow
onShow,
onUnload
} from '@dcloudio/uni-app';
import ChatSidebar from '@/components/ChatSidebar.vue'
import {
@@ -570,19 +585,7 @@
}
// 删除指定图片
const deleteImage = (index) => {
uni.showModal({
title: '提示',
content: '确定要删除这张图片吗?',
success: (res) => {
if (res.confirm) {
previewFileArray.value.splice(index, 1);
uni.showToast({
title: '删除成功',
icon: 'success'
});
}
}
});
previewFileArray.value.splice(index, 1);
};
const fileList = ref([])
@@ -646,9 +649,22 @@
// })
const isThinking = ref(false)
const isSelfSent = ref(false) // 是否当前设备发起的 AI 对话(用于区分弹窗/仅锁输入)
const isUploading = ref(false)
const uploadedFiles = ref([])
// 消息详情弹窗
const showMessageModal = ref(false)
const detailMessageContent = ref('')
const showMessageDetail = (message) => {
detailMessageContent.value = message.content || ''
showMessageModal.value = true
}
const closeMessageDetail = () => {
showMessageModal.value = false
detailMessageContent.value = ''
}
const sendMessage = async () => {
const message = textMessage.value.trim()
if (!message && previewFileArray.value.length === 0) return
@@ -811,16 +827,18 @@
}
socketStore.send(data)
isThinking.value = true
// 3分钟超时
isSelfSent.value = true // 标记为当前设备发起
// 10分钟超时
aiResponseTimer = setTimeout(() => {
uni.showToast({
title: 'AI回复超时请重试',
icon: 'none'
})
isThinking.value = false
isSelfSent.value = false
textMessage.value = ''
uploadedFiles.value = []
}, 180000)
}, 600000)
} catch (error) {
isThinking.value = false
uni.showToast({
@@ -830,21 +848,65 @@
}
}
// 全局监听 socketStore.isThinkingtrue → false 时表示 AI 回复结束
// 中断 AI 对话
const stopConversation = async () => {
console.log('中断对话 - 当前会话ID:', currentSessionId.value)
try {
await socketStore.send({
type: 'stop',
conversation_id: currentSessionId.value
})
console.log('中断指令已发送成功')
} catch (err) {
console.error('中断指令发送失败:', err)
}
// 立即关闭弹窗,清除超时
clearTimeout(aiResponseTimer)
isThinking.value = false
isSelfSent.value = false
textMessage.value = ''
uploadedFiles.value = []
// socketStore.isThinking 和 takeConversationMessages 由后端中断确认驱动(见 handleBusinessMessage
uni.showToast({ title: '已中断', icon: 'none', duration: 1500 })
}
// 全局监听 socketStore.isThinking跨设备同步思考状态锁/解锁输入框
watch(() => socketStore.isThinking, (newVal, oldVal) => {
console.log("isThinking 变化:", oldVal, "→", newVal);
// AI 回复结束:从 true 变为 false且已有回复内容
if (oldVal && !newVal && socketStore.messageString) {
console.log("isThinking 变化:", oldVal, "→", newVal, "messageString:", socketStore.messageString);
// false → true后端开始回复当前设备或其他设备触发锁定输入框
if (!oldVal && newVal) {
console.log('AI开始回复跨设备同步锁定输入框');
isThinking.value = true
return
}
// true → falseAI 回复结束或中断,解锁输入框并刷新消息列表
if (oldVal && !newVal) {
clearTimeout(aiResponseTimer)
console.log("ai返回的消息", socketStore.messageString);
console.log('AI回复结束刷新消息列表');
console.log('AI回复结束正常/中断),解锁并刷新消息列表');
takeConversationMessages()
isThinking.value = false
isSelfSent.value = false
textMessage.value = ''
uploadedFiles.value = []
}
})
// 监听 auth_fail当 pc 端不在线时弹窗提示并取消思考状态
watch(() => socketStore.authFailReason, (reason) => {
if (reason === 'pc offline') {
isThinking.value = false
socketStore.authFailReason = ''
nextTick(() => {
uni.showModal({
title: '提示',
content: 'PC端不在线不可聊天',
showCancel: false,
confirmText: '知道了'
})
})
}
})
const textMessage = ref('')
// 获取用户历史会话列表
@@ -1018,7 +1080,7 @@
const takeConversationMessages = async () => {
try {
allmessages.value = await getConversationMessages(userToken.value, currentSessionId.value) || [];
console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
// console.log("获取到的所有消息:", JSON.stringify(allmessages.value) );
// currentMessages.value = allmessages.value.slice(-pageInfoNumber);
// 数据获取后执行滚动
scrollToBottom();
@@ -1124,10 +1186,12 @@
watch(currentSessionId, (newId) => {
if (newId) {
uni.setStorageSync('currentSessionId', currentSessionId.value)
// takeConversationMessages();
switch (ChatType.value) {
case 0:
takeConversationMessages();
// 切换 AI 会话时重新连接 socket绑定新 conversationId
socketStore.disconnect()
handleConnect()
break;
case 1:
takeFriendMessages();
@@ -1135,7 +1199,6 @@
case 2:
takeGroupMessages();
break;
// 可选:默认兜底
default:
takeConversationMessages();
break;
@@ -1167,6 +1230,10 @@
if (currentSessionId.value) {
takeConversationMessages();
}
// AI 对话页面:主动建立 socket 连接,确保消息实时推送
if (userToken.value && currentSessionId.value && !socketStore.isConnected) {
handleConnect()
}
} else if (chatType === 1) {
await takeFriendList()
takeFriendMessages();
@@ -1177,6 +1244,11 @@
await takeGroupMessages()
}
})
// 离开 AI 对话页面时断开 socket
onUnload(() => {
socketStore.disconnect()
})
</script>
<style lang="scss" scoped>