| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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ï¼ä¸å±ç¤ºï¼hide > locked > disabledï¼ */ |
| | | hideIds?: number[] |
| | | /** éå® userIdï¼é»è®¤å¾éãä¸å¯åæ¶ã计å
¥å·²éæ° */ |
| | | lockedIds?: number[] |
| | | /** 已鿰ä¸éï¼ä¸ä¼ æ <=0 æ¶ä¸é */ |
| | | maxSize?: number |
| | | /** 群æåå表 */ |
| | | members: GroupMemberLite[] |
| | | /** å·²éåºå±ç¤ºå½¢æï¼é»è®¤ list 对é½å¾®ä¿¡æ°è§è§ */ |
| | | selectedDisplay?: 'grid' | 'list' |
| | | /** å·²é userIdï¼v-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) |
| | | } |
| | | |
| | | /** æ¯å¦éä¸ï¼locked è§ä¸ºæ°¸è¿éä¸ */ |
| | | 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 ç±ä¸å¡å£³ææ |
| | | - 䏿è¯ä¹ï¼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 å½¢æï¼çºµåè¡ï¼æ¯è¡å¸¦ à 移é¤ï¼locked 䏿¸²æ Ãï¼ --> |
| | | <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 æåå³ä¸è§å å à 移é¤ï¼locked 䏿¸²æï¼ --> |
| | | <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> |