🎈 perf: 更新用户主页

This commit is contained in:
Litrix2 2024-12-22 15:12:59 +08:00
parent 656538fecc
commit 7e8caace31
17 changed files with 196 additions and 101 deletions

View File

@ -5,7 +5,9 @@
<icon-cs-menu />
</el-icon>
<router-link custom to="/" v-slot="{ navigate }">
<el-icon size="50" @click="navigate"><icon-cs-club /></el-icon>
<el-icon size="50" @click="navigate" color="var(--el-color-primary)">
<icon-cs-club />
</el-icon>
<h3 v-show="showAppTitle" class="app__title" @click="navigate">社团展示系统</h3>
</router-link>
<header-menu v-show="!mdLess" mode="horizontal" />

View File

@ -3,7 +3,7 @@ import {
userInfoRespSchemaNullable,
type SucceedUserInfoResp,
type SucceedUserInfoRespNullable,
} from '@/schemas';
} from '@/schemas/response';
import { useUserStore } from '@/stores/user';
import axios, { type AxiosError } from 'axios';
import { ElMessage } from 'element-plus';

View File

@ -4,23 +4,23 @@
xmlns:xlink="http://www.w3.org/1999/xlink">
<path
d="M512.79616 806.37312c-145.024 0-263.04-118.016-263.04-263.04s118.016-263.04 263.04-263.04 263.04 118.016 263.04 263.04c0.128 145.024-117.888 263.04-263.04 263.04z m0-487.808c-123.904 0-224.64 100.864-224.64 224.64 0 123.904 100.864 224.64 224.64 224.64s224.64-100.864 224.64-224.64c0.128-123.776-100.736-224.64-224.64-224.64z"
fill="#409eff" p-id="1550"></path>
p-id="1550"></path>
<path d="M497.43616 388.70912m-20.352 0a20.352 20.352 0 1 0 40.704 0 20.352 20.352 0 1 0-40.704 0Z" fill="#409eff"
p-id="1551"></path>
<path
d="M497.43616 428.26112c-21.888 0-39.552-17.792-39.552-39.552s17.664-39.68 39.552-39.68c21.888 0 39.552 17.792 39.552 39.552s-17.792 39.68-39.552 39.68z m0-40.832c-0.64 0-1.152 0.512-1.152 1.152s0.512 1.152 1.152 1.152 1.152-0.512 1.152-1.152-0.512-1.152-1.152-1.152z"
fill="#409eff" p-id="1552"></path>
p-id="1552"></path>
<path d="M441.11616 511.46112m-42.88 0a42.88 42.88 0 1 0 85.76 0 42.88 42.88 0 1 0-85.76 0Z" fill="#409eff"
p-id="1553"></path>
<path
d="M441.11616 573.54112c-34.304 0-62.08-27.904-62.08-62.08s27.904-62.08 62.08-62.08 62.08 27.904 62.08 62.08-27.904 62.08-62.08 62.08z m0-85.76c-13.056 0-23.68 10.624-23.68 23.68s10.624 23.68 23.68 23.68 23.68-10.624 23.68-23.68-10.624-23.68-23.68-23.68z"
fill="#409eff" p-id="1554"></path>
p-id="1554"></path>
<path d="M575.64416 485.47712m-19.072 0a19.072 19.072 0 1 0 38.144 0 19.072 19.072 0 1 0-38.144 0Z" fill="#409eff"
p-id="1555"></path>
<path
d="M575.64416 523.87712c-21.12 0-38.272-17.152-38.272-38.272s17.152-38.272 38.272-38.272 38.272 17.152 38.272 38.272-17.024 38.272-38.272 38.272z m0.128-38.4z"
fill="#409eff" p-id="1556"></path>
p-id="1556"></path>
<path
d="M336.79616 763.10912c-56.192 0-98.176-18.176-120.192-52.48-29.952-46.72-16.512-115.456 36.992-188.416l30.976 22.784c-43.008 58.624-56.32 112.768-35.712 145.024 17.92 27.904 61.312 39.808 119.424 32.768 148.48-26.368 282.368-112.64 367.616-236.928 28.928-48.512 35.712-91.904 18.304-119.168-20.224-31.616-73.472-42.496-142.592-29.184l-7.296-37.76c86.4-16.768 152.96 0.128 182.272 46.208 25.984 40.576 19.584 97.408-18.048 160l-0.64 1.024c-45.696 66.688-103.296 122.752-171.136 166.528s-142.72 73.088-222.208 87.168l-1.024 0.128c-12.8 1.536-25.088 2.304-36.736 2.304z"
fill="#409eff" p-id="1557"></path>
p-id="1557"></path>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,9 @@
<svg t="1734838157570" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5158"
data-spm-anchor-id="a313x.search_index.0.i0.2b2d3a81Kw57m1" width="200" height="200">
<path
d="M832 405.344h-224a32 32 0 0 0 0 64h224a32 32 0 0 0 0-64zM832 565.344h-224a32 32 0 0 0 0 64h224a32 32 0 0 0 0-64zM404.352 635.36v-20.864c30.816-19.968 60.544-55.04 76.768-98.048 11.488-4.992 19.744-19.104 26.4-44.256 5.152-19.488-3.104-32.352-14.272-39.488C487.744 340.832 434.144 288 352 288c-83.296 0-139.456 54.368-143.776 148.672-10.112 8.352-16.608 21.76-11.552 40.864 7.2 27.232 16.096 42.88 28.352 48.864 17.312 38.656 45.664 70.016 74.624 88.384v20.576C219.04 644.032 160 672.256 160 705.728c0 40.384 384 40.384 384 0 0-33.472-59.04-61.696-139.648-70.368z"
p-id="5159"></path>
<path
d="M896 128H128a96 96 0 0 0-96 96v576a96 96 0 0 0 96 96h768a96 96 0 0 0 96-96V224a96 96 0 0 0-96-96z m32 672a32 32 0 0 1-32 32H128a32 32 0 0 1-32-32V224a32 32 0 0 1 32-32h768a32 32 0 0 1 32 32v576z"
p-id="5160"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -36,7 +36,7 @@
</style>
<script lang="ts" setup>
import axiosInstance from '@/api';
import { verifyRespSchema } from '@/schemas';
import { verifyRespSchema } from '@/schemas/response';
import { timeout } from '@/utils';
import { errorMessage } from '@/utils/api';
import { AxiosError } from 'axios';

View File

@ -13,7 +13,7 @@
}
</style>
<script setup lang="ts">
import type { UserInfo } from '@/schemas';
import type { UserInfo } from '@/schemas/response';
withDefaults(defineProps<{ user: UserInfo; footer?: boolean }>(), {
footer: false,
});

View File

@ -20,7 +20,20 @@ const routes: RouteRecordRaw[] = [
{
path: '/user/:id',
name: 'User',
component: () => import('@/views/UserPage.vue'),
component: () => import('@/views/user/UserPage.vue'),
redirect: { name: 'UserPosts' },
children: [
{
path: 'posts',
name: 'UserPosts',
component: () => import('@/views/user/UserPosts.vue'),
},
{
path: 'edit',
name: 'UserEdit',
component: () => import('@/views/user/UserEditPage.vue'),
},
],
},
{
path: '/club',

View File

@ -1 +0,0 @@
export * from './response';

View File

@ -1,6 +1,6 @@
import router from '@/router';
import { IDPool } from '@/utils';
import type { PageErrorReason } from '@/views/ErrorPage.vue';
import type { PageErrorReason, PageErrorType } from '@/views/ErrorPage.vue';
import { defineStore } from 'pinia';
import { computed } from 'vue';
import {
@ -31,7 +31,7 @@ export const usePageStore = defineStore('page', () => {
reason: Exclude<
PageErrorReason,
{
type: 'notFound';
type: PageErrorType.NOT_FOUND;
}
>,
to: RouteLocationNormalized,

View File

@ -1,7 +1,7 @@
import { getUserInfo } from '@/api';
import router from '@/router';
import type { UserInfo } from '@/schemas';
import { type idAndNameSchema } from '@/schemas';
import type { UserInfo } from '@/schemas/response';
import { type idAndNameSchema } from '@/schemas/response';
import type { MaybePromise } from '@/utils/types';
import { StorageSerializers, useLocalStorage } from '@vueuse/core';
import { defineStore } from 'pinia';
@ -34,6 +34,7 @@ export const useUserStore = defineStore('user', () => {
},
{ flush: 'sync' },
);
const isSelf = (id: number) => userInfo.value?.id === id;
async function updateSelfUserInfo(showErrorMessage: boolean): Promise<boolean> {
initializing.value = true;
try {
@ -66,6 +67,7 @@ export const useUserStore = defineStore('user', () => {
logined,
permissions,
initializing,
isSelf,
updateSelfUserInfo,
logoutInterceptors,
addLogoutInterceptor,

View File

@ -29,7 +29,7 @@ export type PageErrorReason = {
type: PageErrorType;
};
const descriptionMap: Record<PageErrorType, string> = {
[PageErrorType.NOT_FOUND]: '404',
[PageErrorType.NOT_FOUND]: '页面找不到了',
[PageErrorType.NETWORK_ERROR]: '网络错误',
[PageErrorType.NO_PERMISSION]: '没有权限',
[PageErrorType.NOT_LOGIN]: '你需要登录才能访问此页面',

View File

@ -15,7 +15,7 @@
:element-loading-text="loadingText"
class="gobang-list-page-main flex-col"
:class="[
!rooms.length ? 'justify-center items-center' : undefined,
!rooms.length ? 'justify-center items-center' : '',
{ '!flex': !rooms.length },
]"
>
@ -172,7 +172,7 @@ const playerCountMap: Record<RoomState, string> = {
import CreateGobangRoomDialog from '@/components/gobang/CreateGobangRoomDialog.vue';
import GobangHeader from '@/components/gobang/GobangHeader.vue';
import router from '@/router';
import type { UserInfo } from '@/schemas';
import type { UserInfo } from '@/schemas/response';
import { useUserStore } from '@/stores/user';
import { arrayIncludes } from '@/utils/array';
import {

View File

@ -48,7 +48,7 @@
? cell.isWhite
? 'gobang-chessboard__cell--white'
: 'gobang-chessboard__cell--black'
: undefined,
: '',
,
{
'gobang-chessboard__cell--win-blink': blink?.[y][x],

View File

@ -1,82 +0,0 @@
<template>
<el-main
v-loading="loading"
element-loading-text="正在加载"
class="page-main !flex flex-col items-center gap-10px"
:class="{ 'justify-center': !loading && !userInfo }"
>
<template v-if="!loading">
<template v-if="userInfo">
<el-card class="banner outer">
<el-avatar :size="80"></el-avatar>
<div class="flex flex-col gap-10px">
<h4>{{ userInfo.name }}</h4>
</div>
</el-card>
<div class="outer flex gap-10px">
<el-card class="w-200px">ss</el-card>
<el-card class="flex-1">ss</el-card>
</div>
</template>
<template v-else>
<div class="font-bold text-30px">无法获取用户信息</div>
<el-button type="primary" @click="loadUserInfo">刷新</el-button>
</template>
</template>
</el-main>
</template>
<style lang="scss" scoped>
.page-main {
--el-main-padding: 30px 0;
}
.el-card {
transition: none;
}
.outer {
@apply h-120px max-lg-w-700px lg-w-950px;
}
:deep(.banner) {
.el-card__body {
@apply flex gap-20px;
}
}
</style>
<script lang="ts" setup>
import { getUserInfo } from '@/api';
import { type UserInfo } from '@/schemas';
import { useMediaStore } from '@/stores/media';
import { useUserStore } from '@/stores/user';
import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
const { lgLess, lgOnly } = storeToRefs(useMediaStore());
const route = useRoute();
const userStore = useUserStore();
const loading = ref(true);
const userInfo = ref<UserInfo>();
async function loadUserInfo() {
loading.value = true;
const id = Number(route.params.id as string);
try {
if (isNaN(id)) {
ElMessage.error('参数错误');
return;
}
if (id === -1) {
ElMessage.error('用户不存在');
return;
}
const resp = await getUserInfo(true, id);
if (!resp) return;
if (!resp.data) {
ElMessage.error('用户不存在');
return;
}
userInfo.value = resp.data;
} finally {
loading.value = false;
}
}
onMounted(loadUserInfo);
</script>

View File

@ -0,0 +1,17 @@
<template>fsafasdf</template>
<script lang="ts">
import { usePageStore } from '@/stores/page';
import { useUserStore } from '@/stores/user';
import { PageErrorType } from '@/views/ErrorPage.vue';
import { defineComponent } from 'vue';
import type { RouteLocationNormalized } from 'vue-router';
function validate(to: RouteLocationNormalized) {
if (useUserStore().userInfo?.id !== Number(to.params.id)) {
return usePageStore().createTempErrorRoute({ type: PageErrorType.NO_PERMISSION }, to);
}
}
export default defineComponent({
beforeRouteEnter: validate,
beforeRouteUpdate: validate,
});
</script>

134
src/views/user/UserPage.vue Normal file
View File

@ -0,0 +1,134 @@
<template>
<el-main
v-loading="loading"
element-loading-text="正在加载"
class="page-main !flex justify-center"
>
<div
v-if="!loading"
class="max-lg-w-700px lg-w-950px flex flex-col"
:class="[{ 'gap-10px': userInfo }, !userInfo ? 'justify-center items-center' : '']"
>
<template v-if="userInfo">
<el-card body-class="h-150px flex gap-20px">
<el-avatar :size="80" :icon="userInfo.avatar ?? undefined"></el-avatar>
<div class="flex flex-col gap-5px">
<h4>{{ userInfo.name }}</h4>
<div class="text-3 color-gray">ID: {{ userInfo.id }}</div>
<div class="flex items-center gap-2">
<el-icon color="var(--el-color-primary)"><icon-cs-user-auth /></el-icon>
<div>权限组{{ userInfo.auth.name }}</div>
</div>
<div class="flex items-center gap-2">
<div class="w-4 h-4 flex justify-center items-center">
<el-icon size="2rem" :color="userInfo.club ? 'var(--el-color-primary)' : 'black'">
<icon-cs-club />
</el-icon>
</div>
<div>所属社团{{ userInfo.club?.name ?? '暂未加入社团哦' }}</div>
</div>
</div>
</el-card>
<div class="flex gap-10px">
<el-card class="aside w-200px">
<h5 class="mb-5px text-center">个人中心</h5>
<el-divider />
<el-menu class="menu" router :default-active="$route.fullPath">
<el-menu-item :index="`/user/${$route.params.id}/posts`">发帖记录</el-menu-item>
</el-menu>
<template v-if="userStore.isSelf(userInfo.id)">
<el-divider />
<el-menu class="menu" router :default-active="$route.fullPath">
<el-menu-item :index="`/user/${$route.params.id}/edit`">编辑信息</el-menu-item>
</el-menu>
</template>
</el-card>
<el-card class="flex-1" body-class="!p-15px">
<router-view />
</el-card>
</div>
</template>
<template v-else>
<div class="font-bold text-30px">无法获取用户信息</div>
<div class="flex gap-10px">
<el-button type="primary" @click="loadUserInfo">刷新</el-button>
<el-button type="primary" @click="$router.back()">返回上页</el-button>
</div>
</template>
</div>
</el-main>
</template>
<style lang="scss" scoped>
.page-main {
--el-main-padding: 30px 0;
}
.el-card {
transition: none;
}
.aside {
--el-card-padding: 15px 0;
:deep(.el-card__body) > :not(.el-divider) {
padding: 0 15px;
}
.el-menu-item {
@apply justify-center;
}
}
.el-divider {
margin: 10px 0;
}
.menu {
--el-menu-border-color: transparent;
--el-menu-item-height: 40px;
}
</style>
<script lang="ts" setup>
import { getUserInfo } from '@/api';
import { type UserInfo } from '@/schemas/response';
import { useMediaStore } from '@/stores/media';
import { useUserStore } from '@/stores/user';
import { ElMessage } from 'element-plus';
import { storeToRefs } from 'pinia';
import { computed, toRaw } from 'vue';
import { ref, watch } from 'vue';
import { useRoute } from 'vue-router';
const { lgLess, lgOnly } = storeToRefs(useMediaStore());
const route = useRoute();
const trailingSlashReg = /\/$/;
const editPath = computed(() => `${route.fullPath.replace(trailingSlashReg, '')}/edit`);
const userStore = useUserStore();
const loading = ref(true);
const userInfo = ref<UserInfo>();
async function loadUserInfo() {
loading.value = true;
userInfo.value = undefined;
const id = Number(route.params.id as string);
try {
if (isNaN(id)) {
ElMessage.error('参数错误');
return;
}
if (id === -1) {
ElMessage.error('用户不存在');
return;
}
let data: UserInfo;
if (userStore.userInfo?.id === id) {
data = userStore.userInfo;
} else {
const resp = await getUserInfo(true, id);
if (!resp) return;
if (!resp.data) {
ElMessage.error('用户不存在');
return;
}
({ data } = resp);
}
userInfo.value = data;
console.log(toRaw(userInfo.value));
} finally {
loading.value = false;
}
}
watch(() => route.params.id, loadUserInfo, { immediate: true });
</script>

View File

@ -0,0 +1 @@
<template>123123</template>