🎈 perf: 更新用户主页
This commit is contained in:
parent
656538fecc
commit
7e8caace31
@ -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" />
|
||||
|
@ -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';
|
||||
|
@ -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 |
9
src/assets/icons/UserAuth.svg
Normal file
9
src/assets/icons/UserAuth.svg
Normal 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 |
@ -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';
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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',
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './response';
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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]: '你需要登录才能访问此页面',
|
||||
|
@ -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 {
|
||||
|
@ -48,7 +48,7 @@
|
||||
? cell.isWhite
|
||||
? 'gobang-chessboard__cell--white'
|
||||
: 'gobang-chessboard__cell--black'
|
||||
: undefined,
|
||||
: '',
|
||||
,
|
||||
{
|
||||
'gobang-chessboard__cell--win-blink': blink?.[y][x],
|
||||
|
@ -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>
|
17
src/views/user/UserEditPage.vue
Normal file
17
src/views/user/UserEditPage.vue
Normal 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
134
src/views/user/UserPage.vue
Normal 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>
|
1
src/views/user/UserPosts.vue
Normal file
1
src/views/user/UserPosts.vue
Normal file
@ -0,0 +1 @@
|
||||
<template>123123</template>
|
Loading…
x
Reference in New Issue
Block a user