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/picker/group-member-picker-panel.vue | 242 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 242 insertions(+), 0 deletions(-)
diff --git a/src/views/im/home/components/picker/group-member-picker-panel.vue b/src/views/im/home/components/picker/group-member-picker-panel.vue
new file mode 100644
index 0000000..4a80f4a
--- /dev/null
+++ b/src/views/im/home/components/picker/group-member-picker-panel.vue
@@ -0,0 +1,242 @@
+<script lang="ts" setup>
+import type { GroupMemberLite } from '../group'
+
+import { computed, ref } from 'vue'
+
+import { CommonStatusEnum } from '#/packages/constants/src'
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+
+import { Input, message } from 'ant-design-vue'
+
+import { PagedScroller } from '..'
+import { useSelectedItems } from '../../composables/useSelectedItems'
+import { GroupMemberGrid, GroupMemberItem } from '../group'
+import { UserAvatar } from '../user'
+
+defineOptions({ name: 'ImGroupMemberPickerPanel' })
+
+const props = withDefaults(
+ defineProps<{
+ /** 绂佺敤 userId锛氬垪琛ㄩ噷灞曠ず缃伆銆佷笉鍙嬀閫夈�佷笉璁″叆宸查�夋暟 */
+ disabledIds?: number[]
+ /** 闅愯棌 userId锛氫笉灞曠ず锛坔ide > locked > disabled锛� */
+ hideIds?: number[]
+ /** 閿佸畾 userId锛氶粯璁ゅ嬀閫夈�佷笉鍙彇娑堛�佽鍏ュ凡閫夋暟 */
+ lockedIds?: number[]
+ /** 宸查�夋暟涓婇檺锛涗笉浼犳垨 <=0 鏃朵笉闄� */
+ maxSize?: number
+ /** 缇ゆ垚鍛樺垪琛� */
+ members: GroupMemberLite[]
+ /** 宸查�夊尯灞曠ず褰㈡�侊細榛樿 list 瀵归綈寰俊鏂拌瑙� */
+ selectedDisplay?: 'grid' | 'list'
+ /** 宸查�� userId锛坴-model锛夛紱鎸夋暟缁勯『搴忓嵆涓虹偣鍑婚『搴� */
+ selectedIds: number[]
+ }>(),
+ {
+ lockedIds: () => [],
+ disabledIds: () => [],
+ hideIds: () => [],
+ maxSize: 0,
+ selectedDisplay: 'list'
+ }
+)
+
+const emit = defineEmits<{
+ 'update:selectedIds': [value: number[]]
+}>()
+
+const keyword = ref('')
+
+/** userId 鈫� member 鏄犲皠锛屽凡閫夊弽鏌� / 涓夋�佸垽瀹氬叡鐢� */
+const byId = computed(() => {
+ const map = new Map<number, GroupMemberLite>()
+ for (const member of props.members) {
+ map.set(member.userId, member)
+ }
+ return map
+})
+
+const hideSet = computed(() => new Set(props.hideIds))
+const lockedSet = computed(() => new Set(props.lockedIds))
+const disabledSet = computed(() => new Set(props.disabledIds))
+const selectedSet = computed(() => new Set(props.selectedIds))
+
+/** 褰撳墠灞曠ず鐨勬垚鍛橈細鍓旈櫎 hideIds銆佸墧闄ゅ凡閫�缇わ紙DISABLE锛夈�佹寜鍏抽敭瀛楀ぇ灏忓啓鏃犲叧杩囨护 */
+const shownMembers = computed(() => {
+ const keywordLower = keyword.value.trim().toLowerCase()
+ return props.members.filter(
+ (member) =>
+ !hideSet.value.has(member.userId) &&
+ member.status !== CommonStatusEnum.DISABLE &&
+ (!keywordLower || member.showName.toLowerCase().includes(keywordLower))
+ )
+})
+
+/** 宸查�夋暟 + 宸查�夋垚鍛樺垪琛細涓夋�佷紭鍏堢骇 + 椤哄簭鎷兼帴鐢� useSelectedItems 缁熶竴鎵挎媴 */
+const { selectedCount, selectedItems: selectedMembers } = useSelectedItems<GroupMemberLite>(
+ () => props.selectedIds,
+ () => props.lockedIds,
+ () => props.disabledIds,
+ () => props.hideIds,
+ byId
+)
+
+/** 鏄惁琚攣瀹� */
+function isLocked(member: GroupMemberLite): boolean {
+ return lockedSet.value.has(member.userId)
+}
+
+/** 鏄惁琚鐢細locked / hide 宸茶鍓嶇疆杩囨护锛屽墿涓嬬殑鎵嶇畻 disabled */
+function isDisabled(member: GroupMemberLite): boolean {
+ return !lockedSet.value.has(member.userId) && disabledSet.value.has(member.userId)
+}
+
+/** 鏄惁閫変腑锛歭ocked 瑙嗕负姘歌繙閫変腑 */
+function isSelected(member: GroupMemberLite): boolean {
+ return selectedSet.value.has(member.userId)
+}
+
+/** 鍦嗗舰鍕鹃�夋寚绀哄櫒鐨� class */
+function getCheckClass(member: GroupMemberLite): string {
+ if (isLocked(member) || isSelected(member)) {
+ return 'bg-[#07c160] border border-solid border-[#07c160]'
+ }
+ if (isDisabled(member)) {
+ return 'bg-[var(--ant-color-fill)] border border-solid border-[var(--ant-color-border)]'
+ }
+ return 'border border-solid border-[var(--ant-color-border)] bg-[var(--ant-color-bg-container)]'
+}
+
+/** 鍒囨崲閫変腑鎬侊細locked / disabled 涓嶅搷搴旓紱鍙虫爮 脳 / 琛� click 閮借蛋杩欓噷 */
+function handleToggle(member: GroupMemberLite) {
+ if (isLocked(member) || isDisabled(member)) {
+ return
+ }
+ const next = [...props.selectedIds]
+ const index = next.indexOf(member.userId)
+ if (index === -1) {
+ if (props.maxSize > 0 && selectedCount.value >= props.maxSize) {
+ message.error(`鏈�澶氶�夋嫨 ${props.maxSize} 浣嶆垚鍛榒)
+ return
+ }
+ next.push(member.userId)
+ } else {
+ next.splice(index, 1)
+ }
+ emit('update:selectedIds', next)
+}
+</script>
+
+<template>
+ <!--
+ 缇ゆ垚鍛橀�夋嫨闈㈡澘锛氱敤浜庛�岀Щ闄ゆ垚鍛� / 璁剧疆绠$悊鍛� / 杞缇や富 / 绂佽█鎴愬憳銆嶇瓑鍦烘櫙
+ - 宸︼細鎼滅储 + 缇ゆ垚鍛樺垪琛紙鍦嗗舰鍕鹃�夛級
+ - 鍙筹細宸查�夋暟鏍囬 + 宸查�夋垚鍛橈紙list / grid 涓ょ褰㈡�佸彲閰嶏紝榛樿 list 瀵归綈寰俊鏂拌瑙夛級
+ - Panel 涓嶅甫 el-dialog 澹筹紱dialog 鐢变笟鍔″3鎸佹湁
+ - 涓夋�佽涔夛細hide > locked > disabled
+ -->
+ <div class="flex h-full">
+ <!-- 宸︽爮 -->
+ <div
+ class="flex flex-col flex-1 min-w-0 border-r border-r-solid border-[var(--ant-color-border-secondary)] bg-[var(--ant-color-fill-secondary)]"
+ >
+ <!-- 鎼滅储妗� -->
+ <div class="flex-shrink-0 px-3 py-2">
+ <Input v-model:value="keyword" placeholder="鎼滅储鎴愬憳" allow-clear>
+ <template #prefix>
+ <Icon icon="ant-design:search-outlined" />
+ </template>
+ </Input>
+ </div>
+
+ <div class="flex-1 min-h-0">
+ <PagedScroller :items="shownMembers" :page-size="30" item-key="userId">
+ <template #default="{ item }">
+ <GroupMemberItem
+ :member="item as GroupMemberLite"
+ :height="46"
+ @click="handleToggle(item as GroupMemberLite)"
+ >
+ <span
+ class="flex flex-shrink-0 justify-center items-center w-5 h-5 rounded-full transition-colors"
+ :class="getCheckClass(item as GroupMemberLite)"
+ >
+ <Icon
+ v-if="isSelected(item as GroupMemberLite) || isLocked(item as GroupMemberLite)"
+ icon="ant-design:check-outlined"
+ :size="12"
+ color="#fff"
+ />
+ </span>
+ </GroupMemberItem>
+ </template>
+ </PagedScroller>
+ </div>
+ </div>
+
+ <!-- 鍙虫爮 -->
+ <div class="flex flex-col flex-1 min-w-0">
+ <!-- 鏍囬锛氬凡閫夋暟锛涢珮搴﹀榻愬乏渚� input default锛�32px锛� -->
+ <div
+ class="flex-shrink-0 h-12 px-4 leading-[3rem] border-b border-b-solid text-13px text-[var(--ant-color-text-secondary)] border-[var(--ant-color-border-secondary)]"
+ >
+ 宸查�夋嫨 {{ selectedCount }} 浣嶆垚鍛�
+ </div>
+
+ <div class="flex-1">
+ <!-- list 褰㈡�侊細绾靛悜琛岋紝姣忚甯� 脳 绉婚櫎锛坙ocked 涓嶆覆鏌� 脳锛� -->
+ <template v-if="selectedDisplay === 'list'">
+ <div
+ v-for="member in selectedMembers"
+ :key="member.userId"
+ class="flex gap-2.5 items-center px-4 py-2"
+ >
+ <UserAvatar
+ :id="member.userId"
+ :url="member.avatar"
+ :name="member.nickname"
+ :size="36"
+ :clickable="false"
+ />
+ <span
+ class="flex-1 min-w-0 overflow-hidden text-sm truncate text-[var(--ant-color-text)]"
+ >
+ {{ member.showName }}
+ </span>
+ <Icon
+ v-if="!isLocked(member)"
+ icon="ant-design:close-outlined"
+ :size="14"
+ class="flex-shrink-0 cursor-pointer transition-colors text-[var(--ant-color-text-placeholder)] hover:text-[var(--ant-color-error)]"
+ @click="handleToggle(member)"
+ />
+ </div>
+ </template>
+
+ <!-- grid 褰㈡�侊細瀹牸棰勮锛涢潪 locked 鎴愬憳鍙充笂瑙掑彔鍔� 脳 绉婚櫎锛坙ocked 涓嶆覆鏌擄級 -->
+ <div v-else class="flex flex-wrap p-2.5">
+ <GroupMemberGrid
+ v-for="member in selectedMembers"
+ :key="member.userId"
+ :member="member"
+ >
+ <Icon
+ v-if="!isLocked(member)"
+ icon="ant-design:close-circle-filled"
+ :size="16"
+ class="absolute top-0 right-0 cursor-pointer transition-colors text-[var(--ant-color-text-placeholder)] hover:text-[var(--ant-color-error)]"
+ @click="handleToggle(member)"
+ />
+ </GroupMemberGrid>
+ </div>
+
+ <div
+ v-if="selectedMembers.length === 0"
+ class="py-10 text-13px text-center text-[var(--ant-color-text-disabled)]"
+ >
+ 璇蜂粠宸︿晶閫夋嫨鎴愬憳
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
--
Gitblit v1.9.3