feat: 新增个人中心页面

This commit is contained in:
xlsea
2025-04-28 23:52:26 +08:00
parent d751c14f7b
commit 423a8c031f
22 changed files with 920 additions and 20 deletions

View File

@ -1,9 +1,10 @@
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref } from 'vue';
import type { VNode } from 'vue';
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({
@ -14,11 +15,24 @@ const authStore = useAuthStore();
const { routerPushByKey, toLogin } = useRouterPush();
const { SvgIconVNode } = useSvgIcon();
const avatarLoading = ref(true);
const avatarError = ref(false);
function loginOrRegister() {
toLogin();
}
type DropdownKey = 'logout';
function handleAvatarLoad() {
avatarLoading.value = false;
avatarError.value = false;
}
function handleAvatarError() {
avatarLoading.value = false;
avatarError.value = true;
}
type DropdownKey = 'user-center' | 'logout';
type DropdownOption =
| {
@ -33,13 +47,21 @@ type DropdownOption =
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;
});
@ -59,7 +81,6 @@ function handleDropdown(key: DropdownKey) {
if (key === 'logout') {
logout();
} else {
// If your other options are jumps from other routes, they will be directly supported here
routerPushByKey(key);
}
}
@ -70,13 +91,65 @@ function handleDropdown(key: DropdownKey) {
{{ $t('page.login.common.loginOrRegister') }}
</NButton>
<NDropdown v-else placement="bottom" trigger="click" :options="options" @select="handleDropdown">
<div>
<ButtonIcon>
<SvgIcon icon="ph:user-circle" class="text-icon-large" />
<span class="text-16px font-medium">{{ authStore.userInfo.user?.userName }}</span>
</ButtonIcon>
<div class="avatar-wrapper">
<NSpin :show="avatarLoading">
<div class="avatar-container" :class="{ 'avatar-error': 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="user-name">{{ authStore.userInfo.user?.nickName }}</span>
</div>
</NSpin>
</div>
</NDropdown>
</template>
<style scoped></style>
<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;
font-weight: 500;
color: var(--primary-text-color);
max-width: 120px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-role {
font-size: 12px;
color: var(--secondary-text-color);
background-color: var(--tag-color);
padding: 2px 6px;
border-radius: 4px;
margin-left: 4px;
}
</style>