✨ feat(im): 新增 UserInfoCard.vue
parent
9a4e79e4ef
commit
20c6631e7a
|
|
@ -17,6 +17,7 @@
|
|||
:src="url"
|
||||
:preview-src-list="[url]"
|
||||
:preview-teleported="true"
|
||||
:z-index="previewZIndex"
|
||||
:style="imgStyle"
|
||||
fit="cover"
|
||||
/>
|
||||
|
|
@ -57,13 +58,15 @@ const props = withDefaults(
|
|||
radius?: string // 圆角,支持 CSS 长度;默认 15% 方块小圆角(参考微信)
|
||||
clickable?: boolean // 是否点击弹出 UserInfoCard;默认 true
|
||||
previewable?: boolean // 是否点头像直接放大预览;开启后忽略 clickable,不再弹名片
|
||||
previewZIndex?: number // 预览层 z-index;放在高 z-index 弹层(如 UserInfoCard)里时需手动抬高
|
||||
user?: UserInfo // 额外的用户信息,传了点击就不用现拉接口(弹名片用)
|
||||
}>(),
|
||||
{
|
||||
size: 42,
|
||||
radius: '15%',
|
||||
clickable: true,
|
||||
previewable: false
|
||||
previewable: false,
|
||||
previewZIndex: 2000
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
<template>
|
||||
<!--
|
||||
用户名片
|
||||
由 Index.vue 挂载,通过 useImUiStore.openUserInfoCard(user, position) 触发点遮罩 / 按 Esc 关闭
|
||||
-->
|
||||
<teleport to="body">
|
||||
<div v-if="card.show" class="fixed inset-0 z-9998" @click.self="handleClose">
|
||||
<div
|
||||
class="fixed w-80 p-4 bg-[var(--el-bg-color-overlay)] rounded-md shadow-xl"
|
||||
:style="{ left: card.position.x + 'px', top: card.position.y + 'px' }"
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<UserAvatar
|
||||
:url="user?.avatar"
|
||||
:name="user?.nickname"
|
||||
:size="60"
|
||||
:clickable="false"
|
||||
previewable
|
||||
:preview-z-index="10000"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-16px font-semibold text-[var(--el-text-color-primary)]">
|
||||
{{ user?.nickname || '-' }}
|
||||
</div>
|
||||
<div class="mt-1 text-13px break-all text-[var(--el-text-color-regular)]">
|
||||
<span class="text-[var(--el-text-color-secondary)]">部门:</span>
|
||||
<span>{{ user?.deptName || '-' }}</span>
|
||||
</div>
|
||||
<div class="mt-1 text-13px break-all text-[var(--el-text-color-regular)]">
|
||||
<span class="text-[var(--el-text-color-secondary)]">性别:</span>
|
||||
<span>{{ sexLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider class="!my-3.5" />
|
||||
|
||||
<div class="flex gap-2 justify-center">
|
||||
<el-button v-if="isSelf" type="primary" disabled>不能和自己聊天</el-button>
|
||||
<el-button v-else-if="isFriend" type="primary" @click="handleSendMessage"
|
||||
>发消息</el-button
|
||||
>
|
||||
<el-button v-else type="primary" @click="handleAddFriend">加为好友</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import { DICT_TYPE, getDictLabel } from '@/utils/dict'
|
||||
import { getSimpleUser } from '@/api/system/user'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useImUiStore } from '../store/uiStore'
|
||||
import { useConversationStore } from '../store/conversationStore'
|
||||
import { useFriendStore } from '../store/friendStore'
|
||||
import { ImConversationType } from '../../utils/constants'
|
||||
import type { UserInfo } from '../types'
|
||||
import UserAvatar from './UserAvatar.vue'
|
||||
|
||||
defineOptions({ name: 'ImUserInfoCard' })
|
||||
|
||||
const uiStore = useImUiStore()
|
||||
const conversationStore = useConversationStore()
|
||||
const friendStore = useFriendStore()
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
|
||||
const card = computed(() => uiStore.userInfoCard)
|
||||
const user = computed(() => card.value.user)
|
||||
|
||||
const sexLabel = computed(() => {
|
||||
if (user.value?.sex == null) {
|
||||
return '-'
|
||||
}
|
||||
return getDictLabel(DICT_TYPE.SYSTEM_USER_SEX, user.value.sex) || '-'
|
||||
})
|
||||
|
||||
const isSelf = computed(() => {
|
||||
const myId = Number(userStore.getUser?.id) || 0
|
||||
return !!user.value?.id && user.value.id === myId
|
||||
})
|
||||
|
||||
const isFriend = computed(() => {
|
||||
if (!user.value?.id || isSelf.value) {
|
||||
return false
|
||||
}
|
||||
return friendStore.isFriend(user.value.id)
|
||||
})
|
||||
|
||||
/** 名片打开时拉一次完整信息(部门 / 性别),覆盖调用方传入的最小集 */
|
||||
watch(
|
||||
() => card.value.show,
|
||||
async (show) => {
|
||||
if (!show) {
|
||||
return
|
||||
}
|
||||
const id = user.value?.id
|
||||
if (!id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = (await getSimpleUser(id)) as UserInfo
|
||||
// 二次校验:用户切换或卡片已关闭则丢弃响应
|
||||
if (data && card.value.show && card.value.user?.id === id) {
|
||||
Object.assign(card.value.user, data)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[IM] 拉取用户名片信息失败', e)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/** 关闭名片:点击遮罩或按 Esc 都会触发,直接调用 uiStore 关掉就行 */
|
||||
function handleClose() {
|
||||
uiStore.closeUserInfoCard()
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && card.value.show) {
|
||||
handleClose()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => window.addEventListener('keydown', handleKeydown))
|
||||
onUnmounted(() => window.removeEventListener('keydown', handleKeydown))
|
||||
|
||||
/** 发消息:打开私聊会话,跳转到聊天 tab */
|
||||
function handleSendMessage() {
|
||||
if (!user.value) {
|
||||
return
|
||||
}
|
||||
// 同步 friendStore 里的 muted,避免新建的私聊会话丢失"消息免打扰"状态(与 FriendPage.onChat 行为一致)
|
||||
const friendEntry = friendStore.getFriend(user.value.id)
|
||||
conversationStore.openConversation(
|
||||
user.value.id,
|
||||
ImConversationType.PRIVATE,
|
||||
user.value.nickname || '',
|
||||
user.value.avatar || '',
|
||||
{ muted: !!friendEntry?.muted }
|
||||
)
|
||||
// 跳转到聊天 tab(如果已经在了就算了)
|
||||
if (router.currentRoute.value.name !== 'ImHomeConversation') {
|
||||
router.push({ name: 'ImHomeConversation' })
|
||||
}
|
||||
uiStore.closeUserInfoCard()
|
||||
}
|
||||
|
||||
/** 加为好友:直接加,成功后名片会自动切换到「发消息」按钮 */
|
||||
async function handleAddFriend() {
|
||||
if (!user.value?.id) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await friendStore.addFriend(user.value.id, {
|
||||
nickname: user.value.nickname,
|
||||
avatar: user.value.avatar
|
||||
})
|
||||
ElMessage.success('已添加好友')
|
||||
} catch (e: any) {
|
||||
console.error(
|
||||
'[IM] 添加好友失败',
|
||||
{ userId: user.value?.id, nickname: user.value?.nickname },
|
||||
e
|
||||
)
|
||||
ElMessage.error(e?.message || '添加好友失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Loading…
Reference in New Issue