From 27cd042df9aca0383a49f3514bc21958dd890912 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 六月 2026 15:42:23 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加IM即时通讯模块

---
 src/views/im/home/components/context-menu.vue |   99 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 99 insertions(+), 0 deletions(-)

diff --git a/src/views/im/home/components/context-menu.vue b/src/views/im/home/components/context-menu.vue
new file mode 100644
index 0000000..df364c9
--- /dev/null
+++ b/src/views/im/home/components/context-menu.vue
@@ -0,0 +1,99 @@
+<script lang="ts" setup>
+import { computed } from 'vue'
+
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+
+import { useImUiStore } from '../store/uiStore'
+
+defineOptions({ name: 'ImContextMenu' })
+
+const uiStore = useImUiStore()
+const contextMenu = computed(() => uiStore.contextMenu)
+
+/**
+ * 璁$畻鑿滃崟瀹為檯娓叉煋鍧愭爣锛氶潬杩戣鍙e彸 / 涓嬭竟缂樻椂鍥炲脊锛岄伩鍏嶈彍鍗曡瑁佸壀
+ *
+ * itemHeight / menuWidth 鏄拰妯℃澘閲� px-4 py-2 + text-13px / min-w-30 閰嶅鐨勫疄闄呭昂瀵革紱
+ * dividerHeight = 9px锛坢y-1 涓婁笅鍚� 4 + 1px border锛夛紝浠呴潪棣栭」鐨� divided 璁″叆锛�
+ * menuHeight 棰濆鍔� 8 鏄灞� py-1 鐨勪笂涓� padding 涔嬪拰锛�4px 脳 2锛�
+ */
+const adjustedPosition = computed(() => {
+  const items = contextMenu.value.items
+  const itemHeight = 34
+  const dividerCount = items.filter((it, i) => it.divided && i > 0).length
+  const menuHeight = items.length * itemHeight + dividerCount * 9 + 8
+  const menuWidth = 120
+  let x = contextMenu.value.position.x
+  let y = contextMenu.value.position.y
+  // SSR 鍏滃簳锛歸indow 涓嶅彲鐢ㄦ椂鐩存帴杩斿洖鍘熷鍧愭爣
+  if (typeof window !== 'undefined') {
+    if (y + menuHeight > window.innerHeight) {
+      y = window.innerHeight - menuHeight
+    }
+    if (x + menuWidth > window.innerWidth) {
+      x = window.innerWidth - menuWidth
+    }
+  }
+  // 瑙嗗彛寰堝皬 / 鑿滃崟椤瑰緢澶氭椂涓婇潰鍑忔硶浼氱畻鍑鸿礋鍊硷紝鎶婅彍鍗曢《 / 宸﹁竟鎺ㄥ埌 0 鍏滃簳
+  return { x: Math.max(0, x), y: Math.max(0, y) }
+})
+
+type MenuItem = (typeof contextMenu.value.items)[number]
+
+/** 閫変腑鑿滃崟椤癸細disabled 椤瑰拷鐣ワ紱姝e父椤硅皟 onSelect 鍥炶皟鍚庡叧闂彍鍗� */
+function handleSelect(item: MenuItem) {
+  if (item.disabled) {
+    return
+  }
+  uiStore.contextMenu.onSelect?.(item)
+  uiStore.closeContextMenu()
+}
+
+/** 鍏抽棴鑿滃崟锛氱偣閬僵 / 鍦ㄩ伄缃╀笂鍐嶆鍙抽敭閮戒細瑙﹀彂 */
+function handleClose() {
+  uiStore.closeContextMenu()
+}
+</script>
+
+<template>
+  <!--
+    閫氱敤鍙抽敭鑿滃崟
+    鐢� useImUiStore.openContextMenu(position, items, onSelect) 瑙﹀彂鍏ㄥ眬鍗曚緥灞曠ず
+    璋冪敤鏂瑰湪 @contextmenu.prevent 浜嬩欢閲岃皟 openContextMenu 鍗冲彲锛屼笉闇�瑕佽嚜宸辨寕缁勪欢
+  -->
+  <teleport to="body">
+    <div
+      v-if="contextMenu.show"
+      class="fixed inset-0 z-9999"
+      @click.stop="handleClose"
+      @contextmenu.prevent="handleClose"
+    >
+      <div
+        class="fixed min-w-30 py-1 bg-[var(--ant-color-bg-elevated)] rounded-md shadow-lg"
+        :style="{ left: `${adjustedPosition.x }px`, top: `${adjustedPosition.y }px` }"
+      >
+        <template v-for="(item, index) in contextMenu.items" :key="item.key">
+          <!-- divided 椤逛笂鏂规彃涓�鏉″垎鍓茬嚎锛堥椤硅烦杩囷紝閬垮厤绌虹櫧锛� -->
+          <div
+            v-if="item.divided && index > 0"
+            class="my-1 mx-2 h-[1px] bg-[var(--ant-color-border-secondary)]"
+          ></div>
+          <div
+            class="flex gap-2 items-center px-4 py-2 text-13px text-left cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
+            :class="[
+              item.disabled
+                ? '!text-[var(--ant-color-text-disabled)] cursor-not-allowed hover:!bg-transparent'
+                : item.danger
+                  ? 'text-[#f56c6c]'
+                  : 'text-[var(--ant-color-text)]'
+            ]"
+            @click.stop="handleSelect(item)"
+          >
+            <Icon v-if="item.icon" :icon="item.icon" :size="14" />
+            <span>{{ item.name }}</span>
+          </div>
+        </template>
+      </div>
+    </div>
+  </teleport>
+</template>

--
Gitblit v1.9.3