feat:增加个人中心:20% 支持左侧的个人信息

pull/76/MERGE
YunaiV 2025-04-20 08:04:51 +08:00
parent 1662598488
commit 57c8d88bae
10 changed files with 260 additions and 12 deletions

View File

@ -1,2 +1 @@
export * from './auth';
export * from './user';

View File

@ -1,10 +0,0 @@
import type { UserInfo } from '@vben/types';
import { requestClient } from '#/api/request';
/**
*
*/
export async function getUserInfoApi() {
return requestClient.get<UserInfo>('/system/user/profile/get');
}

View File

@ -0,0 +1,63 @@
import { requestClient } from '#/api/request';
export namespace SystemUserProfileApi {
/** 社交用户信息 */
export interface SocialUser {
type: number;
openid: string;
}
/** 用户个人中心信息 */
export interface UserProfileRespVO {
id: number;
username: string;
nickname: string;
email?: string;
mobile?: string;
sex?: number;
avatar?: string;
loginIp: string;
loginDate: string;
createTime: string;
roles: any[];
dept: any;
posts: any[];
socialUsers: SocialUser[];
}
/** 更新密码请求 */
export interface UpdatePasswordReqVO {
oldPassword: string;
newPassword: string;
}
/** 更新个人信息请求 */
export interface UpdateProfileReqVO {
nickname: string;
email?: string;
mobile?: string;
sex?: number;
}
}
/** 获取登录用户信息 */
export function getUserProfile() {
return requestClient.get<SystemUserProfileApi.UserProfileRespVO>('/system/user/profile/get');
}
/** 修改用户个人信息 */
export function updateUserProfile(data: SystemUserProfileApi.UpdateProfileReqVO) {
return requestClient.put('/system/user/profile/update', data);
}
/** 修改用户个人密码 */
export function updateUserPassword(data: SystemUserProfileApi.UpdatePasswordReqVO) {
return requestClient.put('/system/user/profile/update-password', data);
}
/** 上传用户个人头像 */
export function updateUserAvatar(file: File) {
const formData = new FormData();
formData.append('avatarFile', file);
return requestClient.put('/system/user/profile/update-avatar', formData);
}

View File

@ -6,7 +6,7 @@ import { computed, ref, watch } from 'vue';
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
import { useWatermark } from '@vben/hooks';
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
import { BookOpenText, CircleHelp, MdiGithub, AntdProfileOutlined } from '@vben/icons';
import {
BasicLayout,
LockScreen,
@ -20,6 +20,7 @@ import { openWindow } from '@vben/utils';
import { $t } from '#/locales';
import { useAuthStore } from '#/store';
import LoginForm from '#/views/_core/authentication/login.vue';
import { router } from '#/router';
const notifications = ref<NotificationItem[]>([
{
@ -61,6 +62,13 @@ const showDot = computed(() =>
);
const menus = computed(() => [
{
handler: () => {
router.push({ name: 'Profile' });
},
icon: AntdProfileOutlined,
text: $t('ui.widgets.profile'),
},
{
handler: () => {
openWindow(VBEN_DOC_URL, {

View File

@ -33,6 +33,16 @@ const routes: RouteRecordRaw[] = [
},
],
},
{
name: 'Profile',
path: '/profile',
component: () => import('#/views/_core/profile/index.vue'),
meta: {
icon: 'ant-design:profile-outlined',
title: $t('ui.widgets.profile'),
hideInMenu: true,
},
},
];
export default routes;

View File

@ -0,0 +1,58 @@
<script setup lang="ts">
import type { SystemUserProfileApi } from '#/api/system/user/profile';
import { onMounted, ref } from 'vue';
import { getUserProfile } from '#/api/system/user/profile';
import { useAuthStore } from '#/store';
import { Card, Tabs } from 'ant-design-vue';
import { Page } from '@vben/common-ui';
import ProfileUser from './modules/profile-user.vue';
const authStore = useAuthStore();
const activeName = ref('basicInfo');
/** 加载个人信息 */
const profile = ref<SystemUserProfileApi.UserProfileRespVO>();
async function loadProfile() {
profile.value = await getUserProfile();
}
/** 刷新个人信息 */
async function refreshProfile() {
//
await loadProfile();
// store
await authStore.fetchUserInfo();
}
/** 初始化 */
onMounted(loadProfile);
</script>
<template>
<Page auto-content-height>
<div class="flex">
<!-- 左侧 个人信息 -->
<Card class="w-2/5" title="个人信息">
<ProfileUser :profile="profile" @success="refreshProfile" />
</Card>
<!-- 右侧 标签页 -->
<Card class="ml-3 w-3/5">
<Tabs v-model:active-key="activeName" class="-mt-4">
<Tabs.TabPane key="basicInfo" tab="基本信息">
<!-- <BaseSetting :profile="profile" /> -->
</Tabs.TabPane>
<Tabs.TabPane key="resetPwd" tab="修改密码">
<!-- <ResetPwd /> -->
</Tabs.TabPane>
<Tabs.TabPane key="userSocial" tab="社交信息">
<!-- <UserSocial :profile="profile" /> -->
</Tabs.TabPane>
</Tabs>
</Card>
</div>
</Page>
</template>

View File

@ -0,0 +1,116 @@
<script setup lang="ts">
import type { SystemUserProfileApi } from '#/api/system/user/profile';
import { Descriptions, DescriptionsItem, Tooltip } from 'ant-design-vue';
import { IconifyIcon } from '@vben/icons';
import { computed } from 'vue';
import { preferences } from '@vben/preferences';
import { updateUserAvatar } from '#/api/system/user/profile';
import { formatDateTime } from '@vben/utils';
// import { CropperAvatar } from '#/components/cropper';
const props = defineProps<{ profile?: SystemUserProfileApi.UserProfileRespVO }>();
defineEmits<{
//
success: [];
}>();
const avatar = computed(
() => props.profile?.avatar || preferences.app.defaultAvatar,
);
</script>
<template>
<div v-if="profile">
<div class="flex flex-col items-center gap-[20px]">
<Tooltip title="点击上传头像">
<!-- TODO 芋艿待实现 -->
<CropperAvatar
:show-btn="false"
:upload-api="updateUserAvatar"
:value="avatar"
width="120"
@change=""
/>
</Tooltip>
</div>
<div>
<Descriptions :column="2">
<DescriptionsItem>
<template #label>
<div class="flex items-center">
<IconifyIcon icon="ant-design:user-outlined" class="mr-1" />
用户账号
</div>
</template>
{{ profile.username }}
</DescriptionsItem>
<DescriptionsItem>
<template #label>
<div class="flex items-center">
<IconifyIcon icon="ant-design:user-switch-outlined" class="mr-1" />
所属角色
</div>
</template>
{{ profile.roles.map(role => role.name).join(',') }}
</DescriptionsItem>
<DescriptionsItem>
<template #label>
<div class="flex items-center">
<IconifyIcon icon="ant-design:phone-outlined" class="mr-1" />
手机号码
</div>
</template>
{{ profile.mobile }}
</DescriptionsItem>
<DescriptionsItem>
<template #label>
<div class="flex items-center">
<IconifyIcon icon="ant-design:mail-outlined" class="mr-1" />
用户邮箱
</div>
</template>
{{ profile.email }}
</DescriptionsItem>
<DescriptionsItem>
<template #label>
<div class="flex items-center">
<IconifyIcon icon="ant-design:team-outlined" class="mr-1" />
所属部门
</div>
</template>
{{ profile.dept?.name }}
</DescriptionsItem>
<DescriptionsItem>
<template #label>
<div class="flex items-center">
<IconifyIcon icon="ant-design:usergroup-add-outlined" class="mr-1" />
所属岗位
</div>
</template>
{{ profile.posts.map(post => post.name).join(',') }}
</DescriptionsItem>
<DescriptionsItem>
<template #label>
<div class="flex items-center">
<IconifyIcon icon="ant-design:clock-circle-outlined" class="mr-1" />
创建时间
</div>
</template>
{{ formatDateTime(profile.createTime) }}
</DescriptionsItem>
<DescriptionsItem>
<template #label>
<div class="flex items-center">
<IconifyIcon icon="ant-design:login-outlined" class="mr-1" />
登录时间
</div>
</template>
{{ formatDateTime(profile.loginDate) }}
</DescriptionsItem>
</Descriptions>
</div>
</div>
</template>

View File

@ -15,3 +15,5 @@ export const AntdDingTalk = createIconifyIcon('ant-design:dingtalk')
export const MdiCheckboxMarkedCircleOutline = createIconifyIcon(
'mdi:checkbox-marked-circle-outline',
);
export const AntdProfileOutlined = createIconifyIcon('ant-design:profile-outlined');

View File

@ -77,6 +77,7 @@
},
"widgets": {
"document": "Document",
"profile": "Profile",
"qa": "Q&A",
"setting": "Settings",
"logoutTip": "Do you want to logout?",

View File

@ -77,6 +77,7 @@
},
"widgets": {
"document": "文档",
"profile": "个人中心",
"qa": "问题 & 帮助",
"setting": "设置",
"logoutTip": "是否退出登录?",