ruoyi-plus-soybean/src/layouts/modules/global-header/components/user-avatar.vue
2025-05-09 17:37:24 +08:00

143 lines
3.2 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue';
import type { VNode } from 'vue';
import { useBoolean } from '@sa/hooks';
import { useAuthStore } from '@/store/modules/auth';
import { useRouterPush } from '@/hooks/common/router';
import { useSvgIcon } from '@/hooks/common/icon';
import defaultAvatar from '@/assets/imgs/soybean.jpg';
import { $t } from '@/locales';
defineOptions({
name: 'UserAvatar'
});
const authStore = useAuthStore();
const { routerPushByKey, toLogin } = useRouterPush();
const { SvgIconVNode } = useSvgIcon();
const { bool: avatarError, setTrue: setError, setFalse: clearError } = useBoolean(false);
function loginOrRegister() {
toLogin();
}
function handleAvatarLoad() {
clearError();
}
function handleAvatarError() {
setError();
}
type DropdownKey = 'user-center' | 'logout';
type DropdownOption =
| {
key: DropdownKey;
label: string;
icon?: () => VNode;
}
| {
type: 'divider';
key: string;
};
const options = computed(() => {
const opts: DropdownOption[] = [
{
label: $t('common.userCenter'),
key: 'user-center',
icon: SvgIconVNode({ icon: 'ph:user-circle', fontSize: 18 })
},
{
type: 'divider',
key: 'divider'
},
{
label: $t('common.logout'),
key: 'logout',
icon: SvgIconVNode({ icon: 'ph:sign-out', fontSize: 18 })
}
];
return opts;
});
function logout() {
window.$dialog?.info({
title: $t('common.tip'),
content: $t('common.logoutConfirm'),
positiveText: $t('common.confirm'),
negativeText: $t('common.cancel'),
onPositiveClick: () => {
authStore.logout();
}
});
}
function handleDropdown(key: DropdownKey) {
if (key === 'logout') {
logout();
} else {
routerPushByKey(key);
}
}
</script>
<template>
<NButton v-if="!authStore.isLogin" quaternary @click="loginOrRegister">
{{ $t('page.login.common.loginOrRegister') }}
</NButton>
<NDropdown v-else placement="bottom" trigger="click" :options="options" @select="handleDropdown">
<div class="flex cursor-pointer items-center rounded-md px-2 py-1 transition-colors duration-300 hover:bg-black/6">
<div class="flex items-center gap-2" :class="{ 'opacity-50': avatarError }">
<NAvatar
v-if="authStore.userInfo.user?.avatar"
:size="24"
round
:src="authStore.userInfo.user?.avatar"
@load="handleAvatarLoad"
@error="handleAvatarError"
/>
<NAvatar v-else :size="32" round :src="defaultAvatar" @load="handleAvatarLoad" @error="handleAvatarError" />
<span class="max-w-120px truncate text-14px font-medium">
{{ authStore.userInfo.user?.nickName }}
</span>
</div>
</div>
</NDropdown>
</template>
<style lang="scss" scoped>
.avatar-wrapper {
display: flex;
align-items: center;
padding: 4px 8px;
border-radius: 6px;
transition: all 0.3s ease;
cursor: pointer;
&:hover {
background-color: rgba(0, 0, 0, 0.06);
}
}
.avatar-container {
display: flex;
align-items: center;
gap: 8px;
&.avatar-error {
opacity: 0.5;
}
}
.user-name {
font-size: 14px;
max-width: 120px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>