From 91bfcf61f44f353dfebc54a57b5b774721de3e9a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 19 十月 2026 09:39:22 +0800
Subject: [PATCH] feat(auth): 更新门户认证页面布局和样式
---
src/views/_core/profile/modules/profile-user.vue | 158 ++++++++--------------------------------------------
1 files changed, 25 insertions(+), 133 deletions(-)
diff --git a/src/views/_core/profile/modules/profile-user.vue b/src/views/_core/profile/modules/profile-user.vue
index 57384e3..7eb5073 100644
--- a/src/views/_core/profile/modules/profile-user.vue
+++ b/src/views/_core/profile/modules/profile-user.vue
@@ -1,148 +1,40 @@
<script setup lang="ts">
import type { SystemUserProfileApi } from '#/api/system/user/profile';
-
import { computed } from 'vue';
-
import { IconifyIcon } from '@vben/icons';
import { preferences } from '@vben/preferences';
import { formatDateTime } from '@vben/utils';
-
-import { Descriptions, DescriptionsItem, Tooltip } from 'ant-design-vue';
-
import { updateUserProfile } from '#/api/system/user/profile';
import { CropperAvatar } from '#/components/cropper';
import { useUpload } from '#/components/upload/use-upload';
-
-const props = defineProps<{
- profile?: SystemUserProfileApi.UserProfileRespVO;
-}>();
-
-const emit = defineEmits<{
- (e: 'success'): void;
-}>();
-
-const avatar = computed(
- () => props.profile?.avatar || preferences.app.defaultAvatar,
-);
-
-async function handelUpload({
- file,
- filename,
-}: {
- file: Blob;
- filename: string;
-}) {
- // 1. 涓婁紶澶村儚锛岃幏鍙� URL
+const props = defineProps<{ profile: SystemUserProfileApi.UserProfileRespVO }>();
+const emit = defineEmits<{ success: [] }>();
+const avatar = computed(() => props.profile.avatar || preferences.app.defaultAvatar);
+const details = computed(() => [
+ { label: '鐢ㄦ埛璐﹀彿', value: props.profile.username, icon: 'lucide:user-round' },
+ { label: '鎵�灞為儴闂�', value: props.profile.dept?.name, icon: 'lucide:building-2' },
+ { label: '鎵�灞炲矖浣�', value: props.profile.posts?.map(p => p.name).join('銆�'), icon: 'lucide:briefcase-business' },
+ { label: '鎵嬫満鍙风爜', value: props.profile.mobile, icon: 'lucide:smartphone' },
+ { label: '鐢靛瓙閭', value: props.profile.email, icon: 'lucide:mail' },
+]);
+async function handleUpload({ file, filename }: { file: Blob; filename: string }) {
const { httpRequest } = useUpload();
- // 灏� Blob 杞崲涓� File
- const fileObj = new File([file], filename, { type: file.type });
- const avatar = await httpRequest(fileObj);
- // 2. 鏇存柊鐢ㄦ埛澶村儚
- await updateUserProfile({ avatar });
+ const result = await httpRequest(new File([file], filename, { type: file.type }));
+ if (!result.url) throw new Error('澶村儚涓婁紶鏈繑鍥炲浘鐗囧湴鍧�锛岃閲嶈瘯');
+ await updateUserProfile({ avatar: result.url });
}
</script>
-
<template>
- <div v-if="profile">
- <div class="flex flex-col items-center">
- <Tooltip title="鐐瑰嚮涓婁紶澶村儚">
- <CropperAvatar
- :show-btn="false"
- :upload-api="handelUpload"
- :value="avatar"
- :width="120"
- @change="emit('success')"
- />
- </Tooltip>
- </div>
- <div class="mt-8">
- <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 && profile.posts.length > 0
- ? 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 class="profile-user">
+ <div class="profile-user-banner" aria-hidden="true"></div>
+ <div class="profile-avatar"><CropperAvatar :value="avatar" :width="84" :upload-api="handleUpload" :show-btn="true" btn-text="鏇存崲澶村儚" :btn-props="{ type: 'link', size: 'small' }" @change="emit('success')" /></div>
+ <div class="profile-user-name"><h2>{{ profile.nickname || profile.username }}</h2><p>{{ profile.dept?.name || '涓汉璐﹀彿' }}</p><div v-if="profile.roles?.length" class="profile-roles"><span v-for="role in profile.roles" :key="role.id ?? role.name">{{ role.name }}</span></div></div>
+ <dl class="profile-facts"><div v-for="item in details" :key="item.label"><dt><IconifyIcon :icon="item.icon" />{{ item.label }}</dt><dd>{{ item.value || '鏈缃�' }}</dd></div></dl>
+ <div class="profile-login"><span><IconifyIcon icon="lucide:clock-3" />鏈�杩戠櫥褰�</span><time>{{ profile.loginDate ? formatDateTime(profile.loginDate) : '鏆傛棤璁板綍' }}</time><small v-if="profile.createTime">鍔犲叆鏃堕棿锛歿{ formatDateTime(profile.createTime)?.slice(0, 10) }}</small></div>
</div>
</template>
+<style scoped>
+.profile-user-banner { height: 78px; background: linear-gradient(100deg, rgb(24 74 116 / 92%), rgb(43 105 154 / 38%)), url('/static/imgs/portal-hero-v2.png') center 60% / cover; }.profile-avatar { position: relative; display: flex; justify-content: center; margin-top: -42px; }.profile-avatar :deep(.group) { border: 4px solid #fff; box-shadow: 0 3px 8px rgb(24 66 106 / 9%); }.profile-avatar :deep(.ant-btn) { font-size: 11px; }.profile-user-name { padding: 5px 20px 20px; text-align: center; }.profile-user-name h2 { margin: 0 0 5px; font-size: 21px; font-weight: 650; overflow-wrap: anywhere; }.profile-user-name p { margin: 0; color: var(--portal-muted); font-size: 12px; }.profile-roles { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin-top: 12px; }.profile-roles span { padding: 3px 8px; border-radius: 4px; color: #2d618b; background: #edf5fc; font-size: 11px; }.profile-facts { margin: 0 20px; padding: 5px 0 10px; border-top: 1px solid #e7eff6; }.profile-facts > div { padding: 12px 0; border-bottom: 1px solid #edf2f7; }.profile-facts dt { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; color: var(--portal-muted); font-size: 11px; }.profile-facts svg,.profile-login svg { width: 14px; height: 14px; }.profile-facts dd { margin: 0; padding-left: 21px; color: var(--portal-ink); font-size: 13px; line-height: 1.6; overflow-wrap: anywhere; }.profile-login { display: grid; gap: 6px; padding: 10px 20px 22px; color: var(--portal-muted); font-size: 11px; }.profile-login > span { display: flex; align-items: center; gap: 7px; }.profile-login time { padding-left: 21px; }.profile-login small { padding-left: 21px; font-size: 10px; }
+@media(max-width:767px) { .profile-facts { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); column-gap: 16px; }.profile-login { grid-template-columns: auto 1fr; }.profile-login time { padding: 0; }.profile-login small { grid-column: 1 / -1; } }
+</style>
+
--
Gitblit v1.9.3