24 分钟以前 46b2ae1c889e181ebe186de3758fc09804696029
src/views/im/home/composables/useMuteOverlay.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
import { computed, type ComputedRef, onScopeDispose, ref } from 'vue'
import { getCurrentUserId } from '#/views/im/utils/auth'
import { ImConversationType, ImGroupMemberRole } from '../../utils/constants'
import { isGroupQuit } from '../../utils/user'
import { useConversationStore } from '../store/conversationStore'
import { useGroupStore } from '../store/groupStore'
export type MuteOverlayInfo = { icon: string; text: string; }
/**
 * æ¨¡å—级共享 now tick + è®¢é˜…计数:
 * MessageItem v-for æ—¶æ¯æ¡æ¶ˆæ¯éƒ½ä¼š useMuteOverlay(),单实例自起 timer ä¼šå˜æˆå‡ ç™¾ä¸ª 30s å®šæ—¶å™¨ï¼›
 * æ”¹æˆæ¨¡å—级共享后所有订阅者共用一份 setInterval,订阅数清零时也清掉 timer,避免内存与时钟漂移
 */
const sharedNow = ref(Date.now())
let sharedTickTimer: null | number = null
let subscriberCount = 0
function subscribeNowTick(): void {
  subscriberCount++
  if (!sharedTickTimer) {
    sharedTickTimer = window.setInterval(() => {
      sharedNow.value = Date.now()
    }, 30_000)
  }
}
function unsubscribeNowTick(): void {
  subscriberCount--
  if (subscriberCount <= 0) {
    subscriberCount = 0
    if (sharedTickTimer) {
      window.clearInterval(sharedTickTimer)
      sharedTickTimer = null
    }
  }
}
/**
 * å½“前激活会话的禁言 / å°ç¦çŠ¶æ€ï¼›éžç¾¤èŠæˆ–æ— ç¦è¨€æ—¶è¿”å›ž null
 *
 * ä¼˜å…ˆçº§ï¼šå°ç¦ > å…¨ç¾¤ç¦è¨€ï¼ˆç¾¤ä¸» / ç®¡ç†å‘˜è±å…ï¼‰> æˆå‘˜ç¦è¨€
 *
 * MessageInput é¡¶éƒ¨ overlay / handleResend é‡è¯• / uploadAndSendMedia ä¸Šä¼ å®Œå‰éƒ½å…±ç”¨ä¸€ä»½ï¼Œ
 * é¿å…ã€Œè¾“入框拦了但重试绕过」「上传期间被禁言但 sendRaw ä»å‘出去」之类的不一致
 */
export function useMuteOverlay(): ComputedRef<MuteOverlayInfo | null> {
  const conversationStore = useConversationStore()
  const groupStore = useGroupStore()
  // è®¢é˜…模块级 tick;scope é”€æ¯æ—¶åè®¢é˜…,最后一个订阅者退场后 timer ä¹Ÿè·Ÿç€æ¸…
  subscribeNowTick()
  onScopeDispose(unsubscribeNowTick)
  return computed(() => {
    const conversation = conversationStore.activeConversation
    if (!conversation || conversation.type !== ImConversationType.GROUP) {
      return null
    }
    const group = groupStore.getGroup(conversation.targetId)
    if (!group) {
      return null
    }
    // åŽ†å²é€€ç¾¤ç¾¤ï¼šå·²é€€ç¾¤åªèƒ½æŸ¥çœ‹åŽ†å²ï¼Œç¦æ­¢å‘é€ï¼ˆæ–‡æœ¬ / å›¾ç‰‡ / æ–‡ä»¶ / è¯­éŸ³ / é‡è¯•共用这一层拦截)
    if (isGroupQuit(group)) {
      return { text: '你已退出群聊,仅可查看历史消息', icon: 'ant-design:logout-outlined' }
    }
    const myId = getCurrentUserId()
    // ç¾¤å°ç¦ï¼šç®¡ç†åŽå°æ“ä½œï¼Œæ‰€æœ‰äººä¸å¯å‘送
    if (group.banned) {
      return { text: '该群已被管理员封禁,无法发送消息', icon: 'ant-design:stop-outlined' }
    }
    // å…¨ç¾¤ç¦è¨€ï¼šç¾¤ä¸»èµ° ownerUserId æ¯”较直接豁免;其它人需要成员列表加载完才能区分管理员 vs æ™®é€šæˆå‘˜
    // - åŠ è½½å®Œ + æˆ‘是管理员 â†’ è±å…
    // - åŠ è½½å®Œ + æˆ‘不是管理员(含已退群)→ æ‹¦
    // - åŠ è½½æœªå®Œ â†’ ä¸æ˜¾ç¤º overlay,后端兜底拒绝普通成员;避免误拦管理员
    if (group.mutedAll && myId !== group.ownerUserId && group.membersLoaded) {
      const myMember = group.members?.find((m) => m.userId === myId)
      if (myMember?.role !== ImGroupMemberRole.ADMIN) {
        return { text: '全群禁言中,暂时无法发送消息', icon: 'ant-design:audio-muted-outlined' }
      }
    }
    // æˆå‘˜ç¦è¨€ï¼šmuteEndTime åœ¨æœªæ¥æ‰ç®—;用响应式 sharedNow æ¯”对,到期后下一个 tick å°±è®© overlay æ¶ˆå¤±
    const myMember = group.members?.find((m) => m.userId === myId)
    if (myMember?.muteEndTime) {
      const endTime = new Date(myMember.muteEndTime)
      if (endTime.getTime() > sharedNow.value) {
        const pad = (n: number) => n.toString().padStart(2, '0')
        const timeStr =
          `${pad(endTime.getMonth() + 1)}-${pad(endTime.getDate())} ` +
          `${pad(endTime.getHours())}:${pad(endTime.getMinutes())}`
        return {
          text: `您已被禁言,解除时间:${timeStr}`,
          icon: 'ant-design:audio-muted-outlined'
        }
      }
    }
    return null
  })
}