✨ feat(im): IM 5 个 store 补 HMR + 抽 atAll 常量 + 全面补齐 JSDoc
- 全部 5 个 store(conversation / friend / group / ui / websocket)加 acceptHMRUpdate;Pinia 单例的 actions 是 wrapper 闭包,Vite 推新模块时 不会自动替换闭包内的旧函数体,导致改 store 后看着热重载、跑的还是旧逻辑 - 抽 IM_AT_ALL_USER_ID(-1)+ IM_AT_ALL_NICKNAME('所有人')到 utils/constants.ts;conversationStore 删本地 AT_ALL_FLAG 改用共享常量; MentionPicker 渲染虚拟项 / ChatGroupMember 类型注释也都引这两个常量 - groupStore.loadGroups 改成合并而非全量替换:用 groupMap 按 id 找已有项, 保留 loadGroupMembers 写过的 members / memberCount / muted(这三个字段 不在 ImGroupRespVO 里,全量替换会被冲掉) - groupStore.loadGroupMembers 重写为分步注释(1. 缓存 / 2. 拉取 / 3. 回填 muted / 4.1 占位 / 4.2 直写);await 之后必须重新 getGroup 防 race(loadGroupMembers 与 loadGroups 并发时用入口快照会把真实 name 覆盖成 String(groupId)) - types/GroupMember 补 muted 字段,convertGroupMember 透传, 解决 vue-tsc TS2339 / TS2353 - 5 个 store 缺 JSDoc 的方法全部补齐:removePrivateConversation / removeGroupConversation / getFriend / getActiveFriends / isFriend / loadGroupInfo / upsertGroup / stopHeartbeat - 全局"墓碑"措辞统一为"软删保留记录",types / friendStore / groupStore 三处 - groupStore 删冗余注释(与代码自描述重复的)若干处;变量 g/old 改 group/existingim
parent
a0ed0d800c
commit
3ea04663f2
|
|
@ -1,4 +1,4 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
import { toRaw } from 'vue'
|
import { toRaw } from 'vue'
|
||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
ImConversationType,
|
ImConversationType,
|
||||||
ImMessageType,
|
ImMessageType,
|
||||||
ImMessageStatus,
|
ImMessageStatus,
|
||||||
|
IM_AT_ALL_USER_ID,
|
||||||
TIME_TIP_GAP_MS
|
TIME_TIP_GAP_MS
|
||||||
} from '../../utils/constants'
|
} from '../../utils/constants'
|
||||||
import { imStorage, StorageKeys } from '../../utils/storage'
|
import { imStorage, StorageKeys } from '../../utils/storage'
|
||||||
|
|
@ -19,8 +20,6 @@ import {
|
||||||
} from '../../utils/message'
|
} from '../../utils/message'
|
||||||
import type { Conversation, ConversationStoreMeta, Message } from '../types'
|
import type { Conversation, ConversationStoreMeta, Message } from '../types'
|
||||||
|
|
||||||
const AT_ALL_FLAG = -1 // @全体成员 的特殊 userId 标识:atUserIds 中包含 -1 表示 @all
|
|
||||||
|
|
||||||
// TODO @芋艿:单个 conversation 的消息过多后,可能存储起来会很慢,后续看看怎么优化。
|
// TODO @芋艿:单个 conversation 的消息过多后,可能存储起来会很慢,后续看看怎么优化。
|
||||||
// TODO @芋艿:首次拉取消息时,如果消息过多,可能导致渲染卡顿。(1% 场景)
|
// TODO @芋艿:首次拉取消息时,如果消息过多,可能导致渲染卡顿。(1% 场景)
|
||||||
|
|
||||||
|
|
@ -303,10 +302,12 @@ export const useConversationStore = defineStore('imConversationStore', {
|
||||||
this.saveConversations()
|
this.saveConversations()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 删私聊会话的语义糖:friendStore 删好友时调,避免外面手写 ImConversationType.PRIVATE */
|
||||||
removePrivateConversation(friendId: number) {
|
removePrivateConversation(friendId: number) {
|
||||||
this.removeConversation(ImConversationType.PRIVATE, friendId)
|
this.removeConversation(ImConversationType.PRIVATE, friendId)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 删群聊会话的语义糖:groupStore 群解散时调,避免外面手写 ImConversationType.GROUP */
|
||||||
removeGroupConversation(groupId: number) {
|
removeGroupConversation(groupId: number) {
|
||||||
this.removeConversation(ImConversationType.GROUP, groupId)
|
this.removeConversation(ImConversationType.GROUP, groupId)
|
||||||
},
|
},
|
||||||
|
|
@ -371,7 +372,7 @@ export const useConversationStore = defineStore('imConversationStore', {
|
||||||
if (currentUserId && messageInfo.atUserIds.includes(currentUserId)) {
|
if (currentUserId && messageInfo.atUserIds.includes(currentUserId)) {
|
||||||
conversation.atMe = true
|
conversation.atMe = true
|
||||||
}
|
}
|
||||||
if (messageInfo.atUserIds.includes(AT_ALL_FLAG)) {
|
if (messageInfo.atUserIds.includes(IM_AT_ALL_USER_ID)) {
|
||||||
conversation.atAll = true
|
conversation.atAll = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -674,3 +675,9 @@ export const useConversationStore = defineStore('imConversationStore', {
|
||||||
export const useConversationStoreWithOut = () => {
|
export const useConversationStoreWithOut = () => {
|
||||||
return useConversationStore(store)
|
return useConversationStore(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dev: 让 Pinia 的 actions / state 改动支持 HMR,避免每次改 store 都得硬刷
|
||||||
|
// 否则 Vite 把新模块推下来后,老 store 实例的 action 闭包仍指向旧函数体
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useConversationStore, import.meta.hot))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
|
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
|
@ -29,14 +29,17 @@ export const useFriendStore = defineStore('imFriendStore', {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
/** 按 friendUserId 找好友(含已软删的 DISABLE 记录,调用方自行判定) */
|
||||||
getFriend:
|
getFriend:
|
||||||
(state) =>
|
(state) =>
|
||||||
(friendUserId: number): Friend | undefined => {
|
(friendUserId: number): Friend | undefined => {
|
||||||
return state.friends.find((f) => f.friendUserId === friendUserId)
|
return state.friends.find((f) => f.friendUserId === friendUserId)
|
||||||
},
|
},
|
||||||
|
/** 当前生效的好友列表(过滤掉 DISABLE 软删记录) */
|
||||||
getActiveFriends: (state): Friend[] => {
|
getActiveFriends: (state): Friend[] => {
|
||||||
return state.friends.filter((f) => f.status !== CommonStatusEnum.DISABLE)
|
return state.friends.filter((f) => f.status !== CommonStatusEnum.DISABLE)
|
||||||
},
|
},
|
||||||
|
/** 判断对方是否是当前用户的有效好友(存在 + 非 DISABLE) */
|
||||||
isFriend() {
|
isFriend() {
|
||||||
return (friendUserId: number): boolean => {
|
return (friendUserId: number): boolean => {
|
||||||
const entry = this.getFriend(friendUserId)
|
const entry = this.getFriend(friendUserId)
|
||||||
|
|
@ -93,7 +96,7 @@ export const useFriendStore = defineStore('imFriendStore', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 删除好友(保留墓碑记录,同时级联清理本地私聊会话) */
|
/** 删除好友(软删,保留记录但置 DISABLE;同时级联清理本地私聊会话) */
|
||||||
async deleteFriend(friendUserId: number) {
|
async deleteFriend(friendUserId: number) {
|
||||||
await apiDeleteFriend(friendUserId)
|
await apiDeleteFriend(friendUserId)
|
||||||
this.removeFriend(friendUserId)
|
this.removeFriend(friendUserId)
|
||||||
|
|
@ -168,3 +171,9 @@ function convertFriend(vo: ImFriendRespVO): Friend {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useFriendStoreWithOut = () => useFriendStore(store)
|
export const useFriendStoreWithOut = () => useFriendStore(store)
|
||||||
|
|
||||||
|
// dev: 让 Pinia 的 actions / state 改动支持 HMR,避免每次改 store 都得硬刷
|
||||||
|
// 否则 Vite 把新模块推下来后,老 store 实例的 action 闭包仍指向旧函数体
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useFriendStore, import.meta.hot))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -46,19 +46,34 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
}
|
}
|
||||||
// 拉取当前登录用户加入的所有群(不带成员;成员按需再走 loadGroupMembers)
|
// 拉取当前登录用户加入的所有群(不带成员;成员按需再走 loadGroupMembers)
|
||||||
const list = await apiGetMyGroupList()
|
const list = await apiGetMyGroupList()
|
||||||
this.groups = (list || []).map(convertGroup)
|
const fresh = (list || []).map(convertGroup)
|
||||||
|
// 合并而非全量替换:保留 loadGroupMembers 已经写入的 members / memberCount / muted
|
||||||
|
// (这些字段不在 ImGroupRespVO 里,全量替换会把成员级数据全冲掉)
|
||||||
|
const groupMap = new Map(this.groups.map((group) => [group.id, group]))
|
||||||
|
this.groups = fresh.map((group) => {
|
||||||
|
const existing = groupMap.get(group.id)
|
||||||
|
if (!existing) {
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
members: existing.members,
|
||||||
|
memberCount: existing.memberCount ?? group.memberCount,
|
||||||
|
muted: existing.muted ?? group.muted
|
||||||
|
}
|
||||||
|
})
|
||||||
this.loaded = true
|
this.loaded = true
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
for (const g of this.groups) {
|
for (const group of this.groups) {
|
||||||
conversationStore.updateConversation(ImConversationType.GROUP, g.id, {
|
conversationStore.updateConversation(ImConversationType.GROUP, group.id, {
|
||||||
name: g.name,
|
name: group.name,
|
||||||
avatar: g.avatar,
|
avatar: group.avatar,
|
||||||
muted: g.muted
|
muted: group.muted
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 刷新单个群详情 */
|
/** 单群刷新:用 /im/group/get 拉一份最新元数据再 upsert,常用于 GROUP_UPDATE 推送后或手动 reload */
|
||||||
async loadGroupInfo(groupId: number) {
|
async loadGroupInfo(groupId: number) {
|
||||||
try {
|
try {
|
||||||
const data = await apiGetGroup(groupId)
|
const data = await apiGetGroup(groupId)
|
||||||
|
|
@ -71,24 +86,39 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 按群拉取成员(带缓存,force=true 强制刷新) */
|
/**
|
||||||
|
* 按群拉取成员(带缓存,force=true 强制刷新)
|
||||||
|
*
|
||||||
|
* 1. 缓存:group 已加载且 members 就绪 → 直接返回
|
||||||
|
* 2. 拉取 + 转换:调 /im/group-member/list 后映射成本地 GroupMember
|
||||||
|
* 3. 回填当前用户的 muted:
|
||||||
|
* 后端只在成员维度返回 muted(apiGetMyGroupList 不带),借这次拉成员把它落到 group / conversation;
|
||||||
|
* 否则冷启动 / 清缓存后,服务端已免打扰的群在会话列表里仍显示为未免打扰
|
||||||
|
* 4. 落地(关键:race-safe 重新 getGroup):
|
||||||
|
* apiGetGroupMemberList 期间 loadGroups 可能已经把真实 group 填进 store,
|
||||||
|
* 沿用入口快照会让我们错走 4.1 分支、把真实 name 覆盖成 String(groupId)
|
||||||
|
* 4.1 group 还没就位(loadGroupMembers 跑在 loadGroups 之前)→ 占位 upsertGroup
|
||||||
|
* 4.2 group 已就位 → 直接写 members 字段,并把 muted 单独推回 conversation
|
||||||
|
*/
|
||||||
async loadGroupMembers(groupId: number, force = false): Promise<GroupMember[]> {
|
async loadGroupMembers(groupId: number, force = false): Promise<GroupMember[]> {
|
||||||
// 命中缓存:群已加载且成员列表已就绪,直接返回(force=true 时强制刷)
|
// 1. 缓存
|
||||||
const group = this.getGroup(groupId)
|
const cached = this.getGroup(groupId)
|
||||||
if (group && group.members && !force) {
|
if (cached && cached.members && !force) {
|
||||||
return group.members
|
return cached.members
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拉取该群所有成员(聚合自 AdminUser,含 nickname / avatar / displayUserName)
|
// 2. 拉取 + 转换
|
||||||
const list = await apiGetGroupMemberList(groupId)
|
const list = await apiGetGroupMemberList(groupId)
|
||||||
const members = (list || []).map((member) => convertGroupMember(member, groupId))
|
const members = (list || []).map((member) => convertGroupMember(member, groupId))
|
||||||
// 后端只在成员维度返回当前用户的 muted(apiGetMyGroupList 不带),借这次拉成员一起回填
|
|
||||||
// 否则冷启动 / 清缓存后,服务端已免打扰的群在会话列表里仍显示为未免打扰
|
// 3. 回填 muted
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const currentUserId = Number(userStore.getUser?.id) || 0
|
const currentUserId = Number(userStore.getUser?.id) || 0
|
||||||
const me = members.find((m) => m.userId === currentUserId)
|
const me = members.find((m) => m.userId === currentUserId)
|
||||||
const muted = !!me?.muted
|
const muted = !!me?.muted
|
||||||
// 成员列表可能在群列表之前触发,此时需要占位一个 group
|
|
||||||
|
// 4. 落地(必须 await 之后重新 getGroup,避免踩 race)
|
||||||
|
const group = this.getGroup(groupId)
|
||||||
if (!group) {
|
if (!group) {
|
||||||
this.upsertGroup({
|
this.upsertGroup({
|
||||||
id: groupId,
|
id: groupId,
|
||||||
|
|
@ -101,22 +131,20 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
group.members = members
|
group.members = members
|
||||||
group.memberCount = members.length
|
group.memberCount = members.length
|
||||||
group.muted = muted
|
group.muted = muted
|
||||||
// 已有 group 分支没走 upsertGroup,单独把 muted 推回 conversation 保证会话列表展示一致
|
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
conversationStore.updateConversation(ImConversationType.GROUP, groupId, { muted })
|
conversationStore.updateConversation(ImConversationType.GROUP, groupId, { muted })
|
||||||
}
|
}
|
||||||
return members
|
return members
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 按 id 插入或合并群(命中则浅合并保留旧字段,未命中则追加),同步把 name / avatar / muted 推到对应会话 */
|
||||||
upsertGroup(group: Group) {
|
upsertGroup(group: Group) {
|
||||||
// 按 id 查已有记录下标:>=0 命中则覆盖合并,<0 则追加
|
|
||||||
const index = this.groups.findIndex((g) => g.id === group.id)
|
const index = this.groups.findIndex((g) => g.id === group.id)
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
this.groups[index] = { ...this.groups[index], ...group }
|
this.groups[index] = { ...this.groups[index], ...group }
|
||||||
} else {
|
} else {
|
||||||
this.groups.push(group)
|
this.groups.push(group)
|
||||||
}
|
}
|
||||||
// 同步对应群聊会话的展示
|
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
conversationStore.updateConversation(ImConversationType.GROUP, group.id, {
|
conversationStore.updateConversation(ImConversationType.GROUP, group.id, {
|
||||||
name: group.name,
|
name: group.name,
|
||||||
|
|
@ -125,16 +153,15 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 本地移除(WebSocket GROUP_DEL 事件触发;同时级联清群聊会话) */
|
/** 本地移除(由 WebSocket GROUP_DEL 事件触发) */
|
||||||
removeGroup(id: number) {
|
removeGroup(id: number) {
|
||||||
// 直接从本地列表里移除(群解散是硬删,不留墓碑,区别于好友的软删)
|
// 群解散是硬删(区别于好友删除的软删保留记录);级联清群聊会话避免列表里留死群
|
||||||
this.groups = this.groups.filter((g) => g.id !== id)
|
this.groups = this.groups.filter((g) => g.id !== id)
|
||||||
// 级联清理:对应群聊会话也软删,避免会话列表里留着已解散的群
|
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
conversationStore.removeGroupConversation(id)
|
conversationStore.removeGroupConversation(id)
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 切换免打扰:调 /im/group-member/update 推后端,再把当前用户在该群的 muted 标记落到本地 */
|
/** 切换免打扰:推后端 + 落本地 */
|
||||||
async setMuted(id: number, muted: boolean) {
|
async setMuted(id: number, muted: boolean) {
|
||||||
await apiUpdateGroupMember({ groupId: id, muted })
|
await apiUpdateGroupMember({ groupId: id, muted })
|
||||||
const group = this.getGroup(id)
|
const group = this.getGroup(id)
|
||||||
|
|
@ -176,3 +203,8 @@ function convertGroupMember(member: ImGroupMemberRespVO, groupId: number): Group
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGroupStoreWithOut = () => useGroupStore(store)
|
export const useGroupStoreWithOut = () => useGroupStore(store)
|
||||||
|
|
||||||
|
// dev: 让 Pinia 的 actions 改动支持 HMR,免去每次改 store 都要硬刷
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useGroupStore, import.meta.hot))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
import { reactive } from 'vue'
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
import type { UserInfo } from '../types'
|
import type { UserInfo } from '../types'
|
||||||
|
|
@ -80,3 +80,9 @@ export const useImUiStore = defineStore('imUiStore', () => {
|
||||||
closeContextMenu
|
closeContextMenu
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// dev: 让 Pinia 的 actions / state 改动支持 HMR,避免每次改 store 都得硬刷
|
||||||
|
// 否则 Vite 把新模块推下来后,老 store 实例的 action 闭包仍指向旧函数体
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useImUiStore, import.meta.hot))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
import { getRefreshToken } from '@/utils/auth'
|
import { getRefreshToken } from '@/utils/auth'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
@ -582,6 +582,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
}, 5000)
|
}, 5000)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 停心跳:disconnect / 重连前调,避免老 timer 在新 socket 上继续触发 sendHeartBeat */
|
||||||
stopHeartbeat() {
|
stopHeartbeat() {
|
||||||
if (this.heartbeatTimer) {
|
if (this.heartbeatTimer) {
|
||||||
clearInterval(this.heartbeatTimer)
|
clearInterval(this.heartbeatTimer)
|
||||||
|
|
@ -594,3 +595,9 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
export const useImWebSocketStoreWithOut = () => {
|
export const useImWebSocketStoreWithOut = () => {
|
||||||
return useImWebSocketStore(store)
|
return useImWebSocketStore(store)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dev: 让 Pinia 的 actions / state 改动支持 HMR,避免每次改 store 都得硬刷
|
||||||
|
// 否则 Vite 把新模块推下来后,老 store 实例的 action 闭包仍指向旧函数体
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useImWebSocketStore, import.meta.hot))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ export interface Friend {
|
||||||
nickname: string // 好友昵称
|
nickname: string // 好友昵称
|
||||||
avatar?: string // 好友头像
|
avatar?: string // 好友头像
|
||||||
muted?: boolean // 是否免打扰(不展示未读徽标 + 不响提示音)
|
muted?: boolean // 是否免打扰(不展示未读徽标 + 不响提示音)
|
||||||
status?: number // 好友状态,对齐 CommonStatusEnum(DISABLE = 已删除/墓碑)
|
status?: number // 好友状态,对齐 CommonStatusEnum(DISABLE = 已删除,软删保留记录)
|
||||||
addTime?: number // 添加好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
addTime?: number // 添加好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
||||||
deleteTime?: number // 删除好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
deleteTime?: number // 删除好友时间(毫秒时间戳;后端为 LocalDateTime 字符串,在 convertFriend 转换)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,3 +73,14 @@ export const GROUP_MESSAGE_PULL_SIZE = 100
|
||||||
|
|
||||||
/** 会话之间插入"时间分隔线"的阈值:10 分钟 */
|
/** 会话之间插入"时间分隔线"的阈值:10 分钟 */
|
||||||
export const TIME_TIP_GAP_MS = 10 * 60 * 1000
|
export const TIME_TIP_GAP_MS = 10 * 60 * 1000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @全体成员 的特殊 userId 标识:atUserIds 中包含 -1 表示 @ 全体成员
|
||||||
|
*
|
||||||
|
* 与后端约定:群消息 atUserIds 数组里出现 -1 时,所有成员都收到提醒
|
||||||
|
* MentionPicker 渲染虚拟项 + conversationStore.applyAt 判定 atAll 都靠这个值
|
||||||
|
*/
|
||||||
|
export const IM_AT_ALL_USER_ID = -1
|
||||||
|
|
||||||
|
/** @全体成员 的展示名(对齐微信 PC) */
|
||||||
|
export const IM_AT_ALL_NICKNAME = '所有人'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue