gaoluyang
2 天以前 b64a0deae5b5d33f9e20671a68936b27f0b9b00b
src/views/im/home/store/uiStore.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,155 @@
import type { GroupLite, User } from '../types'
import { reactive, ref } from 'vue'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { ImFriendAddSource } from '../../utils/constants'
/**
 * IM å…¨å±€ UI store
 *
 * æ”¶çº³æ ‡å‡†ï¼šè§¦å‘点 N ä¸ªã€æŒ‚载点想保持 1 ä¸ªçš„æµ®å±‚状态。
 * ä»»æ„ä½ç½®éƒ½å¯èƒ½ open,但 DOM ä¸Šåªæƒ³ç•™ä¸€ä»½å®žä¾‹ â†’ èµ° store æ´¾å‘,
 * ç”± `index.vue` æŒ‚一个订阅组件统一渲染。
 */
export const useImUiStore = defineStore('imUiStore', () => {
  // ==================== ç”¨æˆ·åç‰‡ UserInfoCard ====================
  // ç”¨æˆ·åç‰‡æ‚¬æµ®å¡
  const userInfoCard = reactive({
    show: false,
    user: null as null | User,
    position: { x: 0, y: 0 },
    // addSource / addSourceExtra è·Ÿéšè§¦å‘点带入「加好友」来源(群成员入口 = GROUP + ç¾¤åï¼›å…¶ä½™é»˜è®¤æœç´¢ï¼‰
    addSource: ImFriendAddSource.SEARCH as number,
    addSourceExtra: '' as string
  })
  /** æ‰“开用户名片 */
  function openUserInfoCard(
    user: User,
    position: { x: number; y: number },
    addSource: number = ImFriendAddSource.SEARCH,
    addSourceExtra: string = ''
  ) {
    const viewportWidth = document.documentElement.clientWidth
    const viewportHeight = document.documentElement.clientHeight
    userInfoCard.user = user
    userInfoCard.position.x = Math.min(position.x, viewportWidth - 350)
    userInfoCard.position.y = Math.min(position.y, viewportHeight - 220)
    userInfoCard.addSource = addSource
    userInfoCard.addSourceExtra = addSourceExtra
    userInfoCard.show = true
  }
  /** é¼ æ ‡ç‚¹å‡»ä½ç½® + 20px æ¨ªå‘偏移打开名片:避免名片直接覆盖触发元素,对齐头像 / åç‰‡æ¶ˆæ¯ç­‰ç‚¹å‡»äº¤äº’的统一观感 */
  function openUserInfoCardAtEvent(
    user: User,
    e: MouseEvent,
    addSource: number = ImFriendAddSource.SEARCH,
    addSourceExtra: string = ''
  ) {
    openUserInfoCard(user, { x: e.clientX + 20, y: e.clientY }, addSource, addSourceExtra)
  }
  /** å…³é—­ç”¨æˆ·åç‰‡ */
  function closeUserInfoCard() {
    userInfoCard.show = false
  }
  // ==================== ç¾¤åç‰‡ GroupInfoCard ====================
  const groupInfoCard = reactive({
    show: false,
    group: null as GroupLite | null,
    position: { x: 0, y: 0 }
  })
  /** é¼ æ ‡ç‚¹å‡»ä½ç½® + 20px æ¨ªå‘偏移打开群名片,对齐 UserInfoCard çš„统一观感 */
  function openGroupInfoCardAtEvent(group: GroupLite, e: MouseEvent) {
    const viewportWidth = document.documentElement.clientWidth
    const viewportHeight = document.documentElement.clientHeight
    groupInfoCard.group = group
    groupInfoCard.position.x = Math.min(e.clientX + 20, viewportWidth - 350)
    groupInfoCard.position.y = Math.min(e.clientY, viewportHeight - 220)
    groupInfoCard.show = true
  }
  /** å…³é—­ç¾¤åç‰‡ */
  function closeGroupInfoCard() {
    groupInfoCard.show = false
  }
  // ==================== å³é”®èœå• ContextMenu ====================
  // å³é”®èœå•虽然是一个组件挂在主壳上,但其触发时机分散在各列表
  interface ContextMenuItem {
    key: string
    name: string
    disabled?: boolean
    divided?: boolean // æ˜¯å¦åœ¨è¯¥é¡¹ä¸Šæ–¹æ˜¾ç¤ºåˆ†å‰²çº¿ï¼ˆç”¨äºŽæŠŠ"删除"等危险操作与上面的常规项隔开)
    danger?: boolean // æ˜¯å¦èµ°å±é™©æ“ä½œæ ·å¼ï¼ˆçº¢è‰²æ–‡å­—)
    icon?: string // å¯é€‰ iconify å›¾æ ‡åï¼ˆå¦‚ ant-design:delete-outlined);不传则不渲染前置图标
  }
  const contextMenu = reactive({
    show: false,
    position: { x: 0, y: 0 },
    items: [] as ContextMenuItem[],
    /** é€‰ä¸­å›žè°ƒï¼šæ¯æ¬¡ open æ—¶ç”±è°ƒç”¨æ–¹ä¼ å…¥ */
    onSelect: null as ((item: ContextMenuItem) => void) | null
  })
  /** æ‰“开右键菜单 */
  function openContextMenu(
    position: { x: number; y: number },
    items: ContextMenuItem[],
    onSelect: (item: ContextMenuItem) => void
  ) {
    contextMenu.position = position
    contextMenu.items = items
    contextMenu.onSelect = onSelect
    contextMenu.show = true
  }
  /** å…³é—­å³é”®èœå• */
  function closeContextMenu() {
    contextMenu.show = false
    contextMenu.onSelect = null
  }
  // ==================== æ¶ˆæ¯ Tab è·³è½¬ä¸‹ä¸€æœªè¯» ====================
  // åœ¨ ImHomeConversation é¡µé¢å†æ¬¡ç‚¹å‡»å·¥å…·æ ã€Œæ¶ˆæ¯ã€æ—¶è§¦å‘ï¼›
  // é€šè¿‡é€’增 nonce è®© conversation/index.vue çš„ watch æ„ŸçŸ¥åŽæ‰§è¡Œæ»šåЍ + é«˜äº®
  const nextUnreadJumpNonce = ref(0)
  /** è¯·æ±‚滚动到下一个未读会话(含免打扰) */
  function requestNextUnreadJump() {
    nextUnreadJumpNonce.value++
  }
  return {
    userInfoCard,
    openUserInfoCard,
    openUserInfoCardAtEvent,
    closeUserInfoCard,
    groupInfoCard,
    openGroupInfoCardAtEvent,
    closeGroupInfoCard,
    contextMenu,
    openContextMenu,
    closeContextMenu,
    nextUnreadJumpNonce,
    requestNextUnreadJump
  }
})
// dev: è®© Pinia çš„ actions / state æ”¹åŠ¨æ”¯æŒ HMR,避免每次改 store éƒ½å¾—硬刷
// å¦åˆ™ Vite æŠŠæ–°æ¨¡å—推下来后,老 store å®žä¾‹çš„ action é—­åŒ…仍指向旧函数体
if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useImUiStore, import.meta.hot))
}