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/index.vue |  101 +++++++++++++++++++++++++++++---------------------
 1 files changed, 59 insertions(+), 42 deletions(-)

diff --git a/src/views/_core/profile/index.vue b/src/views/_core/profile/index.vue
index 7120444..5c94b48 100644
--- a/src/views/_core/profile/index.vue
+++ b/src/views/_core/profile/index.vue
@@ -1,67 +1,84 @@
 <script setup lang="ts">
 import type { SystemUserProfileApi } from '#/api/system/user/profile';
-
-import { onMounted, ref } from 'vue';
-
-import { Page } from '@vben/common-ui';
+import { onMounted, ref, watch } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { IconifyIcon } from '@vben/icons';
 import { useUserStore } from '@vben/stores';
-
-import { Card, Tabs } from 'ant-design-vue';
-
+import { Skeleton, Tabs } from 'ant-design-vue';
 import { getAuthPermissionInfoApi } from '#/api';
 import { getUserProfile } from '#/api/system/user/profile';
-
 import BaseInfo from './modules/base-info.vue';
 import ProfileUser from './modules/profile-user.vue';
 import ResetPwd from './modules/reset-pwd.vue';
 import UserSocial from './modules/user-social.vue';
 
+defineOptions({ name: 'Profile' });
 const userStore = useUserStore();
+const route = useRoute();
+const router = useRouter();
 const activeName = ref('basicInfo');
-
-/** 鍔犺浇涓汉淇℃伅 */
 const profile = ref<SystemUserProfileApi.UserProfileRespVO>();
+const loading = ref(true);
+const failed = ref(false);
+const refreshing = ref(false);
+const sections = [
+  { key: 'basicInfo', title: '鍩烘湰璧勬枡', text: '瀹屽杽涓汉鑱旂郴鏂瑰紡锛屼究浜庢帴鏀朵笟鍔¢�氱煡涓庢棩甯歌仈绯汇��', icon: 'lucide:user-round-pen' },
+  { key: 'resetPwd', title: '瀵嗙爜璁剧疆', text: '浣跨敤褰撳墠瀵嗙爜楠岃瘉韬唤鍚庯紝璁剧疆鏂扮殑鐧诲綍瀵嗙爜銆�', icon: 'lucide:shield-check' },
+  { key: 'userSocial', title: '璐﹀彿缁戝畾', text: '绠$悊绗笁鏂硅处鍙凤紝渚夸簬浣跨敤宸茬粦瀹氱殑骞冲彴鐧诲綍銆�', icon: 'lucide:link' },
+];
+watch(() => route.query.section, (value) => {
+  activeName.value = ['basicInfo', 'resetPwd', 'userSocial'].includes(String(value)) ? String(value) : 'basicInfo';
+}, { immediate: true });
+function selectSection(value: string | number) {
+  activeName.value = String(value);
+}
 async function loadProfile() {
-  profile.value = await getUserProfile();
+  loading.value = true; failed.value = false;
+  try { profile.value = await getUserProfile(); }
+  catch { failed.value = true; }
+  finally { loading.value = false; }
 }
-
-/** 鍒锋柊涓汉淇℃伅 */
 async function refreshProfile() {
-  // 鍔犺浇涓汉淇℃伅
-  await loadProfile();
-
-  // 鏇存柊 store
-  const authPermissionInfo = await getAuthPermissionInfoApi();
-  userStore.setUserInfo(authPermissionInfo.user);
+  refreshing.value = true;
+  try {
+    profile.value = await getUserProfile();
+    const info = await getAuthPermissionInfoApi();
+    userStore.setUserInfo(info.user);
+  } finally { refreshing.value = false; }
 }
-
-/** 鍒濆鍖� */
 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="鍩烘湰璁剧疆">
-            <BaseInfo :profile="profile" @success="refreshProfile" />
+  <div class="portal-profile">
+    <header class="profile-page-heading">
+      <div><h1>涓汉涓績</h1><span>绠$悊涓汉璧勬枡銆佺櫥褰曞瘑鐮佷笌绗笁鏂硅处鍙枫��</span></div>
+      <button type="button" @click="router.push('/analytics')"><IconifyIcon icon="lucide:house" />杩斿洖棣栭〉</button>
+    </header>
+    <div v-if="loading" class="profile-loading" aria-live="polite"><Skeleton active avatar :paragraph="{ rows: 7 }" /><span>姝e湪鍔犺浇涓汉璧勬枡鈥�</span></div>
+    <div v-else-if="failed || !profile" class="profile-load-error" role="status"><IconifyIcon icon="lucide:cloud-off" /><h2>涓汉璧勬枡鏆傛椂鏃犳硶鍔犺浇</h2><p>璇锋鏌ョ綉缁滆繛鎺ュ悗閲嶈瘯銆�</p><button type="button" @click="loadProfile">閲嶆柊鍔犺浇</button></div>
+    <div v-else class="profile-layout">
+      <aside class="profile-identity"><ProfileUser :profile="profile" @success="refreshProfile" /></aside>
+      <section class="profile-editor" aria-label="涓汉璐﹀彿璁剧疆" :aria-busy="refreshing">
+        <Tabs :active-key="activeName" @change="selectSection">
+          <Tabs.TabPane v-for="section in sections" :key="section.key" force-render>
+            <template #tab><span class="profile-tab"><IconifyIcon :icon="section.icon" />{{ section.title }}</span></template>
+            <div class="profile-section-heading"><span class="profile-section-icon"><IconifyIcon :icon="section.icon" /></span><div><h2>{{ section.title }}</h2><p>{{ section.text }}</p></div></div>
+            <BaseInfo v-if="section.key === 'basicInfo'" :profile="profile" @success="refreshProfile" />
+            <ResetPwd v-else-if="section.key === 'resetPwd'" />
+            <UserSocial v-else @update:active-name="selectSection" />
           </Tabs.TabPane>
-          <Tabs.TabPane key="resetPwd" tab="瀵嗙爜璁剧疆">
-            <ResetPwd />
-          </Tabs.TabPane>
-          <Tabs.TabPane key="userSocial" tab="绀句氦缁戝畾" force-render>
-            <UserSocial @update:active-name="activeName = $event" />
-          </Tabs.TabPane>
-          <!-- TODO @鑺嬭壙锛氬湪绾胯澶� -->
         </Tabs>
-      </Card>
+      </section>
     </div>
-  </Page>
+  </div>
 </template>
+
+<style scoped>
+.portal-profile { width: min(1240px, 100%); margin: 0 auto; padding: 10px 8px 32px; color: var(--portal-ink); }
+.profile-page-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 12px 0 24px; }.profile-page-heading p { margin: 0 0 5px; color: var(--portal-muted); font-size: 12px; letter-spacing: .12em; }.profile-page-heading h1 { margin: 0 0 8px; font-size: 29px; font-weight: 700; }.profile-page-heading span { color: var(--portal-muted); font-size: 13px; }.profile-page-heading button { display: flex; min-height: 36px; align-items: center; gap: 7px; padding: 6px 12px; border: 1px solid #d8e5f0; border-radius: 5px; color: #315a80; background: var(--portal-surface); font-size: 12px; white-space: nowrap; }.profile-page-heading button svg { width: 15px; }
+.profile-layout { display: grid; grid-template-columns: 282px minmax(0, 1fr); align-items: start; gap: 20px; }.profile-identity,.profile-editor { min-width: 0; overflow: hidden; border: 1px solid #dfebf4; border-radius: 7px; background: var(--portal-surface); box-shadow: 0 3px 10px rgb(24 66 106 / 3%); }.profile-editor { min-height: 540px; padding: 0 28px 32px; }.profile-editor :deep(.ant-tabs-nav) { margin-bottom: 24px; }.profile-editor :deep(.ant-tabs-tab) { padding: 19px 0 16px; }.profile-tab { display: inline-flex; align-items: center; gap: 7px; font-size: 14px; }.profile-tab svg { width: 16px; height: 16px; }.profile-section-heading { display: flex; align-items: center; gap: 12px; margin-bottom: 28px; }.profile-section-icon { display: grid; width: 42px; height: 42px; flex: none; place-items: center; border-radius: 9px; color: #2b6599; background: #edf5fc; }.profile-section-icon svg { width: 21px; }.profile-section-heading h2 { margin: 0 0 5px; font-size: 18px; font-weight: 650; }.profile-section-heading p { margin: 0; color: var(--portal-muted); font-size: 12px; line-height: 1.7; }.profile-loading,.profile-load-error { padding: 36px; border: 1px solid #dfeaf4; border-radius: 7px; background: var(--portal-surface); }.profile-loading > span { color: var(--portal-muted); font-size: 13px; }.profile-load-error { display: grid; min-height: 330px; justify-items: center; align-content: center; gap: 12px; text-align: center; }.profile-load-error > svg { width: 36px; height: 36px; color: #7695b0; }.profile-load-error h2 { margin: 0; font-size: 18px; }.profile-load-error p { margin: 0; color: var(--portal-muted); }.profile-load-error button { padding: 8px 18px; border-radius: 5px; color: #fff; background: #b7242d; }button { cursor: pointer; }button:focus-visible { outline: 2px solid #2c70b2; outline-offset: 3px; }
+@media(max-width:1000px) { .profile-layout { grid-template-columns: 240px minmax(0,1fr); gap: 14px; }.profile-editor { padding: 0 20px 26px; } }
+@media(max-width:767px) { .portal-profile { padding: 0 0 24px; }.profile-page-heading { align-items: flex-start; padding-top: 8px; }.profile-page-heading h1 { font-size: 25px; }.profile-page-heading span { display: block; max-width: 22em; line-height: 1.7; }.profile-page-heading button { padding: 6px 9px; }.profile-layout { grid-template-columns: 1fr; }.profile-editor { min-height: 0; padding: 0 18px 26px; }.profile-tab { gap: 5px; font-size: 13px; }.profile-editor :deep(.ant-tabs-tab + .ant-tabs-tab) { margin-left: 22px; }.profile-section-heading { margin-bottom: 24px; } }
+</style>
+

--
Gitblit v1.9.3