| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { Conversation, Friend } from '../../../../types' |
| | | |
| | | import { computed, ref, watch } from 'vue' |
| | | |
| | | import { IconifyIcon as Icon } from '#/packages/icons/src' |
| | | |
| | | import { Button, Drawer, Input, message, Popover, Spin, Switch } from 'ant-design-vue' |
| | | |
| | | import { useConversationStore } from '#/views/im/home/store/conversationStore' |
| | | import { useFriendStore } from '#/views/im/home/store/friendStore' |
| | | import { useGroupStore } from '#/views/im/home/store/groupStore' |
| | | import { ImConversationType } from '#/views/im/utils/constants' |
| | | import { getFriendDisplayName, getGroupDisplayName } from '#/views/im/utils/user' |
| | | |
| | | import { GroupCreateDialog } from '../../../../components/group' |
| | | import { UserAvatar } from '../../../../components/user' |
| | | |
| | | defineOptions({ name: 'ImConversationPrivateSide' }) |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | conversation?: Conversation | null // å½åä¼è¯ï¼å置顶 / å
ææ°æï¼ |
| | | friend?: Friend // 对æ¹å¥½åä¿¡æ¯ï¼å头å / æµç§°ï¼ |
| | | modelValue?: boolean // æ½å±å¼å
³ï¼v-modelï¼ |
| | | }>(), |
| | | { |
| | | conversation: null, |
| | | friend: undefined, |
| | | modelValue: false |
| | | } |
| | | ) |
| | | |
| | | const emit = defineEmits<{ |
| | | openHistory: [] // ç¹å» "æ¥æ¾è天å
容" è¡ â ç¶ç»ä»¶æå¼ MessageHistory å¼¹çª |
| | | 'update:modelValue': [value: boolean] |
| | | }>() |
| | | |
| | | const visible = computed({ |
| | | get: () => props.modelValue, |
| | | set: (value) => emit('update:modelValue', value) |
| | | }) |
| | | |
| | | const conversationStore = useConversationStore() |
| | | const friendStore = useFriendStore() |
| | | const groupStore = useGroupStore() |
| | | |
| | | /** tile æ ç¾ / åç»è天çé¢ç¨çå±ç¤ºåï¼å¤æ³¨ä¼å
*/ |
| | | const displayName = computed(() => (props.friend ? getFriendDisplayName(props.friend) : '')) |
| | | |
| | | const createGroupDialogRef = ref<InstanceType<typeof GroupCreateDialog>>() // å起群èå¼¹çª refï¼handleOpenCreateGroup è° open({ lockedIds }) éå®å¯¹æ¹ |
| | | |
| | | /** æå¼å起群èå¼¹çªï¼æå¯¹æ¹é»è®¤å¾ä¸ä¸ä¸å¯åæ¶ï¼å¯¹åºå¾®ä¿¡"åºäºç§èå起群è" */ |
| | | function handleOpenCreateGroup() { |
| | | const lockedIds = props.friend ? [props.friend.friendUserId] : [] |
| | | createGroupDialogRef.value?.open({ lockedIds }) |
| | | } |
| | | |
| | | const displayNamePopoverVisible = ref(false) |
| | | const editDisplayName = ref('') |
| | | |
| | | // popover å¼¹åºæ¶æå½å夿³¨çè¿ç¼è¾æï¼é¿å
䏿¬¡æªä¿åçèå¼ |
| | | watch(displayNamePopoverVisible, (open) => { |
| | | if (open) { |
| | | editDisplayName.value = props.friend?.displayName || '' |
| | | } |
| | | }) |
| | | |
| | | // æ½å±å
³éæ¶æè¿æ²¡æ¶æç popover ä¸å¹¶æ¸
æï¼é¿å
䏿¬¡æå¼éªä¸ä¸ |
| | | watch(visible, (open) => { |
| | | if (!open) { |
| | | displayNamePopoverVisible.value = false |
| | | } |
| | | }) |
| | | |
| | | /** 夿³¨ popover ç¹å»ä¿å */ |
| | | async function handleSaveDisplayName() { |
| | | if (!props.friend) { |
| | | return |
| | | } |
| | | await friendStore.setFriendDisplayName(props.friend.friendUserId, editDisplayName.value) |
| | | displayNamePopoverVisible.value = false |
| | | message.success('ä¿åæå') |
| | | } |
| | | |
| | | /** |
| | | * åå
ææ°ï¼ä¹è§åå conversationStore + friendStoreï¼åç«¯å¤±è´¥åæ» conversation ç¶æï¼ä¿æä¸ ConversationItem.handleMuted ä¸è´ |
| | | */ |
| | | function handleMutedChange(value: boolean | number | string) { |
| | | if (!props.conversation) { |
| | | return |
| | | } |
| | | const next = !!value |
| | | const { type, targetId } = props.conversation |
| | | conversationStore.setConversationSilent(type, targetId, next) |
| | | if (type !== ImConversationType.PRIVATE) { |
| | | return |
| | | } |
| | | friendStore.setFriendSilent(targetId, next).catch((error) => { |
| | | console.error('[IM ConversationPrivateSide] 忢å
ææ°å¤±è´¥', { targetId }, error) |
| | | conversationStore.setConversationSilent(type, targetId, !next) |
| | | }) |
| | | } |
| | | |
| | | /** å置顶ï¼çº¯æ¬å° conversationStore æåºæï¼æ åç«¯åæ®µï¼ */ |
| | | function handleTopChange(value: boolean | number | string) { |
| | | if (!props.conversation) { |
| | | return |
| | | } |
| | | conversationStore.setConversationTop(props.conversation.type, props.conversation.targetId, !!value) |
| | | } |
| | | |
| | | /** 群å建æåï¼è·³å°æ°ç¾¤ä¼è¯ + å
³ææ¬ä¾§æ½å±ï¼è®©ç¨æ·ä¸æ³¨æ°ç¾¤ */ |
| | | function handleGroupCreated(groupId: number) { |
| | | const group = groupStore.getGroup(groupId) |
| | | if (!group) { |
| | | return |
| | | } |
| | | conversationStore.openConversation( |
| | | groupId, |
| | | ImConversationType.GROUP, |
| | | getGroupDisplayName(group), |
| | | group.avatar || '', |
| | | { silent: !!group.silent } |
| | | ) |
| | | visible.value = false |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | | <!-- |
| | | ç§èä¾§è¾¹æ½å± |
| | | - æ´ä½ç»æå¯¹é½ ConversationGroupSideï¼å®«æ ¼ + ä¿¡æ¯è¡ + å¼å
³ |
| | | - é¡¶é¨å¥½åå®«æ ¼ + "+" tileï¼ç¹ + è°èµ· GroupCreateDialog å¹¶éå®å¯¹æ¹ï¼å¯¹é½å¾®ä¿¡"åºäºç§èå起群è" |
| | | - "æ¸
空è天记å½"æé®å¨ WeChat éæï¼ä½ç®ååç«¯æ²¡å»ºæ¶æ¯æ¸
空è½åï¼å
ä¸å é¿å
åååå |
| | | --> |
| | | <Drawer |
| | | v-model:open="visible" |
| | | :closable="false" |
| | | placement="right" |
| | | width="340px" |
| | | root-class-name="im-conversation-private-side__modal" |
| | | > |
| | | <!-- friend ç¼ºå¤±åºæ¯ï¼éç人ä¼è¯åæå¼ / 好忰æ®è¿æ²¡è¡¥æå°ï¼ç©ºç½ä¼è®©ç¨æ·ä»¥ä¸ºæ½å±åäºï¼ç»ä¸ªå è½½å ä½ --> |
| | | <div |
| | | v-if="!friend" |
| | | class="flex flex-col items-center justify-center h-full text-13px text-[var(--ant-color-text-placeholder)] bg-[var(--ant-color-bg-container)]" |
| | | > |
| | | <Spin tip="å è½½ä¸..." /> |
| | | </div> |
| | | <div v-else class="flex flex-col h-full bg-[var(--ant-color-bg-container)]"> |
| | | <div class="flex-1 overflow-y-auto bg-[var(--ant-color-fill-secondary)]"> |
| | | <!-- 好åå®«æ ¼ï¼å tile + "+" tileï¼å¯¹é½ GroupSide è§è§ï¼è®©ä¸¤ç§æ½å±çèµ·æ¥æ¯ä¸å®¶ç --> |
| | | <div class="flex flex-wrap gap-1 px-4 pt-4 pb-[14px] bg-[var(--ant-color-bg-container)]"> |
| | | <div class="flex flex-col items-center w-[66px]"> |
| | | <UserAvatar |
| | | :id="friend.friendUserId" |
| | | :url="friend.avatar" |
| | | :name="friend.nickname" |
| | | :size="50" |
| | | /> |
| | | <div class="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--ant-color-text)] text-center truncate"> |
| | | {{ displayName }} |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- + tileï¼ç¹å»è°èµ· GroupCreateDialogï¼æå¯¹æ¹ id ä½ä¸º lockedIds ä¼ å
¥ --> |
| | | <div |
| | | class="im-conversation-private-side__tile-wrap-clickable flex flex-col items-center w-[66px] cursor-pointer" |
| | | title="å起群è" |
| | | @click="handleOpenCreateGroup" |
| | | > |
| | | <div class="im-conversation-private-side__icon-tile flex items-center justify-center w-[50px] h-[50px] text-20px text-[var(--ant-color-text)] bg-[var(--ant-color-fill-tertiary)] border border-dashed border-[var(--ant-color-border)] rounded-md transition-colors duration-200"> |
| | | <Icon icon="ant-design:plus-outlined" /> |
| | | </div> |
| | | <div class="w-full mt-1.5 overflow-hidden text-12px leading-[1.5] text-[var(--ant-color-text)] text-center truncate">æ·»å </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="flex-shrink-0 h-[10px]"></div> |
| | | |
| | | <!-- 夿³¨ï¼ä»
èªå·±å¯è§ï¼ï¼ç¹å»å¼¹ popover ç¼è¾ï¼ä¿ååç«å³å·æ°æ¬æ½å± + ä¼è¯å表å±ç¤ºå --> |
| | | <div class="bg-[var(--ant-color-bg-container)]"> |
| | | <Popover |
| | | v-model:open="displayNamePopoverVisible" |
| | | trigger="click" |
| | | placement="leftTop" |
| | | :overlay-style="{ width: '280px' }" |
| | | > |
| | | <div |
| | | class="im-conversation-private-side__row flex flex-col items-stretch gap-1.5 px-4 py-[14px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">夿³¨</span> |
| | | <span |
| | | v-if="friend.displayName" |
| | | class="text-13px leading-[1.6] text-[var(--ant-color-text)] break-all line-clamp-2" |
| | | > |
| | | {{ friend.displayName }} |
| | | </span> |
| | | <span v-else class="text-13px leading-[1.6] text-[var(--ant-color-text-placeholder)]"> |
| | | 好å夿³¨ä»
èªå·±å¯è§ |
| | | </span> |
| | | </div> |
| | | <template #content> |
| | | <div class="flex flex-col gap-2"> |
| | | <Input |
| | | v-model:value="editDisplayName" |
| | | :maxlength="16" |
| | | show-count |
| | | placeholder="请è¾å
¥å¤æ³¨å" |
| | | /> |
| | | <div class="flex justify-end gap-2"> |
| | | <Button size="small" @click="displayNamePopoverVisible = false">åæ¶</Button> |
| | | <Button size="small" type="primary" @click="handleSaveDisplayName"> |
| | | ä¿å |
| | | </Button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </Popover> |
| | | </div> |
| | | |
| | | <div class="flex-shrink-0 h-[10px]"></div> |
| | | |
| | | <!-- æ¥æ¾è天å
容 --> |
| | | <div class="bg-[var(--ant-color-bg-container)]"> |
| | | <div |
| | | class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 cursor-pointer transition-colors duration-150 hover:bg-[var(--ant-color-fill-tertiary)]" |
| | | @click="emit('openHistory')" |
| | | > |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">æ¥æ¾è天å
容</span> |
| | | <Icon |
| | | icon="ant-design:right-outlined" |
| | | :size="11" |
| | | class="text-[var(--ant-color-text-placeholder)]" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="flex-shrink-0 h-[10px]"></div> |
| | | |
| | | <!-- å¼å
³é¡¹ --> |
| | | <div class="bg-[var(--ant-color-bg-container)]"> |
| | | <div class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">æ¶æ¯å
ææ°</span> |
| | | <Switch :checked="!!conversation?.silent" @change="handleMutedChange" /> |
| | | </div> |
| | | <div class="im-conversation-private-side__row flex items-center justify-between gap-3 px-4 py-[13px] text-14px min-h-6 transition-colors duration-150"> |
| | | <span class="flex-shrink-0 text-14px text-[var(--ant-color-text)]">置顶è天</span> |
| | | <Switch :checked="!!conversation?.top" @change="handleTopChange" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- åå¯¹è¯æ¡ï¼å起群èï¼éå®å¯¹æ¹ä¸ºå·²éï¼ --> |
| | | <GroupCreateDialog ref="createGroupDialogRef" @created="handleGroupCreated" /> |
| | | </Drawer> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | /* ã+ã tileï¼ hover æ¶èå¨å
é¨ icon-tile 走主è²ï¼ è·¨åå
ç´ ç hover è卿 æ³ç¨åå
ç´ å·¥å
·ç±»è¡¨è¾¾ */ |
| | | .im-conversation-private-side__tile-wrap-clickable:hover .im-conversation-private-side__icon-tile { |
| | | color: var(--ant-color-primary); |
| | | border-color: var(--ant-color-primary); |
| | | background-color: var(--ant-color-primary-bg); |
| | | } |
| | | |
| | | /* :deep ç©¿é Icon å
é¨ svgï¼ el-icon å
¨å± color å¨æè²æ¨¡å¼ä¸è¢«ä¸»é¢çè¿ï¼é fill å°å½åè² */ |
| | | .im-conversation-private-side__icon-tile :deep(svg) { |
| | | fill: currentColor !important; |
| | | } |
| | | |
| | | /* ç¸é»ä¿¡æ¯è¡å åéçº¿ï¼ ç¸é»å
å¼éæ©å¨æ æ³ç¨å·¥å
·ç±»è¡¨è¾¾ */ |
| | | .im-conversation-private-side__row + .im-conversation-private-side__row { |
| | | border-top: 1px solid var(--im-border-color-lighter); |
| | | } |
| | | </style> |
| | | |
| | | <!-- å GroupSideï¼antd Drawer ä¼ éå° body å scoped data-v è½ä¸å° body ä¸ï¼é root-class-name ä½ç¥å
éæ©å¨åå
¨å±è§ååæé»è®¤ padding --> |
| | | <style> |
| | | .im-conversation-private-side__modal .ant-drawer-body { |
| | | padding: 0; |
| | | } |
| | | </style> |