diff --git a/src/api/im/manager/group/index.ts b/src/api/im/manager/group/index.ts index ffcaf5b21..6c5588bac 100644 --- a/src/api/im/manager/group/index.ts +++ b/src/api/im/manager/group/index.ts @@ -51,6 +51,11 @@ export const unbanManagerGroup = (id: number) => { return request.put({ url: '/im/manager/group/unban?id=' + id }) } +// 解散群 +export const dissolveManagerGroup = (id: number) => { + return request.delete({ url: '/im/manager/group/dissolve?id=' + id }) +} + // 获得群成员列表(含已退群成员,由前端按需过滤) export const getManagerGroupMemberList = (groupId: number) => { return request.get({ url: '/im/manager/group/member/list?groupId=' + groupId }) diff --git a/src/views/im/home/components/picker/FriendPickerPanel.vue b/src/views/im/home/components/picker/FriendPickerPanel.vue index bb1372586..f613cd033 100644 --- a/src/views/im/home/components/picker/FriendPickerPanel.vue +++ b/src/views/im/home/components/picker/FriendPickerPanel.vue @@ -1,7 +1,7 @@ + +
{{ keyword ? '没有匹配的好友' : '暂无好友' }}
- + @@ -137,6 +127,7 @@ import Icon from '@/components/Icon/src/Icon.vue' import { useMessage } from '@/hooks/web/useMessage' import UserAvatar from '../user/UserAvatar.vue' +import PagedScroller from '../PagedScroller.vue' import { useFriendBuckets } from '../../composables/useFriendBuckets' import { useSelectedItems } from '../../composables/useSelectedItems' import type { FriendLite } from '../../types' @@ -194,8 +185,8 @@ const candidates = computed(() => props.friends.filter((friend) => !hideSet.value.has(friend.id)) ) -/** 委托 useFriendBuckets:搜索 + 字母分桶共用一套规则 */ -const { filtered, buckets } = useFriendBuckets(candidates, keyword) +/** 委托 useFriendBuckets:搜索规则复用,左侧列表按滚动分页渲染 */ +const { filtered } = useFriendBuckets(candidates, keyword) /** 已选数 + 已选好友列表:三态优先级 + 顺序拼接由 useSelectedItems 统一承担 */ const { selectedCount, selectedItems: selectedFriends } = useSelectedItems( @@ -251,4 +242,3 @@ function handleToggle(friend: FriendLite) { emit('update:selectedIds', next) } - diff --git a/src/views/im/home/components/rtc/RtcCallContainer.vue b/src/views/im/home/components/rtc/RtcCallContainer.vue index a3ce61552..48cb17406 100644 --- a/src/views/im/home/components/rtc/RtcCallContainer.vue +++ b/src/views/im/home/components/rtc/RtcCallContainer.vue @@ -369,12 +369,18 @@ function handlePeerDisconnected() { if (!rtcStore.isActive) { return } + const room = rtcStore.call?.room // 给 RTC_CALL_END WebSocket 推送一个小窗口;私聊超时 / 主动挂断等场景下,后端 endSession 会先推 RTC_CALL_END, // 让前端按业务语义("对方未接听" / "已取消" 等)reset,避免错把业务断开 toast 成「通话已断开」 setTimeout(() => { if (!rtcStore.isActive) { return } + // 上报离开房间 + if (room) { + leaveCall(room).catch(() => undefined) + } + // 清理本地通话状态 message.warning('通话已断开') rtcStore.reset() }, 100) diff --git a/src/views/im/home/components/rtc/RtcCallRunning.vue b/src/views/im/home/components/rtc/RtcCallRunning.vue index bb32aa574..a9aa670e2 100644 --- a/src/views/im/home/components/rtc/RtcCallRunning.vue +++ b/src/views/im/home/components/rtc/RtcCallRunning.vue @@ -75,8 +75,13 @@
{{ peerNickname }}
{{ formattedDuration }}
- + diff --git a/src/views/im/home/pages/conversation/components/input/FacePicker.vue b/src/views/im/home/pages/conversation/components/input/FacePicker.vue index 851bcca4c..5d089a507 100644 --- a/src/views/im/home/pages/conversation/components/input/FacePicker.vue +++ b/src/views/im/home/pages/conversation/components/input/FacePicker.vue @@ -280,6 +280,7 @@ async function onUploadPicked(e: Event) { return } uploading.value = true + let payload: { url: string; width: number; height: number } try { // probe 本地图片宽高 + 上传到 OSS 并行起跑(probe 通常远快于上传,几乎完全被遮蔽) const form = new FormData() @@ -293,13 +294,15 @@ async function onUploadPicked(e: Event) { ElMessage.error('上传失败') return } - const ok = await faceStore.addFaceUserItem({ url, width: size.width, height: size.height }) - if (!ok) { - ElMessage.error('添加失败,可能已添加过') - } + payload = { url, width: size.width, height: size.height } } catch (err) { console.warn('[IM] 上传个人表情失败', err) ElMessage.error('上传失败') + uploading.value = false + return + } + try { + await faceStore.addFaceUserItem(payload) } finally { uploading.value = false } diff --git a/src/views/im/home/pages/conversation/components/message/MessageItem.vue b/src/views/im/home/pages/conversation/components/message/MessageItem.vue index 70688a712..25246f74b 100644 --- a/src/views/im/home/pages/conversation/components/message/MessageItem.vue +++ b/src/views/im/home/pages/conversation/components/message/MessageItem.vue @@ -786,7 +786,7 @@ const canAddToFace = computed(() => { return extractAddableFace(props.message) !== null }) -/** 添加到个人表情:从 message 抽 url + 尺寸 + name 写入个人表情库;幂等失败时返回 false 走 toast 兜底 */ +/** 添加到个人表情:从 message 抽 url + 尺寸 + name 写入个人表情库 */ async function handleAddToFace() { const payload = extractAddableFace(props.message) if (!payload) { diff --git a/src/views/im/home/store/faceStore.ts b/src/views/im/home/store/faceStore.ts index 3fe789492..b5dc02388 100644 --- a/src/views/im/home/store/faceStore.ts +++ b/src/views/im/home/store/faceStore.ts @@ -88,34 +88,28 @@ export const useFaceStore = defineStore('imFace', () => { * 添加个人表情;服务端对同 URL 抛 FACE_USER_ITEM_DUPLICATED 错误 * * 来源:1. 用户在表情面板「+」上传图片 2. 长按消息「添加到表情」 - * 返回 true / false 与 removeFaceUserItem 风格对齐;调用方按 boolean 决定是否提示 */ async function addFaceUserItem(reqVO: ImFaceUserItemSaveReqVO): Promise { const requestEpoch = storeEpoch - try { - const id = await apiCreateFaceUserItem(reqVO) - if (!id) { - return false - } - // reset 已切账号:旧请求拿到的 id 不能再 unshift 进新账号内存 - if (requestEpoch !== storeEpoch) { - return false - } - // id 不在缓存里才插入;服务端唯一约束兜底了 race,本地理论上不会拿到重复 id - if (!faceUserItems.value.some((item) => item.id === id)) { - faceUserItems.value.unshift({ - id, - url: reqVO.url, - name: reqVO.name, - width: reqVO.width, - height: reqVO.height - }) - } - return true - } catch (e) { - console.warn('[IM] 添加个人表情失败', { reqVO }, e) + const id = await apiCreateFaceUserItem(reqVO) + if (!id) { return false } + // reset 已切账号:旧请求拿到的 id 不能再 unshift 进新账号内存 + if (requestEpoch !== storeEpoch) { + return false + } + // id 不在缓存里才插入;服务端唯一约束兜底了 race,本地理论上不会拿到重复 id + if (!faceUserItems.value.some((item) => item.id === id)) { + faceUserItems.value.unshift({ + id, + url: reqVO.url, + name: reqVO.name, + width: reqVO.width, + height: reqVO.height + }) + } + return true } /** 删除个人表情;本地立即移除 */ diff --git a/src/views/im/manager/group/index.vue b/src/views/im/manager/group/index.vue index 649188982..405db63cf 100644 --- a/src/views/im/manager/group/index.vue +++ b/src/views/im/manager/group/index.vue @@ -124,7 +124,7 @@ width="180" :formatter="dateFormatter" /> - + @@ -179,6 +188,7 @@