fix(common): 🐛 simplify reconnection sync to avoid duplicate full syncs on first

fix(view): 🐛 display user gitHub and gitee labels
This commit is contained in:
Dawn
2025-12-31 18:24:07 +08:00
committed by GitHub
parent c28ffaa451
commit 6057af2ec7
10 changed files with 171 additions and 63 deletions
+51 -4
View File
@@ -54,7 +54,9 @@
"account": "Account: {account}"
},
"tooltip": {
"copy_account": "Copy account"
"copy_account": "Copy account",
"bound_gitee": "Gitee linked",
"bound_github": "GitHub linked"
},
"empty": {
"no_result": "No matching result",
@@ -272,7 +274,9 @@
},
"location_unknown": "Unknown",
"tooltip": {
"copy_account": "Copy account"
"copy_account": "Copy account",
"bound_gitee": "Gitee linked",
"bound_github": "GitHub linked"
},
"buttons": {
"edit": "Edit profile",
@@ -380,6 +384,43 @@
"network_offline": "Network unavailable, please check your settings",
"network_connecting": "Connecting to server...",
"network_ws_offline": "Server connection lost, retrying",
"copy": {
"empty": "Nothing to copy",
"image_processing": "Converting {format} image to PNG...",
"image_success": "Image copied to clipboard",
"image_convert_success": "Image converted to PNG and copied",
"selected_success": "Selected text copied",
"message_success": "Message copied",
"unknown_error": "Failed to copy content, please try again later"
},
"feature": {
"coming_soon": "Feature under development"
},
"translate": {
"empty": "Nothing to translate"
},
"file": {
"missing_local": "File not found locally, please download first",
"show_failed": "Unable to reveal file in folder",
"download_prompt": "File not downloaded yet, please download first",
"download_success": "File downloaded successfully",
"download_failed": "File download failed, please retry",
"save_success": "File saved locally",
"save_failed": "Failed to save file, please retry"
},
"image": {
"fetch_failed": "Failed to get image URL",
"locate_failed": "Unable to locate this image for now",
"download_prompt": "Image not downloaded, saving locally...",
"save_success": "Image saved locally",
"save_failed": "Failed to save image",
"download_failed": "Image download failed, please retry"
},
"delete": {
"invalid_session": "Unable to determine the session of this message",
"confirm": "Deleted messages will disappear from your history. Continue?",
"success": "Message deleted"
},
"announcement": {
"view_all": "View all"
},
@@ -389,11 +430,17 @@
"cancel": "Cancel",
"group_nickname": {
"title": "Edit group nickname",
"placeholder": "Enter a group nickname"
"placeholder": "Enter a group nickname",
"error": {
"empty": "Group nickname cannot be empty",
"invalid_room": "Group information is invalid"
}
},
"toast": {
"searching": "Searching for messages...",
"not_found": "Failed to load history, please try again"
"not_found": "Failed to load history, please try again",
"multi_select_mobile": "Message multi-select is not available on mobile",
"disbanded_group": "This group has been disbanded"
}
},
"friends_list": {
+48 -3
View File
@@ -271,7 +271,9 @@
},
"location_unknown": "未知",
"tooltip": {
"copy_account": "复制账号"
"copy_account": "复制账号",
"bound_gitee": "已绑定 Gitee",
"bound_github": "已绑定 GitHub"
},
"buttons": {
"edit": "编辑资料",
@@ -379,6 +381,43 @@
"network_offline": "当前网络不可用,请检查你的网络设置",
"network_connecting": "正在连接服务器...",
"network_ws_offline": "服务器连接已断开,正在重试",
"copy": {
"empty": "没有可复制的内容",
"image_processing": "正在将 {format} 格式图片转换为 PNG 并复制...",
"image_success": "图片已复制到剪贴板",
"image_convert_success": "图片已转换为 PNG 并复制到剪贴板",
"selected_success": "选中文本已复制",
"message_success": "消息内容已复制",
"unknown_error": "复制内容失败,请稍后再试"
},
"feature": {
"coming_soon": "功能暂开发"
},
"translate": {
"empty": "没有可翻译的内容"
},
"file": {
"missing_local": "暂时找不到本地文件,请先下载后再试~",
"show_failed": "无法在文件夹中显示该文件",
"download_prompt": "文件还未下载,请先下载后再试~",
"download_success": "文件下载完成,请查看~",
"download_failed": "文件下载失败,请重试",
"save_success": "文件已保存到本地",
"save_failed": "文件保存失败,请重试"
},
"image": {
"fetch_failed": "获取图片地址失败",
"locate_failed": "暂时无法定位该图片~",
"download_prompt": "图片未下载,正在保存到本地...",
"save_success": "图片已保存到本地",
"save_failed": "保存图片失败",
"download_failed": "图片下载失败,请重试~"
},
"delete": {
"invalid_session": "无法确定消息所属的会话",
"confirm": "删除后将不会出现在你的消息记录中,确定删除吗?",
"success": "消息已删除"
},
"announcement": {
"view_all": "查看全部"
},
@@ -388,11 +427,17 @@
"cancel": "取消",
"group_nickname": {
"title": "修改群昵称",
"placeholder": "请输入群昵称"
"placeholder": "请输入群昵称",
"error": {
"empty": "群昵称不能为空",
"invalid_room": "当前群聊信息异常"
}
},
"toast": {
"searching": "正在查找消息...",
"not_found": "加载历史消息失败,请稍后重试"
"not_found": "加载历史消息失败,请稍后重试",
"multi_select_mobile": "移动端暂不支持消息多选",
"disbanded_group": "该群聊已解散,无法执行此操作"
}
},
"friends_list": {
@@ -38,6 +38,8 @@ pub struct RoomMemberResponse {
pub wearing_item_id: Option<String>,
#[serde(rename = "itemIds")]
pub item_ids: Option<Vec<String>>,
pub linked_gitee: Option<bool>,
pub linked_github: Option<bool>,
}
#[tauri::command]
+10 -10
View File
@@ -49,7 +49,6 @@ import { useFeedStore } from '@/stores/feed'
import { useFeedNotificationStore } from '@/stores/feedNotification'
import type { MarkItemType, RevokedMsgType, UserItem } from '@/services/types.ts'
import * as ImRequestUtils from '@/utils/ImRequestUtils'
import { invoke } from '@tauri-apps/api/core'
import { listen } from '@tauri-apps/api/event'
import { useTauriListener } from '@/hooks/useTauriListener'
import { updateSettings } from '@/services/tauriCommand.ts'
@@ -564,8 +563,8 @@ const handleWebsocketEvent = async (event: any) => {
const nextStateRaw = payload.state
const nextState = typeof nextStateRaw === 'string' ? nextStateRaw.toUpperCase() : ''
const isReconnectionFlag = payload.isReconnection ?? payload.is_reconnection
const hasRecoveredFromDrop = Boolean(previousState && previousState !== 'CONNECTED' && nextState === 'CONNECTED')
const shouldHandleReconnect = nextState === 'CONNECTED' && (isReconnectionFlag || hasRecoveredFromDrop)
// 只有明确标记为重连的情况才触发同步,避免首次连接时触发不必要的全量同步
const shouldHandleReconnect = nextState === 'CONNECTED' && isReconnectionFlag
lastWsConnectionState = nextState || previousState
@@ -577,18 +576,19 @@ const handleWebsocketEvent = async (event: any) => {
// 开始同步,显示加载状态
chatStore.syncLoading = true
try {
if (userStore.userInfo?.uid) {
await invoke('sync_messages', { param: { asyncData: true, uid: userStore.userInfo.uid } })
}
// Rust 端已通过 schedule_post_reconnect_sync 调用 sync_messages,前端无需重复调用
await chatStore.getSessionList(true)
await chatStore.setAllSessionMsgList(20)
// 重连后同步频道和当前/首个群聊成员信息,避免展示断网前的旧数据
// 重连后同步当前/首个群聊成员信息,避免展示断网前的旧数据
await refreshActiveGroupMembers()
if (globalStore.currentSessionRoomId) {
await chatStore.resetAndRefreshCurrentRoomMessages()
await chatStore.fetchCurrentRoomRemoteOnce(20)
const currentRoomId = globalStore.currentSessionRoomId
const currentSession = chatStore.getSession(currentRoomId)
// 增量拉取当前会话的新消息,而不是清空重建
await chatStore.fetchCurrentRoomRemoteOnce(20)
// 重连后如果当前会话仍有未读,补一次已读上报和本地清零,避免气泡卡住
if (currentSession?.unreadCount) {
chatStore.markSessionRead(currentRoomId)
+23 -8
View File
@@ -105,15 +105,30 @@
<n-flex align="center" :size="12">
<p class="text-[--info-text-color]">{{ t('home.profile_card.labels.account') }}</p>
<span class="text-(12px [--chat-text-color])">{{ `${groupStore.getUserInfo(uid)?.account}` }}</span>
<n-tooltip trigger="hover">
<template #trigger>
<svg class="size-12px cursor-pointer hover:color-#909090 hover:transition-colors" @click="handleCopy">
<use href="#copy"></use>
</svg>
</template>
<span>{{ t('home.profile_card.tooltip.copy_account') }}</span>
</n-tooltip>
<!-- Gitee/GitHub 标识 -->
<n-tooltip v-if="groupStore.getUserInfo(uid)?.linkedGitee">
<template #trigger>
<svg class="size-18px dark:color-#d5304f"><use href="#gitee-login"></use></svg>
</template>
<span>{{ t('home.profile_card.tooltip.bound_gitee') }}</span>
</n-tooltip>
<n-tooltip v-if="groupStore.getUserInfo(uid)?.linkedGithub">
<template #trigger>
<svg class="size-18px dark:color-#fefefe"><use href="#github-login"></use></svg>
</template>
<span>{{ t('home.profile_card.tooltip.bound_github') }}</span>
</n-tooltip>
</n-flex>
<n-tooltip trigger="hover">
<template #trigger>
<svg class="size-12px cursor-pointer hover:color-#909090 hover:transition-colors" @click="handleCopy">
<use href="#copy"></use>
</svg>
</template>
<span>{{ t('home.profile_card.tooltip.copy_account') }}</span>
</n-tooltip>
</n-flex>
</n-flex>
@@ -526,21 +526,15 @@
</div>
<div class="flex items-center justify-between gap-12px border-t-(1px solid [--line-color]) pt-12px">
<div
class="flex flex-1 flex-col items-center gap-10px cursor-pointer text-(12px [--chat-text-color]) p-8px rounded-12px transition-colors hover:bg-#f4f5f9 dark:hover:bg-#1f232d"
@click="handleForwardGroupQr">
<div class="QRcode-item" @click="handleForwardGroupQr">
<svg class="size-20px"><use href="#share-three"></use></svg>
<span>{{ t('home.chat_header.qr.actions.forward') }}</span>
</div>
<div
class="flex flex-1 flex-col items-center gap-10px cursor-pointer text-(12px [--chat-text-color]) p-8px rounded-12px transition-colors hover:bg-#f4f5f9 dark:hover:bg-#1f232d"
@click="handleCopyGroupId">
<div class="QRcode-item" @click="handleCopyGroupId">
<svg class="size-20px"><use href="#copy"></use></svg>
<span>{{ t('home.chat_header.qr.actions.copy_group_id') }}</span>
</div>
<div
class="flex flex-1 flex-col items-center gap-10px cursor-pointer text-(12px [--chat-text-color]) p-8px rounded-12px transition-colors hover:bg-#f4f5f9 dark:hover:bg-#1f232d"
@click="handleSaveGroupQrImage">
<div class="QRcode-item" @click="handleSaveGroupQrImage">
<svg class="size-20px"><use href="#Importing"></use></svg>
<span>{{ t('home.chat_header.qr.actions.save_image') }}</span>
</div>
@@ -1642,6 +1636,10 @@ onUnmounted(() => {
}
}
.QRcode-item {
@apply flex flex-1 flex-col items-center gap-10px cursor-pointer text-(12px [--chat-text-color]) p-8px rounded-12px transition-colors hover:bg-#e3e3e360 dark:hover:bg-#262626;
}
:deep(.n-scrollbar > .n-scrollbar-container > .n-scrollbar-content) {
padding-left: 2px;
}
@@ -151,8 +151,6 @@
<n-flex vertical :size="2" class="flex-1 truncate">
<p :title="item.name" class="text-12px truncate flex-1 leading-tight">
{{ item.myName ? item.myName : item.name }}
<Icon v-if="item.linkedGithub" icon="mdi:github" class="ml-4px text-12px" />
<Icon v-if="item.linkedGitee" icon="simple-icons:gitee" class="ml-2px text-12px" />
</p>
<n-flex
v-if="item.userStateId && getUserState(item.userStateId)"
@@ -209,7 +207,6 @@ import { useGroupStore } from '@/stores/group.ts'
import { useSettingStore } from '@/stores/setting'
import { useUserStatusStore } from '@/stores/userStatus'
import { AvatarUtils } from '@/utils/AvatarUtils'
import { Icon } from '@iconify/vue'
import { getUserByIds } from '@/utils/ImRequestUtils'
import { useAnnouncementStore } from '@/stores/announcement'
+24 -24
View File
@@ -104,7 +104,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
const trimmedName = groupNicknameValue.value.trim()
if (!trimmedName) {
groupNicknameError.value = '群昵称不能为空'
groupNicknameError.value = t('home.chat_main.group_nickname.error.empty')
return
}
@@ -115,7 +115,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
const { roomId, currentUid } = groupNicknameContext.value
if (!roomId) {
window.$message?.error('当前群聊信息异常')
window.$message?.error(t('home.chat_main.group_nickname.error.invalid_room'))
return
}
@@ -174,14 +174,14 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
const isNoticeMessage = (item: MessageType) => item.message.type === MsgEnum.NOTICE
const revealInDirSafely = async (targetPath?: string | null) => {
if (!targetPath) {
window.$message?.error('暂时找不到本地文件,请先下载后再试~')
window.$message?.error(t('home.chat_main.file.missing_local'))
return
}
try {
await revealItemInDir(targetPath)
} catch (error) {
console.error('在文件夹中显示文件失败:', error)
window.$message?.error('无法在文件夹中显示该文件')
window.$message?.error(t('home.chat_main.file.show_failed'))
}
}
@@ -200,7 +200,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
click: async (item: MessageType) => {
const imageUrl = item.message.body.url || item.message.body.content
if (!imageUrl) {
window.$message.error('获取图片地址失败')
window.$message.error(t('home.chat_main.image.fetch_failed'))
return
}
await emojiStore.addEmoji(imageUrl)
@@ -214,7 +214,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
icon: 'share',
click: (item: MessageType) => {
if (isMobile()) {
window.$message.warning('功能暂开发')
window.$message.warning(t('home.chat_main.feature.coming_soon'))
return
}
handleForward(item)
@@ -295,7 +295,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
icon: 'copy',
click: (item: MessageType) => {
if (isMobile()) {
window.$message.warning('功能暂开发')
window.$message.warning(t('home.chat_main.feature.coming_soon'))
return
}
handleCopy(item.message.body.url, true, item.message.id)
@@ -307,7 +307,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
icon: 'Importing',
click: async (item: MessageType) => {
if (isMobile()) {
window.$message.warning('功能暂开发')
window.$message.warning(t('home.chat_main.feature.coming_soon'))
return
}
await saveVideoAttachmentAs({
@@ -368,7 +368,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
const content = selectedText || item.message.body.content
if (!content) {
window.$message?.warning('没有可翻译的内容')
window.$message?.warning(t('home.chat_main.translate.empty'))
return
}
@@ -437,7 +437,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
icon: 'Importing',
click: async (item: MessageType) => {
if (isMobile()) {
window.$message.warning('功能暂开发')
window.$message.warning(t('home.chat_main.feature.coming_soon'))
return
}
const fileUrl = item.message.body.url
@@ -482,13 +482,13 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
// 最后判断文件不存在本地,那就下载它
if (!fileMeta.exists) {
// 文件不存在本地
const downloadMessage = window.$message.info('文件没下载哦~ 请下载文件后再打开🚀...')
const downloadMessage = window.$message.info(t('home.chat_main.file.download_prompt'))
const _absolutePath = await fileDownloadStore.downloadFile(fileUrl, fileName)
if (_absolutePath) {
absolutePath = _absolutePath
downloadMessage.destroy()
window.$message.success('文件下载好啦!请查看~')
window.$message.success(t('home.chat_main.file.download_success'))
await revealInDirSafely(_absolutePath)
await fileDownloadStore.refreshFileDownloadStatus({
fileUrl: item.message.body.url,
@@ -500,7 +500,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
return
} else {
absolutePath = ''
window.$message.error('文件下载失败,请重试~')
window.$message.error(t('home.chat_main.file.download_failed'))
return
}
}
@@ -520,7 +520,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
label: () => t('menu.del'),
icon: 'delete',
click: (item: any) => {
tips.value = '删除后将不会出现在你的消息记录中,确定删除吗?'
tips.value = t('home.chat_main.delete.confirm')
modalShow.value = true
delIndex.value = item.message.id
delRoomId.value = item.message.roomId
@@ -647,7 +647,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
icon: 'Importing',
click: async (item: RightMouseMessageItem) => {
if (isMobile()) {
window.$message.warning('功能暂开发')
window.$message.warning(t('home.chat_main.feature.coming_soon'))
return
}
await saveFileAttachmentAs({
@@ -682,13 +682,13 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
// 最后判断文件不存在本地,那就下载它
if (!fileMeta.exists) {
// 文件不存在本地
const downloadMessage = window.$message.info('文件没下载哦, 请下载文件后再打开')
const downloadMessage = window.$message.info(t('home.chat_main.file.download_prompt'))
const _absolutePath = await fileDownloadStore.downloadFile(fileUrl, fileName)
if (_absolutePath) {
absolutePath = _absolutePath
downloadMessage.destroy()
window.$message.success('文件已保存到本地')
window.$message.success(t('home.chat_main.file.save_success'))
await revealInDirSafely(_absolutePath)
await fileDownloadStore.refreshFileDownloadStatus({
fileUrl: item.message.body.url,
@@ -700,7 +700,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
return
} else {
absolutePath = ''
window.$message.error('文件下载失败,请重试')
window.$message.error(t('home.chat_main.file.download_failed'))
return
}
}
@@ -726,7 +726,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
icon: 'Importing',
click: async (item: MessageType) => {
if (isMobile()) {
window.$message.warning('功能暂开发')
window.$message.warning(t('home.chat_main.feature.coming_soon'))
return
}
try {
@@ -749,7 +749,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
}
} catch (error) {
console.error('保存图片失败:', error)
window.$message.error('保存图片失败')
window.$message.error(t('home.chat_main.image.save_failed'))
}
}
},
@@ -760,7 +760,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
const fileUrl = item.message.body.url || item.message.body.content
const fileName = item.message.body.fileName || extractFileName(fileUrl)
if (!fileUrl || !fileName) {
window.$message.warning('暂时无法定位该图片~')
window.$message.warning(t('home.chat_main.image.locate_failed'))
return
}
@@ -774,13 +774,13 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
const [fileMeta] = await getFilesMeta<FilesMeta>([fileStatus?.absolutePath || absolutePath || fileUrl])
if (!fileMeta.exists) {
const downloadMessage = window.$message.info('图片没下载, 正在保存到本地...')
const downloadMessage = window.$message.info(t('home.chat_main.image.download_prompt'))
const _absolutePath = await fileDownloadStore.downloadFile(fileUrl, fileName)
if (_absolutePath) {
absolutePath = _absolutePath
downloadMessage.destroy()
window.$message.success('图片已保存到本地')
window.$message.success(t('home.chat_main.image.save_success'))
await revealInDirSafely(_absolutePath)
await fileDownloadStore.refreshFileDownloadStatus({
fileUrl,
@@ -792,7 +792,7 @@ export const useChatMain = (isHistoryMode = false, options: UseChatMainOptions =
return
} else {
absolutePath = ''
window.$message.error('图片下载失败,请重试~')
window.$message.error(t('home.chat_main.image.download_failed'))
return
}
}
+4
View File
@@ -277,6 +277,10 @@ export type UserInfoType = {
client: string
/** 个人简介 */
resume: string
/** 是否绑定 Gitee */
linkedGitee?: boolean
/** 是否绑定 GitHub */
linkedGithub?: boolean
}
export type BadgeType = {
+2 -2
View File
@@ -1,10 +1,10 @@
<template>
<div class="flex items-center justify-center gap-12px text-12px color-#909090">
<div class="flex-center cursor-default gap-12px text-12px color-#909090">
<span class="h-px w-60px bg-#dadada dark:bg-#3a3a3a"></span>
<span>{{ thirdPartyLabel }}</span>
<span class="h-px w-60px bg-#dadada dark:bg-#3a3a3a"></span>
</div>
<div class="flex justify-center gap-28px mt-16px">
<div class="flex-x-center gap-28px mt-16px">
<div
v-for="item in thirdPartyOptions"
:key="item.key"