两端同步流式输出,刷新状态,强制退出。

This commit is contained in:
2026-08-04 17:49:42 +08:00
parent b7c3fc9ff6
commit fbda01a624
3 changed files with 103 additions and 5 deletions

View File

@@ -507,6 +507,10 @@
font-size: 48rpx; font-size: 48rpx;
} }
.chat-btn-group .head-btn:nth-child(4) {
/* 刷新按钮 */
}
.log-out { .log-out {
/* background: #ffd4d4; */ /* background: #ffd4d4; */
} }

View File

@@ -99,10 +99,13 @@
<view class="head-btn" @click="goWorkSpace"> <view class="head-btn" @click="goWorkSpace">
<view class="iconfont icon-wenjianjia"></view> <view class="iconfont icon-wenjianjia"></view>
</view> </view>
<view class="head-btn" @click="goCloudDatabase"> <view class="head-btn" @click="goCloudDatabase">
<view class="iconfont icon-cloud"></view> <view class="iconfont icon-cloud"></view>
</view> </view>
<view class="head-btn log-out" @click="logOut"> <view class="head-btn" @click="handleRefresh">
<uni-icons type="refreshempty" size="24" color="#52c41a"></uni-icons>
</view>
<view class="head-btn log-out" @click="logOut">
<view class="iconfont icon-tuichu"></view> <view class="iconfont icon-tuichu"></view>
</view> </view>
</view> </view>
@@ -839,6 +842,71 @@ import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.
}) })
} }
// 刷新当前会话消息
const handleRefresh = async () => {
// 1. 重置输入框状态
isThinking.value = false
isSelfSent.value = false
socketStore.isThinking = false
textMessage.value = ''
uploadedFiles.value = []
previewFileArray.value = []
// 移除流式消息占位
if (streamingMessageId.value) {
allmessages.value = allmessages.value.filter(m => m._id !== streamingMessageId.value)
streamingMessageId.value = null
}
const titleMap = { 0: 'AI消息', 1: '好友消息', 2: '群聊消息' }
const title = titleMap[ChatType.value] || '消息'
uni.showLoading({ title: `刷新${title}中...`, mask: true })
try {
// 2. 检查当前 token 是否还有效
const token = getToken()
if (!token) {
uni.hideLoading()
uni.showToast({ title: '登录已失效,请重新登录', icon: 'none', duration: 1500 })
setTimeout(() => uni.reLaunch({ url: '/pages/Login/Login' }), 1500)
return
}
await getUserInfo(token)
// 3. 确保 AI 对话模式下 socket 连接正常PC 离线由 watcher 实时监控)
if (ChatType.value === 0 && !socketStore.isConnected) {
await handleConnect()
}
// 4. 刷新消息
switch (ChatType.value) {
case 0:
await takeConversationMessages()
break
case 1:
await takeFriendMessages()
break
case 2:
await takeGroupMessages()
break
default:
await takeConversationMessages()
break
}
uni.hideLoading()
uni.showToast({ title: '已刷新', icon: 'success', duration: 1200 })
} catch (e) {
uni.hideLoading()
// 区分 token 失效和其他错误
const errMsg = typeof e === 'string' ? e : (e?.message || '')
if (errMsg.includes('token') || errMsg.includes('Token') || errMsg.includes('登录') || errMsg.includes('过期')) {
uni.showToast({ title: '登录已失效,请重新登录', icon: 'none', duration: 1500 })
setTimeout(() => uni.reLaunch({ url: '/pages/Login/Login' }), 1500)
} else {
uni.showToast({ title: '刷新失败', icon: 'error', duration: 1500 })
}
}
}
const previewFileArray = ref([]) const previewFileArray = ref([])
const uploadPhoto = () => { const uploadPhoto = () => {
console.log('点击了拍照上传'); console.log('点击了拍照上传');
@@ -1368,7 +1436,10 @@ import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.
if (!oldVal && newVal) { if (!oldVal && newVal) {
console.log('AI开始回复跨设备同步锁定输入框'); console.log('AI开始回复跨设备同步锁定输入框');
isThinking.value = true isThinking.value = true
// 非本设备发起时,也需要添加流式气泡 // 跨设备场景:先加载最新消息(含 PC 发的用户消息),再追加流式气泡
if (!isSelfSent.value) {
await takeConversationMessages()
}
if (!streamingMessageId.value) { if (!streamingMessageId.value) {
streamingMessageId.value = 'streaming-' + Date.now() streamingMessageId.value = 'streaming-' + Date.now()
allmessages.value = [...allmessages.value, { allmessages.value = [...allmessages.value, {
@@ -1416,6 +1487,25 @@ import { openFile as openFileNative, downloadAndOpen } from '@/utils/fileOpener.
} }
}) })
// 监听 pc_offlinePC 端下线时立即强制跳转登录页
watch(() => socketStore.pcOffline, (offline) => {
if (offline) {
isThinking.value = false
socketStore.isThinking = false
socketStore.pcOffline = false
uni.showToast({
title: 'PC端不在线请先登录',
icon: 'none',
duration: 1500
})
setTimeout(() => {
uni.reLaunch({
url: '/pages/Login/Login'
})
}, 1500)
}
})
// 监听流式消息内容,实时更新 AI 回复(流式输出核心) // 监听流式消息内容,实时更新 AI 回复(流式输出核心)
watch(() => socketStore.messageString, (newContent) => { watch(() => socketStore.messageString, (newContent) => {
if (!streamingMessageId.value || !socketStore.isThinking) return if (!streamingMessageId.value || !socketStore.isThinking) return

View File

@@ -30,6 +30,7 @@ export const useSocketStore = defineStore('socket', () => {
const maxLogCount = ref(20) const maxLogCount = ref(20)
const authFailReason = ref('') const authFailReason = ref('')
const pcOffline = ref(false)
// ==================== 计算属性 ==================== // ==================== 计算属性 ====================
const isConnected = computed(() => connectionStatus.value === 'connected') const isConnected = computed(() => connectionStatus.value === 'connected')
@@ -66,6 +67,7 @@ export const useSocketStore = defineStore('socket', () => {
...config.value, ...config.value,
...options ...options
} }
pcOffline.value = false // 每次连接时重置 PC 离线标记
addLog('info', addLog('info',
`准备连接WebSocket。Token:${config.value.token || '未设置'}。ConversationId: ${config.value.conversationId || '未设置'}` `准备连接WebSocket。Token:${config.value.token || '未设置'}。ConversationId: ${config.value.conversationId || '未设置'}`
) )
@@ -182,6 +184,7 @@ export const useSocketStore = defineStore('socket', () => {
break break
case 'pc_offline': case 'pc_offline':
pcOffline.value = true
addLog('warn', 'PC 端已离线') addLog('warn', 'PC 端已离线')
break break
@@ -287,6 +290,7 @@ export const useSocketStore = defineStore('socket', () => {
logs, logs,
config, config,
authFailReason, authFailReason,
pcOffline,
// 计算属性 // 计算属性
isConnected, isConnected,